<commit_before>/**
 * Universal Controls
 *
 * @author Don McCurdy <dm@donmccurdy.com>
 */

var COMPONENT_SUFFIX = '-controls',
    MAX_DELTA = 0.2, // ms
    PI_2 = Math.PI / 2;

module.exports = {

  /*******************************************************************
   * Schema
   */

  dependencies: ['velocity', 'rotation'],

  schema: {
    enabled:              { default: true },
    movementEnabled:      { default: true },
    movementControls:     { default: ['gamepad', 'keyboard', 'touch', 'hmd'] },
    rotationEnabled:      { default: true },
    rotationControls:     { default: ['hmd', 'gamepad', 'mouse'] },
    movementSpeed:        { default: 5 }, // m/s
    movementEasing:       { default: 15 }, // m/s2
    movementAcceleration: { default: 80 }, // m/s2
    rotationSensitivity:  { default: 0.05 }, // radians/frame, ish
    fly:                  { default: false }
  },

  /*******************************************************************
   * Lifecycle
   */

  init: function () {
    var rotation = this.el.getAttribute('rotation');

    if (this.el.hasAttribute('look-controls') && this.data.rotationEnabled) {
      console.error('[universal-controls] The `universal-controls` component is a replacement '
        + 'for `look-controls`, and cannot be used in combination with it.');
    }

    // Movement
    this.velocity = new THREE.Vector3();

    // Rotation
    this.pitch = new THREE.Object3D();
    this.pitch.rotation.x = THREE.Math.degToRad(rotation.x);
    this.yaw = new THREE.Object3D();
    this.yaw.position.y = 10;
    this.yaw.rotation.y = THREE.Math.degToRad(rotation.y);
    this.yaw.add(this.pitch);
    this.heading = new THREE.Euler(0, 0, 0, 'YXZ');

    if (this.el.sceneEl.hasLoaded) {
      this.injectControls();
    } else {
      this.el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
    }
  },

  update: function () {
    if (this.el.sceneEl.hasLoaded) {
      this.injectControls();
    }
  },

  injectControls: function () {
    var i, name,
        data = this.data;

    for (i = 0; i < data.movementControls.length; i++) {
      name = data.movementControls[i] + COMPONENT_SUFFIX;
      if (!this.el.components[name]) {
        this.el.setAttribute(name, '');
      }
    }

    for (i = 0; i < data.rotationControls.length; i++) {
      name = data.rotationControls[i] + COMPONENT_SUFFIX;
      if (!this.el.components[name]) {
        this.el.setAttribute(name, '');
      }
    }
  },

  /*******************************************************************
   * Tick
   */

  tick: function (t, dt) {
    if (!dt) { return; }

    // Update rotation.
    if (this.data.rotationEnabled) this.updateRotation(dt);

    // Update velocity. If FPS is too low, reset.
    if (this.data.movementEnabled && dt / 1000 > MAX_DELTA) {
      this.velocity.set(0, 0, 0);
      this.el.setAttribute('velocity', this.velocity);
    } else {
      this.updateVelocity(dt);
    }
  },

  /*******************************************************************
   * Rotation
   */

  updateRotation: function (dt) {
    var control, dRotation,
        data = this.data;

    for (var i = 0, l = data.rotationControls.length; i < l; i++) {
      control = this.el.components[data.rotationControls[i] + COMPONENT_SUFFIX];
      if (control && control.isRotationActive()) {
        if (control.getRotationDelta) {
          dRotation = control.getRotationDelta(dt);
          dRotation.multiplyScalar(data.rotationSensitivity);
          this.yaw.rotation.y -= dRotation.x;
          this.pitch.rotation.x -= dRotation.y;
          this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x));
          this.el.setAttribute('rotation', {
            x: THREE.Math.radToDeg(this.pitch.rotation.x),
            y: THREE.Math.radToDeg(this.yaw.rotation.y),
            z: 0
          });
        } else if (control.getRotation) {
          this.el.setAttribute('rotation', control.getRotation());
        } else {
          throw new Error('Incompatible rotation controls: %s', data.rotationControls[i]);
        }
        break;
      }
    }
  },

  /*******************************************************************
   * Movement
   */

  updateVelocity: function (dt) {
    var control, dVelocity,
        velocity = this.velocity,
        data = this.data;

    if (data.movementEnabled) {
      for (var i = 0, l = data.movementControls.length; i < l; i++) {
        control = this.el.components[data.movementControls[i] + COMPONENT_SUFFIX];
        if (control && control.isVelocityActive()) {
          if (control.getVelocityDelta) {
            dVelocity = control.getVelocityDelta(dt);
          } else if (control.getVelocity) {
            this.el.setAttribute('velocity', control.getVelocity());
            return;
          } else if (control.getPositionDelta) {
            velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
            this.el.setAttribute('velocity', velocity);
            return;
          } else {
            throw new Error('Incompatible movement controls: ', data.movementControls[i]);
          }
          break;
        }
      }
    }

    velocity.copy(this.el.getAttribute('velocity'));
    velocity.x -= velocity.x * data.movementEasing * dt / 1000;
    velocity.z -= velocity.z * data.movementEasing * dt / 1000;

    if (dVelocity && data.movementEnabled) {
      // Set acceleration
      if (dVelocity.length() > 1) {
        dVelocity.setLength(this.data.movementAcceleration * dt / 1000);
      } else {
        dVelocity.multiplyScalar(this.data.movementAcceleration * dt / 1000);
      }

      // Rotate to heading
      var rotation = this.el.getAttribute('rotation');
      if (rotation) {
        this.heading.set(
          data.fly ? THREE.Math.degToRad(rotation.x) : 0,
          THREE.Math.degToRad(rotation.y),
          0
        );
        dVelocity.applyEuler(this.heading);
      }

      velocity.add(dVelocity);

      // TODO - Several issues here:
      // (1) Interferes w/ gravity.
      // (2) Interferes w/ jumping.
      // (3) Likely to interfere w/ relative position to moving platform.
      // if (velocity.length() > data.movementSpeed) {
      //   velocity.setLength(data.movementSpeed);
      // }
    }

    this.el.setAttribute('velocity', velocity);
  }
};
<commit_msg>Add universal-controls.movementEasingY<commit_after>/**
 * Universal Controls
 *
 * @author Don McCurdy <dm@donmccurdy.com>
 */

var COMPONENT_SUFFIX = '-controls',
    MAX_DELTA = 0.2, // ms
    PI_2 = Math.PI / 2;

