<commit_before>import ShaderProgram from './gl/shader_program';
import GLSL from './gl/glsl';
import Geo from './geo';
import {StyleParser} from './styles/style_parser';

let fs = require('fs');

const shaderSrc_ambientLight = fs.readFileSync(__dirname + '/gl/shaders/ambientLight.glsl', 'utf8');
const shaderSrc_directionalLight = fs.readFileSync(__dirname + '/gl/shaders/directionalLight.glsl', 'utf8');
const shaderSrc_pointLight = fs.readFileSync(__dirname + '/gl/shaders/pointLight.glsl', 'utf8');
const shaderSrc_spotLight = fs.readFileSync(__dirname + '/gl/shaders/spotLight.glsl', 'utf8');

// Abstract light
export default class Light {

    constructor (view, config) {
        this.name = config.name;
        this.view = view;

        if (config.ambient == null || typeof config.ambient === 'number') {
            this.ambient = GLSL.expandVec3(config.ambient || 0);
        }
        else {
            this.ambient = StyleParser.parseColor(config.ambient).slice(0, 3);
        }

        if (config.diffuse == null || typeof config.diffuse === 'number') {
            this.diffuse = GLSL.expandVec3(config.diffuse != null ? config.diffuse : 1);
        }
        else {
            this.diffuse = StyleParser.parseColor(config.diffuse).slice(0, 3);
        }

        if (config.specular == null || typeof config.specular === 'number') {
            this.specular = GLSL.expandVec3(config.specular || 0);
        }
        else {
            this.specular = StyleParser.parseColor(config.specular).slice(0, 3);
        }
    }

    // Create a light by type name, factory-style
    // 'config' must include 'name' and 'type', along with any other type-specific properties
    static create (view, config) {
        if (Light.types[config.type]) {
            return new Light.types[config.type](view, config);
        }
    }

    // Set light for a style: fragment lighting, vertex lighting, or none
    static setMode (mode, style) {
        if (mode === true) {
            mode = 'fragment';
        }
        mode = Light.enabled && ((mode != null) ? mode : 'fragment'); // default to fragment lighting
        style.defines['TANGRAM_LIGHTING_FRAGMENT'] = (mode === 'fragment');
        style.defines['TANGRAM_LIGHTING_VERTEX'] = (mode === 'vertex');
    }

    // Inject all provided light definitions, and calculate cumulative light function
    static inject (lights) {
        // Clear previous injections
        ShaderProgram.removeBlock(Light.block);

        // If lighting is globally disabled, nothing is injected (mostly for debugging or live editing)
        if (!Light.enabled) {
            return;
        }

        // Construct code to calculate each light instance
        let calculateLights = "";
        if (lights && Object.keys(lights).length > 0) {
            // Collect uniques types of lights
            let types = {};
            for (let light_name in lights) {
                types[lights[light_name].type] = true;
            }

            // Inject each type of light
            for (let type in types) {
                Light.types[type].inject();
            }

            // Inject per-instance blocks and construct the list of functions to calculate each light
            for (let light_name in lights) {
                // Define instance
                lights[light_name].inject();

                // Add the calculation function to the list
                calculateLights += `calculateLight(${light_name}, _eyeToPoint, _normal);\n`;
            }
        }

        // Glue together the final lighting function that sums all the lights
        let calculateFunction = `
            vec4 calculateLighting(in vec3 _eyeToPoint, in vec3 _normal, in vec4 _color) {

                // Do initial material calculations over normal, emission, ambient, diffuse and specular values
                calculateMaterial(_eyeToPoint,_normal);

                // Un roll the loop of individual ligths to calculate
                ${calculateLights}

                //  Final light intensity calculation
                vec4 color = vec4(0.0);

                // Keep material alpha channel when alpha blending is on
                #if !defined(TANGRAM_BLEND_OPAQUE)
                    #ifdef TANGRAM_MATERIAL_EMISSION
                        color = material.emission;
                    #endif

                    #ifdef TANGRAM_MATERIAL_AMBIENT
                        color += light_accumulator_ambient * _color * material.ambient;
                    #else
                        #ifdef TANGRAM_MATERIAL_DIFFUSE
                            color += light_accumulator_ambient * _color * material.diffuse;
                        #endif
                    #endif

                    #ifdef TANGRAM_MATERIAL_DIFFUSE
                        color += light_accumulator_diffuse * _color * material.diffuse;
                    #endif

                    #ifdef TANGRAM_MATERIAL_SPECULAR
                        color += light_accumulator_specular * material.specular;
                    #endif
                // Multiply material alpha channel into material RGB when alpha blending is off
                #else
                    color.a = _color.a; // use vertex color alpha

                    #ifdef TANGRAM_MATERIAL_EMISSION
                        color.rgb = material.emission.rgb * material.emission.a;
                    #endif

                    #ifdef TANGRAM_MATERIAL_AMBIENT
                        color.rgb += light_accumulator_ambient.rgb * _color.rgb * material.ambient.rgb * material.ambient.a;
                    #else
                        #ifdef TANGRAM_MATERIAL_DIFFUSE
                            color.rgb += light_accumulator_ambient.rgb * _color.rgb * material.diffuse.rgb * material.diffuse.a;
                        #endif
                    #endif

                    #ifdef TANGRAM_MATERIAL_DIFFUSE
                        color.rgb += light_accumulator_diffuse.rgb * _color.rgb * material.diffuse.rgb * material.diffuse.a;
                    #endif

                    #ifdef TANGRAM_MATERIAL_SPECULAR
                        color.rgb += light_accumulator_specular.rgb * material.specular.rgb * material.specular.a;
                    #endif
                #endif

                // Clamp final color
                color = clamp(color, 0.0, 1.0);

                return color;
            }`;

        ShaderProgram.addBlock(Light.block, calculateFunction);
    }

    // Common instance definition
    inject () {
        let instance =  `
            uniform ${this.struct_name} u_${this.name};
            ${this.struct_name} ${this.name};
            `;
        let assign = `
            ${this.name} = u_${this.name};\n
        `;

        ShaderProgram.addBlock(Light.block, instance);
        ShaderProgram.addBlock('setup', assign);
    }

    // Update method called once per frame
    update () {
    }

    // Called once per frame per program (e.g. for main render pass, then for each additional
    // pass for feature selection, etc.)
    setupProgram (_program) {
        //  Three common light properties
        _program.uniform('3fv', `u_${this.name}.ambient`, this.ambient);
        _program.uniform('3fv', `u_${this.name}.diffuse`, this.diffuse);
        _program.uniform('3fv', `u_${this.name}.specular`, this.specular);
    }

}

Light.types = {}; // references to subclasses by short name
Light.block = 'lighting'; // shader block name
Light.enabled = true; // lighting can be globally enabled/disabled

// Light subclasses
class AmbientLight extends Light {

    constructor(view, config) {
        super(view, config);
        this.type = 'ambient';
        this.struct_name = 'AmbientLight';
    }

    // Inject struct and calculate function
    static inject() {
        ShaderProgram.addBlock(Light.block, shaderSrc_ambientLight);
    }

    setupProgram (_program) {
        _program.uniform('3fv', `u_${this.name}.ambient`, this.ambient);
    }

}
Light.types['ambient'] = AmbientLight;

class DirectionalLight extends Light {

    constructor(view, config) {
        super(view, config);
        this.type = 'directional';
        this.struct_name = 'DirectionalLight';

        if (config.direction) {
            this.direction = config.direction;
        }
        else {
            // Default directional light maintains full intensity on ground, with basic extrusion shading
            this.direction = [-0.707, 0.707, -0.58]; // [x, y, z]

            if (config.ambient == null) {
                this.ambient = GLSL.expandVec3(0.5);
            }
        }

        this.direction = this.direction.map(parseFloat);
    }

    // Inject struct and calculate function
    static inject() {
        ShaderProgram.addBlock(Light.block, shaderSrc_directionalLight);
    }

    setupProgram (_program) {
        super.setupProgram(_program);
        _program.uniform('3fv', `u_${this.name}.direction`, this.direction);
    }

}
Light.types['directional'] = DirectionalLight;

class PointLight extends Light {

    constructor (view, config) {
        super(view, config);
        this.type = 'point';
        this.struct_name = 'PointLight';

        this.position = config.position || [0, 0, '100px'];
        this.position_eye = []; // position in eyespace
        this.origin = config.origin || 'ground';
        this.attenuation = !isNaN(parseFloat(config.attenuation)) ? parseFloat(config.attenuation) : 0;

        if (config.radius) {
            if (Array.isArray(config.radius) && config.radius.length === 2) {
                this.radius = config.radius;
            }
            else {
                this.radius = [null, config.radius];
            }
        }
        else {
            this.radius = null;
        }
    }

    // Inject struct and calculate function
    static inject () {
        ShaderProgram.addBlock(Light.block, shaderSrc_pointLight);
    }

    // Inject isntance-specific settings
    inject() {
        super.inject();

        ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_EXPONENT'] = (this.attenuation !== 0);
        ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_INNER_RADIUS'] = (this.radius != null && this.radius[0] != null);
        ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_OUTER_RADIUS'] = (this.radius != null);
    }

    update () {
        this.updateEyePosition();
    }

