<commit_before>import ObjectRenderer from '../../renderers/webgl/utils/ObjectRenderer';
import WebGLRenderer from '../../renderers/webgl/WebGLRenderer';
import createIndicesForQuads from '../../utils/createIndicesForQuads';
import generateMultiTextureShader from './generateMultiTextureShader';
import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader';
import Buffer from './BatchBuffer';
import { CAN_UPLOAD_SAME_BUFFER } from '../../const';
import settings from '../../settings';
import glCore from 'pixi-gl-core';
import bitTwiddle from 'bit-twiddle';

const { SPRITE_BATCH_SIZE, SPRITE_MAX_TEXTURES } = settings;

let TICK = 0;
let TEXTURE_TICK = 0;

/**
 * Renderer dedicated to drawing and batching sprites.
 *
 * @class
 * @private
 * @memberof PIXI
 * @extends PIXI.ObjectRenderer
 */
export default class SpriteRenderer extends ObjectRenderer
{
    /**
     * @param {PIXI.WebGLRenderer} renderer - The renderer this sprite batch works for.
     */
    constructor(renderer)
    {
        super(renderer);

        /**
         * Number of values sent in the vertex buffer.
         * positionX, positionY, colorR, colorG, colorB = 5
         *
         * @member {number}
         */
        this.vertSize = 5;

        /**
         * The size of the vertex information in bytes.
         *
         * @member {number}
         */
        this.vertByteSize = this.vertSize * 4;

        /**
         * The number of images in the SpriteBatch before it flushes.
         *
         * @member {number}
         */
        this.size = SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop

        // the total number of bytes in our batch
        // let numVerts = this.size * 4 * this.vertByteSize;

        this.buffers = [];
        for (let i = 1; i <= bitTwiddle.nextPow2(this.size); i *= 2)
        {
            this.buffers.push(new Buffer(i * 4 * this.vertByteSize));
        }

        /**
         * Holds the indices of the geometry (quads) to draw
         *
         * @member {Uint16Array}
         */
        this.indices = createIndicesForQuads(this.size);

        /**
         * The default shaders that is used if a sprite doesn't have a more specific one.
         * there is a shader for each number of textures that can be rendererd.
         * These shaders will also be generated on the fly as required.
         * @member {PIXI.Shader[]}
         */
        this.shader = null;

        this.currentIndex = 0;
        TICK = 0;
        this.groups = [];

        for (let k = 0; k < this.size; k++)
        {
            this.groups[k] = { textures: [], textureCount: 0, ids: [], size: 0, start: 0, blend: 0 };
        }

        this.sprites = [];

        this.vertexBuffers = [];
        this.vaos = [];

        this.vaoMax = 2;
        this.vertexCount = 0;

        this.renderer.on('prerender', this.onPrerender, this);
    }

    /**
     * Sets up the renderer context and necessary buffers.
     *
     * @private
     */
    onContextChange()
    {
        const gl = this.renderer.gl;

        // step 1: first check max textures the GPU can handle.
        this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), SPRITE_MAX_TEXTURES);

        // step 2: check the maximum number of if statements the shader can have too..
        this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl);

        const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES);

        // create a couple of buffers
        this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);

        // we use the second shader as the first one depending on your browser may omit aTextureId
        // as it is not used by the shader so is optimized out.

        this.renderer.bindVao(null);

        for (let i = 0; i < this.vaoMax; i++)
        {
            this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);

            /* eslint-disable max-len */

            // build the vao object that will render..
            this.vaos[i] = this.renderer.createVao()
                .addIndex(this.indexBuffer)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);

            /* eslint-disable max-len */
        }

        this.vao = this.vaos[0];
        this.currentBlendMode = 99999;

        this.boundTextures = new Array(this.MAX_TEXTURES);
    }

    /**
     * Called before the renderer starts rendering.
     *
     */
    onPrerender()
    {
        this.vertexCount = 0;
    }

    /**
     * Renders the sprite object.
     *
     * @param {PIXI.Sprite} sprite - the sprite to render when using this spritebatch
     */
    render(sprite)
    {
        // TODO set blend modes..
        // check texture..
        if (this.currentIndex >= this.size)
        {
            this.flush();
        }

        // get the uvs for the texture

        // if the uvs have not updated then no point rendering just yet!
        if (!sprite.texture._uvs)
        {
            return;
        }

        // push a texture.
        // increment the batchsize
        this.sprites[this.currentIndex++] = sprite;
    }

    /**
     * Renders the content and empties the current batch.
     *
     */
    flush()
    {
        if (this.currentIndex === 0)
        {
            return;
        }

        const gl = this.renderer.gl;
        const MAX_TEXTURES = this.MAX_TEXTURES;

        const np2 = bitTwiddle.nextPow2(this.currentIndex);
        const log2 = bitTwiddle.log2(np2);
        const buffer = this.buffers[log2];

        const sprites = this.sprites;
        const groups = this.groups;

        const float32View = buffer.float32View;
        const uint32View = buffer.uint32View;

        const boundTextures = this.boundTextures;
        const rendererBoundTextures = this.renderer.boundTextures;
        const touch = this.renderer.textureGC.count;

        let index = 0;
        let nextTexture;
        let currentTexture;
        let groupCount = 1;
        let textureCount = 0;
        let currentGroup = groups[0];
        let vertexData;
        let uvs;
        let blendMode = sprites[0].blendMode;

        currentGroup.textureCount = 0;
        currentGroup.start = 0;
        currentGroup.blend = blendMode;

        TICK++;

        let i;

        // copy textures..
        for (i = 0; i < MAX_TEXTURES; ++i)
        {
            boundTextures[i] = rendererBoundTextures[i];
            boundTextures[i]._virtalBoundId = i;
        }

        for (i = 0; i < this.currentIndex; ++i)
        {
            // upload the sprite elemetns...
            // they have all ready been calculated so we just need to push them into the buffer.
            const sprite = sprites[i];

            nextTexture = sprite._texture.baseTexture;

            if (blendMode !== sprite.blendMode)
            {
                // finish a group..
                blendMode = sprite.blendMode;

                // force the batch to break!
                currentTexture = null;
                textureCount = MAX_TEXTURES;
                TICK++;
            }

            if (currentTexture !== nextTexture)
            {
                currentTexture = nextTexture;

                if (nextTexture._enabled !== TICK)
                {
                    if (textureCount === MAX_TEXTURES)
                    {
                        TICK++;

                        currentGroup.size = i - currentGroup.start;

                        textureCount = 0;

                        currentGroup = groups[groupCount++];
                        currentGroup.blend = blendMode;
                        currentGroup.textureCount = 0;
                        currentGroup.start = i;
                    }

                    nextTexture.touched = touch;

                    if (nextTexture._virtalBoundId === -1)
                    {
                        for (let j = 0; j < MAX_TEXTURES; ++j)
                        {
                            const tIndex = (j + TEXTURE_TICK) % MAX_TEXTURES;

                            const t = boundTextures[tIndex];

                            if (t._enabled !== TICK)
                            {
                                TEXTURE_TICK++;

                                t._virtalBoundId = -1;

                                nextTexture._virtalBoundId = tIndex;

                                boundTextures[tIndex] = nextTexture;
                                break;
                            }
                        }
                    }

                    nextTexture._enabled = TICK;

                    currentGroup.textureCount++;
                    currentGroup.ids[textureCount] = nextTexture._virtalBoundId;
                    currentGroup.textures[textureCount++] = nextTexture;
                }
            }

            vertexData = sprite.vertexData;

            // TODO this sum does not need to be set each frame..
            uvs = sprite._texture._uvs.uvsUint32;

            if (this.renderer.roundPixels)
            {
                const resolution = this.renderer.resolution;

                // xy
                float32View[index] = ((vertexData[0] * resolution) | 0) / resolution;
                float32View[index + 1] = ((vertexData[1] * resolution) | 0) / resolution;

                // xy
                float32View[index + 5] = ((vertexData[2] * resolution) | 0) / resolution;
                float32View[index + 6] = ((vertexData[3] * resolution) | 0) / resolution;

                // xy
                float32View[index + 10] = ((vertexData[4] * resolution) | 0) / resolution;
                float32View[index + 11] = ((vertexData[5] * resolution) | 0) / resolution;

                // xy
                float32View[index + 15] = ((vertexData[6] * resolution) | 0) / resolution;
                float32View[index + 16] = ((vertexData[7] * resolution) | 0) / resolution;
            }
            else
            {
                // xy
                float32View[index] = vertexData[0];
                float32View[index + 1] = vertexData[1];

                // xy
                float32View[index + 5] = vertexData[2];
                float32View[index + 6] = vertexData[3];

                // xy
                float32View[index + 10] = vertexData[4];
                float32View[index + 11] = vertexData[5];

                // xy
                float32View[index + 15] = vertexData[6];
                float32View[index + 16] = vertexData[7];
            }

            uint32View[index + 2] = uvs[0];
            uint32View[index + 7] = uvs[1];
            uint32View[index + 12] = uvs[2];
            uint32View[index + 17] = uvs[3];

            uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (sprite.worldAlpha * 255 << 24);

            float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId;

            index += 20;
        }

        currentGroup.size = i - currentGroup.start;

        if (!CAN_UPLOAD_SAME_BUFFER)
        {
            // this is still needed for IOS performance..
            // it really does not like uploading to  the same buffer in a single frame!
            if (this.vaoMax <= this.vertexCount)
            {
                this.vaoMax++;
                this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);

                // build the vao object that will render..
                this.vaos[this.vertexCount] = this.renderer.createVao()
                    .addIndex(this.indexBuffer)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);
            }

            this.renderer.bindVao(this.vaos[this.vertexCount]);

            this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false);

            this.vertexCount++;
        }
        else
        {
            // lets use the faster option, always use buffer number 0
            this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true);
        }

        for (i = 0; i < MAX_TEXTURES; ++i)
        {
            rendererBoundTextures[i]._virtalBoundId = -1;
        }

        // render the groups..
        for (i = 0; i < groupCount; ++i)
        {
            const group = groups[i];
            const groupTextureCount = group.textureCount;

            for (let j = 0; j < groupTextureCount; j++)
            {
                currentTexture = group.textures[j];

                // reset virtual ids..
                // lets do a quick check..
                if (rendererBoundTextures[group.ids[j]] !== currentTexture)
                {
                    this.renderer.bindTexture(currentTexture, group.ids[j], true);
                }

                // reset the virtualId..
                currentTexture._virtalBoundId = -1;
            }

            // set the blend mode..
            this.renderer.state.setBlendMode(group.blend);

            gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2);
        }

        // reset elements for the next flush
        this.currentIndex = 0;
    }

    /**
     * Starts a new sprite batch.
     */
    start()
    {
        this.renderer.bindShader(this.shader);

        if (CAN_UPLOAD_SAME_BUFFER)
        {
            // bind buffer #0, we don't need others
            this.renderer.bindVao(this.vaos[this.vertexCount]);

            this.vertexBuffers[this.vertexCount].bind();
        }
    }

    /**
     * Stops and flushes the current batch.
     *
     */
    stop()
    {
        this.flush();
    }

    /**
     * Destroys the SpriteBatch.
     *
     */
    destroy()
    {
        for (let i = 0; i < this.vaoMax; i++)
        {
            if (this.vertexBuffers[i])
            {
                this.vertexBuffers[i].destroy();
            }
            if (this.vaos[i])
            {
                this.vaos[i].destroy();
            }
        }

        if (this.indexBuffer)
        {
            this.indexBuffer.destroy();
        }

        this.renderer.off('prerender', this.onPrerender, this);

        super.destroy();

        if (this.shader)
        {
            this.shader.destroy();
            this.shader = null;
        }

        this.vertexBuffers = null;
        this.vaos = null;
        this.indexBuffer = null;
        this.indices = null;

        this.sprites = null;

        for (let i = 0; i < this.buffers.length; ++i)
        {
            this.buffers[i].destroy();
        }
    }
}