module.exports = {

  /*******************************************************************
   * Schema
   */

  dependencies: ['velocity', 'rotation'],

  schema: {
    enabled:              { default: true },
    movementEnabled:      { default: true },
    movementControls:     { default: ['gamepad', 'keyboard', 'touch', 'hmd'] },
    rotationEnabled:      { default: true },
    rotationControls:     { default: ['hmd', 'gamepad', 'mouse'] },
    movementSpeed:        { default: 5 }, // m/s
    movementEasing:       { default: 15 }, // m/s2
    movementEasingY:      { default: 0  }, // m/s2
    movementAcceleration: { default: 80 }, // m/s2
    rotationSensitivity:  { default: 0.05 }, // radians/frame, ish
    fly:                  { default: false },
  },

  /*******************************************************************
   * Lifecycle
   */

  init: function () {
    var rotation = this.el.getAttribute('rotation');

    if (this.el.hasAttribute('look-controls') && this.data.rotationEnabled) {
      console.error('[universal-controls] The `universal-controls` component is a replacement '
        + 'for `look-controls`, and cannot be used in combination with it.');
    }

    // Movement
    this.velocity = new THREE.Vector3();

    // Rotation
    this.pitch = new THREE.Object3D();
    this.pitch.rotation.x = THREE.Math.degToRad(rotation.x);
    this.yaw = new THREE.Object3D();
    this.yaw.position.y = 10;
    this.yaw.rotation.y = THREE.Math.degToRad(rotation.y);
    this.yaw.add(this.pitch);
    this.heading = new THREE.Euler(0, 0, 0, 'YXZ');

    if (this.el.sceneEl.hasLoaded) {
      this.injectControls();
    } else {
      this.el.sceneEl.addEventListener('loaded', this.injectControls.bind(this));
    }
  },

  update: function () {
    if (this.el.sceneEl.hasLoaded) {
      this.injectControls();
    }
  },

  injectControls: function () {
    var i, name,
        data = this.data;

    for (i = 0; i < data.movementControls.length; i++) {
      name = data.movementControls[i] + COMPONENT_SUFFIX;
      if (!this.el.components[name]) {
        this.el.setAttribute(name, '');
      }
    }

    for (i = 0; i < data.rotationControls.length; i++) {
      name = data.rotationControls[i] + COMPONENT_SUFFIX;
      if (!this.el.components[name]) {
        this.el.setAttribute(name, '');
      }
    }
  },

  /*******************************************************************
   * Tick
   */

  tick: function (t, dt) {
    if (!dt) { return; }

    // Update rotation.
    if (this.data.rotationEnabled) this.updateRotation(dt);

    // Update velocity. If FPS is too low, reset.
    if (this.data.movementEnabled && dt / 1000 > MAX_DELTA) {
      this.velocity.set(0, 0, 0);
      this.el.setAttribute('velocity', this.velocity);
    } else {
      this.updateVelocity(dt);
    }
  },

  /*******************************************************************
   * Rotation
   */

  updateRotation: function (dt) {
    var control, dRotation,
        data = this.data;

    for (var i = 0, l = data.rotationControls.length; i < l; i++) {
      control = this.el.components[data.rotationControls[i] + COMPONENT_SUFFIX];
      if (control && control.isRotationActive()) {
        if (control.getRotationDelta) {
          dRotation = control.getRotationDelta(dt);
          dRotation.multiplyScalar(data.rotationSensitivity);
          this.yaw.rotation.y -= dRotation.x;
          this.pitch.rotation.x -= dRotation.y;
          this.pitch.rotation.x = Math.max(-PI_2, Math.min(PI_2, this.pitch.rotation.x));
          this.el.setAttribute('rotation', {
            x: THREE.Math.radToDeg(this.pitch.rotation.x),
            y: THREE.Math.radToDeg(this.yaw.rotation.y),
            z: 0
          });
        } else if (control.getRotation) {
          this.el.setAttribute('rotation', control.getRotation());
        } else {
          throw new Error('Incompatible rotation controls: %s', data.rotationControls[i]);
        }
        break;
      }
    }
  },

  /*******************************************************************
   * Movement
   */

  updateVelocity: function (dt) {
    var control, dVelocity,
        velocity = this.velocity,
        data = this.data;

    if (data.movementEnabled) {
      for (var i = 0, l = data.movementControls.length; i < l; i++) {
        control = this.el.components[data.movementControls[i] + COMPONENT_SUFFIX];
        if (control && control.isVelocityActive()) {
          if (control.getVelocityDelta) {
            dVelocity = control.getVelocityDelta(dt);
          } else if (control.getVelocity) {
            this.el.setAttribute('velocity', control.getVelocity());
            return;
          } else if (control.getPositionDelta) {
            velocity.copy(control.getPositionDelta(dt).multiplyScalar(1000 / dt));
            this.el.setAttribute('velocity', velocity);
            return;
          } else {
            throw new Error('Incompatible movement controls: ', data.movementControls[i]);
          }
          break;
        }
      }
    }

    velocity.copy(this.el.getAttribute('velocity'));
    velocity.x -= velocity.x * data.movementEasing * dt / 1000;
    velocity.y -= velocity.y * data.movementEasingY * dt / 1000;
    velocity.z -= velocity.z * data.movementEasing * dt / 1000;

    if (dVelocity && data.movementEnabled) {
      // Set acceleration
      if (dVelocity.length() > 1) {
        dVelocity.setLength(this.data.movementAcceleration * dt / 1000);
      } else {
        dVelocity.multiplyScalar(this.data.movementAcceleration * dt / 1000);
      }

      // Rotate to heading
      var rotation = this.el.getAttribute('rotation');
      if (rotation) {
        this.heading.set(
          data.fly ? THREE.Math.degToRad(rotation.x) : 0,
          THREE.Math.degToRad(rotation.y),
          0
        );
        dVelocity.applyEuler(this.heading);
      }

      velocity.add(dVelocity);

      // TODO - Several issues here:
      // (1) Interferes w/ gravity.
      // (2) Interferes w/ jumping.
      // (3) Likely to interfere w/ relative position to moving platform.
      // if (velocity.length() > data.movementSpeed) {
      //   velocity.setLength(data.movementSpeed);
      // }
    }

    this.el.setAttribute('velocity', velocity);
  }
};
<|endoftext|>


<commit_before>var Class = require('../utils/Class');
var Extend = require('../utils/object/Extend');
var EventDispatcher = require('../events/EventDispatcher');
//  Phaser.Sound.BaseSound
var BaseSound = new Class({
    initialize: function BaseSound(manager, key, config) {
        /**
         * Local reference to the sound manager.
         *
         * @property {Phaser.Sound.BaseSoundManager} manager
         */
        this.manager = manager;
        /**
         * [description]
         *
         * @property {string} key
         */
        this.key = key;
        /**
         * [description]
         *
         * Duration set explicitly, rest of default values
         * will be set by other properties setters.
         *
         * @property {ISoundConfig} config
         */
        this.config = {
            duration: 0
        };
        /**
         * Reference to the currently used config.
         * It could be default config or marker config.
         *
         * @property {ISoundConfig} currentConfig
         */
        this.currentConfig = this.config;
        /**
         * [description]
         *
         * @property {boolean} mute
         */
        this.mute = false;
        /**
         * [description]
         *
         * @property {number} volume
         */
        this.volume = 1;
        /**
         * [description]
         *
         * @property {number} rate
         */
        this.rate = 1;
        /**
         * [description]
         *
         * @property {number} seek
         */
        this.seek = 0;
        /**
         * [description]
         *
         * @property {boolean} loop
         */
        this.loop = false;
        /**
         * [description]
         *
         * @property {number} pan
         */
        this.pan = 0;
        this.config = Extend(this.config, config);
        /**
         * Flag indicating if sound is currently playing.
         *
         * @property {boolean} isPlaying
         */
        this.isPlaying = false;
        /**
         * Object containing markers definitions.
         *
         * @property {{}} markers
         */
        this.markers = {};
        /**
         * Name of the currently played marker.
         * If no marker is played, but instead the whole sound
         * the value is an empty string - ''.
         *
         * @property {string} currentMarker
         */
        this.currentMarker = '';
        /**
         * [description]
         *
         * @property {Phaser.Tween}
         */
        this.fadeTween = null; // TODO see how to use global tween
        /**
         * Event dispatches used to handle all sound instance
         * relevant events.
         *
         * @property {Phaser.Events.EventDispatcher}
         */
        this.events = new EventDispatcher();
    },
    // TODO set default methods to NOOP if not used
    addMarker: function (marker) {
        return false;
    },
    removeMarker: function (markerName) {
        return false;
    },
    play: function (marker, config) {
        if (marker === void 0) { marker = ''; }
        if (typeof marker === 'object') {
            config = marker;
            marker = '';
        }
        if (typeof marker !== 'string') {
            console.error('Sound marker name has to be a string!');
            return null;
        }
        if (!marker) {
            this.currentConfig = this.config;
        }
        else {
            if (!this.markers[marker]) {
                console.error('No marker with name \'' + marker + '\' found for sound \'' + this.key + '\'!');
                return null;
            }
            this.currentMarker = marker;
            this.currentConfig = this.markers[marker].config;
        }
        this.currentConfig = Extend(this.currentConfig, config);
        return this;
    },
    pause: function () {
        return this;
    },
    resume: function () {
        return this;
    },
    stop: function () {
        return this;
    },
    applyConfig: function () {
        this.mute = this.currentConfig.mute;
        this.volume = this.currentConfig.volume;
        // TODO assign other config values to buffer source
    },
    fadeTo: function (volume, duration) {
        return null;
    },
    update: function () {
    },
    destroy: function () {
    }
});
module.exports = BaseSound;
<commit_msg>updated description for rate property for BaseSound class setting rate value when applying config<commit_after>var Class = require('../utils/Class');
var Extend = require('../utils/object/Extend');
var EventDispatcher = require('../events/EventDispatcher');
//  Phaser.Sound.BaseSound
var BaseSound = new Class({
    initialize: function BaseSound(manager, key, config) {
        /**
         * Local reference to the sound manager.
         *
         * @property {Phaser.Sound.BaseSoundManager} manager
         */
        this.manager = manager;
        /**
         * [description]
         *
         * @property {string} key
         */
        this.key = key;
        /**
         * [description]
         *
         * Duration set explicitly, rest of default values
         * will be set by other properties setters.
         *
         * @property {ISoundConfig} config
         */
        this.config = {
            duration: 0
        };
        /**
         * Reference to the currently used config.
         * It could be default config or marker config.
         *
         * @property {ISoundConfig} currentConfig
         */
        this.currentConfig = this.config;
        /**
         * [description]
         *
         * @property {boolean} mute
         */
        this.mute = false;
        /**
         * [description]
         *
         * @property {number} volume
         */
        this.volume = 1;
        /**
         * Defines the speed at which the audio asset will be played.
         * Value of 1.0 plays the audio at full speed, 0.5 plays the audio
         * at half speed and 2.0 doubles the audio's playback speed.
         * This value gets multiplied by global rate to have the final playback speed.
         *
         * @property {number} rate
         */
        this.rate = 1;
        /**
         * [description]
         *
         * @property {number} seek
         */
        this.seek = 0;
        /**
         * [description]
         *
         * @property {boolean} loop
         */
        this.loop = false;
        /**
         * [description]
         *
         * @property {number} pan
         */
        this.pan = 0;
        this.config = Extend(this.config, config);
        /**
         * Flag indicating if sound is currently playing.
         *
         * @property {boolean} isPlaying
         */
        this.isPlaying = false;
        /**
         * Object containing markers definitions.
         *
         * @property {{}} markers
         */
        this.markers = {};
        /**
         * Name of the currently played marker.
         * If no marker is played, but instead the whole sound
         * the value is an empty string - ''.
         *
         * @property {string} currentMarker
         */
        this.currentMarker = '';
        /**
         * [description]
         *
         * @property {Phaser.Tween}
         */
        this.fadeTween = null; // TODO see how to use global tween
        /**
         * Event dispatches used to handle all sound instance
         * relevant events.
         *
         * @property {Phaser.Events.EventDispatcher}
         */
        this.events = new EventDispatcher();
    },
    // TODO set default methods to NOOP if not used
    addMarker: function (marker) {
        return false;
    },
    removeMarker: function (markerName) {
        return false;
    },
    play: function (marker, config) {
        if (marker === void 0) { marker = ''; }
        if (typeof marker === 'object') {
            config = marker;
            marker = '';
        }
        if (typeof marker !== 'string') {
            console.error('Sound marker name has to be a string!');
            return null;
        }
        if (!marker) {
            this.currentConfig = this.config;
        }
        else {
            if (!this.markers[marker]) {
                console.error('No marker with name \'' + marker + '\' found for sound \'' + this.key + '\'!');
                return null;
            }
            this.currentMarker = marker;
            this.currentConfig = this.markers[marker].config;
        }
        this.currentConfig = Extend(this.currentConfig, config);
        return this;
    },
    pause: function () {
        return this;
    },
    resume: function () {
        return this;
    },
    stop: function () {
        return this;
    },
    applyConfig: function () {
        this.mute = this.currentConfig.mute;
        this.volume = this.currentConfig.volume;
        this.rate = this.currentConfig.rate;
        // TODO assign other config values to buffer source
    },
    fadeTo: function (volume, duration) {
        return null;
    },
    update: function () {
    },
    destroy: function () {
    }
});
module.exports = BaseSound;
<|endoftext|>


