<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * A general type for keyboard dragging.  Updates a position Property with keyboard interaction. Objects can be
 * dragged in two dimensions with the arrow keys and with the WASD keys.
 *
 * JavaScript does not natively handle multiple 'keydown' events at once, so we have a custom implementation that
 * tracks which keys are down and for how long in a step() function. To function, this drag handler requires a view step.
 *
 * @author Jesse Greenberg
 */

define( function( require ) {
  'use strict';

  // modules
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Vector2 = require( 'DOT/Vector2' );
  var Input = require( 'SCENERY/input/Input' );
  var Bounds2 = require( 'DOT/Bounds2' );

  /**
   * @constructor
   * @param {Property} positionProperty
   * @param {Object} options
   */
  function KeyboardDragHandler( positionProperty, options ) {

    var self = this;
    options = _.extend( {
      positionDelta: 5, // while direction key is down, 1D delta for the positionProperty
      shiftKeyMultiplier: 2, // if shift key is down, dragging speed will be changed by this multiplier
      dragBounds: Bounds2.EVERYTHING, // position will be limited to these bounds
      onDrag: function() {}, // called every drag where the position changes
      startDrag: function( event ) {}, // called at the very start of the drag
      endDrag: function( event ) {} // called at the end of the dragging interaction
    }, options );

    // @private - tracks the state of the keyboard, array elements are objects with key-value pairs of keyCode {number},
    // isDown {boolean}, and timeDown {number}. JavaScript doesn't handle multiple key presses, so we track
    // which keys are currently down and update via step()
    this.keyState = [];

    // @private - groups of keys that will change the position property or have other behavior.  Entries of the array will look
    // like { keys: <Array.number>, callback: <Function> }.  Add hotkey groups with this.addHotkeyGroup
    this.hotkeyGroups = [];

    // @private - a key in the hot key group that is currently down.  Object collects { keyCode: <number>, timeDown: <number> }
    // when one of the hotkeys are pressed down, this
    this.hotKeyDown = {};

    // @private - the change in position (in model coordinates) that will be applied by dragging with the keyboard
    this.positionDelta = options.positionDelta;

    // @private
    this.shiftKeyMultiplier = options.shiftKeyMultiplier;
    this.positionProperty = positionProperty;
    this._dragBounds = options.dragBounds;

    // @private
    this.onDrag = options.onDrag;

    // @private {keys: <Array.number>, callback: <Function>} - when a hotkey group is down, dragging
    // will be disabled until all hotkeys are up again
    this.keyGroupDown = null;

    // @private {boolean} - when a hotkey group is pressed down, dragging will be disabled until
    // all keys are up again in case any of the movemene keys are also used as hotkeys
    this.draggingDisabled = false;

    // @public (read-only) - listener that will be added to the node for dragging behavior, made public on the Object
    // so that a KeyboardDragHandler can be added via myNode.addAccessibleInputListener( myKeyboardDragHandler )
    this.keydown = function( event ) {

      // if the key is already down, don't do anything else (we don't want to create a new keystate object
      // for a key that is already being tracked and down, nor call startDrag every keydown event)
      if ( self.keyInListDown( [ event.keyCode ] ) ) { return; }

      // required to work with Safari and VoiceOver, otherwise arrow keys will move virtual cursor
      if ( Input.isArrowKey( event.keyCode ) ) { event.preventDefault(); }

      // update the key state
      self.keyState.push( {
        keyDown: true,
        keyCode: event.keyCode,
        timeDown: 0 // in ms
      } );

      if ( self.movementKeysDown ) {
        options.startDrag( event );
      }
    };

    // @public (read-only) - listener that will be added to the node for dragging behavior, made public on the Object
    // so that a KeyboardDragHandler can be added via myNode.addAccessibleInputListener( myKeyboardDragHandler )
    this.keyup = function( event ) {
      var moveKeysDown = self.movementKeysDown;

      for ( var i = 0; i < self.keyState.length; i++ ) {
        if ( event.keyCode === self.keyState[ i ].keyCode ) {
          self.keyState.splice( i, 1 );
        }
      }

      // if movement keys are no longer down after keyup, call the optional end drag function
      var moveKeysStillDown = self.movementKeysDown;
      if ( !moveKeysStillDown && moveKeysDown !== moveKeysStillDown ) {
        options.endDrag( event );
      }
    };

    // @public (read-only) - reset when the focus is blurred from the node being dragged.
    this.blur = function() {
      self.reset();
    };
  }

  sceneryPhet.register( 'KeyboardDragHandler', KeyboardDragHandler );

  return inherit( Object, KeyboardDragHandler, {

    /**
     * Step function for the drag handler. JavaScript does not natively handle many keydown events at once,
     * so we need to track the state of the keyboard in an Object and update the position Property in a step
     * function based on the keyboard state object every animation frame.  In order for the drag handler to
     * work, call this function somewhere in ScreenView.step().
     *
     * @public
     */
    step: function( dt ) {

      // for each key that is still down, increment the tracked time that they have been down
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyDown ) {
          this.keyState[ i ].timeDown += dt;
        }
      }

      // check to see if any hotkey combinations are down
      for ( var j = 0; j < this.hotkeyGroups.length; j++ ) {
        var hotkeysDownList = [];
        var keys = this.hotkeyGroups[ j ].keys;

        for ( var k = 0; k < keys.length; k++ ) {
          for ( var l = 0; l < this.keyState.length; l++ ) {
            if ( this.keyState[ l ].keyCode === keys[ k ] ) {
              hotkeysDownList.push( this.keyState[ l ] );
            }
          }
        }

        // the hotkeysDownList array order should match the order of the key group, so now we just need to make
        // sure that the key down times are in the right order
        var keysInOrder = false;
        for ( var m = 0; m < hotkeysDownList.length - 1; m++ ) {
          if ( hotkeysDownList[ m + 1 ] && hotkeysDownList[ m ].timeDown > hotkeysDownList[ m + 1 ].timeDown ) {
            keysInOrder = true;
          }
        }

        // if keys are in order, call the callback associated with the group, and disable dragging until
        // all hotkeys associated with that group are up again
        if ( keysInOrder ) {
          this.keyGroupDown = this.hotkeyGroups[ j ];
          this.hotkeyGroups[ j ].callback();
        }
      }

      // if a key group is down, check to see if any of those keys are still down - if so, we will disable dragging
      // until all of them are up
      if ( this.keyGroupDown ) {
        if ( this.keyInListDown( this.keyGroupDown.keys ) ) {
          this.draggingDisabled = true;
        }
        else {
          this.draggingDisabled = false;

          // keys are no longer down, clear the group
          this.keyGroupDown = null;
        }
      }

      if ( !this.draggingDisabled ) {

        // handle the change in position
        var deltaX = 0;
        var deltaY = 0;
        var positionDelta = this.shiftKeyDown() ? ( this.positionDelta * this.shiftKeyMultiplier ) : this.positionDelta;

        if ( this.leftMovementKeysDown() ) {
          deltaX = -positionDelta;
        }
        if ( this.rightMovementKeysDown() ) {
          deltaX = positionDelta;
        }
        if ( this.upMovementKeysDown() ) {
          deltaY = -positionDelta;
        }
        if ( this.downMovementKeysDown() ) {
          deltaY = positionDelta;
        }

        // determine if the new position is within the constraints of the drag bounds
        var vectorDelta = new Vector2( deltaX, deltaY );
        var newPosition = this.positionProperty.get().plus( vectorDelta );
        newPosition = this._dragBounds.closestPointTo( newPosition );

        // update the position if it is different
        if ( !newPosition.equals( this.positionProperty.get() ) ) {
          this.positionProperty.set( newPosition );

          // on successful drag, call the optional onDrag function
          this.onDrag();
        }
      }
    },

    getKeyInState: function( keyCode ) {
      var keyObject = null;
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyCode === keyCode ) {
          keyObject = this.keyState[ i ];
        }
      }
      return keyObject;
    },

    /**
     * Returns true if any of the keys in the list are currently down.
     *
     * @param  {Array.<number>} keys
     * @return {boolean}
     */
    keyInListDown: function( keys ) {
      var keyIsDown = false;
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyDown ) {
          for ( var j = 0; j < keys.length; j++ ) {
            if ( keys[ j ] === this.keyState[ i ].keyCode ) {
              keyIsDown = true;
              break;
            }
          }
        }
        if ( keyIsDown ) {
          // no need to keep looking
          break;
        }
      }

      return keyIsDown;
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    leftMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_A, Input.KEY_LEFT_ARROW ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    rightMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_RIGHT_ARROW, Input.KEY_D ] );
    },

    /**
     * Returns true if the keystate indicatest that a key is down that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    upMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_UP_ARROW, Input.KEY_W ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the upject down.
     *
     * @public
     * @return {boolean}
     */
    downMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_DOWN_ARROW, Input.KEY_S ] );
    },

    /**
     * Returns true if any of the movement keys are down (arrow keys or WASD keys).
     *
     * @return {boolean}
     */
    getMovementKeysDown: function() {
      return this.rightMovementKeysDown() || this.leftMovementKeysDown() ||
             this.upMovementKeysDown() || this.downMovementKeysDown();
    },
    get movementKeysDown() { return this.getMovementKeysDown(); },

    /**
     * Returns true if the enter key is currently pressed down.
     *
     * @return {boolean}
     */
    enterKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_ENTER ] );
    },

    /**
     * Returns true if the keystate indicates that the shift key is currently down.
     *
     * @return {boolean}
     */
    shiftKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_SHIFT ] );
    },

    /**
     * Sets the bounds for dragging with the keyboard.
     *
     * @public
     * @param {Bounds2} dragBounds
     */
    setDragBounds: function( dragBounds ) {
      this._dragBounds = dragBounds.copy();
      this.positionProperty.set( this._dragBounds.closestPointTo( this.positionProperty.get() ) );
    },
    set dragBounds( dragBounds ) { this.setDragBounds( dragBounds ); },

    /**
     * Get the Bounds2 Object wich constrains the possible Vector2 values of the position Property.
     *
     * @public
     * @return {Bounds2}
     */
    getDragBounds: function() {
      return this._dragBounds;
    },
    get dragBounds() { return this.getDragBounds(); },

    /**
     * Add a set of hotkeys that behave such that the desired callback will be called when
     * all keys listed in the array are pressed down in order.
     *
     * @param {Object} hotKeyGroup - { keys: [].<number>, callback: function }
     */
    addHotkeyGroup: function( hotkeyGroup ) {
      this.hotkeyGroups.push( hotkeyGroup );
    },

    /**
     * Add mutliple sets of hotkey groups that behave such hat the desired callback will be called
     * when all keys listed in the array are pressed down in order.  Behaves much like addHotkeyGroup,
     * but allows you to add multiple groups at one time.
     *
     * @param {[].<string>} hotKeyGroups
     */
    addHotkeyGroups: function( hotKeyGroups ) {
      for ( var i = 0; i < hotKeyGroups.length; i++ ) {
        this.addHotkeyGroup( hotKeyGroups[ i ] );
      }
    },

    /**
     * Reset the keystate Object tracking which keys are currently pressed down.
     *
     * @public
     */
    reset: function() {
      this.keyState = [];
    }
  }, {

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    isLeftMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_A || keyCode === Input.KEY_LEFT_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    isRightMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_D || keyCode === Input.KEY_RIGHT_ARROW;
    },

    /**
     * Returns true if the keycode corresponse to a key that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    isUpMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_W || keyCode === Input.KEY_UP_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the upject down.
     *
     * @public
     * @return {boolean}
     */
    isDownMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_S || keyCode === Input.KEY_DOWN_ARROW;
    },
  } );
} );
<commit_msg>Sorted require statements<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * A general type for keyboard dragging.  Updates a position Property with keyboard interaction. Objects can be
 * dragged in two dimensions with the arrow keys and with the WASD keys.
 *
 * JavaScript does not natively handle multiple 'keydown' events at once, so we have a custom implementation that
 * tracks which keys are down and for how long in a step() function. To function, this drag handler requires a view step.
 *
 * @author Jesse Greenberg
 */

