<commit_before>import * as core from '../core';
import * as mesh from '../mesh';

const Texture = core.Texture;

const Sprite = core.Sprite;
const Rope = mesh.Rope;
const Plane = mesh.Plane;

/**
 * @typedef FrameObject
 * @type {object}
 * @property {PIXI.Texture} texture - The texture object of the frame
 * @property {number} [duration] - the duration of the frame in ms
 * @property {array} [pivot] - the pivot(ratio) of the frame. Index 0 is x; Index 1 is y.
 *
 * If no `frame.duration`, frame.duration will equal `animation.duration / frames.length`
 * If no `frame.pivot`, frame.pivot will be null, then Animation use default or original or previous pivot.
 * Some private fileds will be generated dynamically:
 *     {number} _startTime:
 *     {number} _endTime:
 */

/**
 * An Animation is a simple way to display an animation depicted by a list of frames
 *
 * @class
 * @extends PIXI.Sprite
 * @memberof PIXI.extensions
 */
export default class Animation
{
    /**
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     */
    constructor(frames, duration)
    {
        this.initAnimation(frames, duration);
    }

    /**
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     */
    initAnimation(frames, duration)
    {
        /**
         * Scale the time step when update animation. Higher is faster, lower is slower
         *
         * @member {number}
         * @default 1
         */
        this.timeScale = 1;

        /**
         * Whether or not the animate sprite repeats after playing
         *
         * @member {boolean}
         * @default true
         */
        this.loop = true;

        /**
         * Function to call when a Animation finishes playing
         *
         * @method
         * @memberof PIXI.extensions.Animation#
         */
        this.onComplete = null;

        /**
         * The index of the frame displayed when animation finished.
         *
         * @member {number}
         * @default 0
         */
        this.endIndex = 0;

        /**
         * Function to call when a Animation frame changes
         *
         * @method
         * @memberof PIXI.extensions.Animation#
         */
        this.onFrameChange = null;

        /**
         * Elapsed time since animation has been started, used internally to display current texture
         *
         * @member {number}
         */
        this.currentTime = 0;

        /**
         * Indicates if the Animation is currently playing
         *
         * @member {boolean}
         * @readonly
         */
        this.playing = false;

        /**
         * Whether allow to skip frames if timeStep too large.
         *
         * @member {boolean}
         * @default false
         */
        this.skipFrame = false;

        /**
        * The animation current frame index
        *
        * @member {number}
        * @readonly
        */
        this.currentIndex = -1;

        /**
        * The animation current frame object
        *
        * @member {FrameObject}
        * @memberof PIXI.extensions.Animation#
        * @readonly
        */
        this.currentFrame = null;

        /**
        * The animation current frame object
        *
        * @member {PIXI.Texture}
        * @readonly
        */
        this.currentTexture = null;

        /**
         * `frameCount` is the total number of frames in the Animation
         *
         * @member {number}
         * @default 0
         */
        this.frameCount = 0;

        /**
         * @private
         */
        this._minIndex = -1;
        this._maxIndex = -1;

        this.duration = duration || 0;

        /**
         * @private
         */
        this._frames = [];

        this.setFrames(frames);
    }

    /**
     * Play the Animation
     *
     */
    play()
    {
        this.playing = true;
    }

    /**
     * Pause the Animation
     *
     */
    pause()
    {
        this.playing = false;
    }

    /**
     * Resume playing. In current case , `resume` equals `play`
     *
     */
    resume()
    {
        this.playing = true;
    }

    /**
     * Stop the Animation
     *
     */
    stop()
    {
        this.gotoAndStop(0);
    }

    /**
     * Go to a specific frame and begins playing the Animation
     *
     * @param {number} frameIndex - frame index to start at
     */
    gotoAndPlay(frameIndex)
    {
        if (frameIndex !== this.currentIndex)
        {
            this.frameChange(frameIndex);
        }
        this.currentTime = this.currentFrame._startTime;
        this.play();
    }

    /**
     * Stop the Animation and goes to a specific frame
     *
     * @param {number} frameIndex - frame index to stop at
     */
    gotoAndStop(frameIndex)
    {
        this.pause();

        if (frameIndex !== this.currentIndex)
        {
            this.frameChange(frameIndex);
        }
        this.currentTime = this.currentFrame._startTime;
    }

    /**
     * Update the animation by time-step(delta-time)
     *
     * @param {number} timeStep - Time since last tick
     */
    update(timeStep)
    {
        if (!this.playing)
        {
            return;
        }
        const elapsed = this.timeScale * timeStep;
        // const sign = elapsed < 0 ? -1 : 1;

        this.currentTime += elapsed;

        this.updateByTime(this.currentTime, this.skipFrame);
    }

    /**
     * Update the animation by played-time(total-time)
     *
     * @param {number} time - Time since animation has been started
     * @param {boolean} [skipFrame=false] - Whether allow to skip frames if timeStep too large.
     */
    updateByTime(time, skipFrame)
    {
        const duration = this.duration;
        const frames = this._frames;

        let index = this.currentIndex;
        const lastIndex = index;
        let frame;

        let completed = false;

        do {
            frame = frames[index];
            if (time < frame._startTime)
            {
                // Play backward
                if (index === this._minIndex)
                {
                    if (this.loop === true || (--this.loop) > 0 )
                    {
                        time += duration;
                        index = this._maxIndex;
                        continue;
                    }
                    completed = true;
                    break;
                }
                index--;
                continue;
            }

            if (time < frame._endTime)
            {
                // No frame changed
                break;
            }

            if (index === this._maxIndex)
            {
                // Play forward
                if (this.loop === true || (--this.loop) > 0 )
                {
                    time -= duration;
                    index = 0;
                    continue;
                }
                completed = true;
                break;
            }
            index++;
        } while (skipFrame);

        this.currentTime = time;

        if (completed)
        {
            this.playing = false;

            if (lastIndex !== this.endIndex)
            {
                this.frameChange(this.endIndex);
                this.currentTime = this.currentFrame._startTime;
            }
            if (this.onComplete)
            {
                this.onComplete();
            }
            return;
        }

        if (lastIndex !== index)
        {
            this.frameChange(index);

            // TODO: Whether snap currentTime to frame ?
            // if (!skipFrame)
            // {
                // Play forward:
                // this.currentTime = this.currentFrame._startTime;

                // Play backward:
                // this.currentTime = this.currentFrame._endTime;
            // }
        }
    }

    /**
     * Function to call when a Animation changes which texture is being rendered
     *
     * @param {number} frameIndex - new frame index
     * @private
     */
    frameChange(frameIndex)
    {
        this.currentIndex = frameIndex;

        const frame = this.currentFrame = this._frames[frameIndex];

        this.currentTexure = frame.texture;

        if (frame.pivot)
        {
            const pivot = frame.pivot;
            this._host.transform.pivot.set(pivot[0], pivot[1]);
        }

        this.updateTexture();

        if (this.onFrameChange)
        {
            this.onFrameChange(frameIndex, frame);
        }
    }

    /**
     * Updates the displayed texture to match the current frame index
     *
     * @private
     */
    updateTexture()
    {
        this._host._texture = this.currentTexure;
        this._host._textureID = -1;

        // TODO: `refresh` is hard code , not good enough.
        if (this._host.refresh)
        {
            this._host.refresh();
        }
    }

    /**
     * Get the array of frame objects used for this Animation
     *
     * @return {FrameObject[]} The array of frame objects in animation
     * @memberof PIXI.extensions.Animation#
     */
    getFrames()
    {
        return this._frames;
    }

    /**
     * Set the frames.
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - The frames to set
     */
    setFrames(frames)
    {
        this.playing = false;
        this._frames.length = 0;

        if (!frames)
        {
            this.frameCount = 0;
            this._minIndex = -1;
            this._maxIndex = -1;
            this.currentTime = 0;
            this.currentIndex = -1;
            this.currentFrame = null;
            this.currentTexure = null;
            return;
        }

        const len = this.frameCount = frames.length;

        this._minIndex = 0;
        this._maxIndex = len - 1;

        const preDuration = this.duration / len;
        const useTexture = frames[0] instanceof Texture;

        let startTime = 0;
        let endTime = 0;
        let frame;

        for (let i = 0; i < len; i++)
        {
            if (useTexture)
            {
                frame = {
                    texture: frames[i],
                };
            }
            else
            {
                frame = frames[i];
            }
            this._frames.push(frame);

            frame.duration = frame.duration || preDuration;
            frame.pivot = frame.pivot || null;

            frame._startTime = startTime;
            frame._endTime = (endTime += frame.duration);

            startTime = endTime;
        }

        if (!this.duration || this.duration < endTime)
        {
            this.duration = endTime;
        }

        this.currentIndex = 0;
        this.currentFrame = this._frames[0];
        this.currentTexure = this.currentFrame.texture;
        if (this.currentFrame.pivot)
        {
            const a = this.currentFrame.pivot;
            this._host.transform.pivot.set(a[0], a[1]);
        }
    }

    /**
     * Get the host of Animation
     *
     * @return {PIXI.DisplayObject} The host of Animation
     */
    getHost()
    {
        return this._host;
    }

    /**
     * Add the animation to a display object.
     *
     * @param {PIXI.DisplayObject} host - The host of Animation
     * @param {string} [bindName='anim'] - The property name of host for binding
     */
    bind(host, bindName)
    {
        this.unbind();
        this._host = host;
        this._bindName = bindName || 'anim';
        this._host[this._bindName] = this;
    }

    /**
     * Remove the animation from the display object binded.
     */
    unbind()
    {
        if (this._host)
        {
            delete this._host[this._bindName];
            this._host = null;
        }
    }

    /**
     * Destroy the Animation
     *
     */
    destroy()
    {
        this.setFrames(null);
        super.destroy();
    }

    /**
     * Mixin properties of Animation to a display object , let it become a animation object
     *
     *@param {PIXI.Sprite|PIXI.mesh.Rope} displayObject - the object to apply
     */
    static applyTo(displayObject)
    {
        const properties = [
            // 'initAnimation',
            'play',
            'pause',
            'resume',
            'stop',
            'gotoAndPlay',
            'gotoAndStop',
            'update',
            'updateByTime',
            'frameChange',
            'updateTexture',
            'getHost',
            'getFrames',
            'setFrames',
            'onFrameChange',
            'onComplete',
        ];

        properties.forEach(function(p) {
            displayObject[p] = Animation.prototype[p];
        });
        displayObject._initAnimation = Animation.prototype.initAnimation;
        displayObject.initAnimation = function(frames, duration) {
            this._initAnimation(frames, duration);
            this._host = this;
            this._bindName = '_anim';
        };
    }