<commit_before>module.exports = function(dx, dy, kx, ky) {
  return {
    point: function(coordinates) {
      coordinates[0] = Math.floor((coordinates[0] + dx) * kx);
      coordinates[1] = Math.floor((coordinates[1] + dy) * ky);
    },
    line: function(coordinates) {
      var i = 0,
          j = 1,
          n = coordinates.length,
          pi = coordinates[0],
          pj,
          px = pi[0] = Math.floor((pi[0] + dx) * kx),
          py = pi[1] = Math.floor((pi[1] + dy) * ky),
          x,
          y;

      while (++i < n) {
        pi = coordinates[i];
        x = Math.floor((pi[0] + dx) * kx);
        y = Math.floor((pi[1] + dy) * ky);
        if (x !== px || y !== py) { // skip coincident points
          pj = coordinates[j++];
          pj[0] = px = x;
          pj[1] = py = y;
        }
      }

      coordinates.length = j;
    },
    transform: {
      scale: [1 / kx, 1 / ky],
      translate: [x0, y0]
    }
  };
};
<commit_msg>Remove dead code.<commit_after><|endoftext|>


<commit_before>const fs = require('fs');
const _ = require('lodash');
const moment = require('moment');

let radgradDump;

const getCollectionData = (collectionName) => _.find(radgradDump.collections, (c) => c.name === collectionName).contents;

const getUserProfile = (username) => {
  const studentProfiles = getCollectionData('StudentProfileCollection');
  return _.find(studentProfiles, (p) => p.username === username);
};

const getRegisteredStudentUsernames = () => {
  const studentProfiles = getCollectionData('StudentProfileCollection');
  const students = _.filter(studentProfiles, (p) => p.isAlumni === false);
  return _.map(students, (s) => s.username);
};

const getFullName = (username) => {
  const profile = getUserProfile(username);
  if (profile) {
    return `${profile.firstName} ${profile.lastName}`;
  }
  return '';
};

const getIceSnapshot = (profile) => {
  const iceSnapshots = getCollectionData('IceSnapshotCollection');
  const ice = _.find(iceSnapshots.contents, (i) => i.username === profile.username);
  return ice;
};

const getStudentIce = (username) => {
  const iceSnapshots = getCollectionData('IceSnapshotCollection');
  const ice = _.find(iceSnapshots, (i) => i.username === username);
  return ice;
};

const getUserInteractions = () => {
  const userInteractionCollection = _.find(radgradDump.collections, (c) => c.name === 'UserInteractionCollection').contents;
  return userInteractionCollection;
};

const getUserInteractionsBetween = (startStr, endStr) => {
  const start = moment(startStr);
  const end = moment(endStr);
  const logins = getUserInteractions();
  const between = _.filter(logins, (l) => {
    const lTime = moment(l.timestamp);
    return lTime.isBetween(start, end, null, '[]');
  });
  return between;
};

const getActiveStudentsBetween = (startStr, endStr) => {
  const interactions = getUserInteractionsBetween(startStr, endStr);
  let users = _.map(interactions, (i) => i.username);
  users = _.uniq(users);
  return _.filter(users, (u) => u.username !== 'radgrad@hawaii.edu');
};

const getActiveStudentNamesBetween = (startStr, endStr) => {
  const activeStudents = getActiveStudentsBetween(startStr, endStr);
  // console.log(activeStudents);
  const names = _.map(activeStudents, (u) => {
    // console.log(u, getFullName(u));
    let ice = getStudentIce(u);
    if (_.isUndefined(ice)) {
      ice = {};
      ice.i = 0;
      ice.c = 0;
      ice.e = 0;
    }
    // console.log(ice);
    return `${getFullName(u)}: ${getUserLoginsBetween(u, startStr, endStr).length} [${ice.i}, ${ice.c}, ${ice.e}]`;
  });
  // console.log(names);
  return names;
};

const getInteractionsPerUser = (username) => {
  const interactions = getUserInteractions();
  return _.filter(interactions, (i) => i.username === username);
};

const getInteractionsPerUserBetween = (username, startStr, endStr) => {
  const interactions = getUserInteractionsBetween(startStr, endStr);
  return _.filter(interactions, (i) => i.username === username);
};

const getInteractionsByType = (type) => {
  const interactions = getUserInteractions();
  return _.filter(interactions, (i) => i.type === type);
};

const getInteractionsByTypeBetween = (type, startStr, endStr) => {
  const start = moment(startStr);
  const end = moment(endStr);
  const interactions = getInteractionsByType(type);
  const between = _.filter(interactions, (l) => {
    const lTime = moment(l.timestamp);
    return lTime.isBetween(start, end, null, '[]');
  });
  return between;
};

const getLoginInteractions = () => {
  return getInteractionsByType('login');
};

const getLoginsBetween = (startStr, endStr) => {
  const start = moment(startStr);
  const end = moment(endStr);
  const logins = getLoginInteractions();
  const between = _.filter(logins, (l) => {
    const lTime = moment(l.timestamp);
    return lTime.isBetween(start, end, null, '[]');
  });
  return between;
};

const getLoginsBetweenWoRadgrad = (startStr, endStr) => {
  const logins = getLoginsBetween(startStr, endStr);
  return _.filter(logins, (l) => l.username !== 'radgrad@hawaii.edu');
};

const getUserLogins = (profile) => {
  const logins = getLoginInteractions();
  const userLogins = _.filter(logins, (l) => l.username === profile.username);
  return userLogins;
};

const getUniqLoginsBetween = (startStr, endStr) => {
  const logins = getLoginsBetween(startStr, endStr);
  return _.uniqBy(logins, (l) => l.username);
};

const getUniqLoginsBetweenWoRadgrad = (startStr, endStr) => {
  const logins = getUniqLoginsBetween(startStr, endStr);
  return _.filter(logins, (l) => l.username !== 'radgrad@hawaii.edu');
};

const getUserLoginsBetween = (username, startStr, endStr) => {
  const logins = getLoginsBetween(startStr, endStr);
  return _.filter(logins, (l) => l.username === username);
};

const loginAnalysis = (startString, endString) => {
  const uniqLogins = getUniqLoginsBetween(startString, endString);
  // console.log(uniqLogins);
  let min = 1000;
  let max = 0;
  let sum = 0;
  let mostLogins;
  _.forEach(uniqLogins, (l) => {
    if (l.username !== 'radgrad@hawaii.edu') {
      const userLogins = getUserLoginsBetween(l.username, startString, endString);
      const { length } = userLogins;
      if (min > length) {
        min = length;
      }
      if (max < length) {
        max = length;
        mostLogins = l.username;
      }
      sum += length;
    }
  });
  console.log('User logins min=%o max=%o sum=%o user with most %o', min, max, sum, mostLogins);
};

const userAnalysis = (username, startString, endString) => {
  let results = getFullName(username);
  results = `${results}, ${getUserLoginsBetween(username, startString, endString).length}`;
  return results;
};

const sessionInformation = (username, startStr, endStr) => {
  const interactions = getInteractionsPerUserBetween(username, startStr, endStr);
  let sessions = 0;
  const sessionLength = [];
  let lastTimeStamp;
  let sessionStart;
  let sessionEnd;
  _.forEach(interactions, (i) => {
    if (_.isUndefined(lastTimeStamp)) {
      // console.log('init');
      lastTimeStamp = moment(i.timestamp);
      sessionStart = lastTimeStamp;
      sessionEnd = sessionStart;
    } else {
      // console.log('next');
      const currentTimestamp = moment(i.timestamp);
      if (currentTimestamp.diff(lastTimeStamp, 'hours', true) > 1) {
        sessions++;
        sessionLength.push(sessionEnd.diff(sessionStart, 'minutes'));
        sessionStart = currentTimestamp;
        sessionEnd = currentTimestamp;
      } else {
        sessionEnd = currentTimestamp;
      }
      lastTimeStamp = currentTimestamp;
    }
  });
  let totalSessionTime = 0;
  if (sessionLength.length > 0) {
    totalSessionTime = sessionLength.reduce((total, num) => total + num);
    const retVal = {};
    retVal.username = username;
    retVal.sessionCount = sessions;
    retVal.sessionLength = sessionLength;
    retVal.shortestSession = Math.min(...sessionLength);
    retVal.longestSession = Math.max(...sessionLength);
    retVal.totalSessionTime = totalSessionTime;
    retVal.averageSession = totalSessionTime / sessions;
    return retVal;
  }
  // console.log(totalSessionTime);
  const retVal = {};
  retVal.username = username;
  retVal.sessionCount = 0;
  retVal.sessionLength = 0;
  retVal.shortestSession = 0;
  retVal.longestSession = 0;
  retVal.totalSessionTime = 0;
  retVal.averageSession = 0;
  return retVal;
};