define( function( require ) {
  'use strict';

  // modules
  var Bounds2 = require( 'DOT/Bounds2' );
  var inherit = require( 'PHET_CORE/inherit' );
  var Input = require( 'SCENERY/input/Input' );
  var sceneryPhet = require( 'SCENERY_PHET/sceneryPhet' );
  var Vector2 = require( 'DOT/Vector2' );

  /**
   * @constructor
   * @param {Property} positionProperty
   * @param {Object} options
   */
  function KeyboardDragHandler( positionProperty, options ) {

    var self = this;
    options = _.extend( {
      positionDelta: 5, // while direction key is down, 1D delta for the positionProperty
      shiftKeyMultiplier: 2, // if shift key is down, dragging speed will be changed by this multiplier
      dragBounds: Bounds2.EVERYTHING, // position will be limited to these bounds
      onDrag: function() {}, // called every drag where the position changes
      startDrag: function( event ) {}, // called at the very start of the drag
      endDrag: function( event ) {} // called at the end of the dragging interaction
    }, options );

    // @private - tracks the state of the keyboard, array elements are objects with key-value pairs of keyCode {number},
    // isDown {boolean}, and timeDown {number}. JavaScript doesn't handle multiple key presses, so we track
    // which keys are currently down and update via step()
    this.keyState = [];

    // @private - groups of keys that will change the position property or have other behavior.  Entries of the array will look
    // like { keys: <Array.number>, callback: <Function> }.  Add hotkey groups with this.addHotkeyGroup
    this.hotkeyGroups = [];

    // @private - a key in the hot key group that is currently down.  Object collects { keyCode: <number>, timeDown: <number> }
    // when one of the hotkeys are pressed down, this
    this.hotKeyDown = {};

    // @private - the change in position (in model coordinates) that will be applied by dragging with the keyboard
    this.positionDelta = options.positionDelta;

    // @private
    this.shiftKeyMultiplier = options.shiftKeyMultiplier;
    this.positionProperty = positionProperty;
    this._dragBounds = options.dragBounds;

    // @private
    this.onDrag = options.onDrag;

    // @private {keys: <Array.number>, callback: <Function>} - when a hotkey group is down, dragging
    // will be disabled until all hotkeys are up again
    this.keyGroupDown = null;

    // @private {boolean} - when a hotkey group is pressed down, dragging will be disabled until
    // all keys are up again in case any of the movemene keys are also used as hotkeys
    this.draggingDisabled = false;

    // @public (read-only) - listener that will be added to the node for dragging behavior, made public on the Object
    // so that a KeyboardDragHandler can be added via myNode.addAccessibleInputListener( myKeyboardDragHandler )
    this.keydown = function( event ) {

      // if the key is already down, don't do anything else (we don't want to create a new keystate object
      // for a key that is already being tracked and down, nor call startDrag every keydown event)
      if ( self.keyInListDown( [ event.keyCode ] ) ) { return; }

      // required to work with Safari and VoiceOver, otherwise arrow keys will move virtual cursor
      if ( Input.isArrowKey( event.keyCode ) ) { event.preventDefault(); }

      // update the key state
      self.keyState.push( {
        keyDown: true,
        keyCode: event.keyCode,
        timeDown: 0 // in ms
      } );

      if ( self.movementKeysDown ) {
        options.startDrag( event );
      }
    };

    // @public (read-only) - listener that will be added to the node for dragging behavior, made public on the Object
    // so that a KeyboardDragHandler can be added via myNode.addAccessibleInputListener( myKeyboardDragHandler )
    this.keyup = function( event ) {
      var moveKeysDown = self.movementKeysDown;

      for ( var i = 0; i < self.keyState.length; i++ ) {
        if ( event.keyCode === self.keyState[ i ].keyCode ) {
          self.keyState.splice( i, 1 );
        }
      }

      // if movement keys are no longer down after keyup, call the optional end drag function
      var moveKeysStillDown = self.movementKeysDown;
      if ( !moveKeysStillDown && moveKeysDown !== moveKeysStillDown ) {
        options.endDrag( event );
      }
    };

    // @public (read-only) - reset when the focus is blurred from the node being dragged.
    this.blur = function() {
      self.reset();
    };
  }

  sceneryPhet.register( 'KeyboardDragHandler', KeyboardDragHandler );

  return inherit( Object, KeyboardDragHandler, {

    /**
     * Step function for the drag handler. JavaScript does not natively handle many keydown events at once,
     * so we need to track the state of the keyboard in an Object and update the position Property in a step
     * function based on the keyboard state object every animation frame.  In order for the drag handler to
     * work, call this function somewhere in ScreenView.step().
     *
     * @public
     */
    step: function( dt ) {

      // for each key that is still down, increment the tracked time that they have been down
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyDown ) {
          this.keyState[ i ].timeDown += dt;
        }
      }

      // check to see if any hotkey combinations are down
      for ( var j = 0; j < this.hotkeyGroups.length; j++ ) {
        var hotkeysDownList = [];
        var keys = this.hotkeyGroups[ j ].keys;

        for ( var k = 0; k < keys.length; k++ ) {
          for ( var l = 0; l < this.keyState.length; l++ ) {
            if ( this.keyState[ l ].keyCode === keys[ k ] ) {
              hotkeysDownList.push( this.keyState[ l ] );
            }
          }
        }

        // the hotkeysDownList array order should match the order of the key group, so now we just need to make
        // sure that the key down times are in the right order
        var keysInOrder = false;
        for ( var m = 0; m < hotkeysDownList.length - 1; m++ ) {
          if ( hotkeysDownList[ m + 1 ] && hotkeysDownList[ m ].timeDown > hotkeysDownList[ m + 1 ].timeDown ) {
            keysInOrder = true;
          }
        }

        // if keys are in order, call the callback associated with the group, and disable dragging until
        // all hotkeys associated with that group are up again
        if ( keysInOrder ) {
          this.keyGroupDown = this.hotkeyGroups[ j ];
          this.hotkeyGroups[ j ].callback();
        }
      }

      // if a key group is down, check to see if any of those keys are still down - if so, we will disable dragging
      // until all of them are up
      if ( this.keyGroupDown ) {
        if ( this.keyInListDown( this.keyGroupDown.keys ) ) {
          this.draggingDisabled = true;
        }
        else {
          this.draggingDisabled = false;

          // keys are no longer down, clear the group
          this.keyGroupDown = null;
        }
      }

      if ( !this.draggingDisabled ) {

        // handle the change in position
        var deltaX = 0;
        var deltaY = 0;
        var positionDelta = this.shiftKeyDown() ? ( this.positionDelta * this.shiftKeyMultiplier ) : this.positionDelta;

        if ( this.leftMovementKeysDown() ) {
          deltaX = -positionDelta;
        }
        if ( this.rightMovementKeysDown() ) {
          deltaX = positionDelta;
        }
        if ( this.upMovementKeysDown() ) {
          deltaY = -positionDelta;
        }
        if ( this.downMovementKeysDown() ) {
          deltaY = positionDelta;
        }

        // determine if the new position is within the constraints of the drag bounds
        var vectorDelta = new Vector2( deltaX, deltaY );
        var newPosition = this.positionProperty.get().plus( vectorDelta );
        newPosition = this._dragBounds.closestPointTo( newPosition );

        // update the position if it is different
        if ( !newPosition.equals( this.positionProperty.get() ) ) {
          this.positionProperty.set( newPosition );

          // on successful drag, call the optional onDrag function
          this.onDrag();
        }
      }
    },

    getKeyInState: function( keyCode ) {
      var keyObject = null;
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyCode === keyCode ) {
          keyObject = this.keyState[ i ];
        }
      }
      return keyObject;
    },

    /**
     * Returns true if any of the keys in the list are currently down.
     *
     * @param  {Array.<number>} keys
     * @return {boolean}
     */
    keyInListDown: function( keys ) {
      var keyIsDown = false;
      for ( var i = 0; i < this.keyState.length; i++ ) {
        if ( this.keyState[ i ].keyDown ) {
          for ( var j = 0; j < keys.length; j++ ) {
            if ( keys[ j ] === this.keyState[ i ].keyCode ) {
              keyIsDown = true;
              break;
            }
          }
        }
        if ( keyIsDown ) {
          // no need to keep looking
          break;
        }
      }

      return keyIsDown;
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    leftMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_A, Input.KEY_LEFT_ARROW ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    rightMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_RIGHT_ARROW, Input.KEY_D ] );
    },

    /**
     * Returns true if the keystate indicatest that a key is down that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    upMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_UP_ARROW, Input.KEY_W ] );
    },

    /**
     * Returns true if the keystate indicates that a key is down that should move the upject down.
     *
     * @public
     * @return {boolean}
     */
    downMovementKeysDown: function() {
      return this.keyInListDown( [ Input.KEY_DOWN_ARROW, Input.KEY_S ] );
    },

    /**
     * Returns true if any of the movement keys are down (arrow keys or WASD keys).
     *
     * @return {boolean}
     */
    getMovementKeysDown: function() {
      return this.rightMovementKeysDown() || this.leftMovementKeysDown() ||
             this.upMovementKeysDown() || this.downMovementKeysDown();
    },
    get movementKeysDown() { return this.getMovementKeysDown(); },

    /**
     * Returns true if the enter key is currently pressed down.
     *
     * @return {boolean}
     */
    enterKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_ENTER ] );
    },

    /**
     * Returns true if the keystate indicates that the shift key is currently down.
     *
     * @return {boolean}
     */
    shiftKeyDown: function() {
      return this.keyInListDown( [ Input.KEY_SHIFT ] );
    },

    /**
     * Sets the bounds for dragging with the keyboard.
     *
     * @public
     * @param {Bounds2} dragBounds
     */
    setDragBounds: function( dragBounds ) {
      this._dragBounds = dragBounds.copy();
      this.positionProperty.set( this._dragBounds.closestPointTo( this.positionProperty.get() ) );
    },
    set dragBounds( dragBounds ) { this.setDragBounds( dragBounds ); },

    /**
     * Get the Bounds2 Object wich constrains the possible Vector2 values of the position Property.
     *
     * @public
     * @return {Bounds2}
     */
    getDragBounds: function() {
      return this._dragBounds;
    },
    get dragBounds() { return this.getDragBounds(); },

    /**
     * Add a set of hotkeys that behave such that the desired callback will be called when
     * all keys listed in the array are pressed down in order.
     *
     * @param {Object} hotKeyGroup - { keys: [].<number>, callback: function }
     */
    addHotkeyGroup: function( hotkeyGroup ) {
      this.hotkeyGroups.push( hotkeyGroup );
    },

    /**
     * Add mutliple sets of hotkey groups that behave such hat the desired callback will be called
     * when all keys listed in the array are pressed down in order.  Behaves much like addHotkeyGroup,
     * but allows you to add multiple groups at one time.
     *
     * @param {[].<string>} hotKeyGroups
     */
    addHotkeyGroups: function( hotKeyGroups ) {
      for ( var i = 0; i < hotKeyGroups.length; i++ ) {
        this.addHotkeyGroup( hotKeyGroups[ i ] );
      }
    },

    /**
     * Reset the keystate Object tracking which keys are currently pressed down.
     *
     * @public
     */
    reset: function() {
      this.keyState = [];
    }
  }, {

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the left.
     *
     * @private
     * @return {boolean}
     */
    isLeftMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_A || keyCode === Input.KEY_LEFT_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the object to the right.
     *
     * @public
     * @return {boolean}
     */
    isRightMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_D || keyCode === Input.KEY_RIGHT_ARROW;
    },

    /**
     * Returns true if the keycode corresponse to a key that should move the object up.
     *
     * @public
     * @return {boolean}
     */
    isUpMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_W || keyCode === Input.KEY_UP_ARROW;
    },

    /**
     * Returns true if the keycode corresponds to a key that should move the upject down.
     *
     * @public
     * @return {boolean}
     */
    isDownMovementKey: function( keyCode ) {
      return keyCode === Input.KEY_S || keyCode === Input.KEY_DOWN_ARROW;
    },
  } );
} );
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import DocumentTitle from 'react-document-title';
import styled from 'styled-components';
import { layout } from '../theme/variables';