    /**
     * Create a Sprite with animation
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     *
     * @return {PIXI.Sprite} a sprite with animation
     */
    static createSprite(frames, duration)
    {
        const sprite = new Sprite(Texture.EMPTY);

        Animation.applyTo(sprite);
        sprite.initAnimation(frames, duration);
        return sprite;
    }

    /**
     * Create a Mesh Rope with animation
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     * @param {PIXI.Point[]} points - An array of {@link PIXI.Point} objects to construct this rope.
     *
     * @return {PIXI.mesh.Rope} a mesh rope with animation
     */
    static createMeshRope(frames, duration, points)
    {
        const rope = new Rope(Texture.EMPTY, points);

        Animation.applyTo(rope);
        rope.initAnimation(frames, duration);
        return rope;
    }

    /**
     * Create a Mesh Plane with animation
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     * @param {number} verticesX - The number of vertices in the x-axis
     * @param {number} verticesY - The number of vertices in the y-axis
     *
     * @return {PIXI.mesh.Plane} a mesh plane with animation
     */
    static createMeshPlane(frames, duration, verticesX, verticesY)
    {
        const plane = new Plane(Texture.EMPTY, verticesX, verticesY);

        Animation.applyTo(plane);
        plane.initAnimation(frames, duration);
        return plane;
    }

}
<commit_msg>fix bug about one frame animation<commit_after>import * as core from '../core';
import * as mesh from '../mesh';

const Texture = core.Texture;

const Sprite = core.Sprite;
const Rope = mesh.Rope;
const Plane = mesh.Plane;

/**
 * @typedef FrameObject
 * @type {object}
 * @property {PIXI.Texture} texture - The texture object of the frame
 * @property {number} [duration] - the duration of the frame in ms
 * @property {array} [pivot] - the pivot(ratio) of the frame. Index 0 is x; Index 1 is y.
 *
 * If no `frame.duration`, frame.duration will equal `animation.duration / frames.length`
 * If no `frame.pivot`, frame.pivot will be null, then Animation use default or original or previous pivot.
 * Some private fileds will be generated dynamically:
 *     {number} _startTime:
 *     {number} _endTime:
 */

/**
 * An Animation is a simple way to display an animation depicted by a list of frames
 *
 * @class
 * @extends PIXI.Sprite
 * @memberof PIXI.extensions
 */
export default class Animation
{
    /**
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     */
    constructor(frames, duration)
    {
        this.initAnimation(frames, duration);
    }

    /**
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     */
    initAnimation(frames, duration)
    {
        /**
         * Scale the time step when update animation. Higher is faster, lower is slower
         *
         * @member {number}
         * @default 1
         */
        this.timeScale = 1;

        /**
         * Whether or not the animate sprite repeats after playing
         *
         * @member {boolean}
         * @default true
         */
        this.loop = true;

        /**
         * Function to call when a Animation finishes playing
         *
         * @method
         * @memberof PIXI.extensions.Animation#
         */
        this.onComplete = null;

        /**
         * The index of the frame displayed when animation finished.
         *
         * @member {number}
         * @default 0
         */
        this.endIndex = 0;

        /**
         * Function to call when a Animation frame changes
         *
         * @method
         * @memberof PIXI.extensions.Animation#
         */
        this.onFrameChange = null;

        /**
         * Elapsed time since animation has been started, used internally to display current texture
         *
         * @member {number}
         */
        this.currentTime = 0;

        /**
         * Indicates if the Animation is currently playing
         *
         * @member {boolean}
         * @readonly
         */
        this.playing = false;

        /**
         * Whether allow to skip frames if timeStep too large.
         *
         * @member {boolean}
         * @default false
         */
        this.skipFrame = false;

        /**
        * The animation current frame index
        *
        * @member {number}
        * @readonly
        */
        this.currentIndex = -1;

        /**
        * The animation current frame object
        *
        * @member {FrameObject}
        * @memberof PIXI.extensions.Animation#
        * @readonly
        */
        this.currentFrame = null;

        /**
        * The animation current frame object
        *
        * @member {PIXI.Texture}
        * @readonly
        */
        this.currentTexture = null;

        /**
         * `frameCount` is the total number of frames in the Animation
         *
         * @member {number}
         * @default 0
         */
        this.frameCount = 0;

        /**
         * @private
         */
        this._minIndex = -1;
        this._maxIndex = -1;

        this.duration = duration || 0;

        /**
         * @private
         */
        this._frames = [];

        this.setFrames(frames);
    }

    /**
     * Play the Animation
     *
     */
    play()
    {
        this.playing = true;
    }

    /**
     * Pause the Animation
     *
     */
    pause()
    {
        this.playing = false;
    }

    /**
     * Resume playing. In current case , `resume` equals `play`
     *
     */
    resume()
    {
        this.playing = true;
    }

    /**
     * Stop the Animation
     *
     */
    stop()
    {
        this.gotoAndStop(0);
    }

    /**
     * Go to a specific frame and begins playing the Animation
     *
     * @param {number} frameIndex - frame index to start at
     */
    gotoAndPlay(frameIndex)
    {
        if (frameIndex !== this.currentIndex)
        {
            this.changeFrame(frameIndex);
        }
        this.currentTime = this.currentFrame._startTime;
        this.play();
    }

    /**
     * Stop the Animation and goes to a specific frame
     *
     * @param {number} frameIndex - frame index to stop at
     */
    gotoAndStop(frameIndex)
    {
        this.pause();

        if (frameIndex !== this.currentIndex)
        {
            this.changeFrame(frameIndex);
        }
        this.currentTime = this.currentFrame._startTime;
    }

    /**
     * Update the animation by time-step(delta-time)
     *
     * @param {number} timeStep - Time since last tick
     */
    update(timeStep)
    {
        if (!this.playing)
        {
            return;
        }
        const elapsed = this.timeScale * timeStep;
        // const sign = elapsed < 0 ? -1 : 1;

        this.currentTime += elapsed;

        this.updateByTime(this.currentTime, this.skipFrame);
    }

    /**
     * Update the animation by played-time(total-time)
     *
     * @param {number} time - Time since animation has been started
     * @param {boolean} [skipFrame=false] - Whether allow to skip frames if timeStep too large.
     */
    updateByTime(time, skipFrame)
    {
        const duration = this.duration;
        const frames = this._frames;

        let index = this.currentIndex;
        const lastIndex = index;
        let frame;

        let completed = false;

        do {
            frame = frames[index];
            if (time < frame._startTime)
            {
                // Play backward
                if (index === this._minIndex)
                {
                    if (this.loop === true || (--this.loop) > 0 )
                    {
                        time += duration;
                        index = this._maxIndex;
                        continue;
                    }
                    completed = true;
                    break;
                }
                index--;
                continue;
            }

            if (time < frame._endTime)
            {
                // No frame changed
                break;
            }

            if (index === this._maxIndex)
            {
                // Play forward
                if (this.loop === true || (--this.loop) > 0 )
                {
                    time -= duration;
                    index = this._minIndex;
                    continue;
                }
                completed = true;
                break;
            }
            index++;
        } while (skipFrame);

        this.currentTime = time;

        if (completed)
        {
            this.playing = false;

            if (lastIndex !== this.endIndex)
            {
                this.changeFrame(this.endIndex);
                this.currentTime = this.currentFrame._startTime;
            }
            if (this.onComplete)
            {
                this.onComplete();
            }
            return;
        }

        if (lastIndex !== index)
        {
            this.changeFrame(index);

            // TODO: Whether snap currentTime to frame ?
            // if (!skipFrame)
            // {
                // Play forward:
                // this.currentTime = this.currentFrame._startTime;

                // Play backward:
                // this.currentTime = this.currentFrame._endTime;
            // }
        }
    }

    /**
     * Function to call when a Animation changes which texture is being rendered
     *
     * @param {number} frameIndex - new frame index
     * @private
     */
    changeFrame(frameIndex)
    {
        this.currentIndex = frameIndex;

        const frame = this.currentFrame = this._frames[frameIndex];

        this.currentTexure = frame.texture;

        if (frame.pivot)
        {
            const pivot = frame.pivot;
            this._host.transform.pivot.set(pivot[0], pivot[1]);
        }

        this.updateTexture();

        if (this.onFrameChange)
        {
            this.onFrameChange(frameIndex, frame);
        }
    }

    /**
     * Updates the displayed texture to match the current frame index
     *
     * @private
     */
    updateTexture()
    {
        this._host._texture = this.currentTexure;
        this._host._textureID = -1;

        // TODO: `refresh` is hard code , not good enough.
        if (this._host.refresh)
        {
            this._host.refresh();
        }
    }

    /**
     * Get the array of frame objects used for this Animation
     *
     * @return {FrameObject[]} The array of frame objects in animation
     * @memberof PIXI.extensions.Animation#
     */
    getFrames()
    {
        return this._frames;
    }

    /**
     * Set the frames.
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - The frames to set
     */
    setFrames(frames)
    {
        this.playing = false;
        this._frames.length = 0;

        this.currentTime = 0;
        this.currentIndex = -1;
        this.currentFrame = null;
        this.currentTexure = null;

        if (!frames)
        {
            this.frameCount = 0;
            this._minIndex = -1;
            this._maxIndex = -1;
            this.endIndex = 0;
            this.firstTexure = null;
            return;
        }

        const len = this.frameCount = frames.length;

        this._minIndex = 0;
        this._maxIndex = len - 1;
        this.endIndex = this._maxIndex;

        const preDuration = this.duration / len;
        const useTexture = frames[0] instanceof Texture;

        let startTime = 0;
        let endTime = 0;
        let frame;

        for (let i = 0; i < len; i++)
        {
            if (useTexture)
            {
                frame = {
                    texture: frames[i],
                };
            }
            else
            {
                frame = frames[i];
            }
            this._frames.push(frame);

            frame.duration = frame.duration || preDuration;
            frame.pivot = frame.pivot || null;

            frame._startTime = startTime;
            frame._endTime = (endTime += frame.duration);

            startTime = endTime;
        }

        if (!this.duration || this.duration < endTime)
        {
            this.duration = endTime;
        }

        this.firstTexure = this._frames[0].texture;
    }