    updateEyePosition () {
        if (this.origin === 'world') {
            // For world origin, format is: [longitude, latitude, meters (default) or pixels w/px units]

            // Move light's world position into camera space
            let [x, y] = Geo.latLngToMeters(this.position);
            this.position_eye[0] = x - this.view.camera.position_meters[0];
            this.position_eye[1] = y - this.view.camera.position_meters[1];

            this.position_eye[2] = StyleParser.convertUnits(this.position[2],
                { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) });
            this.position_eye[2] = this.position_eye[2] - this.view.camera.position_meters[2];
        }
        else if (this.origin === 'ground' || this.origin === 'camera') {
            // For camera or ground origin, format is: [x, y, z] in meters (default) or pixels w/px units

            // Light is in camera space by default
            this.position_eye = StyleParser.convertUnits(this.position,
                { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) });

            if (this.origin === 'ground') {
                // Leave light's xy in camera space, but z needs to be moved relative to ground plane
                this.position_eye[2] = this.position_eye[2] - this.view.camera.position_meters[2];
            }
        }
        this.position_eye[3] = 1;
    }

    setupProgram (_program) {
        super.setupProgram(_program);

        _program.uniform('4fv', `u_${this.name}.position`, this.position_eye);

        if(ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_EXPONENT']) {
            _program.uniform('1f', `u_${this.name}.attenuationExponent`, this.attenuation);
        }

        if(ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_INNER_RADIUS']) {
            _program.uniform('1f', `u_${this.name}.innerRadius`,
                StyleParser.convertUnits(this.radius[0],
                    { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) }));
        }

        if(ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_OUTER_RADIUS']) {
            _program.uniform('1f', `u_${this.name}.outerRadius`,
                StyleParser.convertUnits(this.radius[1],
                    { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) }));
        }
    }
}
Light.types['point'] = PointLight;

class SpotLight extends PointLight {

    constructor (view, config) {
        super(view, config);
        this.type = 'spotlight';
        this.struct_name = 'SpotLight';

        this.direction = (config.direction || [0, 0, -1]).map(parseFloat); // [x, y, z]
        this.exponent = config.exponent ? parseFloat(config.exponent) : 0.2;
        this.angle = config.angle ? parseFloat(config.angle) : 20;
    }

    // Inject struct and calculate function
    static inject () {
        ShaderProgram.addBlock(Light.block, shaderSrc_spotLight);
    }

    setupProgram (_program) {
        super.setupProgram(_program);

        _program.uniform('3fv', `u_${this.name}.direction`, this.direction);
        _program.uniform('1f', `u_${this.name}.spotCosCutoff`, Math.cos(this.angle * 3.14159 / 180));
        _program.uniform('1f', `u_${this.name}.spotExponent`, this.exponent);
    }

}
Light.types['spotlight'] = SpotLight;
<commit_msg>lights: base default direction light on angle instead of magic numbers (could be exposed as scene file property in the future)<commit_after>import ShaderProgram from './gl/shader_program';
import GLSL from './gl/glsl';
import Geo from './geo';
import {StyleParser} from './styles/style_parser';

let fs = require('fs');

const shaderSrc_ambientLight = fs.readFileSync(__dirname + '/gl/shaders/ambientLight.glsl', 'utf8');
const shaderSrc_directionalLight = fs.readFileSync(__dirname + '/gl/shaders/directionalLight.glsl', 'utf8');
const shaderSrc_pointLight = fs.readFileSync(__dirname + '/gl/shaders/pointLight.glsl', 'utf8');
const shaderSrc_spotLight = fs.readFileSync(__dirname + '/gl/shaders/spotLight.glsl', 'utf8');

// Abstract light
export default class Light {

    constructor (view, config) {
        this.name = config.name;
        this.view = view;

        if (config.ambient == null || typeof config.ambient === 'number') {
            this.ambient = GLSL.expandVec3(config.ambient || 0);
        }
        else {
            this.ambient = StyleParser.parseColor(config.ambient).slice(0, 3);
        }

        if (config.diffuse == null || typeof config.diffuse === 'number') {
            this.diffuse = GLSL.expandVec3(config.diffuse != null ? config.diffuse : 1);
        }
        else {
            this.diffuse = StyleParser.parseColor(config.diffuse).slice(0, 3);
        }

        if (config.specular == null || typeof config.specular === 'number') {
            this.specular = GLSL.expandVec3(config.specular || 0);
        }
        else {
            this.specular = StyleParser.parseColor(config.specular).slice(0, 3);
        }
    }

    // Create a light by type name, factory-style
    // 'config' must include 'name' and 'type', along with any other type-specific properties
    static create (view, config) {
        if (Light.types[config.type]) {
            return new Light.types[config.type](view, config);
        }
    }

    // Set light for a style: fragment lighting, vertex lighting, or none
    static setMode (mode, style) {
        if (mode === true) {
            mode = 'fragment';
        }
        mode = Light.enabled && ((mode != null) ? mode : 'fragment'); // default to fragment lighting
        style.defines['TANGRAM_LIGHTING_FRAGMENT'] = (mode === 'fragment');
        style.defines['TANGRAM_LIGHTING_VERTEX'] = (mode === 'vertex');
    }

    // Inject all provided light definitions, and calculate cumulative light function
    static inject (lights) {
        // Clear previous injections
        ShaderProgram.removeBlock(Light.block);

        // If lighting is globally disabled, nothing is injected (mostly for debugging or live editing)
        if (!Light.enabled) {
            return;
        }

        // Construct code to calculate each light instance
        let calculateLights = "";
        if (lights && Object.keys(lights).length > 0) {
            // Collect uniques types of lights
            let types = {};
            for (let light_name in lights) {
                types[lights[light_name].type] = true;
            }

            // Inject each type of light
            for (let type in types) {
                Light.types[type].inject();
            }

            // Inject per-instance blocks and construct the list of functions to calculate each light
            for (let light_name in lights) {
                // Define instance
                lights[light_name].inject();

                // Add the calculation function to the list
                calculateLights += `calculateLight(${light_name}, _eyeToPoint, _normal);\n`;
            }
        }

        // Glue together the final lighting function that sums all the lights
        let calculateFunction = `
            vec4 calculateLighting(in vec3 _eyeToPoint, in vec3 _normal, in vec4 _color) {

                // Do initial material calculations over normal, emission, ambient, diffuse and specular values
                calculateMaterial(_eyeToPoint,_normal);

                // Un roll the loop of individual ligths to calculate
                ${calculateLights}

                //  Final light intensity calculation
                vec4 color = vec4(0.0);

                // Keep material alpha channel when alpha blending is on
                #if !defined(TANGRAM_BLEND_OPAQUE)
                    #ifdef TANGRAM_MATERIAL_EMISSION
                        color = material.emission;
                    #endif

                    #ifdef TANGRAM_MATERIAL_AMBIENT
                        color += light_accumulator_ambient * _color * material.ambient;
                    #else
                        #ifdef TANGRAM_MATERIAL_DIFFUSE
                            color += light_accumulator_ambient * _color * material.diffuse;
                        #endif
                    #endif

                    #ifdef TANGRAM_MATERIAL_DIFFUSE
                        color += light_accumulator_diffuse * _color * material.diffuse;
                    #endif

                    #ifdef TANGRAM_MATERIAL_SPECULAR
                        color += light_accumulator_specular * material.specular;
                    #endif
                // Multiply material alpha channel into material RGB when alpha blending is off
                #else
                    color.a = _color.a; // use vertex color alpha

                    #ifdef TANGRAM_MATERIAL_EMISSION
                        color.rgb = material.emission.rgb * material.emission.a;
                    #endif

                    #ifdef TANGRAM_MATERIAL_AMBIENT
                        color.rgb += light_accumulator_ambient.rgb * _color.rgb * material.ambient.rgb * material.ambient.a;
                    #else
                        #ifdef TANGRAM_MATERIAL_DIFFUSE
                            color.rgb += light_accumulator_ambient.rgb * _color.rgb * material.diffuse.rgb * material.diffuse.a;
                        #endif
                    #endif

                    #ifdef TANGRAM_MATERIAL_DIFFUSE
                        color.rgb += light_accumulator_diffuse.rgb * _color.rgb * material.diffuse.rgb * material.diffuse.a;
                    #endif

                    #ifdef TANGRAM_MATERIAL_SPECULAR
                        color.rgb += light_accumulator_specular.rgb * material.specular.rgb * material.specular.a;
                    #endif
                #endif

                // Clamp final color
                color = clamp(color, 0.0, 1.0);

                return color;
            }`;

        ShaderProgram.addBlock(Light.block, calculateFunction);
    }

    // Common instance definition
    inject () {
        let instance =  `
            uniform ${this.struct_name} u_${this.name};
            ${this.struct_name} ${this.name};
            `;
        let assign = `
            ${this.name} = u_${this.name};\n
        `;

        ShaderProgram.addBlock(Light.block, instance);
        ShaderProgram.addBlock('setup', assign);
    }

    // Update method called once per frame
    update () {
    }

    // Called once per frame per program (e.g. for main render pass, then for each additional
    // pass for feature selection, etc.)
    setupProgram (_program) {
        //  Three common light properties
        _program.uniform('3fv', `u_${this.name}.ambient`, this.ambient);
        _program.uniform('3fv', `u_${this.name}.diffuse`, this.diffuse);
        _program.uniform('3fv', `u_${this.name}.specular`, this.specular);
    }

}

Light.types = {}; // references to subclasses by short name
Light.block = 'lighting'; // shader block name
Light.enabled = true; // lighting can be globally enabled/disabled

// Light subclasses
class AmbientLight extends Light {

    constructor(view, config) {
        super(view, config);
        this.type = 'ambient';
        this.struct_name = 'AmbientLight';
    }

    // Inject struct and calculate function
    static inject() {
        ShaderProgram.addBlock(Light.block, shaderSrc_ambientLight);
    }

