<commit_before>'use strict';
const ChromeToElectronModule = require('electron-to-chromium/chromium-versions');
const ChromeToElectron = Object.entries(ChromeToElectronModule);

module.exports = {
  // https://nodejs.org/dist/index.json
  ChromeToNode: [
    [3, '0.0.3'],
    [4, '0.1.19'],
    [5, '0.1.27'],
    [6, '0.1.90'],
    [7, '0.1.101'],
    [9, '0.3.0'],
    [10, '0.3.2'],
    [11, '0.3.8'],
    [14, '0.5.1'],
    [15, '0.5.4'],
    [16, '0.5.6'],
    [18, '0.7.0'],
    [19, '0.7.3'],
    [21, '0.7.11'],
    [23, '0.9.3'],
    [24, '0.9.6'],
    [27, '0.11.0'],
    [28, '0.11.1'],
    [29, '0.11.2'],
    [30, '0.11.4'],
    [31, '0.11.8'],
    [32, '0.11.9'],
    [35, '0.11.13'],
    [36, '0.11.14'],
    [38, '0.11.15'],
    [41, '1.0'], // io.js
    [42, '2.0'], // io.js
    [44, '3.0'], // io.js
    [45, '4.0'],
    [46, '5.0'],
    [50, '6.0'],
    [51, '6.5'],
    [54, '7.0'],
    [55, '7.6'],
    [58, '8.0'],
    [60, '8.3'],
    [61, '8.7'],
    [62, '8.10'],
    [66, '10.0'],
    [67, '10.4'],
    [68, '10.9'],
    [70, '11.0'],
    [74, '12.0'],
    [75, '12.5'],
    [76, '12.9'],
    [77, '12.11'],
    [78, '13.0'],
    [79, '13.2'],
    [81, '14.0'],
    [83, '14.5'],
    [84, '14.6'],
    [86, '15.0'],
  ],
  // https://github.com/mdn/browser-compat-data/blob/master/browsers/samsunginternet_android.json
  ChromeToSamsung: [
    [18, '1.0'],
    [28, '1.5'],
    [34, '2.0'],
    [38, '3.0'],
    [42, '3.4'],
    [44, '4.0'],
    [51, '5.0'],
    [56, '6.0'],
    [59, '7.0'],
    [63, '8.0'],
    [67, '9.0'],
    [71, '10.0'],
    [75, '11.0'],
    [79, '12.0'],
    [83, '13.0'],
  ],
  // https://github.com/mdn/browser-compat-data/blob/master/browsers/opera_android.json
  ChromeToOperaMobile: [
    [59, 43],
    [60, 44],
    [61, 45],
    [63, 46],
    [66, 47],
    [69, 48],
    [70, 49],
    [71, 50],
    [72, 51],
    [73, 52],
    [74, 53],
    [76, 54],
    [77, 55],
  ],
  ChromeToAndroid: [
    [9, '3.0'],
    [12, '4.0'],
    [30, '4.4'],
    [33, '4.4.3'],
  ],
  ChromeToElectron,
  // https://github.com/mdn/browser-compat-data/blob/master/browsers/safari_ios.json
  SafariToIOS: [
    ['3.0', '1.0'],
    ['3.1', '2.0'],
    ['3.2', '3.0'],
    ['4.0', '3.2'],
    ['4.1', '4.2'],
    ['5.1', '5.1'],
    ['6.0', '6.0'],
    ['6.1', '7.0'],
    ['7.1', '8.0'],
    ['9.0', '9.0'],
    ['9.1', '9.3'],
    ['10.0', '10.0'],
    ['10.1', '10.3'],
    ['11.0', '11.0'],
    ['11.1', '11.3'],
    ['12.0', '12.0'],
    ['12.1', '12.2'],
    ['13.0', '13.0'],
  ],
  SafariToPhantomJS: [
    ['4.1', '1.9'],
    ['6.0', '2.0'],
  ],
};
<commit_msg>update mobile opera to chrome mapping<commit_after>'use strict';
const ChromeToElectronModule = require('electron-to-chromium/chromium-versions');
const ChromeToElectron = Object.entries(ChromeToElectronModule);

module.exports = {
  // https://nodejs.org/dist/index.json
  ChromeToNode: [
    [3, '0.0.3'],
    [4, '0.1.19'],
    [5, '0.1.27'],
    [6, '0.1.90'],
    [7, '0.1.101'],
    [9, '0.3.0'],
    [10, '0.3.2'],
    [11, '0.3.8'],
    [14, '0.5.1'],
    [15, '0.5.4'],
    [16, '0.5.6'],
    [18, '0.7.0'],
    [19, '0.7.3'],
    [21, '0.7.11'],
    [23, '0.9.3'],
    [24, '0.9.6'],
    [27, '0.11.0'],
    [28, '0.11.1'],
    [29, '0.11.2'],
    [30, '0.11.4'],
    [31, '0.11.8'],
    [32, '0.11.9'],
    [35, '0.11.13'],
    [36, '0.11.14'],
    [38, '0.11.15'],
    [41, '1.0'], // io.js
    [42, '2.0'], // io.js
    [44, '3.0'], // io.js
    [45, '4.0'],
    [46, '5.0'],
    [50, '6.0'],
    [51, '6.5'],
    [54, '7.0'],
    [55, '7.6'],
    [58, '8.0'],
    [60, '8.3'],
    [61, '8.7'],
    [62, '8.10'],
    [66, '10.0'],
    [67, '10.4'],
    [68, '10.9'],
    [70, '11.0'],
    [74, '12.0'],
    [75, '12.5'],
    [76, '12.9'],
    [77, '12.11'],
    [78, '13.0'],
    [79, '13.2'],
    [81, '14.0'],
    [83, '14.5'],
    [84, '14.6'],
    [86, '15.0'],
  ],
  // https://github.com/mdn/browser-compat-data/blob/master/browsers/samsunginternet_android.json
  ChromeToSamsung: [
    [18, '1.0'],
    [28, '1.5'],
    [34, '2.0'],
    [38, '3.0'],
    [42, '3.4'],
    [44, '4.0'],
    [51, '5.0'],
    [56, '6.0'],
    [59, '7.0'],
    [63, '8.0'],
    [67, '9.0'],
    [71, '10.0'],
    [75, '11.0'],
    [79, '12.0'],
    [83, '13.0'],
  ],
  // https://github.com/mdn/browser-compat-data/blob/master/browsers/opera_android.json
  ChromeToOperaMobile: [
    [59, 43],
    [60, 44],
    [61, 45],
    [63, 46],
    [66, 47],
    [69, 48],
    [70, 49],
    [71, 50],
    [72, 51],
    [73, 52],
    [74, 53],
    [76, 54],
    [77, 55],
    [78, 56],
    [80, 57],
    [81, 58],
    [83, 59],
    [85, 60],
  ],
  ChromeToAndroid: [
    [9, '3.0'],
    [12, '4.0'],
    [30, '4.4'],
    [33, '4.4.3'],
  ],
  ChromeToElectron,
  // https://github.com/mdn/browser-compat-data/blob/master/browsers/safari_ios.json
  SafariToIOS: [
    ['3.0', '1.0'],
    ['3.1', '2.0'],
    ['3.2', '3.0'],
    ['4.0', '3.2'],
    ['4.1', '4.2'],
    ['5.1', '5.1'],
    ['6.0', '6.0'],
    ['6.1', '7.0'],
    ['7.1', '8.0'],
    ['9.0', '9.0'],
    ['9.1', '9.3'],
    ['10.0', '10.0'],
    ['10.1', '10.3'],
    ['11.0', '11.0'],
    ['11.1', '11.3'],
    ['12.0', '12.0'],
    ['12.1', '12.2'],
    ['13.0', '13.0'],
  ],
  SafariToPhantomJS: [
    ['4.1', '1.9'],
    ['6.0', '2.0'],
  ],
};
<|endoftext|>


<commit_before>import Attribute from './Attribute';
import Buffer from './Buffer';
import interleaveTypedArrays from './utils/interleaveTypedArrays';
import getBufferType from './utils/getBufferType';
import { Runner } from '@pixi/runner';

const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 };
let UID = 0;

/* eslint-disable object-shorthand */
const map = {
    Float32Array: Float32Array,
    Uint32Array: Uint32Array,
    Int32Array: Int32Array,
    Uint8Array: Uint8Array,
    Uint16Array: Uint16Array,
};

/* eslint-disable max-len */

/**
 * The Geometry represents a model. It consists of two components:
 * - GeometryStyle - The structure of the model such as the attributes layout
 * - GeometryData - the data of the model - this consists of buffers.
 * This can include anything from positions, uvs, normals, colors etc.
 *
 * Geometry can be defined without passing in a style or data if required (thats how I prefer!)
 *
 * ```js
 * let geometry = new PIXI.Geometry();
 *
 * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2);
 * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2)
 * geometry.addIndex([0,1,2,1,3,2])
 *
 * ```
 * @class
 * @memberof PIXI
 */
