<commit_before>'use strict';

var Transitionable = require('famous-transitions').Transitionable;
var Geometry = require('famous-webgl-geometries');

/**
 * The Mesh class is responsible for providing the API for how
 * a RenderNode will interact with the WebGL API by adding
 * a set of commands to the renderer.
 *
 * @class Mesh
 * @constructor
 * @renderable
 * @param {LocalDispatch} dispatch LocalDispatch to be retrieved
 * @param {object} Options Optional params for configuring Mesh
 */
function Mesh (dispatch, options) {
    this.dispatch = dispatch;
    this.queue = [];
    this._id = dispatch.addRenderable(this);

    this._color;
    this._glossiness = new Transitionable(0);
    this._positionOffset = new Transitionable([0, 0, 0]);
    this._metallness = new Transitionable(0);
    this._normals = new Transitionable([0, 0, 0]);

    this._size = [];
    this._expressions = {};
    this._geometry;
    this._flatShading = 0;

    this.dispatch.onTransformChange(this._receiveTransformChange.bind(this));
    this.dispatch.onSizeChange(this._receiveSizeChange.bind(this));
    this.dispatch.onOpacityChange(this._receiveOpacityChange.bind(this));

    this._receiveTransformChange(this.dispatch.getContext()._transform);
    this._receiveSizeChange(this.dispatch.getContext()._size);
    this._receiveOpacityChange(this.dispatch.getContext()._opacity);

    if (options) this.setOptions(options);
}

/**
* Returns the definition of the Class: 'Mesh'
*
* @method toString
* @return {string} definition
*/
Mesh.toString = function toString() {
    return 'Mesh';
};

/**
 * Receives transform change updates from the scene graph.
 *
 * @private
 */
Mesh.prototype._receiveTransformChange = function _receiveTransformChange(transform) {
    this.dispatch.dirtyRenderable(this._id);
    this.queue.push('GL_UNIFORMS');
    this.queue.push('transform');
    this.queue.push(transform._matrix);
};

/**
 * Receives size change updates from the scene graph.
 *
 * @private
 */
Mesh.prototype._receiveSizeChange = function _receiveSizeChange(size) {
    var size = size.getTopDownSize();
    this.dispatch.dirtyRenderable(this._id);

    this._size[0] = size[0];
    this._size[1] = size[1];
    this._size[2] = size[2];

    this.queue.push('GL_UNIFORMS');
    this.queue.push('size');
    this.queue.push(size);
 };

/**
 * Receives opacity change updates from the scene graph.
 *
 * @private
 */
Mesh.prototype._receiveOpacityChange = function _receiveOpacityChange(opacity) {
    this.dispatch.dirtyRenderable(this._id);
    this.queue.push('GL_UNIFORMS');
    this.queue.push('opacity');
    this.queue.push(opacity.value);
};

/**
 * Returns the size of Mesh.
 *
 * @method getSize
 * @returns {array} Size Returns size
 */
Mesh.prototype.getSize = function getSize() {
    return this._size;
};

/**
 * Set the geometry of a mesh.
 *
 * @method setGeometry
 * @chainable
 *
 * @param {Geometry} geometry instance to be associated with the mesh
 * @param {Object} Options Various configurations for geometries.
 * @chainable
 */
Mesh.prototype.setGeometry = function setGeometry(geometry, options) {
    var i;
    var key;
    var buffers;
    var bufferIndex;

    if (typeof geometry === 'string') {
        if (!Geometry[geometry]) throw 'Invalid geometry: "' + geometry + '".';
        else geometry = new Geometry[geometry](options);
    }

    if (this._geometry !== geometry) {
        this.queue.push('GL_SET_GEOMETRY');
        this.queue.push(geometry.id);
        this.queue.push(geometry.spec.type);
        this.queue.push(geometry.spec.dynamic);

        this._geometry = geometry;
    }

    return this;
};

/**
 * Get the geometry of a mesh.
 *
 * @method getGeometry
 * @returns {Geometry} geometry Geometry of mesh
 */
Mesh.prototype.getGeometry = function getGeometry() {
    return this._geometry;
};

/**
* Returns boolean: if true, renderable is to be updated on next engine tick
*
* @private
* @method clean
* @returns {boolean} Boolean
*/
Mesh.prototype.clean = function clean() {
    var path = this.dispatch.getRenderPath();

    this.dispatch
        .sendDrawCommand('WITH')
        .sendDrawCommand(path)
        .sendDrawCommand('WEBGL');

    var bufferIndex;
    if (this._geometry) {
        i = this._geometry.spec.invalidations.length;
        while (i--) {
            bufferIndex = this._geometry.spec.invalidations.pop();
            this.dispatch.sendDrawCommand('GL_BUFFER_DATA');
            this.dispatch.sendDrawCommand(this._geometry.id);
            this.dispatch.sendDrawCommand(this._geometry.spec.bufferNames[i]);
            this.dispatch.sendDrawCommand(this._geometry.spec.bufferValues[i]);
            this.dispatch.sendDrawCommand(this._geometry.spec.bufferSpacings[i]);
        }
    }

    var baseColor = this._expressions.baseColor;
    var uniformKey;
    if (baseColor) {
        i = baseColor.invalidations.length;
        while (i--) {
            uniformKey = baseColor.invalidations.pop();
            this.dispatch.sendDrawCommand('GL_UNIFORMS');
            this.dispatch.sendDrawCommand(uniformKey);
            this.dispatch.sendDrawCommand(baseColor.uniforms[uniformKey]);
        }
    }

    var positionOffset = this._expressions.positionOffset;
    if (positionOffset) {
        i = positionOffset.invalidations.length;
        while (i--) {
            uniformKey = positionOffset.invalidations.pop();
            this.dispatch.sendDrawCommand('GL_UNIFORMS');
            this.dispatch.sendDrawCommand(uniformKey);
            this.dispatch.sendDrawCommand(positionOffset.uniforms[uniformKey]);
        }
    }

    var i = this.queue.length;
    while (i--) this.dispatch.sendDrawCommand(this.queue.shift());

    if (this._color && this._color.isActive()) {
        this.dispatch.sendDrawCommand('GL_UNIFORMS');
        this.dispatch.sendDrawCommand('baseColor');
        this.dispatch.sendDrawCommand(this._color.getNormalizedRGB());
        return true;
    }

    if (this._glossiness.isActive()) {
        this.dispatch.sendDrawCommand('GL_UNIFORMS');
        this.dispatch.sendDrawCommand('glossiness');
        this.dispatch.sendDrawCommand(this._glossiness.get());
        return true;
    }

    return this.queue.length;
};

/**
* Changes the color of Mesh, passing either a material expression or
* color using the 'Color' utility component.
*
* @method setBaseColor
* @param {Object|Color} Material, image, vec3, or Color instance
* @chainable
*/
Mesh.prototype.setBaseColor = function setBaseColor(color) {
    this.dispatch.dirtyRenderable(this._id);

    // If a material expression
    if (color._compile) {
        this.queue.push('MATERIAL_INPUT');
        this._expressions.baseColor = type;
        color = type._compile();
    }
    // A color component
    else {
        this.queue.push('GL_UNIFORMS');
        if (this._expressions.baseColor) this._expressions.baseColor = null;
        this._color = color;
        color = color.getNormalizedRGB();
    }

    this.queue.push('baseColor');
    this.queue.push(color);
    return this;
};

/**
 * Returns either the material expression or the color of Mesh.
 *
 * @method getBaseColor
 * @returns {MaterialExpress|Color}
 */
Mesh.prototype.getBaseColor = function getBaseColor(option) {
    return this._expressions.baseColor ||
           (this._color.getColor) ? this._color.getColor(option) : this._color;
};

/**
 * Change whether the Mesh is affected by light. Default is true.
 *
 * @method setFlatShading
 * @param {boolean} Boolean
 * @chainable
 */
Mesh.prototype.setFlatShading = function setFlatShading(bool) {
    this.dispatch.dirtyRenderable(this._id);
    this._flatShading = bool ? 1 : 0;
    this.queue.push('GL_UNIFORMS');
    this.queue.push('u_FlatShading');
    this.queue.push(this._flatShading);
    return this;
};

/**
 * Returns a boolean for whether Mesh is affected by light.
 *
 * @method getFlatShading
 * @returns {boolean} Boolean
 */
Mesh.prototype.getFlatShading = function getFlatShading() {
    return this._flatShading ? true : false;
};

/**
 * Defines a 3-element map which is used to provide significant physical
 * detail to the surface by perturbing the facing direction of each individual
 * pixel.
 *
 * @method normal
 * @chainable
 *
 * @param {Object|Array} Material, Image or vec3
 * @return {Element} current Mesh
 */