    setupProgram (_program) {
        _program.uniform('3fv', `u_${this.name}.ambient`, this.ambient);
    }

}
Light.types['ambient'] = AmbientLight;

class DirectionalLight extends Light {

    constructor(view, config) {
        super(view, config);
        this.type = 'directional';
        this.struct_name = 'DirectionalLight';

        if (config.direction) {
            this.direction = config.direction;
        }
        else {
            // Default directional light maintains full intensity on ground, with basic extrusion shading
            let theta = 135; // angle of light in xy plane (rotated around z axis)
            let scale = Math.sin(Math.PI*60/180); // scaling factor to keep total directional intensity to 0.5
            this.direction = [
                Math.cos(Math.PI*theta/180) * scale,
                Math.sin(Math.PI*theta/180) * scale,
                -0.5
            ];

            if (config.ambient == null) {
                this.ambient = GLSL.expandVec3(0.5);
            }
        }

        this.direction = this.direction.map(parseFloat);
    }

    // Inject struct and calculate function
    static inject() {
        ShaderProgram.addBlock(Light.block, shaderSrc_directionalLight);
    }

    setupProgram (_program) {
        super.setupProgram(_program);
        _program.uniform('3fv', `u_${this.name}.direction`, this.direction);
    }

}
Light.types['directional'] = DirectionalLight;

class PointLight extends Light {

    constructor (view, config) {
        super(view, config);
        this.type = 'point';
        this.struct_name = 'PointLight';

        this.position = config.position || [0, 0, '100px'];
        this.position_eye = []; // position in eyespace
        this.origin = config.origin || 'ground';
        this.attenuation = !isNaN(parseFloat(config.attenuation)) ? parseFloat(config.attenuation) : 0;

        if (config.radius) {
            if (Array.isArray(config.radius) && config.radius.length === 2) {
                this.radius = config.radius;
            }
            else {
                this.radius = [null, config.radius];
            }
        }
        else {
            this.radius = null;
        }
    }

    // Inject struct and calculate function
    static inject () {
        ShaderProgram.addBlock(Light.block, shaderSrc_pointLight);
    }

    // Inject isntance-specific settings
    inject() {
        super.inject();

        ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_EXPONENT'] = (this.attenuation !== 0);
        ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_INNER_RADIUS'] = (this.radius != null && this.radius[0] != null);
        ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_OUTER_RADIUS'] = (this.radius != null);
    }

    update () {
        this.updateEyePosition();
    }

    updateEyePosition () {
        if (this.origin === 'world') {
            // For world origin, format is: [longitude, latitude, meters (default) or pixels w/px units]

            // Move light's world position into camera space
            let [x, y] = Geo.latLngToMeters(this.position);
            this.position_eye[0] = x - this.view.camera.position_meters[0];
            this.position_eye[1] = y - this.view.camera.position_meters[1];

            this.position_eye[2] = StyleParser.convertUnits(this.position[2],
                { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) });
            this.position_eye[2] = this.position_eye[2] - this.view.camera.position_meters[2];
        }
        else if (this.origin === 'ground' || this.origin === 'camera') {
            // For camera or ground origin, format is: [x, y, z] in meters (default) or pixels w/px units

            // Light is in camera space by default
            this.position_eye = StyleParser.convertUnits(this.position,
                { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) });

            if (this.origin === 'ground') {
                // Leave light's xy in camera space, but z needs to be moved relative to ground plane
                this.position_eye[2] = this.position_eye[2] - this.view.camera.position_meters[2];
            }
        }
        this.position_eye[3] = 1;
    }

    setupProgram (_program) {
        super.setupProgram(_program);

        _program.uniform('4fv', `u_${this.name}.position`, this.position_eye);

        if(ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_EXPONENT']) {
            _program.uniform('1f', `u_${this.name}.attenuationExponent`, this.attenuation);
        }

        if(ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_INNER_RADIUS']) {
            _program.uniform('1f', `u_${this.name}.innerRadius`,
                StyleParser.convertUnits(this.radius[0],
                    { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) }));
        }

        if(ShaderProgram.defines['TANGRAM_POINTLIGHT_ATTENUATION_OUTER_RADIUS']) {
            _program.uniform('1f', `u_${this.name}.outerRadius`,
                StyleParser.convertUnits(this.radius[1],
                    { zoom: this.view.zoom, meters_per_pixel: Geo.metersPerPixel(this.view.zoom) }));
        }
    }
}
Light.types['point'] = PointLight;

class SpotLight extends PointLight {

    constructor (view, config) {
        super(view, config);
        this.type = 'spotlight';
        this.struct_name = 'SpotLight';

        this.direction = (config.direction || [0, 0, -1]).map(parseFloat); // [x, y, z]
        this.exponent = config.exponent ? parseFloat(config.exponent) : 0.2;
        this.angle = config.angle ? parseFloat(config.angle) : 20;
    }

    // Inject struct and calculate function
    static inject () {
        ShaderProgram.addBlock(Light.block, shaderSrc_spotLight);
    }

    setupProgram (_program) {
        super.setupProgram(_program);

        _program.uniform('3fv', `u_${this.name}.direction`, this.direction);
        _program.uniform('1f', `u_${this.name}.spotCosCutoff`, Math.cos(this.angle * 3.14159 / 180));
        _program.uniform('1f', `u_${this.name}.spotExponent`, this.exponent);
    }

}
Light.types['spotlight'] = SpotLight;
<|endoftext|>


<commit_before>export class PanierController {

    constructor(localStorageService, PizzaService) {
        this.localStorageService = localStorageService;
        this.PizzaService = PizzaService;

        this.panier = this.localStorageService.get('panier');
        this.pizzas = [];
        this.prixTotal = 0;
        this.PizzaService.getPizzas()
<<<<<<< HEAD
            .then(pizzas => { this.pizzas = pizzas })
            .then(() => this.prixTotal = this.getPrixTotal());
=======
            .then(pizzas => { this.pizzas = pizzas; this.prixTotal = this.getPrixTotal })
            .then(this.getPrixTotal());
>>>>>>> #19 Merging src/index.js
    }

    //retourne la pizza de 'pizzas' avec pizzaId
    getPizzabyId(pizzaId) {
<<<<<<< HEAD
        return this.pizzas.find(pizza => pizza.id === parseInt(pizzaId));
=======
        return this.pizzas.find(()=>pizza.id === pizzaId);
>>>>>>> #19 Merging src/index.js
    }

    supprimer(itemPanier) {
        let index = this.panier.indexOf(itemPanier);
        if (index > -1) {
            this.panier.splice(index, 1);
        }
        this.updatePanier();
    }

    plusNbPizza(itemPanier) {
        itemPanier.quantite++;
        this.updatePanier();
    }

    moinsNbPizza(itemPanier) {
        if (itemPanier.quantite === 1) {
            this.supprimer(itemPanier)
        } else {
            itemPanier.quantite--;
        }
        this.updatePanier();
    }

    //repercute tout les changements du panier local sur le localStorage
    updatePanier() {
        this.localStorageService.set('panier', this.panier, 'localStorage');
        this.prixTotal = this.getPrixTotal();
    }

    getPrixTotal() {
        if (this.panier === null) return 0;
        return this.panier
            .reduce( (accumulateur, item) => {
                return accumulateur + (this.getPizzabyId(item.id).prix * item.quantite);
            }, 0)
    }
}<commit_msg>#19 - Gerer Panier fix panier.controller<commit_after>export class PanierController {

    constructor(localStorageService, PizzaService) {
        this.localStorageService = localStorageService;
        this.PizzaService = PizzaService;

        this.panier = this.localStorageService.get('panier');
        this.pizzas = [];
        this.prixTotal = 0;
        this.PizzaService.getPizzas()
            .then(pizzas => { this.pizzas = pizzas })
            .then(() => this.prixTotal = this.getPrixTotal());
    }

    //retourne la pizza de 'pizzas' avec pizzaId
    getPizzabyId(pizzaId) {
        return this.pizzas.find(pizza => pizza.id === parseInt(pizzaId));
    }

    supprimer(itemPanier) {
        let index = this.panier.indexOf(itemPanier);
        if (index > -1) {
            this.panier.splice(index, 1);
        }
        this.updatePanier();
    }

    plusNbPizza(itemPanier) {
        itemPanier.quantite++;
        this.updatePanier();
    }

    moinsNbPizza(itemPanier) {
        if (itemPanier.quantite === 1) {
            this.supprimer(itemPanier)
        } else {
            itemPanier.quantite--;
        }
        this.updatePanier();
    }

    //repercute tout les changements du panier local sur le localStorage
    updatePanier() {
        this.localStorageService.set('panier', this.panier, 'localStorage');
        this.prixTotal = this.getPrixTotal();
    }

    getPrixTotal() {
        if (this.panier === null) return 0;
        return this.panier
            .reduce( (accumulateur, item) => {
                return accumulateur + (this.getPizzabyId(item.id).prix * item.quantite);
            }, 0)
    }
}<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

const ANIMATION_STATE_CLASSES = {
  animating: 'rah-animating',
  animatingUp: 'rah-animating--up',
  animatingDown: 'rah-animating--down',
  animatingToHeightZero: 'rah-animating--to-height-zero',
  animatingToHeightAuto: 'rah-animating--to-height-auto',
  animatingToHeightSpecific: 'rah-animating--to-height-specific',
  static: 'rah-static',
  staticHeightZero: 'rah-static--height-zero',
  staticHeightAuto: 'rah-static--height-auto',
  staticHeightSpecific: 'rah-static--height-specific',
};