const studentSessionInformationBetween = (startStr, endStr, progressp = true) => {
  const usernames = getActiveStudentsBetween(startStr, endStr);
  const results = [];
  _.forEach(usernames, (username, index) => {
    results.push(sessionInformation(username, startStr, endStr));
    if (index % 10 === 0 && progressp) {
      console.log(`working ${(index / usernames.length) * 100} %`);
    }
  });
  return results;
};

const studentSessionInformationToCSV = (data) => {
  let resultStr = 'Student, Session Count, Session Lengths, Shortest, Longest, Total Time, Average\n';
  _.forEach(data, (d) => {
    if (d.sessionCount > 0) {
      resultStr = `${resultStr}${d.username},${d.sessionCount},${d.sessionLength.join(' ')},${d.shortestSession},${d.longestSession},${d.totalSessionTime},${d.averageSession}\n`;
    } else {
      resultStr = `${resultStr}${d.username},0,0,0,0,0,0\n`;
    }
  });
  return resultStr;
};

const sessionInformationBetween = (startStr, endStr) => {
  const usernames = getActiveStudentsBetween(startStr, endStr);
  // console.log(usernames);
  let sessions = 0;
  let sessionTime = 0;
  let lastTimeStamp;
  let sessionStart;
  let sessionEnd;
  let minSessions = 1000;
  let maxSessions = 0;
  _.forEach(usernames, (username, index) => {
    const sessionInfo = sessionInformation(username, startStr, endStr);
    sessions += sessionInfo.sessionCount;
    sessionTime += sessionInfo.totalSessionTime;
    if (minSessions > sessionInfo.sessionCount) {
      minSessions = sessionInfo.sessionCount;
    }
    if (maxSessions < sessionInfo.sessionCount) {
      maxSessions = sessionInfo.sessionCount;
    }
    if (index % 10 === 0) {
      console.log('working %o', index);
    }
  });
  const retVal = {};
  retVal.sessionCount = sessions;
  retVal.minSessions = minSessions;
  retVal.maxSessions = maxSessions;
  retVal.totalSessionTime = sessionTime;
  retVal.averageSessionTime = sessionTime / sessions;
  return retVal;
};

const pageViewsBetween = (startStr, endStr) => {
  const pageViews = getInteractionsByTypeBetween('pageView', startStr, endStr);
  const pageViewsByPage = _.groupBy(pageViews, (p) => p.typeData[0]);
  return _.mapValues(pageViewsByPage, (value) => value.length);
};

const coursePageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('courses'));
};

const opportunityPageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('opportunities'));
};

const degreePlanPageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('degree'));
};

const explorerPageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('explorer'));
};

const initDataDump = () => {
  const { argv } = process;
  // console.log(argv);
  if (argv.length < 3) {
    console.error('Usage: node analyze-data-dump.js <fileName>');
  } else {
    const filename = argv[2];
    const data = fs.readFileSync(filename);
    radgradDump = JSON.parse(data.toString());
  }
};

const opportunityICE = () => {
  const opportunities = getCollectionData('OpportunityCollection');
  const info = _.map(opportunities, (o) => {
    if (o.retired) {
      return `${o.name}: [${o.ice.i}, ${o.ice.c}, ${o.ice.e}] Retired`;
    }
    return `${o.name}: [${o.ice.i}, ${o.ice.c}, ${o.ice.e}]`;
  });
  return info;
};

const analyzeData = () => {
  initDataDump();
  // console.log('# Registered students: %o', getRegisteredStudentUsernames().length);
  // console.log('# Active students Spring 19: %o', getActiveStudentsBetween('2019-01-01', '2019-05-31').length);
  // const names = getActiveStudentNamesBetween('2019-01-01', '2019-05-31');
  // console.log(names.join(', '));
  // loginAnalysis('2019-01-01', '2019-05-31');
  // console.log(sessionInformationBetween('2019-08-01', '2019-12-31'));
  console.log(studentSessionInformationToCSV(studentSessionInformationBetween('2019-08-01', '2019-12-31', false)));
  // console.log(pageViewsBetween('2019-01-01', '2019-05-31'));
  // console.log(opportunityICE());
};

analyzeData();
<commit_msg>Update analyze-data-dump.js<commit_after>const fs = require('fs');
const _ = require('lodash');
const moment = require('moment');

let radgradDump;

const getCollectionData = (collectionName) => _.find(radgradDump.collections, (c) => c.name === collectionName).contents;

const getUserProfile = (username) => {
  const studentProfiles = getCollectionData('StudentProfileCollection');
  return _.find(studentProfiles, (p) => p.username === username);
};

const getRegisteredStudentUsernames = () => {
  const studentProfiles = getCollectionData('StudentProfileCollection');
  const students = _.filter(studentProfiles, (p) => p.isAlumni === false);
  return _.map(students, (s) => s.username);
};

const getFullName = (username) => {
  const profile = getUserProfile(username);
  if (profile) {
    return `${profile.firstName} ${profile.lastName}`;
  }
  return '';
};

const getIceSnapshot = (profile) => {
  const iceSnapshots = getCollectionData('IceSnapshotCollection');
  const ice = _.find(iceSnapshots.contents, (i) => i.username === profile.username);
  return ice;
};

const getStudentIce = (username) => {
  const iceSnapshots = getCollectionData('IceSnapshotCollection');
  const ice = _.find(iceSnapshots, (i) => i.username === username);
  return ice;
};

const getUserInteractions = () => {
  const userInteractionCollection = _.find(radgradDump.collections, (c) => c.name === 'UserInteractionCollection').contents;
  return userInteractionCollection;
};

const getUserInteractionsBetween = (startStr, endStr) => {
  const start = moment(startStr);
  const end = moment(endStr);
  const logins = getUserInteractions();
  const between = _.filter(logins, (l) => {
    const lTime = moment(l.timestamp);
    return lTime.isBetween(start, end, null, '[]');
  });
  return _.sortBy(between, (i) => i.timestamp);
};

const getActiveStudentsBetween = (startStr, endStr) => {
  const interactions = getUserInteractionsBetween(startStr, endStr);
  let users = _.map(interactions, (i) => i.username);
  users = _.uniq(users);
  return _.filter(users, (u) => u.username !== 'radgrad@hawaii.edu');
};

const getActiveStudentNamesBetween = (startStr, endStr) => {
  const activeStudents = getActiveStudentsBetween(startStr, endStr);
  // console.log(activeStudents);
  const names = _.map(activeStudents, (u) => {
    // console.log(u, getFullName(u));
    let ice = getStudentIce(u);
    if (_.isUndefined(ice)) {
      ice = {};
      ice.i = 0;
      ice.c = 0;
      ice.e = 0;
    }
    // console.log(ice);
    return `${getFullName(u)}: ${getUserLoginsBetween(u, startStr, endStr).length} [${ice.i}, ${ice.c}, ${ice.e}]`;
  });
  // console.log(names);
  return names;
};

const getInteractionsPerUser = (username) => {
  const interactions = getUserInteractions();
  return _.filter(interactions, (i) => i.username === username);
};

const getInteractionsPerUserBetween = (username, startStr, endStr) => {
  const interactions = getUserInteractionsBetween(startStr, endStr);
  return _.filter(interactions, (i) => i.username === username);
};

const getInteractionsByType = (type) => {
  const interactions = getUserInteractions();
  return _.filter(interactions, (i) => i.type === type);
};

const getInteractionsByTypeBetween = (type, startStr, endStr) => {
  const start = moment(startStr);
  const end = moment(endStr);
  const interactions = getInteractionsByType(type);
  const between = _.filter(interactions, (l) => {
    const lTime = moment(l.timestamp);
    return lTime.isBetween(start, end, null, '[]');
  });
  return between;
};

const getLoginInteractions = () => {
  return getInteractionsByType('login');
};

const getLoginsBetween = (startStr, endStr) => {
  const start = moment(startStr);
  const end = moment(endStr);
  const logins = getLoginInteractions();
  const between = _.filter(logins, (l) => {
    const lTime = moment(l.timestamp);
    return lTime.isBetween(start, end, null, '[]');
  });
  return between;
};

const getLoginsBetweenWoRadgrad = (startStr, endStr) => {
  const logins = getLoginsBetween(startStr, endStr);
  return _.filter(logins, (l) => l.username !== 'radgrad@hawaii.edu');
};

const getUserLogins = (profile) => {
  const logins = getLoginInteractions();
  const userLogins = _.filter(logins, (l) => l.username === profile.username);
  return userLogins;
};

const getUniqLoginsBetween = (startStr, endStr) => {
  const logins = getLoginsBetween(startStr, endStr);
  return _.uniqBy(logins, (l) => l.username);
};

const getUniqLoginsBetweenWoRadgrad = (startStr, endStr) => {
  const logins = getUniqLoginsBetween(startStr, endStr);
  return _.filter(logins, (l) => l.username !== 'radgrad@hawaii.edu');
};

const getUserLoginsBetween = (username, startStr, endStr) => {
  const logins = getLoginsBetween(startStr, endStr);
  return _.filter(logins, (l) => l.username === username);
};