import PageTitle from '../components/PageTitle';

const Main = styled.main`flex: ${layout.mainFlex};`;

const Home = () => (
  <DocumentTitle>

      <PageTitle title="Home" />

        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi omnis rerum eligendi
        blanditiis quisquam animi ducimus maiores voluptatem repellendus, sapiente asperiores at
        unde debitis quos culpa obcaecati sed, corrupti nulla!

  </DocumentTitle>
);

Home.propTypes = {
  title: PropTypes.string.isRequired,
};

export default Home;
<commit_msg>Add some todos and inspo to home page<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import DocumentTitle from 'react-document-title';
import styled from 'styled-components';
import { layout } from '../theme/variables';

import PageTitle from '../components/PageTitle';

const Main = styled.main`flex: ${layout.mainFlex};`;

const Home = () => (
  <DocumentTitle>

      <PageTitle title="Home" />

TODO

          <input
            type="checkbox"
            name="animate_active_link"
            id="animate_active_link"
            key="animate_active_link"
          />animate active link

          <input
            type="checkbox"
            name="Modernizr_integration"
            id="Modernizr_integration"
            key="Modernizr_integration"
          />Modernizr integration

          <input
            type="checkbox"
            name="Flexbox_layout"
            id="Flexbox_layout"
            key="Flexbox_layout"
          />Flexbox layout

          <input
            type="checkbox"
            name="CSS_Grid_layout"
            id="CSS_Grid_layout"
            key="CSS_Grid_layout"
          />CSS Grid layout

          <input
            type="checkbox"
            name="tagline_arrows"
            id="tagline_arrows"
            key="tagline_arrows"
          />tagline arrows

          <input
            type="checkbox"
            name="firebase_database"
            id="firebase_database"
            key="firebase_database"
          />firebase database

          <input type="checkbox" name="loading_icon" id="loading_icon" key="loading_icon" />loading
          icon

Inspiration

            Sam Bedingfield

            Wes Bos

            Ives van Hoorne

            Line 25 list of 30 portfolios

  </DocumentTitle>
);

Home.propTypes = {
  title: PropTypes.string.isRequired,
};

export default Home;
<|endoftext|>


<commit_before>/**
* @license Apache-2.0
*
* Copyright (c) 2019 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var resolve = require( 'path' ).resolve;
var fastify = require( 'fastify' );
var helmet = require( '@fastify/helmet' );
var fastifyStatic = require( '@fastify/static' );
var isFunction = require( '@stdlib/assert/is-function' );
var isString = require( '@stdlib/assert/is-string' );
var cwd = require( '@stdlib/process/cwd' );
var copy = require( '@stdlib/utils/copy' );
var readFile = require( '@stdlib/fs/read-file' ).sync;
var format = require( '@stdlib/string/format' );
var Template = require( 'template' );
var routes = require( './routes' );
var DEFAULTS = require( './defaults.json' );
var validate = require( './validate.js' );

// MAIN //

/**
* Returns a function which creates an HTTP server for serving API documentation.
*
* @param {Options} [options] - server options
* @param {string} [options.address="127.0.0.1"] - server address
* @param {string} [options.hostname] - server hostname
* @param {string} [options.latest=""] - latest documentation version (e.g., `v0.0.90`)
* @param {(boolean|string)} [options.logger=false] - either a boolean indicating whether to enable logging or a log level
* @param {NonNegativeInteger} [options.port=0] - server port
* @param {(string|StringArray)} [options.prefix="/"] - URL path prefix(es) used to create a virtual mount path(s) for static directories
* @param {string} [options.root] - root documentation directory
* @param {(string|StringArray)} [options.static] - path of the directory (or directories) containing static files to serve
* @param {boolean} [options.trustProxy=false] - boolean indicating whether to trust `X-forwarded-by` headers when the server is sitting behind a proxy
* @param {boolean} [options.ignoreTrailingSlash=true] - boolean indicating whether to ignore trailing slashes in routes
* @throws {TypeError} must provide valid options
* @throws {Error} must provide valid options
* @returns {Function} function which creates an HTTP server
*
* @example
* var App = require( 'my-app' );
*
* var opts = {
*     'port': 7331,
*     'address': '0.0.0.0'
* };
* var createServer = httpServer( opts );
*
* function done( error, fastify ) {
*     if ( error ) {
*         return console.error( error.message );
*     }
*     console.log( 'Success!' );
*     fastify.close();
* }
*
* createServer( App, done );
*/
function httpServer( options ) {
	var opts;
	var err;
	var dir;
	var i;

	opts = copy( DEFAULTS );
	if ( arguments.length ) {
		err = validate( opts, options );
		if ( err ) {
			throw err;
		}
	}
	if ( opts.hostname === void 0 ) {
		opts.hostname = opts.address;
	}
	dir = cwd();
	opts.root = resolve( dir, opts.root );
	if ( opts.static ) {
		if ( isString( opts.static ) ) {
			if ( !isString( opts.prefix ) ) {
				throw new TypeError( format( 'invalid option. Must provide a string `prefix` when `static` is a string. Option: `%s`.', opts.prefix ) );
			}
			opts.static = resolve( dir, opts.static );
		} else {
			if ( !isString( opts.prefix ) && opts.prefix.length !== opts.static.length ) { // eslint-disable-line max-len
				throw new Error( 'invalid option. Number of prefixes must equal the number of static directories.' );
			}
			for ( i = 0; i < opts.static.length; i++ ) {
				opts.static[ i ] = resolve( dir, opts.static[ i ] );
			}
		}
	}
	return createServer;

	/**
	* Creates an HTTP server.
	*
	* @private
	* @param {ReactComponent} App - application component
	* @param {Callback} done - function to invoke after creating a server
	* @throws {TypeError} first argument must be an application component
	* @throws {TypeError} second argument must be a function
	* @throws {Error} unable to load application template
	*/
	function createServer( App, done ) {
		var tmpl;
		var f;
		var i;
		if ( !isFunction( App ) ) {
			throw new TypeError( format( 'invalid argument. First argument must be an application component. Value: `%s`.', App ) );
		}
		if ( !isFunction( done ) ) {
			throw new TypeError( format( 'invalid argument. Second argument must be a function. Value: `%s`.', done ) );
		}
		// Attempt to load the application shell/template...
		tmpl = resolve( opts.root, 'index.html' );
		tmpl = readFile( tmpl, {
			'encoding': 'utf8'
		});
		if ( tmpl instanceof Error ) {
			throw tmpl;
		}
		tmpl = new Template( tmpl );

		// Create a fastify instance:
		f = fastify( opts );

		// Set basic security headers:
		f.register( helmet, {
			'contentSecurityPolicy': false
		});

		// Decorate route handler `this` contexts with additional functionality:
		f.decorate( 'App', App );
		f.decorate( 'appinfo', opts.app );
		f.decorate( 'template', tmpl );
		f.decorate( 'rootDir', opts.root );
		f.decorate( 'latestVersion', opts.latest );

		// Decorate each `request` object with a `locals` object for passing along intermediate results within middleware handlers:
		f.decorateRequest( 'locals', null );

		// Register a plugin for serving static files:
		if ( opts.static ) {
			if ( isString( opts.prefix ) ) {
				f.register( fastifyStatic, {
					'root': opts.static,
					'prefix': opts.prefix,
					'wildcard': false
				});
			} else {
				for ( i = 0; i < opts.prefix.length; i++ ) {
					f.register( fastifyStatic, {
						'root': opts.static[ i ],
						'prefix': opts.prefix[ i ],
						'wildcard': false,
						'decorateReply': ( i === 0 ) // only one plugin registration is allowed to add a reply decorator
					});
				}
			}
		}
		// Register routes:
		f.register( routes, {} );

		// Add a hook to perform clean-up tasks when the server is closed:
		f.addHook( 'onClose', onClose );

		// Add a hook to configure the `locals` object for each request:
		f.addHook( 'onRequest', onRequest );

		// Start listening for HTTP requests:
		f.listen( opts.port, opts.hostname, onListen );

		/**
		* Callback invoked once a server is listening and ready to accept HTTP requests.
		*
		* @private
		* @param {(Error|null)} error - error object
		* @param {string} address - server address
		* @returns {void}
		*/
		function onListen( error, address ) {
			if ( error ) {
				f.log.error( error.message );
				return done( error );
			}
			f.log.info( 'HTTP server initialized. Server is listening for requests on %s.', address );
			done( null, f );
		}

		/**
		* Callback invoked upon receiving an HTTP request.
		*
		* @private
		* @param {Object} request - request object
		* @param {Object} reply - reply object
		* @param {Callback} clbk - callback to invoke once finished
		*/
		function onRequest( request, reply, clbk ) {
			request.locals = {};
			clbk();
		}

		/**
		* Callback invoked when a server is closed.
		*
		* @private
		* @param {Object} instance - fastify instance
		* @param {Callback} clbk - callback to invoke once finished
		*/
		function onClose( instance, clbk ) {
			setTimeout( onEnd, 0 );

			/**
			* Callback invoked upon performing clean-up tasks.
			*
			* @private
			*/
			function onEnd() {
				clbk();
			}
		}
	}
}