function omit(obj, ...keys) {
  if (!keys.length) {
    return obj;
  }

  const res = {};
  const objectKeys = Object.keys(obj);

  for (let i = 0; i < objectKeys.length; i++) {
    const key = objectKeys[i];

    if (keys.indexOf(key) === -1) {
      res[key] = obj[key];
    }
  }

  return res;
}

// Start animation helper using nested requestAnimationFrames
function startAnimationHelper(callback) {
  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      callback();
    });
  });
}

const AnimateHeight = class extends React.Component {
  constructor(props) {
    super(props);

    let height = 'auto';
    let overflow = 'visible';

    if (this.isNumber(props.height)) {
      height = props.height < 0 ? 0 : props.height;
      overflow = 'hidden';
    }

    this.animationStateClasses = Object.assign(ANIMATION_STATE_CLASSES, props.animationStateClasses);

    const animationStateClasses = this.getStaticStateClasses(height);

    this.state = {
      animationStateClasses,
      height,
      overflow,
      shouldUseTransitions: false,
    };
  }

  componentDidMount() {
    const { height } = this.state;

    // Hide content if height is 0 (to prevent tabbing into it)
    // Check for contentElement is added cause this would fail in tests (react-test-renderer)
    // Read more here: https://github.com/Stanko/react-animate-height/issues/17
    if (this.contentElement && this.contentElement.style) {
      this.hideContent(height);
    }
  }

  componentWillReceiveProps(nextProps) {
    const {
      height,
    } = this.props;

    // Check if 'height' prop has changed
    if (this.contentElement && nextProps.height !== height) {
      // Remove display: none from the content div
      // if it was hidden to prevent tabbing into it
      this.showContent();

      // Cache content height
      this.contentElement.style.overflow = 'hidden';
      const contentHeight = this.contentElement.offsetHeight;
      this.contentElement.style.overflow = '';

      let newHeight = null;
      const timeoutState = {
        height: null, // it will be always set to either 'auto' or specific number
        overflow: 'hidden',
      };
      const isCurrentHeightAuto = this.state.height === 'auto';

      if (this.isNumber(nextProps.height)) {
        // If new height is a number
        newHeight = nextProps.height < 0 ? 0 : nextProps.height;
        timeoutState.height = newHeight;
      } else if (
        // Percentage height
        typeof nextProps.height === 'string' &&
        nextProps.height.search('%') === nextProps.height.length - 1 &&
        this.isNumber(nextProps.height.substr(0, nextProps.height.length - 1))
      ) {
        newHeight = nextProps.height;
        timeoutState.height = newHeight;
      } else {
        // If not, animate to content height
        // and then reset to auto
        newHeight = contentHeight;
        timeoutState.height = 'auto';
        timeoutState.overflow = null;
      }

      if (isCurrentHeightAuto) {
        // This is the height to be animated to
        timeoutState.height = newHeight;

        // If previous height was 'auto'
        // set starting height explicitly to be able to use transition
        newHeight = contentHeight;
      }

      // Animation classes
      const animationStateClasses = cx({
        [this.animationStateClasses.animating]: true,
        [this.animationStateClasses.animatingUp]: height === 'auto' || nextProps.height < height,
        [this.animationStateClasses.animatingDown]: nextProps.height === 'auto' || nextProps.height > height,
        [this.animationStateClasses.animatingToHeightZero]: timeoutState.height === 0,
        [this.animationStateClasses.animatingToHeightAuto]: timeoutState.height === 'auto',
        [this.animationStateClasses.animatingToHeightSpecific]: timeoutState.height > 0,
      });

      // Animation classes to be put after animation is complete
      const timeoutAnimationStateClasses = this.getStaticStateClasses(timeoutState.height);

      // Set starting height and animating classes
      this.setState({
        animationStateClasses,
        height: newHeight,
        overflow: 'hidden',
        // When animating from 'auto' we first need to set fixed height
        // that change should be animated
        shouldUseTransitions: !isCurrentHeightAuto,
      });

      // Clear timeouts
      clearTimeout(this.timeoutID);
      clearTimeout(this.animationClassesTimeoutID);

      if (isCurrentHeightAuto) {
        // When animating from 'auto' we use a short timeout to start animation
        // after setting fixed height above
        timeoutState.shouldUseTransitions = true;

        startAnimationHelper(() => {
          this.setState(timeoutState);

          // ANIMATION STARTS, run a callback if it exists
          this.runCallback(nextProps.onAnimationStart);
        });

        // Set static classes and remove transitions when animation ends
        this.animationClassesTimeoutID = setTimeout(() => {
          this.setState({
            animationStateClasses: timeoutAnimationStateClasses,
            shouldUseTransitions: false,
          });

          // ANIMATION ENDS
          // Run a callback if it exists
          this.runCallback(nextProps.onAnimationEnd);
          // Hide content if height is 0 (to prevent tabbing into it)
          this.hideContent(timeoutState.height);
        }, nextProps.duration);
      } else {
        // ANIMATION STARTS, run a callback if it exists
        this.runCallback(nextProps.onAnimationStart);

        // Set end height, classes and remove transitions when animation is complete
        this.timeoutID = setTimeout(() => {
          timeoutState.animationStateClasses = timeoutAnimationStateClasses;
          timeoutState.shouldUseTransitions = false;

          this.setState(timeoutState);

          // ANIMATION ENDS
          // Run a callback if it exists
          this.runCallback(nextProps.onAnimationEnd);
          // Hide content if height is 0 (to prevent tabbing into it)
          this.hideContent(newHeight);
        }, nextProps.duration);
      }
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timeoutID);
    clearTimeout(this.animationClassesTimeoutID);
    this.timeoutID = null;
    this.animationClassesTimeoutID = null;
    this.animationStateClasses = null;
  }

  isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
  }

  runCallback(callback) {
    if (callback && typeof callback === 'function') {
      callback();
    }
  }

  showContent() {
    if (this.state.height === 0) {
      this.contentElement.style.display = '';
    }
  }

  hideContent(newHeight) {
    if (newHeight === 0) {
      this.contentElement.style.display = 'none';
    }
  }

  getStaticStateClasses(height) {
    return cx({
      [this.animationStateClasses.static]: true,
      [this.animationStateClasses.staticHeightZero]: height === 0,
      [this.animationStateClasses.staticHeightSpecific]: height > 0,
      [this.animationStateClasses.staticHeightAuto]: height === 'auto',
    });
  }

  render() {
    const {
      children,
      className,
      contentClassName,
      duration,
      easing,
      style,
    } = this.props;
    const {
      height,
      overflow,
      animationStateClasses,
      shouldUseTransitions,
    } = this.state;

    // Include transition passed through styles
    const userTransition = style.transition ? `${ style.transition },` : '';
    const transitionString = `${ userTransition } height ${ duration }ms ${ easing } `;

    const componentStyle = {
      ...style,
      height,
      overflow: overflow || style.overflow,
    };

    if (shouldUseTransitions) {
      componentStyle.WebkitTransition = transitionString;
      componentStyle.MozTransition = transitionString;
      componentStyle.OTransition = transitionString;
      componentStyle.msTransition = transitionString;
      componentStyle.transition = transitionString;
    }

    const componentClasses = cx({
      [animationStateClasses]: true,
      [className]: className,
    });

    const propsToOmit = ['height', 'duration', 'easing', 'contentClassName', 'animationStateClasses'];

    return (
      <div
        { ...omit(this.props, ...propsToOmit) }
        aria-hidden={ height === 0 }
        className={ componentClasses }
        style={ componentStyle }
      >
        <div
          className={ contentClassName }
          ref={ el => this.contentElement = el }
        >
          { children }
        </div>
      </div>
    );
  }
};

AnimateHeight.propTypes = {
  animationStateClasses: PropTypes.object,
  children: PropTypes.any.isRequired,
  className: PropTypes.string,
  contentClassName: PropTypes.string,
  duration: PropTypes.number.isRequired,
  easing: PropTypes.string.isRequired,
  height: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]),
  onAnimationEnd: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
  onAnimationStart: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
  style: PropTypes.object,
};

AnimateHeight.defaultProps = {
  duration: 250,
  easing: 'ease',
  style: {},
  animationStateClasses: ANIMATION_STATE_CLASSES,
};