const loginAnalysis = (startString, endString) => {
  const uniqLogins = getUniqLoginsBetween(startString, endString);
  // console.log(uniqLogins);
  let min = 1000;
  let max = 0;
  let sum = 0;
  let mostLogins;
  _.forEach(uniqLogins, (l) => {
    if (l.username !== 'radgrad@hawaii.edu') {
      const userLogins = getUserLoginsBetween(l.username, startString, endString);
      const { length } = userLogins;
      if (min > length) {
        min = length;
      }
      if (max < length) {
        max = length;
        mostLogins = l.username;
      }
      sum += length;
    }
  });
  console.log('User logins min=%o max=%o sum=%o user with most %o', min, max, sum, mostLogins);
};

const userAnalysis = (username, startString, endString) => {
  let results = getFullName(username);
  results = `${results}, ${getUserLoginsBetween(username, startString, endString).length}`;
  return results;
};

const sessionInformation = (username, startStr, endStr, consolep) => {
  const interactions = getInteractionsPerUserBetween(username, startStr, endStr);
  // if (consolep) {
  //   console.log(username, startStr, endStr, interactions.length);
  // }
  let sessions = 0;
  const sessionLength = [];
  let lastTimeStamp;
  let sessionStart;
  let sessionEnd;
  _.forEach(interactions, (i) => {
    if (_.isUndefined(lastTimeStamp)) {
      if (consolep) {
        console.log('init', i.timestamp);
      }
      lastTimeStamp = moment(i.timestamp);
      sessionStart = lastTimeStamp;
      sessionEnd = sessionStart;
    } else {
      // if (consolep) {
      //   console.log('next', sessionStart.format(), i.timestamp);
      // }
      const currentTimestamp = moment(i.timestamp);
      if (currentTimestamp.diff(lastTimeStamp, 'hours', true) > 1) {
        if (consolep) {
          console.log('new session', sessionStart.format(),
            sessionEnd.format(), sessionEnd.diff(sessionStart, 'minutes'));
        }
        sessions++;
        sessionLength.push(sessionEnd.diff(sessionStart, 'minutes'));
        sessionStart = currentTimestamp;
        sessionEnd = currentTimestamp;
      } else {
        sessionEnd = currentTimestamp;
      }
      lastTimeStamp = currentTimestamp;
    }
  });
  if (sessionLength.length === 0 && sessionEnd && sessionStart) { // only one session
    sessionLength.push(sessionEnd.diff(sessionStart, 'minutes'));
  }
  let totalSessionTime = 0;
  if (sessionLength.length > 0) {
    totalSessionTime = sessionLength.reduce((total, num) => total + num);
    const retVal = {};
    retVal.username = username;
    retVal.sessionCount = sessions;
    retVal.sessionLength = sessionLength;
    retVal.shortestSession = Math.min(...sessionLength);
    retVal.longestSession = Math.max(...sessionLength);
    retVal.totalSessionTime = totalSessionTime;
    retVal.averageSession = totalSessionTime / sessions;
    // if (consolep) {
    //   console.log(retVal);
    // }
    return retVal;
  }
  // console.log(totalSessionTime);
  const retVal = {};
  retVal.username = username;
  retVal.sessionCount = 0;
  retVal.sessionLength = 0;
  retVal.shortestSession = 0;
  retVal.longestSession = 0;
  retVal.totalSessionTime = 0;
  retVal.averageSession = 0;
  return retVal;
};

const studentSessionInformationBetween = (startStr, endStr, progressp = true, consolep) => {
  const usernames = getActiveStudentsBetween(startStr, endStr);
  const results = [];
  _.forEach(usernames, (username, index) => {
    results.push(sessionInformation(username, startStr, endStr, consolep));
    if (index % 10 === 0 && progressp) {
      console.log(`working ${((index / usernames.length) * 100).toFixed(2)} %`);
    }
  });
  return results;
};

const studentSessionInformationToCSV = (data) => {
  let resultStr = 'Student, Session Count, Session Lengths, Shortest, Longest, Total Time, Average\n';
  _.forEach(data, (d) => {
    if (d.sessionCount > 0) {
      resultStr = `${resultStr}${d.username},${d.sessionCount},${d.sessionLength.join(' ')},${d.shortestSession},${d.longestSession},${d.totalSessionTime},${d.averageSession}\n`;
    } else {
      resultStr = `${resultStr}${d.username},0,0,0,0,0,0\n`;
    }
  });
  return resultStr;
};

const sessionInformationBetween = (startStr, endStr, consolep) => {
  const usernames = getActiveStudentsBetween(startStr, endStr);
  // console.log(usernames);
  let sessions = 0;
  let sessionTime = 0;
  let lastTimeStamp;
  let sessionStart;
  let sessionEnd;
  let minSessions = 1000;
  let maxSessions = 0;
  _.forEach(usernames, (username, index) => {
    const sessionInfo = sessionInformation(username, startStr, endStr, consolep);
    sessions += sessionInfo.sessionCount;
    sessionTime += sessionInfo.totalSessionTime;
    if (minSessions > sessionInfo.sessionCount) {
      minSessions = sessionInfo.sessionCount;
    }
    if (maxSessions < sessionInfo.sessionCount) {
      maxSessions = sessionInfo.sessionCount;
    }
    if (index % 10 === 0 && consolep) {
      console.log('working %o %', ((index / usernames.length) * 100).toFixed(2));
    }
  });
  const retVal = {};
  retVal.sessionCount = sessions;
  retVal.minSessions = minSessions;
  retVal.maxSessions = maxSessions;
  retVal.totalSessionTime = sessionTime;
  retVal.averageSessionTime = sessionTime / sessions;
  return retVal;
};

const pageViewsBetween = (startStr, endStr) => {
  const pageViews = getInteractionsByTypeBetween('pageView', startStr, endStr);
  const pageViewsByPage = _.groupBy(pageViews, (p) => p.typeData[0]);
  return _.mapValues(pageViewsByPage, (value) => value.length);
};

const coursePageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('courses'));
};

const opportunityPageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('opportunities'));
};

const degreePlanPageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('degree'));
};

const explorerPageViewsBetween = (startStr, endStr) => {
  const views = pageViewsBetween(startStr, endStr);
  return _.pickBy(views, (value, key) => key.includes('explorer'));
};

const initDataDump = () => {
  const { argv } = process;
  // console.log(argv);
  if (argv.length < 3) {
    console.error('Usage: node analyze-data-dump.js <fileName>');
  } else {
    const filename = argv[2];
    const data = fs.readFileSync(filename);
    radgradDump = JSON.parse(data.toString());
  }
};

const opportunityICE = () => {
  const opportunities = getCollectionData('OpportunityCollection');
  const info = _.map(opportunities, (o) => {
    if (o.retired) {
      return `${o.name}: [${o.ice.i}, ${o.ice.c}, ${o.ice.e}] Retired`;
    }
    return `${o.name}: [${o.ice.i}, ${o.ice.c}, ${o.ice.e}]`;
  });
  return info;
};

const analyzeData = () => {
  initDataDump();
  // console.log('# Registered students: %o', getRegisteredStudentUsernames().length);
  // console.log('# Active students Spring 20: %o', getActiveStudentsBetween('2020-01-01', '2020-05-31').length);
  // const names = getActiveStudentNamesBetween('2020-01-01', '2020-05-31');
  // console.log(names.join(', '));
  // loginAnalysis('2020-01-01', '2020-05-31');
  // console.log(sessionInformationBetween('2020-01-01', '2020-05-31', false));
  // console.log(studentSessionInformationBetween('2019-01-01', '2019-05-31', false, false));
  // console.log(studentSessionInformationToCSV(studentSessionInformationBetween('2019-01-01', '2019-05-31', false)));
  // console.log(pageViewsBetween('2019-01-01', '2019-05-31'));
  // const oICE = opportunityICE();
  // console.log(oICE.slice(oICE.length - 10, oICE.length));
  // console.log(sessionInformation('gcalica@hawaii.edu', '2020-01-01', '2020-05-31', true));
};

analyzeData();
<|endoftext|>