WebGLRenderer.registerPlugin('sprite', SpriteRenderer);<commit_msg>update to PIXI.js 4.2.1 (SpriteRenderer to dev)<commit_after>import ObjectRenderer from '../../renderers/webgl/utils/ObjectRenderer';
import WebGLRenderer from '../../renderers/webgl/WebGLRenderer';
import createIndicesForQuads from '../../utils/createIndicesForQuads';
import generateMultiTextureShader from './generateMultiTextureShader';
import checkMaxIfStatmentsInShader from '../../renderers/webgl/utils/checkMaxIfStatmentsInShader';
import Buffer from './BatchBuffer';
import { CAN_UPLOAD_SAME_BUFFER } from '../../const';
import settings from '../../settings';
import glCore from 'pixi-gl-core';
import bitTwiddle from 'bit-twiddle';

const { SPRITE_BATCH_SIZE, SPRITE_MAX_TEXTURES } = settings;

let TICK = 0;
let TEXTURE_TICK = 0;

/**
 * Renderer dedicated to drawing and batching sprites.
 *
 * @class
 * @private
 * @memberof PIXI
 * @extends PIXI.ObjectRenderer
 */
export default class SpriteRenderer extends ObjectRenderer
{
    /**
     * @param {PIXI.WebGLRenderer} renderer - The renderer this sprite batch works for.
     */
    constructor(renderer)
    {
        super(renderer);

        /**
         * Number of values sent in the vertex buffer.
         * positionX, positionY, colorR, colorG, colorB = 5
         *
         * @member {number}
         */
        this.vertSize = 5;

        /**
         * The size of the vertex information in bytes.
         *
         * @member {number}
         */
        this.vertByteSize = this.vertSize * 4;

        /**
         * The number of images in the SpriteBatch before it flushes.
         *
         * @member {number}
         */
        this.size = SPRITE_BATCH_SIZE; // 2000 is a nice balance between mobile / desktop

        // the total number of bytes in our batch
        // let numVerts = this.size * 4 * this.vertByteSize;

        this.buffers = [];
        for (let i = 1; i <= bitTwiddle.nextPow2(this.size); i *= 2)
        {
            this.buffers.push(new Buffer(i * 4 * this.vertByteSize));
        }

        /**
         * Holds the indices of the geometry (quads) to draw
         *
         * @member {Uint16Array}
         */
        this.indices = createIndicesForQuads(this.size);

        /**
         * The default shaders that is used if a sprite doesn't have a more specific one.
         * there is a shader for each number of textures that can be rendererd.
         * These shaders will also be generated on the fly as required.
         * @member {PIXI.Shader[]}
         */
        this.shader = null;

        this.currentIndex = 0;
        TICK = 0;
        this.groups = [];

        for (let k = 0; k < this.size; k++)
        {
            this.groups[k] = { textures: [], textureCount: 0, ids: [], size: 0, start: 0, blend: 0 };
        }

        this.sprites = [];

        this.vertexBuffers = [];
        this.vaos = [];

        this.vaoMax = 2;
        this.vertexCount = 0;

        this.renderer.on('prerender', this.onPrerender, this);
    }

    /**
     * Sets up the renderer context and necessary buffers.
     *
     * @private
     */
    onContextChange()
    {
        const gl = this.renderer.gl;

        // step 1: first check max textures the GPU can handle.
        this.MAX_TEXTURES = Math.min(gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS), SPRITE_MAX_TEXTURES);

        // step 2: check the maximum number of if statements the shader can have too..
        this.MAX_TEXTURES = checkMaxIfStatmentsInShader(this.MAX_TEXTURES, gl);

        const shader = this.shader = generateMultiTextureShader(gl, this.MAX_TEXTURES);

        // create a couple of buffers
        this.indexBuffer = glCore.GLBuffer.createIndexBuffer(gl, this.indices, gl.STATIC_DRAW);

        // we use the second shader as the first one depending on your browser may omit aTextureId
        // as it is not used by the shader so is optimized out.

        this.renderer.bindVao(null);

        for (let i = 0; i < this.vaoMax; i++)
        {
            this.vertexBuffers[i] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);

            /* eslint-disable max-len */