// EXPORTS //

module.exports = httpServer;
<commit_msg>Add support for reading cookies in server<commit_after>/**
* @license Apache-2.0
*
* Copyright (c) 2019 The Stdlib Authors.
*
* 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.
*/

'use strict';

// MODULES //

var resolve = require( 'path' ).resolve;
var fastify = require( 'fastify' );
var helmet = require( '@fastify/helmet' );
var cookie = require( '@fastify/cookie' );
var fastifyStatic = require( '@fastify/static' );
var isFunction = require( '@stdlib/assert/is-function' );
var isString = require( '@stdlib/assert/is-string' );
var cwd = require( '@stdlib/process/cwd' );
var copy = require( '@stdlib/utils/copy' );
var readFile = require( '@stdlib/fs/read-file' ).sync;
var format = require( '@stdlib/string/format' );
var Template = require( 'template' );
var routes = require( './routes' );
var DEFAULTS = require( './defaults.json' );
var validate = require( './validate.js' );

// MAIN //

/**
* Returns a function which creates an HTTP server for serving API documentation.
*
* @param {Options} [options] - server options
* @param {string} [options.address="127.0.0.1"] - server address
* @param {string} [options.hostname] - server hostname
* @param {string} [options.latest=""] - latest documentation version (e.g., `v0.0.90`)
* @param {(boolean|string)} [options.logger=false] - either a boolean indicating whether to enable logging or a log level
* @param {NonNegativeInteger} [options.port=0] - server port
* @param {(string|StringArray)} [options.prefix="/"] - URL path prefix(es) used to create a virtual mount path(s) for static directories
* @param {string} [options.root] - root documentation directory
* @param {(string|StringArray)} [options.static] - path of the directory (or directories) containing static files to serve
* @param {boolean} [options.trustProxy=false] - boolean indicating whether to trust `X-forwarded-by` headers when the server is sitting behind a proxy
* @param {boolean} [options.ignoreTrailingSlash=true] - boolean indicating whether to ignore trailing slashes in routes
* @throws {TypeError} must provide valid options
* @throws {Error} must provide valid options
* @returns {Function} function which creates an HTTP server
*
* @example
* var App = require( 'my-app' );
*
* var opts = {
*     'port': 7331,
*     'address': '0.0.0.0'
* };
* var createServer = httpServer( opts );
*
* function done( error, fastify ) {
*     if ( error ) {
*         return console.error( error.message );
*     }
*     console.log( 'Success!' );
*     fastify.close();
* }
*
* createServer( App, done );
*/
function httpServer( options ) {
	var opts;
	var err;
	var dir;
	var i;

	opts = copy( DEFAULTS );
	if ( arguments.length ) {
		err = validate( opts, options );
		if ( err ) {
			throw err;
		}
	}
	if ( opts.hostname === void 0 ) {
		opts.hostname = opts.address;
	}
	dir = cwd();
	opts.root = resolve( dir, opts.root );
	if ( opts.static ) {
		if ( isString( opts.static ) ) {
			if ( !isString( opts.prefix ) ) {
				throw new TypeError( format( 'invalid option. Must provide a string `prefix` when `static` is a string. Option: `%s`.', opts.prefix ) );
			}
			opts.static = resolve( dir, opts.static );
		} else {
			if ( !isString( opts.prefix ) && opts.prefix.length !== opts.static.length ) { // eslint-disable-line max-len
				throw new Error( 'invalid option. Number of prefixes must equal the number of static directories.' );
			}
			for ( i = 0; i < opts.static.length; i++ ) {
				opts.static[ i ] = resolve( dir, opts.static[ i ] );
			}
		}
	}
	return createServer;

	/**
	* Creates an HTTP server.
	*
	* @private
	* @param {ReactComponent} App - application component
	* @param {Callback} done - function to invoke after creating a server
	* @throws {TypeError} first argument must be an application component
	* @throws {TypeError} second argument must be a function
	* @throws {Error} unable to load application template
	*/
	function createServer( App, done ) {
		var tmpl;
		var f;
		var i;
		if ( !isFunction( App ) ) {
			throw new TypeError( format( 'invalid argument. First argument must be an application component. Value: `%s`.', App ) );
		}
		if ( !isFunction( done ) ) {
			throw new TypeError( format( 'invalid argument. Second argument must be a function. Value: `%s`.', done ) );
		}
		// Attempt to load the application shell/template...
		tmpl = resolve( opts.root, 'index.html' );
		tmpl = readFile( tmpl, {
			'encoding': 'utf8'
		});
		if ( tmpl instanceof Error ) {
			throw tmpl;
		}
		tmpl = new Template( tmpl );

		// Create a fastify instance:
		f = fastify( opts );

		// Set basic security headers:
		f.register( helmet, {
			'contentSecurityPolicy': false
		});

		// Add support for reading cookies:
		f.register( cookie );

		// Decorate route handler `this` contexts with additional functionality:
		f.decorate( 'App', App );
		f.decorate( 'appinfo', opts.app );
		f.decorate( 'template', tmpl );
		f.decorate( 'rootDir', opts.root );
		f.decorate( 'latestVersion', opts.latest );

		// Decorate each `request` object with a `locals` object for passing along intermediate results within middleware handlers:
		f.decorateRequest( 'locals', null );

		// Register a plugin for serving static files:
		if ( opts.static ) {
			if ( isString( opts.prefix ) ) {
				f.register( fastifyStatic, {
					'root': opts.static,
					'prefix': opts.prefix,
					'wildcard': false
				});
			} else {
				for ( i = 0; i < opts.prefix.length; i++ ) {
					f.register( fastifyStatic, {
						'root': opts.static[ i ],
						'prefix': opts.prefix[ i ],
						'wildcard': false,
						'decorateReply': ( i === 0 ) // only one plugin registration is allowed to add a reply decorator
					});
				}
			}
		}
		// Register routes:
		f.register( routes, {} );

		// Add a hook to perform clean-up tasks when the server is closed:
		f.addHook( 'onClose', onClose );

		// Add a hook to configure the `locals` object for each request:
		f.addHook( 'onRequest', onRequest );

		// Start listening for HTTP requests:
		f.listen( opts.port, opts.hostname, onListen );

		/**
		* Callback invoked once a server is listening and ready to accept HTTP requests.
		*
		* @private
		* @param {(Error|null)} error - error object
		* @param {string} address - server address
		* @returns {void}
		*/
		function onListen( error, address ) {
			if ( error ) {
				f.log.error( error.message );
				return done( error );
			}
			f.log.info( 'HTTP server initialized. Server is listening for requests on %s.', address );
			done( null, f );
		}

		/**
		* Callback invoked upon receiving an HTTP request.
		*
		* @private
		* @param {Object} request - request object
		* @param {Object} reply - reply object
		* @param {Callback} clbk - callback to invoke once finished
		*/
		function onRequest( request, reply, clbk ) {
			request.locals = {};
			clbk();
		}

		/**
		* Callback invoked when a server is closed.
		*
		* @private
		* @param {Object} instance - fastify instance
		* @param {Callback} clbk - callback to invoke once finished
		*/
		function onClose( instance, clbk ) {
			setTimeout( onEnd, 0 );

			/**
			* Callback invoked upon performing clean-up tasks.
			*
			* @private
			*/
			function onEnd() {
				clbk();
			}
		}
	}
}

// EXPORTS //

module.exports = httpServer;
<|endoftext|>