    /**
     * Get the host of Animation
     *
     * @return {PIXI.DisplayObject} The host of Animation
     */
    getHost()
    {
        return this._host;
    }

    /**
     * Add the animation to a display object.
     *
     * @param {PIXI.DisplayObject} host - The host of Animation
     * @param {string} [bindName='anim'] - The property name of host for binding
     */
    bind(host, bindName)
    {
        this.unbind();
        this._host = host;
        this._bindName = bindName || 'anim';
        this._host[this._bindName] = this;
        this.changeFrame(this._minIndex);
    }

    /**
     * Remove the animation from the display object binded.
     */
    unbind()
    {
        if (this._host)
        {
            delete this._host[this._bindName];
            this._host = null;
        }
    }

    /**
     * Destroy the Animation
     *
     */
    destroy()
    {
        this.setFrames(null);
        super.destroy();
    }

    /**
     * Mixin properties of Animation to a display object , let it become a animation object
     *
     *@param {PIXI.Sprite|PIXI.mesh.Rope} displayObject - the object to apply
     */
    static applyTo(displayObject)
    {
        const properties = [
            // 'initAnimation',
            'play',
            'pause',
            'resume',
            'stop',
            'gotoAndPlay',
            'gotoAndStop',
            'update',
            'updateByTime',
            'changeFrame',
            'updateTexture',
            'getHost',
            'getFrames',
            'setFrames',
            'onFrameChange',
            'onComplete',
        ];

        properties.forEach(function(p) {
            displayObject[p] = Animation.prototype[p];
        });
        displayObject._initAnimation = Animation.prototype.initAnimation;
        displayObject.initAnimation = function(frames, duration) {
            this._initAnimation(frames, duration);
            this._host = this;
            this._bindName = '_anim';
            this.changeFrame(this._minIndex);
        };
    }

    /**
     * Create a Sprite with animation
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     *
     * @return {PIXI.Sprite} a sprite with animation
     */
    static createSprite(frames, duration)
    {
        const sprite = new Sprite(Texture.EMPTY);

        Animation.applyTo(sprite);
        sprite.initAnimation(frames, duration);
        return sprite;
    }

    /**
     * Create a Mesh Rope with animation
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     * @param {PIXI.Point[]} points - An array of {@link PIXI.Point} objects to construct this rope.
     *
     * @return {PIXI.mesh.Rope} a mesh rope with animation
     */
    static createMeshRope(frames, duration, points)
    {
        const rope = new Rope(Texture.EMPTY, points);

        Animation.applyTo(rope);
        rope.initAnimation(frames, duration);
        return rope;
    }

    /**
     * Create a Mesh Plane with animation
     *
     * @param {PIXI.Texture[]|FrameObject[]} frames - an array of {@link PIXI.Texture} or frame
     *  objects that make up the animation
     * @param {number} [duration=0] - The total duration of animation in ms
     *     If no `duration`, the duration will equal the sum of all `frame.duration`
     * @param {number} verticesX - The number of vertices in the x-axis
     * @param {number} verticesY - The number of vertices in the y-axis
     *
     * @return {PIXI.mesh.Plane} a mesh plane with animation
     */
    static createMeshPlane(frames, duration, verticesX, verticesY)
    {
        const plane = new Plane(Texture.EMPTY, verticesX, verticesY);

        Animation.applyTo(plane);
        plane.initAnimation(frames, duration);
        return plane;
    }

}
<|endoftext|>