            // build the vao object that will render..
            this.vaos[i] = this.renderer.createVao()
                .addIndex(this.indexBuffer)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4)
                .addAttribute(this.vertexBuffers[i], shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);

            /* eslint-disable max-len */
        }

        this.vao = this.vaos[0];
        this.currentBlendMode = 99999;

        this.boundTextures = new Array(this.MAX_TEXTURES);
    }

    /**
     * Called before the renderer starts rendering.
     *
     */
    onPrerender()
    {
        this.vertexCount = 0;
    }

    /**
     * Renders the sprite object.
     *
     * @param {PIXI.Sprite} sprite - the sprite to render when using this spritebatch
     */
    render(sprite)
    {
        // TODO set blend modes..
        // check texture..
        if (this.currentIndex >= this.size)
        {
            this.flush();
        }

        // get the uvs for the texture

        // if the uvs have not updated then no point rendering just yet!
        if (!sprite.texture._uvs)
        {
            return;
        }

        // push a texture.
        // increment the batchsize
        this.sprites[this.currentIndex++] = sprite;
    }

    /**
     * Renders the content and empties the current batch.
     *
     */
    flush()
    {
        if (this.currentIndex === 0)
        {
            return;
        }

        const gl = this.renderer.gl;
        const MAX_TEXTURES = this.MAX_TEXTURES;

        const np2 = bitTwiddle.nextPow2(this.currentIndex);
        const log2 = bitTwiddle.log2(np2);
        const buffer = this.buffers[log2];

        const sprites = this.sprites;
        const groups = this.groups;

        const float32View = buffer.float32View;
        const uint32View = buffer.uint32View;

        const boundTextures = this.boundTextures;
        const rendererBoundTextures = this.renderer.boundTextures;
        const touch = this.renderer.textureGC.count;

        let index = 0;
        let nextTexture;
        let currentTexture;
        let groupCount = 1;
        let textureCount = 0;
        let currentGroup = groups[0];
        let vertexData;
        let uvs;
        let blendMode = sprites[0].blendMode;

        currentGroup.textureCount = 0;
        currentGroup.start = 0;
        currentGroup.blend = blendMode;

        TICK++;

        let i;

        // copy textures..
        for (i = 0; i < MAX_TEXTURES; ++i)
        {
            boundTextures[i] = rendererBoundTextures[i];
            boundTextures[i]._virtalBoundId = i;
        }

        for (i = 0; i < this.currentIndex; ++i)
        {
            // upload the sprite elemetns...
            // they have all ready been calculated so we just need to push them into the buffer.
            const sprite = sprites[i];

            nextTexture = sprite._texture.baseTexture;

            if (blendMode !== sprite.blendMode)
            {
                // finish a group..
                blendMode = sprite.blendMode;

                // force the batch to break!
                currentTexture = null;
                textureCount = MAX_TEXTURES;
                TICK++;
            }

            if (currentTexture !== nextTexture)
            {
                currentTexture = nextTexture;

                if (nextTexture._enabled !== TICK)
                {
                    if (textureCount === MAX_TEXTURES)
                    {
                        TICK++;

                        currentGroup.size = i - currentGroup.start;

                        textureCount = 0;

                        currentGroup = groups[groupCount++];
                        currentGroup.blend = blendMode;
                        currentGroup.textureCount = 0;
                        currentGroup.start = i;
                    }

                    nextTexture.touched = touch;

                    if (nextTexture._virtalBoundId === -1)
                    {
                        for (let j = 0; j < MAX_TEXTURES; ++j)
                        {
                            const tIndex = (j + TEXTURE_TICK) % MAX_TEXTURES;

                            const t = boundTextures[tIndex];

                            if (t._enabled !== TICK)
                            {
                                TEXTURE_TICK++;

                                t._virtalBoundId = -1;

                                nextTexture._virtalBoundId = tIndex;

                                boundTextures[tIndex] = nextTexture;
                                break;
                            }
                        }
                    }

                    nextTexture._enabled = TICK;

                    currentGroup.textureCount++;
                    currentGroup.ids[textureCount] = nextTexture._virtalBoundId;
                    currentGroup.textures[textureCount++] = nextTexture;
                }
            }

            vertexData = sprite.vertexData;

            // TODO this sum does not need to be set each frame..
            uvs = sprite._texture._uvs.uvsUint32;

            if (this.renderer.roundPixels)
            {
                const resolution = this.renderer.resolution;

                // xy
                float32View[index] = ((vertexData[0] * resolution) | 0) / resolution;
                float32View[index + 1] = ((vertexData[1] * resolution) | 0) / resolution;

                // xy
                float32View[index + 5] = ((vertexData[2] * resolution) | 0) / resolution;
                float32View[index + 6] = ((vertexData[3] * resolution) | 0) / resolution;

                // xy
                float32View[index + 10] = ((vertexData[4] * resolution) | 0) / resolution;
                float32View[index + 11] = ((vertexData[5] * resolution) | 0) / resolution;

                // xy
                float32View[index + 15] = ((vertexData[6] * resolution) | 0) / resolution;
                float32View[index + 16] = ((vertexData[7] * resolution) | 0) / resolution;
            }
            else
            {
                // xy
                float32View[index] = vertexData[0];
                float32View[index + 1] = vertexData[1];

                // xy
                float32View[index + 5] = vertexData[2];
                float32View[index + 6] = vertexData[3];

                // xy
                float32View[index + 10] = vertexData[4];
                float32View[index + 11] = vertexData[5];

                // xy
                float32View[index + 15] = vertexData[6];
                float32View[index + 16] = vertexData[7];
            }

            uint32View[index + 2] = uvs[0];
            uint32View[index + 7] = uvs[1];
            uint32View[index + 12] = uvs[2];
            uint32View[index + 17] = uvs[3];

            uint32View[index + 3] = uint32View[index + 8] = uint32View[index + 13] = uint32View[index + 18] = sprite._tintRGB + (sprite.worldAlpha * 255 << 24);

            float32View[index + 4] = float32View[index + 9] = float32View[index + 14] = float32View[index + 19] = nextTexture._virtalBoundId;

            index += 20;
        }

        currentGroup.size = i - currentGroup.start;

        if (!CAN_UPLOAD_SAME_BUFFER)
        {
            // this is still needed for IOS performance..
            // it really does not like uploading to  the same buffer in a single frame!
            if (this.vaoMax <= this.vertexCount)
            {
                this.vaoMax++;
                this.vertexBuffers[this.vertexCount] = glCore.GLBuffer.createVertexBuffer(gl, null, gl.STREAM_DRAW);

                // build the vao object that will render..
                this.vaos[this.vertexCount] = this.renderer.createVao()
                    .addIndex(this.indexBuffer)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aVertexPosition, gl.FLOAT, false, this.vertByteSize, 0)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aTextureCoord, gl.UNSIGNED_SHORT, true, this.vertByteSize, 2 * 4)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aColor, gl.UNSIGNED_BYTE, true, this.vertByteSize, 3 * 4)
                    .addAttribute(this.vertexBuffers[this.vertexCount], this.shader.attributes.aTextureId, gl.FLOAT, false, this.vertByteSize, 4 * 4);
            }

            this.renderer.bindVao(this.vaos[this.vertexCount]);

            this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, false);

            this.vertexCount++;
        }
        else
        {
            // lets use the faster option, always use buffer number 0
            this.vertexBuffers[this.vertexCount].upload(buffer.vertices, 0, true);
        }

        for (i = 0; i < MAX_TEXTURES; ++i)
        {
            rendererBoundTextures[i]._virtalBoundId = -1;
        }

        // render the groups..
        for (i = 0; i < groupCount; ++i)
        {
            const group = groups[i];
            const groupTextureCount = group.textureCount;

            for (let j = 0; j < groupTextureCount; j++)
            {
                currentTexture = group.textures[j];

                // reset virtual ids..
                // lets do a quick check..
                if (rendererBoundTextures[group.ids[j]] !== currentTexture)
                {
                    this.renderer.bindTexture(currentTexture, group.ids[j], true);
                }

                // reset the virtualId..
                currentTexture._virtalBoundId = -1;
            }

            // set the blend mode..
            this.renderer.state.setBlendMode(group.blend);

            gl.drawElements(gl.TRIANGLES, group.size * 6, gl.UNSIGNED_SHORT, group.start * 6 * 2);
        }

        // reset elements for the next flush
        this.currentIndex = 0;
    }

    /**
     * Starts a new sprite batch.
     */
    start()
    {
        this.renderer.bindShader(this.shader);

        if (CAN_UPLOAD_SAME_BUFFER)
        {
            // bind buffer #0, we don't need others
            this.renderer.bindVao(this.vaos[this.vertexCount]);

            this.vertexBuffers[this.vertexCount].bind();
        }
    }

    /**
     * Stops and flushes the current batch.
     *
     */
    stop()
    {
        this.flush();
    }

    /**
     * Destroys the SpriteBatch.
     *
     */
    destroy()
    {
        for (let i = 0; i < this.vaoMax; i++)
        {
            if (this.vertexBuffers[i])
            {
                this.vertexBuffers[i].destroy();
            }
            if (this.vaos[i])
            {
                this.vaos[i].destroy();
            }
        }

        if (this.indexBuffer)
        {
            this.indexBuffer.destroy();
        }

        this.renderer.off('prerender', this.onPrerender, this);

        super.destroy();

        if (this.shader)
        {
            this.shader.destroy();
            this.shader = null;
        }

        this.vertexBuffers = null;
        this.vaos = null;
        this.indexBuffer = null;
        this.indices = null;

        this.sprites = null;

        for (let i = 0; i < this.buffers.length; ++i)
        {
            this.buffers[i].destroy();
        }
    }
}

WebGLRenderer.registerPlugin('sprite', SpriteRenderer);
<|endoftext|>


<commit_before>(function () {

	'use strict'

	module.exports = function () {

		/**
		 * populator main object to export
		 * @type {Object}
		 */
		var populator = {}

		/**
		 * [type description]
		 * @type {String}
		 */
		var watch = []

		/**
		 * [moment description]
		 * @type {[type]}
		 */
		var moment = require('moment')

		/**
		 * [utils description]
		 * @type {Object}
		 */
		var log = require('./../logger/logger.js')

		/**
		 * [message description]
		 * @param  {[type]} message [description]
		 * @param  {[type]} type    [description]
		 * @return {[type]}         [description]
		 */
		populator.config = function (name, callback) {

			return (typeof populator[method] !== "function") ? populator[defaults](message, options) : populator[method](message, options)
		}

		return populator

	}

}).call(this)
<commit_msg>up populator<commit_after>(function () {

	'use strict'

	function(err, data){

		if(err){

			return false

		}

	}

	module.exports = function () {

		/**
		 * populator main object to export
		 * @type {Object}
		 */
		var populator = {}

		/**
		 * [type description]
		 * @type {String}
		 */
		var watch = []

		/**
		 * [moment description]
		 * @type {[type]}
		 */
		var moment = require('moment')

		/**
		 * [utils description]
		 * @type {Object}
		 */
		var log = require('./../logger/logger.js')

		var bank = {}

		populator.storing = function(key, array){

			if(typeof array !== 'array') {

				console.error(new Error('Populator :: storing parameter 2 is not array'))
				return false

			}

			if(typeof key !== 'string'){

				console.error(new Error('Populator :: storing parameter 1 is not string'))
				return false

			}

			bank[key] = array

			return true

		}

		populator.populate = function(key, callback){

			if(typeof key !== 'string'){

				console.error(new Error('Populator :: populate parameter 1 is not string'))
				return false

			}

			if(typeof callback !== 'function'){

				console.error(new Error('Populator :: storing parameter 2 is not function'))
				return false

			}

			if(typeof bank[key] !== 'array'){

				console.error(new Error('Populator :: storing parameter data is not an array'))
				return false

			}

			async.map(bank[key], function(_v){

				callback(null, _v)

			}, function(err, result){

				if(err){

					console.error(err)
					callback(err)
					return false

				}

				populator.flush()

			})

		}

		populator.check = function(key){

			if(typeof key !== 'string'){

				console.error(new Error('Populator :: check parameter 1 is not string'))
				return false

			}

			if(bank[key]){

				return true

			}else{

				return false

			}

		}

		populator.flush = function(key){

			if(this.check(key)){

				delete bank[key]

			}else{

				console.error(new Error('No key match to flush'))

			}

		}

		/**
		 * [message description]
		 * @param  {[type]} message [description]
		 * @param  {[type]} type    [description]
		 * @return {[type]}         [description]
		 */
		populator.config = function (name, callback) {

			return (typeof populator[method] !== "function") ? populator[defaults](message, options) : populator[method](message, options)

		}

		return populator

	}

}).call(this)
<|endoftext|>