<commit_before>/*
  Convert the contents of an HTML DOM element into a
  styled text document for an easier reading
  experience or to bring out certain features in the
  document. You can create your own README document.

  if (redistribute == true) {
    return credit;
  }

  --------------------------------------------------

  @DocTyp
  @author: Alexander Hovy
  @param:
    exports - Contains all the public variables and functions of the module.
    query - HTML DOM Element or Tag-, Class- or ID Name of a HTML DOM Element.
    element - HTML DOM Element.
    elements - An array of Elements.
    theme - You can optionally set the style to Dark or Light.
    url - Content of the url that needs to be loaded.
  @credit:
    Prism, Highlight, SHJS, Rainbow, Prettify
*/
var DocTyp = (function(exports) {
  /*============================================================
  ===========================Variable===========================
  ============================================================*/

  /*============================================================
  ============================Private===========================
  ============================================================*/
  function Complete(element, doc) {
    //Create the article Tag
    var article = document.createElement('article');
    article.setAttribute('class', 'doctyp');
    article.innerHTML = doc;
    //Replace the old Element with new
    element.parentNode.replaceChild(article, element);
    if (article.querySelectorAll('pre').length > 0) {
      exports.Syntax(article.querySelectorAll('pre'));
    }
  }

  function Single(element) {
    element.onload = function() {
      //Fetch element text
      var doc = element.innerHTML;
      //Processing
      doc = exports.Header(doc);
      doc = exports.Style(doc);
      doc = exports.Rule(doc);
      doc = exports.Quote(doc);
      doc = exports.Code(doc);
      doc = exports.List(doc);
      doc = exports.Link(doc);
      doc = exports.Image(doc);
      doc = exports.Table(doc);
      doc = exports.Clean(doc);
      //Complete Process
      Complete(element, doc);
    };
  }

  function Multiple(elements) {
    //Check if any elements were found
    if (elements.length > 0) {
      for (var a = 0; a < elements.length; a++) {
        var element = elements[a];
        element.onload = function() {
          //Fetch element text
          var doc = element.innerHTML;
          //Processing
          doc = exports.Header(doc);
          doc = exports.Style(doc);
          doc = exports.Rule(doc);
          doc = exports.Quote(doc);
          doc = exports.Code(doc);
          doc = exports.List(doc);
          doc = exports.Link(doc);
          doc = exports.Image(doc);
          doc = exports.Table(doc);
          doc = exports.Clean(doc);
          //Complete Process
          Complete(element, doc);
        };
      }
    } else {
      throw('The element is not defined or is not a DOM element.');
    }
  }

  /*============================================================
  ============================Public============================
  ============================================================*/
  exports.Docify = function(query, theme) {
    //Check if user is using their own style
    if (theme !== undefined) {
      exports.theme = theme.toLowerCase();
      exports.LoadStyle('Style/' + theme.toLowerCase() + '.css');
    } else {
      exports.theme = 'light';
    }
    //Check if there are multiple elements
    if (query.nodeType && query.nodeType == 1) {
      Single(query);
    } else {
      //Get Elements
      var elements = document.querySelectorAll(query);
      Multiple(elements);
    }
  };

  /*============================================================
  ============================Loader============================
  ============================================================*/
  exports.LoadScript = function(url) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.src = 'https://doctyp.github.io/src/' + url;
    document.getElementsByTagName('head')[0].appendChild(script);
  };
  exports.LoadStyle = function(url) {
    var link = document.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.href = 'https://doctyp.github.io/src/' + url;
    link.media = 'none';
    link.onload = function() {
      if (link.media != 'all') {
        link.media = 'all';
      }
    };
    document.getElementsByTagName('head')[0].appendChild(link);
  };

  /*============================================================
  ============================Export============================
  ============================================================*/
  return exports;
})({});
//Load Extra
DocTyp.LoadScript('extension.js');
<commit_msg>Update DocTyp.js<commit_after>/*
  Convert the contents of an HTML DOM element into a
  styled text document for an easier reading
  experience or to bring out certain features in the
  document. You can create your own README document.

  if (redistribute == true) {
    return credit;
  }

  --------------------------------------------------

  @DocTyp
  @author: Alexander Hovy
  @param:
    exports - Contains all the public variables and functions of the module.
    query - HTML DOM Element or Tag-, Class- or ID Name of a HTML DOM Element.
    element - HTML DOM Element.
    elements - An array of Elements.
    theme - You can optionally set the style to Dark or Light.
    url - Content of the url that needs to be loaded.
  @credit:
    Prism, Highlight, SHJS, Rainbow, Prettify
*/
var DocTyp = (function(exports) {
  /*============================================================
  ===========================Variable===========================
  ============================================================*/

  /*============================================================
  ============================Private===========================
  ============================================================*/
  function Complete(element, doc) {
    //Create the article Tag
    var article = document.createElement('article');
    article.setAttribute('class', 'doctyp');
    article.innerHTML = doc;
    //Replace the old Element with new
    element.parentNode.replaceChild(article, element);
    if (article.querySelectorAll('pre').length > 0) {
      exports.Syntax(article.querySelectorAll('pre'));
    }
  }

  function Single(element) {
    //Fetch element text
    var doc = element.innerHTML;
    //Processing
    doc = exports.Header(doc);
    doc = exports.Style(doc);
    doc = exports.Rule(doc);
    doc = exports.Quote(doc);
    doc = exports.Code(doc);
    doc = exports.List(doc);
    doc = exports.Link(doc);
    doc = exports.Image(doc);
    doc = exports.Table(doc);
    doc = exports.Clean(doc);
    //Complete Process
    Complete(element, doc);
  }

  function Multiple(elements) {
    //Check if any elements were found
    if (elements.length > 0) {
      for (var a = 0; a < elements.length; a++) {
        var element = elements[a];
        //Fetch element text
        var doc = element.innerHTML;
        //Processing
        doc = exports.Header(doc);
        doc = exports.Style(doc);
        doc = exports.Rule(doc);
        doc = exports.Quote(doc);
        doc = exports.Code(doc);
        doc = exports.List(doc);
        doc = exports.Link(doc);
        doc = exports.Image(doc);
        doc = exports.Table(doc);
        doc = exports.Clean(doc);
        //Complete Process
        Complete(element, doc);
      }
    } else {
      throw('The element is not defined or is not a DOM element.');
    }
  }

  /*============================================================
  ============================Public============================
  ============================================================*/
  exports.Docify = function(query, theme) {
    //Check if user is using their own style
    if (theme !== undefined) {
      exports.theme = theme.toLowerCase();
      exports.LoadStyle('Style/' + theme.toLowerCase() + '.css');
    } else {
      exports.theme = 'light';
    }
    //Check if there are multiple elements
    if (query.nodeType && query.nodeType == 1) {
      Single(query);
    } else {
      //Get Elements
      var elements = document.querySelectorAll(query);
      Multiple(elements);
    }
  };

  /*============================================================
  ============================Loader============================
  ============================================================*/
  exports.LoadScript = function(url) {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.async = true;
    script.src = 'https://doctyp.github.io/src/' + url;
    document.getElementsByTagName('head')[0].appendChild(script);
  };
  exports.LoadStyle = function(url) {
    var link = document.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.href = 'https://doctyp.github.io/src/' + url;
    link.media = 'none';
    link.onload = function() {
      if (link.media != 'all') {
        link.media = 'all';
      }
    };
    document.getElementsByTagName('head')[0].appendChild(link);
  };

  /*============================================================
  ============================Export============================
  ============================================================*/
  return exports;
})({});
//Load Extra
DocTyp.LoadScript('extension.js');
<|endoftext|>


<commit_before>import { connect } from 'react-redux'

import { inputSlotPosition, outputSlotPosition } from '../utils'
import Pipe from '../components/Pipe'

const mapStateToProps = (state, ownProps) => {
  const { input, output } = ownProps
  const { entities } = state.workspace

  return {
    inputPosition: inputSlotPosition(
      entities[input.elementId],
      input.slotId
    ),
    outputPosition: outputSlotPosition(
      entities[output.elementId],
      output.slotId
    )
  }
}

const mapDispatchToProps = (dispatch) => {
  return { }
}

export default connect(mapStateToProps, mapDispatchToProps)(Pipe)
<commit_msg>Remove unused function mapDispatchToProps in Pipe Container<commit_after>import { connect } from 'react-redux'

import { inputSlotPosition, outputSlotPosition } from '../utils'
import Pipe from '../components/Pipe'

const mapStateToProps = (state, ownProps) => {
  const { input, output } = ownProps
  const { entities } = state.workspace

  return {
    inputPosition: inputSlotPosition(
      entities[input.elementId],
      input.slotId
    ),
    outputPosition: outputSlotPosition(
      entities[output.elementId],
      output.slotId
    )
  }
}

export default connect(mapStateToProps)(Pipe)
<|endoftext|>


<commit_before>'use strict';

/**
 * ButtressJS - Realtime datastore for business software
 *
 * @file company.js
 * @description Company model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

const mongoose = require('mongoose');
const Model = require('../');
const Logging = require('../../logging');
const Shared = require('../shared');
const humanname = require('humanname');
// const addressit = require('addressit');

const schema = new mongoose.Schema();
let ModelDef = null;
const constants = {};

/* ********************************************************************************
 *
 * EMBEDDED DEPENDENCIES
 *
 **********************************************************************************/

Model.initModel('Location');
Model.initModel('Contact');

/* ********************************************************************************
 *
 * CONSTANTS
 *
 **********************************************************************************/
const employeeBands = [
  '1-4',
  '5-9',
  '10-19',
  '20-99',
  '100-499',
  '500-999',
  '1000-1999',
  '2000-4999',
  '5000-10000',
  '>10000'
];
constants.EmployeeBands = {
  BAND_1: employeeBands[0],
  BAND_2: employeeBands[1],
  BAND_3: employeeBands[2],
  BAND_4: employeeBands[3],
  BAND_5: employeeBands[4],
  BAND_6: employeeBands[5],
  BAND_7: employeeBands[6],
  BAND_8: employeeBands[7],
  BAND_9: employeeBands[8],
  BAND_10: employeeBands[9]
};

const turnoverBands = [
  '0-99k',
  '100k-199k',
  '200k-299k',
  '300k-499k',
  '500k-999k',
  '1m-4.99m',
  '5m-10m',
  '>10m'
];
constants.TurnoverBands = {
  BAND_1: turnoverBands[0],
  BAND_2: turnoverBands[1],
  BAND_3: turnoverBands[2],
  BAND_4: turnoverBands[3],
  BAND_5: turnoverBands[4],
  BAND_6: turnoverBands[5],
  BAND_7: turnoverBands[6],
  BAND_8: turnoverBands[7]
};

/* ********************************************************************************
 *
 * SCHEMA
 *
 **********************************************************************************/
schema.add({
  _parent: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Company'
  },
  name: String,
  number: Number,
  employeeBand: {
    type: String,
    enum: employeeBands
  },
  turnoverBand: {
    type: String,
    enum: turnoverBands
  },
  sector: String,
  subsector: String,
  siccode: Number,
  website: String,
  socialMedia: [{
    type: String,
    url: String
  }],
  primaryLocation: String,
  locations: [{
    name: String,
    address: String,
    city: String,
    postCode: String,
    phoneNumber: String
  }],
  primaryContact: String,
  contacts: [{
    name: String,
    role: String,
    email: String,
    mobile: String,
    directDial: String,
    linkedInProfile: String,
    twitterProfile: String
  }],
  _app: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Application'
  },
  metadata: [{key: String, value: String}],
  notes: [{
    text: String,
    timestamp: {
      type: Date,
      default: Date.create
    }
  }]
});

/* ********************************************************************************
 *
 * SCHEMA STATIC METHODS
 *
 **********************************************************************************/
/**
 * @param {Object} body - body passed through from a POST request to be validated
 * @return {Object} - returns an object with validation context
 */
const __doValidation = body => {
  let res = {
    isValid: true,
    missing: [],
    invalid: []
  };

  if (!body.name) {
    res.isValid = false;
    res.missing.push('name');
  }
  if (!body.location) {
    res.isValid = false;
    res.missing.push('location');
  }
  if (!body.location.name) {
    res.isValid = false;
    res.missing.push('location.name');
  }
  if (!body.location.address) {
    res.isValid = false;
    res.missing.push('location.address');
  }
  if (!body.location.postCode) {
    res.isValid = false;
    res.missing.push('location.postCode');
  }
  if (!body.location.phoneNumber) {
    res.isValid = false;
    res.missing.push('location.phoneNumber');
  }
  if (!body.contact) {
    res.isValid = false;
    res.missing.push('contact');
  }
  if (!body.contact.name) {
    res.isValid = false;
    res.missing.push('contact.name');
  }
  if (!body.contact.role) {
    res.isValid = false;
    res.missing.push('contact.role');
  }

  return res;
};

schema.statics.validate = body => {
  if (body instanceof Array === false) {
    body = [body];
  }
  let validation = body.map(__doValidation).filter(v => v.isValid === false);

  return validation.length >= 1 ? validation[0] : {isValid: true};
};

/*
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
const __addCompany = body => {
  return prev => {
    // Logging.logDebug(body);
    // const loc = new Model.Location({
    //   name: body.location.name,
    //   address: Model.Address.create(body.location.address),
    //   phoneNumber: body.location.phoneNumber
    // });

    // Logging.logDebug(loc.address.details);

    // const contact = Model.Contact.create(body.contact);

    const company = new ModelDef({
      name: body.name,
      number: body.number,
      employeeBand: body.employeeBand,
      turnoverBand: body.turnoverBand,
      siccode: body.siccode,
      sector: body.sector,
      subsector: body.subsector,
      website: body.website,
      locations: [body.location],
      contacts: [body.contact],
      _app: Model.authApp._id
    });

    company.primaryContact = company.contacts[0]._id;
    company.primaryLocation = company.locations[0]._id;

    return Promise.resolve(prev.concat([company.toObject()]));
  };
};

schema.statics.add = body => {
  if (body instanceof Array === false) {
    body = [body];
  }

  return body.reduce((promise, item) => {
    return promise
      .then(__addCompany(item))
      .catch(Logging.Promise.logError());
  }, Promise.resolve([]))
  .then(companies => {
    return new Promise((resolve, reject) => {
      ModelDef.collection.insert(companies, (err, res) => {
        if (err) {
          reject(err);
          return;
        }

        resolve(res.ops.map(c => c._id));
      });
    });
  });
};

/* ********************************************************************************
 *
 * SCHEMA VIRTUAL METHODS
 *
 *********************************************************************************/