export default AnimateHeight;
<commit_msg>Add percentage support to constructor (#32)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';

const ANIMATION_STATE_CLASSES = {
  animating: 'rah-animating',
  animatingUp: 'rah-animating--up',
  animatingDown: 'rah-animating--down',
  animatingToHeightZero: 'rah-animating--to-height-zero',
  animatingToHeightAuto: 'rah-animating--to-height-auto',
  animatingToHeightSpecific: 'rah-animating--to-height-specific',
  static: 'rah-static',
  staticHeightZero: 'rah-static--height-zero',
  staticHeightAuto: 'rah-static--height-auto',
  staticHeightSpecific: 'rah-static--height-specific',
};

function omit(obj, ...keys) {
  if (!keys.length) {
    return obj;
  }

  const res = {};
  const objectKeys = Object.keys(obj);

  for (let i = 0; i < objectKeys.length; i++) {
    const key = objectKeys[i];

    if (keys.indexOf(key) === -1) {
      res[key] = obj[key];
    }
  }

  return res;
}

// Start animation helper using nested requestAnimationFrames
function startAnimationHelper(callback) {
  requestAnimationFrame(() => {
    requestAnimationFrame(() => {
      callback();
    });
  });
}

const AnimateHeight = class extends React.Component {
  constructor(props) {
    super(props);

    let height = 'auto';
    let overflow = 'visible';

    if (this.isNumber(props.height)) {
      height = props.height < 0 ? 0 : props.height;
      overflow = 'hidden';
    } else if (
      typeof props.height === 'string' &&
      props.height.search('%') === props.height.length - 1 &&
      this.isNumber(props.height.substr(0, props.height.length - 1))
    ) {
      height = props.height;
      overflow = 'hidden';
    }

    this.animationStateClasses = Object.assign(ANIMATION_STATE_CLASSES, props.animationStateClasses);

    const animationStateClasses = this.getStaticStateClasses(height);

    this.state = {
      animationStateClasses,
      height,
      overflow,
      shouldUseTransitions: false,
    };
  }

  componentDidMount() {
    const { height } = this.state;

    // Hide content if height is 0 (to prevent tabbing into it)
    // Check for contentElement is added cause this would fail in tests (react-test-renderer)
    // Read more here: https://github.com/Stanko/react-animate-height/issues/17
    if (this.contentElement && this.contentElement.style) {
      this.hideContent(height);
    }
  }

  componentWillReceiveProps(nextProps) {
    const {
      height,
    } = this.props;

    // Check if 'height' prop has changed
    if (this.contentElement && nextProps.height !== height) {
      // Remove display: none from the content div
      // if it was hidden to prevent tabbing into it
      this.showContent();

      // Cache content height
      this.contentElement.style.overflow = 'hidden';
      const contentHeight = this.contentElement.offsetHeight;
      this.contentElement.style.overflow = '';

      let newHeight = null;
      const timeoutState = {
        height: null, // it will be always set to either 'auto' or specific number
        overflow: 'hidden',
      };
      const isCurrentHeightAuto = this.state.height === 'auto';

      if (this.isNumber(nextProps.height)) {
        // If new height is a number
        newHeight = nextProps.height < 0 ? 0 : nextProps.height;
        timeoutState.height = newHeight;
      } else if (
        // Percentage height
        typeof nextProps.height === 'string' &&
        nextProps.height.search('%') === nextProps.height.length - 1 &&
        this.isNumber(nextProps.height.substr(0, nextProps.height.length - 1))
      ) {
        newHeight = nextProps.height;
        timeoutState.height = newHeight;
      } else {
        // If not, animate to content height
        // and then reset to auto
        newHeight = contentHeight;
        timeoutState.height = 'auto';
        timeoutState.overflow = null;
      }

      if (isCurrentHeightAuto) {
        // This is the height to be animated to
        timeoutState.height = newHeight;

        // If previous height was 'auto'
        // set starting height explicitly to be able to use transition
        newHeight = contentHeight;
      }

      // Animation classes
      const animationStateClasses = cx({
        [this.animationStateClasses.animating]: true,
        [this.animationStateClasses.animatingUp]: height === 'auto' || nextProps.height < height,
        [this.animationStateClasses.animatingDown]: nextProps.height === 'auto' || nextProps.height > height,
        [this.animationStateClasses.animatingToHeightZero]: timeoutState.height === 0,
        [this.animationStateClasses.animatingToHeightAuto]: timeoutState.height === 'auto',
        [this.animationStateClasses.animatingToHeightSpecific]: timeoutState.height > 0,
      });

      // Animation classes to be put after animation is complete
      const timeoutAnimationStateClasses = this.getStaticStateClasses(timeoutState.height);

      // Set starting height and animating classes
      this.setState({
        animationStateClasses,
        height: newHeight,
        overflow: 'hidden',
        // When animating from 'auto' we first need to set fixed height
        // that change should be animated
        shouldUseTransitions: !isCurrentHeightAuto,
      });

      // Clear timeouts
      clearTimeout(this.timeoutID);
      clearTimeout(this.animationClassesTimeoutID);

      if (isCurrentHeightAuto) {
        // When animating from 'auto' we use a short timeout to start animation
        // after setting fixed height above
        timeoutState.shouldUseTransitions = true;

        startAnimationHelper(() => {
          this.setState(timeoutState);

          // ANIMATION STARTS, run a callback if it exists
          this.runCallback(nextProps.onAnimationStart);
        });

        // Set static classes and remove transitions when animation ends
        this.animationClassesTimeoutID = setTimeout(() => {
          this.setState({
            animationStateClasses: timeoutAnimationStateClasses,
            shouldUseTransitions: false,
          });

          // ANIMATION ENDS
          // Run a callback if it exists
          this.runCallback(nextProps.onAnimationEnd);
          // Hide content if height is 0 (to prevent tabbing into it)
          this.hideContent(timeoutState.height);
        }, nextProps.duration);
      } else {
        // ANIMATION STARTS, run a callback if it exists
        this.runCallback(nextProps.onAnimationStart);

        // Set end height, classes and remove transitions when animation is complete
        this.timeoutID = setTimeout(() => {
          timeoutState.animationStateClasses = timeoutAnimationStateClasses;
          timeoutState.shouldUseTransitions = false;

          this.setState(timeoutState);

          // ANIMATION ENDS
          // Run a callback if it exists
          this.runCallback(nextProps.onAnimationEnd);
          // Hide content if height is 0 (to prevent tabbing into it)
          this.hideContent(newHeight);
        }, nextProps.duration);
      }
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timeoutID);
    clearTimeout(this.animationClassesTimeoutID);
    this.timeoutID = null;
    this.animationClassesTimeoutID = null;
    this.animationStateClasses = null;
  }

  isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
  }

  runCallback(callback) {
    if (callback && typeof callback === 'function') {
      callback();
    }
  }

  showContent() {
    if (this.state.height === 0) {
      this.contentElement.style.display = '';
    }
  }

  hideContent(newHeight) {
    if (newHeight === 0) {
      this.contentElement.style.display = 'none';
    }
  }

  getStaticStateClasses(height) {
    return cx({
      [this.animationStateClasses.static]: true,
      [this.animationStateClasses.staticHeightZero]: height === 0,
      [this.animationStateClasses.staticHeightSpecific]: height > 0,
      [this.animationStateClasses.staticHeightAuto]: height === 'auto',
    });
  }

  render() {
    const {
      children,
      className,
      contentClassName,
      duration,
      easing,
      style,
    } = this.props;
    const {
      height,
      overflow,
      animationStateClasses,
      shouldUseTransitions,
    } = this.state;

    // Include transition passed through styles
    const userTransition = style.transition ? `${ style.transition },` : '';
    const transitionString = `${ userTransition } height ${ duration }ms ${ easing } `;

    const componentStyle = {
      ...style,
      height,
      overflow: overflow || style.overflow,
    };

    if (shouldUseTransitions) {
      componentStyle.WebkitTransition = transitionString;
      componentStyle.MozTransition = transitionString;
      componentStyle.OTransition = transitionString;
      componentStyle.msTransition = transitionString;
      componentStyle.transition = transitionString;
    }

    const componentClasses = cx({
      [animationStateClasses]: true,
      [className]: className,
    });

    const propsToOmit = ['height', 'duration', 'easing', 'contentClassName', 'animationStateClasses'];

    return (
      <div
        { ...omit(this.props, ...propsToOmit) }
        aria-hidden={ height === 0 }
        className={ componentClasses }
        style={ componentStyle }
      >
        <div
          className={ contentClassName }
          ref={ el => this.contentElement = el }
        >
          { children }
        </div>
      </div>
    );
  }
};

AnimateHeight.propTypes = {
  animationStateClasses: PropTypes.object,
  children: PropTypes.any.isRequired,
  className: PropTypes.string,
  contentClassName: PropTypes.string,
  duration: PropTypes.number.isRequired,
  easing: PropTypes.string.isRequired,
  height: PropTypes.oneOfType([
    PropTypes.string,
    PropTypes.number,
  ]),
  onAnimationEnd: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
  onAnimationStart: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
  style: PropTypes.object,
};

AnimateHeight.defaultProps = {
  duration: 250,
  easing: 'ease',
  style: {},
  animationStateClasses: ANIMATION_STATE_CLASSES,
};

export default AnimateHeight;
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: Loader.js                                                            \\
// Module: Loader                                                             \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

/*global window, document, setTimeout, XMLHttpRequest, XDomainRequest,
         localStorage */

"use strict";