<commit_before>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'sourceSelector', mw.KBaseComponent.extend({

		defaultConfig: {
			"parent": "controlsContainer",
			"order": 61,
			"displayImportance": 'low',
			"align": "right",
			"showTooltip": true,
			"switchOnResize": false,
			"simpleFormat": true,
			"iconClass": "icon-cog",
            "displayMode": "size" //'size' – displays frame size ( default ), 'bitrate' – displays the bitrate, 'sizebitrate' displays size followed by bitrate
		},

		isDisabled: false,
		inUpdateLayout:false,
		selectSourceTitle: gM( 'mwe-embedplayer-select_source' ),
		switchSourceTitle: gM( 'mwe-embedplayer-switch_source' ),
        AutoTitle: gM( 'mwe-embedplayer-auto_source' ),
		saveBackgroundColor: null, // used to save background color upon disable and rotate and return it when enabled again to prevent rotating box around the icon when custom style is applied

        sourcesList: [],
        firstPlay:false,

		setup: function(){
			var _this = this;

			this.bind( 'playerReady sourcesReplaced', function(){
				_this.buildMenu();
			});

            this.bind( 'firstPlay', function(){
                _this.firstPlay = true;
            });

			this.bind( 'SourceChange', function(){
				var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
				var selectedId = selectedSrc.getAssetId();

				//if selected source is not part of the menu, show the source before it as the selected one
				//workaround when auto switch with kplayer occurred and the selected source is not part of the menu data provider
				if ( selectedSrc.skip ) {
					var sources = _this.getSources();
					for ( var i = 0; i< sources.length; i++ ) {
						//look for the closest flavor
						if ( selectedSrc.getSrc() == sources[i].getSrc() ) {
							if ( i == 0 && sources.length > 1 ) {
								selectedId = sources[i+1].getAssetId();
							} else {
								selectedId = sources[i-1].getAssetId();
							}
							break;
						}
					}
				}
				_this.getMenu().setActive({'key': 'id', 'val': selectedId});
			});

			this.bind( 'sourceSwitchingStarted', function(){
				_this.getComponent().find('button').addClass( 'in-progress-state' );
				_this.onDisable();
			});
			this.bind( 'sourceSwitchingEnd', function(newIndex){
				_this.getComponent().find('button').removeClass( 'in-progress-state' );
                _this.onEnable();
			});
            this.bind( 'onHideControlBar', function(){
                if ( _this.getMenu().isOpen() )
                    _this.getMenu().close();
            });

			// Check for switch on resize option
			if( this.getConfig( 'switchOnResize' ) ){
				this.bind( 'updateLayout', function(){
					// workaround to avoid the amount of 'updateLayout' events
					// !seeking will avoid getting current time equal to 0
					if ( !_this.inUpdateLayout && !_this.embedPlayer.seeking ){
						_this.inUpdateLayout = true;
						_this.updateLayoutTimout = setTimeout(function() {
							_this.inUpdateLayout = false;
						},1000);
						//if we're working with kplayer - mp4 can't be seeked - so disable this feature
						//this only effect native for now
						if (_this.embedPlayer.instanceOf === "Native" && !_this.embedPlayer.isInSequence() ) {
							// TODO add additional logic for "auto" where multiple bitrates
							// exist at the same resolution.
							var selectedSource = _this.embedPlayer.mediaElement.autoSelectSource(_this.embedPlayer.supportsURLTimeEncoding(), _this.embedPlayer.startTime, _this.embedPlayer.pauseTime);
							if ( selectedSource ) { // source was found
								_this.embedPlayer.switchSrc( selectedSource );
							}
						} else {
							mw.log( "sourceSelector - switchOnResize is ignored - Can't switch source since not using native player or during ad playback");
						}
					}
				});
			}

			this.embedPlayer.bindHelper("propertyChangedEvent", function(event, data){
				if ( data.plugin === _this.pluginName ){
					if ( data.property === "sources" ){
						_this.getMenu().$el.find("li a")[data.value].click();
					}
				}
			});
		},
		getSources: function(){
			return this.getPlayer().getSources();
		},

		buildMenu: function(){
			var _this = this;

			// Destroy old menu
			this.getMenu().destroy();

			var sources = this.getSources().slice(0);

			if( ! sources.length ){
				_this.log("Error with getting sources");
				return ;
			}

            //add Auto for addaptive bitrate streams
            if ( !this.handleAdaptiveBitrateAndContinue() )
                return;

			if( sources.length == 1 ){
				// no need to do building menu logic.
				this.addSourceToMenu( sources[0], _this.getSourceTitle(sources[0]) );
				return ;
			}
			// sort by height then bitrate:
			sources.sort(function(a,b){
				var hdiff = b.getHeight() - a.getHeight();
				if( hdiff != 0 ){
					return hdiff;
				}
				return b.getBitrate() - a.getBitrate();
			});

			// if simple format don't include more then two sources per size in menu
			if( _this.getConfig( 'simpleFormat' ) ){
				var prevSource = null;
				var twice = false;
				$.each( sources, function( sourceIndex, source ) {
					if( ! prevSource ){
						prevSource = source;
						return true;
					}
					if( source.getHeight() != 0
						&&
						( _this.getSourceSizeName( prevSource )
							==
							_this.getSourceSizeName( source ) )
						){
						//if the selected source has the same height, skip this source
						var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
						if ( selectedSrc
							&&
							!_this.isSourceSelected( source )
							&&
							!_this.isSourceSelected( prevSource )
							&&
							( _this.getSourceSizeName( source )
								==
								_this.getSourceSizeName( selectedSrc ) )
							){
							source.skip = true;
						}
						else if( twice ){
							// don't skip if this is the default source:
							if( !_this.isSourceSelected( source ) ){
								// skip this source
								source.skip = true;
							} else {
								source.skip = false;
							}
							prevSource = source;
							return true;
						}
						// set the first source as "HQ"
						prevSource.hq = true;
						twice = true;
					} else {
						twice = false;
					}
					// always update prevSource
					prevSource = source;
				});
			}
			var items = [];
			var itemLabels = [];
			var prevSource = null;
			$.each( sources, function( sourceIndex, source ) {
				if( source.skip ){
					return true;
				}
				// Output the player select code:
				var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() );
				for ( var i = 0; i < supportingPlayers.length ; i++ ) {
					if(
						(
							_this.getPlayer().selectedPlayer === undefined
								&&
								supportingPlayers[i].library == 'Native'
							)
							||
							(
								_this.getPlayer().selectedPlayer !== undefined
									&&
									supportingPlayers[i].library == _this.getPlayer().selectedPlayer.library
								)
						){
						_this.addSourceToMenu( source );
						var label = _this.getSourceTitle( source )
						if ($.inArray(label, itemLabels) === -1){
							itemLabels.push(label)
							items.push({'label':label, 'value':label});
						}
					}
				}
			});
			// dispatch event to be used by a master plugin if defined
			this.getPlayer().triggerHelper("updatePropertyEvent",{"plugin": this.pluginName, "property": "sources", "items": items, "selectedItem": this.getMenu().$el.find('.active a').text()});

		},
		isSourceSelected: function( source ){
			var _this = this;
			return ( _this.getPlayer().mediaElement.selectedSource && source.getSrc()
				==
				_this.getPlayer().mediaElement.selectedSource.getSrc()
				);
		},
        handleAdaptiveBitrateAndContinue: function (){
            //Silverlight smoothStream
            if( ( this.getPlayer().streamerType === "smoothStream" ) ){
                this.addAutoToMenu();
                return true;
            }

            //HLS, HDS

            if (this.getPlayer().streamerType !== "http" && mw.isNativeApp()) {
            	this.sourcesList = [];
                this.addAutoToMenu();
                return true;
            }

            if(  this.getPlayer().streamerType != "http" && !this.getPlayer().isPlaying() ){
                this.addAutoToMenu();
                return false;
            }

            if( this.getPlayer().streamerType != "http" && this.firstPlay ){ //add and select Auto for adaptive bitrate
                this.addAutoToMenu();
                this.firstPlay = false;
            }
            return true;
        },
        addAutoToMenu: function (){
            var _this = this;
            this.getMenu().addItem({
                'label': _this.AutoTitle,
                'callback': function () {
                    _this.getPlayer().switchSrc(-1);
                },
               'active': true
            });
        },
		addSourceToMenu: function( source ){
			var _this = this;

            var sourceLabel = this.getSourceTitle( source );
            if( $.inArray(sourceLabel, this.sourcesList) == -1 ) {

                this.sourcesList.push(sourceLabel);

                this.getMenu().addItem({
                    'label': sourceLabel,
                    'attributes': {
                        'id': source.getAssetId()
                    },
                    'callback': function () {
                        _this.getPlayer().switchSrc(source);
                    },
                    'active': _this.isSourceSelected(source)
                });
            }
		},
        getSourceSizeName: function( source ){
			if( source.getHeight() < 255 ){
				return '240P';
			} else if( source.getHeight() < 370 ){
				return '360P';
			} else if( source.getHeight() < 500 ){
				return '480P';
			} else if( source.getHeight() < 800 ){
				return '720P';
			} else {
				return '1080P';
			}
		},
		getSourceTitle: function( source ){
			// We should return "Auto" for Apple HLS
			if( source.getMIMEType() == 'application/vnd.apple.mpegurl' ) {
				return this.AutoTitle;
			}

            var title = '';
            switch( this.getConfig( 'displayMode' ) ){
                case 'size' :
                    title = this.getSourceTitleSize(source);
                    break;
                case 'bitrate' :
                    title = this.getSourceTitleBitrate(source);
                    break;
                case 'sizebitrate' :
                    title = this.getSourceTitleSizeBitrate(source);
                    break;
            }
            return title;
		},
        getSourceTitleSize: function( source ){
            var title = '';
            if( source.getHeight() ){
                title = this.getSourceSizeName( source );
                if( this.getConfig( 'displayMode' ) === 'size' && this.getConfig( 'simpleFormat' ) && source.hq ){
                    title += ' HQ';
                }
            } else { //fallback for a case we don't have a frame size (height) for the source (for example HLS source)
                title = this.getSourceTitleBitrate(source);
            }
            return title;
        },
        getSourceTitleBitrate: function( source ){
            var title = '';
            if ( source.getBitrate() ) {
                var bits = ( Math.round( source.getBitrate() / 1024 * 10 ) / 10 ) + '';
                if( bits[0] == '0' ){
                    bits = bits.substring(1);
                }
                title+= ' ' + bits + ' Mbs ';
            }
            return title;
        },
        getSourceTitleSizeBitrate: function( source ){
            var title = '';
            if( source.getHeight() ){
                title = this.getSourceSizeName( source ) + ' ';
            }
            title += this.getSourceTitleBitrate(source);
            return title;
        },
		toggleMenu: function(){
			if ( this.isDisabled ) {
				return;
			}
			this.getMenu().toggle();
		},
		getComponent: function() {
			var _this = this;
			if( !this.$el ) {
				var $menu = $( '<ul />' );
				var $button = $( '<button />' )
					.addClass( 'btn icon-cog' )
					.attr('title', _this.selectSourceTitle)
					.click( function(e){
						_this.toggleMenu();
					});
				this.setAccessibility($button,_this.selectSourceTitle);
				this.$el = $( '<div />' )
					.addClass( 'dropup' + this.getCssClass() )
					.append( $button, $menu );
			}
			return this.$el;
		},
		getMenu: function(){
			if( !this.menu ) {
				this.menu = new mw.KMenu(this.getComponent().find('ul'), {
					tabIndex: this.getBtn().attr('tabindex')
				});
			}
			return this.menu;
		},
		getBtn: function(){
			return this.getComponent().find( 'button' );
		},
		onEnable: function(){
			this.isDisabled = false;
			this.updateTooltip( this.selectSourceTitle );
			this.getBtn().removeClass( 'disabled' );
			if (this.saveBackgroundColor){
				this.getComponent().find('button').attr('style', 'background-color: ' + this.saveBackgroundColor + ' !important');
			}
		},
		onDisable: function(){
			this.isDisabled = true;
			this.updateTooltip( this.switchSourceTitle );
			this.saveBackgroundColor = this.getComponent().find('button').css("background-color");
			this.getComponent().find('button').attr('style', 'background-color: null !important');
			this.getComponent().removeClass( 'open' );
			this.getBtn().addClass( 'disabled' );
		}
	}));

} )( window.mw, window.jQuery );
<commit_msg>#SUP-5551 #comment clear sourcesList onChangeMedia #time 1h<commit_after>( function( mw, $ ) {"use strict";

	mw.PluginManager.add( 'sourceSelector', mw.KBaseComponent.extend({

		defaultConfig: {
			"parent": "controlsContainer",
			"order": 61,
			"displayImportance": 'low',
			"align": "right",
			"showTooltip": true,
			"switchOnResize": false,
			"simpleFormat": true,
			"iconClass": "icon-cog",
            "displayMode": "size" //'size' – displays frame size ( default ), 'bitrate' – displays the bitrate, 'sizebitrate' displays size followed by bitrate
		},

		isDisabled: false,
		inUpdateLayout:false,
		selectSourceTitle: gM( 'mwe-embedplayer-select_source' ),
		switchSourceTitle: gM( 'mwe-embedplayer-switch_source' ),
        AutoTitle: gM( 'mwe-embedplayer-auto_source' ),
		saveBackgroundColor: null, // used to save background color upon disable and rotate and return it when enabled again to prevent rotating box around the icon when custom style is applied

        sourcesList: [],
        firstPlay:false,

		setup: function(){
			var _this = this;

			this.bind( 'playerReady sourcesReplaced', function(){
				_this.buildMenu();
			});

            this.bind( 'firstPlay', function(){
                _this.firstPlay = true;
            });

			this.bind( 'SourceChange', function(){
				var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
				var selectedId = selectedSrc.getAssetId();

				//if selected source is not part of the menu, show the source before it as the selected one
				//workaround when auto switch with kplayer occurred and the selected source is not part of the menu data provider
				if ( selectedSrc.skip ) {
					var sources = _this.getSources();
					for ( var i = 0; i< sources.length; i++ ) {
						//look for the closest flavor
						if ( selectedSrc.getSrc() == sources[i].getSrc() ) {
							if ( i == 0 && sources.length > 1 ) {
								selectedId = sources[i+1].getAssetId();
							} else {
								selectedId = sources[i-1].getAssetId();
							}
							break;
						}
					}
				}
				_this.getMenu().setActive({'key': 'id', 'val': selectedId});
			});

			this.bind( 'sourceSwitchingStarted', function(){
				_this.getComponent().find('button').addClass( 'in-progress-state' );
				_this.onDisable();
			});
			this.bind( 'sourceSwitchingEnd', function(newIndex){
				_this.getComponent().find('button').removeClass( 'in-progress-state' );
                _this.onEnable();
			});
            this.bind( 'onHideControlBar', function(){
                if ( _this.getMenu().isOpen() )
                    _this.getMenu().close();
            });
			this.bind( 'onChangeMedia', function(){
				_this.sourcesList = [];
			});

			// Check for switch on resize option
			if( this.getConfig( 'switchOnResize' ) ){
				this.bind( 'updateLayout', function(){
					// workaround to avoid the amount of 'updateLayout' events
					// !seeking will avoid getting current time equal to 0
					if ( !_this.inUpdateLayout && !_this.embedPlayer.seeking ){
						_this.inUpdateLayout = true;
						_this.updateLayoutTimout = setTimeout(function() {
							_this.inUpdateLayout = false;
						},1000);
						//if we're working with kplayer - mp4 can't be seeked - so disable this feature
						//this only effect native for now
						if (_this.embedPlayer.instanceOf === "Native" && !_this.embedPlayer.isInSequence() ) {
							// TODO add additional logic for "auto" where multiple bitrates
							// exist at the same resolution.
							var selectedSource = _this.embedPlayer.mediaElement.autoSelectSource(_this.embedPlayer.supportsURLTimeEncoding(), _this.embedPlayer.startTime, _this.embedPlayer.pauseTime);
							if ( selectedSource ) { // source was found
								_this.embedPlayer.switchSrc( selectedSource );
							}
						} else {
							mw.log( "sourceSelector - switchOnResize is ignored - Can't switch source since not using native player or during ad playback");
						}
					}
				});
			}

			this.embedPlayer.bindHelper("propertyChangedEvent", function(event, data){
				if ( data.plugin === _this.pluginName ){
					if ( data.property === "sources" ){
						_this.getMenu().$el.find("li a")[data.value].click();
					}
				}
			});
		},
		getSources: function(){
			return this.getPlayer().getSources();
		},

		buildMenu: function(){
			var _this = this;

			// Destroy old menu
			this.getMenu().destroy();

			var sources = this.getSources().slice(0);

			if( ! sources.length ){
				_this.log("Error with getting sources");
				return ;
			}

            //add Auto for addaptive bitrate streams
            if ( !this.handleAdaptiveBitrateAndContinue() )
                return;

			if( sources.length == 1 ){
				// no need to do building menu logic.
				this.addSourceToMenu( sources[0], _this.getSourceTitle(sources[0]) );
				return ;
			}
			// sort by height then bitrate:
			sources.sort(function(a,b){
				var hdiff = b.getHeight() - a.getHeight();
				if( hdiff != 0 ){
					return hdiff;
				}
				return b.getBitrate() - a.getBitrate();
			});

			// if simple format don't include more then two sources per size in menu
			if( _this.getConfig( 'simpleFormat' ) ){
				var prevSource = null;
				var twice = false;
				$.each( sources, function( sourceIndex, source ) {
					if( ! prevSource ){
						prevSource = source;
						return true;
					}
					if( source.getHeight() != 0
						&&
						( _this.getSourceSizeName( prevSource )
							==
							_this.getSourceSizeName( source ) )
						){
						//if the selected source has the same height, skip this source
						var selectedSrc = _this.getPlayer().mediaElement.selectedSource;
						if ( selectedSrc
							&&
							!_this.isSourceSelected( source )
							&&
							!_this.isSourceSelected( prevSource )
							&&
							( _this.getSourceSizeName( source )
								==
								_this.getSourceSizeName( selectedSrc ) )
							){
							source.skip = true;
						}
						else if( twice ){
							// don't skip if this is the default source:
							if( !_this.isSourceSelected( source ) ){
								// skip this source
								source.skip = true;
							} else {
								source.skip = false;
							}
							prevSource = source;
							return true;
						}
						// set the first source as "HQ"
						prevSource.hq = true;
						twice = true;
					} else {
						twice = false;
					}
					// always update prevSource
					prevSource = source;
				});
			}
			var items = [];
			var itemLabels = [];
			var prevSource = null;
			$.each( sources, function( sourceIndex, source ) {
				if( source.skip ){
					return true;
				}
				// Output the player select code:
				var supportingPlayers = mw.EmbedTypes.getMediaPlayers().getMIMETypePlayers( source.getMIMEType() );
				for ( var i = 0; i < supportingPlayers.length ; i++ ) {
					if(
						(
							_this.getPlayer().selectedPlayer === undefined
								&&
								supportingPlayers[i].library == 'Native'
							)
							||
							(
								_this.getPlayer().selectedPlayer !== undefined
									&&
									supportingPlayers[i].library == _this.getPlayer().selectedPlayer.library
								)
						){
						_this.addSourceToMenu( source );
						var label = _this.getSourceTitle( source )
						if ($.inArray(label, itemLabels) === -1){
							itemLabels.push(label)
							items.push({'label':label, 'value':label});
						}
					}
				}
			});
			// dispatch event to be used by a master plugin if defined
			this.getPlayer().triggerHelper("updatePropertyEvent",{"plugin": this.pluginName, "property": "sources", "items": items, "selectedItem": this.getMenu().$el.find('.active a').text()});

		},
		isSourceSelected: function( source ){
			var _this = this;
			return ( _this.getPlayer().mediaElement.selectedSource && source.getSrc()
				==
				_this.getPlayer().mediaElement.selectedSource.getSrc()
				);
		},
        handleAdaptiveBitrateAndContinue: function (){
            //Silverlight smoothStream
            if( ( this.getPlayer().streamerType === "smoothStream" ) ){
                this.addAutoToMenu();
                return true;
            }

            //HLS, HDS

            if (this.getPlayer().streamerType !== "http" && mw.isNativeApp()) {
            	this.sourcesList = [];
                this.addAutoToMenu();
                return true;
            }

            if(  this.getPlayer().streamerType != "http" && !this.getPlayer().isPlaying() ){
                this.addAutoToMenu();
                return false;
            }

            if( this.getPlayer().streamerType != "http" && this.firstPlay ){ //add and select Auto for adaptive bitrate
                this.addAutoToMenu();
                this.firstPlay = false;
            }
            return true;
        },
        addAutoToMenu: function (){
            var _this = this;
            this.getMenu().addItem({
                'label': _this.AutoTitle,
                'callback': function () {
                    _this.getPlayer().switchSrc(-1);
                },
               'active': true
            });
        },
		addSourceToMenu: function( source ){
			var _this = this;

            var sourceLabel = this.getSourceTitle( source );
            if( $.inArray(sourceLabel, this.sourcesList) == -1 ) {

                this.sourcesList.push(sourceLabel);

                this.getMenu().addItem({
                    'label': sourceLabel,
                    'attributes': {
                        'id': source.getAssetId()
                    },
                    'callback': function () {
                        _this.getPlayer().switchSrc(source);
                    },
                    'active': _this.isSourceSelected(source)
                });
            }
		},
        getSourceSizeName: function( source ){
			if( source.getHeight() < 255 ){
				return '240P';
			} else if( source.getHeight() < 370 ){
				return '360P';
			} else if( source.getHeight() < 500 ){
				return '480P';
			} else if( source.getHeight() < 800 ){
				return '720P';
			} else {
				return '1080P';
			}
		},
		getSourceTitle: function( source ){
			// We should return "Auto" for Apple HLS
			if( source.getMIMEType() == 'application/vnd.apple.mpegurl' ) {
				return this.AutoTitle;
			}

            var title = '';
            switch( this.getConfig( 'displayMode' ) ){
                case 'size' :
                    title = this.getSourceTitleSize(source);
                    break;
                case 'bitrate' :
                    title = this.getSourceTitleBitrate(source);
                    break;
                case 'sizebitrate' :
                    title = this.getSourceTitleSizeBitrate(source);
                    break;
            }
            return title;
		},
        getSourceTitleSize: function( source ){
            var title = '';
            if( source.getHeight() ){
                title = this.getSourceSizeName( source );
                if( this.getConfig( 'displayMode' ) === 'size' && this.getConfig( 'simpleFormat' ) && source.hq ){
                    title += ' HQ';
                }
            } else { //fallback for a case we don't have a frame size (height) for the source (for example HLS source)
                title = this.getSourceTitleBitrate(source);
            }
            return title;
        },
        getSourceTitleBitrate: function( source ){
            var title = '';
            if ( source.getBitrate() ) {
                var bits = ( Math.round( source.getBitrate() / 1024 * 10 ) / 10 ) + '';
                if( bits[0] == '0' ){
                    bits = bits.substring(1);
                }
                title+= ' ' + bits + ' Mbs ';
            }
            return title;
        },
        getSourceTitleSizeBitrate: function( source ){
            var title = '';
            if( source.getHeight() ){
                title = this.getSourceSizeName( source ) + ' ';
            }
            title += this.getSourceTitleBitrate(source);
            return title;
        },
		toggleMenu: function(){
			if ( this.isDisabled ) {
				return;
			}
			this.getMenu().toggle();
		},
		getComponent: function() {
			var _this = this;
			if( !this.$el ) {
				var $menu = $( '<ul />' );
				var $button = $( '<button />' )
					.addClass( 'btn icon-cog' )
					.attr('title', _this.selectSourceTitle)
					.click( function(e){
						_this.toggleMenu();
					});
				this.setAccessibility($button,_this.selectSourceTitle);
				this.$el = $( '<div />' )
					.addClass( 'dropup' + this.getCssClass() )
					.append( $button, $menu );
			}
			return this.$el;
		},
		getMenu: function(){
			if( !this.menu ) {
				this.menu = new mw.KMenu(this.getComponent().find('ul'), {
					tabIndex: this.getBtn().attr('tabindex')
				});
			}
			return this.menu;
		},
		getBtn: function(){
			return this.getComponent().find( 'button' );
		},
		onEnable: function(){
			this.isDisabled = false;
			this.updateTooltip( this.selectSourceTitle );
			this.getBtn().removeClass( 'disabled' );
			if (this.saveBackgroundColor){
				this.getComponent().find('button').attr('style', 'background-color: ' + this.saveBackgroundColor + ' !important');
			}
		},
		onDisable: function(){
			this.isDisabled = true;
			this.updateTooltip( this.switchSourceTitle );
			this.saveBackgroundColor = this.getComponent().find('button').css("background-color");
			this.getComponent().find('button').attr('style', 'background-color: null !important');
			this.getComponent().removeClass( 'open' );
			this.getBtn().addClass( 'disabled' );
		}
	}));

} )( window.mw, window.jQuery );
<|endoftext|>