schema.virtual('details').get(function() {
  // Logging.logDebug(this.locations[this.primaryLocation].details);

  const _locations = this.locations.map(l => {
    // const _address = addressit(l.address, {locale: 'en-GB'});
    // const regions = _address.regions;
    Logging.log(l.address, Logging.Constants.LogLevel.SILLY);
    // Logging.log(_address, Logging.Constants.LogLevel.DEBUG);
    return {
      id: l._id,
      name: l.name,
      address: l.address,
      city: l.city,
      postCode: l.postCode,
      phoneNumber: l.phoneNumber
      // address: {
      //   full: l.address,
      //   unit: _address.unit,
      //   number: _address.number,
      //   street: _address.street,
      //   town: regions.length >= 2 ? regions.shift() : '',
      //   city: regions.length >= 1 ? regions.shift() : '',
      //   county: _address.state,
      //   postcode: _address.postalcode
      // }
    };
  });

  const _contacts = this.contacts.map(c => {
    // const name = humanname.parse(c.name);
    // const formalName =
    //   `${name.title ? name.title + ' ' : ''}${name.firstName} ${name.initials ? name.initials + ' ' : ''}${name.lastName}`;
    return {
      // name: {
      //   full: c.name,
      //   formal: formalName,
      //   title: name.title,
      //   forename: name.firstName,
      //   initials: name.initials,
      //   surname: name.lastName,
      //   suffix: name.suffix
      // },
      id: c._id,
      name: c.name,
      role: c.role,
      email: c.email,
      directDial: c.directDial,
      mobile: c.mobile
    };
  });
  return {
    id: this._id,
    name: this.name,
    number: this.number,
    employeeBand: this.employeeBand,
    turnoverBand: this.turnoverBand,
    sector: this.sector,
    subsector: this.subsector,
    locations: _locations,
    contacts: _contacts,
    primaryLocation: this.primaryLocation,
    primaryContact: this.primaryContact,
    website: this.website,
    notes: this.notes.map(n => ({text: n.text, timestamp: n.timestamp}))
  };
});

/* ********************************************************************************
 *
 * SCHEMA METHODS
 *
 **********************************************************************************/

/**
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.methods.updateByObject = function(body) {
  Logging.log(body, Logging.Constants.LogLevel.VERBOSE);

  this.name = body.name ? body.name : this.name;
  this.number = body.number ? body.number : this.number;
  this.siccode = body.siccode ? body.siccode : this.siccode;
  this.employeeBand = body.employeeBand ? body.employeeBand : this.employeeBand;
  this.turnoverBand = body.turnoverBand ? body.turnoverBand : this.turnoverBand;
  this.sector = body.sector ? body.sector : this.sector;
  this.subsector = body.subsector ? body.subsector : this.subsector;
  this.website = body.website ? body.website : this.website;

  if (body.location && this.locations[this.primaryLocation]) {
    let loc = this.locations[this.primaryLocation];
    loc.name = body.location.name ? body.location.name : loc.name;
    loc.phoneNumber = body.location.phoneNumber ? body.location.phoneNumber : loc.phoneNumber;
    if (body.location.address) {
      loc.address.update(body.location.address);
    }
  }

  return this.save();
};

/* ********************************************************************************
 *
 * UPDATE BY PATH
 *
 **********************************************************************************/

const PATH_CONTEXT = {
  '^notes$': {type: 'vector-add', values: []},
  '^notes.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^notes.([0-9]{1,3}).__remove__$': {type: 'vector-rm', values: []},
  '^notes.([0-9]{1,3}).text$': {type: 'scalar', values: []},
  '^contacts$': {type: 'vector-add', values: []},
  '^contacts.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^contacts.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^contacts.([0-9]{1,3}).(email|directDial|mobile|role|name|linkedInProfile|twitterProfile)$': {type: 'scalar', values: []},
  '^locations$': {type: 'vector-add', values: []},
  '^locations.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^locations.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^locations.([0-9]{1,3}).(name|phoneNumber|address|city|postCode)$': {type: 'scalar', values: []}
};

schema.statics.validateUpdate = Shared.validateUpdate(PATH_CONTEXT);
schema.methods.updateByPath = Shared.updateByPath(PATH_CONTEXT);

/* ********************************************************************************
 *
 * SCHEMA DB FUNCTIONS
 *
 **********************************************************************************/

/*
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.isDuplicate = details => {
  return Promise.resolve(false);
};

/**
 * @param {App} company - Company object to be deleted
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.rm = company => {
  Logging.log(`DELETING: ${company._id}`, Logging.Constants.LogLevel.DEBUG);
  // Logging.log(org.details, Logging.Constants.LogLevel.VERBOSE);
  return ModelDef.remove({_id: company._id});
};

/**
 * @param {Array} ids - Array of company ids to delete
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.rmBulk = ids => {
  Logging.log(`DELETING: ${ids}`, Logging.Constants.LogLevel.SILLY);
  return ModelDef.remove({_id: {$in: ids}}).exec();
};

/*
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.rmAll = () => {
  return ModelDef.remove({});
};

/**
 * @return {Promise} - resolves to an array of Companies
 */
schema.statics.findAll = () => {
  Logging.log(`findAll: ${Model.authApp._id}`, Logging.Constants.LogLevel.DEBUG);

  if (Model.token.authLevel === Model.Constants.Token.AuthLevel.SUPER) {
    return ModelDef.find({});
  }

  return ModelDef.find({_app: Model.authApp._id});
};

/**
 * @param {Array} ids - Array of company ids to get
 * @return {Promise} - resolves to an array of Companies
 */
schema.statics.findAllById = ids => {
  Logging.log(`findAllById: ${Model.authApp._id}`, Logging.Constants.LogLevel.INFO);

  return ModelDef.find({_id: {$in: ids}, _app: Model.authApp._id});
};

/* ********************************************************************************
 *
 * METADATA
 *
 **********************************************************************************/

schema.methods.addOrUpdateMetadata = Shared.addOrUpdateMetadata;
schema.methods.findMetadata = Shared.findMetadata;
schema.methods.rmMetadata = Shared.rmMetadata;

/* ********************************************************************************
 *
 * EXPORTS
 *
 **********************************************************************************/
ModelDef = mongoose.model('Company', schema);

module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;
<commit_msg>- ADDED: various additional fields - companyType, reference, contractIds, contact.responsibility. - CHANGED: Validation rules - no longer demand a phoneNumber.<commit_after>'use strict';

/**
 * ButtressJS - Realtime datastore for business software
 *
 * @file company.js
 * @description Company model definition.
 * @module Model
 * @exports model, schema, constants
 * @author Chris Bates-Keegan
 *
 */

const mongoose = require('mongoose');
const Model = require('../');
const Logging = require('../../logging');
const Shared = require('../shared');
const humanname = require('humanname');
// const addressit = require('addressit');

const schema = new mongoose.Schema();
let ModelDef = null;
const constants = {};

/* ********************************************************************************
 *
 * EMBEDDED DEPENDENCIES
 *
 **********************************************************************************/

Model.initModel('Location');
Model.initModel('Contact');

/* ********************************************************************************
 *
 * CONSTANTS
 *
 **********************************************************************************/
const types = [
  'prospect',
  'client',
  'supplier'
];

constants.Type = {
  PROSPECT: types[0],
  CLIENT: types[1],
  SUPPLIER: types[2]
};

const employeeBands = [
  '1-4',
  '5-9',
  '10-19',
  '20-99',
  '100-499',
  '500-999',
  '1000-1999',
  '2000-4999',
  '5000-10000',
  '>10000'
];
constants.EmployeeBands = {
  BAND_1: employeeBands[0],
  BAND_2: employeeBands[1],
  BAND_3: employeeBands[2],
  BAND_4: employeeBands[3],
  BAND_5: employeeBands[4],
  BAND_6: employeeBands[5],
  BAND_7: employeeBands[6],
  BAND_8: employeeBands[7],
  BAND_9: employeeBands[8],
  BAND_10: employeeBands[9]
};

const turnoverBands = [
  '0-99k',
  '100k-199k',
  '200k-299k',
  '300k-499k',
  '500k-999k',
  '1m-4.99m',
  '5m-10m',
  '>10m'
];
constants.TurnoverBands = {
  BAND_1: turnoverBands[0],
  BAND_2: turnoverBands[1],
  BAND_3: turnoverBands[2],
  BAND_4: turnoverBands[3],
  BAND_5: turnoverBands[4],
  BAND_6: turnoverBands[5],
  BAND_7: turnoverBands[6],
  BAND_8: turnoverBands[7]
};

/* ********************************************************************************
 *
 * SCHEMA
 *
 **********************************************************************************/
schema.add({
  _parent: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Company'
  },
  companyType: {
    type: String,
    enum: types,
    default: constants.Type.PROSPECT
  },
  name: String,
  reference: String,
  number: Number,
  employeeBand: {
    type: String,
    enum: employeeBands
  },
  turnoverBand: {
    type: String,
    enum: turnoverBands
  },
  sector: String,
  subsector: String,
  siccode: Number,
  website: String,
  socialMedia: [{
    type: String,
    url: String
  }],
  primaryLocation: String,
  locations: [{
    name: String,
    address: String,
    city: String,
    postCode: String,
    phoneNumber: String
  }],
  primaryContact: String,
  contacts: [{
    name: String,
    role: String,
    responsibility: String,
    email: String,
    mobile: String,
    directDial: String,
    linkedInProfile: String,
    twitterProfile: String
  }],
  contractIds: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Contract'
  }],
  _app: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Application'
  },
  metadata: [{key: String, value: String}],
  notes: [{
    text: String,
    timestamp: {
      type: Date,
      default: Date.create
    }
  }]
});

/* ********************************************************************************
 *
 * SCHEMA STATIC METHODS
 *
 **********************************************************************************/
/**
 * @param {Object} body - body passed through from a POST request to be validated
 * @return {Object} - returns an object with validation context
 */
const __doValidation = body => {
  let res = {
    isValid: true,
    missing: [],
    invalid: []
  };

  if (!body.name) {
    res.isValid = false;
    res.missing.push('name');
  }
  if (body.companyType && !types.indexOf(body.companyType) === -1) {
    res.isValid = false;
    res.invalid.push('companyType');
  }
  if (!body.location) {
    res.isValid = false;
    res.missing.push('location');
  }
  if (!body.location.name) {
    res.isValid = false;
    res.missing.push('location.name');
  }
  if (!body.location.address) {
    res.isValid = false;
    res.missing.push('location.address');
  }
  if (!body.location.postCode) {
    res.isValid = false;
    res.missing.push('location.postCode');
  }
  // if (!body.location.phoneNumber) {
  //   res.isValid = false;
  //   res.missing.push('location.phoneNumber');
  // }
  if (!body.contact) {
    res.isValid = false;
    res.missing.push('contact');
  }
  if (!body.contact.name) {
    res.isValid = false;
    res.missing.push('contact.name');
  }
  if (!body.contact.role) {
    res.isValid = false;
    res.missing.push('contact.role');
  }

  return res;
};

schema.statics.validate = body => {
  if (body instanceof Array === false) {
    body = [body];
  }
  let validation = body.map(__doValidation).filter(v => v.isValid === false);

  return validation.length >= 1 ? validation[0] : {isValid: true};
};

