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

/**
  A Property is a high-level instance variable.

  Properties contain more information than typical variable declarations.
  Such as: label, help text, pre/post-set callbacks, default value,
  value factory, units, etc.

  A sub-class or refinement can include a partial Property definition which
  will override or add meta-information to the Property.
**/
foam.CLASS({
  package: 'foam.core',
  name: 'Property',
  extends: 'FObject',

  properties: [
    {
      name: 'name',
      required: true
    },
    {
      name: 'label',
      // If not provided, it defaults to the name "labelized".
      expression: function(name) { return foam.String.labelize(name); }
    },

    /* User-level help. Could/should appear in GUI's as online help. */
    'help',

    /* Hidden properties to not appear in GUI's by default. */
    { class: 'Boolean', name: 'hidden' },

    /**
      The default-value of this property.
      A property which has never been set or has been cleared
      by setting it to 'undefined' or cleared with clearProperty()
      will have the default value.
    */
    'value',

    /**
      A factory is a function which initializes the property value
      when accessed for the first time, if not already set.
    */
    'factory',

    /**
      A function of the form:
        Object function(oldValue, newValue)
      adapt is called whenver the property is set. It's intended
      to adapt the value to the appropriate type if required.
      Adapt must return a value. It can return newValue unchanged
      if it was already the appropriate type.
    */
    'adapt',

    /**
      A function of the form:
        Object function(oldValue, newValue)
      preSet is called before the propery's value is updated.
      It can veto the value change by returning a different newValue
      (including returning oldValue to leave the property unchanged).
    */
    'preSet',

    /**
      A function of the form:
        void function(oldValue, newValue) throws Exception
      assertValue can validate newValue and throw an exception if it's an
      invalid value.
    */
    'assertValue',

    /**
      A function of the form:
        void function(oldValue, newValue)
      postSet is called after the Property's value has been updated.
    */
    'postSet',

    /**
      A dynamic function which defines this Property's value.
      Similar to 'factory', except that the function takes arguments
      which are named the same as other properties of this object.
      Whenever the values of any of the argument properties change,
      the value of this Property is invalidated. Like a regular factory,
      an invalidated property will be recalculated by calling the provided
      expression function when accessed. This makes expressions very efficient
      because the value is only recomputed when actually needed.
    */
    'expression',

    /**
      A getter function which completely replaces the normal
      Property getter process. Whenever the property is accessed, getter is
      called and its value is returned.
    */
    'getter',

    /**
      A setter function which completely replaces the normal
      Property setter process. Whenever the property is set, setter is
      called.
      A function of the form:
        void function(newValue)
    */
    'setter',

    /**
      A final Property can only be set once.
      After being set, its value is final (read-only).
    */
    'final',

    /**
      A required Property can not be set to null, undefined, 0 or "".
     */
    'required',

    [
      /**
        Compare two values taken from this property.

Used by Property.compare().
        It is a property rather than a method so that it can be configured
        without subclassing.
      */
      'comparePropertyValues',
      function(o1, o2) { return foam.util.compare(o1, o2); }
    ],

    {
      // Makes Properties useful as map functions.
      name: 'f',
      factory: function() {
        var name = this.name;
        return function f(o) { return o[name]; }
      }
    },

    {
      // Makes Properties useful as comparators.
      name: 'compare',
      factory: function() {
        var comparePropertyValues = this.comparePropertyValues;
        var f = this.f;
        return function compare(o1, o2) {
          return comparePropertyValues(f(o1), f(o2));
        }
      }
    }
  ],

  methods: [
    /**
      Handle overriding of Property definition from parent class by
      copying undefined values from parent Property, if it exists.
    */
    function installInClass(c) {
      var prop = this;
      var superProp = c.__proto__.getAxiomByName(prop.name);

      if ( superProp ) {
        prop = prop.cls_ === foam.core.Property ?
          superProp.clone().copyFrom(prop) :
          prop.cls_.create().copyFrom(superProp).copyFrom(this) ;

        c.axiomMap_[prop.name] = prop;
      }

      var cName = foam.String.constantize(prop.name);
      var prev = c[cName];

      // Detect constant name collisions
      if ( prev && prev.name !== prop.name ) {
        throw 'Class constant conflict: ' +
          c.id + '.' + cName + ' from: ' + prop.name + ' and ' + prev.name;
      }

      c[cName] = prop;
    },

    /**
      Install a property onto a prototype from a Property definition.
      (Property is 'this').
    */
    function installInProto(proto) {
      // Take Axiom from class rather than using 'this' directly,
      // since installInClass() may have created a modified version
      // to inherit Property Properties from a super-Property.
      var prop        = proto.cls_.getAxiomByName(this.name);
      var name        = prop.name;
      var adapt       = prop.adapt
      var assertValue = prop.assertValue;
      var preSet      = prop.preSet;
      var postSet     = prop.postSet;
      var factory     = prop.factory;
      var value       = prop.value;
      var hasValue    = typeof value !== 'undefined';
      var slotName    = name + '$';
      var isFinal     = prop.final;
      var eFactory    = this.exprFactory(prop.expression);

      // This costs us about 4% of our boot time.
      // If not in debug mode we should share implementations like in F1.
      // TODO: doc
      Object.defineProperty(proto, slotName, {
        get: function propertySlotGetter() {
          return prop.toSlot(this);
        },
        set: function propertySlotSetter(slot2) {
          prop.toSlot(this).link(slot2);
        },
        configurable: true,
        enumerable: false
      });

      // TODO: doc
      var getter =
        prop.getter ? prop.getter :
        factory ? function factoryGetter() {
          return this.hasOwnProperty(name) ?
            this.instance_[name] :
            this[name] = factory.call(this) ;
        } :
        eFactory ? function eFactoryGetter() {
          return this.hasOwnProperty(name) ? this.instance_[name]   :
                 this.hasOwnPrivate_(name) ? this.getPrivate_(name) :
                 this.setPrivate_(name, eFactory.call(this)) ;
        } :
        hasValue ? function valueGetter() {
          var v = this.instance_[name];
          return typeof v !== 'undefined' ? v : value ;
        } :
        function simpleGetter() { return this.instance_[name]; };

      var setter = prop.setter ? prop.setter :
        function propSetter(newValue) {
          // ???: Should clearProperty() call set(undefined)?
          if ( newValue === undefined ) {
            this.clearProperty(name);
            return;
          }

          // Get old value but avoid triggering factory if present
          // TODO: expand
          var oldValue =
            factory  ? ( this.hasOwnProperty(name) ? this[name] : undefined ) :
            eFactory ? ( this.hasOwnPrivate_(name) || this.hasOwnProperty(name) ? this[name] : undefined ) :
            this[name] ;

          if ( adapt )  newValue = adapt.call(this, oldValue, newValue, prop);

          if ( assertValue ) assertValue.call(this, newValue, prop);

          if ( preSet ) newValue = preSet.call(this, oldValue, newValue, prop);

          // ???: Should newValue === undefined check go here instead?

          this.instance_[name] = newValue;

          if ( isFinal ) {
            Object.defineProperty(this, name, {
              value: newValue,
              writable: false,
              configurable: true // ???: is this needed?
            });
          }

          this.pubPropertyChange_(prop, oldValue, newValue);

          // FUTURE: pub to a global topic to support dynamic()

          if ( postSet ) postSet.call(this, oldValue, newValue, prop);
        };

      Object.defineProperty(proto, name, {
        get: getter,
        set: setter,
        configurable: true
      });
    },

    /* Validate an object which has this Property. */
    function validateInstance(o) {
      if ( this.required && ! o[this.name] ) {
        throw 'Required property ' +
            o.cls_.id + '.' + this.name +
            ' not defined.';
      }
    },

    /**
     * Create a factory function from an expression function.
     * Function arguments are validated in debug.js.
     **/
    function exprFactory(e) {
      if ( ! e ) return null;

      var argNames = foam.Function.argsArray(e);
      var name     = this.name;

      // FUTURE: determine how often the value is being invalidated,
      // and if it's happening often, then don't unsubscribe.
      return function() {
        var self = this;
        var args = new Array(argNames.length);
        var subs = [];
        var l    = function() {
          if ( ! self.hasOwnProperty(name) ) self.clearPrivate_(name);
          for ( var i = 0 ; i < subs.length ; i++ ) subs[i].destroy();
        };
        for ( var i = 0 ; i < argNames.length ; i++ ) {
          subs.push(this.slot(argNames[i]).sub(l));
          args[i] = this[argNames[i]];
        }
        return e.apply(this, args);
      };
    },

    /** Returns a developer-readable description of this Property. **/
    function toString() { return this.name; },

    /** Flyweight getter for this Property. **/
    function get(o) { return o[this.name]; },

    /** Flyweight setter for this Property. **/
    function set(o, value) {
      o[this.name] = value;
      return this;
    },

    function exportAs(obj) {
      /** Export obj.name$ instead of just obj.name. **/
      return this.toSlot(obj);
    },

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

      if ( ! slot ) {
        slot = foam.core.internal.PropertySlot.create();
        slot.obj  = obj;
        slot.prop = this;
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function cloneProperty(
      /* any // The value to clone */         value,
      /* object // Add values to this map to
         have them installed on the clone. */ cloneMap
    ) {
      /** Override to provide special deep cloning behavior. */
      cloneMap[this.name] = ( value && value.clone ) ? value.clone() : value;
    }
  ]
});

/**
  A Simple Property skips the regular FOAM Property getter/setter/instance_
  mechanism. In gets installed on the CLASS as a Property constant, but isn't
  added to the prototype at all. From this point of view, it's mostly just for
  documentation. Simple Properties are used only in special cases to maximize
  performance and/or minimize memory use.
  Used for MDAO indices and Slots.

  USE WITH EXTREME CAUTION (OR NOT AT ALL).
*/
foam.CLASS({
  package: 'foam.core',
  name: 'Simple',
  extends: 'Property',

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

/**
  A Property is a high-level instance variable.

  Properties contain more information than typical variable declarations.
  Such as: label, help text, pre/post-set callbacks, default value,
  value factory, units, etc.

  When setting a Propery's value, the callback order is:
    1. adapt()
    2. assertValue()
    3. preSet()
       value updated
       property change event fired
    4. postSet()

   Unless the user has provided a customer 'setter', in which case the order is
     1. setter()

  A sub-class or refinement can include a partial Property definition which
  will override or add meta-information to the Property.
**/
foam.CLASS({
  package: 'foam.core',
  name: 'Property',
  extends: 'FObject',

  properties: [
    {
      name: 'name',
      required: true
    },
    {
      name: 'label',
      // If not provided, it defaults to the name "labelized".
      expression: function(name) { return foam.String.labelize(name); }
    },

    /* User-level help. Could/should appear in GUI's as online help. */
    'help',

    /* Hidden properties to not appear in GUI's by default. */
    { class: 'Boolean', name: 'hidden' },

    /**
      The default-value of this property.
      A property which has never been set or has been cleared
      by setting it to 'undefined' or cleared with clearProperty()
      will have the default value.
    */
    'value',

    /**
      A factory is a function which initializes the property value
      when accessed for the first time, if not already set.
    */
    'factory',

    /**
      A function of the form:
        Object function(oldValue, newValue)
      adapt is called whenver the property is set. It's intended
      to adapt the value to the appropriate type if required.
      Adapt must return a value. It can return newValue unchanged
      if it was already the appropriate type.
    */
    'adapt',

    /**
      A function of the form:
        Object function(oldValue, newValue)
      preSet is called before the propery's value is updated.
      It can veto the value change by returning a different newValue
      (including returning oldValue to leave the property unchanged).
    */
    'preSet',

    /**
      A function of the form:
        void function(oldValue, newValue) throws Exception
      assertValue can validate newValue and throw an exception if it's an
      invalid value.
    */
    'assertValue',

    /**
      A function of the form:
        void function(oldValue, newValue)
      postSet is called after the Property's value has been updated.
    */
    'postSet',

    /**
      A dynamic function which defines this Property's value.
      Similar to 'factory', except that the function takes arguments
      which are named the same as other properties of this object.
      Whenever the values of any of the argument properties change,
      the value of this Property is invalidated. Like a regular factory,
      an invalidated property will be recalculated by calling the provided
      expression function when accessed. This makes expressions very efficient
      because the value is only recomputed when actually needed.
    */
    'expression',

    /**
      A getter function which completely replaces the normal
      Property getter process. Whenever the property is accessed, getter is
      called and its value is returned.
    */
    'getter',

    /**
      A setter function which completely replaces the normal
      Property setter process. Whenever the property is set, setter is
      called.
      A function of the form:
        void function(newValue)
    */
    'setter',

    /**
      A final Property can only be set once.
      After being set, its value is final (read-only).
    */
    'final',

    /**
      A required Property can not be set to null, undefined, 0 or "".
     */
    'required',

    [
      /**
        Compare two values taken from this property.

Used by Property.compare().
        It is a property rather than a method so that it can be configured
        without subclassing.
      */
      'comparePropertyValues',
      function(o1, o2) { return foam.util.compare(o1, o2); }
    ],

    {
      // Makes Properties useful as map functions.
      name: 'f',
      factory: function() {
        var name = this.name;
        return function f(o) { return o[name]; }
      }
    },

    {
      // Makes Properties useful as comparators.
      name: 'compare',
      factory: function() {
        var comparePropertyValues = this.comparePropertyValues;
        var f = this.f;
        return function compare(o1, o2) {
          return comparePropertyValues(f(o1), f(o2));
        }
      }
    }
  ],

  methods: [
    /**
      Handle overriding of Property definition from parent class by
      copying undefined values from parent Property, if it exists.
    */
    function installInClass(c) {
      var prop = this;
      var superProp = c.__proto__.getAxiomByName(prop.name);

      if ( superProp ) {
        prop = prop.cls_ === foam.core.Property ?
          superProp.clone().copyFrom(prop) :
          prop.cls_.create().copyFrom(superProp).copyFrom(this) ;

        c.axiomMap_[prop.name] = prop;
      }

      var cName = foam.String.constantize(prop.name);
      var prev = c[cName];

      // Detect constant name collisions
      if ( prev && prev.name !== prop.name ) {
        throw 'Class constant conflict: ' +
          c.id + '.' + cName + ' from: ' + prop.name + ' and ' + prev.name;
      }

      c[cName] = prop;
    },

    /**
      Install a property onto a prototype from a Property definition.
      (Property is 'this').
    */
    function installInProto(proto) {
      // Take Axiom from class rather than using 'this' directly,
      // since installInClass() may have created a modified version
      // to inherit Property Properties from a super-Property.
      var prop        = proto.cls_.getAxiomByName(this.name);
      var name        = prop.name;
      var adapt       = prop.adapt
      var assertValue = prop.assertValue;
      var preSet      = prop.preSet;
      var postSet     = prop.postSet;
      var factory     = prop.factory;
      var value       = prop.value;
      var hasValue    = typeof value !== 'undefined';
      var slotName    = name + '$';
      var isFinal     = prop.final;
      var eFactory    = this.exprFactory(prop.expression);

      // This costs us about 4% of our boot time.
      // If not in debug mode we should share implementations like in F1.
      // TODO: doc
      Object.defineProperty(proto, slotName, {
        get: function propertySlotGetter() {
          return prop.toSlot(this);
        },
        set: function propertySlotSetter(slot2) {
          prop.toSlot(this).link(slot2);
        },
        configurable: true,
        enumerable: false
      });

      // TODO: doc
      var getter =
        prop.getter ? prop.getter :
        factory ? function factoryGetter() {
          return this.hasOwnProperty(name) ?
            this.instance_[name] :
            this[name] = factory.call(this) ;
        } :
        eFactory ? function eFactoryGetter() {
          return this.hasOwnProperty(name) ? this.instance_[name]   :
                 this.hasOwnPrivate_(name) ? this.getPrivate_(name) :
                 this.setPrivate_(name, eFactory.call(this)) ;
        } :
        hasValue ? function valueGetter() {
          var v = this.instance_[name];
          return typeof v !== 'undefined' ? v : value ;
        } :
        function simpleGetter() { return this.instance_[name]; };

      var setter = prop.setter ? prop.setter :
        function propSetter(newValue) {
          // ???: Should clearProperty() call set(undefined)?
          if ( newValue === undefined ) {
            this.clearProperty(name);
            return;
          }

          // Get old value but avoid triggering factory if present
          // TODO: expand
          var oldValue =
            factory  ? ( this.hasOwnProperty(name) ? this[name] : undefined ) :
            eFactory ? ( this.hasOwnPrivate_(name) || this.hasOwnProperty(name) ? this[name] : undefined ) :
            this[name] ;

          if ( adapt )  newValue = adapt.call(this, oldValue, newValue, prop);

          if ( assertValue ) assertValue.call(this, newValue, prop);

          if ( preSet ) newValue = preSet.call(this, oldValue, newValue, prop);

          // ???: Should newValue === undefined check go here instead?

          this.instance_[name] = newValue;

          if ( isFinal ) {
            Object.defineProperty(this, name, {
              value: newValue,
              writable: false,
              configurable: true // ???: is this needed?
            });
          }

          this.pubPropertyChange_(prop, oldValue, newValue);

          // FUTURE: pub to a global topic to support dynamic()

          if ( postSet ) postSet.call(this, oldValue, newValue, prop);
        };

      Object.defineProperty(proto, name, {
        get: getter,
        set: setter,
        configurable: true
      });
    },

    /* Validate an object which has this Property. */
    function validateInstance(o) {
      if ( this.required && ! o[this.name] ) {
        throw 'Required property ' +
            o.cls_.id + '.' + this.name +
            ' not defined.';
      }
    },

    /**
     * Create a factory function from an expression function.
     * Function arguments are validated in debug.js.
     **/
    function exprFactory(e) {
      if ( ! e ) return null;

      var argNames = foam.Function.argsArray(e);
      var name     = this.name;

      // FUTURE: determine how often the value is being invalidated,
      // and if it's happening often, then don't unsubscribe.
      return function() {
        var self = this;
        var args = new Array(argNames.length);
        var subs = [];
        var l    = function() {
          if ( ! self.hasOwnProperty(name) ) self.clearPrivate_(name);
          for ( var i = 0 ; i < subs.length ; i++ ) subs[i].destroy();
        };
        for ( var i = 0 ; i < argNames.length ; i++ ) {
          subs.push(this.slot(argNames[i]).sub(l));
          args[i] = this[argNames[i]];
        }
        return e.apply(this, args);
      };
    },

    /** Returns a developer-readable description of this Property. **/
    function toString() { return this.name; },

    /** Flyweight getter for this Property. **/
    function get(o) { return o[this.name]; },

    /** Flyweight setter for this Property. **/
    function set(o, value) {
      o[this.name] = value;
      return this;
    },

    function exportAs(obj) {
      /** Export obj.name$ instead of just obj.name. **/
      return this.toSlot(obj);
    },

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

      if ( ! slot ) {
        slot = foam.core.internal.PropertySlot.create();
        slot.obj  = obj;
        slot.prop = this;
        obj.setPrivate_(slotName, slot);
      }

      return slot;
    },

    function cloneProperty(
      /* any // The value to clone */         value,
      /* object // Add values to this map to
         have them installed on the clone. */ cloneMap
    ) {
      /** Override to provide special deep cloning behavior. */
      cloneMap[this.name] = ( value && value.clone ) ? value.clone() : value;
    }
  ]
});

/**
  A Simple Property skips the regular FOAM Property getter/setter/instance_
  mechanism. In gets installed on the CLASS as a Property constant, but isn't
  added to the prototype at all. From this point of view, it's mostly just for
  documentation. Simple Properties are used only in special cases to maximize
  performance and/or minimize memory use.
  Used for MDAO indices and Slots.

  USE WITH EXTREME CAUTION (OR NOT AT ALL).
*/
foam.CLASS({
  package: 'foam.core',
  name: 'Simple',
  extends: 'Property',

  methods: [
    function installInProto(proto) {}
  ]
});
<|endoftext|>


<commit_before>import * as GL from './GL';
import {
	mat4,
} from 'gl-matrix';
import {
	log,
	warn,
} from 'utils/Console';
import {
	WEBGL_CONTEXT,
	WEBGL2_CONTEXT,
	RENDERER_DEFAULT_CONTEXT,
	RENDERER_DEFAULT_WIDTH,
	RENDERER_DEFAULT_HEIGHT,
	LINE_DEFAULT_WIDTH,
} from 'core/Constants';
import * as Capabilities from 'core/Capabilities';
import * as UniformBuffers from 'core/UniformBuffers';
import Detect from 'utils/Detect';
const config = require('../../package.json');

let gl;

export default class Renderer {
	constructor(settings = {}) {
		// Default renderer settings
		const defaults = {
			width: RENDERER_DEFAULT_WIDTH,
			height: RENDERER_DEFAULT_HEIGHT,
			ratio: RENDERER_DEFAULT_WIDTH / RENDERER_DEFAULT_HEIGHT,
			preserveDrawingBuffer: false,
			pixelRatio: 1,
			prefferedContext: RENDERER_DEFAULT_CONTEXT,
		};

		window.Capabilities = Capabilities;

		// Apply defaults
		Object.assign(this, defaults, settings);

		// Create canvas
		this.canvas = document.createElement('canvas');
		this.canvas.width = this.width;
		this.canvas.height = this.height;

		// Try initialising gl
		const attributes = {
			preserveDrawingBuffer: this.preserveDrawingBuffer,
		};

		const detect = Detect();

		if (detect) {
			if (detect.webgl2 && this.prefferedContext === WEBGL2_CONTEXT) {
				gl = this.canvas.getContext('webgl2', attributes);
			} else if (this.prefferedContext === WEBGL_CONTEXT) {
				gl = this.canvas.getContext('webgl', attributes) || this.canvas.getContext('experimental-webgl');
			} else {
				warn(`${this.prefferedContext} not supported`);
				return;
			}
			GL.set(gl, this.prefferedContext);
		} else {
			warn('Webgl not supported');
			return;
		}

		log(`%c${config.name} ${config.version} webgl${GL.webgl2 ? 2 : ''}`, 'padding: 1px; background: #222; color: #ff00ff');

		gl = GL.get();

		// Log Capabilities of gpu
		Capabilities.set(gl);

		// Setup global uniform buffers
		if (GL.webgl2) {
			UniformBuffers.setup();
		}

		// log('capabilities', Capabilities.capabilities);
		// log('extensions', Capabilities.extensions);

		gl.clearColor(0.0, 0.0, 0.0, 1.0);
		gl.enable(gl.DEPTH_TEST);
	}

	setSize(width, height) {
		const newWidth = width * this.pixelRatio;
		const newHeight = height * this.pixelRatio;

		if (newWidth !== this.width || newHeight !== this.height) {
			this.width = width * this.pixelRatio;
			this.height = height * this.pixelRatio;
			this.ratio = this.width / this.height;

			this.canvas.width = this.width;
			this.canvas.height = this.height;

			this.canvas.style.width = `${width}px`;
			this.canvas.style.height = `${height}px`;
		}
	}

	setDevicePixelRatio(ratio = 1) {
		this.pixelRatio = ratio || 1;
		this.setSize(this.width, this.height);
	}

	_reset(gl) {
		// Line width
		gl.lineWidth(LINE_DEFAULT_WIDTH);
	}

	_drawObjects(scene, projectionMatrix, modelViewMatrix, camera) {
		if (GL.webgl2) {
			// Update global uniform buffers
			UniformBuffers.updateProjectionView(gl, projectionMatrix, modelViewMatrix);
		}

		// Render the scene objects
		scene.objects.forEach(child => {
			if (child.isInstanced) {
				child.drawInstance(modelViewMatrix, projectionMatrix, camera);
			} else {
				child.draw(modelViewMatrix, projectionMatrix, camera);
			}
		});
	}

	render(scene, camera) {
		gl = GL.get();
		this._reset(gl);

		gl.viewport(0.0, 0.0, gl.drawingBufferWidth, gl.drawingBufferHeight);

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		if (camera.isPespectiveCamera) {
			mat4.perspective(camera.projectionMatrix, camera.fov, this.ratio, camera.near, camera.far);
		} else if (camera.isOrthographicCamera) {
			mat4.ortho(camera.projectionMatrix, -1.0, 1.0, -1.0, 1.0, camera.near, camera.far);
		} else {
			throw new Error('Camera type not supported');
		}

		mat4.identity(scene.modelViewMatrix);

		mat4.lookAt(scene.modelViewMatrix, camera.position.v, camera.center.v, camera.up.v);

		// Update the scene
		scene.update();

		// Draw the scene objects
		this._drawObjects(scene, camera.projectionMatrix, scene.modelViewMatrix, camera);
	}

	// For debug atm
	renderStereo(scene,
		leftProjectionMatrix,
		leftViewMatrix,
		rightProjectionMatrix,
		rightViewMatrix,
		cameraL,
		cameraR,
	) {
		gl = GL.get();
		this._reset(gl);

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		// Draw both eyes

		mat4.identity(scene.modelViewMatrix);

		// Update the scene
		scene.update();

		// Left
		gl.viewport(0.0, 0.0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);

		mat4.perspective(leftProjectionMatrix, cameraL.fov, this.ratio, cameraL.near, cameraL.far);
		mat4.lookAt(leftViewMatrix, cameraL.position.v, cameraL.center.v, cameraL.up.v);

		this._drawObjects(scene, leftProjectionMatrix, leftViewMatrix);

		// Right
		gl.viewport(gl.drawingBufferWidth * 0.5,
			0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);

		mat4.perspective(rightProjectionMatrix, cameraR.fov, this.ratio, cameraR.near, cameraR.far);
		mat4.lookAt(rightViewMatrix, cameraR.position.v, cameraR.center.v, cameraR.up.v);

		this._drawObjects(scene, rightProjectionMatrix, rightViewMatrix);
	}

	renderWebVR(scene,
		leftProjectionMatrix,
		leftViewMatrix,
		rightProjectionMatrix,
		rightViewMatrix,
	) {
		gl = GL.get();
		this._reset(gl);

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		// Draw both eyes

		mat4.identity(scene.modelViewMatrix);

		// Update the scene
		scene.update();

		// Left
		gl.viewport(0.0, 0.0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);
		this._drawObjects(scene, leftProjectionMatrix, leftViewMatrix);

		// Right
		gl.viewport(gl.drawingBufferWidth * 0.5,
			0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);

		this._drawObjects(scene, rightProjectionMatrix, rightViewMatrix);
	}
}
<commit_msg>Fixing contextType for GL.set<commit_after>import * as GL from './GL';
import {
	mat4,
} from 'gl-matrix';
import {
	log,
	warn,
} from 'utils/Console';
import {
	WEBGL_CONTEXT,
	WEBGL2_CONTEXT,
	RENDERER_DEFAULT_CONTEXT,
	RENDERER_DEFAULT_WIDTH,
	RENDERER_DEFAULT_HEIGHT,
	LINE_DEFAULT_WIDTH,
} from 'core/Constants';
import * as Capabilities from 'core/Capabilities';
import * as UniformBuffers from 'core/UniformBuffers';
import Detect from 'utils/Detect';
const config = require('../../package.json');

let gl;

export default class Renderer {
	constructor(settings = {}) {
		// Default renderer settings
		const defaults = {
			width: RENDERER_DEFAULT_WIDTH,
			height: RENDERER_DEFAULT_HEIGHT,
			ratio: RENDERER_DEFAULT_WIDTH / RENDERER_DEFAULT_HEIGHT,
			preserveDrawingBuffer: false,
			pixelRatio: 1,
			prefferedContext: RENDERER_DEFAULT_CONTEXT,
		};

		window.Capabilities = Capabilities;

		// Apply defaults
		Object.assign(this, defaults, settings);

		// Create canvas
		this.canvas = document.createElement('canvas');
		this.canvas.width = this.width;
		this.canvas.height = this.height;

		// Try initialising gl
		const attributes = {
			preserveDrawingBuffer: this.preserveDrawingBuffer,
		};

		const detect = Detect();

		if (detect) {
			let contextType;
			if (detect.webgl2 && this.prefferedContext === WEBGL2_CONTEXT) {
				contextType = WEBGL2_CONTEXT;
				gl = this.canvas.getContext('webgl2', attributes);
			} else {
				contextType = WEBGL_CONTEXT;
				gl = this.canvas.getContext('webgl', attributes) || this.canvas.getContext('experimental-webgl');
			}
			GL.set(gl, contextType);
		} else {
			warn('Webgl not supported');
			return;
		}

		log(`%c${config.name} ${config.version} webgl${GL.webgl2 ? 2 : ''}`, 'padding: 1px; background: #222; color: #ff00ff');

		gl = GL.get();

		// Log Capabilities of gpu
		Capabilities.set(gl);

		// Setup global uniform buffers
		if (GL.webgl2) {
			UniformBuffers.setup();
		}

		// log('capabilities', Capabilities.capabilities);
		// log('extensions', Capabilities.extensions);

		gl.clearColor(0.0, 0.0, 0.0, 1.0);
		gl.enable(gl.DEPTH_TEST);
	}

	setSize(width, height) {
		const newWidth = width * this.pixelRatio;
		const newHeight = height * this.pixelRatio;

		if (newWidth !== this.width || newHeight !== this.height) {
			this.width = width * this.pixelRatio;
			this.height = height * this.pixelRatio;
			this.ratio = this.width / this.height;

			this.canvas.width = this.width;
			this.canvas.height = this.height;

			this.canvas.style.width = `${width}px`;
			this.canvas.style.height = `${height}px`;
		}
	}

	setDevicePixelRatio(ratio = 1) {
		this.pixelRatio = ratio || 1;
		this.setSize(this.width, this.height);
	}

	_reset(gl) {
		// Line width
		gl.lineWidth(LINE_DEFAULT_WIDTH);
	}

	_drawObjects(scene, projectionMatrix, modelViewMatrix, camera) {
		if (GL.webgl2) {
			// Update global uniform buffers
			UniformBuffers.updateProjectionView(gl, projectionMatrix, modelViewMatrix);
		}

		// Render the scene objects
		scene.objects.forEach(child => {
			if (child.isInstanced) {
				child.drawInstance(modelViewMatrix, projectionMatrix, camera);
			} else {
				child.draw(modelViewMatrix, projectionMatrix, camera);
			}
		});
	}

	render(scene, camera) {
		gl = GL.get();
		this._reset(gl);

		gl.viewport(0.0, 0.0, gl.drawingBufferWidth, gl.drawingBufferHeight);

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		if (camera.isPespectiveCamera) {
			mat4.perspective(camera.projectionMatrix, camera.fov, this.ratio, camera.near, camera.far);
		} else if (camera.isOrthographicCamera) {
			mat4.ortho(camera.projectionMatrix, -1.0, 1.0, -1.0, 1.0, camera.near, camera.far);
		} else {
			throw new Error('Camera type not supported');
		}

		mat4.identity(scene.modelViewMatrix);

		mat4.lookAt(scene.modelViewMatrix, camera.position.v, camera.center.v, camera.up.v);

		// Update the scene
		scene.update();

		// Draw the scene objects
		this._drawObjects(scene, camera.projectionMatrix, scene.modelViewMatrix, camera);
	}

	// For debug atm
	renderStereo(scene,
		leftProjectionMatrix,
		leftViewMatrix,
		rightProjectionMatrix,
		rightViewMatrix,
		cameraL,
		cameraR,
	) {
		gl = GL.get();
		this._reset(gl);

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		// Draw both eyes

		mat4.identity(scene.modelViewMatrix);

		// Update the scene
		scene.update();

		// Left
		gl.viewport(0.0, 0.0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);

		mat4.perspective(leftProjectionMatrix, cameraL.fov, this.ratio, cameraL.near, cameraL.far);
		mat4.lookAt(leftViewMatrix, cameraL.position.v, cameraL.center.v, cameraL.up.v);

		this._drawObjects(scene, leftProjectionMatrix, leftViewMatrix);

		// Right
		gl.viewport(gl.drawingBufferWidth * 0.5,
			0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);

		mat4.perspective(rightProjectionMatrix, cameraR.fov, this.ratio, cameraR.near, cameraR.far);
		mat4.lookAt(rightViewMatrix, cameraR.position.v, cameraR.center.v, cameraR.up.v);

		this._drawObjects(scene, rightProjectionMatrix, rightViewMatrix);
	}

	renderWebVR(scene,
		leftProjectionMatrix,
		leftViewMatrix,
		rightProjectionMatrix,
		rightViewMatrix,
	) {
		gl = GL.get();
		this._reset(gl);

		gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

		// Draw both eyes

		mat4.identity(scene.modelViewMatrix);

		// Update the scene
		scene.update();

		// Left
		gl.viewport(0.0, 0.0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);
		this._drawObjects(scene, leftProjectionMatrix, leftViewMatrix);

		// Right
		gl.viewport(gl.drawingBufferWidth * 0.5,
			0, gl.drawingBufferWidth * 0.5, gl.drawingBufferHeight);

		this._drawObjects(scene, rightProjectionMatrix, rightViewMatrix);
	}
}
<|endoftext|>


<commit_before>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Zynga Inc.
==================================================================================================
*/

(function()
{
	/** Keys are all URIs which are completely loaded */
	var completed = {};

	/** Maps extensions to loader classes */
	var typeLoader =
	{
		js : core.io.Script,
		css : core.io.StyleSheet,
		jsonp : core.io.Jsonp,
		json : core.io.Text,
		txt : core.io.Text,
		md : core.io.Text,
		html : core.io.Text,
		png : core.io.Image,
		jpeg : core.io.Image,
		jpg : core.io.Image,
		gif : core.io.Image
	};

	/**
	 * {String} Returns the extension of the given @filename {String}
	 */
	var extractExtension = function(filename)
	{
		// Filter out query string and find last dot to split extension
		var result = filename.match(/\.([^\.\?]+)(?:\?|$)/);

		// Extension found
		if (result != null) {
			return result[1];
		}

		// Support for callback params in URI (JSON-P)
		if (filename.indexOf("callback=") != -1) {
			return "jsonp";
		}

		return null;
	};

	/**
	 * Generic URLs loader queue with support for different type "backend" modules.
	 *
	 * Uses parallel loading where available and load all other resources
	 * sequentially. Sequential loading is done by type so that multiple
	 * different types are loaded in parallel.
	 *
	 * Loader module need to implement the following interface:
	 *
	 * * method load(uri, callback, context, nocache) which calls the callback like callback.call(context, uri, errornous, data)
	 * * constant `SUPPORTS_PARALLEL` with a boolean value whether the loader supports parallel loading
	 */
	core.Module("core.io.Queue",
	{
		/**
		 * {Boolean} Returns whether the given @uris {String|String[]} were loaded before.
		 */
		isLoaded : function(uris)
		{
			if (typeof uris == "string") {
				return !!completed[uris];
			}

			if (core.Env.isSet("debug")) {
				core.Assert.isType(uris, "Array", "Invalid list of URIs!");
			}

			for (var i=0, l=uris.length; i<l; i++)
			{
				if (!completed[uris[i]]) {
					return false;
				}
			}

			return true;
		},

		/**
		 * Loads the given @uris {String[]} and optionally executes the given @callback {Function} with the @context {Object?null} after all are completed.
		 * One can optionally disable the browser caching using enforced get parameters via the @nocache {Boolean?false} flag. Typically
		 * the matching loader is figured out automatically based on the file extension but can be controlled using the @type {String?} parameter.
		 */
		load : function(uris, callback, context, nocache, type)
		{
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(uris, "Array");

				if (callback != null) {
					core.Assert.isType(callback, "Function", "Invalid callback method!");
				}

				if (context != null) {
					core.Assert.isType(context, "Object", "Invalid callback context!");
				}

				if (nocache != null) {
					core.Assert.isType(nocache, "Boolean");
				}

				if (type != null) {
					core.Assert.isType(type, "String");
				}
			}

			// Keys are all URIs which are currently loading
			var loading = {};

			// Data cache for callback return
			var cache = {};

			/**
			 * Registers the given @uri {String} as being loaded and deals with error reports (@errornous {Boolean?false})
			 * and @data {var?null} delivered by the loader.
			 */
			var onLoad = function(uri, errornous, data)
			{
				if (core.Env.isSet("debug"))
				{
					core.Assert.isType(uri, "String", "Got invalid URI from loader!");

					if (errornous != null) {
						core.Assert.isType(errornous, "Boolean", "Got invalid errornous flag from loader for uri: " + uri);
					}
				}

				delete loading[uri];
				completed[uri] = true;

				// Make data available for callback
				if (data != null) {
					cache[uri] = data;
				}

				// Check whether there is more to load
				for (var queued in loading) {
					return;
				}

				// Execute callback
				if (callback) {
					context ? callback.call(context, cache) : callback(cache);
				}
			};

			var executeDirectly = !!callback;
			var autoType = !type;

			// List of sequential items sorted by type
			var sequential = {};

			// Process all URIs
			for (var i=0, l=uris.length; i<l; i++)
			{
				var currentUri = uris[i];

				if (!completed[currentUri])
				{
					if (autoType) {
						type = extractExtension(currentUri);

						if (core.Env.isSet("debug") && (!type || !typeLoader[type])) {
							throw new Error("Could not figure out loader to use for URI: " + currentUri);
						}
					}

					var loader = typeLoader[type];

					// As we are waiting for things to load, we can't execute the callback directly anymore
					executeDirectly = false;

					// When script is not being loaded already, then start with it here
					// (Otherwise we just added the callback to the queue and wait for it to be executed)
					if (!loading[currentUri])
					{
						// Register globally as loading
						loading[currentUri] = true;

						// Differenciate between loader capabilities
						if (loader.SUPPORTS_PARALLEL)
						{
							loader.load(currentUri, onLoad, null, nocache);
						}
						else
						{
							// Sort in the URI into a type specific queue
							if (sequential[type]) {
								sequential[type].push(currentUri);
							} else {
								sequential[type] = [currentUri];
							}
						}
					}
				}
			}

			// If all scripts are loaded already, just execute the callback
			if (executeDirectly)
			{
				// Nothing to load, execute callback directly
				context ? callback.call(context, cache) : callback(cache);
			}
			else
			{
				/**
				 * Loads the next URI for the given @type {String}
				 */
				var loadNext = function(type)
				{
					var uri = sequential[type].shift();
					if (uri)
					{
						typeLoader[type].load(uri, function(uri, errornous, data)
						{
							onLoad(uri, errornous, data);
							loadNext(type);
						},
						null, nocache);
					}
				};

				// Load and execute first item in each queue
				for (var type in sequential) {
					loadNext(type);
				}
			}
		}
	});
})();

<commit_msg>Removed support for tracking loading complete state<commit_after>/*
==================================================================================================
  Core - JavaScript Foundation
  Copyright 2010-2012 Zynga Inc.
==================================================================================================
*/

(function()
{
	/** Maps extensions to loader classes */
	var typeLoader =
	{
		js : core.io.Script,
		css : core.io.StyleSheet,
		jsonp : core.io.Jsonp,
		json : core.io.Text,
		txt : core.io.Text,
		md : core.io.Text,
		html : core.io.Text,
		png : core.io.Image,
		jpeg : core.io.Image,
		jpg : core.io.Image,
		gif : core.io.Image
	};

	/**
	 * {String} Returns the extension of the given @filename {String}
	 */
	var extractExtension = function(filename)
	{
		// Filter out query string and find last dot to split extension
		var result = filename.match(/\.([^\.\?]+)(?:\?|$)/);

		// Extension found
		if (result != null) {
			return result[1];
		}

		// Support for callback params in URI (JSON-P)
		if (filename.indexOf("callback=") != -1) {
			return "jsonp";
		}

		return null;
	};

	/**
	 * Generic URLs loader queue with support for different type "backend" modules.
	 *
	 * Uses parallel loading where available and load all other resources
	 * sequentially. Sequential loading is done by type so that multiple
	 * different types are loaded in parallel.
	 *
	 * Loader module need to implement the following interface:
	 *
	 * * method load(uri, callback, context, nocache) which calls the callback like callback.call(context, uri, errornous, data)
	 * * constant `SUPPORTS_PARALLEL` with a boolean value whether the loader supports parallel loading
	 */
	core.Module("core.io.Queue",
	{
		/**
		 * Loads the given @uris {String[]} and optionally executes the given @callback {Function} with the @context {Object?null} after all are completed.
		 * One can optionally disable the browser caching using enforced get parameters via the @nocache {Boolean?false} flag. Typically
		 * the matching loader is figured out automatically based on the file extension but can be controlled using the @type {String?} parameter.
		 */
		load : function(uris, callback, context, nocache, type)
		{
			if (core.Env.isSet("debug"))
			{
				core.Assert.isType(uris, "Array");

				if (callback != null) {
					core.Assert.isType(callback, "Function", "Invalid callback method!");
				}

				if (context != null) {
					core.Assert.isType(context, "Object", "Invalid callback context!");
				}

				if (nocache != null) {
					core.Assert.isType(nocache, "Boolean");
				}

				if (type != null) {
					core.Assert.isType(type, "String");
				}
			}

			// Keys are all URIs which are currently loading
			var loading = {};

			// Data cache for callback return
			var cache = {};

			/**
			 * Registers the given @uri {String} as being loaded and deals with error reports (@errornous {Boolean?false})
			 * and @data {var?null} delivered by the loader.
			 */
			var onLoad = function(uri, errornous, data)
			{
				if (core.Env.isSet("debug"))
				{
					core.Assert.isType(uri, "String", "Got invalid URI from loader!");

					if (errornous != null) {
						core.Assert.isType(errornous, "Boolean", "Got invalid errornous flag from loader for uri: " + uri);
					}
				}

				delete loading[uri];

				// Make data available for callback
				if (data != null) {
					cache[uri] = data;
				}

				// Check whether there is more to load
				for (var queued in loading) {
					return;
				}

				// Execute callback
				if (callback) {
					context ? callback.call(context, cache) : callback(cache);
				}
			};

			var executeDirectly = !!callback;
			var autoType = !type;

			// List of sequential items sorted by type
			var sequential = {};

			// Process all URIs
			for (var i=0, l=uris.length; i<l; i++)
			{
				var currentUri = uris[i];

				if (autoType) {
					type = extractExtension(currentUri);

					if (core.Env.isSet("debug") && (!type || !typeLoader[type])) {
						throw new Error("Could not figure out loader to use for URI: " + currentUri);
					}
				}

				var loader = typeLoader[type];

				// As we are waiting for things to load, we can't execute the callback directly anymore
				executeDirectly = false;

				// When script is not being loaded already, then start with it here
				// (Otherwise we just added the callback to the queue and wait for it to be executed)
				if (!loading[currentUri])
				{
					// Register globally as loading
					loading[currentUri] = true;

					// Differenciate between loader capabilities
					if (loader.SUPPORTS_PARALLEL)
					{
						loader.load(currentUri, onLoad, null, nocache);
					}
					else
					{
						// Sort in the URI into a type specific queue
						if (sequential[type]) {
							sequential[type].push(currentUri);
						} else {
							sequential[type] = [currentUri];
						}
					}
				}
			}

			// If all scripts are loaded already, just execute the callback
			if (executeDirectly)
			{
				// Nothing to load, execute callback directly
				context ? callback.call(context, cache) : callback(cache);
			}
			else
			{
				/**
				 * Loads the next URI for the given @type {String}
				 */
				var loadNext = function(type)
				{
					var uri = sequential[type].shift();
					if (uri)
					{
						typeLoader[type].load(uri, function(uri, errornous, data)
						{
							onLoad(uri, errornous, data);
							loadNext(type);
						},
						null, nocache);
					}
				};

				// Load and execute first item in each queue
				for (var type in sequential) {
					loadNext(type);
				}
			}
		}
	});
})();

<|endoftext|>


<commit_before>import assign from 'object-assign'
import {cloneElement, isValidElement} from 'react'
import assignStyles from 'assign-styles'
import processStyles from './processor'

/**
 * Resolves provided looks into style objects
 * Processes those using a predefined processor lineup
 * Maps the final style objects to the element
 * @param {Object} Component - wrapping React Component providing looks and elements
 * @param {Object} element - previously rendered React element
 * @param {Object} childIndexMap - information on child index and child types
 */
export default function resolveLook(Component, element, childIndexMap) {
	if (element && element.props) {
		let props = element.props

		//resolving child looks recursively to make sure they will be rendered correctly
		let newProps = assign({}, props)
		newProps.children = resolveChildren(Component, props.children)

		//Extracts only relevant styles according to the look prop
		let styles = extractStyles(props, Component.styles)

		if (styles) {
			//Triggers style processing
			//Uses the exact processor lineup defined within Config
			let processArgs = {
				newProps, Component, element, childIndexMap
			}
			styles = processStyles(styles, Component._processors, processArgs)
			if (props.style) {
				styles = assignStyles(styles, props.style)
			}

			newProps.style = styles
		}

		return cloneElement(element, newProps, newProps.children)
	} else {
		return element
	}
}

/**
 * Resolves provided looks for an elements children
 * @param {Object} Component - wrapping React Component providing looks and elements
 * @param {Array|string|number} children - children that get resolved
 */
export function resolveChildren(Component, children) {
	if (children) {
		//If there are more than one child, iterate over them
		if (children instanceof Array) {
			let newChildren = []

			let typeMap = generateTypeMap(children)
			let indexMap = {}
			let childType, childIndex

			//Recursively resolve look for child elements first
			//Generate index-maps to resolve child-index-sensitive pseudo classes
			children.forEach((child, index) => {

				//only resolve child if it actually is a valid react element
				if (isValidElement(child)) {

					//Provides information on child (type-sensitive) child indexes to resolve index-sensitive pseudo-classes
					indexMap = generateIndexMap(child, indexMap)

					childType = getChildType(child)
					childIndex = {
						'index': index + 1,
						'length': children.length,
						'typeIndex': indexMap[childType],
						'typeLength': typeMap[childType]
					}
					newChildren.push(resolveLook(Component, child, childIndex))

				} else {
					//This clears undefined childs as they would falsely render
					//e.g. if you're trying to map {this.props.title} but it is not defined
					//It also fires a warning so that you may remove them on your own
					if (child === undefined) {
						console.warn('There are children which are either undefined, empty or invalid React Elements: ', children)
						console.warn('Look removed 1 child while validating (look="' + props.look + '"): child ', child)
					} else {
						newChildren.push(child);
					}
				}
			})
			return newChildren
		} else {
			return resolveLook(Component, children)
		}
	} else {
		return children === 0 ? children : false
	}
}

/**
 * Extracts referenced styles to an elements props
 * @param {Object} props - elements props that will be assigned
 * @param {Object} styles - a valid style object
 */
export function extractStyles(props, styles) {
	if (props.hasOwnProperty('look')) {

		//Resolve look shortcut _default and map referenced styles
		if (props.look === true) {
			return styles._default
		} else {
			let extracted = {}
				//Splits look to resolve multiple looks
				//Reverse to loop backwards in order to resolve with priority
			let lookList = props.look.split(' ').reverse()
			lookList.forEach(look => {
				//Reduce if look is existing otherwise throw a warning
				if (styles.hasOwnProperty(look)) {
					extracted = assignStyles({}, styles[look], extracted)
				} else {
					console.warn('Assigned look does not exist and will be ignored.')
					console.warn('Provided styles: ' + styles + ' do not include ' + look)
				}
			})
			return extracted
		}
		delete props.look
	} else {
		return false
	}
}

/**
 * Generates a index map with information on type/index of each child
 * This is needed to validate type-specific index-sensitive pseudo-classes
 * e.g. :last-type-of
 * @param {Array} children - an array of children
 * @pararam {Object} indexMap - an object which stores the information
 */
export function generateIndexMap(child, indexMap) {

	// use component displayName if child is a function (ES6 component)
	let childType = getChildType(child)

	if (indexMap.hasOwnProperty(childType)) {
		++indexMap[childType]
	} else {
		indexMap[childType] = 1;
	}
	return indexMap
}

/**
 * Iterate through all children and create a map with type/index information
 * @param {Array} children - an array of children
 */
export function generateTypeMap(children) {
	let indexMap = {}
	children.forEach((child, index) => {
		generateIndexMap(child, indexMap)
	})
	return indexMap
}

/**
 * Returns a childs type
 * If child is an ES6 class it returns the displayName
 * @param {Object} child - child which type gets identified
 */
export function getChildType(child) {
	let childType
	if (child.type instanceof Function) {
		childType = (child.type.hasOwnProperty('name') ? child.type.name : child.type)
	} else {
		childType = child.type
	}
	return childType
}<commit_msg>#73 Resolving elements passed by props<commit_after>import assign from 'object-assign'
import {cloneElement, isValidElement} from 'react'
import assignStyles from 'assign-styles'
import processStyles from './processor'

/**
 * Resolves provided looks into style objects
 * Processes those using a predefined processor lineup
 * Maps the final style objects to the element
 * @param {Object} Component - wrapping React Component providing looks and elements
 * @param {Object} element - previously rendered React element
 * @param {Object} childIndexMap - information on child index and child types
 */
export default function resolveLook(Component, element, childIndexMap) {
	if (element && element.props) {
		let props = element.props

		//resolving child looks recursively to make sure they will be rendered correctly
		let newProps = assign({}, props)
		newProps.children = resolveChildren(Component, props.children)

		//Extracts only relevant styles according to the look prop
		let styles = extractStyles(props, Component.styles)

		if (styles) {
			//Triggers style processing
			//Uses the exact processor lineup defined within Config
			let processArgs = {
				newProps, Component, element, childIndexMap
			}
			styles = processStyles(styles, Component._processors, processArgs)
			if (props.style) {
				styles = assignStyles(styles, props.style)
			}

			newProps.style = styles
		}

		//Resolving styles for elements passed by props
		let prop
		for (prop in newProps) {
			if (prop === 'children') {
				continue
			}
			if (isValidElement(newProps[prop])){
				newProps[prop] = resolveLook(Component, newProps[prop])
			}
		}

		return cloneElement(element, newProps, newProps.children)
	} else {
		return element
	}
}

/**
 * Resolves provided looks for an elements children
 * @param {Object} Component - wrapping React Component providing looks and elements
 * @param {Array|string|number} children - children that get resolved
 */
export function resolveChildren(Component, children) {
	if (children) {
		//If there are more than one child, iterate over them
		if (children instanceof Array) {
			let newChildren = []

			let typeMap = generateTypeMap(children)
			let indexMap = {}
			let childType, childIndex

			//Recursively resolve look for child elements first
			//Generate index-maps to resolve child-index-sensitive pseudo classes
			children.forEach((child, index) => {

				//only resolve child if it actually is a valid react element
				if (isValidElement(child)) {

					//Provides information on child (type-sensitive) child indexes to resolve index-sensitive pseudo-classes
					indexMap = generateIndexMap(child, indexMap)

					childType = getChildType(child)
					childIndex = {
						'index': index + 1,
						'length': children.length,
						'typeIndex': indexMap[childType],
						'typeLength': typeMap[childType]
					}
					newChildren.push(resolveLook(Component, child, childIndex))

				} else {
					//This clears undefined childs as they would falsely render
					//e.g. if you're trying to map {this.props.title} but it is not defined
					//It also fires a warning so that you may remove them on your own
					if (child === undefined) {
						console.warn('There are children which are either undefined, empty or invalid React Elements: ', children)
						console.warn('Look removed 1 child while validating (look="' + props.look + '"): child ', child)
					} else {
						newChildren.push(child);
					}
				}
			})
			return newChildren
		} else {
			return resolveLook(Component, children)
		}
	} else {
		return children === 0 ? children : false
	}
}

/**
 * Extracts referenced styles to an elements props
 * @param {Object} props - elements props that will be assigned
 * @param {Object} styles - a valid style object
 */
export function extractStyles(props, styles) {
	if (props.hasOwnProperty('look')) {

		//Resolve look shortcut _default and map referenced styles
		if (props.look === true) {
			return styles._default
		} else {
			let extracted = {}
				//Splits look to resolve multiple looks
				//Reverse to loop backwards in order to resolve with priority
			let lookList = props.look.split(' ').reverse()
			lookList.forEach(look => {
				//Reduce if look is existing otherwise throw a warning
				if (styles.hasOwnProperty(look)) {
					extracted = assignStyles({}, styles[look], extracted)
				} else {
					console.warn('Assigned look does not exist and will be ignored.')
					console.warn('Provided styles: ' + styles + ' do not include ' + look)
				}
			})
			return extracted
		}
		delete props.look
	} else {
		return false
	}
}

/**
 * Generates a index map with information on type/index of each child
 * This is needed to validate type-specific index-sensitive pseudo-classes
 * e.g. :last-type-of
 * @param {Array} children - an array of children
 * @pararam {Object} indexMap - an object which stores the information
 */
export function generateIndexMap(child, indexMap) {

	// use component displayName if child is a function (ES6 component)
	let childType = getChildType(child)

	if (indexMap.hasOwnProperty(childType)) {
		++indexMap[childType]
	} else {
		indexMap[childType] = 1;
	}
	return indexMap
}

/**
 * Iterate through all children and create a map with type/index information
 * @param {Array} children - an array of children
 */
export function generateTypeMap(children) {
	let indexMap = {}
	children.forEach((child, index) => {
		generateIndexMap(child, indexMap)
	})
	return indexMap
}

/**
 * Returns a childs type
 * If child is an ES6 class it returns the displayName
 * @param {Object} child - child which type gets identified
 */
export function getChildType(child) {
	let childType
	if (child.type instanceof Function) {
		childType = (child.type.hasOwnProperty('name') ? child.type.name : child.type)
	} else {
		childType = child.type
	}
	return childType
}
<|endoftext|>


<commit_before>var fs = require('fs');
var path = require('path');
var sys = require('sys')
var exec = require('child_process').exec;

function takeStill(response){
	// take a still photo and send it back in the response
	exec('raspistill -vf -t 0 -o -', function (error, stdout, stderr) {
		if (error === null) {
			response.writeHead(200, { 'Content-Type': 'image/jpg' });
			response.end(stdout, 'binary');
		} else {
			response.writeHead(500, { 'Content-Type': 'text/plain' });
			response.end('error taking still', 'utf-8');
		}
	});
};

module.exports.takeStill = takeStill;<commit_msg>remove unused imports<commit_after>var exec = require('child_process').exec;

function takeStill(response){
	// take a still photo and send it back in the response
	exec('raspistill -vf -t 0 -o -', function (error, stdout, stderr) {
		if (error === null) {
			response.writeHead(200, { 'Content-Type': 'image/jpg' });
			response.end(stdout, 'binary');
		} else {
			response.writeHead(500, { 'Content-Type': 'text/plain' });
			response.end('error taking still', 'utf-8');
		}
	});
};

module.exports.takeStill = takeStill;
<|endoftext|>


<commit_before>var fwk = require('fwk');
var config = fwk.baseConfig();

config['DATTSS_SECRET'] = 'dummy-env';
config['DATTSS_HMAC'] = 'dummy-env';
config['DATTSS_SRV_AUTH_KEY'] = 'dummy-env';

config['DATTSS_SALT_SPACE'] = 60 * 60 * 24 * 7; /* number of seconds in a week */

config['DATTSS_HTTP_PORT'] = 3002;
config['DATTSS_DOMAIN'] = 'dummy-env';

config['DATTSS_MONGO_URL'] = 'mongodb://localhost:27017/dts-data';

config['DATTSS_SENDGRID_USER'] = 'dummy-env';
config['DATTSS_SENDGRID_PASS'] = 'dummy-env';
config['DATTSS_SENDGRID_FROM'] = 'dummy-env';
config['DATTSS_SENDGRID_FROMNAME'] = 'dummy-env';

config['DATTSS_PUBLIC_ENDPOINTS'] = [ /^\/s\/auth\// ];

config['DATTSS_COOKIE_AGE'] = 1000 * 60 * 60 * 24 * 365;

/* we remove before 8 days because we want 7 days available for all timezones */
config['DATTSS_HISTORY_PERIOD'] = 8 * 24 * 60 * 60 * 1000;
config['DATTSS_CROND_PERIOD'] = 60 * 1000;

config['DATTSS_BTW_ALERTS'] = 10 * 60 * 1000;
config['DATTSS_MAX_ALERTS'] = 20;
config['DATTSS_ALERTS_AVG_ITV'] = 5;  /* Moving average use by alerts in minutes */

config['DATTSS_DEMO_UID'] = 'b22b7b32acb9e606befec211b738316beb191c23';

exports.config = config;
<commit_msg>mark must-replace config in dattss<commit_after>var fwk = require('fwk');
var config = fwk.baseConfig();

config['DATTSS_SECRET'] = 'dummy-env';
config['DATTSS_HMAC'] = 'dummy-env';
config['DATTSS_SRV_AUTH_KEY'] = 'REPLACE_ME_WITH_TRUE_AUTH_KEY';

config['DATTSS_SALT_SPACE'] = 60 * 60 * 24 * 7; /* number of seconds in a week */

config['DATTSS_HTTP_PORT'] = 3002;
config['DATTSS_DOMAIN'] = 'REPLACE_ME_WITH_TRUE_DOMAIN';

config['DATTSS_MONGO_URL'] = 'mongodb://localhost:27017/dts-data';

config['DATTSS_SENDGRID_USER'] = 'dummy-env';
config['DATTSS_SENDGRID_PASS'] = 'dummy-env';
config['DATTSS_SENDGRID_FROM'] = 'dummy-env';
config['DATTSS_SENDGRID_FROMNAME'] = 'dummy-env';

config['DATTSS_PUBLIC_ENDPOINTS'] = [ /^\/s\/auth\// ];

config['DATTSS_COOKIE_AGE'] = 1000 * 60 * 60 * 24 * 365;

/* we remove before 8 days because we want 7 days available for all timezones */
config['DATTSS_HISTORY_PERIOD'] = 8 * 24 * 60 * 60 * 1000;
config['DATTSS_CROND_PERIOD'] = 60 * 1000;

config['DATTSS_BTW_ALERTS'] = 10 * 60 * 1000;
config['DATTSS_MAX_ALERTS'] = 20;
config['DATTSS_ALERTS_AVG_ITV'] = 5;  /* Moving average use by alerts in minutes */

config['DATTSS_DEMO_UID'] = 'b22b7b32acb9e606befec211b738316beb191c23';

exports.config = config;
<|endoftext|>


<commit_before>var fs = require("fs");
var path = require("path");
var proxy = require("./proxy");
var checkAccess = require("./checkAccess");
var encode62 = require("../process/crypt/encode62");
var mimes = require("../process/mime");

var { IN_TEST_MODE, PAGE_PATH, PUBLIC_PATH } = process.env;
var root = IN_TEST_MODE ? PAGE_PATH : PUBLIC_PATH;
var cachePieceSize = 32 * 1024;
var cacheCountLimit = 6000;
var allowSocketTime = 24 * 60 * 60 * 1000;
function piperead(h, start, end, res, sign) {
    var inc = start;
    var timeout = false;
    var safeend = function () {
        timeout = true;
        fs.close(h, function () {
            cacheCountLimit++;
        });
        res.end();
    };
    cacheCountLimit--;
    if (cacheCountLimit < 0) {
        safeend();
        return;
    }
    var halfend = setTimeout(safeend, allowSocketTime);
    var saveend = function () {
        clearTimeout(halfend);
        safeend();
    };
    var run = function (err) {
        if (err) return safeend();
        var ind = inc + cachePieceSize;
        if (ind > end) ind = end;
        var chunk = Buffer.alloc(ind - inc);
        if (inc >= end) return saveend();
        fs.read(h, chunk, 0, chunk.length, inc, function (error, size, chunk) {
            if (timeout) return;
            if (error) {
                res.writeHead(403, {});
                res.write(String(error));
                saveend();
                return;
            }
            if (size < chunk.length) {
                chunk = chunk.slice(0, size);
            }
            if (sign) {
                var signoffset = inc % sign.length;
                var signcurrent = sign.slice(signoffset) + sign.slice(0, signoffset);
                chunk = encode62.decode(chunk, signcurrent);
            }
            inc = inc + size;
            res.write(chunk, run);
        });
    };
    run();
}

function doGetFile(req, res, filepath, code) {

    var [, start, end] = String(req.headers.range).match(/bytes\s*=\s*(\d*)\s*\-\s*(\d*)/) || [];
    fs.exists(filepath, function (exists) {
        if (!exists) {
            res.writeHead(404, {});
            res.end("not found");
            return;
        }
        fs.stat(filepath, function (err, stats) {
            if (err) {
                res.writeHead(403, {});
                res.end(String(err));
                return;
            }
            if (stats.isDirectory()) {
                res.writeHead(400, {});
                res.end("folder");
                return;
            }
            start = +start | 0;
            if (!start || start < 0 || req.headers["if-range"] && req.headers["if-range"] !== stats.mtime.toUTCString()) {
                start = 0;
            }
            if (start > stats.size || end && end < start || start < 0) {
                res.writeHead(416, {});
                res.end("out of range");
                return;
            }
            if (code) {
                var sign = encode62.timedecode(code);
                if (!sign) {
                    res.writeHead(404, {});
                    res.end("not found");
                    return;
                }
            }
            if (end) {
                end = +end + 1;
            } else {
                end = stats.size;
            }
            fs.open(filepath, "r", function (error, h) {
                if (error) {
                    res.writeHead(403, {});
                    res.end(String(error));
                    return fs.closeSync(h);
                }

                var extend = filepath.match(/\.([^\.]*?)$/);
                var headers = {
                    "Content-Length": end ? end - start : stats.size - start,
                    "Last-Modified": stats.mtime.toUTCString()
                };
                if (req.headers.range) {
                    headers["Accept-Ranges"] = "bytes";
                    headers["Content-Range"] = `bytes ${start}-${end ? end - 1 : ''}/${stats.size}`;
                }
                if (extend) {
                    var mime = mimes[extend[1]];
                    if (mime) {
                        headers['Content-Type'] = mime;
                    }
                }
                res.writeHead(start === 0 && (!end || end === stats.size) ? 200 : 206, headers);
                piperead(h, start, end ? end : stats.size, res, sign);
            });
        })
    });

}

function doDeleteFile(req, res, filepath) {
    fs.exists(filepath, function (exists) {
        if (!exists) {
            res.writeHead(404, {});
            res.end("");
            return;
        }
        fs.unlink(filepath, function (error) {
            if (error) {
                res.writeHead(403, {});
                res.end(String(error));
                return;
            }
            res.end('');
        })
    })
}

function doPutFile(req, res, filepath, code) {
    fs.exists(path.dirname(filepath), function (exists) {
        if (!exists) {
            res.writeHead(403, {});
            res.end(`Directory not exists.`);
            return;
        }
        fs.exists(filepath, function (exists) {
            if (exists) {
                res.writeHead(403, {});
                res.end(`File already exists.`);
                return;
            }
        });

        if (cacheCountLimit <= 1) {
            res.writeHead(403, {});
            res.end("");
            return;
        }
        if (code) {
            var sign = encode62.timedecode(code);
            if (!sign) {
                res.writeHead(401, {});
                res.end("");
                return;
            }
        }
        cacheCountLimit--;
        var w = fs.createWriteStream(filepath);
        var fired = false;
        var safeend = function () {
            if (fired) return;
            cacheCountLimit++;
            fired = true;
            w.close();
            res.end(filepath);
        };
        var inc = 0;
        req.on("data", function (chunk) {
            if (sign) {
                var signoffset = inc % sign.length;
                var signcurrent = sign.slice(signoffset) + sign.slice(0, signoffset);
                chunk = encode62.decode(chunk, signcurrent);
            }
            inc = inc + chunk.length;
            w.write(chunk);
        });
        req.on("end", function () {
            w.end('');
            safeend();
        });
        req.on("error", safeend);
        req.on("close", safeend);
    });
}

function doPatchFile(req, res) {
    res.end();
}

function doFile(req, res) {
    if (/^\/@/.test(req.url)) {
        var filepath = req.url.slice(2);
        if (!checkAccess(filepath)) {
            res.writeHead(406, {});
            res.end();
            return;
        }
    } else {
        var url = proxy(req);
        var filepath = path.join(root, url);
    }
    if (/\!(\w+)(\.\w*)?$/.test(filepath)) {
        var [, filepath, code, extend] = /^([\s\S]*)\!(\w+)(\.\w*)?$/.exec(filepath);
        filepath = filepath + extend;
    }

    switch (req.method.toLowerCase()) {
        case "get":
            doGetFile(req, res, filepath, code);
            break;
        case "put":
            doPutFile(req, res, filepath, code);
            break;
        case "patch":
            doPatchFile(req, res, filepath, code);
            break;
        case "delete":
            doDeleteFile(req, res, filepath, code);
            break;
        default:
            res.writeHead(405, {});
            res.end(`${req.method} not allowed.`);
    }
}
module.exports = doFile;<commit_msg>防止不可访问的文件夹被修改<commit_after>var fs = require("fs");
var path = require("path");
var proxy = require("./proxy");
var checkAccess = require("./checkAccess");
var encode62 = require("../process/crypt/encode62");
var mimes = require("../process/mime");

var { IN_TEST_MODE, PAGE_PATH, PUBLIC_PATH } = process.env;
var root = IN_TEST_MODE ? PAGE_PATH : PUBLIC_PATH;
var cachePieceSize = 32 * 1024;
var cacheCountLimit = 6000;
var allowSocketTime = 24 * 60 * 60 * 1000;
function piperead(h, start, end, res, sign) {
    var inc = start;
    var timeout = false;
    var safeend = function () {
        timeout = true;
        fs.close(h, function () {
            cacheCountLimit++;
        });
        res.end();
    };
    cacheCountLimit--;
    if (cacheCountLimit < 0) {
        safeend();
        return;
    }
    var halfend = setTimeout(safeend, allowSocketTime);
    var saveend = function () {
        clearTimeout(halfend);
        safeend();
    };
    var run = function (err) {
        if (err) return safeend();
        var ind = inc + cachePieceSize;
        if (ind > end) ind = end;
        var chunk = Buffer.alloc(ind - inc);
        if (inc >= end) return saveend();
        fs.read(h, chunk, 0, chunk.length, inc, function (error, size, chunk) {
            if (timeout) return;
            if (error) {
                res.writeHead(403, {});
                res.write(String(error));
                saveend();
                return;
            }
            if (size < chunk.length) {
                chunk = chunk.slice(0, size);
            }
            if (sign) {
                var signoffset = inc % sign.length;
                var signcurrent = sign.slice(signoffset) + sign.slice(0, signoffset);
                chunk = encode62.decode(chunk, signcurrent);
            }
            inc = inc + size;
            res.write(chunk, run);
        });
    };
    run();
}

function doGetFile(req, res, filepath, code) {

    var [, start, end] = String(req.headers.range).match(/bytes\s*=\s*(\d*)\s*\-\s*(\d*)/) || [];
    fs.exists(filepath, function (exists) {
        if (!exists) {
            res.writeHead(404, {});
            res.end("not found");
            return;
        }
        fs.stat(filepath, function (err, stats) {
            if (err) {
                res.writeHead(403, {});
                res.end(String(err));
                return;
            }
            if (stats.isDirectory()) {
                res.writeHead(400, {});
                res.end("folder");
                return;
            }
            start = +start | 0;
            if (!start || start < 0 || req.headers["if-range"] && req.headers["if-range"] !== stats.mtime.toUTCString()) {
                start = 0;
            }
            if (start > stats.size || end && end < start || start < 0) {
                res.writeHead(416, {});
                res.end("out of range");
                return;
            }
            if (code) {
                var sign = encode62.timedecode(code);
                if (!sign) {
                    res.writeHead(404, {});
                    res.end("not found");
                    return;
                }
            }
            if (end) {
                end = +end + 1;
            } else {
                end = stats.size;
            }
            fs.open(filepath, "r", function (error, h) {
                if (error) {
                    res.writeHead(403, {});
                    res.end(String(error));
                    return fs.closeSync(h);
                }

                var extend = filepath.match(/\.([^\.]*?)$/);
                var headers = {
                    "Content-Length": end ? end - start : stats.size - start,
                    "Last-Modified": stats.mtime.toUTCString()
                };
                if (req.headers.range) {
                    headers["Accept-Ranges"] = "bytes";
                    headers["Content-Range"] = `bytes ${start}-${end ? end - 1 : ''}/${stats.size}`;
                }
                if (extend) {
                    var mime = mimes[extend[1]];
                    if (mime) {
                        headers['Content-Type'] = mime;
                    }
                }
                res.writeHead(start === 0 && (!end || end === stats.size) ? 200 : 206, headers);
                piperead(h, start, end ? end : stats.size, res, sign);
            });
        })
    });

}

function doDeleteFile(req, res, filepath) {
    fs.exists(filepath, function (exists) {
        if (!exists) {
            res.writeHead(404, {});
            res.end("");
            return;
        }
        fs.unlink(filepath, function (error) {
            if (error) {
                res.writeHead(403, {});
                res.end(String(error));
                return;
            }
            res.end('');
        })
    })
}

function doPutFile(req, res, filepath, code) {
    fs.exists(path.dirname(filepath), function (exists) {
        if (!exists) {
            res.writeHead(403, {});
            res.end(`Directory not exists.`);
            return;
        }
        fs.exists(filepath, function (exists) {
            if (exists) {
                res.writeHead(403, {});
                res.end(`File already exists.`);
                return;
            }
        });

        if (cacheCountLimit <= 1) {
            res.writeHead(403, {});
            res.end("");
            return;
        }
        if (code) {
            var sign = encode62.timedecode(code);
            if (!sign) {
                res.writeHead(401, {});
                res.end("");
                return;
            }
        }
        cacheCountLimit--;
        var w = fs.createWriteStream(filepath);
        var fired = false;
        var safeend = function () {
            if (fired) return;
            cacheCountLimit++;
            fired = true;
            w.close();
            res.end(filepath);
        };
        var inc = 0;
        req.on("data", function (chunk) {
            if (sign) {
                var signoffset = inc % sign.length;
                var signcurrent = sign.slice(signoffset) + sign.slice(0, signoffset);
                chunk = encode62.decode(chunk, signcurrent);
            }
            inc = inc + chunk.length;
            w.write(chunk);
        });
        req.on("end", function () {
            w.end('');
            safeend();
        });
        req.on("error", safeend);
        req.on("close", safeend);
    });
}

function doPatchFile(req, res) {
    res.end();
}

function doFile(req, res) {
    if (/^\/@/.test(req.url)) {
        var filepath = req.url.slice(2);
        if (!checkAccess(filepath)) {
            res.writeHead(406, {});
            res.end();
            return;
        }
    } else {
        var url = proxy(req);
        var filepath = path.join(root, url);
    }
    if (/\!(\w+)(\.\w*)?$/.test(filepath)) {
        var [, filepath, code, extend] = /^([\s\S]*)\!(\w+)(\.\w*)?$/.exec(filepath);
        filepath = filepath + extend;
    }
    if (!/get/.test(req.method)) {
        if (!checkAccess(filepath)) {
            res.writeHead(406, {});
            res.end();
            return;
        }
    }

    switch (req.method.toLowerCase()) {
        case "get":
            doGetFile(req, res, filepath, code);
            break;
        case "put":
            doPutFile(req, res, filepath, code);
            break;
        case "patch":
            doPatchFile(req, res, filepath, code);
            break;
        case "delete":
            doDeleteFile(req, res, filepath, code);
            break;
        default:
            res.writeHead(405, {});
            res.end(`${req.method} not allowed.`);
    }
}
module.exports = doFile;<|endoftext|>


<commit_before>'use strict';

// Sanitizes a string to be a filename
function stringSanitize(str) {
    return str
        .toLowerCase()
        .replace(/[àáâãäå]/g, 'a')
        .replace(/[èéêë]/g, 'e')
        .replace(/[ìíîï]/g, 'i')
        .replace(/[òóôõö]/g, 'o')
        .replace(/[ùúûü]/g, 'u')
        .replace(/æ/g, 'ae')
        .replace(/ç/g, 'c')
        .replace(/ñ/g, 'n')
        .replace(/œ/g, 'oe')
        .replace(/[ýÿ]/g, 'y')
        .replace(/[^a-z0-9]/gi, '_');
}

// Formats a string with arguments
function stringFormat(str) {
    for (var i = 0; i + 1 < arguments.length; i++) {
        str = str.replace('{' + i + '}', arguments[i + 1]);
    }

    return str;
}

// Reads a json file and return its content
function readJsonFile(path) {
    var fileExists = require('file-exists');
    var jsonfile = require('jsonfile');

    if (!fileExists.sync(path)) {
        return [];
    }

    return jsonfile.readFileSync(path, {
        throws: false
    });
}

// Writes content in a json file
function writeJsonFile(path, content) {
    var fs = require('fs');

    fs.writeFile(path, JSON.stringify(content, null, 4), (err) => {
        if (err) {
            log('Error while writing: ' + path + ' -> ' + err);
        } else {
            log('File updated: ' + path);
        }
    });
}

// Scrapes an url and call the callback with its content
function scrapeUrl(url, callback) {
    var request = require('request');
    var cheerio = require('cheerio');

    request(url, (err, resp, body) => {
        if (err) {
            log('Error while requesting: ' + url + ' -> ' + err);
        } else {
            var $ = cheerio.load(body);
            try {
                callback($);
            } catch (e) {
                log('Error while analysing: ' + url + ' -> ' + e);
            }
        }
    });
}

// Manage the flag property of an object
function manageFlagProperty(item) {
    item.flag = stringSanitize(item.country);

    var config = require('./config');
    if (config.downloadImages) {
        downloadImage('http:' + item.flagSrc, stringFormat(config.paths.flagsData, item.flag));
    }

    delete item.flagSrc;
}

// Manage the logo property of an object
function manageLogoProperty(item) {
    item.logo = stringSanitize(item.team);

    var config = require('./config');
    if (config.downloadImages) {
        downloadImage('http:' + item.logoSrc, stringFormat(config.paths.logosData, item.logo));
    }

    delete item.logoSrc;
}

// Download an image in a path
function downloadImage(src, path) {
    var fileExists = require('file-exists');
    var request = require('request');
    var fs = require('fs');

    if (!fileExists.sync(path) && !path.endsWith('/.gif')) {
        request.head(src, (err) => {
            if (err) {
                log('Error while downloading image: ' + path + ' -> ' + err);
            } else {
                request(src).pipe(fs.createWriteStream(path)).on('close', () => {
                    log('Image downloaded: ' + path);
                });
            }
        });
    }
}

// Helper to run an update of data
function runUpdate(itemsExtended, updateData, arg) {
    if (!arg) {
        return;
    }

    // Run the update only on the specified item
    if (typeof arg === 'string') {
        var itemArg = itemsExtended.find(itemExtended => itemExtended.item.smallName.toLowerCase() === arg.toLowerCase());

        if (itemArg) {
            updateData(itemArg);
        } else {
            log('Item not found: ' + arg);
        }

        return;
    }

    // Run the update on all items
    for (var i = 0; i < itemsExtended.length; i++) {
        updateData(itemsExtended[i]);
    }
}

// Get the current round of a league
function getLeagueCurrentRound(resultsData) {
    if (!resultsData) {
        return 1;
    }

    var round = 1;

    for (var i = 0; i < resultsData.length; i++) {
        var result = resultsData[i];

        if (result.matches.filter(p => p.score === '-:-').length === result.matches.length) {
            break;
        }

        round = result.round;
    }

    return round;
}

// Log a message in the console and a log file
function log(message) {
    var winston = require('winston');
    winston.configure({
        transports: [new (winston.transports.File)({
                filename: 'info.log'
            }),
            new (winston.transports.Console)()
        ]
    });
    winston.info(message);
}

module.exports = {
    stringSanitize: stringSanitize,
    stringFormat: stringFormat,
    readJsonFile: readJsonFile,
    writeJsonFile: writeJsonFile,
    scrapeUrl: scrapeUrl,
    manageFlagProperty: manageFlagProperty,
    manageLogoProperty: manageLogoProperty,
    runUpdate: runUpdate,
    getLeagueCurrentRound: getLeagueCurrentRound,
    log: log
};<commit_msg>Fix image update<commit_after>'use strict';

// Sanitizes a string to be a filename
function stringSanitize(str) {
    return str
        .toLowerCase()
        .replace(/[àáâãäå]/g, 'a')
        .replace(/[èéêë]/g, 'e')
        .replace(/[ìíîï]/g, 'i')
        .replace(/[òóôõö]/g, 'o')
        .replace(/[ùúûü]/g, 'u')
        .replace(/æ/g, 'ae')
        .replace(/ç/g, 'c')
        .replace(/ñ/g, 'n')
        .replace(/œ/g, 'oe')
        .replace(/[ýÿ]/g, 'y')
        .replace(/[^a-z0-9]/gi, '_');
}

// Formats a string with arguments
function stringFormat(str) {
    for (var i = 0; i + 1 < arguments.length; i++) {
        str = str.replace('{' + i + '}', arguments[i + 1]);
    }

    return str;
}

// Reads a json file and return its content
function readJsonFile(path) {
    var fileExists = require('file-exists');
    var jsonfile = require('jsonfile');

    if (!fileExists.sync(path)) {
        return [];
    }

    return jsonfile.readFileSync(path, {
        throws: false
    });
}

// Writes content in a json file
function writeJsonFile(path, content) {
    var fs = require('fs');

    fs.writeFile(path, JSON.stringify(content, null, 4), (err) => {
        if (err) {
            log('Error while writing: ' + path + ' -> ' + err);
        } else {
            log('File updated: ' + path);
        }
    });
}

// Scrapes an url and call the callback with its content
function scrapeUrl(url, callback) {
    var request = require('request');
    var cheerio = require('cheerio');

    request(url, (err, resp, body) => {
        if (err) {
            log('Error while requesting: ' + url + ' -> ' + err);
        } else {
            var $ = cheerio.load(body);
            try {
                callback($);
            } catch (e) {
                log('Error while analysing: ' + url + ' -> ' + e);
            }
        }
    });
}

// Manage the flag property of an object
function manageFlagProperty(item) {
    item.flag = stringSanitize(item.country);

    var config = require('./config');
    if (config.downloadImages) {
        downloadImage(item.flagSrc, stringFormat(config.paths.flagsData, item.flag));
    }

    delete item.flagSrc;
}

// Manage the logo property of an object
function manageLogoProperty(item) {
    item.logo = stringSanitize(item.team);

    var config = require('./config');
    if (config.downloadImages) {
        downloadImage(item.logoSrc, stringFormat(config.paths.logosData, item.logo));
    }

    delete item.logoSrc;
}

// Download an image in a path
function downloadImage(src, path) {
    var fileExists = require('file-exists');
    var request = require('request');
    var fs = require('fs');

    if (!fileExists.sync(path) && !path.endsWith('/.gif')) {
        request.head(src, (err) => {
            if (err) {
                log('Error while downloading image: ' + path + ' -> ' + err);
            } else {
                request(src).pipe(fs.createWriteStream(path)).on('close', () => {
                    log('Image downloaded: ' + path);
                });
            }
        });
    }
}

// Helper to run an update of data
function runUpdate(itemsExtended, updateData, arg) {
    if (!arg) {
        return;
    }

    // Run the update only on the specified item
    if (typeof arg === 'string') {
        var itemArg = itemsExtended.find(itemExtended => itemExtended.item.smallName.toLowerCase() === arg.toLowerCase());

        if (itemArg) {
            updateData(itemArg);
        } else {
            log('Item not found: ' + arg);
        }

        return;
    }

    // Run the update on all items
    for (var i = 0; i < itemsExtended.length; i++) {
        updateData(itemsExtended[i]);
    }
}

// Get the current round of a league
function getLeagueCurrentRound(resultsData) {
    if (!resultsData) {
        return 1;
    }

    var round = 1;

    for (var i = 0; i < resultsData.length; i++) {
        var result = resultsData[i];

        if (result.matches.filter(p => p.score === '-:-').length === result.matches.length) {
            break;
        }

        round = result.round;
    }

    return round;
}

// Log a message in the console and a log file
function log(message) {
    var winston = require('winston');
    winston.configure({
        transports: [new (winston.transports.File)({
                filename: 'info.log'
            }),
            new (winston.transports.Console)()
        ]
    });
    winston.info(message);
}

module.exports = {
    stringSanitize: stringSanitize,
    stringFormat: stringFormat,
    readJsonFile: readJsonFile,
    writeJsonFile: writeJsonFile,
    scrapeUrl: scrapeUrl,
    manageFlagProperty: manageFlagProperty,
    manageLogoProperty: manageLogoProperty,
    runUpdate: runUpdate,
    getLeagueCurrentRound: getLeagueCurrentRound,
    log: log
};<|endoftext|>


<commit_before>'use strict';

const express = require('express');
const projects = require('./projects.js');

const processPrj = (op, res, keys, data, options) => {
  projects[op](keys, data, options, (err, data) => {
    if (err) return void res.status(500).send(err);
    if (data === null) return void res.status(404).send('Item(s) not found');
    res.json(data);
  });
};

const send400 = res => void res.status(400).send('Bad request');

const testFields = /^\s*\w+\s*(,\s*\w+\s*)*$/;
const testSearch = /^\s*\w+\s*=\s*\w[\w\s]*$/;

module.exports = (dataRouter, done) => {
  const projectRouter = express.Router();
  dataRouter.use('/projects', projectRouter);

  projectRouter.get('/', (req, res) => {
    let fields = req.query.fields;
    let search = req.query.search;

    if (
      (fields && !testFields.test(fields)) ||
      (search && !testSearch.test(search))
    ) {
      return send400(res);
    }
    processPrj('getAllProjects', res, null, null, {fields, search});
  });

  projectRouter.get('/:pid', (req, res) => {
    let pid = Number(req.params.pid);
    if (Number.isNaN(pid)) return send400(res);
    processPrj('getProjectById', res, {pid});
  });

  projectRouter.get('/:pid/:tid', (req, res) => {
    let pid = Number(req.params.pid);
    let tid = Number(req.params.tid);
    if (Number.isNaN(pid) || Number.isNaN(tid)) return send400(res);
    processPrj('getTaskByTid', res, {pid, tid});
  });

  projectRouter.post('/', (req, res) => {
    let name = req.body.name;
    let descr = req.body.descr;
    if (name === undefined && descr === undefined) return send400(res);
    let data = {};
    if (name !== undefined) data.name = name;
    if (descr !== undefined) data.descr = descr;
    processPrj('addProject', res, null, data);
  });

  projectRouter.post('/:pid', (req, res) => {
    let pid = Number(req.params.pid);
    if (Number.isNaN(pid)) return send400(res);
    let descr = req.body.descr;
    let complete = req.body.complete;
    if (descr === undefined && complete === undefined) return send400(res);
    let data = {};
    if (descr !== undefined) data.descr = descr;
    if (complete !== undefined) data.complete = !!complete;
    processPrj('addTaskToProject', res, {pid}, data);
  });

  projectRouter.put('/:pid', (req, res) => {
    let pid = Number(req.params.pid);
    if (Number.isNaN(pid)) return send400(res);
    let name = req.body.name;
    let descr = req.body.descr;
    if (name === undefined && descr === undefined) return send400(res);
    let data = {};
    if (name !== undefined) data.name = name;
    if (descr !== undefined) data.descr = descr;
    processPrj('updateProject', res, {pid}, data);
  });

  projectRouter.put('/:pid/:tid', (req, res) => {
    let pid = Number(req.params.pid);
    let tid = Number(req.params.tid);
    if (Number.isNaN(pid) || Number.isNaN(tid)) return send400(res);
    let descr = req.body.descr;
    let complete = req.body.complete;
    if (descr === undefined && complete === undefined) return send400(res);
    let data = {};
    if (descr !== undefined) data.descr = descr;
    if (complete !== undefined) data.complete = !!complete;
    processPrj('updateTask', res, {pid, tid}, data);
  });

  projectRouter.delete('/:pid', (req, res) => {
    let pid = Number(req.params.pid);
    if (Number.isNaN(pid)) return send400(res);
    processPrj('deleteProject', res, {pid});
  });

  projectRouter.delete('/:pid/:tid', (req, res) => {
    let pid = Number(req.params.pid);
    let tid = Number(req.params.tid);
    if (Number.isNaN(pid) || Number.isNaN(tid)) return send400(res);
    processPrj('deleteTask', res, {pid, tid});
  });
  projects.init(done);
};
<commit_msg>chapter 9-2: validating through middleware<commit_after>'use strict';

const express = require('express');
const projects = require('./projects.js');

const send400 = res => void res.status(400).send('Bad request');

const testFields = /^\s*\w+\s*(,\s*\w+\s*)*$/;
const testSearch = /^\s*\w+\s*=\s*\w[\w\s]*$/;

const add$valid = (req, res, next) => {
  req.$valid = { };
  next();
};

const validatePid = (req, res, next) => {
  let pid = Number(req.params.pid);
  if (Number.isNaN(pid)) return send400(res);
  req.$valid.keys = {pid};
  next();
};

const validateTid = (req, res, next) => {
  let pid = Number(req.params.pid);
  let tid = Number(req.params.tid);
  if (Number.isNaN(pid) || Number.isNaN(tid)) return send400(res);
  req.$valid.keys = {pid, tid};
  next();
};

const validateOptions = (req, res, next) => {
  let fields = req.query.fields;
  let search = req.query.search;

  if (
    (fields && !testFields.test(fields)) ||
    (search && !testSearch.test(search))
  ) {
    return send400(res);
  }
  req.$valid.options = {fields, search};
  next();
};

const validatePrjData = (req, res, next) => {
  let name = req.body.name;
  let descr = req.body.descr;
  if (name === undefined && descr === undefined) return send400(res);
  let data = {};
  if (name !== undefined) data.name = name;
  if (descr !== undefined) data.descr = descr;
  req.$valid.data = data;
  next();
};

const validateTaskData = (req, res, next) => {
  let descr = req.body.descr;
  let complete = req.body.complete;
  if (descr === undefined && complete === undefined) return send400(res);
  let data = {};
  if (descr !== undefined) data.descr = descr;
  if (complete !== undefined) data.complete = !!complete;
  req.$valid.data = data;
  next();
};

const processPrj = (op, req, res) => {
  const valid = req.$valid;
  projects[op](valid.keys, valid.data, valid.options, (err, data) => {
    if (err) return void res.status(500).send(err);
    if (data === null) return void res.status(404).send('Item(s) not found');
    res.json(data);
  });
};

module.exports = (dataRouter, done) => {
  const projectRouter = express.Router();
  dataRouter.use('/projects', add$valid, projectRouter);

  projectRouter.get('/', validateOptions, (req, res) => {
    processPrj('getAllProjects', req, res);
  });

  projectRouter.get('/:pid', validatePid, (req, res) => {
    processPrj('getProjectById', req, res);
  });

  projectRouter.get('/:pid/:tid', validateTid, (req, res) => {
    processPrj('getTaskByTid', req, res);
  });

  projectRouter.post('/', validatePrjData, (req, res) => {
    processPrj('addProject', req, res);
  });

  projectRouter.post('/:pid', validatePid, validateTaskData, (req, res) => {
    processPrj('addTaskToProject', req, res);
  });

  projectRouter.put('/:pid', validatePid, validatePrjData, (req, res) => {
    processPrj('updateProject', req, res);
  });

  projectRouter.put('/:pid/:tid', validateTid, validateTaskData, (req, res) => {
    processPrj('updateTask', req, res);
  });

  projectRouter.delete('/:pid', validatePid, (req, res) => {
    processPrj('deleteProject', req, res);
  });

  projectRouter.delete('/:pid/:tid', validateTid, (req, res) => {
    processPrj('deleteTask', req, res);
  });

  projects.init(done);
};
<|endoftext|>