export default class Geometry
{
    /**
     * @param {PIXI.Buffer[]} [buffers]  an array of buffers. optional.
     * @param {object} [attributes] of the geometry, optional structure of the attributes layout
     */
    constructor(buffers = [], attributes = {})
    {
        this.buffers = buffers;

        this.indexBuffer = null;

        this.attributes = attributes;

        /**
         * A map of renderer IDs to webgl VAOs
         *
         * @protected
         * @type {object}
         */
        this.glVertexArrayObjects = {};

        this.id = UID++;

        this.instanced = false;

        this.instanceCount = 1;

        this._size = null;

        this.disposeRunner = new Runner('disposeGeometry', 2);

        /**
         * Count of existing (not destroyed) meshes that reference this geometry
         * @member {boolean}
         */
        this.refCount = 0;
    }

    /**
    *
    * Adds an attribute to the geometry
    *
    * @param {String} id - the name of the attribute (matching up to a shader)
    * @param {PIXI.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it.
    * @param {Number} [size=0] the size of the attribute. If you have 2 floats per vertex (eg position x and y) this would be 2
    * @param {Boolean} [normalized=false] should the data be normalized.
    * @param {Number} [type=PIXI.TYPES.FLOAT] what type of number is the attribute. Check {PIXI.TYPES} to see the ones available
    * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data)
    * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data)
    *
    * @return {PIXI.Geometry} returns self, useful for chaining.
    */
    addAttribute(id, buffer, size, normalized = false, type, stride, start, instance = false)
    {
        if (!buffer)
        {
            throw new Error('You must pass a buffer when creating an attribute');
        }

        // check if this is a buffer!
        if (!buffer.data)
        {
            // its an array!
            if (buffer instanceof Array)
            {
                buffer = new Float32Array(buffer);
            }

            buffer = new Buffer(buffer);
        }

        const ids = id.split('|');

        if (ids.length > 1)
        {
            for (let i = 0; i < ids.length; i++)
            {
                this.addAttribute(ids[i], buffer, size, normalized, type);
            }

            return this;
        }

        let bufferIndex = this.buffers.indexOf(buffer);

        if (bufferIndex === -1)
        {
            this.buffers.push(buffer);
            bufferIndex = this.buffers.length - 1;
        }

        this.attributes[id] = new Attribute(bufferIndex, size, normalized, type, stride, start, instance);

        // assuming that if there is instanced data then this will be drawn with instancing!
        this.instanced = this.instanced || instance;

        return this;
    }

    /**
     * returns the requested attribute
     *
     * @param {String} id  the name of the attribute required
     * @return {PIXI.Attribute} the attribute requested.
     */
    getAttribute(id)
    {
        return this.attributes[id];
    }

    /**
     * returns the requested buffer
     *
     * @param {String} id  the name of the buffer required
     * @return {PIXI.Buffer} the buffer requested.
     */
    getBuffer(id)
    {
        return this.buffers[this.getAttribute(id).buffer];
    }

    /**
    *
    * Adds an index buffer to the geometry
    * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer.
    *
    * @param {PIXI.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it.
    * @return {PIXI.Geometry} returns self, useful for chaining.
    */
    addIndex(buffer)
    {
        if (!buffer.data)
        {
            // its an array!
            if (buffer instanceof Array)
            {
                buffer = new Uint16Array(buffer);
            }

            buffer = new Buffer(buffer);
        }

        buffer.index = true;
        this.indexBuffer = buffer;

        if (this.buffers.indexOf(buffer) === -1)
        {
            this.buffers.push(buffer);
        }

        return this;
    }

    /**
     * returns the index buffer
     *
     * @return {PIXI.Buffer} the index buffer.
     */
    getIndex()
    {
        return this.indexBuffer;
    }

    /**
     * this function modifies the structure so that all current attributes become interleaved into a single buffer
     * This can be useful if your model remains static as it offers a little performance boost
     *
     * @return {PIXI.Geometry} returns self, useful for chaining.
     */
    interleave()
    {
        // a simple check to see if buffers are already interleaved..
        if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this;

        // assume already that no buffers are interleaved
        const arrays = [];
        const sizes = [];
        const interleavedBuffer = new Buffer();
        let i;

        for (i in this.attributes)
        {
            const attribute = this.attributes[i];

            const buffer = this.buffers[attribute.buffer];

            arrays.push(buffer.data);

            sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4);

            attribute.buffer = 0;
        }

        interleavedBuffer.data = interleaveTypedArrays(arrays, sizes);

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

        this.buffers = [interleavedBuffer];

        if (this.indexBuffer)
        {
            this.buffers.push(this.indexBuffer);
        }