/*
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
const __addCompany = body => {
  return prev => {
    // Logging.logDebug(body);
    // const loc = new Model.Location({
    //   name: body.location.name,
    //   address: Model.Address.create(body.location.address),
    //   phoneNumber: body.location.phoneNumber
    // });

    // Logging.logDebug(loc.address.details);

    // const contact = Model.Contact.create(body.contact);

    const company = new ModelDef({
      name: body.name,
      companyType: body.companyType,
      reference: body.reference,
      number: body.number,
      employeeBand: body.employeeBand,
      turnoverBand: body.turnoverBand,
      siccode: body.siccode,
      sector: body.sector,
      subsector: body.subsector,
      website: body.website,
      locations: [body.location],
      contacts: [body.contact],
      _app: Model.authApp._id
    });

    company.primaryContact = company.contacts[0]._id;
    company.primaryLocation = company.locations[0]._id;

    return Promise.resolve(prev.concat([company.toObject()]));
  };
};

schema.statics.add = body => {
  if (body instanceof Array === false) {
    body = [body];
  }

  return body.reduce((promise, item) => {
    return promise
      .then(__addCompany(item))
      .catch(Logging.Promise.logError());
  }, Promise.resolve([]))
  .then(companies => {
    return new Promise((resolve, reject) => {
      ModelDef.collection.insert(companies, (err, res) => {
        if (err) {
          reject(err);
          return;
        }

        resolve(res.ops.map(c => c._id));
      });
    });
  });
};

/* ********************************************************************************
 *
 * SCHEMA VIRTUAL METHODS
 *
 *********************************************************************************/

schema.virtual('details').get(function() {
  // Logging.logDebug(this.locations[this.primaryLocation].details);

  const _locations = this.locations.map(l => {
    // const _address = addressit(l.address, {locale: 'en-GB'});
    // const regions = _address.regions;
    Logging.log(l.address, Logging.Constants.LogLevel.SILLY);
    // Logging.log(_address, Logging.Constants.LogLevel.DEBUG);
    return {
      id: l._id,
      name: l.name,
      address: l.address,
      city: l.city,
      postCode: l.postCode,
      phoneNumber: l.phoneNumber
      // address: {
      //   full: l.address,
      //   unit: _address.unit,
      //   number: _address.number,
      //   street: _address.street,
      //   town: regions.length >= 2 ? regions.shift() : '',
      //   city: regions.length >= 1 ? regions.shift() : '',
      //   county: _address.state,
      //   postcode: _address.postalcode
      // }
    };
  });

  const _contacts = this.contacts.map(c => {
    // const name = humanname.parse(c.name);
    // const formalName =
    //   `${name.title ? name.title + ' ' : ''}${name.firstName} ${name.initials ? name.initials + ' ' : ''}${name.lastName}`;
    return {
      // name: {
      //   full: c.name,
      //   formal: formalName,
      //   title: name.title,
      //   forename: name.firstName,
      //   initials: name.initials,
      //   surname: name.lastName,
      //   suffix: name.suffix
      // },
      id: c._id,
      name: c.name,
      role: c.role,
      email: c.email,
      directDial: c.directDial,
      mobile: c.mobile
    };
  });
  return {
    id: this._id,
    name: this.name,
    companyType: this.companyType,
    reference: this.reference,
    number: this.number,
    employeeBand: this.employeeBand,
    turnoverBand: this.turnoverBand,
    sector: this.sector,
    subsector: this.subsector,
    locations: _locations,
    contacts: _contacts,
    contractIds: this.contractIds,
    primaryLocation: this.primaryLocation,
    primaryContact: this.primaryContact,
    website: this.website,
    notes: this.notes.map(n => ({text: n.text, timestamp: n.timestamp}))
  };
});

/* ********************************************************************************
 *
 * SCHEMA METHODS
 *
 **********************************************************************************/

/**
 * @param {Object} body - body passed through from a POST request
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.methods.updateByObject = function(body) {
  Logging.log(body, Logging.Constants.LogLevel.VERBOSE);

  this.name = body.name ? body.name : this.name;
  this.number = body.number ? body.number : this.number;
  this.siccode = body.siccode ? body.siccode : this.siccode;
  this.employeeBand = body.employeeBand ? body.employeeBand : this.employeeBand;
  this.turnoverBand = body.turnoverBand ? body.turnoverBand : this.turnoverBand;
  this.sector = body.sector ? body.sector : this.sector;
  this.subsector = body.subsector ? body.subsector : this.subsector;
  this.website = body.website ? body.website : this.website;

  if (body.location && this.locations[this.primaryLocation]) {
    let loc = this.locations[this.primaryLocation];
    loc.name = body.location.name ? body.location.name : loc.name;
    loc.phoneNumber = body.location.phoneNumber ? body.location.phoneNumber : loc.phoneNumber;
    if (body.location.address) {
      loc.address.update(body.location.address);
    }
  }

  return this.save();
};

/* ********************************************************************************
 *
 * UPDATE BY PATH
 *
 **********************************************************************************/

const PATH_CONTEXT = {
  '^notes$': {type: 'vector-add', values: []},
  '^notes.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^notes.([0-9]{1,3}).__remove__$': {type: 'vector-rm', values: []},
  '^notes.([0-9]{1,3}).text$': {type: 'scalar', values: []},
  '^contractIds$': {type: 'vector-add', values: []},
  '^contractIds.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^contractIds.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^contacts$': {type: 'vector-add', values: []},
  '^contacts.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^contacts.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^contacts.([0-9]{1,3}).(email|directDial|mobile|role|name|linkedInProfile|twitterProfile)$': {type: 'scalar', values: []},
  '^locations$': {type: 'vector-add', values: []},
  '^locations.([0-9]{1,3})$': {type: 'scalar', values: []},
  '^locations.([0-9]{1,3}).(__remove__)$': {type: 'vector-rm', values: []},
  '^locations.([0-9]{1,3}).(name|phoneNumber|address|city|postCode)$': {type: 'scalar', values: []}
};

schema.statics.validateUpdate = Shared.validateUpdate(PATH_CONTEXT);
schema.methods.updateByPath = Shared.updateByPath(PATH_CONTEXT);

/* ********************************************************************************
 *
 * SCHEMA DB FUNCTIONS
 *
 **********************************************************************************/

/*
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.isDuplicate = details => {
  return Promise.resolve(false);
};

/**
 * @param {App} company - Company object to be deleted
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.rm = company => {
  Logging.log(`DELETING: ${company._id}`, Logging.Constants.LogLevel.DEBUG);
  // Logging.log(org.details, Logging.Constants.LogLevel.VERBOSE);
  return ModelDef.remove({_id: company._id});
};

/**
 * @param {Array} ids - Array of company ids to delete
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.rmBulk = ids => {
  Logging.log(`DELETING: ${ids}`, Logging.Constants.LogLevel.SILLY);
  return ModelDef.remove({_id: {$in: ids}}).exec();
};

/*
 * @return {Promise} - returns a promise that is fulfilled when the database request is completed
 */
schema.statics.rmAll = () => {
  return ModelDef.remove({});
};

/**
 * @return {Promise} - resolves to an array of Companies
 */
schema.statics.findAll = () => {
  Logging.log(`findAll: ${Model.authApp._id}`, Logging.Constants.LogLevel.DEBUG);

  if (Model.token.authLevel === Model.Constants.Token.AuthLevel.SUPER) {
    return ModelDef.find({});
  }

  return ModelDef.find({_app: Model.authApp._id});
};

/**
 * @param {Array} ids - Array of company ids to get
 * @return {Promise} - resolves to an array of Companies
 */
schema.statics.findAllById = ids => {
  Logging.log(`findAllById: ${Model.authApp._id}`, Logging.Constants.LogLevel.INFO);

  return ModelDef.find({_id: {$in: ids}, _app: Model.authApp._id});
};

/* ********************************************************************************
 *
 * METADATA
 *
 **********************************************************************************/

schema.methods.addOrUpdateMetadata = Shared.addOrUpdateMetadata;
schema.methods.findMetadata = Shared.findMetadata;
schema.methods.rmMetadata = Shared.rmMetadata;

/* ********************************************************************************
 *
 * EXPORTS
 *
 **********************************************************************************/
ModelDef = mongoose.model('Company', schema);

module.exports.constants = constants;
module.exports.schema = schema;
module.exports.model = ModelDef;
<|endoftext|>