<commit_before>var D = {};

function toggleGoalCompletion(goal){
	D.goals[D.dayShowing][goal].completed = !D.goals[D.dayShowing][goal].completed;
}

function displayGoal(goal, container){
	goalWrapper = $("<div>");
	label = $("<label>").addClass("goal").text(goal.text).appendTo(goalWrapper);
	$("<input type='checkbox' />").addClass("goalCheck")
								  .appendTo(label)
								  .attr("name", goal.text)
								  .prop("checked", goal.completed)
								  .click(function(e){
								  		toggleGoalCompletion($(this).attr("name"));
								  });
	goalWrapper.appendTo(container);
}

function displayGoals(goals){
	goalContainer = $("#createdGoalsContainer");
	if (Object.keys(goals).length == 0){
		// $("<div>").text("You should add some goals").appendTo(goalContainer);
	}else{
		for (var goal in goals){
			displayGoal(goals[goal], goalContainer);
		}
	}
}

function clearGoals(){
	$("#createdGoalsContainer").empty();
}

$("#addBtn").click(function(){
	goalBox = $("#newGoal");
	goalText = $.trim(goalBox.val());
	console.log(goalText)
	if (goalText.length > 0){
		goal = {text: goalText, complete:false}
		D.goals[D.dayShowing][goalText] = goal;
		clearGoals();
		displayGoals(D.goals[D.dayShowing]);
		goalBox.val('');
	}
});