<commit_before>BR.Routing = L.Routing.extend({
    options: {
        position: 'topright',
        icons: {
            /* not implemented yet
            start: new L.Icon.Default({iconUrl: 'bower_components/leaflet-gpx/pin-icon-start.png'}),
            end: new L.Icon.Default(),
            normal: new L.Icon.Default()
            */
            draw: false,
            opacity: 1
        },
        snapping: null,
        zIndexOffset: -2000
    },

    onAdd: function (map) {
        this._segmentsCasing = new L.FeatureGroup().addTo(map);

        var container = L.Routing.prototype.onAdd.call(this, map);

        this._segments.on('layeradd', this._addSegmentCasing, this);
        this._segments.on('layerremove', this._removeSegmentCasing, this);

        this._waypoints.on('layeradd', this._setMarkerOpacity, this);

        // turn line mouse marker off while over waypoint marker
        this.on('waypoint:mouseover', function(e) {
            // L.Routing.Edit._segmentOnMouseout without firing 'segment:mouseout' (enables draw)
            if (this._dragging) { return; }

            this._mouseMarker.setOpacity(0.0);
            this._map.off('mousemove', this._segmentOnMousemove, this);
            this._suspended = true;
        }, this._edit);

        this.on('waypoint:mouseout', function(e) {
            this._segmentOnMouseover(e);
            this._suspended = false;
        }, this._edit);

        this._edit._mouseMarker.setIcon(L.divIcon({
          className: 'line-mouse-marker'
          ,iconAnchor: [8, 8] // size/2 + border/2
          ,iconSize: [16, 16]
        }));

        this._draw.on('enabled', function() {
            // crosshair cursor
            L.DomUtil.addClass(map.getContainer(), 'routing-draw-enabled');

            // prevent cursor marker from consuming mouse events (invisible with draw:false)
            this._marker._icon.style.pointerEvents = 'none';

            // intercept listener: only re-show draw trailer after marker hover
            // when edit is not active (i.e. wasn't also supended)
            this._parent.off('waypoint:mouseout' , this._catchWaypointEvent, this);
            this.on('waypoint:mouseout' , function(e) {
                if (!this._parent._edit._suspended) {
                    this._catchWaypointEvent(e);
                }
            }, this);
        });
        this._draw.on('disabled', function() {
            L.DomUtil.removeClass(map.getContainer(), 'routing-draw-enabled');
        });

        // hide trailer over controls and outside map
        function show() {
            if (this._hidden && this._parent._waypoints._first) {
                this._show();
            }
        }
        function hide() {
            if (!this._hidden && this._parent._waypoints._first) {
                this._hide();
            }
        }
        this._draw.on('enabled', function() {
            this._map.on('mouseout', hide, this);
            this._map.on('mouseover', show, this);
            L.DomEvent.on(this._map._controlContainer, 'mouseout', show, this);
            L.DomEvent.on(this._map._controlContainer, 'mouseover', hide, this);
        });
        this._draw.on('disabled', function() {
            this._map.off('mouseout', hide, this);
            this._map.off('mouseover', show, this);
            L.DomEvent.off(this._map._controlContainer, 'mouseout', show, this);
            L.DomEvent.off(this._map._controlContainer, 'mouseover', hide, this);
        });

        // enable drawing mode
        this.draw(true);

        return container;
    }

  ,_addSegmentCasing: function(e) {
    var casing = L.polyline(e.layer.getLatLngs(), this.options.styles.trackCasing);
    this._segmentsCasing.addLayer(casing);
    e.layer._casing = casing;
    this._segments.bringToFront();
  }

  ,_removeSegmentCasing: function(e) {
    this._segmentsCasing.removeLayer(e.layer._casing);
  }

  ,setOpacity: function(opacity) {
    // Due to the second Polyline layer for casing, the combined opacity is less
    // transparent than with a single layer and the slider is non-linear. The
    // inverted formula is used to get the same result as with a single layer.
    // SVG simple alpha compositing: Ca' = 1 - (1 - Ea) * (1 - Ca)
    // http://www.w3.org/TR/SVG11/masking.html#SimpleAlphaBlending
    var sourceOpacity = 1 - Math.sqrt(1 - opacity);

    this.options.styles.track.opacity = sourceOpacity;
    this.options.styles.trackCasing.opacity = sourceOpacity;
    this.options.icons.opacity = opacity;

    this._segments.setStyle({
        opacity: sourceOpacity
    });
    this._segmentsCasing.setStyle({
        opacity: sourceOpacity
    });
    this._waypoints.eachLayer(function(marker) {
        marker.setOpacity(opacity);
    });
  }

  ,_setMarkerOpacity: function(e) {
    e.layer.setOpacity(this.options.icons.opacity);
  }

  ,_removeMarkerEvents: function(marker) {
      marker.off('mouseover', this._fireWaypointEvent, this);
      marker.off('mouseout' , this._fireWaypointEvent, this);
      marker.off('dragstart', this._fireWaypointEvent, this);
      marker.off('dragend'  , this._fireWaypointEvent, this);
      marker.off('drag'     , this._fireWaypointEvent, this);
      marker.off('click'    , this._fireWaypointEvent, this);
  }

  ,clear: function() {
    var drawEnabled = this._draw._enabled;
    var current = this._waypoints._first;

    this.draw(false);

    if (current === null) { return; }
    this._removeMarkerEvents(current);
    while (current._routing.nextMarker) {
      var marker = current._routing.nextMarker;
      this._removeMarkerEvents(marker);
      current = marker;
    };

    this._waypoints._first = null;
    this._waypoints._last = null;
    this._waypoints.clearLayers();
    this._segments.clearLayers();

    if (drawEnabled) {
        this.draw(true);
    }
  }

  ,setWaypoints: function(latLngs, cb) {
    var i;
    var callbackCount = 0;
    var firstErr;
    var $this = this;

    var callback = function(err, data) {
      callbackCount++;
      firstErr = firstErr || err;
      if (callbackCount >= latLngs.length) {
        $this.fire('routing:setWaypointsEnd', { err: firstErr });
        if (cb) {
          cb(firstErr);
        }
      }
    };

    this.fire('routing:setWaypointsStart');
    for (i = 0; latLngs && i < latLngs.length; i++) {
      this.addWaypoint(latLngs[i], this._waypoints._last, null, callback);
    }
  }

  // patch to fix error when line is null or error line
  // (when called while still segments to calculate, e.g. permalink or fast drawing)
   ,toPolyline: function() {
    var latLngs = [];

    this._eachSegment(function(m1, m2, line) {
      // omit if null (still calculating) or error
      // NOTE: feature check specific to BRouter GeoJSON response, workaround to detect error line
      if (line && line.feature) {
        latLngs = latLngs.concat(line.getLatLngs());
      }
    });

    return L.polyline(latLngs);
  }

  ,_routeSegment: function(m1, m2, cb) {
    var loadingTrailer;

    // change segment color before request to indicate recalculation (mark old)
    if (m1 && m1._routing.nextLine !== null) {
        m1._routing.nextLine.options.color = 'dimgray';
        m1._routing.nextLine._updateStyle();
    }

    // animate dashed trailer as loading indicator
    if (m1 && m2) {
        loadingTrailer = new L.Polyline([m1.getLatLng(), m2.getLatLng()], {
            color: this.options.styles.track.color,
            opacity: this.options.styles.trailer.opacity,
            dashArray: [10, 10],
            className: 'loading-trailer'
        });
        loadingTrailer.addTo(this._map);
    }

    L.Routing.prototype._routeSegment.call(this, m1, m2, L.bind(function(err, data) {
        if (loadingTrailer) {
            this._map.removeLayer(loadingTrailer);
        }
        cb(err, data);
    }, this));
  }

  ,getSegments: function() {
    var segments = [];

    this._eachSegment(function(m1, m2, line) {
      // omit if null (still calculating) or error
      // NOTE: feature check specific to BRouter GeoJSON response, workaround to detect error line
      if (line && line.feature) {
        segments.push(line);
      }
    });

    return segments;
  }

  // add 'esc' to disable drawing
  ,_keyupListener: function (e) {
    if (e.keyCode === 27) {
        this._draw.disable();
    } else {
        L.Routing.prototype._keyupListener.call(this, e);
    }
  }
});
<commit_msg>fix adding new waypoint after deleting the last<commit_after>L.Routing.Draw.prototype._hideTrailer = function() {
  if (this._trailer.options.opacity !== 0.0) {
    this._trailer.setStyle({opacity: 0.0});
  }
};