Mesh.prototype.setNormals = function setNormals(materialExpression) {
    this.dispatch.dirtyRenderable(this._id);
    if (materialExpression._compile) materialExpression = materialExpression._compile();
    this.queue.push(typeof materialExpression === 'number' ? 'UNIFORM_INPUT' : 'MATERIAL_INPUT');
    this.queue.push('normal');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns the Normals expression of Mesh (work in progress)
 *
 * @method getNormals
 * @returns The normals expression for Mesh
 */
Mesh.prototype.getNormals = function getNormals(materialExpression) {
    return null;
};

/**
 * Defines the glossiness of the mesh from either a material expression or a
 * scalar value
 *
 * @method setGlossiness
 * @param {MaterialExpression|Number}
 * @param {Object} Options Optional paramter to be passed with scalar
 * glossiness for tweening.
 * @chainable
 */
Mesh.prototype.setGlossiness = function setGlossiness(value) {
    this.dispatch.dirtyRenderable(this._id);

    if (materialExpression[0]._compile) {
        this.queue.push('MATERIAL_INPUT');
        this._expressions.glossiness = materialExpression[0];
        materialExpression = materialExpression[0]._compile();
    }
    else {
        this._glossiness.set(materialExpression[0], materialExpression[1]);
        this._expressions.glossiness = null;
        this.queue.push('GL_UNIFORMS');
        materialExpression = this._glossiness.get();
    }

    this.queue.push('glossiness');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns material expression or scalar value for glossiness.
 *
 * @method getGlossiness
 * @returns {MaterialExpress|Number}
 */
Mesh.prototype.getGlossiness = function getGlossiness(materialExpression) {
    return this._expressions.glossiness || this._glossiness.get();
};

/**
 * Defines 1 element map which describes the electrical conductivity of a
 * material.
 *
 * @method metallic
 * @chainable
 *
 * @param {Object} Material or Image
 * @return {Element} current Mesh
 */
Mesh.prototype.setMetallness = function setMetallness(materialExpression) {
    this.dispatch.dirtyRenderable(this._id);
    if (materialExpression._compile) materialExpression = materialExpression._compile();
    this.queue.push(typeof materialExpression === 'number' ? 'UNIFORM_INPUT' : 'MATERIAL_INPUT');
    this.queue.push('metallic');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns material expression for metallness.
 *
 * @method getMetallness
 * @returns {MaterialExpress}
 */
Mesh.prototype.getMetallness = function getMetallness() {
    return this._expressions.metallness || this._metallness.get();
};

/**
 * Defines 3 element map which displaces the position of each vertex in world
 * space.
 *
 * @method setPositionOffset
 * @chainable
 *
 * @param {Object} Material Expression
 * @chainable
 */
Mesh.prototype.setPositionOffset = function positionOffset(materialExpression) {
    this.dispatch.dirtyRenderable(this._id);
    var materialExpression = Array.prototype.concat.apply([], arguments);

    if (materialExpression[0]._compile) {
        this.queue.push('MATERIAL_INPUT');
        this._expressions.positionOffset = materialExpression[0];
        materialExpression = materialExpression[0]._compile();
    }
    else {
        this._positionOffset.set(materialExpression[0], materialExpression[1]);
        this._expressions.positionOffset = null;
        this.queue.push('GL_UNIFORMS');
        materialExpression = this._positionOffset.get();
    }

    this.queue.push('positionOffset');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns position offset.
 *
 * @method getPositionOffset
 * @returns {MaterialExpress|Number}
 */
Mesh.prototype.getPositionOffset = function getPositionOffset(materialExpression) {
    return this._expressions.positionOffset || this._positionOffset.get();
};

/**
 * Defines 3 element map which displaces the position of each vertex in world
 * space.
 *
 * @method setOptions
 * @chainable
 *
 * @param {Object} Options
 * @chainable
 */
Mesh.prototype.setOptions = function setOptions(options) {
    this.queue.push('GL_SET_DRAW_OPTIONS');
    this.queue.push(options);
    return this;
};

module.exports = Mesh;
<commit_msg>Fix: Update mesh base expression variable<commit_after>'use strict';

var Transitionable = require('famous-transitions').Transitionable;
var Geometry = require('famous-webgl-geometries');

/**
 * The Mesh class is responsible for providing the API for how
 * a RenderNode will interact with the WebGL API by adding
 * a set of commands to the renderer.
 *
 * @class Mesh
 * @constructor
 * @renderable
 * @param {LocalDispatch} dispatch LocalDispatch to be retrieved
 * @param {object} Options Optional params for configuring Mesh
 */
function Mesh (dispatch, options) {
    this.dispatch = dispatch;
    this.queue = [];
    this._id = dispatch.addRenderable(this);

    this._color;
    this._glossiness = new Transitionable(0);
    this._positionOffset = new Transitionable([0, 0, 0]);
    this._metallness = new Transitionable(0);
    this._normals = new Transitionable([0, 0, 0]);

    this._size = [];
    this._expressions = {};
    this._geometry;
    this._flatShading = 0;

    this.dispatch.onTransformChange(this._receiveTransformChange.bind(this));
    this.dispatch.onSizeChange(this._receiveSizeChange.bind(this));
    this.dispatch.onOpacityChange(this._receiveOpacityChange.bind(this));

    this._receiveTransformChange(this.dispatch.getContext()._transform);
    this._receiveSizeChange(this.dispatch.getContext()._size);
    this._receiveOpacityChange(this.dispatch.getContext()._opacity);

    if (options) this.setOptions(options);
}

/**
* Returns the definition of the Class: 'Mesh'
*
* @method toString
* @return {string} definition
*/
Mesh.toString = function toString() {
    return 'Mesh';
};

/**
 * Receives transform change updates from the scene graph.
 *
 * @private
 */
Mesh.prototype._receiveTransformChange = function _receiveTransformChange(transform) {
    this.dispatch.dirtyRenderable(this._id);
    this.queue.push('GL_UNIFORMS');
    this.queue.push('transform');
    this.queue.push(transform._matrix);
};

/**
 * Receives size change updates from the scene graph.
 *
 * @private
 */
Mesh.prototype._receiveSizeChange = function _receiveSizeChange(size) {
    var size = size.getTopDownSize();
    this.dispatch.dirtyRenderable(this._id);

    this._size[0] = size[0];
    this._size[1] = size[1];
    this._size[2] = size[2];

    this.queue.push('GL_UNIFORMS');
    this.queue.push('size');
    this.queue.push(size);
 };

/**
 * Receives opacity change updates from the scene graph.
 *
 * @private
 */
Mesh.prototype._receiveOpacityChange = function _receiveOpacityChange(opacity) {
    this.dispatch.dirtyRenderable(this._id);
    this.queue.push('GL_UNIFORMS');
    this.queue.push('opacity');
    this.queue.push(opacity.value);
};

/**
 * Returns the size of Mesh.
 *
 * @method getSize
 * @returns {array} Size Returns size
 */
Mesh.prototype.getSize = function getSize() {
    return this._size;
};

/**
 * Set the geometry of a mesh.
 *
 * @method setGeometry
 * @chainable
 *
 * @param {Geometry} geometry instance to be associated with the mesh
 * @param {Object} Options Various configurations for geometries.
 * @chainable
 */
Mesh.prototype.setGeometry = function setGeometry(geometry, options) {
    var i;
    var key;
    var buffers;
    var bufferIndex;

    if (typeof geometry === 'string') {
        if (!Geometry[geometry]) throw 'Invalid geometry: "' + geometry + '".';
        else geometry = new Geometry[geometry](options);
    }

    if (this._geometry !== geometry) {
        this.queue.push('GL_SET_GEOMETRY');
        this.queue.push(geometry.id);
        this.queue.push(geometry.spec.type);
        this.queue.push(geometry.spec.dynamic);

        this._geometry = geometry;
    }

    return this;
};

/**
 * Get the geometry of a mesh.
 *
 * @method getGeometry
 * @returns {Geometry} geometry Geometry of mesh
 */
Mesh.prototype.getGeometry = function getGeometry() {
    return this._geometry;
};

/**
* Returns boolean: if true, renderable is to be updated on next engine tick
*
* @private
* @method clean
* @returns {boolean} Boolean
*/
Mesh.prototype.clean = function clean() {
    var path = this.dispatch.getRenderPath();

    this.dispatch
        .sendDrawCommand('WITH')
        .sendDrawCommand(path)
        .sendDrawCommand('WEBGL');

    var bufferIndex;
    if (this._geometry) {
        i = this._geometry.spec.invalidations.length;
        while (i--) {
            bufferIndex = this._geometry.spec.invalidations.pop();
            this.dispatch.sendDrawCommand('GL_BUFFER_DATA');
            this.dispatch.sendDrawCommand(this._geometry.id);
            this.dispatch.sendDrawCommand(this._geometry.spec.bufferNames[i]);
            this.dispatch.sendDrawCommand(this._geometry.spec.bufferValues[i]);
            this.dispatch.sendDrawCommand(this._geometry.spec.bufferSpacings[i]);
        }
    }

    var baseColor = this._expressions.baseColor;
    var uniformKey;
    if (baseColor) {
        i = baseColor.invalidations.length;
        while (i--) {
            uniformKey = baseColor.invalidations.pop();
            this.dispatch.sendDrawCommand('GL_UNIFORMS');
            this.dispatch.sendDrawCommand(uniformKey);
            this.dispatch.sendDrawCommand(baseColor.uniforms[uniformKey]);
        }
    }

    var positionOffset = this._expressions.positionOffset;
    if (positionOffset) {
        i = positionOffset.invalidations.length;
        while (i--) {
            uniformKey = positionOffset.invalidations.pop();
            this.dispatch.sendDrawCommand('GL_UNIFORMS');
            this.dispatch.sendDrawCommand(uniformKey);
            this.dispatch.sendDrawCommand(positionOffset.uniforms[uniformKey]);
        }
    }

    var i = this.queue.length;
    while (i--) this.dispatch.sendDrawCommand(this.queue.shift());

    if (this._color && this._color.isActive()) {
        this.dispatch.sendDrawCommand('GL_UNIFORMS');
        this.dispatch.sendDrawCommand('baseColor');
        this.dispatch.sendDrawCommand(this._color.getNormalizedRGB());
        return true;
    }

    if (this._glossiness.isActive()) {
        this.dispatch.sendDrawCommand('GL_UNIFORMS');
        this.dispatch.sendDrawCommand('glossiness');
        this.dispatch.sendDrawCommand(this._glossiness.get());
        return true;
    }

    return this.queue.length;
};

/**
* Changes the color of Mesh, passing either a material expression or
* color using the 'Color' utility component.
*
* @method setBaseColor
* @param {Object|Color} Material, image, vec3, or Color instance
* @chainable
*/
Mesh.prototype.setBaseColor = function setBaseColor(color) {
    this.dispatch.dirtyRenderable(this._id);

    // If a material expression
    if (color._compile) {
        this.queue.push('MATERIAL_INPUT');
        this._expressions.baseColor = color;
        color = color._compile();
    }
    // A color component
    else {
        this.queue.push('GL_UNIFORMS');
        if (this._expressions.baseColor) this._expressions.baseColor = null;
        this._color = color;
        color = color.getNormalizedRGB();
    }

    this.queue.push('baseColor');
    this.queue.push(color);
    return this;
};

/**
 * Returns either the material expression or the color of Mesh.
 *
 * @method getBaseColor
 * @returns {MaterialExpress|Color}
 */
Mesh.prototype.getBaseColor = function getBaseColor(option) {
    return this._expressions.baseColor ||
           (this._color.getColor) ? this._color.getColor(option) : this._color;
};

/**
 * Change whether the Mesh is affected by light. Default is true.
 *
 * @method setFlatShading
 * @param {boolean} Boolean
 * @chainable
 */
Mesh.prototype.setFlatShading = function setFlatShading(bool) {
    this.dispatch.dirtyRenderable(this._id);
    this._flatShading = bool ? 1 : 0;
    this.queue.push('GL_UNIFORMS');
    this.queue.push('u_FlatShading');
    this.queue.push(this._flatShading);
    return this;
};

/**
 * Returns a boolean for whether Mesh is affected by light.
 *
 * @method getFlatShading
 * @returns {boolean} Boolean
 */
Mesh.prototype.getFlatShading = function getFlatShading() {
    return this._flatShading ? true : false;
};

/**
 * Defines a 3-element map which is used to provide significant physical
 * detail to the surface by perturbing the facing direction of each individual
 * pixel.
 *
 * @method normal
 * @chainable
 *
 * @param {Object|Array} Material, Image or vec3
 * @return {Element} current Mesh
 */
Mesh.prototype.setNormals = function setNormals(materialExpression) {
    this.dispatch.dirtyRenderable(this._id);
    if (materialExpression._compile) materialExpression = materialExpression._compile();
    this.queue.push(typeof materialExpression === 'number' ? 'UNIFORM_INPUT' : 'MATERIAL_INPUT');
    this.queue.push('normal');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns the Normals expression of Mesh (work in progress)
 *
 * @method getNormals
 * @returns The normals expression for Mesh
 */
Mesh.prototype.getNormals = function getNormals(materialExpression) {
    return null;
};

/**
 * Defines the glossiness of the mesh from either a material expression or a
 * scalar value
 *
 * @method setGlossiness
 * @param {MaterialExpression|Number}
 * @param {Object} Options Optional paramter to be passed with scalar
 * glossiness for tweening.
 * @chainable
 */
Mesh.prototype.setGlossiness = function setGlossiness(value) {
    this.dispatch.dirtyRenderable(this._id);

    if (materialExpression[0]._compile) {
        this.queue.push('MATERIAL_INPUT');
        this._expressions.glossiness = materialExpression[0];
        materialExpression = materialExpression[0]._compile();
    }
    else {
        this._glossiness.set(materialExpression[0], materialExpression[1]);
        this._expressions.glossiness = null;
        this.queue.push('GL_UNIFORMS');
        materialExpression = this._glossiness.get();
    }

    this.queue.push('glossiness');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns material expression or scalar value for glossiness.
 *
 * @method getGlossiness
 * @returns {MaterialExpress|Number}
 */
Mesh.prototype.getGlossiness = function getGlossiness(materialExpression) {
    return this._expressions.glossiness || this._glossiness.get();
};

/**
 * Defines 1 element map which describes the electrical conductivity of a
 * material.
 *
 * @method metallic
 * @chainable
 *
 * @param {Object} Material or Image
 * @return {Element} current Mesh
 */
Mesh.prototype.setMetallness = function setMetallness(materialExpression) {
    this.dispatch.dirtyRenderable(this._id);
    if (materialExpression._compile) materialExpression = materialExpression._compile();
    this.queue.push(typeof materialExpression === 'number' ? 'UNIFORM_INPUT' : 'MATERIAL_INPUT');
    this.queue.push('metallic');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns material expression for metallness.
 *
 * @method getMetallness
 * @returns {MaterialExpress}
 */
Mesh.prototype.getMetallness = function getMetallness() {
    return this._expressions.metallness || this._metallness.get();
};

/**
 * Defines 3 element map which displaces the position of each vertex in world
 * space.
 *
 * @method setPositionOffset
 * @chainable
 *
 * @param {Object} Material Expression
 * @chainable
 */
Mesh.prototype.setPositionOffset = function positionOffset(materialExpression) {
    this.dispatch.dirtyRenderable(this._id);
    var materialExpression = Array.prototype.concat.apply([], arguments);

    if (materialExpression[0]._compile) {
        this.queue.push('MATERIAL_INPUT');
        this._expressions.positionOffset = materialExpression[0];
        materialExpression = materialExpression[0]._compile();
    }
    else {
        this._positionOffset.set(materialExpression[0], materialExpression[1]);
        this._expressions.positionOffset = null;
        this.queue.push('GL_UNIFORMS');
        materialExpression = this._positionOffset.get();
    }

    this.queue.push('positionOffset');
    this.queue.push(materialExpression);
    return this;
};

/**
 * Returns position offset.
 *
 * @method getPositionOffset
 * @returns {MaterialExpress|Number}
 */
Mesh.prototype.getPositionOffset = function getPositionOffset(materialExpression) {
    return this._expressions.positionOffset || this._positionOffset.get();
};

/**
 * Defines 3 element map which displaces the position of each vertex in world
 * space.
 *
 * @method setOptions
 * @chainable
 *
 * @param {Object} Options
 * @chainable
 */
Mesh.prototype.setOptions = function setOptions(options) {
    this.queue.push('GL_SET_DRAW_OPTIONS');
    this.queue.push(options);
    return this;
};

module.exports = Mesh;
<|endoftext|>


<commit_before>/**

A constructor function for creating `Node` objects with ordered children
to be used as part of the composite design pattern.

Do not mutate the elements of the `childNodes` array directly.
Instead use the `appendChild`, `insertBefore`, `replaceChild`, and `removeChild`
methods to manage the children.

    var node = new hijos.Node();

@constructor

@extends hijos.Leaf

*/
hijos.Node = function() {
    hijos.Leaf.call(this);
    this.childNodes = [];
    this.firstChild = null;
    this.lastChild = null;
};

/**

@property hijos.Node.superConstructor

*/
hijos.Node.superConstructor = hijos.Leaf;

// Inherit from hijos.Leaf. Not all browsers have Object.create
// so write out the equivalent inline.
hijos.Node.prototype = (function() {
    function F() {}
    F.prototype = hijos.Leaf.prototype;
    return new F();
}());
hijos.Node.prototype.constructor = hijos.Node;

/**

The array of child objects.

@member hijos.Node.prototype.childNodes

@type {Array}

@readonly

*/

/**

The first child of this object. Null if this object has no children.

@member hijos.Node.prototype.firstChild

@type {hijos.Leaf}

@readonly

*/

/**

The last child of this object. Null if this object has no children.

@member hijos.Node.prototype.lastChild

@type {hijos.Leaf}

@readonly

*/

/**

Call before your application code looses its last reference to the object.
Generally this will be called for you by the destroy method of the containing
`Node` object unless this object is not contained by another `Node`.

@override

*/
hijos.Node.prototype.destroy = function() {
    // copy in case one of the destroy methods modifies this.childNodes
    var children = this.childNodes.slice(0);
    for (var i = 0, ilen = children.length; i < ilen; i++) {
        children[i].destroy();
    }
    hijos.Leaf.prototype.destroy.call(this);
    // Loosing references to relations may help garbage collection.
    this.childNodes = null;
    this.firstChild = null;
    this.lastChild = null;
};

/**

Does this `Node` have any children?

@return {boolean} `true` if this `Node` has children. Otherwise `false`.

*/
hijos.Node.prototype.hasChildNodes = function() {
    return this.childNodes.length > 0;
};

/**

Inserts `newChild` before `oldChild`. If `oldChild` is `null` then this is equivalent
to appending `newChild`. If `newChild` is a child of another `Node` then `newChild` is
removed from that other `Node` before appending to this `Node`.

    var parent = new hijos.Node();
    var child0 = new hijos.Leaf();
    parent.insertBefore(child0, null);
    var child1 = new hijos.Node();
    parent.insertBefore(child1, child0);

@param {Object} newChild The Leaf or Node object to insert.

@param {(Object|null)} [oldChild] The child object to insert before.

*/
hijos.Node.prototype.insertBefore = function(newChild, oldChild) {
    if (arguments.length < 2) {
        throw new Error('hijos.Node.prototype.insertBefore: not enough arguments.');
    }
    // Allow caller to be sloppy and send undefined instead of null.
    if (oldChild === undefined) {
        oldChild = null;
    }
    // Is newChild is already in correct position?
    if ((newChild === oldChild) || // inserting a node before itself
        (oldChild && (oldChild.previousSibling === newChild)) || // inserting newChild where it already is
        ((oldChild === null) && this.lastChild === newChild)) { // inserting child at end when it is already at the end
        return;
    }
    // do not allow the creation of a circular tree structure
    var node = this;
    while (node) {
        if (node === newChild) {
            throw new Error('hijos.Node.prototype.insertBefore: Node cannot be inserted at the specified point in the hierarchy.');
        }
        node = node.parentNode;
    }
    // remove from previous composite
    var parent = newChild.parentNode;
    if (parent) {
        parent.removeChild(newChild);
    }
    // continue with insertion
    var children = this.childNodes;
    // find index for newChild
    var indexForNewChild;
    if (oldChild === null) {
        // want to append to end of children
        indexForNewChild = children.length;
    }
    else {
        for (var i = 0, ilen = children.length; i < ilen; i++) {
            if (children[i] === oldChild) {
                indexForNewChild = i;
                break;
            }
        }
        if (typeof indexForNewChild !== 'number') {
            throw new Error('hijos.Node.prototype.insertBefore: Node was not found.');
        }
    }
    // add to this composite
    children.splice(indexForNewChild, 0, newChild);
    this.firstChild = children[0];
    this.lastChild = children[children.length - 1];
    newChild.parentNode = this;
    var previousSibling = newChild.previousSibling = (children[indexForNewChild - 1] || null);
    if (previousSibling) {
        previousSibling.nextSibling = newChild;
    }
    var nextSibling = newChild.nextSibling = (children[indexForNewChild + 1] || null);
    if (nextSibling) {
        nextSibling.previousSibling = newChild;
    }
};

/**

Adds `newChild` as the last child of this `Node`. If `newChild` is a child of
another `Node` then `newChild` is removed from that other `Node` before appending
to this `Node`.

    var parent = new hijos.Node();
    var child = new hijos.Leaf();
    parent.appendChild(child);
    var child = new hijos.Node();
    parent.appendChild(child);

@param {Object} newChild The Leaf or Node object to append.

*/
hijos.Node.prototype.appendChild = function(newChild) {
    if (arguments.length < 1) {
        throw new Error('hijos.Node.prototype.appendChild: not enough arguments.');
    }
    this.insertBefore(newChild, null);
};

/**

Replaces `oldChild` with `newChild`. If `newChild` is a child of another `Node`
then `newChild` is removed from that other `Node` before appending to this `Node`.

    var parent = new hijos.Node();
    var child0 = new hijos.Leaf();
    parent.appendChild(child0);
    var child1 = new hijos.Node();
    parent.replaceChild(child1, child0);

@param {Object} newChild The Leaf or Node object to insert.

@param {Object} oldChild The child object to remove/replace.

*/
hijos.Node.prototype.replaceChild = function(newChild, oldChild) {
    if (arguments.length < 2) {
        throw new Error('hijos.Node.prototype.replaceChild: not enough arguments.');
    }
    if (!oldChild) {
        throw new Error('hijos.Node.prototype.replaceChild: oldChild must not be falsy.');
    }
    // child is already in correct position and
    // do not want removeChild to be called below
    if (newChild === oldChild) {
        return;
    }
    this.insertBefore(newChild, oldChild);
    this.removeChild(oldChild);
};

/**

Removes `oldChild`.

    var parent = new hijos.Node();
    var child = new hijos.Leaf();
    parent.appendChild(child);
    parent.removeChild(child);

@param {Object} oldChild The child object to remove.

*/
hijos.Node.prototype.removeChild = function(oldChild) {
    if (arguments.length < 1) {
        throw new Error('hijos.Node.prototype.removeChild: not enough arguments.');
    }
    var children = this.childNodes;
    for (var i = 0, ilen = children.length; i < ilen; i++) {
        if (children[i] === oldChild) {
            var previousSibling = children[i - 1];
            if (previousSibling) {
                previousSibling.nextSibling = oldChild.nextSibling;
            }
            var nextSibling = children[i + 1];
            if (nextSibling) {
                nextSibling.previousSibling = oldChild.previousSibling;
            }
            oldChild.parentNode = null;
            oldChild.previousSibling = null;
            oldChild.nextSibling = null;
            children.splice(i, 1);
            this.firstChild = children[0] || null;
            this.lastChild = children[children.length - 1] || null;
            return; // stop looking
        }
    }
    throw new Error('hijos.Node.prototype.removeChild: node not found.');
};

// insure prototype object is initialized correctly
hijos.Node.call(hijos.Node.prototype);

/**

Mixes in the Node methods into any object.

Example 1

    app.MyView = function() {
        hijos.Node.call(this);
    };
    hijos.Node.mixin(app.MyView.prototype);

Example 2

    var obj = {};
    hijos.Node.mixin(obj);

@param {Object} obj The object to become a `Node`.

*/
hijos.Node.mixin = function(obj) {
    for (var p in hijos.Node.prototype) {
        if (Object.prototype.hasOwnProperty.call(hijos.Node.prototype, p) &&
            (typeof hijos.Node.prototype[p] === 'function')) {
            obj[p] = hijos.Node.prototype[p];
        }
    }
    hijos.Node.call(obj);
};
<commit_msg>a stylistic change so that the parent class of hijos.Node is only expressed once in the code<commit_after>/**

A constructor function for creating `Node` objects with ordered children
to be used as part of the composite design pattern.

Do not mutate the elements of the `childNodes` array directly.
Instead use the `appendChild`, `insertBefore`, `replaceChild`, and `removeChild`
methods to manage the children.

    var node = new hijos.Node();

@constructor

@extends hijos.Leaf

*/
hijos.Node = function() {
    hijos.Node.superConstructor.call(this);
    this.childNodes = [];
    this.firstChild = null;
    this.lastChild = null;
};

/**

@property hijos.Node.superConstructor

*/
hijos.Node.superConstructor = hijos.Leaf;

// Inherit from hijos.Leaf. Not all browsers have Object.create
// so write out the equivalent inline.
hijos.Node.prototype = (function() {
    function F() {}
    F.prototype = hijos.Node.superConstructor.prototype;
    return new F();
}());
hijos.Node.prototype.constructor = hijos.Node;

/**

The array of child objects.

@member hijos.Node.prototype.childNodes

@type {Array}

@readonly

*/

/**

The first child of this object. Null if this object has no children.

@member hijos.Node.prototype.firstChild

@type {hijos.Leaf}

@readonly

*/

/**

The last child of this object. Null if this object has no children.

@member hijos.Node.prototype.lastChild

@type {hijos.Leaf}

@readonly

*/

/**

Call before your application code looses its last reference to the object.
Generally this will be called for you by the destroy method of the containing
`Node` object unless this object is not contained by another `Node`.

@override

*/
hijos.Node.prototype.destroy = function() {
    // copy in case one of the destroy methods modifies this.childNodes
    var children = this.childNodes.slice(0);
    for (var i = 0, ilen = children.length; i < ilen; i++) {
        children[i].destroy();
    }
    hijos.Node.superConstructor.prototype.destroy.call(this);
    // Loosing references to relations may help garbage collection.
    this.childNodes = null;
    this.firstChild = null;
    this.lastChild = null;
};

/**

Does this `Node` have any children?

@return {boolean} `true` if this `Node` has children. Otherwise `false`.

*/
hijos.Node.prototype.hasChildNodes = function() {
    return this.childNodes.length > 0;
};

/**

Inserts `newChild` before `oldChild`. If `oldChild` is `null` then this is equivalent
to appending `newChild`. If `newChild` is a child of another `Node` then `newChild` is
removed from that other `Node` before appending to this `Node`.

    var parent = new hijos.Node();
    var child0 = new hijos.Leaf();
    parent.insertBefore(child0, null);
    var child1 = new hijos.Node();
    parent.insertBefore(child1, child0);

@param {Object} newChild The Leaf or Node object to insert.

@param {(Object|null)} [oldChild] The child object to insert before.

*/
hijos.Node.prototype.insertBefore = function(newChild, oldChild) {
    if (arguments.length < 2) {
        throw new Error('hijos.Node.prototype.insertBefore: not enough arguments.');
    }
    // Allow caller to be sloppy and send undefined instead of null.
    if (oldChild === undefined) {
        oldChild = null;
    }
    // Is newChild is already in correct position?
    if ((newChild === oldChild) || // inserting a node before itself
        (oldChild && (oldChild.previousSibling === newChild)) || // inserting newChild where it already is
        ((oldChild === null) && this.lastChild === newChild)) { // inserting child at end when it is already at the end
        return;
    }
    // do not allow the creation of a circular tree structure
    var node = this;
    while (node) {
        if (node === newChild) {
            throw new Error('hijos.Node.prototype.insertBefore: Node cannot be inserted at the specified point in the hierarchy.');
        }
        node = node.parentNode;
    }
    // remove from previous composite
    var parent = newChild.parentNode;
    if (parent) {
        parent.removeChild(newChild);
    }
    // continue with insertion
    var children = this.childNodes;
    // find index for newChild
    var indexForNewChild;
    if (oldChild === null) {
        // want to append to end of children
        indexForNewChild = children.length;
    }
    else {
        for (var i = 0, ilen = children.length; i < ilen; i++) {
            if (children[i] === oldChild) {
                indexForNewChild = i;
                break;
            }
        }
        if (typeof indexForNewChild !== 'number') {
            throw new Error('hijos.Node.prototype.insertBefore: Node was not found.');
        }
    }
    // add to this composite
    children.splice(indexForNewChild, 0, newChild);
    this.firstChild = children[0];
    this.lastChild = children[children.length - 1];
    newChild.parentNode = this;
    var previousSibling = newChild.previousSibling = (children[indexForNewChild - 1] || null);
    if (previousSibling) {
        previousSibling.nextSibling = newChild;
    }
    var nextSibling = newChild.nextSibling = (children[indexForNewChild + 1] || null);
    if (nextSibling) {
        nextSibling.previousSibling = newChild;
    }
};

/**

Adds `newChild` as the last child of this `Node`. If `newChild` is a child of
another `Node` then `newChild` is removed from that other `Node` before appending
to this `Node`.

    var parent = new hijos.Node();
    var child = new hijos.Leaf();
    parent.appendChild(child);
    var child = new hijos.Node();
    parent.appendChild(child);

@param {Object} newChild The Leaf or Node object to append.

*/
hijos.Node.prototype.appendChild = function(newChild) {
    if (arguments.length < 1) {
        throw new Error('hijos.Node.prototype.appendChild: not enough arguments.');
    }
    this.insertBefore(newChild, null);
};

/**

Replaces `oldChild` with `newChild`. If `newChild` is a child of another `Node`
then `newChild` is removed from that other `Node` before appending to this `Node`.

    var parent = new hijos.Node();
    var child0 = new hijos.Leaf();
    parent.appendChild(child0);
    var child1 = new hijos.Node();
    parent.replaceChild(child1, child0);

@param {Object} newChild The Leaf or Node object to insert.

@param {Object} oldChild The child object to remove/replace.

*/
hijos.Node.prototype.replaceChild = function(newChild, oldChild) {
    if (arguments.length < 2) {
        throw new Error('hijos.Node.prototype.replaceChild: not enough arguments.');
    }
    if (!oldChild) {
        throw new Error('hijos.Node.prototype.replaceChild: oldChild must not be falsy.');
    }
    // child is already in correct position and
    // do not want removeChild to be called below
    if (newChild === oldChild) {
        return;
    }
    this.insertBefore(newChild, oldChild);
    this.removeChild(oldChild);
};

/**

Removes `oldChild`.

    var parent = new hijos.Node();
    var child = new hijos.Leaf();
    parent.appendChild(child);
    parent.removeChild(child);

@param {Object} oldChild The child object to remove.

*/
hijos.Node.prototype.removeChild = function(oldChild) {
    if (arguments.length < 1) {
        throw new Error('hijos.Node.prototype.removeChild: not enough arguments.');
    }
    var children = this.childNodes;
    for (var i = 0, ilen = children.length; i < ilen; i++) {
        if (children[i] === oldChild) {
            var previousSibling = children[i - 1];
            if (previousSibling) {
                previousSibling.nextSibling = oldChild.nextSibling;
            }
            var nextSibling = children[i + 1];
            if (nextSibling) {
                nextSibling.previousSibling = oldChild.previousSibling;
            }
            oldChild.parentNode = null;
            oldChild.previousSibling = null;
            oldChild.nextSibling = null;
            children.splice(i, 1);
            this.firstChild = children[0] || null;
            this.lastChild = children[children.length - 1] || null;
            return; // stop looking
        }
    }
    throw new Error('hijos.Node.prototype.removeChild: node not found.');
};

// insure prototype object is initialized correctly
hijos.Node.call(hijos.Node.prototype);

/**

Mixes in the Node methods into any object.

Example 1

    app.MyView = function() {
        hijos.Node.call(this);
    };
    hijos.Node.mixin(app.MyView.prototype);

Example 2

    var obj = {};
    hijos.Node.mixin(obj);

@param {Object} obj The object to become a `Node`.

*/
hijos.Node.mixin = function(obj) {
    for (var p in hijos.Node.prototype) {
        if (Object.prototype.hasOwnProperty.call(hijos.Node.prototype, p) &&
            (typeof hijos.Node.prototype[p] === 'function')) {
            obj[p] = hijos.Node.prototype[p];
        }
    }
    hijos.Node.call(obj);
};
<|endoftext|>


<commit_before>(function () {
    'use strict';

    angular
        .module('dpMap')
        .factory('highlight', highlightFactory);

    highlightFactory.$inject = [
        'L',
        '$rootScope',
        'crsService',
        'ICON_CONFIG',
        'geojson',
        'crsConverter',
        'mapConfig',
        'panning',
        'store',
        'ACTIONS',
        'clusteredMarkersConfig'
    ];

    /* eslint-disable max-params */
    function highlightFactory (
    /* eslint-enable max-params */
        L,
        $rootScope,
        crsService,
        ICON_CONFIG,
        geojson,
        crsConverter,
        mapConfig,
        panning,
        store,
        ACTIONS,
        clusteredMarkersConfig) {
        const layers = {};
        let clusteredLayer;

        return {
            initialize,
            addMarker,
            removeMarker,
            setCluster,
            clearCluster
        };

        function initialize () {
            L.Icon.Default.imagePath = 'assets';
        }

        /**
         * @param {Object} leafletMap - A Leaflet map instance
         * @param {Object} item - An object with the following properties:
         *  - id: in case of a marker this needs to be a mapping to a key of ICON_CONFIG
         *  - geometry: GeoJSON using RD coordinates
         */
        function addMarker (leafletMap, item) {
            item.geometry.crs = crsService.getRdObject();
            const layer = L.Proj.geoJson(item.geometry, {
                style: function () {
                    return {
                        color: 'red',
                        fillColor: 'red',
                        weight: 2,
                        opacity: 1.6,
                        fillOpacity: 0.2
                    };
                },
                pointToLayer: function (feature, latLng) {
                    const icon = L.icon(ICON_CONFIG[item.id]),
                        rotationAngle = item.orientation || 0;

                    return L.marker(latLng, {
                        icon: icon,
                        rotationAngle: rotationAngle
                    });
                }
            });

            layers[item.id] = layer;

            if (item.useAutoFocus) {
                zoomToLayer(leafletMap, layer, item.geometry);
            }

            leafletMap.addLayer(layer);
        }

        function removeMarker (leafletMap, item) {
            leafletMap.removeLayer(layers[item.id]);
        }

        function setCluster (leafletMap, markers) {
            clusteredLayer = L.markerClusterGroup(clusteredMarkersConfig);

            markers.forEach(marker => {
                clusteredLayer.addLayer(
                    L.marker(marker, {
                        icon: L.icon(ICON_CONFIG.detail)
                    })
                );
            });

            if (!leafletMap.getBounds().contains(clusteredLayer.getBounds())) {
                zoomToLayer(leafletMap, clusteredLayer);
            }

            leafletMap.addLayer(clusteredLayer);
        }

        function clearCluster (leafletMap) {
            if (angular.isDefined(clusteredLayer)) {
                leafletMap.removeLayer(clusteredLayer);
            }
        }

        function zoomToLayer (leafletMap, layer, geometry) {
            if (!store.getState().map.highlight) {
                store.dispatch({
                    type: ACTIONS.MAP_HIGHLIGHT,
                    payload: true
                });
                return;
            }

            let location,
                zoomLevel;

            const bounds = layer.getBounds();
            zoomLevel = leafletMap.getBoundsZoom(bounds);

            if (!isNaN(zoomLevel)) {
                // A valid zoom level has been determined
                leafletMap.fitBounds(bounds, {
                    animate: false
                });

                location = panning.getCurrentLocation(leafletMap);
            } else if (angular.isDefined(geometry)) {
                // Set the location and zoomLevel manually
                location = crsConverter.rdToWgs84(geojson.getCenter(geometry));
                zoomLevel = Math.max(leafletMap.getZoom(), mapConfig.DEFAULT_ZOOM_HIGHLIGHT);
            }

            // this will trigger a re rendering of map layers after zooming for firefox
            $rootScope.$applyAsync(() => {
                leafletMap.eachLayer(wmsLayer => {
                    if (wmsLayer.options && wmsLayer.options.layers) {
                        const currentOpacity = L.DomUtil.getStyle(wmsLayer.getElement(), 'opacity');
                        wmsLayer.removeFrom(leafletMap);
                        wmsLayer.addTo(leafletMap);
                        wmsLayer.setOpacity(currentOpacity);
                    }
                });
            });

            store.dispatch({
                type: ACTIONS.MAP_ZOOM,
                payload: {
                    viewCenter: location,
                    zoom: zoomLevel
                }
            });
        }
    }
})();
<commit_msg>added alt text to marker image on map DP-4963<commit_after>(function () {
    'use strict';

    angular
        .module('dpMap')
        .factory('highlight', highlightFactory);

    highlightFactory.$inject = [
        'L',
        '$rootScope',
        'crsService',
        'ICON_CONFIG',
        'geojson',
        'crsConverter',
        'mapConfig',
        'panning',
        'store',
        'ACTIONS',
        'clusteredMarkersConfig'
    ];

    /* eslint-disable max-params */
    function highlightFactory (
    /* eslint-enable max-params */
        L,
        $rootScope,
        crsService,
        ICON_CONFIG,
        geojson,
        crsConverter,
        mapConfig,
        panning,
        store,
        ACTIONS,
        clusteredMarkersConfig) {
        const layers = {};
        let clusteredLayer;

        return {
            initialize,
            addMarker,
            removeMarker,
            setCluster,
            clearCluster
        };

        function initialize () {
            L.Icon.Default.imagePath = 'assets';
        }

        /**
         * @param {Object} leafletMap - A Leaflet map instance
         * @param {Object} item - An object with the following properties:
         *  - id: in case of a marker this needs to be a mapping to a key of ICON_CONFIG
         *  - geometry: GeoJSON using RD coordinates
         */
        function addMarker (leafletMap, item) {
            item.geometry.crs = crsService.getRdObject();
            const layer = L.Proj.geoJson(item.geometry, {
                style: function () {
                    return {
                        color: 'red',
                        fillColor: 'red',
                        weight: 2,
                        opacity: 1.6,
                        fillOpacity: 0.2
                    };
                },
                pointToLayer: function (feature, latLng) {
                    const icon = L.icon(ICON_CONFIG[item.id]),
                        rotationAngle = item.orientation || 0;

                    return L.marker(latLng, {
                        icon: icon,
                        rotationAngle: rotationAngle,
                        alt: 'Marker op de kaart'
                    });
                }
            });

            layers[item.id] = layer;

            if (item.useAutoFocus) {
                zoomToLayer(leafletMap, layer, item.geometry);
            }

            leafletMap.addLayer(layer);
        }

        function removeMarker (leafletMap, item) {
            leafletMap.removeLayer(layers[item.id]);
        }

        function setCluster (leafletMap, markers) {
            clusteredLayer = L.markerClusterGroup(clusteredMarkersConfig);

            markers.forEach(marker => {
                clusteredLayer.addLayer(
                    L.marker(marker, {
                        icon: L.icon(ICON_CONFIG.detail)
                    })
                );
            });

            if (!leafletMap.getBounds().contains(clusteredLayer.getBounds())) {
                zoomToLayer(leafletMap, clusteredLayer);
            }

            leafletMap.addLayer(clusteredLayer);
        }

        function clearCluster (leafletMap) {
            if (angular.isDefined(clusteredLayer)) {
                leafletMap.removeLayer(clusteredLayer);
            }
        }

        function zoomToLayer (leafletMap, layer, geometry) {
            if (!store.getState().map.highlight) {
                store.dispatch({
                    type: ACTIONS.MAP_HIGHLIGHT,
                    payload: true
                });
                return;
            }

            let location,
                zoomLevel;

            const bounds = layer.getBounds();
            zoomLevel = leafletMap.getBoundsZoom(bounds);

            if (!isNaN(zoomLevel)) {
                // A valid zoom level has been determined
                leafletMap.fitBounds(bounds, {
                    animate: false
                });

                location = panning.getCurrentLocation(leafletMap);
            } else if (angular.isDefined(geometry)) {
                // Set the location and zoomLevel manually
                location = crsConverter.rdToWgs84(geojson.getCenter(geometry));
                zoomLevel = Math.max(leafletMap.getZoom(), mapConfig.DEFAULT_ZOOM_HIGHLIGHT);
            }

            // this will trigger a re rendering of map layers after zooming for firefox
            $rootScope.$applyAsync(() => {
                leafletMap.eachLayer(wmsLayer => {
                    if (wmsLayer.options && wmsLayer.options.layers) {
                        const currentOpacity = L.DomUtil.getStyle(wmsLayer.getElement(), 'opacity');
                        wmsLayer.removeFrom(leafletMap);
                        wmsLayer.addTo(leafletMap);
                        wmsLayer.setOpacity(currentOpacity);
                    }
                });
            });

            store.dispatch({
                type: ACTIONS.MAP_ZOOM,
                payload: {
                    viewCenter: location,
                    zoom: zoomLevel
                }
            });
        }
    }
})();
<|endoftext|>


<commit_before>(function()
{
    'use strict';

    angular
        .module('lumx.search-filter')
        .filter('lxSearchHighlight', lxSearchHighlight)
        .directive('lxSearchFilter', lxSearchFilter);

    lxSearchHighlight.$inject = ['$sce'];

    function lxSearchHighlight($sce)
    {
        function escapeRegexp(queryToEscape)
        {
            return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
        }

        return function (matchItem, query, icon)
        {
            var string = '';

            if (icon)
            {
                string += '';
            }

            string += query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem;

            return $sce.trustAsHtml(string);
        };
    }

    function lxSearchFilter()
    {
        return {
            restrict: 'E',
            templateUrl: 'search-filter.html',
            scope:
            {
                autocomplete: '&?lxAutocomplete',
                closed: '=?lxClosed',
                color: '@?lxColor',
                icon: '@?lxIcon',
                searchOnFocus: '=?lxSearchOnFocus',
                width: '@?lxWidth'
            },
            link: link,
            controller: LxSearchFilterController,
            controllerAs: 'lxSearchFilter',
            bindToController: true,
            replace: true,
            transclude: true
        };

        function link(scope, element, attrs, ctrl, transclude)
        {
            var input;

            attrs.$observe('lxWidth', function(newWidth)
            {
                if (angular.isDefined(scope.lxSearchFilter.closed) && scope.lxSearchFilter.closed)
                {
                    element.find('.search-filter__container').css('width', newWidth);
                }
            });

            transclude(function()
            {
                input = element.find('input');

                ctrl.setInput(input);
                ctrl.setModel(input.data('$ngModelController'));

                input.on('focus', ctrl.focusInput);
                input.on('blur', ctrl.blurInput);
                input.on('keydown', ctrl.keyEvent);
            });

            scope.$on('$destroy', function()
            {
                input.off();
            });
        }
    }

    LxSearchFilterController.$inject = ['$element', '$scope', 'LxDropdownService', 'LxNotificationService', 'LxUtils'];

    function LxSearchFilterController($element, $scope, LxDropdownService, LxNotificationService, LxUtils)
    {
        var lxSearchFilter = this;
        var input;
        var itemSelected = false;

        lxSearchFilter.blurInput = blurInput;
        lxSearchFilter.clearInput = clearInput;
        lxSearchFilter.focusInput = focusInput;
        lxSearchFilter.getClass = getClass;
        lxSearchFilter.keyEvent = keyEvent;
        lxSearchFilter.openInput = openInput;
        lxSearchFilter.selectItem = selectItem;
        lxSearchFilter.setInput = setInput;
        lxSearchFilter.setModel = setModel;

        lxSearchFilter.activeChoiceIndex = -1;
        lxSearchFilter.color = angular.isDefined(lxSearchFilter.color) ? lxSearchFilter.color : 'black';
        lxSearchFilter.dropdownId = LxUtils.generateUUID();

        ////////////

        function blurInput()
        {
            if (angular.isDefined(lxSearchFilter.closed) && lxSearchFilter.closed && !input.val())
            {
                $element.velocity(
                {
                    width: 40
                },
                {
                    duration: 400,
                    easing: 'easeOutQuint',
                    queue: false
                });
            }
        }

        function clearInput()
        {
            lxSearchFilter.modelController.$setViewValue(undefined);
            lxSearchFilter.modelController.$render();

            input.focus();
        }

        function focusInput()
        {
            if (!lxSearchFilter.searchOnFocus)
            {
                return;
            }

            updateAutocomplete(lxSearchFilter.modelController.$viewValue);
        }

        function getClass()
        {
            var searchFilterClass = [];

            if (angular.isUndefined(lxSearchFilter.closed) || !lxSearchFilter.closed)
            {
                searchFilterClass.push('search-filter--opened-mode');
            }

            if (angular.isDefined(lxSearchFilter.closed) && lxSearchFilter.closed)
            {
                searchFilterClass.push('search-filter--closed-mode');
            }

            if (input.val())
            {
                searchFilterClass.push('search-filter--has-clear-button');
            }

            if (angular.isDefined(lxSearchFilter.color))
            {
                searchFilterClass.push('search-filter--' + lxSearchFilter.color);
            }

            if (angular.isFunction(lxSearchFilter.autocomplete))
            {
                searchFilterClass.push('search-filter--autocomplete');
            }

            if (LxDropdownService.isOpen(lxSearchFilter.dropdownId))
            {
                searchFilterClass.push('search-filter--is-open');
            }

            return searchFilterClass;
        }

        function keyEvent(_event)
        {
            if (!angular.isFunction(lxSearchFilter.autocomplete))
            {
                return;
            }

            if (!LxDropdownService.isOpen(lxSearchFilter.dropdownId))
            {
                lxSearchFilter.activeChoiceIndex = -1;
            }

            switch (_event.keyCode) {
                case 13:
                    keySelect();
                    _event.preventDefault();
                    break;

                case 38:
                    keyUp();
                    _event.preventDefault();
                    break;

                case 40:
                    keyDown();
                    _event.preventDefault();
                    break;
            }

            $scope.$apply();
        }

        function keyDown()
        {
            if (lxSearchFilter.autocompleteList.length)
            {
                lxSearchFilter.activeChoiceIndex += 1;

                if (lxSearchFilter.activeChoiceIndex >= lxSearchFilter.autocompleteList.length)
                {
                    lxSearchFilter.activeChoiceIndex = 0;
                }
            }
        }

        function keySelect()
        {
            itemSelected = true;

            LxDropdownService.close(lxSearchFilter.dropdownId);

            lxSearchFilter.modelController.$setViewValue(lxSearchFilter.autocompleteList[lxSearchFilter.activeChoiceIndex]);
            lxSearchFilter.modelController.$render();
        }

        function keyUp()
        {
            if (lxSearchFilter.autocompleteList.length)
            {
                lxSearchFilter.activeChoiceIndex -= 1;

                if (lxSearchFilter.activeChoiceIndex < 0)
                {
                    lxSearchFilter.activeChoiceIndex = lxSearchFilter.autocompleteList.length - 1;
                }
            }
        }

        function openInput()
        {
            if (angular.isDefined(lxSearchFilter.closed) && lxSearchFilter.closed)
            {
                $element.velocity(
                {
                    width: angular.isDefined(lxSearchFilter.width) ? parseInt(lxSearchFilter.width) : 240
                },
                {
                    duration: 400,
                    easing: 'easeOutQuint',
                    queue: false,
                    complete: function()
                    {
                        input.focus();
                    }
                });
            }
            else
            {
                input.focus();
            }
        }

        function selectItem(_item)
        {
            itemSelected = true;

            lxSearchFilter.modelController.$setViewValue(_item);
            lxSearchFilter.modelController.$render();
        }

        function setInput(_input)
        {
            input = _input;
        }

        function setModel(_modelController)
        {
            lxSearchFilter.modelController = _modelController;

            if (angular.isFunction(lxSearchFilter.autocomplete))
            {
                if (angular.isDefined(lxSearchFilter.modelController.$overrideModelOptions))
                {
                    lxSearchFilter.modelController.$overrideModelOptions({ debounce: { 'default': 500 } });
                }
                else
                {
                    lxSearchFilter.modelController.$options = lxSearchFilter.modelController.$options || {};
                    lxSearchFilter.modelController.$options.updateOnDefault = true;
                    lxSearchFilter.modelController.$options.debounce = { 'default': 500 };
                }

                lxSearchFilter.modelController.$parsers.push(updateAutocomplete);
            }
        }

        function updateAutocomplete(_newValue)
        {
            if ((_newValue || (!_newValue && lxSearchFilter.searchOnFocus)) && !itemSelected && !lxSearchFilter.isLoading)
            {
                lxSearchFilter.isLoading = true;

                var promise = lxSearchFilter.autocomplete({ newValue: _newValue });

                promise.then(function(autocompleteList)
                {
                    lxSearchFilter.autocompleteList = autocompleteList;

                    if (lxSearchFilter.autocompleteList.length)
                    {
                        LxDropdownService.open(lxSearchFilter.dropdownId, $element);
                    }
                    else
                    {
                        LxDropdownService.close(lxSearchFilter.dropdownId);
                    }
                }).catch(function(error)
                {
                    LxNotificationService.error(error);
                }).finally(function()
                {
                    lxSearchFilter.isLoading = false;
                });

            } else {
                LxDropdownService.close(lxSearchFilter.dropdownId);
            }

            itemSelected = false;
        }
    }
})();<commit_msg>fix(search-filter):  functions expect a return value<commit_after>(function()
{
    'use strict';

    angular
        .module('lumx.search-filter')
        .filter('lxSearchHighlight', lxSearchHighlight)
        .directive('lxSearchFilter', lxSearchFilter);

    lxSearchHighlight.$inject = ['$sce'];

    function lxSearchHighlight($sce)
    {
        function escapeRegexp(queryToEscape)
        {
            return queryToEscape.replace(/([.?*+^$[\]\\(){}|-])/g, '\\$1');
        }

        return function (matchItem, query, icon)
        {
            var string = '';

            if (icon)
            {
                string += '';
            }

            string += query && matchItem ? matchItem.replace(new RegExp(escapeRegexp(query), 'gi'), '$&') : matchItem;

            return $sce.trustAsHtml(string);
        };
    }

    function lxSearchFilter()
    {
        return {
            restrict: 'E',
            templateUrl: 'search-filter.html',
            scope:
            {
                autocomplete: '&?lxAutocomplete',
                closed: '=?lxClosed',
                color: '@?lxColor',
                icon: '@?lxIcon',
                searchOnFocus: '=?lxSearchOnFocus',
                width: '@?lxWidth'
            },
            link: link,
            controller: LxSearchFilterController,
            controllerAs: 'lxSearchFilter',
            bindToController: true,
            replace: true,
            transclude: true
        };

        function link(scope, element, attrs, ctrl, transclude)
        {
            var input;

            attrs.$observe('lxWidth', function(newWidth)
            {
                if (angular.isDefined(scope.lxSearchFilter.closed) && scope.lxSearchFilter.closed)
                {
                    element.find('.search-filter__container').css('width', newWidth);
                }
            });

            transclude(function()
            {
                input = element.find('input');

                ctrl.setInput(input);
                ctrl.setModel(input.data('$ngModelController'));

                input.on('focus', ctrl.focusInput);
                input.on('blur', ctrl.blurInput);
                input.on('keydown', ctrl.keyEvent);
            });

            scope.$on('$destroy', function()
            {
                input.off();
            });
        }
    }

    LxSearchFilterController.$inject = ['$element', '$scope', 'LxDropdownService', 'LxNotificationService', 'LxUtils'];

    function LxSearchFilterController($element, $scope, LxDropdownService, LxNotificationService, LxUtils)
    {
        var lxSearchFilter = this;
        var input;
        var itemSelected = false;

        lxSearchFilter.blurInput = blurInput;
        lxSearchFilter.clearInput = clearInput;
        lxSearchFilter.focusInput = focusInput;
        lxSearchFilter.getClass = getClass;
        lxSearchFilter.keyEvent = keyEvent;
        lxSearchFilter.openInput = openInput;
        lxSearchFilter.selectItem = selectItem;
        lxSearchFilter.setInput = setInput;
        lxSearchFilter.setModel = setModel;

        lxSearchFilter.activeChoiceIndex = -1;
        lxSearchFilter.color = angular.isDefined(lxSearchFilter.color) ? lxSearchFilter.color : 'black';
        lxSearchFilter.dropdownId = LxUtils.generateUUID();

        ////////////

        function blurInput()
        {
            if (angular.isDefined(lxSearchFilter.closed) && lxSearchFilter.closed && !input.val())
            {
                $element.velocity(
                {
                    width: 40
                },
                {
                    duration: 400,
                    easing: 'easeOutQuint',
                    queue: false
                });
            }
        }

        function clearInput()
        {
            lxSearchFilter.modelController.$setViewValue(undefined);
            lxSearchFilter.modelController.$render();

            input.focus();
        }

        function focusInput()
        {
            if (!lxSearchFilter.searchOnFocus)
            {
                return;
            }

            updateAutocomplete(lxSearchFilter.modelController.$viewValue);
        }

        function getClass()
        {
            var searchFilterClass = [];

            if (angular.isUndefined(lxSearchFilter.closed) || !lxSearchFilter.closed)
            {
                searchFilterClass.push('search-filter--opened-mode');
            }

            if (angular.isDefined(lxSearchFilter.closed) && lxSearchFilter.closed)
            {
                searchFilterClass.push('search-filter--closed-mode');
            }

            if (input.val())
            {
                searchFilterClass.push('search-filter--has-clear-button');
            }

            if (angular.isDefined(lxSearchFilter.color))
            {
                searchFilterClass.push('search-filter--' + lxSearchFilter.color);
            }

            if (angular.isFunction(lxSearchFilter.autocomplete))
            {
                searchFilterClass.push('search-filter--autocomplete');
            }

            if (LxDropdownService.isOpen(lxSearchFilter.dropdownId))
            {
                searchFilterClass.push('search-filter--is-open');
            }

            return searchFilterClass;
        }

        function keyEvent(_event)
        {
            if (!angular.isFunction(lxSearchFilter.autocomplete))
            {
                return;
            }

            if (!LxDropdownService.isOpen(lxSearchFilter.dropdownId))
            {
                lxSearchFilter.activeChoiceIndex = -1;
            }

            switch (_event.keyCode) {
                case 13:
                    keySelect();
                    _event.preventDefault();
                    break;

                case 38:
                    keyUp();
                    _event.preventDefault();
                    break;

                case 40:
                    keyDown();
                    _event.preventDefault();
                    break;
            }

            $scope.$apply();
        }

        function keyDown()
        {
            if (lxSearchFilter.autocompleteList.length)
            {
                lxSearchFilter.activeChoiceIndex += 1;

                if (lxSearchFilter.activeChoiceIndex >= lxSearchFilter.autocompleteList.length)
                {
                    lxSearchFilter.activeChoiceIndex = 0;
                }
            }
        }

        function keySelect()
        {
            itemSelected = true;

            LxDropdownService.close(lxSearchFilter.dropdownId);

            lxSearchFilter.modelController.$setViewValue(lxSearchFilter.autocompleteList[lxSearchFilter.activeChoiceIndex]);
            lxSearchFilter.modelController.$render();
        }

        function keyUp()
        {
            if (lxSearchFilter.autocompleteList.length)
            {
                lxSearchFilter.activeChoiceIndex -= 1;

                if (lxSearchFilter.activeChoiceIndex < 0)
                {
                    lxSearchFilter.activeChoiceIndex = lxSearchFilter.autocompleteList.length - 1;
                }
            }
        }

        function openInput()
        {
            if (angular.isDefined(lxSearchFilter.closed) && lxSearchFilter.closed)
            {
                $element.velocity(
                {
                    width: angular.isDefined(lxSearchFilter.width) ? parseInt(lxSearchFilter.width) : 240
                },
                {
                    duration: 400,
                    easing: 'easeOutQuint',
                    queue: false,
                    complete: function()
                    {
                        input.focus();
                    }
                });
            }
            else
            {
                input.focus();
            }
        }

        function selectItem(_item)
        {
            itemSelected = true;

            lxSearchFilter.modelController.$setViewValue(_item);
            lxSearchFilter.modelController.$render();
        }

        function setInput(_input)
        {
            input = _input;
        }

        function setModel(_modelController)
        {
            lxSearchFilter.modelController = _modelController;

            if (angular.isFunction(lxSearchFilter.autocomplete))
            {
                if (angular.isDefined(lxSearchFilter.modelController.$overrideModelOptions))
                {
                    lxSearchFilter.modelController.$overrideModelOptions({ debounce: { 'default': 500 } });
                }
                else
                {
                    lxSearchFilter.modelController.$options = lxSearchFilter.modelController.$options || {};
                    lxSearchFilter.modelController.$options.updateOnDefault = true;
                    lxSearchFilter.modelController.$options.debounce = { 'default': 500 };
                }

                lxSearchFilter.modelController.$parsers.push(updateAutocomplete);
            }
        }

        function updateAutocomplete(_newValue)
        {
            if ((_newValue || (!_newValue && lxSearchFilter.searchOnFocus)) && !itemSelected && !lxSearchFilter.isLoading)
            {
                lxSearchFilter.isLoading = true;

                var promise = lxSearchFilter.autocomplete({ newValue: _newValue });

                promise.then(function(autocompleteList)
                {
                    lxSearchFilter.autocompleteList = autocompleteList;

                    if (lxSearchFilter.autocompleteList.length)
                    {
                        LxDropdownService.open(lxSearchFilter.dropdownId, $element);
                    }
                    else
                    {
                        LxDropdownService.close(lxSearchFilter.dropdownId);
                    }
                }).catch(function(error)
                {
                    LxNotificationService.error(error);
                }).finally(function()
                {
                    lxSearchFilter.isLoading = false;
                });

            } else {
                LxDropdownService.close(lxSearchFilter.dropdownId);
            }

            itemSelected = false;

            return _newValue;
        }
    }
})();<|endoftext|>


<commit_before>import NumberType from './Number';

class FloatType extends NumberType {

  constructor() {
    super();
  }

  set precision(v: number): void {
    this.props.precision = v;
  }

  toJoi(): Object {
    let joi = super.toJoi();

    if (this.props.precision) joi = joi.precision(this.props.precision);

    return joi;
  }

  toJSON(): Object {
    const json = super.toJSON();

    json.type = 'float';

    return json;
  }

}

export default FloatType;
<commit_msg>Change precision semantics + add scale property<commit_after>import _ from 'lodash';
import NumberType from './Number';

/**
 * Calculate the max absolute value for the given precision and scale.
 * @param {number} precision the number of total digits in value, including decimals.
 * @param {number} scale the numver of decimal digits in value.
 * @return {number}
 * @private
 */
function calculateMaxValue(precision: number, scale = 0: ?number): number {
  const arr = _.fill(Array(precision), '9');

  if (scale !== 0) {
    arr.splice(precision - scale, '.');
  }

  return parseFloat(arr.join(''));
}

class FloatType extends NumberType {

  constructor() {
    super();
  }

  set precision(v: number): void {
    this.props.precision = v;
  }

  set scale(v: number): void {
    this.props.scale = v;
  }

  toJoi(): Object {
    // reset max + min values based on precision + scale
    if (!_.isUndefined(this.props.precision)) {
      const maxValue = calculateMaxValue(this.props.precision, this.props.scale);

      if (_.isUndefined(this.props.max) || this.props.max > maxValue) {
        this.props.max = maxValue;
      }

      if (_.isUndefined(this.props.min) || this.props.min < -maxValue) {
        this.props.min = -maxValue;
      }
    }

    let joi = super.toJoi();

    if (this.props.scale) joi = joi.precision(this.props.scale);

    return joi;
  }

  toJSON(): Object {
    const json = super.toJSON();

    json.type = 'float';

    return json;
  }

}

export default FloatType;
<|endoftext|>


<commit_before>"use strict";
const Path = require('path');
const jsonfile = require('jsonfile');
const glob = require("glob");
const Package = require('./Package');
const constants = require('./constants.js');
const FileUtil = require('./utils/FileUtil.js');
const cwd = process.cwd();

class Repo {

    /**
     * @param manifestPath
     * @private
     */
    static getRepoPath(manifestPath) {
        let parentDirectory = Path.resolve(manifestPath, '..');
        let manifestFile = Path.resolve(manifestPath, constants.manifest);

        if (FileUtil.exists(manifestFile)) {
            let json = jsonfile.readFileSync(manifestFile);
            let mondo = json.mondo || {};
            if (mondo[constants.container]) {
                let childFile = Path.resolve(manifestPath, constants.child);
                if (FileUtil.exists(childFile)) {
                    let childManifest = require(childFile);
                    return Path.resolve(manifestPath, childManifest.root, constants.manifest);
                } else {
                    return manifestPath;
                }
            }
        }

        if (parentDirectory === manifestPath) {
            return null;
        } else {
            return Repo.getRepoPath(parentDirectory);
        }
    }

    /**
     * @param {String} [repoPath] Optional path to open. Defaults to the current working directory.
     * @returns {Repo}
     */
    static open(repoPath = cwd) {
        repoPath = FileUtil.absolute(repoPath);
        repoPath = Repo.getRepoPath(repoPath);
        let repo = new Repo({path: repoPath});
        repo.open();

        return repo;
    }

    constructor(config) {
        this._registry = {};
        Object.assign(this, config);
    }

    /**
     * @property {Package[]} allPackages
     */
    get allPackages() {
        let allPackages = this._allPackages;

        if (!allPackages) {
            if (this.isRoot) {
                allPackages = this.packages.slice();
                let repos = this.allRepos;
                repos.forEach(repo => allPackages.push(...repo.packages));
                this._allPackages = allPackages;
            } else {
                allPackages = this._allPackages = this.root.allPackages;
            }
        }

        return allPackages;
    }

    /**
     * @property {Object[]} allPackageAliases
     */
    get allPackageAliases() {
        let allPackageAliases = this._allPackageAliases;

        if (!allPackageAliases) {
            let allPackages = this.allPackages;
            allPackageAliases = this._allPackageAliases = {};
            allPackages.forEach(pkg => {
                this._allPackageAliases[pkg.name] = pkg.path;
            });
        }

        return allPackageAliases;
    }

    /**
     * @property {Repo[]} allRepos
     */
    get allRepos() {
        let allRepos = this._allRepos;

        if (!allRepos) {
            if (this.isRoot) {
                allRepos = {};
                let getAllUses = (repo) => {
                    let uses = repo.uses;
                    if (uses && uses.length) {
                        for (let usedRepo of uses) {
                            if (!allRepos[usedRepo.name]) {
                                allRepos[usedRepo.name] = usedRepo;
                                getAllUses(usedRepo);
                            }
                        }
                    }
                };

                getAllUses(this);
                this._allRepos = allRepos;
            } else {
                let root = this.root;
                return root.allRepos;
            }
        }

        return Object.keys(allRepos).map(name => allRepos[name]);
    }

    /**
     * @property {String} installDir
     */
    get installDir() {
        let root = this.root;

        if (root && root.manifest) {
            return Path.resolve(root.path, root.manifest.install || constants.install);
        } else {
            throw new Error(`Unable to find root for Repositories or Root manifest is not set.`);
        }
    }

    /**
     * @property {boolean} isRoot
     */
    get isRoot() {
        return this.root === this;
    }

    /**
     * @property {Object} manifest
     */
    get manifest() {
        return this._manifest;
    }

    /**
     * @property {Object} mondo
     */
    get mondo() {
        if (this.manifest) {
            return this.manifest.mondo;
        }
    }

    /**
     * @property {String} name
     */
    set name(name) {
        if (this._name && this._name !== name) {
            throw new Error(`Inconsistent name for ${name}`);
        }

        this._name = name;
    }

    get name() {
        return this._name;
    }

    /**
     * @property {Package[]} packages
     */
    get packages() {
        let packages = this._packages;

        if (!packages) {
            let manifest = this.manifest;

            if (manifest) {
                let mondo = this.mondo || {};
                let directories = mondo.packages || Array.from(constants.packages);
                let manifestPath = this.path;

                if (!Array.isArray(directories)) {
                    directories = [directories];
                }

                packages = [];
                for (let packageDir of directories) {
                    let npmPackagesPaths;
                    packageDir = Path.resolve(manifestPath, packageDir);

                    // test for self package root, allows for only one package
                    if (manifestPath === packageDir) {
                        npmPackagesPaths = [Path.resolve(packageDir, 'package.json')];
                    } else {
                        npmPackagesPaths = glob.sync(`${packageDir}/**/package.json`, {ignore: '**/node_modules/**/*'});
                    }

                    for (let npmPackagePath of npmPackagesPaths) {
                        let npmPackage = new Package(npmPackagePath);
                        npmPackage._repo = this;
                        packages.push(npmPackage);
                    }
                }

                this._packages = packages;
            } else {
                throw new Error(`Unable to get packages from a repo without path information. Configure 'path' for ${this.name}`);
            }
        }

        return packages;
    }

    /**
     * @property {String} path
     */
    get path() {
        return this._manifestPath;
    }

    set path(path) {
        let manifestPath = this._manifestPath = FileUtil.absolute(path);

        if (Path.basename(manifestPath) === constants.manifest) {
            this._manifestPath = Path.dirname(manifestPath);
            this._manifestFile = manifestPath;
        } else {
            this._manifestFile = Path.resolve(manifestPath, constants.manifest);
        }
    }

    /**
     * @property {Repo} root
     */
    get root() {
        let root = this._root;

        if (!root) {
            let manifestPath = this.path;
            let mondoDescriptorFile = Path.resolve(manifestPath, constants.child);

            // Does a mondo descriptor file exist for this repo
            if (FileUtil.exists(mondoDescriptorFile)) {
                let mondoDescriptor = require(mondoDescriptorFile);
                let rootRepoPath = Path.resolve(manifestPath, mondoDescriptor.root || '.');

                // Descriptor has a path pointing to the root repo
                if (rootRepoPath !== manifestPath) {
                    let rootRepoManifestFile = Path.resolve(rootRepoPath, constants.manifest);
                    root = new Repo({path: rootRepoManifestFile});
                    root._root = root;
                    root.open();
                    root.registerRepo(this.name, this);
                }
            }

            this._root = root || (root = this);
        }

        return root;
    }

    /**
     * @property {Object} source
     * @property {String} source.repository
     * @property {String} source.branch
     */
    set source(source) {
        this._source = source;
    }

    get source() {
        return this._source;
    }

    /**
     * @property {Repo[]} uses
     */
    get uses() {
        let uses = this._uses;

        if (!uses) {
            let manifest = this.manifest;

            if (manifest) {
                let mondo = this.mondo;
                uses = {};

                if (mondo) {
                    let manifestUses = mondo.uses || {};
                    let names = Object.keys(manifestUses);

                    names.forEach(name => {
                        uses[name] = this.resolveRepo(name, manifestUses[name]);
                    });

                    this._uses = uses;
                }
            } else {
                throw new Error('Unable to get uses. Mondo Manifest is not set for this repo');
            }
        }

        // node needs Object.values... sad day
        return Object.keys(uses).map(key => uses[key]);
    }

    /**
     * @property {Package[]} visiblePackages
     */
    get visiblePackages() {
        let visiblePackages = this._visiblePackages;

        if (!visiblePackages) {
            let manifest = this.manifest;

            if (manifest) {
                let visiblePackages = this.packages.slice();

                this.uses.forEach(repo => {
                    visiblePackages.push(...repo.packages);
                });

                this._visiblePackages = visiblePackages;
            } else {
                throw new Error(`Unable to get visible packages from a repo without path information. Configure 'path' for '${this.name}'`);
            }
        }

        return visiblePackages;
    }

    /**
     * @returns {boolean}
     * @private
     */
    exists() {
        return FileUtil.exists(this._manifestFile);
    }

    /**
     * @param {string} name
     * @param {object} source
     * @private
     */
    resolveRepo(name, source) {
        let root = this.root;
        let registry = root._registry;
        let repo = registry[name];

        if (repo) {
            let existingSource = repo.source;

            if (!existingSource) {
                repo.source = source;
            } else if (source) {
                let sourceRepository = source.repository;
                let sourceBranch = source.branch;
                let existingSourceRepository = existingSource.repository;
                let existingSourceBranch = existingSource.branch;

                if (sourceRepository !== existingSourceRepository || sourceBranch !== existingSourceBranch) {
                    throw new Error(`'${name}' repo source mismatch. '${sourceRepository}@${sourceBranch}' mismatch '${existingSourceRepository}@${existingSourceBranch}`);
                }
            } else if (!this.isRoot) {
                throw new Error(`Attempt to register a non-root Repo without source information. Configure 'source' for '${this.name}'`);
            }
        } else {
            let installDir = this.installDir;
            let repoPath = Path.resolve(installDir, name, constants.manifest);
            let config = {name, source: source, path: repoPath, _root: root};
            repo = new Repo(config);
            if (repo.exists()) {
                repo.open();
            }
            registry[name] = repo;
        }

        return repo;
    }

    /**
     * @param name {string}
     * @param repo {Repo}
     * @private
     */
    registerRepo(name, repo) {
        let registry = this.root._registry;

        if (registry[name]) {
            throw new Error(`Repo ${name} already registered`);
        }

        registry[name] = repo;
    }

    /**
     * @private
     */
    open() {
        if (!this._manifest) {
            if (this.exists()) {
                this._manifest = require(this._manifestFile);
                this.name = this._manifest.name;
            } else {
                throw new Error(`Unable to find Repo manifest at '${this._manifestFile}`);
            }
        }
    }
}

module.exports = Repo;
<commit_msg>fix property getter using block scope to contain intermediate results (#4)<commit_after>"use strict";
const Path = require('path');
const jsonfile = require('jsonfile');
const glob = require("glob");
const Package = require('./Package');
const constants = require('./constants.js');
const FileUtil = require('./utils/FileUtil.js');
const cwd = process.cwd();

class Repo {

    /**
     * @param manifestPath
     * @private
     */
    static getRepoPath(manifestPath) {
        let parentDirectory = Path.resolve(manifestPath, '..');
        let manifestFile = Path.resolve(manifestPath, constants.manifest);

        if (FileUtil.exists(manifestFile)) {
            let json = jsonfile.readFileSync(manifestFile);
            let mondo = json.mondo || {};
            if (mondo[constants.container]) {
                let childFile = Path.resolve(manifestPath, constants.child);
                if (FileUtil.exists(childFile)) {
                    let childManifest = require(childFile);
                    return Path.resolve(manifestPath, childManifest.root, constants.manifest);
                } else {
                    return manifestPath;
                }
            }
        }

        if (parentDirectory === manifestPath) {
            return null;
        } else {
            return Repo.getRepoPath(parentDirectory);
        }
    }

    /**
     * @param {String} [repoPath] Optional path to open. Defaults to the current working directory.
     * @returns {Repo}
     */
    static open(repoPath = cwd) {
        repoPath = FileUtil.absolute(repoPath);
        repoPath = Repo.getRepoPath(repoPath);
        let repo = new Repo({path: repoPath});
        repo.open();

        return repo;
    }

    constructor(config) {
        this._registry = {};
        Object.assign(this, config);
    }

    /**
     * @property {Package[]} allPackages
     */
    get allPackages() {
        let allPackages = this._allPackages;

        if (!allPackages) {
            if (this.isRoot) {
                allPackages = this.packages.slice();
                let repos = this.allRepos;
                repos.forEach(repo => allPackages.push(...repo.packages));
                this._allPackages = allPackages;
            } else {
                allPackages = this._allPackages = this.root.allPackages;
            }
        }

        return allPackages;
    }

    /**
     * @property {Object[]} allPackageAliases
     */
    get allPackageAliases() {
        let allPackageAliases = this._allPackageAliases;

        if (!allPackageAliases) {
            let allPackages = this.allPackages;
            allPackageAliases = this._allPackageAliases = {};
            allPackages.forEach(pkg => {
                this._allPackageAliases[pkg.name] = pkg.path;
            });
        }

        return allPackageAliases;
    }

    /**
     * @property {Repo[]} allRepos
     */
    get allRepos() {
        let allRepos = this._allRepos;

        if (!allRepos) {
            if (this.isRoot) {
                allRepos = {};
                let getAllUses = (repo) => {
                    let uses = repo.uses;
                    if (uses && uses.length) {
                        for (let usedRepo of uses) {
                            if (!allRepos[usedRepo.name]) {
                                allRepos[usedRepo.name] = usedRepo;
                                getAllUses(usedRepo);
                            }
                        }
                    }
                };

                getAllUses(this);
                this._allRepos = allRepos;
            } else {
                let root = this.root;
                return root.allRepos;
            }
        }

        return Object.keys(allRepos).map(name => allRepos[name]);
    }

    /**
     * @property {String} installDir
     */
    get installDir() {
        let root = this.root;

        if (root && root.manifest) {
            return Path.resolve(root.path, root.manifest.install || constants.install);
        } else {
            throw new Error(`Unable to find root for Repositories or Root manifest is not set.`);
        }
    }

    /**
     * @property {boolean} isRoot
     */
    get isRoot() {
        return this.root === this;
    }

    /**
     * @property {Object} manifest
     */
    get manifest() {
        return this._manifest;
    }

    /**
     * @property {Object} mondo
     */
    get mondo() {
        if (this.manifest) {
            return this.manifest.mondo;
        }
    }

    /**
     * @property {String} name
     */
    set name(name) {
        if (this._name && this._name !== name) {
            throw new Error(`Inconsistent name for ${name}`);
        }

        this._name = name;
    }

    get name() {
        return this._name;
    }

    /**
     * @property {Package[]} packages
     */
    get packages() {
        let packages = this._packages;

        if (!packages) {
            let manifest = this.manifest;

            if (manifest) {
                let mondo = this.mondo || {};
                let directories = mondo.packages || Array.from(constants.packages);
                let manifestPath = this.path;

                if (!Array.isArray(directories)) {
                    directories = [directories];
                }

                packages = [];
                for (let packageDir of directories) {
                    let npmPackagesPaths;
                    packageDir = Path.resolve(manifestPath, packageDir);

                    // test for self package root, allows for only one package
                    if (manifestPath === packageDir) {
                        npmPackagesPaths = [Path.resolve(packageDir, 'package.json')];
                    } else {
                        npmPackagesPaths = glob.sync(`${packageDir}/**/package.json`, {ignore: '**/node_modules/**/*'});
                    }

                    for (let npmPackagePath of npmPackagesPaths) {
                        let npmPackage = new Package(npmPackagePath);
                        npmPackage._repo = this;
                        packages.push(npmPackage);
                    }
                }

                this._packages = packages;
            } else {
                throw new Error(`Unable to get packages from a repo without path information. Configure 'path' for ${this.name}`);
            }
        }

        return packages;
    }

    /**
     * @property {String} path
     */
    get path() {
        return this._manifestPath;
    }

    set path(path) {
        let manifestPath = this._manifestPath = FileUtil.absolute(path);

        if (Path.basename(manifestPath) === constants.manifest) {
            this._manifestPath = Path.dirname(manifestPath);
            this._manifestFile = manifestPath;
        } else {
            this._manifestFile = Path.resolve(manifestPath, constants.manifest);
        }
    }

    /**
     * @property {Repo} root
     */
    get root() {
        let root = this._root;

        if (!root) {
            let manifestPath = this.path;
            let mondoDescriptorFile = Path.resolve(manifestPath, constants.child);

            // Does a mondo descriptor file exist for this repo
            if (FileUtil.exists(mondoDescriptorFile)) {
                let mondoDescriptor = require(mondoDescriptorFile);
                let rootRepoPath = Path.resolve(manifestPath, mondoDescriptor.root || '.');

                // Descriptor has a path pointing to the root repo
                if (rootRepoPath !== manifestPath) {
                    let rootRepoManifestFile = Path.resolve(rootRepoPath, constants.manifest);
                    root = new Repo({path: rootRepoManifestFile});
                    root._root = root;
                    root.open();
                    root.registerRepo(this.name, this);
                }
            }

            this._root = root || (root = this);
        }

        return root;
    }

    /**
     * @property {Object} source
     * @property {String} source.repository
     * @property {String} source.branch
     */
    set source(source) {
        this._source = source;
    }

    get source() {
        return this._source;
    }

    /**
     * @property {Repo[]} uses
     */
    get uses() {
        let uses = this._uses;

        if (!uses) {
            let manifest = this.manifest;

            if (manifest) {
                let mondo = this.mondo;
                uses = {};

                if (mondo) {
                    let manifestUses = mondo.uses || {};
                    let names = Object.keys(manifestUses);

                    names.forEach(name => {
                        uses[name] = this.resolveRepo(name, manifestUses[name]);
                    });

                    this._uses = uses;
                }
            } else {
                throw new Error('Unable to get uses. Mondo Manifest is not set for this repo');
            }
        }

        // node needs Object.values... sad day
        return Object.keys(uses).map(key => uses[key]);
    }

    /**
     * @property {Package[]} visiblePackages
     */
    get visiblePackages() {
        let visiblePackages = this._visiblePackages;

        if (!visiblePackages) {
            let manifest = this.manifest;

            if (manifest) {
                visiblePackages = this.packages.slice();

                this.uses.forEach(repo => {
                    visiblePackages.push(...repo.packages);
                });

                this._visiblePackages = visiblePackages;
            } else {
                throw new Error(`Unable to get visible packages from a repo without path information. Configure 'path' for '${this.name}'`);
            }
        }

        return visiblePackages;
    }

    /**
     * @returns {boolean}
     * @private
     */
    exists() {
        return FileUtil.exists(this._manifestFile);
    }

    /**
     * @param {string} name
     * @param {object} source
     * @private
     */
    resolveRepo(name, source) {
        let root = this.root;
        let registry = root._registry;
        let repo = registry[name];

        if (repo) {
            let existingSource = repo.source;

            if (!existingSource) {
                repo.source = source;
            } else if (source) {
                let sourceRepository = source.repository;
                let sourceBranch = source.branch;
                let existingSourceRepository = existingSource.repository;
                let existingSourceBranch = existingSource.branch;

                if (sourceRepository !== existingSourceRepository || sourceBranch !== existingSourceBranch) {
                    throw new Error(`'${name}' repo source mismatch. '${sourceRepository}@${sourceBranch}' mismatch '${existingSourceRepository}@${existingSourceBranch}`);
                }
            } else if (!this.isRoot) {
                throw new Error(`Attempt to register a non-root Repo without source information. Configure 'source' for '${this.name}'`);
            }
        } else {
            let installDir = this.installDir;
            let repoPath = Path.resolve(installDir, name, constants.manifest);
            let config = {name, source: source, path: repoPath, _root: root};
            repo = new Repo(config);
            if (repo.exists()) {
                repo.open();
            }
            registry[name] = repo;
        }

        return repo;
    }

    /**
     * @param name {string}
     * @param repo {Repo}
     * @private
     */
    registerRepo(name, repo) {
        let registry = this.root._registry;

        if (registry[name]) {
            throw new Error(`Repo ${name} already registered`);
        }

        registry[name] = repo;
    }

    /**
     * @private
     */
    open() {
        if (!this._manifest) {
            if (this.exists()) {
                this._manifest = require(this._manifestFile);
                this.name = this._manifest.name;
            } else {
                throw new Error(`Unable to find Repo manifest at '${this._manifestFile}`);
            }
        }
    }
}

module.exports = Repo;
<|endoftext|>