function onStartUp(){
	D.goals = {"today":
				{ "Finish App":
					{text: "Finish App",
					 completed: true
					}
				}
			  };
	D.dayShowing = "today";
	console.log(D.goals[D.dayShowing]);
	displayGoals(D.goals[D.dayShowing]);
}

onStartUp();<commit_msg>App uses localStorage<commit_after>var D = {goals: [],
		 dayShowing: -1};

function toggleGoalCompletion(goal){
	D.goals[D.dayShowing][goal].completed = !D.goals[D.dayShowing][goal].completed;
	saveData();
}

function displayGoal(goal, container){
	goalWrapper = $("<div>");
	label = $("<label>").addClass("goal").text(goal.text).appendTo(goalWrapper);
	$("<input type='checkbox' />").addClass("goalCheck")
								  .appendTo(label)
								  .attr("name", goal.text)
								  .prop("checked", goal.completed)
								  .click(function(e){
								  		toggleGoalCompletion($(this).attr("name"));
								  });
	goalWrapper.appendTo(container);
}

function displayGoals(goals){
	goalContainer = $("#createdGoalsContainer");
	if (Object.keys(goals).length > 0){
		for (var goal in goals){
			console.log(goals[goal])
			//Make sure to only try to display goal objects
			if (goals[goal].text){
				displayGoal(goals[goal], goalContainer);
			}
		}
	}
}

function clearGoals(){
	$("#createdGoalsContainer").empty();
}

$("#addBtn").click(function(){
	goalBox = $("#newGoal");
	goalText = $.trim(goalBox.val());
	console.log(goalText)
	if (goalText.length > 0){
		goal = {text: goalText, completed:false}
		D.goals[D.dayShowing][goalText] = goal;
		clearGoals();
		displayGoals(D.goals[D.dayShowing]);
		goalBox.val('');
		saveData();
	}
});

function saveData(){
	localStorage["goals"] = JSON.stringify(D.goals);
}

function loadData(){
	goals = localStorage["goals"];
	if (goals){
		D.goals = JSON.parse(goals);
	}else {
		D.goals = [{date: "today"}];
	}
	D.dayShowing = D.goals.length - 1; //goals pushed to end of list for each day
}

function onStartUp(){
	loadData();
	console.log(D.goals[D.dayShowing]);
	displayGoals(D.goals[D.dayShowing]);
}

onStartUp();<|endoftext|>


<commit_before>// Restaurant list service
NGApp.factory('RestaurantsService', function ($http, $rootScope, PositionsService ) {

	var service = { permalink : 'food-delivery', forceLoad : true, forceGetStatus : false, cachedAt: false };
	var restaurants = false;

	service.reset = function () {
		restaurants = false;
	}

	service.getRestaurants = function(){
		return restaurants;
	}

	service.position = PositionsService;

	service.sort = function () {

		var list = restaurants;

		var areAllTheRestaurantsClosed = true;

		// call here that way this method does not have to be called inside each restaurant object
		var now = dateTime.getNow();

		// call the method open to check it status and tagfy
		for (var x in list) {
			if( list[x].open( now ) ){
				areAllTheRestaurantsClosed = false;
			}
			list[x].tagfy();
		}

		// if all are closed sort by that are opening the soonest
		if( areAllTheRestaurantsClosed ){

			if( list && list.sort ){

				list.sort(
					sort_by( {
						name: '_hasHours',
						reverse: true
					}, {
						name: '_opensIn',
						primer: function( n ){
							if( !isNaN( parseInt( n ) ) ){
								return parseInt( n );
							} else {
								// the restaurant does not have open hours for next 24 hours, fake opens in to next week just to make it the last of the list
								return ( 16800 * 60 );
							}
						},
						reverse: false
					}, {
						name: 'distance',
						primer: parseFloat,
						reverse: false
					}, {
						name: '_weight',
						primer: parseFloat,
						reverse: true
					}, {
						name: 'name',
						reverse: false
					} )
				);
			}
		} else {
			list.sort(
				sort_by( {
					name: '_open',
					reverse: true
				}, {
					name: 'delivery',
					reverse: true
				}, {
					name: '_weight',
					primer: parseFloat,
					reverse: true
				}, {
					name: '_opensIn',
					primer: function( n ){
						if( !isNaN( parseInt( n ) ) ){
							return parseInt( n );
						} else {
							// the restaurant does not have open hours for next 24 hours, fake opens in to next week
							return ( 16800 * 60 );
						}
					},
					reverse: false
				}, {
					name: 'distance',
					primer: parseFloat,
					reverse: false
				} )
			);
		}

		if( areAllTheRestaurantsClosed ){
			// Number of restaurants that will have the opening tag
			var tagRestaurantsAsClosing = 3;
			for ( var x in list ) {
				if( tagRestaurantsAsClosing <= 0 ){
					break;
				}
				list[ x ].tagfy( 'opening' );
				tagRestaurantsAsClosing--;
			}
		}

		restaurants = list;
		return restaurants;
	}

	service.getStatus = function () {

		var list = restaurants;
		var allClosed = true;
		var totalClosedRestaurantsAfter = 0;
		var totalClosedRestaurantsBefore = 0;

		for (var x in list) {
			if( !list[x]._open ){
				totalClosedRestaurantsBefore++;
			} else {
				allClosed = false;
			}
		}

		for (var x in list) {
			if( !list[x].open() ){
				totalClosedRestaurantsAfter++;
			}
			list[x].tagfy();
		};

		restaurants = list;

		// Reorder the restaurants
		if( allClosed || ( totalClosedRestaurantsAfter != totalClosedRestaurantsBefore ) ){
			service.sort();
		}
		// check if it is necessary to reload the hours
		service.reloadHours();
		return restaurants;
	}

	service.reloadHours = function(){
		var now = ( Math.floor( new Date().getTime() / 1000 ) );
		var age = Math.floor( now - service.cachedAt ); // age in seconds
		// if the age is more or equals to 23 hours reload the hours
		if( age >= ( ( 60 * 60 ) * 23 ) ){
			service.cachedAt = now;
			console.debug('reloading...');
			for ( var x in restaurants ) {
				restaurants[ x ].reloadHours( true );
			}
		}
	}

	service.list = function ( success, error ) {

		if (!service.position.pos().valid('restaurants')) {
			if (error) {
				error();
			}
			return false;
		}

		if (restaurants === false || service.forceLoad) {

			var url = App.service + 'restaurants?lat=' + service.position.pos().lat() + '&lon=' + service.position.pos().lon() + '&range=' + (service.position.range || 2 );

			service.forceGetStatus = false;

			$http.get( url, {
				cache: false
			}).success(function (data) {
				// property to control the cache expiration of the list
				service.cachedAt = ( Math.floor( new Date().getTime() / 1000 ) );
				var list = [];
				if (typeof data.restaurants == 'undefined' || data.restaurants.length == 0) {
					if (error) {
						error();
						return false;
					}
				} else {
					for (var x in data.restaurants) {
						list[list.length] = new Restaurant(data.restaurants[x]);
					}
					restaurants = list;
					if (success) {
						success(list);
					}
					return list;
				}
			});
			service.forceLoad = false;
		} else {
			if (success) {
				success(restaurants);
			}
			return restaurants;
		}
	}

	$rootScope.$on( 'NewLocationAdded', function(e, data) {
		service.forceLoad = true;
		restaurants = false;
	});

	return service;
});