<commit_before>(function (factory) {
	var L, proj4;
	if (typeof define === 'function' && define.amd) {
		// AMD
		define(['leaflet', 'proj4'], factory);
	} else if (typeof module !== 'undefined') {
		// Node/CommonJS
		L = require('leaflet');
		proj4 = require('proj4js');
		module.exports = factory(L, proj4);
	} else {
		// Browser globals
		if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined')
			throw "Leaflet and proj4 must be loaded first";
		factory(window.L, window.proj4);
	}
}(function (L, proj4) {

L.Proj = {};

L.Proj._isProj4Obj = function(a) {
	return (typeof a['inverse'] !== 'undefined'
		&& typeof a['forward'] !== 'undefined');
};

L.Proj.ScaleDependantTransformation = function(scaleTransforms) {
		this.scaleTransforms = scaleTransforms;
	}

L.Proj.ScaleDependantTransformation.prototype.transform = function(point, scale) {
		return this.scaleTransforms[scale].transform(point, scale);
};

L.Proj.ScaleDependantTransformation.prototype.untransform = function(point, scale) {
		return this.scaleTransforms[scale].untransform(point, scale);
};

L.Proj.Projection = L.Class.extend({
	initialize: function(a, def) {
		if (L.Proj._isProj4Obj(a)) {
			this._proj = a;
		} else {
			var code = a;
			if (def) {
				proj4.defs(code, def);
			} else if (proj4.defs[code] === undefined) {
				var urn = code.split(':');
				if (urn.length > 3)
					code = urn[urn.length - 3] + ':' + urn[urn.length - 1];
				if (proj4.defs[code] === undefined)
					throw "No projection definition for code " + code;
			}
			this._proj = proj4(code);
		}
	},

	project: function (latlng) {
		var point = this._proj.forward([latlng.lng, latlng.lat]);
		return new L.Point(point[0], point[1]);
	},

	unproject: function (point, unbounded) {
		var point2 = this._proj.inverse([point.x, point.y]);
		return new L.LatLng(point2[1], point2[0], unbounded);
	}
});

L.Proj.CRS = L.Class.extend({
	includes: L.CRS,

	options: {
		transformation: new L.Transformation(1, 0, -1, 0)
	},

	initialize: function(a, b, c) {
		var code, proj, def, options;

		if (L.Proj._isProj4Obj(a)) {
			proj = a;
			code = proj.srsCode;
			options = b || {};

			this.projection = new L.Proj.Projection(proj);
		} else {
			code = a;
			def = b;
			options = c || {};
			this.projection = new L.Proj.Projection(code, def);
		}

		L.Util.setOptions(this, options);
		this.code = code;
		this.transformation = this.options.transformation;

		if (this.options.origin) {
			this.transformation =
				new L.Transformation(1, -this.options.origin[0],
					-1, this.options.origin[1]);
		}

		if (this.options.scales) {
			this.scale = function(zoom) {
				return this.options.scales[zoom];
			}
		} else if (this.options.resolutions) {
			this.scale = function(zoom) {
				return 1 / this.options.resolutions[zoom];
			}
		}
	}
});

L.Proj.CRS.TMS = L.Proj.CRS.extend({
	initialize: function(a, b, c, d) {
		if (L.Proj._isProj4Obj(a)) {
			var proj = a,
				projectedBounds = b,
				options = c || {};
			options.origin = [projectedBounds[0], projectedBounds[3]];
			L.Proj.CRS.prototype.initialize(proj, options);
		} else {
			var code = a,
				def = b,
				projectedBounds = c,
				options = d || {};
			options.origin = [projectedBounds[0], projectedBounds[3]];
			L.Proj.CRS.prototype.initialize(code, def, options);
		}

		this.projectedBounds = projectedBounds;
	}
});

L.Proj.TileLayer = {};

L.Proj.TileLayer.TMS = L.TileLayer.extend({
	options: {
		tms: true,
		continuousWorld: true
	},

	initialize: function(urlTemplate, crs, options) {
		var boundsMatchesGrid = true,
			scaleTransforms,
			upperY,
			crsBounds;

		if (!(crs instanceof L.Proj.CRS.TMS)) {
			throw new Error("CRS is not L.Proj.CRS.TMS.");
		}

		L.TileLayer.prototype.initialize.call(this, urlTemplate, options);
		this.crs = crs;
		crsBounds = this.crs.projectedBounds;

		// Verify grid alignment
		for (var i = this.options.minZoom; i < this.options.maxZoom && boundsMatchesGrid; i++) {
			var gridHeight = (crsBounds[3] - crsBounds[1]) /
				this._projectedTileSize(i);
			boundsMatchesGrid = Math.abs(gridHeight - Math.round(gridHeight)) > 1e-3;
		}

		if (!boundsMatchesGrid) {
			scaleTransforms = {};
			for (var i = this.options.minZoom; i < this.options.maxZoom; i++) {
				upperY = crsBounds[1] + Math.ceil((crsBounds[3] - crsBounds[1]) /
					this._projectedTileSize(i)) * this._projectedTileSize(i);
				scaleTransforms[this.crs.scale(i)] = new L.Transformation(1, -crsBounds[0], -1, upperY);
			}

			this.crs = new L.Proj.CRS.TMS(this.crs.projection._proj, crsBounds, this.crs.options);
			this.crs.transformation = new L.Proj.ScaleDependantTransformation(scaleTransforms);
		}
	},

	getTileUrl: function(tilePoint) {
		// TODO: relies on some of TileLayer's internals
		var z = this._getZoomForUrl(),
			gridHeight = Math.ceil((this.crs.projectedBounds[3] - this.crs.projectedBounds[1]) / this._projectedTileSize(z));

		return L.Util.template(this._url, L.Util.extend({
			s: this._getSubdomain(tilePoint),
			z: z,
			x: tilePoint.x,
			y: gridHeight - tilePoint.y - 1
		}, this.options));
	},

	_projectedTileSize: function(zoom) {
		return (this.options.tileSize / this.crs.scale(zoom));
	}
});

L.Proj.GeoJSON = L.GeoJSON.extend({
	initialize: function(geojson, options) {
		if (geojson.crs && geojson.crs.type == 'name') {
			var crs = new L.Proj.CRS(geojson.crs.properties.name)
			options = options || {};
			options.coordsToLatLng = function(coords) {
				var point = L.point(coords[0], coords[1]);
				return crs.projection.unproject(point);
			};
		}
		L.GeoJSON.prototype.initialize.call(this, geojson, options);
	}
});

L.Proj.geoJson = function(geojson, options) {
	return new L.Proj.GeoJSON(geojson, options);
};

if (typeof L.CRS !== 'undefined') {
	// This is left here for backwards compatibility
	L.CRS.proj4js = (function () {
		return function (code, def, transformation, options) {
			options = options || {};
			if (transformation) options.transformation = transformation;

			return new L.Proj.CRS(code, def, options);
		};
	}());
}

return L.Proj;

}));
<commit_msg>Fixed changed proj4 module name<commit_after>(function (factory) {
	var L, proj4;
	if (typeof define === 'function' && define.amd) {
		// AMD
		define(['leaflet', 'proj4'], factory);
	} else if (typeof module !== 'undefined') {
		// Node/CommonJS
		L = require('leaflet');
		proj4 = require('proj4');
		module.exports = factory(L, proj4);
	} else {
		// Browser globals
		if (typeof window.L === 'undefined' || typeof window.proj4 === 'undefined')
			throw "Leaflet and proj4 must be loaded first";
		factory(window.L, window.proj4);
	}
}(function (L, proj4) {

L.Proj = {};

L.Proj._isProj4Obj = function(a) {
	return (typeof a['inverse'] !== 'undefined'
		&& typeof a['forward'] !== 'undefined');
};

L.Proj.ScaleDependantTransformation = function(scaleTransforms) {
		this.scaleTransforms = scaleTransforms;
	}

L.Proj.ScaleDependantTransformation.prototype.transform = function(point, scale) {
		return this.scaleTransforms[scale].transform(point, scale);
};

L.Proj.ScaleDependantTransformation.prototype.untransform = function(point, scale) {
		return this.scaleTransforms[scale].untransform(point, scale);
};

L.Proj.Projection = L.Class.extend({
	initialize: function(a, def) {
		if (L.Proj._isProj4Obj(a)) {
			this._proj = a;
		} else {
			var code = a;
			if (def) {
				proj4.defs(code, def);
			} else if (proj4.defs[code] === undefined) {
				var urn = code.split(':');
				if (urn.length > 3)
					code = urn[urn.length - 3] + ':' + urn[urn.length - 1];
				if (proj4.defs[code] === undefined)
					throw "No projection definition for code " + code;
			}
			this._proj = proj4(code);
		}
	},

	project: function (latlng) {
		var point = this._proj.forward([latlng.lng, latlng.lat]);
		return new L.Point(point[0], point[1]);
	},

	unproject: function (point, unbounded) {
		var point2 = this._proj.inverse([point.x, point.y]);
		return new L.LatLng(point2[1], point2[0], unbounded);
	}
});

L.Proj.CRS = L.Class.extend({
	includes: L.CRS,

	options: {
		transformation: new L.Transformation(1, 0, -1, 0)
	},

	initialize: function(a, b, c) {
		var code, proj, def, options;

		if (L.Proj._isProj4Obj(a)) {
			proj = a;
			code = proj.srsCode;
			options = b || {};

			this.projection = new L.Proj.Projection(proj);
		} else {
			code = a;
			def = b;
			options = c || {};
			this.projection = new L.Proj.Projection(code, def);
		}

		L.Util.setOptions(this, options);
		this.code = code;
		this.transformation = this.options.transformation;

		if (this.options.origin) {
			this.transformation =
				new L.Transformation(1, -this.options.origin[0],
					-1, this.options.origin[1]);
		}

		if (this.options.scales) {
			this.scale = function(zoom) {
				return this.options.scales[zoom];
			}
		} else if (this.options.resolutions) {
			this.scale = function(zoom) {
				return 1 / this.options.resolutions[zoom];
			}
		}
	}
});

L.Proj.CRS.TMS = L.Proj.CRS.extend({
	initialize: function(a, b, c, d) {
		if (L.Proj._isProj4Obj(a)) {
			var proj = a,
				projectedBounds = b,
				options = c || {};
			options.origin = [projectedBounds[0], projectedBounds[3]];
			L.Proj.CRS.prototype.initialize(proj, options);
		} else {
			var code = a,
				def = b,
				projectedBounds = c,
				options = d || {};
			options.origin = [projectedBounds[0], projectedBounds[3]];
			L.Proj.CRS.prototype.initialize(code, def, options);
		}

		this.projectedBounds = projectedBounds;
	}
});

L.Proj.TileLayer = {};

L.Proj.TileLayer.TMS = L.TileLayer.extend({
	options: {
		tms: true,
		continuousWorld: true
	},

	initialize: function(urlTemplate, crs, options) {
		var boundsMatchesGrid = true,
			scaleTransforms,
			upperY,
			crsBounds;

		if (!(crs instanceof L.Proj.CRS.TMS)) {
			throw new Error("CRS is not L.Proj.CRS.TMS.");
		}

		L.TileLayer.prototype.initialize.call(this, urlTemplate, options);
		this.crs = crs;
		crsBounds = this.crs.projectedBounds;

		// Verify grid alignment
		for (var i = this.options.minZoom; i < this.options.maxZoom && boundsMatchesGrid; i++) {
			var gridHeight = (crsBounds[3] - crsBounds[1]) /
				this._projectedTileSize(i);
			boundsMatchesGrid = Math.abs(gridHeight - Math.round(gridHeight)) > 1e-3;
		}

		if (!boundsMatchesGrid) {
			scaleTransforms = {};
			for (var i = this.options.minZoom; i < this.options.maxZoom; i++) {
				upperY = crsBounds[1] + Math.ceil((crsBounds[3] - crsBounds[1]) /
					this._projectedTileSize(i)) * this._projectedTileSize(i);
				scaleTransforms[this.crs.scale(i)] = new L.Transformation(1, -crsBounds[0], -1, upperY);
			}

			this.crs = new L.Proj.CRS.TMS(this.crs.projection._proj, crsBounds, this.crs.options);
			this.crs.transformation = new L.Proj.ScaleDependantTransformation(scaleTransforms);
		}
	},

	getTileUrl: function(tilePoint) {
		// TODO: relies on some of TileLayer's internals
		var z = this._getZoomForUrl(),
			gridHeight = Math.ceil((this.crs.projectedBounds[3] - this.crs.projectedBounds[1]) / this._projectedTileSize(z));

		return L.Util.template(this._url, L.Util.extend({
			s: this._getSubdomain(tilePoint),
			z: z,
			x: tilePoint.x,
			y: gridHeight - tilePoint.y - 1
		}, this.options));
	},

	_projectedTileSize: function(zoom) {
		return (this.options.tileSize / this.crs.scale(zoom));
	}
});

L.Proj.GeoJSON = L.GeoJSON.extend({
	initialize: function(geojson, options) {
		if (geojson.crs && geojson.crs.type == 'name') {
			var crs = new L.Proj.CRS(geojson.crs.properties.name)
			options = options || {};
			options.coordsToLatLng = function(coords) {
				var point = L.point(coords[0], coords[1]);
				return crs.projection.unproject(point);
			};
		}
		L.GeoJSON.prototype.initialize.call(this, geojson, options);
	}
});

L.Proj.geoJson = function(geojson, options) {
	return new L.Proj.GeoJSON(geojson, options);
};

if (typeof L.CRS !== 'undefined') {
	// This is left here for backwards compatibility
	L.CRS.proj4js = (function () {
		return function (code, def, transformation, options) {
			options = options || {};
			if (transformation) options.transformation = transformation;

			return new L.Proj.CRS(code, def, options);
		};
	}());
}

return L.Proj;

}));
<|endoftext|>