( function ( NS, XMLHttpRequest ) {

/*
    Object: O.Loader

    The Loader class handles loading in modules as and when they're
    needed.
*/
var UNREQUESTED = 0,
    LOADING = 1,
    LOADED = 2,
    WILL_EXECUTE = 4,
    EXECUTED = 8;

var ls = localStorage;
var LS_PREFIX = 'OResource-';
var LS_V_PREFIX = 'OResource-v-';

var moduleInfo = {};
var require;

var CORSRequest =
    ( 'withCredentials' in new XMLHttpRequest() ) ? XMLHttpRequest :
    ( typeof XDomainRequest !== 'undefined' ) ? XDomainRequest : null;

var afterModuleExecute = function ( name ) {
    var info = moduleInfo[ name ],
        callbacks = info.callbacks,
        loader = NS.loader,
        i, l, callback;

    if ( loader.fire ) {
        NS.loader.fire( 'loader:didLoadModule', { module: name } );
    } else if ( NS.meta ) {
        NS.extend( loader, NS.Events );
        afterModuleExecute = afterModuleExecute.invokeInRunLoop();
    }
    info.status = EXECUTED;

    if ( callbacks ) {
        for ( i = 0, l = callbacks.length; i < l; i += 1 ) {
            callback = callbacks[i];
            if ( !( callback.refCount -= 1 ) ) {
                callback.fn.call( callback.bind );
            }
        }
    }
    info.callbacks = null;
};

var executeModule = function ( name ) {
    // If inside an event handler from a different context (i.e. an
    // iframe), Opera will sometimes get into a weird internal state
    // if you execute the code immediately, so delay to the next
    // event loop.
    var info = moduleInfo[ name ],
        data = info.data;
    setTimeout( function () {
        NS.execute( data );
        afterModuleExecute( name, info );
    }, 0 );
    info.data = null;
};

var checkAndExecuteModule = function ( name ) {
    var info = moduleInfo[ name ];
    if ( info.status === LOADED ) {
        info.status = (LOADED|WILL_EXECUTE);
        if ( info.dependencies ) {
            require( info.dependencies, function () {
                executeModule( name );
            });
        } else {
             executeModule( name );
        }
    }
};

var moduleDidLoad = function ( name, data ) {
    var info = moduleInfo[ name ],
        currentStatus = info.status;

    info.data = data;
    info.status = LOADED;

    if ( currentStatus & WILL_EXECUTE ) {
        checkAndExecuteModule( name );
    }
};

// Loads text, but does not parse/execute unless executeOnLoad is set.
var load = function ( name, executeOnLoad ) {
    var info = moduleInfo[ name ],
        src = info.src,
        status = info.status,
        loader = NS.loader,
        useScriptTag = !CORSRequest || ( loader.debug && !window.ie ),
        data, doc, script, xhr, send, wait;

    if ( useScriptTag && !executeOnLoad ) { return; }

    if ( status === UNREQUESTED ) {
        // Set new status
        info.status = executeOnLoad ? (LOADING|WILL_EXECUTE) : LOADING;
        if ( executeOnLoad && loader.fire ) {
            loader.fire( 'loader:willLoadModule', { module: name } );
        }

        // Check local storage for module data
        if ( loader.cacheModules ) {
            try {
                data = ls.getItem( LS_PREFIX + name );
                if ( data ) {
                    if ( ls.getItem( LS_V_PREFIX + name ) === src ) {
                        moduleDidLoad( name, data );
                        return;
                    } else {
                        ls.removeItem( LS_PREFIX + name );
                        ls.removeItem( LS_V_PREFIX + name );
                    }
                }
            } catch ( error ) {}
        }

        // If not found, request.
        if ( useScriptTag ) {
            doc = document;
            script = doc.createElement( 'script' );
            script.type = 'text/javascript';
            script.charset = 'utf-8';
            script.async = false;
            script.onload = function () {
                script.onload = null;
                script.parentNode.removeChild( script );
                afterModuleExecute( name, info );
            };
            script.src = src;
            doc.documentElement.firstChild.appendChild( script );
        } else {
            xhr = new CORSRequest();
            send = function () {
                xhr.open( 'GET', src );
                xhr.send();
            };
            wait = 1000;
            xhr.onload = function () {
                xhr.onload = xhr.onerror = null;
                var data = this.responseText +
                    '\n//@ sourceURL=' + src;
                if ( loader.cacheModules ) {
                    try {
                        ls.setItem( LS_V_PREFIX + name, src );
                        ls.setItem( LS_PREFIX + name, data );
                    } catch ( error ) {}
                }
                moduleDidLoad( name, data );
            };
            xhr.onerror = function () {
                setTimeout( send, wait = Math.min( wait * 2, 32000 ) );
            };
            // IE randomly aborts some requests if these handlers aren't set.
            xhr.onprogress = function () {};
            xhr.ontimeout = function () {};
            setTimeout( send, 0 );
        }
    } else if ( executeOnLoad && !( status & (WILL_EXECUTE|EXECUTED) ) ) {
        if ( loader.fire ) {
            loader.fire( 'loader:willLoadModule', { module: name } );
        }
        if ( status & LOADING ) {
            info.status = (LOADING|WILL_EXECUTE);
        } else {
            checkAndExecuteModule( name );
        }
    }
};

require = function ( modules, fn, bind ) {
    if ( !( modules instanceof Array ) ) {
         modules = [ modules ];
    }

    var allLoaded = true,
        l = modules.length,
        module, info, dependencies, waitObj;

    while ( l-- ) {
        module = modules[l];
        info = moduleInfo[ module ];

        if ( info.status !== EXECUTED ) {
            allLoaded = false;

            // Push callback onto stack for module
            if ( fn ) {
                if ( !waitObj ) {
                    waitObj = {
                        fn: fn,
                        bind: bind,
                        refCount: 0
                    };
                }
                waitObj.refCount += 1;
                if ( !info.callbacks ) {
                    info.callbacks = [];
                }
                info.callbacks.push( waitObj );
            }

            // Load module dependencies
            if ( dependencies = info.dependencies ) {
                require( dependencies );
            }

            // Load this module
            load( module, true );
        }
    }

    // Everything already loaded, synchronously callback the fn.
    if ( allLoaded && fn ) { fn.call( bind ); }

    return allLoaded;
};

/*
    Event: loader:willLoadModule

    This event is fired immediately before a new module is requested from
    the server.
*/

/*
    Event: loader:didLoadModule

    This event is fired immediately after a new module finishes
    loading, before any waiting require() functions are called.
*/
NS.loader = {
    debug: false,
    cacheModules: false,

    register: function ( name, info ) {
        if ( !info.status ) {
            info.status = info.src ? UNREQUESTED : LOADED;
        }
        moduleInfo[ name ] = info;
        return this;
    },

    prefetch: function ( name ) {
        var info = moduleInfo[ name ],
            dependencies = info.dependencies,
            l = dependencies ? dependencies.length : 0;
        while ( l-- ) {
            load( dependencies[l] );
        }
        load( name );
        return this;
    },
    // Requires all modules listed.
    // Returns whether loaded.
    // Presuming all dependency chains resolved.
    require: require
};
NS.require = require;

}( this.O || ( this.O = {} ), XMLHttpRequest ) );

O.execute = ( function ( global ) {
    /*jshint evil: true */
    var isGlobal = function ( original, Object ) {
        try {
            // Indirect eval is our preferred method for execution in the global
            // scope. But we need to check it works correctly in the current
            // engine:
            // 1. Does `Object` resolve to a local variable, or to the global,
            //    built-in `Object` reference?
            // 2. Is the this parameter bound correctly to the global object
            //    when the eval code is strict (Opera bug)?
            return ( ( 1, eval )( 'Object' ) === original ) &&
                ( ( 1, eval )( '"use strict";this' ) === global );
        }
        catch ( error ) {
            // If indirect eval errors out (as allowed per ES3), then just bail
            // out with `false`
            return false;
        }
    }( Object, 1 );

    // Due to a bug in FF3, we can't just make this the O.execute method, as
    // it will incorrectly bind `this` to the `O` object. But if we call it as a
    // function instead of a method, `this` is set to the global object.
    var evaluate = function ( code ) {
        ( 1, eval )( code );
    };
    /*jshint evil: false */

    return isGlobal ? function ( code ) {
        evaluate( code );
    } : window.execScript || function ( code ) {
        var doc = document,
            head = doc.documentElement.firstChild,
            script = doc.createElement( 'script' );
        script.type = 'text/javascript';
        script.charset = 'utf-8';
        script.text = code;
        head.appendChild( script );
        head.removeChild( script );
    };
}( this ) );
<commit_msg>Make module info public in Loader.<commit_after>// -------------------------------------------------------------------------- \\
// File: Loader.js                                                            \\
// Module: Loader                                                             \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2012 Opera Software ASA. All rights reserved.              \\
// -------------------------------------------------------------------------- \\

/*global window, document, setTimeout, XMLHttpRequest, XDomainRequest,
         localStorage */

"use strict";