BR.Routing = L.Routing.extend({
    options: {
        position: 'topright',
        icons: {
            /* not implemented yet
            start: new L.Icon.Default({iconUrl: 'bower_components/leaflet-gpx/pin-icon-start.png'}),
            end: new L.Icon.Default(),
            normal: new L.Icon.Default()
            */
            draw: false,
            opacity: 1
        },
        snapping: null,
        zIndexOffset: -2000
    },

    onAdd: function (map) {
        this._segmentsCasing = new L.FeatureGroup().addTo(map);

        var container = L.Routing.prototype.onAdd.call(this, map);

        this._segments.on('layeradd', this._addSegmentCasing, this);
        this._segments.on('layerremove', this._removeSegmentCasing, this);

        this._waypoints.on('layeradd', this._setMarkerOpacity, this);

        // turn line mouse marker off while over waypoint marker
        this.on('waypoint:mouseover', function(e) {
            // L.Routing.Edit._segmentOnMouseout without firing 'segment:mouseout' (enables draw)
            if (this._dragging) { return; }

            this._mouseMarker.setOpacity(0.0);
            this._map.off('mousemove', this._segmentOnMousemove, this);
            this._suspended = true;
        }, this._edit);

        this.on('waypoint:mouseout', function(e) {
            this._segmentOnMouseover(e);
            this._suspended = false;
        }, this._edit);

        this._edit._mouseMarker.setIcon(L.divIcon({
          className: 'line-mouse-marker'
          ,iconAnchor: [8, 8] // size/2 + border/2
          ,iconSize: [16, 16]
        }));

        this._draw.on('enabled', function() {
            // crosshair cursor
            L.DomUtil.addClass(map.getContainer(), 'routing-draw-enabled');

            // prevent cursor marker from consuming mouse events (invisible with draw:false)
            this._marker._icon.style.pointerEvents = 'none';

            // intercept listener: only re-show draw trailer after marker hover
            // when edit is not active (i.e. wasn't also supended)
            this._parent.off('waypoint:mouseout' , this._catchWaypointEvent, this);
            this.on('waypoint:mouseout' , function(e) {
                if (!this._parent._edit._suspended) {
                    this._catchWaypointEvent(e);
                }
            }, this);
        });
        this._draw.on('disabled', function() {
            L.DomUtil.removeClass(map.getContainer(), 'routing-draw-enabled');
        });

        // hide trailer over controls and outside map
        function show() {
            if (this._hidden && this._parent._waypoints._first) {
                this._show();
            }
        }
        function hide() {
            if (!this._hidden && this._parent._waypoints._first) {
                this._hide();
            }
        }
        this._draw.on('enabled', function() {
            this._map.on('mouseout', hide, this);
            this._map.on('mouseover', show, this);
            L.DomEvent.on(this._map._controlContainer, 'mouseout', show, this);
            L.DomEvent.on(this._map._controlContainer, 'mouseover', hide, this);
        });
        this._draw.on('disabled', function() {
            this._map.off('mouseout', hide, this);
            this._map.off('mouseover', show, this);
            L.DomEvent.off(this._map._controlContainer, 'mouseout', show, this);
            L.DomEvent.off(this._map._controlContainer, 'mouseover', hide, this);
        });

        // Call show after deleting last waypoint, but hide trailer.
        // Gets hidden in _catchWaypointEvent on waypoint mouseover, but
        // mouseout to show again never fires when deleted. Click handler
        // _onMouseClick aborts when hidden, so no waypoint can be added
        // although enabled.
        this.on('waypoint:click', function() {
            if (this._hidden && !this._parent._waypoints._first) {
                this._show();
                this._hideTrailer();
            }
        }, this._draw);

        // enable drawing mode
        this.draw(true);

        return container;
    }

  ,_addSegmentCasing: function(e) {
    var casing = L.polyline(e.layer.getLatLngs(), this.options.styles.trackCasing);
    this._segmentsCasing.addLayer(casing);
    e.layer._casing = casing;
    this._segments.bringToFront();
  }

  ,_removeSegmentCasing: function(e) {
    this._segmentsCasing.removeLayer(e.layer._casing);
  }

  ,setOpacity: function(opacity) {
    // Due to the second Polyline layer for casing, the combined opacity is less
    // transparent than with a single layer and the slider is non-linear. The
    // inverted formula is used to get the same result as with a single layer.
    // SVG simple alpha compositing: Ca' = 1 - (1 - Ea) * (1 - Ca)
    // http://www.w3.org/TR/SVG11/masking.html#SimpleAlphaBlending
    var sourceOpacity = 1 - Math.sqrt(1 - opacity);

    this.options.styles.track.opacity = sourceOpacity;
    this.options.styles.trackCasing.opacity = sourceOpacity;
    this.options.icons.opacity = opacity;

    this._segments.setStyle({
        opacity: sourceOpacity
    });
    this._segmentsCasing.setStyle({
        opacity: sourceOpacity
    });
    this._waypoints.eachLayer(function(marker) {
        marker.setOpacity(opacity);
    });
  }

  ,_setMarkerOpacity: function(e) {
    e.layer.setOpacity(this.options.icons.opacity);
  }

  ,_removeMarkerEvents: function(marker) {
      marker.off('mouseover', this._fireWaypointEvent, this);
      marker.off('mouseout' , this._fireWaypointEvent, this);
      marker.off('dragstart', this._fireWaypointEvent, this);
      marker.off('dragend'  , this._fireWaypointEvent, this);
      marker.off('drag'     , this._fireWaypointEvent, this);
      marker.off('click'    , this._fireWaypointEvent, this);
  }

  ,clear: function() {
    var drawEnabled = this._draw._enabled;
    var current = this._waypoints._first;

    this.draw(false);

    if (current === null) { return; }
    this._removeMarkerEvents(current);
    while (current._routing.nextMarker) {
      var marker = current._routing.nextMarker;
      this._removeMarkerEvents(marker);
      current = marker;
    };

    this._waypoints._first = null;
    this._waypoints._last = null;
    this._waypoints.clearLayers();
    this._segments.clearLayers();

    if (drawEnabled) {
        this.draw(true);
    }
  }

  ,setWaypoints: function(latLngs, cb) {
    var i;
    var callbackCount = 0;
    var firstErr;
    var $this = this;

    var callback = function(err, data) {
      callbackCount++;
      firstErr = firstErr || err;
      if (callbackCount >= latLngs.length) {
        $this.fire('routing:setWaypointsEnd', { err: firstErr });
        if (cb) {
          cb(firstErr);
        }
      }
    };

    this.fire('routing:setWaypointsStart');
    for (i = 0; latLngs && i < latLngs.length; i++) {
      this.addWaypoint(latLngs[i], this._waypoints._last, null, callback);
    }
  }

  // patch to fix error when line is null or error line
  // (when called while still segments to calculate, e.g. permalink or fast drawing)
   ,toPolyline: function() {
    var latLngs = [];

    this._eachSegment(function(m1, m2, line) {
      // omit if null (still calculating) or error
      // NOTE: feature check specific to BRouter GeoJSON response, workaround to detect error line
      if (line && line.feature) {
        latLngs = latLngs.concat(line.getLatLngs());
      }
    });

    return L.polyline(latLngs);
  }

  ,_routeSegment: function(m1, m2, cb) {
    var loadingTrailer;

    // change segment color before request to indicate recalculation (mark old)
    if (m1 && m1._routing.nextLine !== null) {
        m1._routing.nextLine.options.color = 'dimgray';
        m1._routing.nextLine._updateStyle();
    }

    // animate dashed trailer as loading indicator
    if (m1 && m2) {
        loadingTrailer = new L.Polyline([m1.getLatLng(), m2.getLatLng()], {
            color: this.options.styles.track.color,
            opacity: this.options.styles.trailer.opacity,
            dashArray: [10, 10],
            className: 'loading-trailer'
        });
        loadingTrailer.addTo(this._map);
    }

    L.Routing.prototype._routeSegment.call(this, m1, m2, L.bind(function(err, data) {
        if (loadingTrailer) {
            this._map.removeLayer(loadingTrailer);
        }
        cb(err, data);
    }, this));
  }

  ,getSegments: function() {
    var segments = [];

    this._eachSegment(function(m1, m2, line) {
      // omit if null (still calculating) or error
      // NOTE: feature check specific to BRouter GeoJSON response, workaround to detect error line
      if (line && line.feature) {
        segments.push(line);
      }
    });

    return segments;
  }

  // add 'esc' to disable drawing
  ,_keyupListener: function (e) {
    if (e.keyCode === 27) {
        this._draw.disable();
    } else {
        L.Routing.prototype._keyupListener.call(this, e);
    }
  }
});
<|endoftext|>


<commit_before>const path = require( 'path' );
const SvgStore = require( 'webpack-svgstore-plugin' );
const ExtractTextPlugin = require( "extract-text-webpack-plugin" );
const HtmlWebpackPlugin = require( 'html-webpack-plugin' );
const FlowBabelWebpackPlugin = require( 'flow-babel-webpack-plugin' );

const extractCss = new ExtractTextPlugin( {filename: 'styles.css'} );
const extractCssLibs = new ExtractTextPlugin( {filename: 'libs.css'} );