        return this;
    }

    getSize()
    {
        for (const i in this.attributes)
        {
            const attribute = this.attributes[i];
            const buffer = this.buffers[attribute.buffer];

            return buffer.data.length / ((attribute.stride / 4) || attribute.size);
        }

        return 0;
    }

    /**
     * disposes WebGL resources that are connected to this geometry
     */
    dispose()
    {
        this.disposeRunner.run(this, false);
    }

    /**
     * Destroys the geometry.
     */
    destroy()
    {
        this.dispose();

        this.buffers = null;
        this.indexBuffer.destroy();

        this.attributes = null;
    }

    /**
     * returns a clone of the geometry
     *
     * @returns {PIXI.Geometry} a new clone of this geometry
     */
    clone()
    {
        const geometry = new Geometry();

        for (let i = 0; i < this.buffers.length; i++)
        {
            geometry.buffers[i] = new Buffer(this.buffers[i].data.slice());
        }

        for (const i in this.attributes)
        {
            const attrib = this.attributes[i];

            geometry.attributes[i] = new Attribute(
                attrib.buffer,
                attrib.size,
                attrib.normalized,
                attrib.type,
                attrib.stride,
                attrib.start,
                attrib.instance
            );
        }

        if (this.indexBuffer)
        {
            geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)];
            geometry.indexBuffer.index = true;
        }

        return geometry;
    }

    /**
     * merges an array of geometries into a new single one
     * geometry attribute styles must match for this operation to work
     *
     * @param {PIXI.Geometry[]} geometries array of geometries to merge
     * @returns {PIXI.Geometry} shiny new geometry!
     */
    static merge(geometries)
    {
        // todo add a geometry check!
        // also a size check.. cant be too big!]

        const geometryOut = new Geometry();

        const arrays = [];
        const sizes = [];
        const offsets = [];

        let geometry;

        // pass one.. get sizes..
        for (let i = 0; i < geometries.length; i++)
        {
            geometry = geometries[i];

            for (let j = 0; j < geometry.buffers.length; j++)
            {
                sizes[j] = sizes[j] || 0;
                sizes[j] += geometry.buffers[j].data.length;
                offsets[j] = 0;
            }
        }

        // build the correct size arrays..
        for (let i = 0; i < geometry.buffers.length; i++)
        {
            // TODO types!
            arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]);
            geometryOut.buffers[i] = new Buffer(arrays[i]);
        }

        // pass to set data..
        for (let i = 0; i < geometries.length; i++)
        {
            geometry = geometries[i];

            for (let j = 0; j < geometry.buffers.length; j++)
            {
                arrays[j].set(geometry.buffers[j].data, offsets[j]);
                offsets[j] += geometry.buffers[j].data.length;
            }
        }

        geometryOut.attributes = geometry.attributes;

        if (geometry.indexBuffer)
        {
            geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)];
            geometryOut.indexBuffer.index = true;

            let offset = 0;
            let stride = 0;
            let offset2 = 0;
            let bufferIndexToCount = 0;

            // get a buffer
            for (let i = 0; i < geometry.buffers.length; i++)
            {
                if (geometry.buffers[i] !== geometry.indexBuffer)
                {
                    bufferIndexToCount = i;
                    break;
                }
            }

            // figure out the stride of one buffer..
            for (const i in geometry.attributes)
            {
                const attribute = geometry.attributes[i];

                if ((attribute.buffer | 0) === bufferIndexToCount)
                {
                    stride += ((attribute.size * byteSizeMap[attribute.type]) / 4);
                }
            }

            // time to off set all indexes..
            for (let i = 0; i < geometries.length; i++)
            {
                const indexBufferData = geometries[i].indexBuffer.data;

                for (let j = 0; j < indexBufferData.length; j++)
                {
                    geometryOut.indexBuffer.data[j + offset2] += offset;
                }

                offset += geometry.buffers[bufferIndexToCount].data.length / (stride);
                offset2 += indexBufferData.length;
            }
        }

        return geometryOut;
    }
}
<commit_msg>Fix JSDoc type of refCount (#5787)<commit_after>import Attribute from './Attribute';
import Buffer from './Buffer';
import interleaveTypedArrays from './utils/interleaveTypedArrays';
import getBufferType from './utils/getBufferType';
import { Runner } from '@pixi/runner';

const byteSizeMap = { 5126: 4, 5123: 2, 5121: 1 };
let UID = 0;

/* eslint-disable object-shorthand */
const map = {
    Float32Array: Float32Array,
    Uint32Array: Uint32Array,
    Int32Array: Int32Array,
    Uint8Array: Uint8Array,
    Uint16Array: Uint16Array,
};

/* eslint-disable max-len */

/**
 * The Geometry represents a model. It consists of two components:
 * - GeometryStyle - The structure of the model such as the attributes layout
 * - GeometryData - the data of the model - this consists of buffers.
 * This can include anything from positions, uvs, normals, colors etc.
 *
 * Geometry can be defined without passing in a style or data if required (thats how I prefer!)
 *
 * ```js
 * let geometry = new PIXI.Geometry();
 *
 * geometry.addAttribute('positions', [0, 0, 100, 0, 100, 100, 0, 100], 2);
 * geometry.addAttribute('uvs', [0,0,1,0,1,1,0,1],2)
 * geometry.addIndex([0,1,2,1,3,2])
 *
 * ```
 * @class
 * @memberof PIXI
 */
export default class Geometry
{
    /**
     * @param {PIXI.Buffer[]} [buffers]  an array of buffers. optional.
     * @param {object} [attributes] of the geometry, optional structure of the attributes layout
     */
    constructor(buffers = [], attributes = {})
    {
        this.buffers = buffers;

        this.indexBuffer = null;

        this.attributes = attributes;

        /**
         * A map of renderer IDs to webgl VAOs
         *
         * @protected
         * @type {object}
         */
        this.glVertexArrayObjects = {};

        this.id = UID++;

        this.instanced = false;

        this.instanceCount = 1;

        this._size = null;

        this.disposeRunner = new Runner('disposeGeometry', 2);

        /**
         * Count of existing (not destroyed) meshes that reference this geometry
         * @member {number}
         */
        this.refCount = 0;
    }

    /**
    *
    * Adds an attribute to the geometry
    *
    * @param {String} id - the name of the attribute (matching up to a shader)
    * @param {PIXI.Buffer} [buffer] the buffer that holds the data of the attribute . You can also provide an Array and a buffer will be created from it.
    * @param {Number} [size=0] the size of the attribute. If you have 2 floats per vertex (eg position x and y) this would be 2
    * @param {Boolean} [normalized=false] should the data be normalized.
    * @param {Number} [type=PIXI.TYPES.FLOAT] what type of number is the attribute. Check {PIXI.TYPES} to see the ones available
    * @param {Number} [stride=0] How far apart (in floats) the start of each value is. (used for interleaving data)
    * @param {Number} [start=0] How far into the array to start reading values (used for interleaving data)
    *
    * @return {PIXI.Geometry} returns self, useful for chaining.
    */
    addAttribute(id, buffer, size, normalized = false, type, stride, start, instance = false)
    {
        if (!buffer)
        {
            throw new Error('You must pass a buffer when creating an attribute');
        }

        // check if this is a buffer!
        if (!buffer.data)
        {
            // its an array!
            if (buffer instanceof Array)
            {
                buffer = new Float32Array(buffer);
            }

            buffer = new Buffer(buffer);
        }

        const ids = id.split('|');

        if (ids.length > 1)
        {
            for (let i = 0; i < ids.length; i++)
            {
                this.addAttribute(ids[i], buffer, size, normalized, type);
            }

            return this;
        }

        let bufferIndex = this.buffers.indexOf(buffer);

        if (bufferIndex === -1)
        {
            this.buffers.push(buffer);
            bufferIndex = this.buffers.length - 1;
        }

        this.attributes[id] = new Attribute(bufferIndex, size, normalized, type, stride, start, instance);

        // assuming that if there is instanced data then this will be drawn with instancing!
        this.instanced = this.instanced || instance;

        return this;
    }

    /**
     * returns the requested attribute
     *
     * @param {String} id  the name of the attribute required
     * @return {PIXI.Attribute} the attribute requested.
     */
    getAttribute(id)
    {
        return this.attributes[id];
    }

    /**
     * returns the requested buffer
     *
     * @param {String} id  the name of the buffer required
     * @return {PIXI.Buffer} the buffer requested.
     */
    getBuffer(id)
    {
        return this.buffers[this.getAttribute(id).buffer];
    }

    /**
    *
    * Adds an index buffer to the geometry
    * The index buffer contains integers, three for each triangle in the geometry, which reference the various attribute buffers (position, colour, UV coordinates, other UV coordinates, normal, …). There is only ONE index buffer.
    *
    * @param {PIXI.Buffer} [buffer] the buffer that holds the data of the index buffer. You can also provide an Array and a buffer will be created from it.
    * @return {PIXI.Geometry} returns self, useful for chaining.
    */
    addIndex(buffer)
    {
        if (!buffer.data)
        {
            // its an array!
            if (buffer instanceof Array)
            {
                buffer = new Uint16Array(buffer);
            }

            buffer = new Buffer(buffer);
        }

        buffer.index = true;
        this.indexBuffer = buffer;

        if (this.buffers.indexOf(buffer) === -1)
        {
            this.buffers.push(buffer);
        }

        return this;
    }

    /**
     * returns the index buffer
     *
     * @return {PIXI.Buffer} the index buffer.
     */
    getIndex()
    {
        return this.indexBuffer;
    }

    /**
     * this function modifies the structure so that all current attributes become interleaved into a single buffer
     * This can be useful if your model remains static as it offers a little performance boost
     *
     * @return {PIXI.Geometry} returns self, useful for chaining.
     */
    interleave()
    {
        // a simple check to see if buffers are already interleaved..
        if (this.buffers.length === 1 || (this.buffers.length === 2 && this.indexBuffer)) return this;

        // assume already that no buffers are interleaved
        const arrays = [];
        const sizes = [];
        const interleavedBuffer = new Buffer();
        let i;

        for (i in this.attributes)
        {
            const attribute = this.attributes[i];

            const buffer = this.buffers[attribute.buffer];

            arrays.push(buffer.data);

            sizes.push((attribute.size * byteSizeMap[attribute.type]) / 4);

            attribute.buffer = 0;
        }

        interleavedBuffer.data = interleaveTypedArrays(arrays, sizes);

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

        this.buffers = [interleavedBuffer];

        if (this.indexBuffer)
        {
            this.buffers.push(this.indexBuffer);
        }

        return this;
    }

    getSize()
    {
        for (const i in this.attributes)
        {
            const attribute = this.attributes[i];
            const buffer = this.buffers[attribute.buffer];

            return buffer.data.length / ((attribute.stride / 4) || attribute.size);
        }

        return 0;
    }

    /**
     * disposes WebGL resources that are connected to this geometry
     */
    dispose()
    {
        this.disposeRunner.run(this, false);
    }

    /**
     * Destroys the geometry.
     */
    destroy()
    {
        this.dispose();

        this.buffers = null;
        this.indexBuffer.destroy();

        this.attributes = null;
    }

    /**
     * returns a clone of the geometry
     *
     * @returns {PIXI.Geometry} a new clone of this geometry
     */
    clone()
    {
        const geometry = new Geometry();

        for (let i = 0; i < this.buffers.length; i++)
        {
            geometry.buffers[i] = new Buffer(this.buffers[i].data.slice());
        }

        for (const i in this.attributes)
        {
            const attrib = this.attributes[i];

            geometry.attributes[i] = new Attribute(
                attrib.buffer,
                attrib.size,
                attrib.normalized,
                attrib.type,
                attrib.stride,
                attrib.start,
                attrib.instance
            );
        }

        if (this.indexBuffer)
        {
            geometry.indexBuffer = geometry.buffers[this.buffers.indexOf(this.indexBuffer)];
            geometry.indexBuffer.index = true;
        }

        return geometry;
    }

    /**
     * merges an array of geometries into a new single one
     * geometry attribute styles must match for this operation to work
     *
     * @param {PIXI.Geometry[]} geometries array of geometries to merge
     * @returns {PIXI.Geometry} shiny new geometry!
     */
    static merge(geometries)
    {
        // todo add a geometry check!
        // also a size check.. cant be too big!]

        const geometryOut = new Geometry();

        const arrays = [];
        const sizes = [];
        const offsets = [];

        let geometry;

        // pass one.. get sizes..
        for (let i = 0; i < geometries.length; i++)
        {
            geometry = geometries[i];

            for (let j = 0; j < geometry.buffers.length; j++)
            {
                sizes[j] = sizes[j] || 0;
                sizes[j] += geometry.buffers[j].data.length;
                offsets[j] = 0;
            }
        }

        // build the correct size arrays..
        for (let i = 0; i < geometry.buffers.length; i++)
        {
            // TODO types!
            arrays[i] = new map[getBufferType(geometry.buffers[i].data)](sizes[i]);
            geometryOut.buffers[i] = new Buffer(arrays[i]);
        }

        // pass to set data..
        for (let i = 0; i < geometries.length; i++)
        {
            geometry = geometries[i];

            for (let j = 0; j < geometry.buffers.length; j++)
            {
                arrays[j].set(geometry.buffers[j].data, offsets[j]);
                offsets[j] += geometry.buffers[j].data.length;
            }
        }

        geometryOut.attributes = geometry.attributes;

        if (geometry.indexBuffer)
        {
            geometryOut.indexBuffer = geometryOut.buffers[geometry.buffers.indexOf(geometry.indexBuffer)];
            geometryOut.indexBuffer.index = true;

            let offset = 0;
            let stride = 0;
            let offset2 = 0;
            let bufferIndexToCount = 0;

            // get a buffer
            for (let i = 0; i < geometry.buffers.length; i++)
            {
                if (geometry.buffers[i] !== geometry.indexBuffer)
                {
                    bufferIndexToCount = i;
                    break;
                }
            }

            // figure out the stride of one buffer..
            for (const i in geometry.attributes)
            {
                const attribute = geometry.attributes[i];

                if ((attribute.buffer | 0) === bufferIndexToCount)
                {
                    stride += ((attribute.size * byteSizeMap[attribute.type]) / 4);
                }
            }

            // time to off set all indexes..
            for (let i = 0; i < geometries.length; i++)
            {
                const indexBufferData = geometries[i].indexBuffer.data;

                for (let j = 0; j < indexBufferData.length; j++)
                {
                    geometryOut.indexBuffer.data[j + offset2] += offset;
                }

                offset += geometry.buffers[bufferIndexToCount].data.length / (stride);
                offset2 += indexBufferData.length;
            }
        }

        return geometryOut;
    }
}
<|endoftext|>


<commit_before>const parseCommandLine = require('../cli/parse-command-line')
const normalizeParams = require('../cli/normalize-params')
const autobind = require('autobind-decorator')
const {
  clone,
  merge,
  when,
  equals,
  always,
  join,
  split,
  trim,
  pipe,
  replace,
  find,
  append,
  forEach,
  isNil,
  dissoc,
  map,
  is
} = require('ramda')
const { findByProp, startsWith, isNilOrEmpty } = require('ramdasauce')
const { isBlank } = require('../utils/string-utils')
const { subdirectories, isDirectory } = require('../utils/filesystem-utils')
const RunContext = require('./run-context')
const loadPluginFromDirectory = require('../loaders/toml-plugin-loader')

// core extensions
const addTemplateExtension = require('../core-extensions/template-extension')
const addPrintExtension = require('../core-extensions/print-extension')
const addFilesystemExtension = require(
  '../core-extensions/filesystem-extension'
)
const addSemverExtension = require('../core-extensions/semver-extension')
const addSystemExtension = require('../core-extensions/system-extension')
const addPromptExtension = require('../core-extensions/prompt-extension')
const addHttpExtension = require('../core-extensions/http-extension')
const addStringsExtension = require('../core-extensions/strings-extension')
const addPatchingExtension = require('../core-extensions/patching-extension')

const COMMAND_DELIMITER = ' '

/**
 * Strips the command out of the args returns an array of the rest.
 *
 * @param {string} args        The full arguments including command.
 * @param {string} commandName The name of the command to strip.
 */
const extractSubArguments = (args, commandName) =>
  pipe(
    replace(commandName, ''),
    trim,
    split(COMMAND_DELIMITER),
    when(equals(['']), always([]))
  )(args)

/**
* Runs a command.
*
* @param  {{}} options Additional options use to execute a command.
*                      If nothing is passed, it will read from the command line.
* @return {RunContext} The RunContext object indicating what happened.
*/
async function run (options) {
  // prepare the run context
  const context = new RunContext()

  // attach the runtime
  context.runtime = this

  // prepare the context parameters
  const optionsIsArray = is(Array, options)

  // set initial context parameters, these will change further along
  // once we understand better what the user is looking for.
  if (isNilOrEmpty(options) || optionsIsArray) {
    // grab the params from the command line
    const { first, rest, options: parsedOptions } = parseCommandLine(
      options || process.argv
    )
    context.parameters.pluginName = first
    context.parameters.rawCommand = rest
    context.parameters.options = parsedOptions || {}
  } else {
    // grab the options that were passed in
    context.parameters.options = options.options || {}

    // did the user fill out the rawCommand but not the plug name?
    if (isBlank(options.pluginName) && !isBlank(options.rawCommand)) {
      // let's upgrade
      const upgrade = parseCommandLine(['', '', options.rawCommand])
      context.parameters.pluginName = upgrade.first
      context.parameters.rawCommand = upgrade.rest
    } else {
      context.parameters.pluginName = options.pluginName
      context.parameters.rawCommand = options.rawCommand
    }
  }

  // first check for a command inside the default
  const defaultPluginCommand = this.findCommand(
    this.defaultPlugin,
    context.parameters.pluginName || this.defaultCommand
  )
  if (defaultPluginCommand) {
    // we found a command in the default plugin
    context.plugin = this.defaultPlugin
    context.command = defaultPluginCommand
    context.parameters.pluginName = context.plugin.name
    context.parameters.rawCommand = `${context.plugin.name} ${context.parameters.rawCommand}`
  } else {
    // there wasn't a default command
    context.plugin = this.findPlugin(context.parameters.pluginName)
    context.command = this.findCommand(
      context.plugin,
      context.parameters.rawCommand
    )
  }

  // jet if we have no plugin
  if (isNil(context.plugin)) return context

  // setup the config
  context.config = clone(this.config)
  context.config[context.plugin.name] = merge(
    context.plugin.defaults,
    (this.defaults && this.defaults[context.plugin.name]) || {}
  )

  // jet if we have no command
  if (isNil(context.command)) return context

  // setup the rest of the parameters
  const subArgs = extractSubArguments(
    context.parameters.rawCommand,
    trim(context.command.name)
  )
  context.parameters.array = subArgs
  context.parameters.first = subArgs[0]
  context.parameters.second = subArgs[1]
  context.parameters.third = subArgs[2]
  context.parameters.string = join(COMMAND_DELIMITER, subArgs)

  // normalized params (experimental)
  const normalizedParams = normalizeParams(
    context.plugin.name,
    context.command.name,
    process.argv
  )

  context.params = {
    ...normalizedParams,
    plugin: context.plugin.name,
    command: context.command.name,
  }

  // kick it off
  if (context.command.run) {
    // attach extensions
    forEach(
      extension => {
        const extend = extension.setup(
          context.plugin,
          context.command,
          context
        )
        context[extension.name] = extend
      },
      this.extensions
    )

    context.result = await context.command.run(context)
  }

  return context
}

/**
 * Loads plugins an action through the gauntlet.
 */
class Runtime {
  /**
   * Create and initialize an empty Runtime.
   */
  constructor (brand) {
    this.brand = brand
    this.run = run // awkward because node.js doesn't support async-based class functions yet.
    this.plugins = []
    this.extensions = []
    this.defaults = {}
    this.defaultPlugin = null
    this.events = {}
    this.config = {}

    this.addCoreExtensions()
  }

  /**
   * Adds the core extensions.  These provide the basic features
   * available in gluegun, but follow the exact same method
   * for extending the core as 3rd party extensions do.
   */
  addCoreExtensions () {
    this.addExtension('strings', addStringsExtension)
    this.addExtension('print', addPrintExtension)
    this.addExtension('template', addTemplateExtension)
    this.addExtension('filesystem', addFilesystemExtension)
    this.addExtension('semver', addSemverExtension)
    this.addExtension('system', addSystemExtension)
    this.addExtension('http', addHttpExtension)
    this.addExtension('prompt', addPromptExtension)
    this.addExtension('patching', addPatchingExtension)
  }

  /**
   * Adds an extension so it is available when commands run. They live
   * as the given name on the context object passed to commands. The
   * 2nd parameter is a function that, when called, creates that object.
   *
   * @param {string} name   The context property name.
   * @param {object} setup  The setup function.
   */
  addExtension (name, setup) {
    this.extensions.push({ name, setup })
    return this
  }

  /**
   * Loads a plugin from a directory.
   *
   * @param  {string} directory The directory to load from.
   * @param  {Object} options   Additional loading options.
   * @return {Plugin}           A plugin.
   */
  load (directory, options = {}) {
    const {
      brand,
      extensionNameToken,
      commandNameToken,
      commandDescriptionToken,
      commandHiddenToken,
      commandAliasToken
    } = this

    const plugin = loadPluginFromDirectory(directory, {
      extensionNameToken,
      commandNameToken,
      commandHiddenToken,
      commandAliasToken,
      commandDescriptionToken,
      brand,
      hidden: options['hidden'],
      name: options['name'],
      commandFilePattern: options['commandFilePattern'],
      extensionFilePattern: options['extensionFilePattern']
    })

    this.plugins = append(plugin, this.plugins)
    forEach(
      extension => this.addExtension(extension.name, extension.setup),
      plugin.extensions
    )
    return plugin
  }

  /**
   * Loads a plugin from a directory and sets it as the default.
   *
   * @param  {string} directory The directory to load from.
   * @param  {Object} options   Additional loading options.
   * @return {Plugin}           A plugin.
   */
  loadDefault (directory, options = {}) {
    const plugin = this.load(directory, options)
    this.defaultPlugin = plugin
    return plugin
  }

  /**
   * Loads a bunch of plugins from the immediate sub-directories of a directory.
   *
   * @param {string} directory The directory to grab from.
   * @param {Object} options   Addition loading options.
   * @return {Plugin[]}        A bunch of plugins
   */
  loadAll (directory, options = {}) {
    if (isBlank(directory) || !isDirectory(directory)) return []
    return pipe(
      dir => subdirectories(dir, false, options['matching'], true),
      map(dir => this.load(dir, dissoc('matching', options)))
    )(directory)
  }

  /**
   * The list of commands registered.
   *
   * @return {[]} A list of [{plugin, command}]
   */
  listCommands () {
    const commands = []
    const eachPlugin = plugin => {
      const eachCommand = command => {
        commands.push({ plugin, command })
      }
      forEach(eachCommand, plugin.commands)
    }
    forEach(eachPlugin, this.plugins)

    return commands
  }

  /**
   * Find the plugin for this name.
   *
   * @param {string} name The name to search through.
   * @returns {*}         A Plugin otherwise null.
   */
  findPlugin (name) {
    return findByProp('name', name || '', this.plugins)
  }

  /**
   * Find the command for this pluginName & command.
   *
   * @param {Plugin} plugin      The plugin in which the command lives.
   * @param {string} rawCommand  The command arguments to parse.
   * @returns {*}                A Command otherwise null.
   */
  findCommand (plugin, rawCommand) {
    if (isNil(plugin) || isBlank(rawCommand)) return null
    if (isNilOrEmpty(plugin.commands)) return null

    return find(
      command =>
        startsWith(command.name, rawCommand) || rawCommand === command.alias,
      plugin.commands
    )
  }
}

module.exports = autobind(Runtime)
<commit_msg>Uses Object.assign rather than spread for old smelly node versions<commit_after>const parseCommandLine = require('../cli/parse-command-line')
const normalizeParams = require('../cli/normalize-params')
const autobind = require('autobind-decorator')
const {
  clone,
  merge,
  when,
  equals,
  always,
  join,
  split,
  trim,
  pipe,
  replace,
  find,
  append,
  forEach,
  isNil,
  dissoc,
  map,
  is
} = require('ramda')
const { findByProp, startsWith, isNilOrEmpty } = require('ramdasauce')
const { isBlank } = require('../utils/string-utils')
const { subdirectories, isDirectory } = require('../utils/filesystem-utils')
const RunContext = require('./run-context')
const loadPluginFromDirectory = require('../loaders/toml-plugin-loader')

// core extensions
const addTemplateExtension = require('../core-extensions/template-extension')
const addPrintExtension = require('../core-extensions/print-extension')
const addFilesystemExtension = require(
  '../core-extensions/filesystem-extension'
)
const addSemverExtension = require('../core-extensions/semver-extension')
const addSystemExtension = require('../core-extensions/system-extension')
const addPromptExtension = require('../core-extensions/prompt-extension')
const addHttpExtension = require('../core-extensions/http-extension')
const addStringsExtension = require('../core-extensions/strings-extension')
const addPatchingExtension = require('../core-extensions/patching-extension')

const COMMAND_DELIMITER = ' '

/**
 * Strips the command out of the args returns an array of the rest.
 *
 * @param {string} args        The full arguments including command.
 * @param {string} commandName The name of the command to strip.
 */
const extractSubArguments = (args, commandName) =>
  pipe(
    replace(commandName, ''),
    trim,
    split(COMMAND_DELIMITER),
    when(equals(['']), always([]))
  )(args)

/**
* Runs a command.
*
* @param  {{}} options Additional options use to execute a command.
*                      If nothing is passed, it will read from the command line.
* @return {RunContext} The RunContext object indicating what happened.
*/
async function run (options) {
  // prepare the run context
  const context = new RunContext()

  // attach the runtime
  context.runtime = this

  // prepare the context parameters
  const optionsIsArray = is(Array, options)

  // set initial context parameters, these will change further along
  // once we understand better what the user is looking for.
  if (isNilOrEmpty(options) || optionsIsArray) {
    // grab the params from the command line
    const { first, rest, options: parsedOptions } = parseCommandLine(
      options || process.argv
    )
    context.parameters.pluginName = first
    context.parameters.rawCommand = rest
    context.parameters.options = parsedOptions || {}
  } else {
    // grab the options that were passed in
    context.parameters.options = options.options || {}

    // did the user fill out the rawCommand but not the plug name?
    if (isBlank(options.pluginName) && !isBlank(options.rawCommand)) {
      // let's upgrade
      const upgrade = parseCommandLine(['', '', options.rawCommand])
      context.parameters.pluginName = upgrade.first
      context.parameters.rawCommand = upgrade.rest
    } else {
      context.parameters.pluginName = options.pluginName
      context.parameters.rawCommand = options.rawCommand
    }
  }

  // first check for a command inside the default
  const defaultPluginCommand = this.findCommand(
    this.defaultPlugin,
    context.parameters.pluginName || this.defaultCommand
  )
  if (defaultPluginCommand) {
    // we found a command in the default plugin
    context.plugin = this.defaultPlugin
    context.command = defaultPluginCommand
    context.parameters.pluginName = context.plugin.name
    context.parameters.rawCommand = `${context.plugin.name} ${context.parameters.rawCommand}`
  } else {
    // there wasn't a default command
    context.plugin = this.findPlugin(context.parameters.pluginName)
    context.command = this.findCommand(
      context.plugin,
      context.parameters.rawCommand
    )
  }

  // jet if we have no plugin
  if (isNil(context.plugin)) return context

  // setup the config
  context.config = clone(this.config)
  context.config[context.plugin.name] = merge(
    context.plugin.defaults,
    (this.defaults && this.defaults[context.plugin.name]) || {}
  )

  // jet if we have no command
  if (isNil(context.command)) return context

  // setup the rest of the parameters
  const subArgs = extractSubArguments(
    context.parameters.rawCommand,
    trim(context.command.name)
  )
  context.parameters.array = subArgs
  context.parameters.first = subArgs[0]
  context.parameters.second = subArgs[1]
  context.parameters.third = subArgs[2]
  context.parameters.string = join(COMMAND_DELIMITER, subArgs)

  // normalized params (experimental)
  const normalizedParams = normalizeParams(
    context.plugin.name,
    context.command.name,
    process.argv
  )

  context.params = Object.assign(normalizedParams, {
    plugin: context.plugin.name,
    command: context.command.name,
  })

  // kick it off
  if (context.command.run) {
    // attach extensions
    forEach(
      extension => {
        const extend = extension.setup(
          context.plugin,
          context.command,
          context
        )
        context[extension.name] = extend
      },
      this.extensions
    )

    context.result = await context.command.run(context)
  }

  return context
}

/**
 * Loads plugins an action through the gauntlet.
 */
class Runtime {
  /**
   * Create and initialize an empty Runtime.
   */
  constructor (brand) {
    this.brand = brand
    this.run = run // awkward because node.js doesn't support async-based class functions yet.
    this.plugins = []
    this.extensions = []
    this.defaults = {}
    this.defaultPlugin = null
    this.events = {}
    this.config = {}

    this.addCoreExtensions()
  }

  /**
   * Adds the core extensions.  These provide the basic features
   * available in gluegun, but follow the exact same method
   * for extending the core as 3rd party extensions do.
   */
  addCoreExtensions () {
    this.addExtension('strings', addStringsExtension)
    this.addExtension('print', addPrintExtension)
    this.addExtension('template', addTemplateExtension)
    this.addExtension('filesystem', addFilesystemExtension)
    this.addExtension('semver', addSemverExtension)
    this.addExtension('system', addSystemExtension)
    this.addExtension('http', addHttpExtension)
    this.addExtension('prompt', addPromptExtension)
    this.addExtension('patching', addPatchingExtension)
  }

  /**
   * Adds an extension so it is available when commands run. They live
   * as the given name on the context object passed to commands. The
   * 2nd parameter is a function that, when called, creates that object.
   *
   * @param {string} name   The context property name.
   * @param {object} setup  The setup function.
   */
  addExtension (name, setup) {
    this.extensions.push({ name, setup })
    return this
  }

  /**
   * Loads a plugin from a directory.
   *
   * @param  {string} directory The directory to load from.
   * @param  {Object} options   Additional loading options.
   * @return {Plugin}           A plugin.
   */
  load (directory, options = {}) {
    const {
      brand,
      extensionNameToken,
      commandNameToken,
      commandDescriptionToken,
      commandHiddenToken,
      commandAliasToken
    } = this

    const plugin = loadPluginFromDirectory(directory, {
      extensionNameToken,
      commandNameToken,
      commandHiddenToken,
      commandAliasToken,
      commandDescriptionToken,
      brand,
      hidden: options['hidden'],
      name: options['name'],
      commandFilePattern: options['commandFilePattern'],
      extensionFilePattern: options['extensionFilePattern']
    })

    this.plugins = append(plugin, this.plugins)
    forEach(
      extension => this.addExtension(extension.name, extension.setup),
      plugin.extensions
    )
    return plugin
  }

  /**
   * Loads a plugin from a directory and sets it as the default.
   *
   * @param  {string} directory The directory to load from.
   * @param  {Object} options   Additional loading options.
   * @return {Plugin}           A plugin.
   */
  loadDefault (directory, options = {}) {
    const plugin = this.load(directory, options)
    this.defaultPlugin = plugin
    return plugin
  }

  /**
   * Loads a bunch of plugins from the immediate sub-directories of a directory.
   *
   * @param {string} directory The directory to grab from.
   * @param {Object} options   Addition loading options.
   * @return {Plugin[]}        A bunch of plugins
   */
  loadAll (directory, options = {}) {
    if (isBlank(directory) || !isDirectory(directory)) return []
    return pipe(
      dir => subdirectories(dir, false, options['matching'], true),
      map(dir => this.load(dir, dissoc('matching', options)))
    )(directory)
  }

  /**
   * The list of commands registered.
   *
   * @return {[]} A list of [{plugin, command}]
   */
  listCommands () {
    const commands = []
    const eachPlugin = plugin => {
      const eachCommand = command => {
        commands.push({ plugin, command })
      }
      forEach(eachCommand, plugin.commands)
    }
    forEach(eachPlugin, this.plugins)

    return commands
  }

  /**
   * Find the plugin for this name.
   *
   * @param {string} name The name to search through.
   * @returns {*}         A Plugin otherwise null.
   */
  findPlugin (name) {
    return findByProp('name', name || '', this.plugins)
  }

  /**
   * Find the command for this pluginName & command.
   *
   * @param {Plugin} plugin      The plugin in which the command lives.
   * @param {string} rawCommand  The command arguments to parse.
   * @returns {*}                A Command otherwise null.
   */
  findCommand (plugin, rawCommand) {
    if (isNil(plugin) || isBlank(rawCommand)) return null
    if (isNilOrEmpty(plugin.commands)) return null

    return find(
      command =>
        startsWith(command.name, rawCommand) || rawCommand === command.alias,
      plugin.commands
    )
  }
}

module.exports = autobind(Runtime)
<|endoftext|>


<commit_before>module.exports = window.fetch || (window.fetch = require('unfetch').default || require('unfetch'));
<commit_msg>window -> self<commit_after>module.exports = self.fetch || (self.fetch = require('unfetch').default || require('unfetch'));
<|endoftext|>


<commit_before>import UAParser from 'ua-parser-js';

const uaParser = new UAParser();
const ua = uaParser.getResult();

export const hasModulesSupport = () => {
    const script = document.createElement('script');
    return 'noModule' in script;
};

export const isFileSaverSupported = () => 'download' in document.createElement('a') || navigator.msSaveOrOpenBlob;

export const textToClipboard = (text) => {
    const dummy = document.createElement('textarea');

    document.body.appendChild(dummy);
    dummy.value = text;
    dummy.select();
    document.execCommand('copy');
    document.body.removeChild(dummy);
};

export const getOS = () => {
    const { name = 'other', version = '' } = ua.os;
    return { name, version };
};

export const isSafari = () => ['Safari', 'Mobile Safari'].includes(ua.browser.name);
export const isSafariMobile = () => ua.browser.name === 'Mobile Safari';
export const isIE11 = () => ua.browser.name === 'IE' && ua.browser.major === '11';
export const isEdge = () => ua.browser.name === 'Edge';
export const isFirefox = () => ua.browser.name === 'Firefox';
export const isChrome = () => ua.browser.name === 'Chrome';
export const isMac = () => ua.os.name === 'Mac OS';
export const hasTouch = 'ontouchstart' in document.documentElement;
export const hasCookie = () => navigator.cookieEnabled;
export const getBrowser = () => ua.browser;
export const getDevice = () => ua.device;
export const isMobile = () => {
    const { type } = getDevice();
    return type === 'mobile';
};

export const doNotTrack = () => {
    return (
        navigator.doNotTrack === '1' ||
        navigator.doNotTrack === 'yes' ||
        navigator.msDoNotTrack === '1' ||
        window.doNotTrack === '1'
    );
};

export const parseURL = (url = '') => {
    const parser = document.createElement('a');
    const searchObject = {};
    // Let the browser do the work
    parser.href = url;
    // Convert query string to object
    const queries = parser.search.replace(/^\?/, '').split('&');

    for (let i = 0; i < queries.length; i++) {
        const [key, value] = queries[i].split('=');
        searchObject[key] = value;
    }

    return {
        protocol: parser.protocol,
        host: parser.host,
        hostname: parser.hostname,
        port: parser.port,
        pathname: parser.pathname,
        search: parser.search,
        searchObject,
        hash: parser.hash
    };
};
<commit_msg>Add helper to detect PDF support<commit_after>import UAParser from 'ua-parser-js';

const uaParser = new UAParser();
const ua = uaParser.getResult();

export const hasModulesSupport = () => {
    const script = document.createElement('script');
    return 'noModule' in script;
};

export const isFileSaverSupported = () => 'download' in document.createElement('a') || navigator.msSaveOrOpenBlob;

export const textToClipboard = (text) => {
    const dummy = document.createElement('textarea');

    document.body.appendChild(dummy);
    dummy.value = text;
    dummy.select();
    document.execCommand('copy');
    document.body.removeChild(dummy);
};

export const getOS = () => {
    const { name = 'other', version = '' } = ua.os;
    return { name, version };
};

export const isSafari = () => ['Safari', 'Mobile Safari'].includes(ua.browser.name);
export const isSafariMobile = () => ua.browser.name === 'Mobile Safari';
export const isIE11 = () => ua.browser.name === 'IE' && ua.browser.major === '11';
export const isEdge = () => ua.browser.name === 'Edge';
export const isFirefox = () => ua.browser.name === 'Firefox';
export const isChrome = () => ua.browser.name === 'Chrome';
export const isMac = () => ua.os.name === 'Mac OS';
export const hasTouch = 'ontouchstart' in document.documentElement;
export const hasCookie = () => navigator.cookieEnabled;
export const getBrowser = () => ua.browser;
export const getDevice = () => ua.device;
export const isMobile = () => {
    const { type } = getDevice();
    return type === 'mobile';
};

export const doNotTrack = () => {
    return (
        navigator.doNotTrack === '1' ||
        navigator.doNotTrack === 'yes' ||
        navigator.msDoNotTrack === '1' ||
        window.doNotTrack === '1'
    );
};

export const parseURL = (url = '') => {
    const parser = document.createElement('a');
    const searchObject = {};
    // Let the browser do the work
    parser.href = url;
    // Convert query string to object
    const queries = parser.search.replace(/^\?/, '').split('&');

    for (let i = 0; i < queries.length; i++) {
        const [key, value] = queries[i].split('=');
        searchObject[key] = value;
    }

    return {
        protocol: parser.protocol,
        host: parser.host,
        hostname: parser.hostname,
        port: parser.port,
        pathname: parser.pathname,
        search: parser.search,
        searchObject,
        hash: parser.hash
    };
};

export const getActiveXObject = (name) => {
    try {
        // eslint-disable-next-line no-undef
        return new ActiveXObject(name);
    } catch (error) {
        // eslint-disable-next-line no-console
        console.error(error);
    }
};

export const isIos = () => /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
export const hasAcrobatInstalled = () => getActiveXObject('AcroPDF.PDF') || getActiveXObject('PDF.PdfCtrl');
export const hasPDFSupport = () => navigator.mimeTypes['application/pdf'] || hasAcrobatInstalled() || isIos();
<|endoftext|>


<commit_before>var Command = require("../lib/command");
var commands = require("../lib/commands");
var commander = new Command(commands);
var assert = require("assert");

describe("Commander", function() {
  before("assert preconditions", function() {
    // These commands are expected to exist in tests.
    assert.notEqual(commands.migrate, null);
    assert.notEqual(commands.compile, null);
    assert.notEqual(commands.console, null);
  });

  it("will infer commands based on initial letters entered", function() {
    var actualCommand = commander.getCommand("m").command;
    assert.equal(actualCommand, commands.migrate);
  });

  it("will infer commands based on initial letters entered, even when typos exist later", function() {
    var actualCommand = commander.getCommand("complie").command;
    assert.equal(actualCommand, commands.compile);
  });

  it("will NOT infer a command if not given enough information", function() {
    // Note: "co" matches "console" and "compile"
    var actualCommand = commander.getCommand("co");
    assert.equal(actualCommand, null);
  });

  it("will infer commands based on initial letters entered, given matches for shorter substrings", function() {
    // Note: "co" matches "console" and "compile"
    var actualCommand = commander.getCommand("com").command;
    assert.equal(actualCommand, commands.compile);
  });

  it("will ignore inferring if full command is specified", function() {
    var actualCommand = commander.getCommand("console").command;
    assert.equal(actualCommand, commands.console);
  });

  // it("will stop and display error for unsupported flags in commands", function() {
  //   var actualCommand = commander.getCommand("mig").command;
  //   assert.equal(actualCommand, commands.migrate);
  //   commander.run(
  //     [
  //       "migrate",
  //       "--network",
  //       "localhost",
  //       "--unsupportedflag",
  //       "invalidoption"
  //     ],
  //     { noAliases: true },
  //     function(err) {
  //       assert.equal(
  //         err.message.split(":")[0],
  //         "Unsupported (Undocumented) command line option"
  //       );
  //     }
  //   );
  // });
});
<commit_msg>add unit test with config<commit_after>var Command = require("../lib/command");
var commands = require("../lib/commands");
var commander = new Command(commands);
var assert = require("assert");

describe("Commander", function() {
  before("assert preconditions", function() {
    // These commands are expected to exist in tests.
    assert.notEqual(commands.migrate, null);
    assert.notEqual(commands.compile, null);
    assert.notEqual(commands.console, null);
  });

  it("will infer commands based on initial letters entered", function() {
    var actualCommand = commander.getCommand("m").command;
    assert.equal(actualCommand, commands.migrate);
  });

  it("will infer commands based on initial letters entered, even when typos exist later", function() {
    var actualCommand = commander.getCommand("complie").command;
    assert.equal(actualCommand, commands.compile);
  });

  it("will NOT infer a command if not given enough information", function() {
    // Note: "co" matches "console" and "compile"
    var actualCommand = commander.getCommand("co");
    assert.equal(actualCommand, null);
  });

  it("will infer commands based on initial letters entered, given matches for shorter substrings", function() {
    // Note: "co" matches "console" and "compile"
    var actualCommand = commander.getCommand("com").command;
    assert.equal(actualCommand, commands.compile);
  });

  it("will ignore inferring if full command is specified", function() {
    var actualCommand = commander.getCommand("console").command;
    assert.equal(actualCommand, commands.console);
  });

  // it("will stop and display error for unsupported flags in commands", function() {
  //   var actualCommand = commander.getCommand("mig").command;
  //   assert.equal(actualCommand, commands.migrate);
  //   commander.run(
  //     [
  //       "migrate",
  //       "--network",
  //       "localhost",
  //       "--unsupportedflag",
  //       "invalidoption"
  //     ],
  //     { noAliases: true },
  //     function(err) {
  //       assert.equal(
  //         err.message.split(":")[0],
  //         "Unsupported (Undocumented) command line option"
  //       );
  //     }
  //   );
  // });

  it("will stop and display error for unsupported flags in commands", function() {
    var actualCommand = commander.getCommand("config").command;
    assert.equal(actualCommand, commands.config);
    commander.run(
      ["config", "--enable-analytics", "--unsupportedflag", "invalidoption"],
      { noAliases: true },
      function(err) {
        assert.equal(
          err.message.split(":")[0],
          "Unsupported (Undocumented) command line option"
        );
      }
    );
  });
});
<|endoftext|>


<commit_before>/*global angular, jQuery */

(function ($) {
    'use strict';

    function bbReorder($filter) {
        var bbAutonumericConfig = {
            mDec: 0
        };

        function applyNumberFormatting(index) {
            return $filter('bbAutonumeric')(index, bbAutonumericConfig);
        }

        function setSiblingNumbers(incrementStep, siblingTraversalFunc, displayIndex, startSibling) {
            var curSibling;

            curSibling = siblingTraversalFunc.call(startSibling).not('.bb-reorder-list-sorting-item');
            while (curSibling.length > 0) {
                displayIndex = displayIndex + incrementStep;
                curSibling.find('.bb-reorder-list-sorting-number').text(applyNumberFormatting(displayIndex));
                curSibling = siblingTraversalFunc.call(curSibling).not('.bb-reorder-list-sorting-item');
            }
        }

        function link(scope, el, attrs, vm) {
            var sortableOptions, // jQuery sortable widget options
                originalSortItemIndex, // the original index of th item being sorted before sorting starts
                currentSortItemIndex, // the index where the sorting item is currently being placed
                finalIndex = -1, // the final index of the element being sorting after sorting has ended
                currentRepeaterItems, // the set of items from ng-repeat before sorting starts
                actionBeingDone = false; // are we currently sorting or sending an item to the top

            vm.sorting = false;

            // sends a item to the top of the list with a rising animation
            vm.pushToTop = function (item) {
                var toTheTopEl,
                    index,
                    toTheTopElOffset,
                    animateCloneEl;

                // if we are doing something already just ignore
                if (actionBeingDone) {
                    return;
                }

                index = vm.bbReorderItems.indexOf(item);

                actionBeingDone = true;
                el.sortable("disable"); // don't allow sorting during animation

                toTheTopEl = $(el.children()[index]);
                toTheTopElOffset = toTheTopEl.position();

                // create a clone of the element being sent to the top so we can animate it
                animateCloneEl = toTheTopEl.clone();
                animateCloneEl.addClass('bb-reorder-animate-element');
                animateCloneEl.css({top: toTheTopElOffset.top + "px", left: toTheTopElOffset.left + "px", width: toTheTopEl.outerWidth() + "px"});

                el.append(animateCloneEl);

                toTheTopEl.addClass('bb-reorder-list-row-placeholder');

                // animate to the top of the directive
                $(animateCloneEl).fadeOut({duration: 500, queue: false}).slideUp({duration: 500, queue: false, always: function () {
                   toTheTopEl.children().css('visibility', '');
                   toTheTopEl.removeClass('bb-reorder-list-row-placeholder');

                   animateCloneEl.remove();
                   el.sortable("enable");

                   actionBeingDone = false;

                   scope.$apply(function () {
                       // perform the swap moving the item to the 0 index in the list
                       vm.bbReorderItems.splice(
                         0, 0,
                         vm.bbReorderItems.splice(index, 1)[0]);
                   });
               }});
            };

            //Setup jQuery sortable options for the items being sorted
            sortableOptions = {
                placeholder: 'bb-reorder-list-row-placeholder', // class to put on placeholder element
                axis: 'y', // constrain movement to the Y axis,
                start: function (e, ui) {
                    scope.$apply(function () {
                        vm.sorting = true;
                    });

                    // need to keep track of the how the items were placed in the DOM since
                    // the sortable is going to mess them up which breaks ng-repeat
                    currentRepeaterItems = el.contents().not(ui.placeholder);

                    ui.item.addClass('bb-reorder-list-sorting-item');

                    actionBeingDone = true;
                    originalSortItemIndex = el.children().not('.bb-reorder-list-sorting-item').index(ui.placeholder);
                    currentSortItemIndex = originalSortItemIndex;

                    // need to set the height of the placeholder since we need to account for the padding on the row items
                    ui.placeholder.height(ui.item.outerHeight());

                    // set the current index of all rows for display purposes
                    $.each(el.children('.bb-reorder-list-row'), function (i, item) {
                        $(item).find('.bb-reorder-list-sorting-number').text(applyNumberFormatting(i + 1));
                    });
                },
                stop: function (e, ui) {
                    // replace the repeater elements and comments so ng-repeat does not break
                    currentRepeaterItems.appendTo(el);

                    ui.item.removeClass('bb-reorder-list-sorting-item');

                    if (finalIndex >= 0 && finalIndex !== originalSortItemIndex) {
                        scope.$apply(function () {
                            // perform the swap that the user just performed
                            vm.bbReorderItems.splice(
                              finalIndex, 0,
                              vm.bbReorderItems.splice(originalSortItemIndex, 1)[0]);
                        });
                    }

                    scope.$apply(function () {
                        vm.sorting = false;
                    });

                    actionBeingDone = false;
                    originalSortItemIndex = null;
                    currentSortItemIndex = null;
                    finalIndex = -1;
                    currentRepeaterItems = null;
                },
                update: function (e, ui) {
                    finalIndex = ui.item.index();
                    // stop the sortable from moving the element as we want the ng-repeat directive to do the actual reorder
                    el.sortable('cancel');
                },
                change: function (e, ui) {
                    var displayIndex,
                        newIndex;
                    // Since the element being sorted is positioned absolute it remains in the
                    // same position so we can't use its index. Instead use the placeholder since
                    // that will be in the right position in the list.
                    newIndex = el.children().not('.bb-reorder-list-sorting-item').index(ui.placeholder);

                    if (newIndex === currentSortItemIndex) {
                        return;
                    }

                    displayIndex = newIndex + 1;

                    ui.item.find('.bb-reorder-list-sorting-number').text(applyNumberFormatting(displayIndex));

                    // when we are sorting, change the position numbers on the rows
                    if (newIndex > currentSortItemIndex) {
                        // set the text of all previous siblings to account for change
                        setSiblingNumbers(-1, $.fn.prev, displayIndex, ui.placeholder);
                    } else {
                        // set the text of all next siblings to account for change
                        setSiblingNumbers(1, $.fn.next, displayIndex, ui.placeholder);
                    }

                    currentSortItemIndex = newIndex;
                }
            };

            el.sortable(sortableOptions);
        }

        return {
            replace: true,
            restrict: 'E',
            scope: {},
            bindToController: {
                bbReorderItems: '='
            },
            controller: angular.noop,
            controllerAs: 'bbReorder',
            templateUrl: 'sky/templates/reorder/reorder.directive.html',
            link: link
        };
    }

    bbReorder.$inject = ['$filter'];

    angular.module('sky.reorder.directive', ['sky.resources', 'sky.autonumeric'])
        .directive('bbReorder', bbReorder);
}(jQuery));
<commit_msg>some more cleanup<commit_after>/*global angular, jQuery */

(function ($) {
    'use strict';

    function bbReorder($filter) {
        var bbAutonumericConfig = {
            mDec: 0 // no decimals
        };

        function applyNumberFormatting(index) {
            return $filter('bbAutonumeric')(index, bbAutonumericConfig);
        }

        function setPositionNumberText(item, index) {
            $(item).find('.bb-reorder-list-sorting-number').text(applyNumberFormatting(index));
        }

        function setPositionNumbers(incrementStep, siblingTraversalFunc, displayIndex, placeholder, item) {
            var curSibling;

            // set the position number of the item being sorted to account for how the user moved it
            setPositionNumberText(item, displayIndex);

            curSibling = siblingTraversalFunc.call(placeholder).not(item);
            while (curSibling.length > 0) {
                displayIndex = displayIndex + incrementStep;
                setPositionNumberText(curSibling, displayIndex);
                curSibling = siblingTraversalFunc.call(curSibling).not(item);
            }
        }

        function link(scope, el, attrs, vm) {
            var sortableOptions, // jQuery sortable widget options
                originalSortItemIndex, // the original index of th item being sorted before sorting starts
                currentSortItemIndex, // the index where the sorting item is currently being placed
                finalIndex = -1, // the final index of the element being sorting after sorting has ended
                currentRepeaterItems; // the set of items from ng-repeat before sorting starts

            vm.sorting = false;

            // sends a item to the top of the list with a rising animation
            vm.pushToTop = function (item) {
                var toTheTopEl,
                    index,
                    toTheTopElOffset,
                    animateCloneEl;

                // if the user is sorting and drops the sortable on the button don't perform the move to the top
                if (vm.sorting) {
                    return;
                }

                index = vm.bbReorderItems.indexOf(item);

                el.sortable("disable"); // don't allow sorting during animation

                toTheTopEl = $(el.children()[index]);
                toTheTopElOffset = toTheTopEl.position();

                // create a clone of the element being moved to the top so we can animate it without messing with the ng-repeat
                animateCloneEl = toTheTopEl.clone();
                animateCloneEl.addClass('bb-reorder-animate-element');
                animateCloneEl.css({top: toTheTopElOffset.top + "px", left: toTheTopElOffset.left + "px", width: toTheTopEl.outerWidth() + "px"});

                el.append(animateCloneEl);

                toTheTopEl.addClass('bb-reorder-list-row-placeholder');

                // animate that we are moving the item to the top of the list
                $(animateCloneEl).fadeOut({duration: 500, queue: false}).slideUp({duration: 500, queue: false, always: function () {
                   toTheTopEl.removeClass('bb-reorder-list-row-placeholder');

                   animateCloneEl.remove();
                   el.sortable("enable");

                   scope.$apply(function () {
                       // perform the swap moving the item to the top of the list
                       vm.bbReorderItems.splice(
                         0, 0,
                         vm.bbReorderItems.splice(index, 1)[0]);
                   });
               }});
            };

            //Setup jQuery sortable options for the items being sorted
            sortableOptions = {
                placeholder: 'bb-reorder-list-row-placeholder', // class to put on placeholder element
                axis: 'y', // constrain movement to the Y axis,
                start: function (e, ui) {
                    scope.$apply(function () {
                        vm.sorting = true;
                    });

                    // need to keep track of the how the items were placed in the DOM since
                    // the sortable is going to mess them up which breaks ng-repeat
                    currentRepeaterItems = el.contents().not(ui.placeholder);

                    ui.item.addClass('bb-reorder-list-sorting-item');

                    originalSortItemIndex = ui.item.index();
                    currentSortItemIndex = originalSortItemIndex;

                    // need to set the height of the placeholder since we need to account for the padding on the row items
                    ui.placeholder.height(ui.item.outerHeight());

                    // set the current index of all rows for display purposes
                    $.each(el.children('.bb-reorder-list-row'), function (i, item) {
                        setPositionNumberText(item, i + 1);
                    });
                },
                stop: function (e, ui) {
                    // replace the repeater elements and comments so ng-repeat does not break
                    currentRepeaterItems.appendTo(el);

                    ui.item.removeClass('bb-reorder-list-sorting-item');

                    if (finalIndex >= 0 && finalIndex !== originalSortItemIndex) {
                        scope.$apply(function () {
                            // perform the swap that the user just performed
                            vm.bbReorderItems.splice(
                              finalIndex, 0,
                              vm.bbReorderItems.splice(originalSortItemIndex, 1)[0]);
                        });
                    }

                    scope.$apply(function () {
                        vm.sorting = false;
                    });

                    originalSortItemIndex = null;
                    currentSortItemIndex = null;
                    finalIndex = -1;
                    currentRepeaterItems = null;
                },
                update: function (e, ui) {
                    // grab the final index of the item being sorted before we cancel
                    // the sort and its position gets reset.
                    finalIndex = ui.item.index();

                    // stop the sortable from moving the element as we want the ng-repeat directive to do the actual reorder
                    el.sortable('cancel');
                },
                change: function (e, ui) {
                    var displayIndex,
                        newIndex;

                    // Since the element being sorted is positioned absolute it remains in the
                    // same position so we can't use its index. Instead use the placeholder since
                    // that will be in the right position in the list.
                    newIndex = ui.item.siblings().index(ui.placeholder);

                    if (newIndex === currentSortItemIndex) {
                        return;
                    }

                    // the display position shown to the user should start at 1, not 0
                    displayIndex = newIndex + 1;

                    // when we are sorting, change the position numbers on the rows
                    if (newIndex > currentSortItemIndex) {
                        // set the text of all previous siblings to account for change
                        setPositionNumbers(-1, $.fn.prev, displayIndex, ui.placeholder, ui.item);
                    } else {
                        // set the text of all next siblings to account for change
                        setPositionNumbers(1, $.fn.next, displayIndex, ui.placeholder, ui.item);
                    }

                    currentSortItemIndex = newIndex;
                }
            };

            el.sortable(sortableOptions);
        }

        return {
            replace: true,
            restrict: 'E',
            scope: {},
            bindToController: {
                bbReorderItems: '='
            },
            controller: angular.noop,
            controllerAs: 'bbReorder',
            templateUrl: 'sky/templates/reorder/reorder.directive.html',
            link: link
        };
    }

    bbReorder.$inject = ['$filter'];

    angular.module('sky.reorder.directive', ['sky.resources', 'sky.autonumeric'])
        .directive('bbReorder', bbReorder);
}(jQuery));
<|endoftext|>


<commit_before>define(['jquery', 'underscore', 'backbone', 'views/jqm/elements/text'],
      function($, _, Backbone, TextElementView) {
  'use strict';

  var TextAreaElementView = TextElementView.extend({
    // TODO: fix bindings, the only thing that works is initial display :S
    render: function() {
      var self = this,
          $fieldset,
          $legend,
          type = this.model.attributes.type,
          name = this.model.attributes.name,
          iType = type === 'select' ? 'radio' : 'checkbox',
          iName = type === 'select' ? name + '_' + self.cid : name + '[]';

      this.$el.empty();

      $fieldset = $('<fieldset></fieldset>').attr({
        'data-role': 'controlgroup'
      });
      $legend = $('<legend></legend>').text(this.model.attributes.label);
      $fieldset.prepend($legend);

      _.forEach(this.model.attributes.options, function(label, value) {
        var $label = $('' + label + ''),
            $input = $('<input type="' + iType + '" />');

        $input.attr({
          name: iName,
          'data-rv-value': 'm.value'
        });
        $label.prepend($input);
        $fieldset.append($label);
      });

      this.$el.append($fieldset);
      this.bindRivets();
    }
  });

  return TextAreaElementView;
});

<commit_msg>fix data binding in ChoiceExpandedElementView<commit_after>define(['jquery', 'underscore', 'backbone', 'views/jqm/elements/choice'],
      function($, _, Backbone, ChoiceElementView) {
  'use strict';

  var ChoiceExpandedElementView = ChoiceElementView.extend({
    // TODO: fix bindings, the only thing that works is initial display :S
    render: function() {
      var self = this,
          $fieldset,
          $legend,
          type = this.model.attributes.type,
          name = this.model.attributes.name,
          iType = type === 'select' ? 'radio' : 'checkbox',
          iName = type === 'select' ? name + '_' + self.cid : name + '[]';

      this.$el.empty();

      $fieldset = $('<fieldset></fieldset>').attr({
        'data-role': 'controlgroup'
      });
      $legend = $('<legend></legend>').text(this.model.attributes.label);
      $fieldset.prepend($legend);

      _.forEach(this.model.attributes.options, function(label, value) {
        var $label = $('' + label + ''),
            $input = $('<input type="' + iType + '" />');

        $input.attr({
          name: iName,
          'data-rv-checked': 'm.value',
          value: value
        });
        $label.prepend($input);
        $fieldset.append($label);
      });

      this.$el.append($fieldset);
      if (type === 'select') {
        this.bindRivets();
        this.model.on('change:value', this.onSelectValueChange, this);
      } else { // type === 'multi'
        // bind custom handler for checkboxes -> array
        // Note: jQM uses triggerHandler, so this has to be a direct event
        $fieldset.find('input').on('click', {
          view: this,
          model: this.model
        }, this.onMultiInputClick);
        // bind custom handler for checkboxes <- array
        this.model.on('change:value', this.onMultiValueChange, this);
      }
    },
    onMultiInputClick: function(event) {
      var view = event.data.view,
          model = event.data.model,
          $inputs = view.$el.find('input:checked'),
          val;

      val = _.map($inputs, function(input) {
        return $(input).val();
      });
      model.set('value', val);
    },
    onMultiValueChange: function(event) {
      var view = this,
          model = this.model,
          $inputs = view.$el.find('input[type=radio],input[type=checkbox]'),
          value = model.attributes.value;

      if (!_.isArray(value)) {
        value = [];
      }
      $inputs.each(function(index, input) {
        var $input = $(input);
        $input.prop('checked', _.indexOf(value, $input.val()) !== -1);
      });

      $inputs.checkboxradio('refresh');
    },
    onSelectValueChange: function(event) {
      var view = this,
          $inputs = view.$el.find('input[type=radio],input[type=checkbox]');

      $inputs.checkboxradio('refresh');
    }
  });

  return ChoiceExpandedElementView;
});

<|endoftext|>