<commit_before>var GeometryUtils = {

	merge: function ( object1, object2 ) {

		var isMesh = object2 instanceof THREE.Mesh;

		var geometry1 = object1,
		vertexPosition = geometry1.vertices.length,
		facePosition = geometry1.faces.length,
		uvPosition = geometry1.uvs.length,
		geometry2 = isMesh ? object2.geometry : object2;

		if ( isMesh ) object2.updateMatrix();

		for ( var i = 0, il = geometry2.vertices.length; i < il; i ++ ) {

			var vertex = geometry2.vertices[ i ];

			var vertexCopy = new THREE.Vertex( vertex.position.clone() );

			if ( isMesh ) object2.matrix.transform( vertexCopy.position );

			geometry1.vertices.push( vertexCopy );

		}

		for ( var i = 0, il = geometry2.faces.length; i < il; i ++ ) {

			var face = geometry2.faces[ i ];

			if ( face instanceof THREE.Face3 ) {

				var faceCopy = new THREE.Face3();
				faceCopy.a = face.a + vertexPosition;
				faceCopy.b = face.b + vertexPosition;
				faceCopy.c = face.c + vertexPosition;

			} else if ( face instanceof THREE.Face4 ) {

				var faceCopy = new THREE.Face4();
				faceCopy.a = face.a + vertexPosition;
				faceCopy.b = face.b + vertexPosition;
				faceCopy.c = face.c + vertexPosition;
				faceCopy.d = face.d + vertexPosition;

			}

			for ( var j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {

				var normal = face.vertexNormals[ j ];
				faceCopy.vertexNormals.push( normal.clone() );
			}

			geometry1.faces.push( faceCopy );

		}

		for ( var i = 0, il = geometry2.uvs.length; i < il; i ++ ) {

			var uv = geometry2.uvs[ i ];

			var uvCopy = [];

			for ( var j = 0, jl = uv.length; j < jl; j ++ ) {

				uvCopy.push( new THREE.UV( uv[ j ].u, uv[ j ].v ) );

			}

			geometry1.uvs.push( uvCopy );

		}

	}

}
<commit_msg>Optimised GeometryUtils::merge<commit_after>var GeometryUtils = {

	merge: function ( geometry1, object2 /* mesh | geometry */ ) {

		var isMesh = object2 instanceof THREE.Mesh,
		vertexPosition = geometry1.vertices.length,
		facePosition = geometry1.faces.length,
		uvPosition = geometry1.uvs.length,
		geometry2 = isMesh ? object2.geometry : object2,
		vertices1 = geometry1.vertices,
		vertices2 = geometry2.vertices,
		faces1 = geometry1.faces,
		faces2 = geometry2.faces,
		uvs1 = geometry1.uvs,
		uvs2 = geometry2.uvs;

		isMesh && object2.updateMatrix();

		for ( var i = 0, il = vertices2.length; i < il; i ++ ) {

			var vertex = vertices2[ i ];

			var vertexCopy = new THREE.Vertex( vertex.position.clone() );

			isMesh && object2.matrix.transform( vertexCopy.position );

			vertices1.push( vertexCopy );

		}

		for ( var i = 0, il = faces2.length; i < il; i ++ ) {

			var face = faces2[ i ], faceCopy, normal,
			faceVertexNormals = face.vertexNormals;

			if ( face instanceof THREE.Face3 ) {

				faceCopy = new THREE.Face3( face.a + vertexPosition, face.b + vertexPosition, face.c + vertexPosition );

			} else if ( face instanceof THREE.Face4 ) {

				faceCopy = new THREE.Face4( face.a + vertexPosition, face.b + vertexPosition, face.c + vertexPosition, face.d + vertexPosition );

			}

			for ( var j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {

				normal = faceVertexNormals[ j ];
				faceCopy.vertexNormals.push( normal.clone() );

			}

			faces1.push( faceCopy );

		}

		for ( var i = 0, il = uvs2.length; i < il; i ++ ) {

			var uv = uvs2[ i ], uvCopy = [];

			for ( var j = 0, jl = uv.length; j < jl; j ++ ) {

				uvCopy.push( new THREE.UV( uv[ j ].u, uv[ j ].v ) );

			}

			uvs1.push( uvCopy );

		}

	}

}
<|endoftext|>


<commit_before>import { get as objGet, forEach, isFunction, isString, isArray, debounce, uniqueId } from "lodash";
import validators from "../utils/validators";
import { slugifyFormID } from "../utils/schema";

function convertValidator(validator) {
	if (isString(validator)) {
		if (validators[validator] != null) return validators[validator];
		else {
			console.warn(`'${validator}' is not a validator function!`);
			return null; // caller need to handle null
		}
	}
	return validator;
}

function attributesDirective(el, binding, vnode) {
	let attrs = objGet(vnode.context, "schema.attributes", {});
	let container = binding.value || "input";
	if (isString(container)) {
		attrs = objGet(attrs, container) || attrs;
	}
	forEach(attrs, (val, key) => {
		el.setAttribute(key, val);
	});
}

export default {
	props: ["vfg", "model", "schema", "formOptions", "disabled"],

	data() {
		return {
			errors: [],
			debouncedValidateFunc: null,
			debouncedFormatFunc: null
		};
	},

	directives: {
		attributes: {
			bind: attributesDirective,
			updated: attributesDirective,
			componentUpdated: attributesDirective
		}
	},

	computed: {
		value: {
			cache: false,
			get() {
				let val;
				if (isFunction(objGet(this.schema, "get"))) {
					val = this.schema.get(this.model);
				} else {
					val = objGet(this.model, this.schema.model);
				}

				return this.formatValueToField(val);
			},

			set(newValue) {
				let oldValue = this.value;
				newValue = this.formatValueToModel(newValue);

				if (isFunction(newValue)) {
					newValue(newValue, oldValue);
				} else {
					this.updateModelValue(newValue, oldValue);
				}
			}
		}
	},

	methods: {
		validate(calledParent) {
			this.clearValidationErrors();
			let validateAsync = objGet(this.formOptions, "validateAsync", false);

			let results = [];

			if (this.schema.validator && this.schema.readonly !== true && this.disabled !== true) {
				let validators = [];
				if (!isArray(this.schema.validator)) {
					validators.push(convertValidator(this.schema.validator).bind(this));
				} else {
					forEach(this.schema.validator, validator => {
						validators.push(convertValidator(validator).bind(this));
					});
				}

				forEach(validators, validator => {
					if (validateAsync) {
						results.push(validator(this.value, this.schema, this.model));
					} else {
						let result = validator(this.value, this.schema, this.model);
						if (result && isFunction(result.then)) {
							result.then(err => {
								if (err) {
									this.errors = this.errors.concat(err);
								}
								let isValid = this.errors.length === 0;
								this.$emit("validated", isValid, this.errors, this);
							});
						} else if (result) {
							results = results.concat(result);
						}
					}
				});
			}

			let handleErrors = errors => {
				let fieldErrors = [];
				forEach(errors, err => {
					if (isArray(err) && err.length > 0) {
						fieldErrors = fieldErrors.concat(err);
					} else if (isString(err)) {
						fieldErrors.push(err);
					}
				});
				if (isFunction(this.schema.onValidated)) {
					this.schema.onValidated.call(this, this.model, fieldErrors, this.schema);
				}

				let isValid = fieldErrors.length === 0;
				if (!calledParent) {
					this.$emit("validated", isValid, fieldErrors, this);
				}
				this.errors = fieldErrors;
				return fieldErrors;
			};

			if (!validateAsync) {
				return handleErrors(results);
			}

			return Promise.all(results).then(handleErrors);
		},

		debouncedValidate() {
			if (!isFunction(this.debouncedValidateFunc)) {
				this.debouncedValidateFunc = debounce(
					this.validate.bind(this),
					objGet(this, "$parent.options.validateDebounceTime", 500)
				);
			}
			this.debouncedValidateFunc();
		},

		updateModelValue(newValue, oldValue) {
			let changed = false;
			if (isFunction(this.schema.set)) {
				this.schema.set(this.model, newValue);
				changed = true;
			} else if (this.schema.model) {
				this.setModelValueByPath(this.schema.model, newValue);
				changed = true;
			}

			if (changed) {
				this.$emit("model-updated", newValue, this.schema.model);

				if (isFunction(this.schema.onChanged)) {
					this.schema.onChanged.call(this, this.model, newValue, oldValue, this.schema);
				}

				if (objGet(this.$parent, "options.validateAfterChanged", false) === true) {
					if (objGet(this.$parent, "options.validateDebounceTime", 0) > 0) {
						this.debouncedValidate();
					} else {
						this.validate();
					}
				}
			}
		},

		clearValidationErrors() {
			this.errors.splice(0);
		},

		setModelValueByPath(path, value) {
			// convert array indexes to properties
			let s = path.replace(/\[(\w+)\]/g, ".$1");

			// strip a leading dot
			s = s.replace(/^\./, "");

			let o = this.model;
			const a = s.split(".");
			let i = 0;
			const n = a.length;
			while (i < n) {
				let k = a[i];
				if (i < n - 1)
					if (o[k] !== undefined) {
						// Found parent property. Step in
						o = o[k];
					} else {
						// Create missing property (new level)
						this.$root.$set(o, k, {});
						o = o[k];
					}
				else {
					// Set final property value
					this.$root.$set(o, k, value);
					return;
				}

				++i;
			}
		},

		getFieldID(schema, unique = false) {
			const idPrefix = objGet(this.formOptions, "fieldIdPrefix", "");
			return slugifyFormID(schema, idPrefix) + (unique ? "-" + uniqueId() : "");
		},

		getFieldClasses() {
			return objGet(this.schema, "fieldClasses", []);
		},

		formatValueToField(value) {
			return value;
		},

		formatValueToModel(value) {
			return value;
		}
	}
};
<commit_msg>Ref #563 - return unique validation errors (prevents multiple validators from returning "this field is required", etc)<commit_after>import { get as objGet, forEach, isFunction, isString, isArray, debounce, uniqueId, uniq as arrayUniq } from "lodash";
import validators from "../utils/validators";
import { slugifyFormID } from "../utils/schema";

function convertValidator(validator) {
	if (isString(validator)) {
		if (validators[validator] != null) return validators[validator];
		else {
			console.warn(`'${validator}' is not a validator function!`);
			return null; // caller need to handle null
		}
	}
	return validator;
}

function attributesDirective(el, binding, vnode) {
	let attrs = objGet(vnode.context, "schema.attributes", {});
	let container = binding.value || "input";
	if (isString(container)) {
		attrs = objGet(attrs, container) || attrs;
	}
	forEach(attrs, (val, key) => {
		el.setAttribute(key, val);
	});
}

export default {
	props: ["vfg", "model", "schema", "formOptions", "disabled"],

	data() {
		return {
			errors: [],
			debouncedValidateFunc: null,
			debouncedFormatFunc: null
		};
	},

	directives: {
		attributes: {
			bind: attributesDirective,
			updated: attributesDirective,
			componentUpdated: attributesDirective
		}
	},

	computed: {
		value: {
			cache: false,
			get() {
				let val;
				if (isFunction(objGet(this.schema, "get"))) {
					val = this.schema.get(this.model);
				} else {
					val = objGet(this.model, this.schema.model);
				}

				return this.formatValueToField(val);
			},

			set(newValue) {
				let oldValue = this.value;
				newValue = this.formatValueToModel(newValue);

				if (isFunction(newValue)) {
					newValue(newValue, oldValue);
				} else {
					this.updateModelValue(newValue, oldValue);
				}
			}
		}
	},

	methods: {
		validate(calledParent) {
			this.clearValidationErrors();
			let validateAsync = objGet(this.formOptions, "validateAsync", false);

			let results = [];

			if (this.schema.validator && this.schema.readonly !== true && this.disabled !== true) {
				let validators = [];
				if (!isArray(this.schema.validator)) {
					validators.push(convertValidator(this.schema.validator).bind(this));
				} else {
					forEach(this.schema.validator, validator => {
						validators.push(convertValidator(validator).bind(this));
					});
				}

				forEach(validators, validator => {
					if (validateAsync) {
						results.push(validator(this.value, this.schema, this.model));
					} else {
						let result = validator(this.value, this.schema, this.model);
						if (result && isFunction(result.then)) {
							result.then(err => {
								if (err) {
									this.errors = this.errors.concat(err);
								}
								let isValid = this.errors.length === 0;
								this.$emit("validated", isValid, this.errors, this);
							});
						} else if (result) {
							results = results.concat(result);
						}
					}
				});
			}

			let handleErrors = (errors) => {
				let fieldErrors = [];
				forEach(arrayUniq(errors), err => {
					if (isArray(err) && err.length > 0) {
						fieldErrors = fieldErrors.concat(err);
					} else if (isString(err)) {
						fieldErrors.push(err);
					}
				});
				if (isFunction(this.schema.onValidated)) {
					this.schema.onValidated.call(this, this.model, fieldErrors, this.schema);
				}

				let isValid = fieldErrors.length === 0;
				if (!calledParent) {
					this.$emit("validated", isValid, fieldErrors, this);
				}
				this.errors = fieldErrors;
				return fieldErrors;
			};

			if (!validateAsync) {
				return handleErrors(results);
			}

			return Promise.all(results).then(handleErrors);
		},

		debouncedValidate() {
			if (!isFunction(this.debouncedValidateFunc)) {
				this.debouncedValidateFunc = debounce(
					this.validate.bind(this),
					objGet(this.schema, "validateDebounceTime", objGet(this.formOptions, "validateDebounceTime", 500))
				);
			}
			this.debouncedValidateFunc();
		},

		updateModelValue(newValue, oldValue) {
			let changed = false;
			if (isFunction(this.schema.set)) {
				this.schema.set(this.model, newValue);
				changed = true;
			} else if (this.schema.model) {
				this.setModelValueByPath(this.schema.model, newValue);
				changed = true;
			}

			if (changed) {
				this.$emit("model-updated", newValue, this.schema.model);

				if (isFunction(this.schema.onChanged)) {
					this.schema.onChanged.call(this, this.model, newValue, oldValue, this.schema);
				}

				if (objGet(this.formOptions, "validateAfterChanged", false) === true) {
					if (objGet(this.schema, "validateDebounceTime", objGet(this.formOptions, "validateDebounceTime", 0)) > 0) {
						this.debouncedValidate();
					} else {
						this.validate();
					}
				}
			}
		},

		clearValidationErrors() {
			this.errors.splice(0);
		},

		setModelValueByPath(path, value) {
			// convert array indexes to properties
			let s = path.replace(/\[(\w+)\]/g, ".$1");

			// strip a leading dot
			s = s.replace(/^\./, "");

			let o = this.model;
			const a = s.split(".");
			let i = 0;
			const n = a.length;
			while (i < n) {
				let k = a[i];
				if (i < n - 1)
					if (o[k] !== undefined) {
						// Found parent property. Step in
						o = o[k];
					} else {
						// Create missing property (new level)
						this.$root.$set(o, k, {});
						o = o[k];
					}
				else {
					// Set final property value
					this.$root.$set(o, k, value);
					return;
				}

				++i;
			}
		},

		getFieldID(schema, unique = false) {
			const idPrefix = objGet(this.formOptions, "fieldIdPrefix", "");
			return slugifyFormID(schema, idPrefix) + (unique ? "-" + uniqueId() : "");
		},

		getFieldClasses() {
			return objGet(this.schema, "fieldClasses", []);
		},

		formatValueToField(value) {
			return value;
		},

		formatValueToModel(value) {
			return value;
		}
	}
};
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationPredicate',
  properties: [
    {
      name: 'predicateFactory'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      expression: function(predicateFactory) {
        return predicateFactory ?
          predicateFactory(foam.mlang.ExpressionsSingleton.create()) :
          null;
      }
    },
    {
      class: 'StringArray',
      name: 'args'
    },
    {
      class: 'Function',
      name: 'jsFunc',
      expression: function(predicate, jsErr) {
        return function() {
          if ( ! predicate.f(this) ) return jsErr(this);
        };
      }
    },
    {
      class: 'String',
      name: 'errorString'
    },
    {
      class: 'Function',
      name: 'jsErr',
      expression: function(errorString) {
        return function() { return errorString; };
      }
    }
  ],
  methods: [
    function createErrorSlotFor(data) {
      return this.ExpressionSlot.create({
        args: this.args.map(a => data[a+'$']),
        code: this.jsFunc.bind(data)
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PropertyValidationRefinement',
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      // We override 'compare' here because we need to avoid infinite recursion
      // that occurs when a validation predicate for a given property contains a
      // reference to the property itself.
      // This is an incorrect implementation of compare since it will always
      // return a match, even if the validation predicates are different. It
      // would be preferable to find a way to deal with circular references.
      compare: function() { return 0; }
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates) {
        if ( validationPredicates.length ) {
          var args = foam.Array.unique(validationPredicates
            .map(vp => vp.args)
            .flat());
          return [args, function() {
            for ( var i = 0 ; i < validationPredicates.length ; i++ ) {
              var vp = validationPredicates[i];
              if ( vp.jsFunc.bind(this)() ) return vp.jsErr.bind(this)();
            }
            return null;
          }];
        }
        return !required ? null : [[name],
          function() {
            return !this.hasOwnProperty(name) && (`Please enter ${label.toLowerCase()}`);
          }]
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'StringPropertyValidationRefinement',
  refines: 'foam.core.String',
  properties: [
    'minLength',
    'maxLength',
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), self.minLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with least ${this.minLength} character${this.minLength>1?'s':''}`
          });
        }
        if ( foam.Number.isInstance(this.maxLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.LTE(foam.mlang.StringLength.create({ arg1: self }), self.maxLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at most ${this.maxLength} character${this.maxLength>1?'s':''}`
          });
        }
        if ( this.required && ! foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), 1);
            },
            errorString: `Please enter a ${this.label}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'FObjectPropertyValidationRefinement',
  refines: 'foam.core.FObjectProperty',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates, autoValidate) {
        if ( autoValidate ) {
          return [
            [`${name}$errors_`],
            function(errs) {
              return errs ? `Please enter valid ${label.toLowerCase()}` : null;
            }
          ];
        }
        return foam.core.Property.VALIDATE_OBJ.expression.apply(this, arguments);
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'IntPropertyValidationRefinement',
  refines: 'foam.core.Int',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validationPredicates',
      factory: function() {
        if ( ! this.autoValidate ) return [];
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(self.min) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.GTE(self, self.min);
            },
            errorString: `Please enter ${self.label.toLowerCase()} greater than or equal to ${self.min}.`
          });
        }
        if ( foam.Number.isInstance(self.max) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.LTE(self, self.max);
            },
            errorString: `Please enter ${self.label.toLowerCase()} less than or equal to ${self.max}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'Errors',
//  extends: 'foam.core.Property',

  documentation: `
    A psedo-Property Axiom added to FObject which contains an object\'s validation errors.
    Adds the following attributes to an Object:

errors_

list of current errors

errors_$

Slot representation of errors_

validateObject()

calls the validateObj() method of all property Axioms, allowing them to populate errors_

  `,

  properties: [
    [ 'name', 'errors_' ]
  ],

  methods: [
    function installInProto(proto) {
      var self = this;
      Object.defineProperty(proto, 'errors_', {
        get: function() {
          return self.toSlot(this).get();
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, 'errors_$', {
        get: function() {
          return self.toSlot(this);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      var slotName = this.slotName_ || ( this.slotName_ = this.name + '$' );
      var slot     = obj.getPrivate_(slotName);

      if ( ! slot ) {
        slot = this.createErrorSlot_(obj)
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function createErrorSlot_(obj) {
      var args = [];
      var ps   = obj.cls_.getAxiomsByClass(foam.core.Property).
        filter(function(a) { return a.validateObj; });

      for ( var i = 0 ; i < ps.length ; i++ ) {
        var p = ps[i];
        args.push(obj.slot(p.validateObj));
      }

      function validateObject() {
        var ret;

        for ( var i = 0 ; i < ps.length ; i++ ) {
          var p = ps[i];
          var err = args[i].get();
          if ( err ) (ret || (ret = [])).push([p, err]);
        }

        return ret;
      }

      return foam.core.ExpressionSlot.create({
        obj: obj,
        code: validateObject,
        args: args
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationFObjectRefinement',
  refines: 'foam.core.FObject',

  axioms: [
    foam.core.internal.Errors.create()
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'EmailPropertyValidationRefinement',
  refines: 'foam.core.EMail',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var ret = [
          {
            args: [this.name],
            predicateFactory: function(e) {
              return e.REG_EXP(self, /^$|.+@.+\..+/);
            },
            errorString: 'Please enter email address'
          }
        ];
        if ( this.required ) {
          ret.push(
            {
              args: [this.name],
              predicateFactory: function(e) {
                return e.NEQ(self, '');
              },
              errorString: 'Please enter email address'
            }
          );
        }
        return ret;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PhoneNumberPropertyValidationRefinement',
  refines: 'foam.core.PhoneNumber',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        if ( ! this.required ) return [];
        return [
          {
            args: [this.name],
            predicateFactory: function(e) {
                return e.REG_EXP(
                  self,
                  /^(?:\+?1[-.●]?)?\(?([0-9]{3})\)?[-.●]?([0-9]{3})[-.●]?([0-9]{4})$/
                 );
            },
            errorString: 'Please enter valid phone number'
          }
        ];
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'DatePropertyValidationRefinement',
  refines: 'foam.core.Date',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        return [
          {
            args: [self.name],
            predicateFactory: function(e) {
              return e.OR(
                e.NOT(e.HAS(self)), // Allow null dates.
                e.AND(
                  e.LTE(
                    self,
                    // Maximum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(8640000000000000)
                  ),
                  e.GTE(
                    self,
                    // Minimum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(-8640000000000000)
                  )
                )
              );
            },
            errorString: 'Invalid date value'
          }
        ];
      }
    }
  ]
});
<commit_msg>addressed the comments on validation wordings - typo, etc<commit_after>/**
 * @license
 * Copyright 2016 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationPredicate',
  properties: [
    {
      name: 'predicateFactory'
    },
    {
      class: 'FObjectProperty',
      of: 'foam.mlang.predicate.Predicate',
      name: 'predicate',
      expression: function(predicateFactory) {
        return predicateFactory ?
          predicateFactory(foam.mlang.ExpressionsSingleton.create()) :
          null;
      }
    },
    {
      class: 'StringArray',
      name: 'args'
    },
    {
      class: 'Function',
      name: 'jsFunc',
      expression: function(predicate, jsErr) {
        return function() {
          if ( ! predicate.f(this) ) return jsErr(this);
        };
      }
    },
    {
      class: 'String',
      name: 'errorString'
    },
    {
      class: 'Function',
      name: 'jsErr',
      expression: function(errorString) {
        return function() { return errorString; };
      }
    }
  ],
  methods: [
    function createErrorSlotFor(data) {
      return this.ExpressionSlot.create({
        args: this.args.map(a => data[a+'$']),
        code: this.jsFunc.bind(data)
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PropertyValidationRefinement',
  refines: 'foam.core.Property',

  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      // We override 'compare' here because we need to avoid infinite recursion
      // that occurs when a validation predicate for a given property contains a
      // reference to the property itself.
      // This is an incorrect implementation of compare since it will always
      // return a match, even if the validation predicates are different. It
      // would be preferable to find a way to deal with circular references.
      compare: function() { return 0; }
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates) {
        if ( validationPredicates.length ) {
          var args = foam.Array.unique(validationPredicates
            .map(vp => vp.args)
            .flat());
          return [args, function() {
            for ( var i = 0 ; i < validationPredicates.length ; i++ ) {
              var vp = validationPredicates[i];
              if ( vp.jsFunc.bind(this)() ) return vp.jsErr.bind(this)();
            }
            return null;
          }];
        }
        return !required ? null : [[name],
          function() {
            return !this.hasOwnProperty(name) && (`Please enter ${label.toLowerCase()}`);
          }]
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'StringPropertyValidationRefinement',
  refines: 'foam.core.String',
  properties: [
    'minLength',
    'maxLength',
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), self.minLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at least ${this.minLength} character${this.minLength>1?'s':''}`
          });
        }
        if ( foam.Number.isInstance(this.maxLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.LTE(foam.mlang.StringLength.create({ arg1: self }), self.maxLength);
            },
            errorString: `Please enter ${this.label.toLowerCase()} with at most ${this.maxLength} character${this.maxLength>1?'s':''}`
          });
        }
        if ( this.required && ! foam.Number.isInstance(this.minLength) ) {
          a.push({
            args: [this.name],
            predicateFactory: function(e) {
              return e.GTE(foam.mlang.StringLength.create({ arg1: self }), 1);
            },
            errorString: `Please enter ${this.label.toLowerCase()}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'FObjectPropertyValidationRefinement',
  refines: 'foam.core.FObjectProperty',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validateObj',
      expression: function(name, label, required, validationPredicates, autoValidate) {
        if ( autoValidate ) {
          return [
            [`${name}$errors_`],
            function(errs) {
              return errs ? `Please enter valid ${label.toLowerCase()}` : null;
            }
          ];
        }
        return foam.core.Property.VALIDATE_OBJ.expression.apply(this, arguments);
      },
    },
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'IntPropertyValidationRefinement',
  refines: 'foam.core.Int',
  properties: [
    {
      class: 'Boolean',
      name: 'autoValidate'
    },
    {
      name: 'validationPredicates',
      factory: function() {
        if ( ! this.autoValidate ) return [];
        var self = this;
        var a = [];
        if ( foam.Number.isInstance(self.min) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.GTE(self, self.min);
            },
            errorString: `Please enter ${self.label.toLowerCase()} greater than or equal to ${self.min}.`
          });
        }
        if ( foam.Number.isInstance(self.max) ) {
          a.push({
            args: [self.name],
            predicateFactory: function(e) {
              return e.LTE(self, self.max);
            },
            errorString: `Please enter ${self.label.toLowerCase()} less than or equal to ${self.max}`
          });
        }
        return a;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core.internal',
  name: 'Errors',
//  extends: 'foam.core.Property',

  documentation: `
    A psedo-Property Axiom added to FObject which contains an object\'s validation errors.
    Adds the following attributes to an Object:

errors_

list of current errors

errors_$

Slot representation of errors_

validateObject()

calls the validateObj() method of all property Axioms, allowing them to populate errors_

  `,

  properties: [
    [ 'name', 'errors_' ]
  ],

  methods: [
    function installInProto(proto) {
      var self = this;
      Object.defineProperty(proto, 'errors_', {
        get: function() {
          return self.toSlot(this).get();
        },
        configurable: true,
        enumerable: false
      });

      Object.defineProperty(proto, 'errors_$', {
        get: function() {
          return self.toSlot(this);
        },
        configurable: true,
        enumerable: false
      });
    },

    function toSlot(obj) {
      var slotName = this.slotName_ || ( this.slotName_ = this.name + '$' );
      var slot     = obj.getPrivate_(slotName);

      if ( ! slot ) {
        slot = this.createErrorSlot_(obj)
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function createErrorSlot_(obj) {
      var args = [];
      var ps   = obj.cls_.getAxiomsByClass(foam.core.Property).
        filter(function(a) { return a.validateObj; });

      for ( var i = 0 ; i < ps.length ; i++ ) {
        var p = ps[i];
        args.push(obj.slot(p.validateObj));
      }

      function validateObject() {
        var ret;

        for ( var i = 0 ; i < ps.length ; i++ ) {
          var p = ps[i];
          var err = args[i].get();
          if ( err ) (ret || (ret = [])).push([p, err]);
        }

        return ret;
      }

      return foam.core.ExpressionSlot.create({
        obj: obj,
        code: validateObject,
        args: args
      });
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'ValidationFObjectRefinement',
  refines: 'foam.core.FObject',

  axioms: [
    foam.core.internal.Errors.create()
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'EmailPropertyValidationRefinement',
  refines: 'foam.core.EMail',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        var ret = [
          {
            args: [this.name],
            predicateFactory: function(e) {
              return e.REG_EXP(self, /^$|.+@.+\..+/);
            },
            errorString: 'Please enter email address'
          }
        ];
        if ( this.required ) {
          ret.push(
            {
              args: [this.name],
              predicateFactory: function(e) {
                return e.NEQ(self, '');
              },
              errorString: 'Please enter email address'
            }
          );
        }
        return ret;
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'PhoneNumberPropertyValidationRefinement',
  refines: 'foam.core.PhoneNumber',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        if ( ! this.required ) return [];
        return [
          {
            args: [this.name],
            predicateFactory: function(e) {
                return e.REG_EXP(
                  self,
                  /^(?:\+?1[-.●]?)?\(?([0-9]{3})\)?[-.●]?([0-9]{3})[-.●]?([0-9]{4})$/
                 );
            },
            errorString: 'Please enter phone number'
          }
        ];
      }
    }
  ]
});

foam.CLASS({
  package: 'foam.core',
  name: 'DatePropertyValidationRefinement',
  refines: 'foam.core.Date',
  properties: [
    {
      class: 'FObjectArray',
      of: 'foam.core.ValidationPredicate',
      name: 'validationPredicates',
      factory: function() {
        var self = this;
        return [
          {
            args: [self.name],
            predicateFactory: function(e) {
              return e.OR(
                e.NOT(e.HAS(self)), // Allow null dates.
                e.AND(
                  e.LTE(
                    self,
                    // Maximum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(8640000000000000)
                  ),
                  e.GTE(
                    self,
                    // Minimum date supported by FOAM
                    // (bounded by JavaScript's limit)
                    new Date(-8640000000000000)
                  )
                )
              );
            },
            errorString: 'Invalid date value'
          }
        ];
      }
    }
  ]
});
<|endoftext|>


<commit_before>/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  A Faceted Axiom, when added to a Class, makes it implement
  the Facet-Manager Pattern, meaning that calls to create() can
  be intercepted and return a special Facet class depending on the
  value of the 'of' create argument.

  Ex.:
  foam.CLASS({
    name: 'View',
    axioms: [ foam.pattern.Faceted.create() ],
    properties: [ 'of' ],
    methods: [ function view() { return 'default' } ]
  });

  foam.CLASS({name: 'A'});
  foam.CLASS({name: 'B'});
  foam.CLASS({name: 'C'});
  foam.CLASS({name: 'BView', extends: 'View', methods: [function view() { return 'BView'; }]});
  foam.CLASS({name: 'CView', extends: 'View', methods: [function view() { return 'CView'; }]});

  console.log(View.create({of: A}));
  console.log(View.create({of: B}));
  console.log(View.create({of: C}));
*/
// FUTURE: add createOriginal() (or similar) method.
foam.CLASS({
  package: 'foam.pattern',
  name: 'Faceted',

  methods: [
    function installInClass(cls) {
      var oldCreate = cls.create;

      cls.getFacetOf = function(of, X) {
        if ( ! of ) return this;
        X = X || foam.__context__;

        var name   = foam.String.isInstance(of) ? of : of.name;
        var facets = this.private_.facets || ( this.private_.facets = {} );

        if ( ! facets.hasOwnProperty(name) ) {
          var id = this.package ?
            this.package + '.' + name + this.name :
            name + this.name ;
          facets[name] = X.lookup(id, true) || this;
        }

        return facets[name];
      };

      // ignoreFacets is set to true when called to prevent a second-level
      // of facet checking
      cls.create = function(args, X, ignoreFacets) {
        if ( ! ignoreFacets ) {
          var facetCls = this.getFacetOf(args && args.of, X);

          if ( facetCls ) return facetCls.create(args, X, true);
        }

        return oldCreate.apply(this, arguments);
      }
    }
  ],

  properties: [
    ['name', 'foam.pattern.Faceted']
  ]
});
<commit_msg>Faceted:  Don't call delegate create if facetCls === this.<commit_after>/**
 * @license
 * Copyright 2017 Google Inc. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
  A Faceted Axiom, when added to a Class, makes it implement
  the Facet-Manager Pattern, meaning that calls to create() can
  be intercepted and return a special Facet class depending on the
  value of the 'of' create argument.

  Ex.:
  foam.CLASS({
    name: 'View',
    axioms: [ foam.pattern.Faceted.create() ],
    properties: [ 'of' ],
    methods: [ function view() { return 'default' } ]
  });

  foam.CLASS({name: 'A'});
  foam.CLASS({name: 'B'});
  foam.CLASS({name: 'C'});
  foam.CLASS({name: 'BView', extends: 'View', methods: [function view() { return 'BView'; }]});
  foam.CLASS({name: 'CView', extends: 'View', methods: [function view() { return 'CView'; }]});

  console.log(View.create({of: A}));
  console.log(View.create({of: B}));
  console.log(View.create({of: C}));
*/
// FUTURE: add createOriginal() (or similar) method.
foam.CLASS({
  package: 'foam.pattern',
  name: 'Faceted',

  methods: [
    function installInClass(cls) {
      var oldCreate = cls.create;

      cls.getFacetOf = function(of, X) {
        if ( ! of ) return this;
        X = X || foam.__context__;

        var name   = foam.String.isInstance(of) ? of : of.name;
        var facets = this.private_.facets || ( this.private_.facets = {} );

        if ( ! facets.hasOwnProperty(name) ) {
          var id = this.package ?
            this.package + '.' + name + this.name :
            name + this.name ;
          facets[name] = X.lookup(id, true) || this;
        }

        return facets[name];
      };

      // ignoreFacets is set to true when called to prevent a second-level
      // of facet checking
      cls.create = function(args, X, ignoreFacets) {
        if ( ! ignoreFacets ) {
          var facetCls = this.getFacetOf(args && args.of, X);

          if ( facetCls !== this ) return facetCls.create(args, X, true);
        }

        return oldCreate.apply(this, arguments);
      }
    }
  ],

  properties: [
    ['name', 'foam.pattern.Faceted']
  ]
});
<|endoftext|>


<commit_before>var _ = require('underscore');
var config = require('../../cdb.config');
var LayerModelBase = require('./layer-model-base');
var InfowindowTemplate = require('./infowindow-template');
var TooltipTemplate = require('./tooltip-template');
var Legends = require('./legends/legends');
window.LayerGroupCollection = {};
var ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD = ['sql', 'source', 'sql_wrap', 'cartocss'];

var RasterLayer = LayerModelBase.extend({
  defaults: {
    type: 'Raster',
    attribution: config.get('cartodb_attributions')
  },

  initialize: function (attrs, options) {
    attrs = attrs || {};
    options = options || {};
    if (!options.vis) throw new Error('vis is required');

    this._vis = options.vis;
    if (attrs && attrs.cartocss) {
      this.set('initialStyle', attrs.cartocss);
    }

    // Hopefully in the future (@apercas)
      // // PUBLIC PROPERTIES
      // this.infowindow = new InfowindowTemplate(attrs.infowindow);
      // this.tooltip = new TooltipTemplate(attrs.tooltip);
      // this.unset('infowindow');
      // this.unset('tooltip');

      // this.legends = new Legends(attrs.legends, {
      //   visModel: this._vis
      // });
      // this.unset('legends');

      this.bind('change', this._onAttributeChanged, this);
      // this.infowindow.fields.bind('reset add remove', this._reloadVis, this);
      // this.tooltip.fields.bind('reset add remove', this._reloadVis, this);

    // Pretty basic raster config
    const RASTERCONFIG = {
        "version": "1.3.1",
        "layers": [
            {
                "type": "cartodb",
                "options": {
                    "sql": " SELECT * FROM " + attrs.layer_name,
                    "cartocss": "#" + attrs.layer_name + " {raster-opacity: 1}",
                    "cartocss_version": "2.3.0",
                    "geom_column": "the_raster_webmercator",
                    "geom_type": "raster"
                }
            }
        ]
    };
    this.config = RASTERCONFIG;
    this._newRasterLayer();

    LayerModelBase.prototype.initialize.apply(this, arguments);
  },

  _onAttributeChanged: function () {
    var reloadVis = _.any(ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD, function (attr) {
      if (this.hasChanged(attr)) {
        if (attr === 'cartocss' && this._dataProvider) {
          return false;
        }
        return true;
      }
    }, this);

    if (reloadVis) {
      this._reloadVis();
    }
  },

  _reloadVis: function () {
    this._vis.reload({
      sourceId: this.get('id')
    });
  },

  restoreCartoCSS: function () {
    this.set('cartocss', this.get('initialStyle'));
  },

  isVisible: function () {
    return this.get('visible');
  },

  _hasInfowindowFields: function () {
    return this.infowindow.hasFields();
  },

  _hasTooltipFields: function () {
    return this.tooltip.hasFields();
  },

  getGeometryType: function () {
    if (this._dataProvider) {
      var index = this._dataProvider._layerIndex;
      var sublayer = this._dataProvider._vectorLayerView.renderers[index];
      return sublayer.inferGeometryType();
    }
    return null;
  },

  getInteractiveColumnNames: function () {
    return ['cartodb_id'];
    return _.chain(['cartodb_id'])
      .union(this.infowindow.getFieldNames())
      .union(this.tooltip.getFieldNames())
      .uniq()
      .value();
  },

  isInfowindowEnabled: function () {
    return this.infowindow.hasTemplate();
  },

  isTooltipEnabled: function () {
    return this.tooltip.hasTemplate();
  },

  getName: function () {
    return this.get('layer_name');
  },

  setDataProvider: function (dataProvider) {
    this._dataProvider = dataProvider;
  },

  getDataProvider: function () {
    return this._dataProvider;
  },

  _newRasterLayer: function () {
    const CURRENTURI = window.location;
    var USER = '';
    if (CURRENTURI.href.split('user/').length === 2) {
      USER = CURRENTURI.href.split('user/')[1].split('/')[0];
    } else {
      USER = CURRENTURI.href.split('//')[1].split('/')[0].split('.')[0];
    }
    const DOMAIN  = CURRENTURI.href.split('//')[1].split('/')[0];
    const APIKEY  = this._vis.attributes.apiKey;
    const APIURL  = CURRENTURI.protocol+ "//" + DOMAIN + "/user/" + USER + "/api/v1/map";
    const SELF    = this;

    function currentEndpoint() {
      return APIURL;
    }
    function getourThis(){
      return SELF;
    }

    //based on torque.js/lib/torque/provider/windshaft.js
    var request = new XMLHttpRequest();
    request.open('POST', currentEndpoint() + "?api_key=" + APIKEY, true);
    request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
    request.onload = function() {
        if (this.status >= 200 && this.status < 400){
            const DATA      = JSON.parse(this.response);
            const SELF      = getourThis();
            const ENDPOINT  = currentEndpoint() + "/" + DATA.layergroupid + "/{z}/{x}/{y}.png?api_key=" + APIKEY;
            window.LayerGroupCollection[SELF.attributes.layer_name] = DATA.layergroupid;

            // store layergroupid on LayerGroupCollection but don't load the layer on the map yet
            if (SELF.attributes.visible == false) return false;
            rasterLayer = L.tileLayer(ENDPOINT, {
                maxZoom: 18
            }).addTo(SELF._vis.map);
        } else {
            throw 'Error calling server: Error ' + this.status + ' -> ' + this.response;
        }
    };
    request.send(JSON.stringify(this.config));

  }
});

module.exports = RasterLayer;
<commit_msg>get user when on domains and organizations mode<commit_after>var _ = require('underscore');
var config = require('../../cdb.config');
var LayerModelBase = require('./layer-model-base');
var InfowindowTemplate = require('./infowindow-template');
var TooltipTemplate = require('./tooltip-template');
var Legends = require('./legends/legends');
window.LayerGroupCollection = {};
var ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD = ['sql', 'source', 'sql_wrap', 'cartocss'];

var RasterLayer = LayerModelBase.extend({
  defaults: {
    type: 'Raster',
    attribution: config.get('cartodb_attributions')
  },

  initialize: function (attrs, options) {
    attrs = attrs || {};
    options = options || {};
    if (!options.vis) throw new Error('vis is required');

    this._vis = options.vis;
    if (attrs && attrs.cartocss) {
      this.set('initialStyle', attrs.cartocss);
    }

    // Hopefully in the future (@apercas)
      // // PUBLIC PROPERTIES
      // this.infowindow = new InfowindowTemplate(attrs.infowindow);
      // this.tooltip = new TooltipTemplate(attrs.tooltip);
      // this.unset('infowindow');
      // this.unset('tooltip');

      // this.legends = new Legends(attrs.legends, {
      //   visModel: this._vis
      // });
      // this.unset('legends');

      this.bind('change', this._onAttributeChanged, this);
      // this.infowindow.fields.bind('reset add remove', this._reloadVis, this);
      // this.tooltip.fields.bind('reset add remove', this._reloadVis, this);

    // Pretty basic raster config
    const RASTERCONFIG = {
        "version": "1.3.1",
        "layers": [
            {
                "type": "cartodb",
                "options": {
                    "sql": " SELECT * FROM " + attrs.layer_name,
                    "cartocss": "#" + attrs.layer_name + " {raster-opacity: 1}",
                    "cartocss_version": "2.3.0",
                    "geom_column": "the_raster_webmercator",
                    "geom_type": "raster"
                }
            }
        ]
    };
    this.config = RASTERCONFIG;
    this._newRasterLayer();

    LayerModelBase.prototype.initialize.apply(this, arguments);
  },

  _onAttributeChanged: function () {
    var reloadVis = _.any(ATTRIBUTES_THAT_TRIGGER_VIS_RELOAD, function (attr) {
      if (this.hasChanged(attr)) {
        if (attr === 'cartocss' && this._dataProvider) {
          return false;
        }
        return true;
      }
    }, this);

    if (reloadVis) {
      this._reloadVis();
    }
  },

  _reloadVis: function () {
    this._vis.reload({
      sourceId: this.get('id')
    });
  },

  restoreCartoCSS: function () {
    this.set('cartocss', this.get('initialStyle'));
  },

  isVisible: function () {
    return this.get('visible');
  },

  _hasInfowindowFields: function () {
    return this.infowindow.hasFields();
  },

  _hasTooltipFields: function () {
    return this.tooltip.hasFields();
  },

  getGeometryType: function () {
    if (this._dataProvider) {
      var index = this._dataProvider._layerIndex;
      var sublayer = this._dataProvider._vectorLayerView.renderers[index];
      return sublayer.inferGeometryType();
    }
    return null;
  },

  getInteractiveColumnNames: function () {
    return ['cartodb_id'];
    return _.chain(['cartodb_id'])
      .union(this.infowindow.getFieldNames())
      .union(this.tooltip.getFieldNames())
      .uniq()
      .value();
  },

  isInfowindowEnabled: function () {
    return this.infowindow.hasTemplate();
  },

  isTooltipEnabled: function () {
    return this.tooltip.hasTemplate();
  },

  getName: function () {
    return this.get('layer_name');
  },

  setDataProvider: function (dataProvider) {
    this._dataProvider = dataProvider;
  },

  getDataProvider: function () {
    return this._dataProvider;
  },

  _newRasterLayer: function () {
    const CURRENTURI = window.location;
    var USER = '';
    if (CURRENTURI.href.split('user/').length === 2) {
      USER = CURRENTURI.href.split('user/')[1].split('/')[0];
    } else if (CURRENTURI.href.split('/u/').length == 2) {
      // organizations & domains mode
      USER = CURRENTURI.href.split('/u/')[1].split('/')[0]
    } else {
      USER = CURRENTURI.href.split('//')[1].split('/')[0].split('.')[0];
    }
    const DOMAIN  = CURRENTURI.href.split('//')[1].split('/')[0];
    const APIKEY  = this._vis.attributes.apiKey;
    const APIURL  = CURRENTURI.protocol+ "//" + DOMAIN + "/user/" + USER + "/api/v1/map";
    const SELF    = this;

    function currentEndpoint() {
      return APIURL;
    }
    function getourThis(){
      return SELF;
    }

    //based on torque.js/lib/torque/provider/windshaft.js
    var request = new XMLHttpRequest();
    request.open('POST', currentEndpoint() + "?api_key=" + APIKEY, true);
    request.setRequestHeader('Content-Type', 'application/json; charset=UTF-8');
    request.onload = function() {
        if (this.status >= 200 && this.status < 400){
            const DATA      = JSON.parse(this.response);
            const SELF      = getourThis();
            const ENDPOINT  = currentEndpoint() + "/" + DATA.layergroupid + "/{z}/{x}/{y}.png?api_key=" + APIKEY;
            window.LayerGroupCollection[SELF.attributes.layer_name] = DATA.layergroupid;

            // store layergroupid on LayerGroupCollection but don't load the layer on the map yet
            if (SELF.attributes.visible == false) return false;
            rasterLayer = L.tileLayer(ENDPOINT, {
                maxZoom: 18
            }).addTo(SELF._vis.map);
        } else {
            throw 'Error calling server: Error ' + this.status + ' -> ' + this.response;
        }
    };
    request.send(JSON.stringify(this.config));

  }
});

module.exports = RasterLayer;
<|endoftext|>


<commit_before>'use strict';
const path = require('path');
const electron = require('electron');

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const shell = electron.shell;
let tray = null;
const issueURL = 'https://github.com/klauscfhq/ao/issues/new';

function activate(command) {
  const appWindow = BrowserWindow.getAllWindows()[0];
  // Extra measure in order to be shown
  appWindow.show();
  appWindow.webContents.send(command);
}

exports.create = win => {
  if (process.platform === 'darwin' || tray) {
    return;
  }

  const iconPath = path.join(__dirname, 'static/IconTray.png');

  const toggleWin = () => {
    // Toggle/untoggle window
    if (win.isVisible()) {
      win.hide();
    } else {
      win.show();
    }
  };

  const showWin = () => {
    // Bring window on top if not visible
    if (!win.isVisible()) {
      win.show();
    }
  };

  const contextMenu = electron.Menu.buildFromTemplate([{
    label: 'Open Ao',
    click() {
      toggleWin();
    }
  }, {
    type: 'separator'
  }, {
    label: 'Search',
    click() {
      showWin();
      activate('search');
    }
  }, {
    type: 'separator'
  }, {
    label: 'My Day',
    click() {
      showWin();
      activate('my-day');
    }
  }, {
    label: 'New List',
    click() {
      showWin();
      activate('new-list');
    }
  }, {
    label: 'New Todo',
    click() {
      showWin();
      activate('new-todo');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sidebar',
    click() {
      showWin();
      activate('toggle-sidebar');
    }
  }, {
    label: 'Toggle Sepia Mode',
    click() {
      showWin();
      activate('toggle-sepia-mode');
    }
  }, {
    label: 'Toggle Dark Mode',
    click() {
      showWin();
      activate('toggle-dark-mode');
    }
  }, {
    type: 'separator'
  }, {
    label: `Settings`,
    click() {
      showWin();
      activate('settings');
    }
  }, {
    label: `Report Issue`,
    click() {
      shell.openExternal(issueURL);
    }
  }, {
    type: 'separator'
  }, {
    role: 'quit'
  }]);

  tray = new electron.Tray(iconPath);
  tray.setToolTip(`${app.getName()}`);
  tray.setContextMenu(contextMenu);
  tray.on('click', toggleWin);
};
<commit_msg>Toggle `black theme` from tray<commit_after>'use strict';
const path = require('path');
const electron = require('electron');

const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const shell = electron.shell;
let tray = null;
const issueURL = 'https://github.com/klauscfhq/ao/issues/new';

function activate(command) {
  const appWindow = BrowserWindow.getAllWindows()[0];
  // Extra measure in order to be shown
  appWindow.show();
  appWindow.webContents.send(command);
}

exports.create = win => {
  if (process.platform === 'darwin' || tray) {
    return;
  }

  const iconPath = path.join(__dirname, 'static/IconTray.png');

  const toggleWin = () => {
    // Toggle/untoggle window
    if (win.isVisible()) {
      win.hide();
    } else {
      win.show();
    }
  };

  const showWin = () => {
    // Bring window on top if not visible
    if (!win.isVisible()) {
      win.show();
    }
  };

  const contextMenu = electron.Menu.buildFromTemplate([{
    label: 'Open Ao',
    click() {
      toggleWin();
    }
  }, {
    type: 'separator'
  }, {
    label: 'Search',
    click() {
      showWin();
      activate('search');
    }
  }, {
    type: 'separator'
  }, {
    label: 'My Day',
    click() {
      showWin();
      activate('my-day');
    }
  }, {
    label: 'New List',
    click() {
      showWin();
      activate('new-list');
    }
  }, {
    label: 'New Todo',
    click() {
      showWin();
      activate('new-todo');
    }
  }, {
    type: 'separator'
  }, {
    label: 'Toggle Sidebar',
    click() {
      showWin();
      activate('toggle-sidebar');
    }
  }, {
    label: 'Toggle Sepia Mode',
    click() {
      showWin();
      activate('toggle-sepia-mode');
    }
  }, {
    label: 'Toggle Black Mode',
    click() {
      showWin();
      activate('toggle-black-mode');
    }
  }, {
    label: 'Toggle Dark Mode',
    click() {
      showWin();
      activate('toggle-dark-mode');
    }
  }, {
    type: 'separator'
  }, {
    label: `Settings`,
    click() {
      showWin();
      activate('settings');
    }
  }, {
    label: `Report Issue`,
    click() {
      shell.openExternal(issueURL);
    }
  }, {
    type: 'separator'
  }, {
    role: 'quit'
  }]);

  tray = new electron.Tray(iconPath);
  tray.setToolTip(`${app.getName()}`);
  tray.setContextMenu(contextMenu);
  tray.on('click', toggleWin);
};
<|endoftext|>


<commit_before>/**
 * @file Application Core
 * @author <%= userName %>
 * @version 1.0.0
 * @license MIT
 * @module app
 * @exports app
**/
define(function(require, exports, module) {
    'use strict';

    require('shims/marionette.radio.shim');
    require('handlebars.helpers');
    require('jquery.extensions');
    require('underscore.mixins');

    /**
     * @class ApplicationModel
     * @extends Backbone.Model
     * @prop {object} default
     * @prop {string} default.name='<%= projectName %>'
    **/
    var ApplicationModel = Backbone.Model.extend({
        defaults: {
            name: '<%= projectName %>'
        }
    });
    var App = new Marionette.Application();
    App.model = new ApplicationModel();
    App.regions = new Marionette.RegionManager({
        regions: {
            'root': 'body'
        }
    });

    module.exports = App;
});
<commit_msg>Removed white space<commit_after>/**
 * @file Application Core
 * @author <%= userName %>
 * @version 1.0.0
 * @license MIT
 * @module app
 * @exports app
**/
define(function(require, exports, module) {
    'use strict';

    require('shims/marionette.radio.shim');
    require('handlebars.helpers');
    require('jquery.extensions');
    require('underscore.mixins');

    /**
     * @class ApplicationModel
     * @extends Backbone.Model
     * @prop {object} default
     * @prop {string} default.name='<%= projectName %>'
    **/
    var ApplicationModel = Backbone.Model.extend({
        defaults: {
            name: '<%= projectName %>'
        }
    });
    var App = new Marionette.Application();
    App.model = new ApplicationModel();
    App.regions = new Marionette.RegionManager({
        regions: {
            'root': 'body'
        }
    });

    module.exports = App;
});
<|endoftext|>


<commit_before>define([
	'goo/renderer/MeshData',
	'goo/util/ObjectUtil'
	],
/** @lends */

function(
	MeshData,
	_
) {
	'use strict';

	/**
	 * @class An axis-aligned rectangular prism defined by a center point and x-, y- and z-extents (radii) from that center.
	 * @description Creates a new box.
	 * @param {number} [width=1] Total width of box.
	 * @param {number} [height=1] Total height of box.
	 * @param {number} [length=1] Total length of box.
	 */

	function SimpleBox(width, height, length) {
		if (arguments.length === 1 && arguments[0] instanceof Object) {
			var props = arguments[0];
			width = props.width;
			height = props.height;
			length = props.length;
		}

		this.xExtent = width !== undefined ? width * 0.5 : 0.5;
		this.yExtent = height !== undefined ? height * 0.5 : 0.5;
		this.zExtent = length !== undefined ? length * 0.5 : 0.5;

		var attributeMap = MeshData.defaultMap([MeshData.POSITION]);
		MeshData.call(this, attributeMap, 8, 36);

		this.rebuild();
	}

	SimpleBox.prototype = Object.create(MeshData.prototype);
	SimpleBox.prototype.constructor = SimpleBox;

	/**
	 * @description Builds or rebuilds the mesh data.
	 * @returns {SimpleBox} Self for chaining.
	 */
	SimpleBox.prototype.rebuild = function() {
		var xExtent = this.xExtent;
		var yExtent = this.yExtent;
		var zExtent = this.zExtent;

		this.getAttributeBuffer(MeshData.POSITION).set([
			-xExtent, -yExtent, -zExtent,
			 xExtent, -yExtent, -zExtent,
			 xExtent,  yExtent, -zExtent,
			-xExtent,  yExtent, -zExtent,

			-xExtent, -yExtent,  zExtent,
			 xExtent, -yExtent,  zExtent,
			 xExtent,  yExtent,  zExtent,
			-xExtent,  yExtent,  zExtent
		]);

		this.getIndexBuffer().set([
			//front
			0, 1, 2, 2, 3, 0,
			//back
			7, 6, 5, 5, 4, 7,
			//left
			0, 3, 7, 7, 4, 0,
			//right
			1, 2, 6, 6, 5, 1,
			//top
			3, 2, 6, 6, 7, 3,
			//bottom
			0, 1, 5, 5, 4, 0
		]);

		return this;
	};

	/**
	 * Returns a clone of this quad
	 * @returns {SimpleBox}
	 */
	SimpleBox.prototype.clone = function () {
		var options = _.shallowSelectiveClone(this, ['xExtent', 'yExtent', 'zExtent']);

		return new SimpleBox(options);
	};

	return SimpleBox;
});<commit_msg>Fixed SimpleBox winding<commit_after>define([
	'goo/renderer/MeshData',
	'goo/util/ObjectUtil'
	],
/** @lends */

function(
	MeshData,
	_
) {
	'use strict';

	/**
	 * @class An axis-aligned rectangular prism defined by a center point and x-, y- and z-extents (radii) from that center.
	 * @description Creates a new box.
	 * @param {number} [width=1] Total width of box.
	 * @param {number} [height=1] Total height of box.
	 * @param {number} [length=1] Total length of box.
	 */
	function SimpleBox(width, height, length) {
		if (arguments.length === 1 && arguments[0] instanceof Object) {
			var props = arguments[0];
			width = props.width;
			height = props.height;
			length = props.length;
		}

		this.xExtent = width !== undefined ? width * 0.5 : 0.5;
		this.yExtent = height !== undefined ? height * 0.5 : 0.5;
		this.zExtent = length !== undefined ? length * 0.5 : 0.5;

		var attributeMap = MeshData.defaultMap([MeshData.POSITION]);
		MeshData.call(this, attributeMap, 8, 36);

		this.rebuild();
	}

	SimpleBox.prototype = Object.create(MeshData.prototype);
	SimpleBox.prototype.constructor = SimpleBox;

	/**
	 * @description Builds or rebuilds the mesh data.
	 * @returns {SimpleBox} Self for chaining.
	 */
	SimpleBox.prototype.rebuild = function() {
		var xExtent = this.xExtent;
		var yExtent = this.yExtent;
		var zExtent = this.zExtent;

		this.getAttributeBuffer(MeshData.POSITION).set([
			-xExtent, -yExtent, -zExtent,
			 xExtent, -yExtent, -zExtent,
			 xExtent,  yExtent, -zExtent,
			-xExtent,  yExtent, -zExtent,

			-xExtent, -yExtent,  zExtent,
			 xExtent, -yExtent,  zExtent,
			 xExtent,  yExtent,  zExtent,
			-xExtent,  yExtent,  zExtent
		]);

		this.getIndexBuffer().set([
			//front
			2, 1, 0, 0, 3, 2,
			//back
			5, 6, 7, 7, 4, 5,
			//left
			7, 3, 0, 0, 4, 7,
			//right
			1, 2, 6, 6, 5, 1,
			//top
			6, 2, 3, 3, 7, 6,
			//bottom
			0, 1, 5, 5, 4, 0
		]);

		return this;
	};

	/**
	 * Returns a clone of this quad
	 * @returns {SimpleBox}
	 */
	SimpleBox.prototype.clone = function () {
		var options = _.shallowSelectiveClone(this, ['xExtent', 'yExtent', 'zExtent']);

		return new SimpleBox(options);
	};

	return SimpleBox;
});<|endoftext|>