//CommunityService Service
NGApp.factory( 'CommunityService', function( $http ){
	var service = {};
	service.getById = function( id ){
		for (x in App.communities) {
			if( App.communities[x].id_community == id ){
				return App.communities[x];
			}
		}
		return false;
	}
	return service;
} );

//RestaurantService Service
NGApp.factory( 'RestaurantService', function ($http, $routeParams, $rootScope, CommunityService ) {

	var service = { basicInfo : null, loadedList: {} };

	service.alreadyLoaded = function(){
		return ( service.loadedList[ $routeParams.id ] ? true : false );
	}

	service.init = function(){
		App.cache( 'Restaurant', $routeParams.id, function () {
			var restaurant = this;
			service.loadedList[ $routeParams.id ] = true;
			var community = CommunityService.getById( restaurant.id_community );
			$rootScope.$broadcast( 'restaurantLoaded',  { restaurant : restaurant, community : community } );

		});
	}
	return service;
});<commit_msg>partial #2278 - removed the debug<commit_after>// Restaurant list service
NGApp.factory('RestaurantsService', function ($http, $rootScope, PositionsService ) {

	var service = { permalink : 'food-delivery', forceLoad : true, forceGetStatus : false, cachedAt: false };
	var restaurants = false;

	service.reset = function () {
		restaurants = false;
	}

	service.getRestaurants = function(){
		return restaurants;
	}

	service.position = PositionsService;

	service.sort = function () {

		var list = restaurants;

		var areAllTheRestaurantsClosed = true;

		// call here that way this method does not have to be called inside each restaurant object
		var now = dateTime.getNow();

		// call the method open to check it status and tagfy
		for (var x in list) {
			if( list[x].open( now ) ){
				areAllTheRestaurantsClosed = false;
			}
			list[x].tagfy();
		}

		// if all are closed sort by that are opening the soonest
		if( areAllTheRestaurantsClosed ){

			if( list && list.sort ){

				list.sort(
					sort_by( {
						name: '_hasHours',
						reverse: true
					}, {
						name: '_opensIn',
						primer: function( n ){
							if( !isNaN( parseInt( n ) ) ){
								return parseInt( n );
							} else {
								// the restaurant does not have open hours for next 24 hours, fake opens in to next week just to make it the last of the list
								return ( 16800 * 60 );
							}
						},
						reverse: false
					}, {
						name: 'distance',
						primer: parseFloat,
						reverse: false
					}, {
						name: '_weight',
						primer: parseFloat,
						reverse: true
					}, {
						name: 'name',
						reverse: false
					} )
				);
			}
		} else {
			list.sort(
				sort_by( {
					name: '_open',
					reverse: true
				}, {
					name: 'delivery',
					reverse: true
				}, {
					name: '_weight',
					primer: parseFloat,
					reverse: true
				}, {
					name: '_opensIn',
					primer: function( n ){
						if( !isNaN( parseInt( n ) ) ){
							return parseInt( n );
						} else {
							// the restaurant does not have open hours for next 24 hours, fake opens in to next week
							return ( 16800 * 60 );
						}
					},
					reverse: false
				}, {
					name: 'distance',
					primer: parseFloat,
					reverse: false
				} )
			);
		}

		if( areAllTheRestaurantsClosed ){
			// Number of restaurants that will have the opening tag
			var tagRestaurantsAsClosing = 3;
			for ( var x in list ) {
				if( tagRestaurantsAsClosing <= 0 ){
					break;
				}
				list[ x ].tagfy( 'opening' );
				tagRestaurantsAsClosing--;
			}
		}

		restaurants = list;
		return restaurants;
	}

	service.getStatus = function () {

		var list = restaurants;
		var allClosed = true;
		var totalClosedRestaurantsAfter = 0;
		var totalClosedRestaurantsBefore = 0;

		for (var x in list) {
			if( !list[x]._open ){
				totalClosedRestaurantsBefore++;
			} else {
				allClosed = false;
			}
		}

		for (var x in list) {
			if( !list[x].open() ){
				totalClosedRestaurantsAfter++;
			}
			list[x].tagfy();
		};

		restaurants = list;

		// Reorder the restaurants
		if( allClosed || ( totalClosedRestaurantsAfter != totalClosedRestaurantsBefore ) ){
			service.sort();
		}
		// check if it is necessary to reload the hours
		service.reloadHours();
		return restaurants;
	}

	service.reloadHours = function(){
		var now = ( Math.floor( new Date().getTime() / 1000 ) );
		var age = Math.floor( now - service.cachedAt ); // age in seconds
		// if the age is more or equals to 23 hours reload the hours
		if( age >= ( ( 60 * 60 ) * 23 ) ){
			service.cachedAt = now;
			for ( var x in restaurants ) {
				restaurants[ x ].reloadHours( true );
			}
		}
	}

	service.list = function ( success, error ) {

		if (!service.position.pos().valid('restaurants')) {
			if (error) {
				error();
			}
			return false;
		}

		if (restaurants === false || service.forceLoad) {

			var url = App.service + 'restaurants?lat=' + service.position.pos().lat() + '&lon=' + service.position.pos().lon() + '&range=' + (service.position.range || 2 );

			service.forceGetStatus = false;

			$http.get( url, {
				cache: false
			}).success(function (data) {
				// property to control the cache expiration of the list
				service.cachedAt = ( Math.floor( new Date().getTime() / 1000 ) );
				var list = [];
				if (typeof data.restaurants == 'undefined' || data.restaurants.length == 0) {
					if (error) {
						error();
						return false;
					}
				} else {
					for (var x in data.restaurants) {
						list[list.length] = new Restaurant(data.restaurants[x]);
					}
					restaurants = list;
					if (success) {
						success(list);
					}
					return list;
				}
			});
			service.forceLoad = false;
		} else {
			if (success) {
				success(restaurants);
			}
			return restaurants;
		}
	}

	$rootScope.$on( 'NewLocationAdded', function(e, data) {
		service.forceLoad = true;
		restaurants = false;
	});

	return service;
});

//CommunityService Service
NGApp.factory( 'CommunityService', function( $http ){
	var service = {};
	service.getById = function( id ){
		for (x in App.communities) {
			if( App.communities[x].id_community == id ){
				return App.communities[x];
			}
		}
		return false;
	}
	return service;
} );

//RestaurantService Service
NGApp.factory( 'RestaurantService', function ($http, $routeParams, $rootScope, CommunityService ) {

	var service = { basicInfo : null, loadedList: {} };

	service.alreadyLoaded = function(){
		return ( service.loadedList[ $routeParams.id ] ? true : false );
	}

	service.init = function(){
		App.cache( 'Restaurant', $routeParams.id, function () {
			var restaurant = this;
			service.loadedList[ $routeParams.id ] = true;
			var community = CommunityService.getById( restaurant.id_community );
			$rootScope.$broadcast( 'restaurantLoaded',  { restaurant : restaurant, community : community } );

		});
	}
	return service;
});<|endoftext|>


<commit_before>import React from 'react';
import { Button, Grid, Row, Col, FormGroup, FormControl, ControlLabel, ListGroup, ListGroupItem, Glyphicon } from 'react-bootstrap';
import Utility from '../../../../shared/util/Utility';
import devOnly from '../../../../shared/util/devOnly';

import * as Lessons from '../../../../shared/reducers/Lessons';
import * as Security from '../../../../shared/reducers/Security';

export const Reducers = [Lessons, Security];