( function ( NS, XMLHttpRequest ) {

/*
    Object: O.Loader

    The Loader class handles loading in modules as and when they're
    needed.
*/
var UNREQUESTED = 0,
    LOADING = 1,
    LOADED = 2,
    WILL_EXECUTE = 4,
    EXECUTED = 8;

var ls = localStorage;
var LS_PREFIX = 'OResource-';
var LS_V_PREFIX = 'OResource-v-';

var moduleInfo = {};
var require;

var CORSRequest =
    ( 'withCredentials' in new XMLHttpRequest() ) ? XMLHttpRequest :
    ( typeof XDomainRequest !== 'undefined' ) ? XDomainRequest : null;

var afterModuleExecute = function ( name ) {
    var info = moduleInfo[ name ],
        callbacks = info.callbacks,
        loader = NS.loader,
        i, l, callback;

    if ( loader.fire ) {
        NS.loader.fire( 'loader:didLoadModule', { module: name } );
    } else if ( NS.meta ) {
        NS.extend( loader, NS.Events );
        afterModuleExecute = afterModuleExecute.invokeInRunLoop();
    }
    info.status = EXECUTED;

    if ( callbacks ) {
        for ( i = 0, l = callbacks.length; i < l; i += 1 ) {
            callback = callbacks[i];
            if ( !( callback.refCount -= 1 ) ) {
                callback.fn.call( callback.bind );
            }
        }
    }
    info.callbacks = null;
};

var executeModule = function ( name ) {
    // If inside an event handler from a different context (i.e. an
    // iframe), Opera will sometimes get into a weird internal state
    // if you execute the code immediately, so delay to the next
    // event loop.
    var info = moduleInfo[ name ],
        data = info.data;
    setTimeout( function () {
        NS.execute( data );
        afterModuleExecute( name, info );
    }, 0 );
    info.data = null;
};

var checkAndExecuteModule = function ( name ) {
    var info = moduleInfo[ name ];
    if ( info.status === LOADED ) {
        info.status = (LOADED|WILL_EXECUTE);
        if ( info.dependencies ) {
            require( info.dependencies, function () {
                executeModule( name );
            });
        } else {
             executeModule( name );
        }
    }
};

var moduleDidLoad = function ( name, data ) {
    var info = moduleInfo[ name ],
        currentStatus = info.status;

    info.data = data;
    info.status = LOADED;

    if ( currentStatus & WILL_EXECUTE ) {
        checkAndExecuteModule( name );
    }
};

// Loads text, but does not parse/execute unless executeOnLoad is set.
var load = function ( name, executeOnLoad ) {
    var info = moduleInfo[ name ],
        src = info.src,
        status = info.status,
        loader = NS.loader,
        useScriptTag = !CORSRequest || ( loader.debug && !window.ie ),
        data, doc, script, xhr, send, wait;

    if ( useScriptTag && !executeOnLoad ) { return; }

    if ( status === UNREQUESTED ) {
        // Set new status
        info.status = executeOnLoad ? (LOADING|WILL_EXECUTE) : LOADING;
        if ( executeOnLoad && loader.fire ) {
            loader.fire( 'loader:willLoadModule', { module: name } );
        }

        // Check local storage for module data
        if ( loader.cacheModules ) {
            try {
                data = ls.getItem( LS_PREFIX + name );
                if ( data ) {
                    if ( ls.getItem( LS_V_PREFIX + name ) === src ) {
                        moduleDidLoad( name, data );
                        return;
                    } else {
                        ls.removeItem( LS_PREFIX + name );
                        ls.removeItem( LS_V_PREFIX + name );
                    }
                }
            } catch ( error ) {}
        }

        // If not found, request.
        if ( useScriptTag ) {
            doc = document;
            script = doc.createElement( 'script' );
            script.type = 'text/javascript';
            script.charset = 'utf-8';
            script.async = false;
            script.onload = function () {
                script.onload = null;
                script.parentNode.removeChild( script );
                afterModuleExecute( name, info );
            };
            script.src = src;
            doc.documentElement.firstChild.appendChild( script );
        } else {
            xhr = new CORSRequest();
            send = function () {
                xhr.open( 'GET', src );
                xhr.send();
            };
            wait = 1000;
            xhr.onload = function () {
                xhr.onload = xhr.onerror = null;
                var data = this.responseText +
                    '\n//@ sourceURL=' + src;
                if ( loader.cacheModules ) {
                    try {
                        ls.setItem( LS_V_PREFIX + name, src );
                        ls.setItem( LS_PREFIX + name, data );
                    } catch ( error ) {}
                }
                moduleDidLoad( name, data );
            };
            xhr.onerror = function () {
                setTimeout( send, wait = Math.min( wait * 2, 32000 ) );
            };
            // IE randomly aborts some requests if these handlers aren't set.
            xhr.onprogress = function () {};
            xhr.ontimeout = function () {};
            setTimeout( send, 0 );
        }
    } else if ( executeOnLoad && !( status & (WILL_EXECUTE|EXECUTED) ) ) {
        if ( loader.fire ) {
            loader.fire( 'loader:willLoadModule', { module: name } );
        }
        if ( status & LOADING ) {
            info.status = (LOADING|WILL_EXECUTE);
        } else {
            checkAndExecuteModule( name );
        }
    }
};

require = function ( modules, fn, bind ) {
    if ( !( modules instanceof Array ) ) {
         modules = [ modules ];
    }

    var allLoaded = true,
        l = modules.length,
        module, info, dependencies, waitObj;

    while ( l-- ) {
        module = modules[l];
        info = moduleInfo[ module ];

        if ( info.status !== EXECUTED ) {
            allLoaded = false;

            // Push callback onto stack for module
            if ( fn ) {
                if ( !waitObj ) {
                    waitObj = {
                        fn: fn,
                        bind: bind,
                        refCount: 0
                    };
                }
                waitObj.refCount += 1;
                if ( !info.callbacks ) {
                    info.callbacks = [];
                }
                info.callbacks.push( waitObj );
            }

            // Load module dependencies
            if ( dependencies = info.dependencies ) {
                require( dependencies );
            }

            // Load this module
            load( module, true );
        }
    }

    // Everything already loaded, synchronously callback the fn.
    if ( allLoaded && fn ) { fn.call( bind ); }

    return allLoaded;
};

/*
    Event: loader:willLoadModule

    This event is fired immediately before a new module is requested from
    the server.
*/

/*
    Event: loader:didLoadModule

    This event is fired immediately after a new module finishes
    loading, before any waiting require() functions are called.
*/
NS.loader = {
    debug: false,
    cacheModules: false,
    modules: moduleInfo,

    register: function ( name, info ) {
        if ( !info.status ) {
            info.status = info.src ? UNREQUESTED : LOADED;
        }
        moduleInfo[ name ] = info;
        return this;
    },

    prefetch: function ( name ) {
        var info = moduleInfo[ name ],
            dependencies = info.dependencies,
            l = dependencies ? dependencies.length : 0;
        while ( l-- ) {
            load( dependencies[l] );
        }
        load( name );
        return this;
    },
    // Requires all modules listed.
    // Returns whether loaded.
    // Presuming all dependency chains resolved.
    require: require
};
NS.require = require;

}( this.O || ( this.O = {} ), XMLHttpRequest ) );

O.execute = ( function ( global ) {
    /*jshint evil: true */
    var isGlobal = function ( original, Object ) {
        try {
            // Indirect eval is our preferred method for execution in the global
            // scope. But we need to check it works correctly in the current
            // engine:
            // 1. Does `Object` resolve to a local variable, or to the global,
            //    built-in `Object` reference?
            // 2. Is the this parameter bound correctly to the global object
            //    when the eval code is strict (Opera bug)?
            return ( ( 1, eval )( 'Object' ) === original ) &&
                ( ( 1, eval )( '"use strict";this' ) === global );
        }
        catch ( error ) {
            // If indirect eval errors out (as allowed per ES3), then just bail
            // out with `false`
            return false;
        }
    }( Object, 1 );

    // Due to a bug in FF3, we can't just make this the O.execute method, as
    // it will incorrectly bind `this` to the `O` object. But if we call it as a
    // function instead of a method, `this` is set to the global object.
    var evaluate = function ( code ) {
        ( 1, eval )( code );
    };
    /*jshint evil: false */

    return isGlobal ? function ( code ) {
        evaluate( code );
    } : window.execScript || function ( code ) {
        var doc = document,
            head = doc.documentElement.firstChild,
            script = doc.createElement( 'script' );
        script.type = 'text/javascript';
        script.charset = 'utf-8';
        script.text = code;
        head.appendChild( script );
        head.removeChild( script );
    };
}( this ) );
<|endoftext|>


<commit_before>// -------------------------------------------------------------------------- \\
// File: UA.js                                                                \\
// Module: UA                                                                 \\
// Requires: Core, Foundation                                                 \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2013 FastMail Pty Ltd. All rights reserved.                \\
// -------------------------------------------------------------------------- \\

/*global navigator, document, window */

"use strict";