module.exports = {
  devtool: 'source-map',
  entry: [
    './source/randux'
  ],
  output: {
    path: path.join( __dirname, 'dev' ),
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  devServer: {
    historyApiFallback: true
  },
  plugins: [
    new SvgStore( {
      prefix: 'icon-'
    } ),
    extractCssLibs,
    extractCss,
    // create a new index.html in the root folder based on the template
    // so that bundles etc can be injected, and hashed ones can be injected in production
    new HtmlWebpackPlugin( {
      template: 'index.template.html',
      filename: '../index.html'
    } ),
    new FlowBabelWebpackPlugin()
  ],
  module: {

    rules: [
      // js
      {
        test: /\.js$/,
        use: [ 'babel-loader?cacheDirectory', 'eslint-loader' ],
        include: path.join( __dirname, 'source' ),
        exclude: /node_modules/
      },
      // add normalize.css
      {
        test: /\.css$/,
        include: path.join( __dirname, 'node_modules/normalize.css' ),
        loader: extractCssLibs.extract({
          fallbackLoader: "style-loader",
          loader: 'css-loader'
        })
      },
      // add animate.css
      {
        test: /\.css$/,
        include: path.join( __dirname, 'node_modules/animate.css' ),
        loader: extractCssLibs.extract({
          fallbackLoader: "style-loader",
          loader: 'css-loader'
        })
      },
      // images
      {
        test: /\.(png|jpg|svg)$/,
        loader: 'file-loader?name=[path][name].[ext]'
      },
      // css
      // assume one styles.scss that imports all it needs from the components
      // so each new component needs to be added to styles.scss
      // a separate css file is created
      {
        test: /\.scss$/,
        include: path.join( __dirname, 'source/styles/styles.scss' ),
        loader: extractCss.extract({
          fallbackLoader: "style-loader",
          loader: ['css-loader', 'postcss-loader', 'sass-loader']
        })
      }
    ]
  }
};
<commit_msg>format<commit_after>const path = require( 'path' );
const SvgStore = require( 'webpack-svgstore-plugin' );
const ExtractTextPlugin = require( "extract-text-webpack-plugin" );
const HtmlWebpackPlugin = require( 'html-webpack-plugin' );
const FlowBabelWebpackPlugin = require( 'flow-babel-webpack-plugin' );

const extractCss = new ExtractTextPlugin( {
  filename: 'styles.css'
} );

const extractCssLibs = new ExtractTextPlugin( {
  filename: 'libs.css'
} );

module.exports = {
  devtool: 'source-map',
  entry: [
    './source/randux'
  ],
  output: {
    path: path.join( __dirname, 'dev' ),
    filename: 'bundle.js',
    publicPath: '/static/'
  },
  devServer: {
    historyApiFallback: true
  },
  plugins: [
    new SvgStore( {
      prefix: 'icon-'
    } ),
    extractCssLibs,
    extractCss,
    // create a new index.html in the root folder based on the template
    // so that bundles etc can be injected, and hashed ones can be injected in production
    new HtmlWebpackPlugin( {
      template: 'index.template.html',
      filename: '../index.html'
    } ),
    new FlowBabelWebpackPlugin()
  ],
  module: {

    rules: [
      // js
      {
        test: /\.js$/,
        use: [ 'babel-loader?cacheDirectory', 'eslint-loader' ],
        include: path.join( __dirname, 'source' ),
        exclude: /node_modules/
      },
      // add normalize.css
      {
        test: /\.css$/,
        include: path.join( __dirname, 'node_modules/normalize.css' ),
        loader: extractCssLibs.extract( {
          fallbackLoader: "style-loader",
          loader: 'css-loader'
        } )
      },
      // add animate.css
      {
        test: /\.css$/,
        include: path.join( __dirname, 'node_modules/animate.css' ),
        loader: extractCssLibs.extract( {
          fallbackLoader: "style-loader",
          loader: 'css-loader'
        } )
      },
      // images
      {
        test: /\.(png|jpg|svg)$/,
        loader: 'file-loader?name=[path][name].[ext]'
      },
      // css
      // assume one styles.scss that imports all it needs from the components
      // so each new component needs to be added to styles.scss
      // a separate css file is created
      {
        test: /\.scss$/,
        include: path.join( __dirname, 'source/styles/styles.scss' ),
        loader: extractCss.extract( {
          fallbackLoader: "style-loader",
          loader: [ 'css-loader', 'postcss-loader', 'sass-loader' ]
        } )
      }
    ]
  }
};
<|endoftext|>


<commit_before>/*
MagJS v0.25.5
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(index) {
    modules.splice(index, 1)
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined' ? true : false
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key === mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }

      // reinitialize the controller ?
      return modules[index]
    }
    modules[index] = [0, 0, 0, 0, 0]
    mod.setProps(index, mag.utils.copy(props))
    var controller = function(context) {
        return (module.controller || function() {}).call(context, mod.getProps(index)) || context
      },
      view = function(index, state, ele) {
        module.view && module.view.call(module, state, mod.getProps(index), ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] !== '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

        if(!a.length) return MAGNUM;

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              i = {
                _value: item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if(item && !item.hasChildNodes()){
          tmp.push(MAGNUM)
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a[0];
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.doc.getElementById(mod.getId(index));

      //Get parent correct parent not just root!?

      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (typeof res != 'undefined' && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (typeof res != 'undefined') {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {
    var controller;

    mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    controller = new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

    return controller;
  }

  mod.iscached = function(key) {
    var data = mag.utils.merge(mag.utils.copy(mod.getProps(key)), mod.getState(key));
    if (mod.cache[key] && mod.cache[key] === JSON.stringify(data)) {
      return true
    }
    mod.cache[key] = JSON.stringify(data);
  }

  mod.clear = function(key) {
    if (key > -1 && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    mod.getView(index)(mod.getState(index), node)
  }

  mag.mod = mod;

}(mag));
<commit_msg>Update module.js<commit_after>/*
MagJS v0.25.5
http://github.com/magnumjs/mag.js
(c) Michael Glazer
License: MIT
*/
(function(mag) {

  'use strict';

  var modules = [],
    MAGNUM = mag.MAGNUM,
    controllers = []

  var mod = {
    innerMods: [],
    cache: []
  }

  mod.getState = function(index) {
    return modules[index][1]
  }
  mod.setState = function(index, state) {
    modules[index][1] = state
  }
  mod.getView = function(index) {
    return modules[index][0]
  }
  mod.getProps = function(index) {
    return modules[index][2]
  }
  mod.setProps = function(index, props) {
    return modules[index][2] = props
  }
  mod.remove = function(index) {
    modules.splice(index, 1)
  }
  mod.getId = function(index) {
    return modules[index] && modules[index][3]
  }
  mod.exists = function(index) {
    return typeof modules[index] != 'undefined' ? true : false
  }
  mod.setFrameId = function(index, fid) {
    modules[index][4] = fid;
  }
  mod.getFrameId = function(index) {
    return modules[index][4];
  }
  mod.submodule = function(id, index, module, props) {
    if (modules[index]) {
      // new call to existing
      // update props, merge with existing if same key
      if (props.key && props.key === mod.getProps(index).key) {
        mod.setProps(index, mag.utils.copy(mag.utils.merge(mod.getProps(index), props)));
      } else {
        mod.setProps(index, props);
      }

      // reinitialize the controller ?
      return modules[index]
    }
    modules[index] = [0, 0, 0, 0, 0]
    mod.setProps(index, props);
    var controller = function(context) {
        return (module.controller || function() {}).call(context, mod.getProps(index)) || context
      },
      view = function(index, state, ele) {
        module.view && module.view.call(module, state, mod.getProps(index), ele)
      }.bind({}, index),
      output = {
        controller: controller,
        view: view
      }
    modules[index][0] = output.view;
    modules[index][3] = id;
    modules[index][1] = getController(output.controller, index, id);
    // register the module
    return modules[index]
  }

  var timers = [];

  function findMissing(change, element) {
    var prop = change.name;
    if (typeof change.object[change.name] == 'undefined' && prop[0] !== '_') {
      // prop might be hierarchical?
      // getparent Object property chain?

      // get value of property from DOM
      var a = mag.fill.find(element, prop),
        greedy = prop[0] === '$',
        // can be an array, object or string
        // for each
        tmp = [];

        if(!a.length) return MAGNUM;

      a.forEach(function(item) {
        var i;
        if (item && item.type && !~['submit', 'button'].indexOf(item.type)) {
          if (item.value && item.value.length > 0) {

            if (item.type == 'checkbox' || item.type == 'radio') {

              if (item.checked) {
                i = {};
                i._checked = true;
              }
              if (!greedy) {
                //if grouped name item
                var items = element.querySelectorAll('[name=' + item.name + ']');
                if (items.length) {
                  for (var sitem of items) {
                    if (sitem.checked) {
                      if (i) {
                        i._value = sitem.value;
                      } else {
                        i = {
                          _value: sitem.value
                        };
                      }
                    }
                  }
                }
              } else {
                if (i) {
                  i._value = item.value;
                } else {
                  i = {
                    _value: item.value
                  };
                }
              }
            } else {
              i = {
                _value: item.value
              };
            }
          }
        } else if ((typeof change.oldValue == 'undefined' && typeof change.object[change.name] == 'undefined') && item && !~['submit', 'button'].indexOf(item.type) && item.childNodes.length == 1 && item.childNodes[0].textContent.trim()) {
          i = {
            _text: item.childNodes[0].textContent.trim()
          };
        }
        if (i) tmp.push(i);
        else if(item && !item.hasChildNodes()){
          tmp.push(MAGNUM)
        }

      });

      if (tmp.length === 0) return;
      else if (greedy) return tmp;
      else {
        return tmp[0] && typeof tmp[0]._value != 'undefined' ? tmp[0]._value : tmp[0] && tmp[0]._text ? tmp[0]._text : tmp[0]
      }
    }
  }

  var findParentNodeWithPath = function(node, path) {
    var snode = node;
    var parts = path.split('/');

    for (var prop of parts) {
      if (prop) {
        var a = mag.fill.find(snode, prop);
        if (a.length) {
          snode = a[0];
        }
      }
    }
    return snode
  };

  var handler = function(type, index, change) {

    if (change.type == 'get' && type != 'props' && !~mag.fill.ignorekeys.indexOf(change.name.toString()) && typeof change.oldValue == 'undefined') {

      var rootNode = mag.doc.getElementById(mod.getId(index));

      //Get parent correct parent not just root!?

      if (change.path && change.path[0] == '/') var fnode = findParentNodeWithPath(rootNode, change.path)

      var res = findMissing(change, fnode ? fnode : rootNode);

      if (typeof res != 'undefined' && typeof res == 'object' && change.object) {
        mag.utils.merge(res, change.object[change.name]);
      }
      if (typeof res != 'undefined') {
        mod.cached[index] = 0;
        return res;
      }
    } else if (change.type == 'set' && type != 'props' && change.object[change.name] && change.object[change.name].draw && typeof change.object[change.name].draw == 'function') {

      // setting a state prop to a module

      // call unloader for module
      mod.innerMods[mod.getId(index)] = [change.name, change.object[change.name]];
    }

    // call setup handler
    var fun = mod.getFrameId(index);
    if (typeof fun == 'function' && change.type == 'set') {
      //debounce
      cancelAnimationFrame(timers[index]);
      timers[index] = requestAnimationFrame(fun);
    }

  };

  function getController(ctrl, index, id) {
    var controller;

    mod.setProps(index, mag.proxy(mod.getProps(index), handler.bind({}, 'props', index), 'prop'));

    controller = new ctrl(mag.proxy({}, handler.bind({}, 'state', index)));

    return controller;
  }

  mod.iscached = function(key) {
    var data = mag.utils.merge(mag.utils.copy(mod.getProps(key)), mod.getState(key));
    if (mod.cache[key] && mod.cache[key] === JSON.stringify(data)) {
      return true
    }
    mod.cache[key] = JSON.stringify(data);
  }

  mod.clear = function(key) {
    if (key > -1 && mod.cache[key]) {
      mod.cache.splice(key, 1);
    }
  }

  mod.cached = [];
  mod.callView = function(node, index) {
    //TODO: ASYNC, promises .. ?
    mod.runningViewInstance = index;
    if (!mod.cached[index]) {
      mag.props.attachToArgs(index, mod.getState(index), node)
      mod.cached[index] = 1
    }
    mod.getView(index)(mod.getState(index), node)
  }

  mag.mod = mod;

}(mag));
<|endoftext|>