export class selectScreen extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleLanguageSelection = this.handleLanguageSelection.bind(this);
    this.handleStarredClick = this.handleStarredClick.bind(this);

    this.onKeys = this.onKeys.bind(this);
  }

  componentDidMount() {
    window.addEventListener('keydown', this.onKeys);
  }

  componentWillMount() {
    this.props.fetchLessons(this.props.params.type)
      .catch(() => this.props.verifyUserLoggedIn());

    this.props.fetchUserStarredLessons()
      .catch(() => this.props.verifyUserLoggedIn());
  }

  // Triggers when we change between play types but remain in "selection" page
  componentWillReceiveProps(nextProps) {
    if (this.props.params.type !== nextProps.params.type) {
      this.props.fetchLessons(nextProps.params.type)
        .catch(() => this.props.verifyUserLoggedIn());
    }
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.onKeys);
  }

  onKeys(event) {
    if (event.keyCode === 13) {
      this.handleSubmit(event);
    }
  }

  getPageHeader() {
    switch (this.props.params.type) {
      case 'quiz':
        return
Quiz
;
      case 'guess':
        return
Gissa ordet
;
      case 'translate':
        return
Översätt ordet
;
      case 'flashcards':
        return
Bildkort
;
      default:
        throw new Error('No play type specified');
    }
  }

  handleLanguageSelection(event) {
    switch (event.target.name) {
      case 'selectedLesson':
        this.props.setSelectedLesson(event.target.value);
        break;
      case 'questionType':
        this.props.setQuestionLanguage(event.target.value);
        break;
      case 'answerType':
        this.props.setAnswerLanguage(event.target.value);
        break;
      default:
        break;
    }
  }
  handleSubmit(event) {
    event.preventDefault();
    try {
      this.props.fetchLesson(this.props.params.type)
          .then(() => {
            this.props.setPageByName(`/play/${this.props.params.type}`);
          });
    } catch (err) {
      this.props.verifyUserLoggedIn();
    }
  }

  handleStarredClick(lesson) {
    return this.props.starredLessons.map(userLesson => userLesson.lesson.name).includes(lesson.name) ?
      this.props.removeStarredLesson(lesson.name) :
      this.props.addStarredLesson(lesson.name);
  }
  render() {
    const options = this.props.lessons.map(lesson =>
      <Row key={lesson.name}>
        <Col xs={2} md={1} lg={2}>
          <Button
            bsStyle={this.props.starredLessons.map(userLesson => userLesson.lesson.name).includes(lesson.name) ? 'warning' : null}
            onClick={() => this.handleStarredClick(lesson)}
          >
            <Glyphicon glyph="star" />
          </Button>
        </Col>
        <Col xs={10} md={11} lg={10}>
          <ListGroupItem
            key={lesson.name}
            onClick={() => this.props.setSelectedLesson(lesson)}
            value={lesson.name}
            header={lesson.name}
            bsStyle={lesson.name === this.props.selectedLesson.name ? 'info' : null}
          />
        </Col>
      </Row>);

    const languages = [];
    languages.push(
      <option key={'reading'} value={'reading'}>Japanska</option>,
      <option key={'swedish'} value={'swedish'}>Svenska</option>
    );

    devOnly(this, () => languages.push(<option key={'english'} value={'english'}>Engelska</option>));

    let languageSelection;
    if (this.props.params.type === 'quiz') {
      languageSelection =
;
    } else {
      languageSelection = (

          <ControlLabel>Välj frågespråk</ControlLabel>
          <FormControl
            componentClass="select"
            name="questionType"
            id="questionLanguageSelection"
            onChange={this.handleLanguageSelection}
            value={this.props.questionType}
          >
            {languages}
          </FormControl>
          <ControlLabel>Välj svarspråk</ControlLabel>
          <FormControl
            componentClass="select"
            name="answerType"
            id="answerLanguageSelection"
            onChange={this.handleLanguageSelection}
            value={this.props.answerType}
          >
            {languages}
          </FormControl>

);
    }
    return (
      <Grid className="text-center">
        <Row>
          {this.getPageHeader()}
        </Row>
        <Row>
          <Col xs={8} xsOffset={2} lg={4} lgOffset={4}>
            <form href="#" onSubmit={this.handleSubmit}>
              <FormGroup>
                <ControlLabel>Välj lista av frågor</ControlLabel>
                <ListGroup>
                  {options}
                </ListGroup>
                {languageSelection}
              </FormGroup>
              <FormControl
                type="hidden"
                onKeyPress={this.handleKeyPress}
              />
              <Button type="submit" bsStyle="primary"> Starta </Button>
            </form>
          </Col>
        </Row>
        <Row>

        </Row>
      </Grid>
    );
  }
}

selectScreen.defaultProps = Utility.reduxEnabledDefaultProps({

}, Reducers);

selectScreen.propTypes = Utility.reduxEnabledPropTypes({

}, Reducers);

export default Utility.superConnect(this, Reducers)(selectScreen);
<commit_msg>Make select screen prettier<commit_after>import React from 'react';
import { Button, Grid, Row, Col, FormGroup, FormControl, ControlLabel, ListGroup, ListGroupItem, Glyphicon, HelpBlock } from 'react-bootstrap';
import Utility from '../../../../shared/util/Utility';
import devOnly from '../../../../shared/util/devOnly';

import * as Lessons from '../../../../shared/reducers/Lessons';
import * as Security from '../../../../shared/reducers/Security';

export const Reducers = [Lessons, Security];

export class selectScreen extends React.Component {
  constructor(props) {
    super(props);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleLanguageSelection = this.handleLanguageSelection.bind(this);
    this.handleStarredClick = this.handleStarredClick.bind(this);

    this.onKeys = this.onKeys.bind(this);
  }

  componentWillMount() {
    this.props.fetchLessons(this.props.params.type)
      .catch(() => this.props.verifyUserLoggedIn());

    this.props.fetchUserStarredLessons()
      .catch(() => this.props.verifyUserLoggedIn());
  }

  componentDidMount() {
    window.addEventListener('keydown', this.onKeys);
  }

  // Triggers when we change between play types but remain in "selection" page
  componentWillReceiveProps(nextProps) {
    if (this.props.params.type !== nextProps.params.type) {
      this.props.fetchLessons(nextProps.params.type)
        .catch(() => this.props.verifyUserLoggedIn());
    }
  }

  componentWillUnmount() {
    window.removeEventListener('keydown', this.onKeys);
  }

  onKeys(event) {
    if (event.keyCode === 13) {
      this.handleSubmit(event);
    }
  }

  getPageHeader() {
    switch (this.props.params.type) {
      case 'quiz':
        return
Quiz
;
      case 'guess':
        return
Gissa ordet
;
      case 'translate':
        return
Översätt ordet
;
      case 'flashcards':
        return
Bildkort
;
      default:
        throw new Error('No play type specified');
    }
  }

  getPageDescription() {
    switch (this.props.params.type) {
      case 'quiz':
        return (Sätt dina kunskaper om Japan på prov genom att välja en av 4 svarsalternativ);
      case 'guess':
        return Välj mellan 4 svarsalternativ för den korrekta översättningen.;
      case 'translate':
        return Översätt det visade ordet i fritext.;
      case 'flashcards':
        return (Träna dig själv genom att använda kort,
        med frågan på ena sidan och rätta svaret på den andra.);
      default:
        throw new Error('No play type specified');
    }
  }

  handleLanguageSelection(event) {
    switch (event.target.name) {
      case 'selectedLesson':
        this.props.setSelectedLesson(event.target.value);
        break;
      case 'questionType':
        this.props.setQuestionLanguage(event.target.value);
        break;
      case 'answerType':
        this.props.setAnswerLanguage(event.target.value);
        break;
      default:
        break;
    }
  }
  handleSubmit(event) {
    event.preventDefault();
    try {
      this.props.fetchLesson(this.props.params.type)
          .then(() => {
            this.props.setPageByName(`/play/${this.props.params.type}`);
          });
    } catch (err) {
      this.props.verifyUserLoggedIn();
    }
  }

  handleStarredClick(lesson) {
    return this.props.starredLessons.map(userLesson => userLesson.lesson.name).includes(lesson.name) ?
      this.props.removeStarredLesson(lesson.name) :
      this.props.addStarredLesson(lesson.name);
  }
  render() {
    const options = this.props.lessons.map(lesson =>
      <Row key={lesson.name}>
        <Col xs={2} md={1} lg={2}>
          <Button
            bsStyle={this.props.starredLessons.map(userLesson => userLesson.lesson.name).includes(lesson.name) ? 'warning' : null}
            onClick={() => this.handleStarredClick(lesson)}
          >
            <Glyphicon glyph="star" />
          </Button>
        </Col>
        <Col xs={10} md={11} lg={10}>
          <ListGroupItem
            key={lesson.name}
            onClick={() => this.props.setSelectedLesson(lesson)}
            value={lesson.name}
            header={lesson.name}
            bsStyle={lesson.name === this.props.selectedLesson.name ? 'info' : null}
          />
        </Col>
      </Row>);

    const languages = [];
    languages.push(
      <option key={'reading'} value={'reading'}>Japanska</option>,
      <option key={'swedish'} value={'swedish'}>Svenska</option>
    );

    devOnly(this, () => languages.push(<option key={'english'} value={'english'}>Engelska</option>));

    let languageSelection;
    if (this.props.params.type === 'quiz') {
      languageSelection =
;
    } else {
      languageSelection = (
        <FormGroup>
          <Row>
            <Col xs={6} pullLeft>
              <HelpBlock>Frågespråk</HelpBlock>
              <FormControl
                componentClass="select"
                name="questionType"
                id="questionLanguageSelection"
                onChange={this.handleLanguageSelection}
                value={this.props.questionType}
              >
                {languages}
              </FormControl>
            </Col>
            <Col xs={6}>
              <HelpBlock>Svarspråk</HelpBlock>
              <FormControl
                componentClass="select"
                name="answerType"
                id="answerLanguageSelection"
                onChange={this.handleLanguageSelection}
                value={this.props.answerType}
              >
                {languages}
              </FormControl>

            </Col>
          </Row>
        </FormGroup>);
    }
    return (
      <Grid className="text-center">
        <Col xs={8} xsOffset={2} lg={4} lgOffset={4}>
          <form href="#" onSubmit={this.handleSubmit}>
            <FormGroup>
              <ControlLabel>{this.getPageHeader()}</ControlLabel>
              <ControlLabel>{this.getPageDescription()}</ControlLabel>
            </FormGroup>

            <FormGroup>
              <HelpBlock>Välj ordsamlingar i listan nedan</HelpBlock>
              <ListGroup>
                {options}
              </ListGroup>
              {languageSelection}
            </FormGroup>
            <FormGroup>
              <FormControl
                type="hidden"
                onKeyPress={this.handleKeyPress}
              />
              <Button type="submit" bsStyle="primary"> Starta </Button>
            </FormGroup>

          </form>
        </Col>
      </Grid>
    );
  }
}

selectScreen.defaultProps = Utility.reduxEnabledDefaultProps({

}, Reducers);

selectScreen.propTypes = Utility.reduxEnabledPropTypes({

}, Reducers);

export default Utility.superConnect(this, Reducers)(selectScreen);
<|endoftext|>