/**
    Module: UA

    The UA module contains information about the platform on which the
    application is running.
*/
( function ( NS ) {

var ua = navigator.userAgent.toLowerCase(),
    other = [ 'other', '0' ],
    platform = /ip(?:ad|hone|od)/.test( ua ) ? 'ios' : (
        /mac|win|linux/.exec( navigator.platform.toLowerCase() ) ||
        /android|webos/.exec( ua ) ||
        other
    )[0],
    browser = ( /chrome|opera|safari|firefox|msie/.exec( ua ) || other )[0],
    version = parseFloat(
        ( /(?:version\/|chrome\/|firefox\/|msie\s|os )(\d+(?:[._]\d+)?)/.exec( ua )|| other )[1].replace( '_', '.' )
    ),
    cssPrefixes = {
        chrome: '-webkit-',
        firefox: '-moz-',
        msie: '-ms-',
        opera: '-o-',
        safari: '-webkit-',
        other: '-webkit-'
    },
    cssProps = {};

( function () {
    var el = document.createElement( 'div' ),
        props = {
            'box-shadow': {
                name: 'box-shadow',
                value: '0 0 0 #000'
            },
            transform: {
                name: 'transform',
                value: 'translateX(0)'
            },
            transform3d: {
                name: 'transform',
                value: 'translateZ(0)'
            },
            transition: {
                name: 'transition',
                value: 'all .3s'
            },
            perspective: {
                name: 'perspective',
                value: '1px'
            },
            'user-select': {
                name: 'user-select',
                value: 'none'
            }
        },
        prefix = cssPrefixes[ browser ],
        prop, test, css;

    for ( prop in props ) {
        test = props[ prop ];
        css = test.name + ':' + test.value;
        el.style.cssText = css;
        if ( el.style.length ) {
            cssProps[ prop ] = test.name;
        } else {
            el.style.cssText = prefix + css;
            cssProps[ prop ] = el.style.length ?
                prefix + test.name : null;
        }
    }
    css = cssProps.transition;
    [ 'delay', 'timing', 'duration', 'property' ].forEach( function ( prop ) {
        cssProps[ 'transition-' + prop ] = css ? css + '-' + prop : null;
    });
    el = null;

    // Browser bugs:
    // 1. iOS5 Sometimes fails to transform stuff.
    // 2. Chrome on Windows XP has edge case bugs like
    //    not rendering scroll bars in transformed elements.
    if ( ( platform === 'ios' && version < 6 ) ||
            /windows nt 5.1/.test( ua ) ) {
        cssProps.transform3d = false;
    }
}() );

/**
    Namespace: O.UA

    The O.UA namespace contains information about which browser and platform the
    application is currently running on, and which CSS properties are supported.
*/
NS.UA = {
    /**
        Property: O.UA.platform
        Type: String

        The operating system being run. "mac", "win", "linux" or "ios".
    */
    platform: platform,

    /**
        Property: O.UA.isMac
        Type: Boolean

        True if running on a mac.
    */
    isMac: platform === 'mac',
    /**
        Property: O.UA.isWin
        Type: Boolean

        True if running on windows.
    */
    isWin: platform === 'win',
    /**
        Property: O.UA.isLinux
        Type: Boolean

        True if running on linux.
    */
    isLinux: platform === 'linux',
    /**
        Property: O.UA.isIOS
        Type: Boolean

        True if running on iOS.
    */
    isIOS: platform === 'ios',

    /**
        Property: O.UA.browser
        Type: String

        The browser being run. "chrome", "firefox", "msie" or "opera" or
        "safari".
    */
    browser: browser,
    /**
        Property: O.UA.version
        Type: Number

        The browser version being run. This is a float, and includes the first
        minor revision as well as the major revision. For example, if the user
        is running Opera 12.5, this will be `12.5`, not just `12`.
    */
    version: version,

    /**
        Property: O.UA.chrome
        Type: Number

        If running Chrome, this will be the version number running. Otherwise 0.
    */
    chrome: browser === 'chrome' ? version : 0,
    /**
        Property: O.UA.firefox
        Type: Number

        If running Firefox, this will be the version number running. Otherwise
        0.
    */
    firefox: browser === 'firefox' ? version : 0,
    /**
        Property: O.UA.msie
        Type: Number

        If running Internet Explorer, this will be the version number running.
        Otherwise 0.
    */
    msie: browser === 'msie' ? version : 0,
    /**
        Property: O.UA.opera
        Type: Number

        If running Opera, this will be the version number running. Otherwise 0.
    */
    opera: browser === 'opera' ? version : 0,
    /**
        Property: O.UA.safari
        Type: Number

        If running Safari, this will be the version number running. Otherwise 0.
    */
    safari: browser === 'safari' ? version : 0,
    /**
        Property: O.UA.operaMobile
        Type: Number

        If running Opera Mobile, this will be the version number running.
        Otherwise 0.
    */
    operaMobile: /opera mobi/.test( ua ) ? version : 0,

    /**
        Property: O.UA.operaMini
        Type: Number

        If running Opera Mini, this will be the version number running.
        Otherwise 0.
    */
    operaMini: !!window.operamini ? version : 0,

    /**
        Property: O.UA.cssProps
        Type: Object

        A map of certain CSS property names to the browser-specific CSS property
        name required, or null if the browser does not support the property.

        The following properties are available: box-shadow, float, transform,
        transform3d, transition, transition-delay, transition-duration,
        transition-property and transition-timing.
    */
    cssProps: cssProps,
    /**
        Property: O.UA.cssPrefix
        Type: String

        The CSS prefix to use for this browser.
    */
    cssPrefix: cssPrefixes[ browser ]
};

}( this.O ) );
<commit_msg>Detect support for CSS flex-box<commit_after>// -------------------------------------------------------------------------- \\
// File: UA.js                                                                \\
// Module: UA                                                                 \\
// Requires: Core, Foundation                                                 \\
// Author: Neil Jenkins                                                       \\
// License: © 2010–2013 FastMail Pty Ltd. All rights reserved.                \\
// -------------------------------------------------------------------------- \\

/*global navigator, document, window */

"use strict";

/**
    Module: UA

    The UA module contains information about the platform on which the
    application is running.
*/
( function ( NS ) {

var ua = navigator.userAgent.toLowerCase(),
    other = [ 'other', '0' ],
    platform = /ip(?:ad|hone|od)/.test( ua ) ? 'ios' : (
        /mac|win|linux/.exec( navigator.platform.toLowerCase() ) ||
        /android|webos/.exec( ua ) ||
        other
    )[0],
    browser = ( /chrome|opera|safari|firefox|msie/.exec( ua ) || other )[0],
    version = parseFloat(
        ( /(?:version\/|chrome\/|firefox\/|msie\s|os )(\d+(?:[._]\d+)?)/.exec( ua )|| other )[1].replace( '_', '.' )
    ),
    cssPrefixes = {
        chrome: '-webkit-',
        firefox: '-moz-',
        msie: '-ms-',
        opera: '-o-',
        safari: '-webkit-',
        other: '-webkit-'
    },
    cssProps = {};

( function () {
    var el = document.createElement( 'div' ),
        style = el.style,
        props = {
            'box-shadow': {
                name: 'box-shadow',
                value: '0 0 0 #000'
            },
            transform: {
                name: 'transform',
                value: 'translateX(0)'
            },
            transform3d: {
                name: 'transform',
                value: 'translateZ(0)'
            },
            transition: {
                name: 'transition',
                value: 'all .3s'
            },
            perspective: {
                name: 'perspective',
                value: '1px'
            },
            'user-select': {
                name: 'user-select',
                value: 'none'
            }
        },
        prefix = cssPrefixes[ browser ],
        prop, test, css;

    for ( prop in props ) {
        test = props[ prop ];
        css = test.name + ':' + test.value;
        style.cssText = css;
        if ( style.length ) {
            cssProps[ prop ] = test.name;
        } else {
            style.cssText = prefix + css;
            cssProps[ prop ] = style.length ? prefix + test.name : null;
        }
    }
    style.cssText = 'display:flex';
    if ( style.length ) {
        cssProps.flexbox = 'flex';
    } else {
        style.cssText = 'display:' + prefix + 'flex';
        cssProps.flexbox = el.style.length ? prefix + 'flex' : null;
    }
    css = cssProps.transition;
    [ 'delay', 'timing', 'duration', 'property' ].forEach( function ( prop ) {
        cssProps[ 'transition-' + prop ] = css ? css + '-' + prop : null;
    });
    el = null;
    style = null;

    // Browser bugs:
    // 1. iOS5 Sometimes fails to transform stuff.
    // 2. Chrome on Windows XP has edge case bugs like
    //    not rendering scroll bars in transformed elements.
    if ( ( platform === 'ios' && version < 6 ) ||
            /windows nt 5.1/.test( ua ) ) {
        cssProps.transform3d = false;
    }
}() );

/**
    Namespace: O.UA

    The O.UA namespace contains information about which browser and platform the
    application is currently running on, and which CSS properties are supported.
*/
NS.UA = {
    /**
        Property: O.UA.platform
        Type: String

        The operating system being run. "mac", "win", "linux" or "ios".
    */
    platform: platform,

    /**
        Property: O.UA.isMac
        Type: Boolean

        True if running on a mac.
    */
    isMac: platform === 'mac',
    /**
        Property: O.UA.isWin
        Type: Boolean

        True if running on windows.
    */
    isWin: platform === 'win',
    /**
        Property: O.UA.isLinux
        Type: Boolean

        True if running on linux.
    */
    isLinux: platform === 'linux',
    /**
        Property: O.UA.isIOS
        Type: Boolean

        True if running on iOS.
    */
    isIOS: platform === 'ios',

    /**
        Property: O.UA.browser
        Type: String

        The browser being run. "chrome", "firefox", "msie" or "opera" or
        "safari".
    */
    browser: browser,
    /**
        Property: O.UA.version
        Type: Number

        The browser version being run. This is a float, and includes the first
        minor revision as well as the major revision. For example, if the user
        is running Opera 12.5, this will be `12.5`, not just `12`.
    */
    version: version,

    /**
        Property: O.UA.chrome
        Type: Number

        If running Chrome, this will be the version number running. Otherwise 0.
    */
    chrome: browser === 'chrome' ? version : 0,
    /**
        Property: O.UA.firefox
        Type: Number

        If running Firefox, this will be the version number running. Otherwise
        0.
    */
    firefox: browser === 'firefox' ? version : 0,
    /**
        Property: O.UA.msie
        Type: Number

        If running Internet Explorer, this will be the version number running.
        Otherwise 0.
    */
    msie: browser === 'msie' ? version : 0,
    /**
        Property: O.UA.opera
        Type: Number

        If running Opera, this will be the version number running. Otherwise 0.
    */
    opera: browser === 'opera' ? version : 0,
    /**
        Property: O.UA.safari
        Type: Number

        If running Safari, this will be the version number running. Otherwise 0.
    */
    safari: browser === 'safari' ? version : 0,
    /**
        Property: O.UA.operaMobile
        Type: Number

        If running Opera Mobile, this will be the version number running.
        Otherwise 0.
    */
    operaMobile: /opera mobi/.test( ua ) ? version : 0,

    /**
        Property: O.UA.operaMini
        Type: Number

        If running Opera Mini, this will be the version number running.
        Otherwise 0.
    */
    operaMini: !!window.operamini ? version : 0,

    /**
        Property: O.UA.cssProps
        Type: Object

        A map of certain CSS property names to the browser-specific CSS property
        name required, or null if the browser does not support the property.

        The following properties are available: box-shadow, float, transform,
        transform3d, transition, transition-delay, transition-duration,
        transition-property and transition-timing.
    */
    cssProps: cssProps,
    /**
        Property: O.UA.cssPrefix
        Type: String

        The CSS prefix to use for this browser.
    */
    cssPrefix: cssPrefixes[ browser ]
};

}( this.O ) );
<|endoftext|>