<commit_before>/* global THREE, AFRAME, NAF */

/**
 * Represent any 6dof controller as a hand with live tracked animated gestures and normalized events.
 *
 * Updated and modified adaption of https://github.com/aframevr/aframe/blob/master/src/components/hand-controls.js
 * Designed for use with NAF, but can also be used without NAF.
 *
 * Auto-detects appropriate controller.
 * Handles common events coming from the detected vendor-specific controls.
 * Loads hand model with gestures that are applied based on the button pressed.
 * Translate button events to semantic hand-related event names:
 * (fist, thumbUp, point, hold, open, pistol) + (start, end)
 * note that the events were incomplete/broken in prior implementation, and therefore a breaking change
 */

AFRAME.registerComponent('networked-hand-controls', {
  schema: {
    color: { default: 'white', type: 'color' },
    hand: { type: "string", default: 'left' },
    handModelStyle: { type: "string", default: 'highPoly', oneOf: ['lowPoly', 'highPoly', 'toon'] },

    handModelURL: { type: "string", default: '' },
    // ^for specifying a custom model URL; only allowed at init, not via update
    // (must correspond to existing models and have matching animations to work properly)

    gesture: { type: "string", default: 'open' },
    visible: { type: "bool", default: false },
  },

  init() {
    this.Z = Math.random();
    this.Y[this.Z] = { contact: {}, reverse: false, isContact: false };

    this.el.object3D.visible = this.data.visible;

    this.checkLocalHand();
    if (this.local) {
      Object.keys(this.buttonEventMap).forEach(evtName => {
        this.eventFunctionMap[this.data.hand][evtName] = this.handleButton.bind(this, ...this.buttonEventMap[evtName])
      })

      this.el.addEventListener('controllerconnected', () => {
        this.el.setAttribute('networked-hand-controls', 'visible', true);
      });
      this.el.addEventListener('controllerdisconnected', () => {
        this.el.setAttribute('networked-hand-controls', 'visible', false);
      });
    }
    else {
      this.el.classList.add('naf-remote-hand');
    }

    this.loader = new THREE.GLTFLoader();
    this.loader.setCrossOrigin('anonymous');

    const handmodelUrl = this.MODEL_BASE + this.MODEL_URLS[this.data.handModelStyle + this.data.hand.charAt(0).toUpperCase() + this.data.hand.slice(1)];

    this.loader.load(this.data.handModelURL || handmodelUrl,
                      gltf => {
      const newMesh = gltf.scene.children[0];
      const handModelOrientation = this.data.hand === 'left' ? Math.PI / 2 : -Math.PI / 2;
      newMesh.mixer = new THREE.AnimationMixer(newMesh);

      this.clips = gltf.animations;
      this.clips.forEach((clip, clipIndex) => {
        this.clipNameToClip[clip.name] = clip;
      })

      this.el.setObject3D('mesh', newMesh);

      const handMaterial = newMesh.children[1].material;
      handMaterial.color = new THREE.Color(this.data.handColor);
      newMesh.position.set(0, 0, 0);
      newMesh.rotation.set(0, 0, handModelOrientation);

      if (this.local) {
        const controlConfiguration = {
          hand: this.data.hand,
          model: false,
        }
        this.el.setAttribute('magicleap-controls', controlConfiguration);
        this.el.setAttribute('vive-controls', controlConfiguration);
        this.el.setAttribute('oculus-touch-controls', controlConfiguration);
        this.el.setAttribute('windows-motion-controls', controlConfiguration);
        this.el.setAttribute('hp-mixed-reality-controls', controlConfiguration);
        this.isViveController();
      }

    });
  },

  play() {
    if (this.local) {
        this.addEventListeners();
    }
  },

  pause() {
    if (this.local) {
      this.removeEventListeners();
    }
  },

  tick(time, delta) {
    if (this.getMesh()?.mixer) this.getMesh().mixer.update(delta / 1000);
  },

  update(oldData) {
    if (oldData.visible != this.data.visible) {
      this.el.object3D.visible = this.data.visible;
    }

    if (!this.local && (this.data.gesture != oldData.gesture)) {
      this.handleGesture(this.data.gesture, oldData.gesture);
    }
  },

  remove() {
    this.el.removeObject3D('mesh');
  },

  // cache system, protection from A-Frame optimization behavior while reducing garbage collection overhead
  Z: 0,
  Y: {},

  getMesh() {
    return this.el.getObject3D(this.str.mesh)
  },

  // local hands are controlled by one's controllers
  // non-local hands are controlled by updates via NAF
  local: false,
  checkLocalHand() {
    // this.local = this.el.components.networked.data.owner == NAF.clientId;
    // ^while this would be more ideal, NAF doesn't populate the owner until later
    // and we need to know this at init(), and we can't supply schema options
    // when instantiating a template instance--so, we require adding a class for now.
    if (this.el.classList.contains("local-naf-hand")) this.local = true;
  },

  clipNameToClip: {},
  eventFunctionMap: {left:{}, right: {}},

  buttonEventMap: {
    gripdown: ['grip', 'down'],
    gripup: ['grip', 'up'],
    trackpaddown: ['trackpad', 'down'],
    trackpadup: ['trackpad', 'up'],
    trackpadtouchstart: ['trackpad', 'touchstart'],
    trackpadtouchend: ['trackpad', 'touchend'],
    triggerdown: ['trigger', 'down'],
    triggerup: ['trigger', 'up'],
    triggertouchstart: ['trigger', 'touchstart'],
    triggertouchend: ['trigger', 'touchend'],
    griptouchstart: ['grip', 'touchstart'],
    griptouchend: ['grip', 'touchend'],
    abuttontouchstart: ['AorX', 'touchstart'],
    abuttontouchend: ['AorX', 'touchend'],
    bbuttontouchstart: ['BorY', 'touchstart'],
    bbuttontouchend: ['BorY', 'touchend'],
    xbuttontouchstart: ['AorX', 'touchstart'],
    xbuttontouchend: ['AorX', 'touchend'],
    ybuttontouchstart: ['BorY', 'touchstart'],
    ybuttontouchend: ['BorY', 'touchend'],
    surfacetouchstart: ['surface', 'touchstart'],
    surfacetouchend: ['surface', 'touchend'],
    // these seem to not be used
    // thumbstickdown: ['thumbstick', 'down'],
    // thumbstickup: ['thumbstick', 'up'],
  },

  addEventListeners() {
    Object.keys(this.buttonEventMap).forEach(evtName => {
      this.el.addEventListener(evtName, this.eventFunctionMap[this.data.hand][evtName])
    })
  },

  removeEventListeners() {
    Object.keys(this.buttonEventMap).forEach(evtName => {
      this.el.removeEventListener(evtName, this.eventFunctionMap[this.data.hand][evtName])
    })
  },

  // to minimize garbage collection, prevents generating huge numbers of generated strings
  str: {
    nafHandControls: "networked-hand-controls",
    gesture: "gesture",
    mesh: "mesh",
    down: 'down',
    touchstart: 'touchstart',
    fist: "fist",
    point: "point",
    thumbUp: "thumbUp",
    pistol: "pistol",
    hold: "hold",
    open: "open",
  },

  handleButton(button, evt) {
    this.Y[this.Z].isContact = evt === this.str.down || evt === this.str.touchstart;

    if (this.Y[this.Z].isContact === this.Y[this.Z].contact[button]) {
      return;
    }
    this.Y[this.Z].contact[button] = this.Y[this.Z].isContact;

    this.Y[this.Z].lastGesture = this.data.gesture;
    this.determineGesture();
    if (this.Y[this.Z].gesture === this.Y[this.Z].lastGesture) { return; }

    // set new gesture into schema to propagate via NAF
    this.el.setAttribute(this.str.nafHandControls, this.str.gesture, this.Y[this.Z].gesture);

    this.handleGesture();
  },

  handleGesture() {
    if (this.data.gesture === this.Y[this.Z].lastGesture) { return; }

    this.playAnimation();

    this.emitGestureEvents();
  },

  // note: using preserved references to reduce garbage collection, as this function can fire thousands of times.
  determineGesture() {
    this.Y[this.Z].gripIsActive = this.Y[this.Z].contact.grip;
    this.Y[this.Z].triggerIsActive = this.Y[this.Z].contact.trigger
    this.Y[this.Z].thumbIsEngaged =
          this.Y[this.Z].contact.trackpad || this.Y[this.Z].contact.surface
          this.Y[this.Z].contact.AorX || this.Y[this.Z].contact.BorY;

    if (!this.isVive) {
      this.Y[this.Z].gesture =
          this.Y[this.Z].gripIsActive &&
          this.Y[this.Z].thumbIsEngaged &&
          this.Y[this.Z].triggerIsActive ?
                          this.str.fist :
          this.Y[this.Z].gripIsActive &&
          this.Y[this.Z].thumbIsEngaged &&
        !this.Y[this.Z].triggerIsActive ?
                          this.str.point :
          this.Y[this.Z].gripIsActive &&
        !this.Y[this.Z].thumbIsEngaged &&
          this.Y[this.Z].triggerIsActive ?
                          this.str.thumbUp :
          this.Y[this.Z].gripIsActive &&
        !this.Y[this.Z].thumbIsEngaged &&
        !this.Y[this.Z].triggerIsActive ?
                          this.str.pistol :
          this.Y[this.Z].triggerIsActive ?
                          this.str.hold :
                          this.str.open ;
    }
    else { // Vive handling was supposedly left incomplete
      this.Y[this.Z].gesture =
        this.Y[this.Z].gripIsActive ||
        this.Y[this.Z].triggerIsActive ?
                          this.str.fist :
        this.Y[this.Z].contact.trackpad ||
        this.Y[this.Z].contact.trackpad ?
                          this.str.point :
                          this.str.open ;
    }
  },

  emitGestureEvents() {
    this.el.emit(
      this.eventNames[this.Y[this.Z].lastGesture]?.inactive
    );
    this.el.emit(
      this.eventNames[this.data.gesture]?.active
    );
  },

  eventNames: {
    fist: {active: "fistStart", inactive: "fistEnd"},
    thumbUp: {active: "thumbUpStart", inactive: "thumbUpEnd"},
    point: {active: "pointStart", inactive: "pointEnd"},
    hold: {active: "holdStart", inactive: "holdEnd"},
    open: {active: "openStart", inactive: "openEnd"},
    pistol: {active: "pistolStart", inactive: "pistolEnd"},
  },

  playAnimation() {
    // this function seems like it could be improved
    // implementation is from the original hand-controls component

    if (!this.getMesh()) { return; }

    // Stop all current animations.
    this.getMesh().mixer.stopAllAction();

    // Grab clip action.
    this.Y[this.Z].clip = this.clipNameToClip[this.ANIMATIONS[this.data.gesture]];
    this.Y[this.Z].toAction = this.getMesh().mixer.clipAction(this.Y[this.Z].clip);
    this.Y[this.Z].toAction.clampWhenFinished = true;
    this.Y[this.Z].toAction.loop = THREE.LoopRepeat;
    this.Y[this.Z].toAction.repetitions = 0;
    this.Y[this.Z].toAction.timeScale = this.Y[this.Z].reverse ? -1 : 1;
    this.Y[this.Z].toAction.time = this.Y[this.Z].reverse ? this.Y[this.Z].clip.duration : 0;
    this.Y[this.Z].toAction.weight = 1;

    if (!this.Y[this.Z].lastGesture || this.data.gesture === this.Y[this.Z].lastGesture) {
      this.getMesh().mixer.stopAllAction();
      this.Y[this.Z].toAction.play();
      return;
    }

    // Animate or crossfade from gesture to gesture.
    this.Y[this.Z].clip = this.clipNameToClip[this.ANIMATIONS[this.Y[this.Z].lastGesture]];
    this.Y[this.Z].fromAction = this.getMesh().mixer.clipAction(this.Y[this.Z].clip);
    this.Y[this.Z].fromAction.weight = 0.15;
    this.Y[this.Z].fromAction.play();
    this.Y[this.Z].toAction.play();
    this.Y[this.Z].fromAction.crossFadeTo(this.Y[this.Z].toAction, 0.15, true);
  },

  // see https://github.com/aframevr/assets.
  MODEL_BASE: 'https://cdn.aframe.io/controllers/hands/',
  MODEL_URLS: {
    toonLeft: 'leftHand.glb',
    toonRight: 'rightHand.glb',
    lowPolyLeft: 'leftHandLow.glb',
    lowPolyRight: 'rightHandLow.glb',
    highPolyLeft: 'leftHandHigh.glb',
    highPolyRight: 'rightHandHigh.glb',
  },

  // map from internal state names to the names of the clips as specified within the models
  ANIMATIONS: {
    open: 'Open',
    point: 'Point',
    pistol: 'Point + Thumb',
    fist: 'Fist',
    hold: 'Hold',
    thumbUp: 'Thumb Up',
  },

  isVive: false,
  isViveController() {
    let controller = this.el.components['tracked-controls'] && this.el.components['tracked-controls'].controller;
    this.isVive = controller && (controller.id && controller.id.indexOf('OpenVR ') === 0 ||
      (controller.profiles &&
        controller.profiles[0] &&
        controller.profiles[0] === 'htc-vive'));
  },
});
  <commit_msg>semantic (non-camel-case) event names<commit_after>/* global THREE, AFRAME, NAF */

/**
 * Represent any 6dof controller as a hand with live tracked animated gestures and normalized events.
 *
 * Updated and modified adaption of https://github.com/aframevr/aframe/blob/master/src/components/hand-controls.js
 * Designed for use with NAF, but can also be used without NAF.
 *
 * Auto-detects appropriate controller.
 * Handles common events coming from the detected vendor-specific controls.
 * Loads hand model with gestures that are applied based on the button pressed.
 * Translate button events to semantic hand-related event names:
 * (fist, thumbUp, point, hold, open, pistol) + (start, end)
 * note that the events were incomplete/broken in prior implementation, and therefore a breaking change
 */

AFRAME.registerComponent('networked-hand-controls', {
  schema: {
    color: { default: 'white', type: 'color' },
    hand: { type: "string", default: 'left' },
    handModelStyle: { type: "string", default: 'highPoly', oneOf: ['lowPoly', 'highPoly', 'toon'] },

    handModelURL: { type: "string", default: '' },
    // ^for specifying a custom model URL; only allowed at init, not via update
    // (must correspond to existing models and have matching animations to work properly)

    gesture: { type: "string", default: 'open' },
    visible: { type: "bool", default: false },
  },

  init() {
    this.Z = Math.random();
    this.Y[this.Z] = { contact: {}, reverse: false, isContact: false };

    this.el.object3D.visible = this.data.visible;

    this.checkLocalHand();
    if (this.local) {
      Object.keys(this.buttonEventMap).forEach(evtName => {
        this.eventFunctionMap[this.data.hand][evtName] = this.handleButton.bind(this, ...this.buttonEventMap[evtName])
      })

      this.el.addEventListener('controllerconnected', () => {
        this.el.setAttribute('networked-hand-controls', 'visible', true);
      });
      this.el.addEventListener('controllerdisconnected', () => {
        this.el.setAttribute('networked-hand-controls', 'visible', false);
      });
    }
    else {
      this.el.classList.add('naf-remote-hand');
    }

    this.loader = new THREE.GLTFLoader();
    this.loader.setCrossOrigin('anonymous');

    const handmodelUrl = this.MODEL_BASE + this.MODEL_URLS[this.data.handModelStyle + this.data.hand.charAt(0).toUpperCase() + this.data.hand.slice(1)];

    this.loader.load(this.data.handModelURL || handmodelUrl,
                      gltf => {
      const newMesh = gltf.scene.children[0];
      const handModelOrientation = this.data.hand === 'left' ? Math.PI / 2 : -Math.PI / 2;
      newMesh.mixer = new THREE.AnimationMixer(newMesh);

      this.clips = gltf.animations;
      this.clips.forEach((clip, clipIndex) => {
        this.clipNameToClip[clip.name] = clip;
      })

      this.el.setObject3D('mesh', newMesh);

      const handMaterial = newMesh.children[1].material;
      handMaterial.color = new THREE.Color(this.data.handColor);
      newMesh.position.set(0, 0, 0);
      newMesh.rotation.set(0, 0, handModelOrientation);

      if (this.local) {
        const controlConfiguration = {
          hand: this.data.hand,
          model: false,
        }
        this.el.setAttribute('magicleap-controls', controlConfiguration);
        this.el.setAttribute('vive-controls', controlConfiguration);
        this.el.setAttribute('oculus-touch-controls', controlConfiguration);
        this.el.setAttribute('windows-motion-controls', controlConfiguration);
        this.el.setAttribute('hp-mixed-reality-controls', controlConfiguration);
        this.isViveController();
      }

    });
  },

  play() {
    if (this.local) {
        this.addEventListeners();
    }
  },

  pause() {
    if (this.local) {
      this.removeEventListeners();
    }
  },

  tick(time, delta) {
    if (this.getMesh()?.mixer) this.getMesh().mixer.update(delta / 1000);
  },

  update(oldData) {
    if (oldData.visible != this.data.visible) {
      this.el.object3D.visible = this.data.visible;
    }

    if (!this.local && (this.data.gesture != oldData.gesture)) {
      this.handleGesture(this.data.gesture, oldData.gesture);
    }
  },

  remove() {
    this.el.removeObject3D('mesh');
  },

  // cache system, protection from A-Frame optimization behavior while reducing garbage collection overhead
  Z: 0,
  Y: {},

  getMesh() {
    return this.el.getObject3D(this.str.mesh)
  },

  // local hands are controlled by one's controllers
  // non-local hands are controlled by updates via NAF
  local: false,
  checkLocalHand() {
    // this.local = this.el.components.networked.data.owner == NAF.clientId;
    // ^while this would be more ideal, NAF doesn't populate the owner until later
    // and we need to know this at init(), and we can't supply schema options
    // when instantiating a template instance--so, we require adding a class for now.
    if (this.el.classList.contains("local-naf-hand")) this.local = true;
  },

  clipNameToClip: {},
  eventFunctionMap: {left:{}, right: {}},

  buttonEventMap: {
    gripdown: ['grip', 'down'],
    gripup: ['grip', 'up'],
    trackpaddown: ['trackpad', 'down'],
    trackpadup: ['trackpad', 'up'],
    trackpadtouchstart: ['trackpad', 'touchstart'],
    trackpadtouchend: ['trackpad', 'touchend'],
    triggerdown: ['trigger', 'down'],
    triggerup: ['trigger', 'up'],
    triggertouchstart: ['trigger', 'touchstart'],
    triggertouchend: ['trigger', 'touchend'],
    griptouchstart: ['grip', 'touchstart'],
    griptouchend: ['grip', 'touchend'],
    abuttontouchstart: ['AorX', 'touchstart'],
    abuttontouchend: ['AorX', 'touchend'],
    bbuttontouchstart: ['BorY', 'touchstart'],
    bbuttontouchend: ['BorY', 'touchend'],
    xbuttontouchstart: ['AorX', 'touchstart'],
    xbuttontouchend: ['AorX', 'touchend'],
    ybuttontouchstart: ['BorY', 'touchstart'],
    ybuttontouchend: ['BorY', 'touchend'],
    surfacetouchstart: ['surface', 'touchstart'],
    surfacetouchend: ['surface', 'touchend'],
    // these seem to not be used
    // thumbstickdown: ['thumbstick', 'down'],
    // thumbstickup: ['thumbstick', 'up'],
  },

  addEventListeners() {
    Object.keys(this.buttonEventMap).forEach(evtName => {
      this.el.addEventListener(evtName, this.eventFunctionMap[this.data.hand][evtName])
    })
  },

  removeEventListeners() {
    Object.keys(this.buttonEventMap).forEach(evtName => {
      this.el.removeEventListener(evtName, this.eventFunctionMap[this.data.hand][evtName])
    })
  },

  // to minimize garbage collection, prevents generating huge numbers of generated strings
  str: {
    nafHandControls: "networked-hand-controls",
    gesture: "gesture",
    mesh: "mesh",
    down: 'down',
    touchstart: 'touchstart',
    fist: "fist",
    point: "point",
    thumbUp: "thumbUp",
    pistol: "pistol",
    hold: "hold",
    open: "open",
  },

  handleButton(button, evt) {
    this.Y[this.Z].isContact = evt === this.str.down || evt === this.str.touchstart;

    if (this.Y[this.Z].isContact === this.Y[this.Z].contact[button]) {
      return;
    }
    this.Y[this.Z].contact[button] = this.Y[this.Z].isContact;

    this.Y[this.Z].lastGesture = this.data.gesture;
    this.determineGesture();
    if (this.Y[this.Z].gesture === this.Y[this.Z].lastGesture) { return; }

    // set new gesture into schema to propagate via NAF
    this.el.setAttribute(this.str.nafHandControls, this.str.gesture, this.Y[this.Z].gesture);

    this.handleGesture();
  },

  handleGesture() {
    if (this.data.gesture === this.Y[this.Z].lastGesture) { return; }

    this.playAnimation();

    this.emitGestureEvents();
  },

  // note: using preserved references to reduce garbage collection, as this function can fire thousands of times.
  determineGesture() {
    this.Y[this.Z].gripIsActive = this.Y[this.Z].contact.grip;
    this.Y[this.Z].triggerIsActive = this.Y[this.Z].contact.trigger
    this.Y[this.Z].thumbIsEngaged =
          this.Y[this.Z].contact.trackpad || this.Y[this.Z].contact.surface
          this.Y[this.Z].contact.AorX || this.Y[this.Z].contact.BorY;

    if (!this.isVive) {
      this.Y[this.Z].gesture =
          this.Y[this.Z].gripIsActive &&
          this.Y[this.Z].thumbIsEngaged &&
          this.Y[this.Z].triggerIsActive ?
                          this.str.fist :
          this.Y[this.Z].gripIsActive &&
          this.Y[this.Z].thumbIsEngaged &&
        !this.Y[this.Z].triggerIsActive ?
                          this.str.point :
          this.Y[this.Z].gripIsActive &&
        !this.Y[this.Z].thumbIsEngaged &&
          this.Y[this.Z].triggerIsActive ?
                          this.str.thumbUp :
          this.Y[this.Z].gripIsActive &&
        !this.Y[this.Z].thumbIsEngaged &&
        !this.Y[this.Z].triggerIsActive ?
                          this.str.pistol :
          this.Y[this.Z].triggerIsActive ?
                          this.str.hold :
                          this.str.open ;
    }
    else { // Vive handling was supposedly left incomplete
      this.Y[this.Z].gesture =
        this.Y[this.Z].gripIsActive ||
        this.Y[this.Z].triggerIsActive ?
                          this.str.fist :
        this.Y[this.Z].contact.trackpad ||
        this.Y[this.Z].contact.trackpad ?
                          this.str.point :
                          this.str.open ;
    }
  },

  emitGestureEvents() {
    this.el.emit(
      this.eventNames[this.Y[this.Z].lastGesture]?.inactive
    );
    this.el.emit(
      this.eventNames[this.data.gesture]?.active
    );
  },

  // mapping rather than function to reduce garbage collection
  eventNames: {
    fist: {active: "fiststart", inactive: "fistend"},
    thumbUp: {active: "thumbupstart", inactive: "thumbupend"},
    point: {active: "pointstart", inactive: "pointend"},
    hold: {active: "holdstart", inactive: "holdend"},
    open: {active: "openstart", inactive: "openend"},
    pistol: {active: "pistolstart", inactive: "pistolend"},
  },

  playAnimation() {
    // this function seems like it could be improved
    // implementation is from the original hand-controls component

    if (!this.getMesh()) { return; }

    // Stop all current animations.
    this.getMesh().mixer.stopAllAction();

    // Grab clip action.
    this.Y[this.Z].clip = this.clipNameToClip[this.ANIMATIONS[this.data.gesture]];
    this.Y[this.Z].toAction = this.getMesh().mixer.clipAction(this.Y[this.Z].clip);
    this.Y[this.Z].toAction.clampWhenFinished = true;
    this.Y[this.Z].toAction.loop = THREE.LoopRepeat;
    this.Y[this.Z].toAction.repetitions = 0;
    this.Y[this.Z].toAction.timeScale = this.Y[this.Z].reverse ? -1 : 1;
    this.Y[this.Z].toAction.time = this.Y[this.Z].reverse ? this.Y[this.Z].clip.duration : 0;
    this.Y[this.Z].toAction.weight = 1;

    if (!this.Y[this.Z].lastGesture || this.data.gesture === this.Y[this.Z].lastGesture) {
      this.getMesh().mixer.stopAllAction();
      this.Y[this.Z].toAction.play();
      return;
    }

    // Animate or crossfade from gesture to gesture.
    this.Y[this.Z].clip = this.clipNameToClip[this.ANIMATIONS[this.Y[this.Z].lastGesture]];
    this.Y[this.Z].fromAction = this.getMesh().mixer.clipAction(this.Y[this.Z].clip);
    this.Y[this.Z].fromAction.weight = 0.15;
    this.Y[this.Z].fromAction.play();
    this.Y[this.Z].toAction.play();
    this.Y[this.Z].fromAction.crossFadeTo(this.Y[this.Z].toAction, 0.15, true);
  },

  // see https://github.com/aframevr/assets.
  MODEL_BASE: 'https://cdn.aframe.io/controllers/hands/',
  MODEL_URLS: {
    toonLeft: 'leftHand.glb',
    toonRight: 'rightHand.glb',
    lowPolyLeft: 'leftHandLow.glb',
    lowPolyRight: 'rightHandLow.glb',
    highPolyLeft: 'leftHandHigh.glb',
    highPolyRight: 'rightHandHigh.glb',
  },

  // map from internal state names to the names of the clips as specified within the models
  ANIMATIONS: {
    open: 'Open',
    point: 'Point',
    pistol: 'Point + Thumb',
    fist: 'Fist',
    hold: 'Hold',
    thumbUp: 'Thumb Up',
  },

  isVive: false,
  isViveController() {
    let controller = this.el.components['tracked-controls'] && this.el.components['tracked-controls'].controller;
    this.isVive = controller && (controller.id && controller.id.indexOf('OpenVR ') === 0 ||
      (controller.profiles &&
        controller.profiles[0] &&
        controller.profiles[0] === 'htc-vive'));
  },
});
  <|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {OAUTH_AUTHORIZE_URL,
  OAUTH_PROVIDER_NAME,
  CLIENT_ID,
  OAUTH_GH_REPO_STATUS_SCOPE} from '../../utils/constants';
import * as oauthActions from '../../actions/oauthActions';
import * as repoActions from '../../actions/repoActions';
import GatekeeperApi from '../../api/gatekeeperAPI';
import GithubAPI from '../../api/githubAPI';

import toastr from 'toastr';

class OAuthSignInButton extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.handleOAuthClick = this.handleOAuthClick.bind(this);
    this.getTokenFromCode = this.getTokenFromCode.bind(this);
  }

  handleOAuthClick(){
    let currentThis = this;
    let isToken = currentThis.props.oauths.oauthReturnedToken.length !== 0;

    // if token does not exist, then login to GitHub; otherwise logout of GitHub.
    if (!isToken) {
      authenticate(currentThis);
    }else{
      this.logoutGithub();
    }
  }

  getTokenFromCode(){
    GatekeeperApi.exchangeCodeForToken(this.props.oauths.oauthReturnedTempCode).then(result => {
      this.props.oauthActions.storeOAuthToken(result);
      GithubAPI.addTokenToOcto(this.props.oauths.oauthReturnedToken).then(() => {
        this.props.oauthActions.loadAuthenticatedUser();
      });
    }).catch(error => {
      throw(error);
    });
  }

  logoutGithub(){
    GithubAPI.removeTokenFromOcto().then(() => {
      this.props.oauthActions.destroyOAuthToken();
    });
  }

  render() {
    let isToken = this.props.oauths.oauthReturnedToken.length === 0;

    return (
      <button type="button" className={isToken === true ? "btn btn-success" : "btn btn-danger"} onClick={this.handleOAuthClick}>
        {isToken === true ? "Sign Into GitHub" : "Sign Out of GitHub"}
      </button>
    );
  }
}

OAuthSignInButton.propTypes = {
  oauthActions: PropTypes.object,
  repoActions: PropTypes.object,
  oauths: PropTypes.object
};

function authenticate(currentThis){
  //each scope in the builtScopeList string should be separated by a space
  let builtScopeList = OAUTH_GH_REPO_STATUS_SCOPE;
  let builtOAuthRequestURL = encodeURI(OAUTH_AUTHORIZE_URL + "?client_id=" + CLIENT_ID + "&scope=" + builtScopeList);
  let timer, authWindow;

  window.addEventListener('message', windowReturnHandler);

  authWindow = window.open(builtOAuthRequestURL, OAUTH_PROVIDER_NAME, "width=500,height=800");

  //TODO: Refactor '500' timer interval out to constant
  timer = setInterval(checkAuthWindow, 500);

  function checkAuthWindow() {
    if (authWindow.closed) {
      clearInterval(timer);

      if(!currentThis.props.oauths.oauthReturnedTempCode){
        window.removeEventListener('message', windowReturnHandler);
        toastr.error("GitHub Login Failed");
      }
    }
  }

  function windowReturnHandler(event) {
    let tempCode = event.data;
    window.removeEventListener('message', windowReturnHandler);
    currentThis.props.oauthActions.storeOAuthTempCode(tempCode);
    currentThis.getTokenFromCode();
  }
}

function mapStateToProps(state) {
  return {
    oauths: state.oauths
  };
}

function mapDispatchToProps(dispatch) {
  return {
    oauthActions: bindActionCreators(oauthActions, dispatch),
    repoActions: bindActionCreators(repoActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(OAuthSignInButton);
<commit_msg>Replace 'magic number' in 'authenticate' function with local const<commit_after>import React, {PropTypes} from 'react';
import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import {OAUTH_AUTHORIZE_URL,
  OAUTH_PROVIDER_NAME,
  CLIENT_ID,
  OAUTH_GH_REPO_STATUS_SCOPE} from '../../utils/constants';
import * as oauthActions from '../../actions/oauthActions';
import * as repoActions from '../../actions/repoActions';
import GatekeeperApi from '../../api/gatekeeperAPI';
import GithubAPI from '../../api/githubAPI';

import toastr from 'toastr';

class OAuthSignInButton extends React.Component {
  constructor(props, context) {
    super(props, context);

    this.handleOAuthClick = this.handleOAuthClick.bind(this);
    this.getTokenFromCode = this.getTokenFromCode.bind(this);
  }

  handleOAuthClick(){
    let currentThis = this;
    let isToken = currentThis.props.oauths.oauthReturnedToken.length !== 0;

    // if token does not exist, then login to GitHub; otherwise logout of GitHub.
    if (!isToken) {
      authenticate(currentThis);
    }else{
      this.logoutGithub();
    }
  }

  getTokenFromCode(){
    GatekeeperApi.exchangeCodeForToken(this.props.oauths.oauthReturnedTempCode).then(result => {
      this.props.oauthActions.storeOAuthToken(result);
      GithubAPI.addTokenToOcto(this.props.oauths.oauthReturnedToken).then(() => {
        this.props.oauthActions.loadAuthenticatedUser();
      });
    }).catch(error => {
      throw(error);
    });
  }

  logoutGithub(){
    GithubAPI.removeTokenFromOcto().then(() => {
      this.props.oauthActions.destroyOAuthToken();
    });
  }

  render() {
    let isToken = this.props.oauths.oauthReturnedToken.length === 0;

    return (
      <button type="button" className={isToken === true ? "btn btn-success" : "btn btn-danger"} onClick={this.handleOAuthClick}>
        {isToken === true ? "Sign Into GitHub" : "Sign Out of GitHub"}
      </button>
    );
  }
}

OAuthSignInButton.propTypes = {
  oauthActions: PropTypes.object,
  repoActions: PropTypes.object,
  oauths: PropTypes.object
};

function authenticate(currentThis){
  const windowClosedCheckRepeatInterval = 500;
  //each scope in the builtScopeList string should be separated by a space
  let builtScopeList = OAUTH_GH_REPO_STATUS_SCOPE;
  let builtOAuthRequestURL = encodeURI(OAUTH_AUTHORIZE_URL + "?client_id=" + CLIENT_ID + "&scope=" + builtScopeList);
  let timer, authWindow;

  window.addEventListener('message', windowReturnHandler);

  authWindow = window.open(builtOAuthRequestURL, OAUTH_PROVIDER_NAME, "width=500,height=800");

  timer = setInterval(checkAuthWindow, windowClosedCheckRepeatInterval);

  function checkAuthWindow() {
    if (authWindow.closed) {
      clearInterval(timer);

      if(!currentThis.props.oauths.oauthReturnedTempCode){
        window.removeEventListener('message', windowReturnHandler);
        toastr.error("GitHub Login Failed");
      }
    }
  }

  function windowReturnHandler(event) {
    let tempCode = event.data;
    window.removeEventListener('message', windowReturnHandler);
    currentThis.props.oauthActions.storeOAuthTempCode(tempCode);
    currentThis.getTokenFromCode();
  }
}

function mapStateToProps(state) {
  return {
    oauths: state.oauths
  };
}

function mapDispatchToProps(dispatch) {
  return {
    oauthActions: bindActionCreators(oauthActions, dispatch),
    repoActions: bindActionCreators(repoActions, dispatch)
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(OAuthSignInButton);
<|endoftext|>


<commit_before>'use strict';

/**
 * @description
 *  How to use this?
 *
 *

 *
 *         TabNavigation( $('.buttons-group.sections') ).goTo( 'Topology' );
 *
 *

 * @param base base element for component
 * @constructor
 */

module.exports = function TabNavigation(base) {

    return {
        getSections : function(){
            return base.all(by.css('button'));
        },
        goTo : function( sectionName ){
            var sections = this.getSections().filter(function (section) {
                return section.getText().then(function (text) {
                    return text === sectionName;
                });
            });
            expect(sections.count()).toBe(1, 'section ' + sectionName + ' should exist');
            sections.click();
        }
    };
};<commit_msg>fix tab navigation component. we switched from buttons to anchor elements.<commit_after>'use strict';

/**
 * @description
 *  How to use this?
 *
 *

 *
 *         TabNavigation( $('.buttons-group.sections') ).goTo( 'Topology' );
 *
 *

 * @param base base element for component
 * @constructor
 */

module.exports = function TabNavigation(base) {

    return {
        getSections : function(){
            return base.all(by.css('a'));
        },
        goTo : function( sectionName ){
            var sections = this.getSections().filter(function (section) {
                return section.getText().then(function (text) {
                    return text === sectionName;
                });
            });
            expect(sections.count()).toBe(1, 'section ' + sectionName + ' should exist');
            sections.click();
        }
    };
};<|endoftext|>


<commit_before>// @flow
import React from 'react'
import styled from 'styled-components'
import {interpose} from '../../lib'
import brwsr from 'brwsr'

import {
    installOperaExtension,
    installChromeExtension,
    installFirefoxExtension,
} from '../../web/helpers/extension-helpers'

const Button = styled`
    display: inline-block;
`

type ButtonProps = {
    onClick: (SyntheticInputEvent<HTMLButtonElement>) => any,
    browserName: string,
    disabled?: boolean,
}

function BrowserButton({onClick, browserName, disabled}: ButtonProps) {
    return (
        <Button type="button" disabled={disabled} onClick={onClick}>
            {browserName}
        </Button>
    )
}

type Props = {
    onInstall: () => any,
}

type State = {
    installError: ?Error,
    installAttempted: boolean,
    extensionInstalled: boolean,
}

export class BrowserExtensionsComponent extends React.Component<Props, State> {
    state = {
        installAttempted: false,
        installError: null,
        extensionInstalled: false,
    }

    checkExtensionStatus = () => {
        if (global.gobbldygook_extension >= '1.0.0') {
            this.setState({extensionInstalled: true})
        }
    }

    installChromeExtension = (ev: SyntheticEvent<>) => {
        ev.preventDefault()
        ev.stopPropagation()

        installChromeExtension()
            .then(this.installSuccess)
            .catch(this.installFailure)
    }

    installFirefoxExtension = (ev: SyntheticEvent<>) => {
        ev.preventDefault()
        ev.stopPropagation()

        installFirefoxExtension()
            .then(this.installSuccess)
            .catch(this.installFailure)
    }

    installOperaExtension = (ev: SyntheticEvent<>) => {
        ev.preventDefault()
        ev.stopPropagation()

        installOperaExtension()
            .then(this.installSuccess)
            .catch(this.installFailure)
    }

    // eslint-disable-next-line no-unused-vars
    installSafariExtension = (ev: SyntheticEvent<>) => {}

    // eslint-disable-next-line no-unused-vars
    installEdgeExtension = (ev: SyntheticEvent<>) => {}

    installSuccess = () => {
        this.setState({installAttempted: true})
        this.props.onInstall()
    }

    installFailure = (err: Error) => {
        this.setState({installError: err, installAttempted: true})
    }

    detectBrowser() {
        return brwsr()
    }

    flavorText() {
        let browser = this.detectBrowser()
        let base = `It looks like you're running ${browser}.`
        if (browser === 'Internet Explorer') {
            return `${base} There is no extension available for your browser.`
        }
        if (browser === 'Opera' || browser === 'Safari') {
            return `${base} The extension is under development. Let me know if you would use it.`
        }
        return `${base} Use the appropriate link below to install the extension.`
    }

    buttons() {
        return [
            {
                name: 'Google Chrome',
                button: (
                    <BrowserButton
                        key="chrome"
                        onClick={this.installChromeExtension}
                        browserName="Chrome"
                    />
                ),
            },
            {
                name: 'Mozilla Firefox',
                button: (
                    <BrowserButton
                        key="firefox"
                        onClick={this.installFirefoxExtension}
                        browserName="Firefox"
                    />
                ),
            },
            {
                name: 'Microsoft Edge',
                button: (
                    <BrowserButton
                        key="edge"
                        disabled
                        onClick={this.installEdgeExtension}
                        browserName="Edge"
                    />
                ),
            },
            {
                name: 'Safari',
                button: (
                    <BrowserButton
                        key="safari"
                        disabled
                        onClick={this.installSafariExtension}
                        browserName="Safari"
                    />
                ),
            },
            {
                name: 'Opera',
                button: (
                    <BrowserButton
                        key="opera"
                        disabled
                        onClick={this.installOperaExtension}
                        browserName="Opera"
                    />
                ),
            },
        ]
    }

    primaryButton() {
        return this.buttons()
            .filter(btn => btn.name === this.detectBrowser())
            .map(btn => btn.button)[0]
    }

    secondaryButtons() {
        return this.buttons()
            .filter(btn => btn.name !== this.detectBrowser())
            .map(btn => btn.button)
    }

    render() {
        if (this.checkExtensionStatus()) {
            return (

The extension is installed and active. Let's rock!

            )
        }

        return (

                    Gobbldygook uses a browser extension to import your data
                    from the SIS. Once you have imported your information, you
                    don't need the extension anymore.

{this.flavorText()}

Install for {this.primaryButton()}.

                    Not {this.detectBrowser()}?

                        We also have {interpose(this.secondaryButtons(), ', ')}.

                    {this.state.installError
                        ? this.state.installError.message
                        : null}

        )
    }
}
<commit_msg>fix styled-components bug in BrowserExtensions<commit_after>// @flow
import React from 'react'
import styled from 'styled-components'
import {interpose} from '../../lib'
import brwsr from 'brwsr'

import {
    installOperaExtension,
    installChromeExtension,
    installFirefoxExtension,
} from '../../web/helpers/extension-helpers'

const Button = styled.button`
    display: inline-block;
`

type ButtonProps = {
    onClick: (SyntheticInputEvent<HTMLButtonElement>) => any,
    browserName: string,
    disabled?: boolean,
}

function BrowserButton({onClick, browserName, disabled}: ButtonProps) {
    return (
        <Button type="button" disabled={disabled} onClick={onClick}>
            {browserName}
        </Button>
    )
}

type Props = {
    onInstall: () => any,
}

type State = {
    installError: ?Error,
    installAttempted: boolean,
    extensionInstalled: boolean,
}

export class BrowserExtensionsComponent extends React.Component<Props, State> {
    state = {
        installAttempted: false,
        installError: null,
        extensionInstalled: false,
    }

    checkExtensionStatus = () => {
        if (global.gobbldygook_extension >= '1.0.0') {
            this.setState({extensionInstalled: true})
        }
    }

    installChromeExtension = (ev: SyntheticEvent<>) => {
        ev.preventDefault()
        ev.stopPropagation()

        installChromeExtension()
            .then(this.installSuccess)
            .catch(this.installFailure)
    }

    installFirefoxExtension = (ev: SyntheticEvent<>) => {
        ev.preventDefault()
        ev.stopPropagation()

        installFirefoxExtension()
            .then(this.installSuccess)
            .catch(this.installFailure)
    }

    installOperaExtension = (ev: SyntheticEvent<>) => {
        ev.preventDefault()
        ev.stopPropagation()

        installOperaExtension()
            .then(this.installSuccess)
            .catch(this.installFailure)
    }

    // eslint-disable-next-line no-unused-vars
    installSafariExtension = (ev: SyntheticEvent<>) => {}

    // eslint-disable-next-line no-unused-vars
    installEdgeExtension = (ev: SyntheticEvent<>) => {}

    installSuccess = () => {
        this.setState({installAttempted: true})
        this.props.onInstall()
    }

    installFailure = (err: Error) => {
        this.setState({installError: err, installAttempted: true})
    }

    detectBrowser() {
        return brwsr()
    }

    flavorText() {
        let browser = this.detectBrowser()
        let base = `It looks like you're running ${browser}.`
        if (browser === 'Internet Explorer') {
            return `${base} There is no extension available for your browser.`
        }
        if (browser === 'Opera' || browser === 'Safari') {
            return `${base} The extension is under development. Let me know if you would use it.`
        }
        return `${base} Use the appropriate link below to install the extension.`
    }

    buttons() {
        return [
            {
                name: 'Google Chrome',
                button: (
                    <BrowserButton
                        key="chrome"
                        onClick={this.installChromeExtension}
                        browserName="Chrome"
                    />
                ),
            },
            {
                name: 'Mozilla Firefox',
                button: (
                    <BrowserButton
                        key="firefox"
                        onClick={this.installFirefoxExtension}
                        browserName="Firefox"
                    />
                ),
            },
            {
                name: 'Microsoft Edge',
                button: (
                    <BrowserButton
                        key="edge"
                        disabled
                        onClick={this.installEdgeExtension}
                        browserName="Edge"
                    />
                ),
            },
            {
                name: 'Safari',
                button: (
                    <BrowserButton
                        key="safari"
                        disabled
                        onClick={this.installSafariExtension}
                        browserName="Safari"
                    />
                ),
            },
            {
                name: 'Opera',
                button: (
                    <BrowserButton
                        key="opera"
                        disabled
                        onClick={this.installOperaExtension}
                        browserName="Opera"
                    />
                ),
            },
        ]
    }

    primaryButton() {
        return this.buttons()
            .filter(btn => btn.name === this.detectBrowser())
            .map(btn => btn.button)[0]
    }

    secondaryButtons() {
        return this.buttons()
            .filter(btn => btn.name !== this.detectBrowser())
            .map(btn => btn.button)
    }

    render() {
        if (this.checkExtensionStatus()) {
            return (

The extension is installed and active. Let's rock!

            )
        }

        return (

                    Gobbldygook uses a browser extension to import your data
                    from the SIS. Once you have imported your information, you
                    don't need the extension anymore.

{this.flavorText()}

Install for {this.primaryButton()}.

                    Not {this.detectBrowser()}?

                        We also have {interpose(this.secondaryButtons(), ', ')}.

                    {this.state.installError
                        ? this.state.installError.message
                        : null}

        )
    }
}
<|endoftext|>


<commit_before>$(document).ready(function () {
    var userID;
    var baseURL;
    var myData;
    var searchQry;
    var trackID;
    var sendInfo;
    var Snapster;
    var playlists = [];
    var total;
    var totalVariable;
    var urlData;
    var jsonData;
    var obj;
    var flag;
    var jData;
    var partyPlaylist;
    $(window).load(function () {
        baseURL = "https://api.spotify.com/v1/users/";
        searchQry = document.getElementById('filename').value;
        userID = "b.univthink";
        //document.getElementById('userID').innerHTML;
        $.ajax({
            type: "GET",
            url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
            headers: { 'Authorization': 'Bearer ' + access_token },
            dataType: "json",
            data: "formdata",
            success: function (data) {
                for (i = 0; i < data.items.length; i++) {
                    playlists.push(data.items[i].name);
                }
                partyPlaylist = playlists.indexOf("Partify");
                console.log(partyPlaylist);
                if (playlists.indexOf("Partify") == -1) {
                    sendInfo = { "name": "Partify", "public": true, }
                    $.ajax({
                        type: "POST",
                        url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                        headers: { 'Authorization': 'Bearer ' + access_token },
                        dataType: "application/json",
                        data: JSON.stringify(sendInfo),
                        success: function (dataFirst) {
                            $.ajax({
                                type: "GET",
                                url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                headers: { 'Authorization': 'Bearer ' + access_token },
                                dataType: "json",
                                data: "formdata",
                                success: function (data) {
                                    Snapster = data.items[i].id;
                                    $("#results").hide().fadeIn('fast');
                                    location.reload();
                                }
                            });
                        }
                    });
                }
            }
        });
    $("#filename").keypress(function (event) {
            if (event.which == 13) {
                $("#results").empty();
          //  $("#searchSongs").click(function () {
                    baseURL = "https://api.spotify.com/v1/users/";
                    searchQry = document.getElementById('filename').value;
                userID = "b.univthink";
                    //document.getElementById('userID').innerHTML;
                $.ajax({
                    type: "GET",
                    url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                    headers: { 'Authorization': 'Bearer ' + access_token },
                    dataType: "json",
                    data: "formdata",
                    success: function (data) {
                        for (i = 0; i < data.items.length; i++) {
                            playlists.push(data.items[i].name);
                        }
                        partyPlaylist = playlists.indexOf("Partify");
                        console.log(partyPlaylist);
                        if (playlists.indexOf("Partify") == -1) {
                            sendInfo = { "name": "Partify", "public": true, }
                            $.ajax({
                                type: "POST",
                                url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                headers: { 'Authorization': 'Bearer ' + access_token },
                                dataType: "application/json",
                                data: JSON.stringify(sendInfo),
                                success: function (dataFirst) {
                                    $.ajax({
                                        type: "GET",
                                        url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                        dataType: "json",
                                        data: "formdata",
                                        success: function (data) {
                                            Snapster = data.items[i].id;
                                            $("#results").hide().fadeIn('fast');
                                        }
                                    });
                                    $.ajax({
                                        type: "GET",
                                        url: "https://api.spotify.com/v1/search?q=" + searchQry + "&type=track,artist&market=us&limit=50&offset=0",
                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                        dataType: "json",
                                        data: "formdata",
                                        success: function (myData) {
                                            for (i = 0; i < myData.tracks.length; i++) {
                                                $('#results').append("" + myData.tracks.items[i].artists[0].name + "
" + myData.tracks.items[i].name + "
");
                                                $(".songLink").eq(i).attr("id", "songLink" + i);
                                                $(".songLink").eq(i).attr("name", baseURL + userID + "/playlists/" + Snapster + "/tracks?&uris=spotify%3Atrack%3A" + myData.tracks.items[i].id);
                                                $('#songLink' + i).on("click", function () {
                                                    //for (i = 0; i < data.items.length; i++) {
                                                    //    $('#songLink' + i).on("click", function () {
                                                    $.ajax({
                                                        type: "POST",
                                                        url: $(this).attr('name'),
                                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                                        dataType: "json",
                                                        data: "formdata",
                                                        success: function (dataFirst) {
                                                        console.log(Snapster);
                                                        $("#results").empty();
                                                        $("#results").css("text-align", "center");
                                                        $("#results").append($('').attr({ display: "block", src: "check-mark.png", top: "25%" }));
                                                        setTimeout(function () {
                                                            location.reload();
                                                        }, 1000);
                                                        }
                                                    });
                                                });
                                            }
                                        }

                                    });
                                }
                            });

                        }
                        else if (playlists.indexOf("Partify") != -1) {
                            console.log(partyPlaylist + "Already Exists");
                            Snapster = data.items[partyPlaylist].id;
                            $("#results").hide().fadeIn('fast');
                            $.ajax({
                                type: "GET",
                                url: "https://api.spotify.com/v1/search?q=" + searchQry + "&type=track,artist&market=us&limit=50&offset=0",
                                headers: { 'Authorization': 'Bearer ' + access_token },
                                dataType: "json",
                                data: "formdata",
                                success: function (myData) {
                                    $.ajax({
                                        type: "GET",
                                        url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                        dataType: "json",
                                        data: "formdata",
                                        success: function (data) {
                                            for (i = 0; i < data.items.length; i++) {
                                                playlists.push(data.items[i].name);
                                            }
                                            if (playlists.indexOf("Partify") != -1) {
                                                for (i = 0; i < data.items.length; i++) {
                                                    console.log("Already Exists");
                                                    Snapster = data.items[partyPlaylist].id;
                                                }
                                                for (i = 0; i < myData.tracks.items.length; i++) {
                                                    $('#results').append("" + myData.tracks.items[i].artists[0].name + "
" + myData.tracks.items[i].name + "
");
                                                    $(".songLink").eq(i).attr("id", "songLink" + i);
                                                    $(".songLink").eq(i).attr("name", baseURL + userID + "/playlists/" + Snapster + "/tracks?&uris=spotify%3Atrack%3A" + myData.tracks.items[i].id);
                                                    $('header#songLink' + i).on("click", function () {
                                                    $.ajax( {
                                                        type: "POST",
                                                        url: $(this).attr('name'),
                                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                                        dataType: "json",
                                                        data: "formdata",
                                                        success: function (dataFirst) {
                                                            console.log("playlist id=" + Snapster);
                                                            console.log("song id=" + myData.tracks.items.id);
                                                            $("#results").empty();
                                                            $("#results").css("text-align", "center");
                                                            $("#results").append($('').attr({ display: "block", src: "check-mark.png", top: "25%" }));
                                                            setTimeout(function () {
                                                                location.reload();
                                                            }, 1000);
                                                            }
                                                        });
                                                    });
                                                }
                                            }
                                        }
                                    });
                                }
                            });

                        }
                    }
                });

         //   });

        }
    });
    });
});<commit_msg>Headaches<commit_after>$(document).ready(function () {
    var userID;
    var baseURL;
    var myData;
    var searchQry;
    var trackID;
    var sendInfo;
    var Snapster;
    var playlists = [];
    var total;
    var totalVariable;
    var urlData;
    var jsonData;
    var obj;
    var flag;
    var jData;
    var partyPlaylist;
    $(window).load(function () {
        baseURL = "https://api.spotify.com/v1/users/";
        searchQry = document.getElementById('filename').value;
        //userID = "b.univthink";
        //document.getElementById('userID').innerHTML;
        $.ajax({
            type: "GET",
            url: "https://api.spotify.com/v1/me/",
            headers: { 'Authorization': 'Bearer ' + access_token },
            success: function (data) {
                userID = data.id;
            }
        });
        $.ajax({
            type: "GET",
            url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
            headers: { 'Authorization': 'Bearer ' + access_token },
            dataType: "json",
            data: "formdata",
            success: function (data) {
                for (i = 0; i < data.items.length; i++) {
                    playlists.push(data.items[i].name);
                }
                partyPlaylist = playlists.indexOf("Partify");
                console.log(partyPlaylist);
                if (playlists.indexOf("Partify") == -1) {
                    sendInfo = { "name": "Partify", "public": true, }
                    $.ajax({
                        type: "POST",
                        url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                        headers: { 'Authorization': 'Bearer ' + access_token },
                        dataType: "application/json",
                        data: JSON.stringify(sendInfo),
                        success: function (dataFirst) {
                            $.ajax({
                                type: "GET",
                                url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                headers: { 'Authorization': 'Bearer ' + access_token },
                                dataType: "json",
                                data: "formdata",
                                success: function (data) {
                                    Snapster = data.items[i].id;
                                    $("#results").hide().fadeIn('fast');
                                    location.reload();
                                }
                            });
                        }
                    });
                }
            }
        });
    $("#filename").keypress(function (event) {
            if (event.which == 13) {
                $("#results").empty();
          //  $("#searchSongs").click(function () {
                    baseURL = "https://api.spotify.com/v1/users/";
                    searchQry = document.getElementById('filename').value;
                userID = "b.univthink";
                    //document.getElementById('userID').innerHTML;
                $.ajax({
                    type: "GET",
                    url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                    headers: { 'Authorization': 'Bearer ' + access_token },
                    dataType: "json",
                    data: "formdata",
                    success: function (data) {
                        for (i = 0; i < data.items.length; i++) {
                            playlists.push(data.items[i].name);
                        }
                        partyPlaylist = playlists.indexOf("Partify");
                        console.log(partyPlaylist);
                        if (playlists.indexOf("Partify") == -1) {
                            sendInfo = { "name": "Partify", "public": true, }
                            $.ajax({
                                type: "POST",
                                url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                headers: { 'Authorization': 'Bearer ' + access_token },
                                dataType: "application/json",
                                data: JSON.stringify(sendInfo),
                                success: function (dataFirst) {
                                    $.ajax({
                                        type: "GET",
                                        url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                        dataType: "json",
                                        data: "formdata",
                                        success: function (data) {
                                            Snapster = data.items[partyPlaylist].id;
                                            $("#results").hide().fadeIn('fast');
                                        }
                                    });
                                    $.ajax({
                                        type: "GET",
                                        url: "https://api.spotify.com/v1/search?q=" + searchQry + "&type=track,artist&market=us&limit=50&offset=0",
                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                        dataType: "json",
                                        data: "formdata",
                                        success: function (myData) {
                                            for (i = 0; i < myData.tracks.length; i++) {
                                                $('#results').append("" + myData.tracks.items[i].artists[0].name + "
" + myData.tracks.items[i].name + "
");
                                                $(".songLink").eq(i).attr("id", "songLink" + i);
                                                $(".songLink").eq(i).attr("name", baseURL + userID + "/playlists/" + Snapster + "/tracks?&uris=spotify%3Atrack%3A" + myData.tracks.items[i].id);
                                                $('#songLink' + i).on("click", function () {
                                                    //for (i = 0; i < data.items.length; i++) {
                                                    //    $('#songLink' + i).on("click", function () {
                                                    $.ajax({
                                                        type: "POST",
                                                        url: $(this).attr('name'),
                                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                                        dataType: "json",
                                                        data: "formdata",
                                                        success: function (dataFirst) {
                                                        console.log(Snapster);
                                                        $("#results").empty();
                                                        $("#results").css("text-align", "center");
                                                        $("#results").append($('').attr({ display: "block", src: "check-mark.png", top: "25%" }));
                                                        setTimeout(function () {
                                                            location.reload();
                                                        }, 1000);
                                                        }
                                                    });
                                                });
                                            }
                                        }

                                    });
                                }
                            });

                        }
                        else if (playlists.indexOf("Partify") != -1) {
                            console.log(partyPlaylist + "Already Exists");
                            Snapster = data.items[partyPlaylist].id;
                            $("#results").hide().fadeIn('fast');
                            $.ajax({
                                type: "GET",
                                url: "https://api.spotify.com/v1/search?q=" + searchQry + "&type=track,artist&market=us&limit=50&offset=0",
                                headers: { 'Authorization': 'Bearer ' + access_token },
                                dataType: "json",
                                data: "formdata",
                                success: function (myData) {
                                    $.ajax({
                                        type: "GET",
                                        url: "https://api.spotify.com/v1/users/" + userID + "/playlists",
                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                        dataType: "json",
                                        data: "formdata",
                                        success: function (data) {
                                            for (i = 0; i < data.items.length; i++) {
                                                playlists.push(data.items[i].name);
                                            }
                                            if (playlists.indexOf("Partify") != -1) {
                                                for (i = 0; i < data.items.length; i++) {
                                                    console.log("Already Exists");
                                                    Snapster = data.items[partyPlaylist].id;
                                                }
                                                for (i = 0; i < myData.tracks.items.length; i++) {
                                                    $('#results').append("" + myData.tracks.items[i].artists[0].name + "
" + myData.tracks.items[i].name + "
");
                                                    $(".songLink").eq(i).attr("id", "songLink" + i);
                                                    $(".songLink").eq(i).attr("name", baseURL + userID + "/playlists/" + Snapster + "/tracks?&uris=spotify%3Atrack%3A" + myData.tracks.items[i].id);
                                                    $('header#songLink' + i).on("click", function () {
                                                    $.ajax( {
                                                        type: "POST",
                                                        url: $(this).attr('name'),
                                                        headers: { 'Authorization': 'Bearer ' + access_token },
                                                        dataType: "json",
                                                        data: "formdata",
                                                        success: function (dataFirst) {
                                                            console.log("playlist id=" + Snapster);
                                                            console.log("song id=" + myData.tracks.items.id);
                                                            $("#results").empty();
                                                            $("#results").css("text-align", "center");
                                                            $("#results").append($('').attr({ display: "block", src: "check-mark.png", top: "25%" }));
                                                            setTimeout(function () {
                                                                location.reload();
                                                            }, 1000);
                                                            }
                                                        });
                                                    });
                                                }
                                            }
                                        }
                                    });
                                }
                            });

                        }
                    }
                });

         //   });

        }
    });
    });
});
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies.
 */

const mongoose = require('mongoose');
const TwitterStrategy = require('passport-twitter').Strategy;
const config = require('../../config');
const User = mongoose.model('User');
const extract = require('../../lib/extract');

/**
 * Expose
 */

module.exports = new TwitterStrategy({
    consumerKey: config.twitter.clientID,
    consumerSecret: config.twitter.clientSecret,
    callbackURL: config.twitter.callbackURL,
    includeEmail: true,
  },
  function (accessToken, refreshToken, profile, done) {
    const options = {
      criteria: { "twitter_id": profile.id }
    };
    User.load(options, function (err, user) {
      if (err) return done(err);

      if (!user) {
        let user = new User({
          name: profile.displayName,
          username: profile.username,
          email: profile.email,
          provider: 'twitter',
          twitter_id: profile.id,
          twitter: profile._json
        });
        //pulling profile images
        // if (user.twitter && user.twitter.profile_image_url){
        //   const uID = user._id;
        //   const defaultImageURL = user.twitter.profile_image_url;
        //   const defaultImageUID =  'profile_images/default_'+ uID;
        //   extract.upload(defaultImageURL, defaultImageUID, function(err, url, dimensions){
        //     if (err) return console.log(err, 'TwitterStrategy Auth');
        //     user.profile_image = url;
        //     user.save();
        //   });
        //   const largeImageURL = user.twitter.profile_image_url.replace('_normal.jpg', '.jpg');
        //   const largeImageUID =  'profile_images/large_'+ uID;
        //   extract.upload(largeImageURL, largeImageUID, function(err, url, dimensions){
        //     if (err) return console.log(err, 'TwitterStrategy Auth');
        //     user.profile_image_large = url;
        //     user.save();
        //   });
        // }

        user.save(function (err) {
          if (err) console.log(err);
          return done(err, user);
        });
      } else {
        return done(err, user);
      }
    })
  }
);
<commit_msg>adding twitter email.<commit_after>'use strict';

/**
 * Module dependencies.
 */

const mongoose = require('mongoose');
const TwitterStrategy = require('passport-twitter').Strategy;
const config = require('../../config');
const User = mongoose.model('User');
const extract = require('../../lib/extract');

/**
 * Expose
 */

module.exports = new TwitterStrategy({
    consumerKey: config.twitter.clientID,
    consumerSecret: config.twitter.clientSecret,
    callbackURL: config.twitter.callbackURL,
    includeEmail: true,
  },
  function (accessToken, refreshToken, profile, done) {
    const options = {
      criteria: { "twitter_id": profile.id }
    };
    User.load(options, function (err, user) {
      if (err) return done(err);

      if (!user) {
        let user = new User({
          name: profile.displayName,
          username: profile.username,
          email: profile._json.email,
          provider: 'twitter',
          twitter_id: profile.id,
          twitter: profile._json
        });
        //pulling profile images
        // if (user.twitter && user.twitter.profile_image_url){
        //   const uID = user._id;
        //   const defaultImageURL = user.twitter.profile_image_url;
        //   const defaultImageUID =  'profile_images/default_'+ uID;
        //   extract.upload(defaultImageURL, defaultImageUID, function(err, url, dimensions){
        //     if (err) return console.log(err, 'TwitterStrategy Auth');
        //     user.profile_image = url;
        //     user.save();
        //   });
        //   const largeImageURL = user.twitter.profile_image_url.replace('_normal.jpg', '.jpg');
        //   const largeImageUID =  'profile_images/large_'+ uID;
        //   extract.upload(largeImageURL, largeImageUID, function(err, url, dimensions){
        //     if (err) return console.log(err, 'TwitterStrategy Auth');
        //     user.profile_image_large = url;
        //     user.save();
        //   });
        // }

        user.save(function (err) {
          if (err) console.log(err);
          return done(err, user);
        });
      } else {
        return done(err, user);
      }
    })
  }
);
<|endoftext|>


<commit_before>//selection logic separated into another class for clarity, but included in this file for simplicity
var SelectionManager = L.Class.extend({

	initialize: function (map) {
		this._map = map;
	},

	getSelectedMarkers: function() {
		var markers = [];
		Object.keys(this._allSelectedMarkers).sort().forEach(function(markerId) { //sort is just so the order is predictable, makes debugging easier
			var marker = this._allSelectedMarkers[markerId];
			if (marker instanceof L.MarkerCluster) { //the actual cluster icon itself
				markers = markers.concat(marker.getAllChildMarkers());
			} else { // standard marker hopefully
				markers.push(marker);
			}
		}.bind(this));
		return markers;
	},

	enable: function() {
		this._clearExistingSelection();
	},

	_clearExistingSelection: function() {
		this._allSelectedMarkers = {};
		this._previouslySelectedMarkers = [];
		this._currentlySelectedMarkers = [];
	},

	disable: function() {
		for (key in this._allSelectedMarkers) {
			var marker = this._allSelectedMarkers[key];
			this._toggleSelection(marker, false);
		}
		this._clearExistingSelection();
	},

	start: function() {
		this._currentlySelectedMarkers = [];
		this._findAllLoadedMarkers();
	},

	_findAllLoadedMarkers: function() {
		this._allLoadedMarkers = {};
		var fakeBounds = {contains: function() {return true;}};//TODO refactor enumerate to remove this
		this._enumerateMarkers(fakeBounds, this._map._layers, function(marker) {
			var key = L.Util.stamp(marker);
			this._allLoadedMarkers[key] = marker; // include the cluster icons, as those are what we want to highlight
		}.bind(this));
	},

	update: function(latLngBounds) {
		var newlySelectedMarkers = {};
		this._enumerateMarkers(latLngBounds, this._map._layers, function(marker) {
			var key = L.Util.stamp(marker);
			newlySelectedMarkers[key] = marker;
		}.bind(this));
		var mismatches = this._outerJoinArrays(Object.keys(this._currentlySelectedMarkers), Object.keys(newlySelectedMarkers));
		var idToSelect = mismatches.in2ButNot1;
		var idToDeselect = mismatches.in1ButNot2;
		for (var i = 0; i < idToSelect.length; i++) {
			var marker = this._allLoadedMarkers[idToSelect[i]];
			this._toggleSelection(marker, true);
		}
		for (var i = 0; i < idToDeselect.length; i++) {
			//if this marker was selected in a previous run, don't de-select it now
			if (this._previouslySelectedMarkers.indexOf(idToDeselect[i]) == -1) {
				var marker = this._allLoadedMarkers[idToDeselect[i]];
				this._toggleSelection(marker, false);
			}
		}
		this._currentlySelectedMarkers = newlySelectedMarkers;
	},

	finish: function(bounds) {
		this._enumerateMarkers(bounds, this._map._layers, function(marker) {
			if (this._allSelectedMarkers == null) {
				this._allSelectedMarkers = {};
			}
			var key = L.Util.stamp(marker);
			this._allSelectedMarkers[key] = marker; //may already be there, but just override it
		}.bind(this));
		this._previouslySelectedMarkers = this._previouslySelectedMarkers.concat(Object.keys(this._currentlySelectedMarkers));
	},

	_isIgnoredLayer: function(layer) {
		//it isn't obvious that there's a clever way to work out if we should ignore a layer, so for now we'll just have to have a list of 'known bad' layer types
		//sub groups (https://github.com/ghybs/Leaflet.FeatureGroup.SubGroup) essentially duplicate the markers and don't appear on the UI, so ignore them
		if (L.FeatureGroup.SubGroup != undefined) {
			return layer instanceof L.FeatureGroup.SubGroup;
		} else {
			return false;
		}
	},

	_enumerateMarkers: function(bounds, layers, callback) {
		Object.keys(layers).forEach(function (key) {
			var layer = layers[key];
			if (!this._isIgnoredLayer(layer)) {
				if (layer instanceof L.Marker) {
					if (bounds.contains(layer.getLatLng())) {
						callback(layer);
					}
				} else if (layer._layers != undefined) {
					this._enumerateMarkers(bounds, layer._layers, callback);
				}
			}
		}, this);
	},

	_toggleSelection(marker, select) {
		if (select) {
			L.DomUtil.addClass(marker._icon, 'marker-highlight');
		} else {
			L.DomUtil.removeClass(marker._icon, 'marker-highlight');
		}
	},

	//both arrays must contain only numbers
	_outerJoinArrays: function (arr1, arr2) {
		function numericalSort(a, b) {
			return a - b;
		}
		arr1 = arr1.sort(numericalSort);
		arr2 = arr2.sort(numericalSort);
		var i = 0;
		var j = 0;
		var in1ButNot2 = [];
		var in2ButNot1 = [];
		while (i < arr1.length || j < arr2.length) {
			if (i == arr1.length) {
				//run out of array 1, so everything left in array 2 must be missing from 1
				in2ButNot1.push(arr2[j]); //TODO could just merge the arrays to be more efficient here
				j++;
			} else if (j == arr2.length) {
				//run out of array 2, so everything left in array 1 must be missing from 2
				in1ButNot2.push(arr1[i]); //TODO could just merge the arrays to be more efficient here
				i++;
			} else if (arr1[i] == arr2[j]) {
				//in both, move on
				i++;
				j++;
			} else if (arr1[i] < arr2[j]) {
				//must be missing from arr2
				in1ButNot2.push(arr1[i]);
				i++;
			} else {//if (arr1[i] > arr2[j]) {
				//must be missing from arr1
				in2ButNot1.push(arr2[j]);
				j++;
			}
		}
		if (i != arr1.length || j != arr2.length) {
			console.error("not all the things were consumed");
			console.error("arr1.length=" + arr1.length + ", i=" + i);
			console.error("arr2.length=" + arr2.length + ", j=" + j);
		}
		return {
			in1ButNot2: in1ButNot2,
			in2ButNot1: in2ButNot1
		};
	}
});

L.Control.BoxSelector = L.Control.extend({

	options: {
		highlightOnDrag: true //on by default but could cause poor performance if there is a large number of markers
	},

	initialize: function (options) {
		L.setOptions(this, options);
		if (this.options.actions == null) {
			//set default action
			this.options.actions = {
				alert: {
					display: "Display selected coords",
					action:	L.Control.BoxSelector.Actions.Alert
				}
			}
		}
	},

	onAdd: function (map) {
		this._map = map;
		this._mapcontainer = map._container;
		this._pane = map._panes.overlayPane;
		this._addHooks();
		this._manager = new SelectionManager(this._map);

		//set up select icon
		this._selectorContainer = L.DomUtil.create('div', 'leaflet-control-box-selector leaflet-bar leaflet-control boxselector-control boxselector-hidden');
		this._buttonBar = L.DomUtil.create('div', 'leaflet-bar-part leaflet-bar-part-single boxselector-button-bar', this._selectorContainer);
		this._toggleElement = L.DomUtil.create('a', 'boxselector-icon', this._buttonBar);
		this._toggleElement.id = 'boxselector-icon';
		this._toggleElement.href = '#';
		var iconWrapper = L.DomUtil.create('div', 'icon-wrapper', this._toggleElement);
		var icon = L.DomUtil.create('div', 'icon', iconWrapper);

		//set up dropdown icon
		var dropdownButton = L.DomUtil.create('a', 'boxselector-dropdown-button', this._buttonBar);
		dropdownButton.id = 'boxselector-dropdown-button';
		dropdownButton.href = '#';
		var dropdownIconWrapper = L.DomUtil.create('div', 'boxselector-dropdown-icon-wrapper', dropdownButton);
		var dropdownIcon = L.DomUtil.create('div', 'boxselector-dropdown-icon', dropdownIconWrapper);
		L.DomEvent.on(dropdownButton, 'click', this._expandCollapse, this)

		//set up drop down (hidden to start with)
		this._expanded = false;
		this._dropdown = L.DomUtil.create('div', 'boxselector-dropdown', this._selectorContainer);
		for (var actionId in this.options.actions) {
			var dropdownEntry = L.DomUtil.create('a', 'boxselector-dropdown-entry', this._dropdown);
			dropdownEntry.actionId = actionId;
			dropdownEntry.href = '#';
			var label = L.DomUtil.create('span', '', dropdownEntry);
			label.innerHTML = this.options.actions[actionId].display;
			label.actionId = actionId;

			L.DomEvent.on(dropdownEntry, 'click', this._actionItemClick, this);
		}

		L.DomEvent.on(this._toggleElement, 'click', this._toggle, this);

		//ensure we tear down
		this._map.on('unload', this._removeHooks, this);

		return this._selectorContainer;
	},

	_expandCollapse: function() {
		if (this._expanded) {
			L.DomUtil.addClass(this._selectorContainer, 'boxselector-hidden');
			L.DomUtil.removeClass(this._selectorContainer, 'boxselector-expanded');
			this._expanded = false;
		} else {
			L.DomUtil.addClass(this._selectorContainer, 'boxselector-expanded');
			L.DomUtil.removeClass(this._selectorContainer, 'boxselector-hidden');
			this._expanded = true;
		}
	},

	_actionItemClick: function(e) {
		var actionItemElement = e.target;
		var actionId = actionItemElement.actionId;
		var action = this.options.actions[actionId].action;
		this._expandCollapse();
		action(this.getSelectedMarkers());
	},

	_addHooks: function () {
		L.DomEvent.on(this._mapcontainer, 'mousedown', this._onMouseDown, this);
	},

	_removeHooks: function () {
		L.DomEvent.off(this._mapcontainer, 'mousedown', this._onMouseDown, this);
	},

	_toggle: function() {
		this._setEnabled(!this._isEnabled());
	},

	_isEnabled: function() {
		return this._selectionEnabled;
	},

	_setEnabled: function(enabled) {
		this._selectionEnabled = enabled;
		if (enabled) {
			//enable selection
			this._map.dragging.disable();
			L.DomUtil.addClass(this._buttonBar, 'selection-enabled');
			this._manager.enable();
		} else {
			//disable selection
			this._map.dragging.enable();
			L.DomUtil.removeClass(this._buttonBar, 'selection-enabled');
			this._manager.disable();
		}
	},

	_resetState: function () {
		this._moved = false;
	},

	_onMouseDown: function (e) {
		if (!this._isEnabled() || (e.button !== 0)) {
			return false;
		}

		this._resetState();

		L.DomUtil.disableTextSelection();
		L.DomUtil.disableImageDrag();

		this._startPoint = this._map.mouseEventToContainerPoint(e);

		L.DomEvent.on(document, {
			contextmenu: L.DomEvent.stop,
			mousemove: this._onMouseMove,
			mouseup: this._onMouseUp,
			keydown: this._onKeyDown
		}, this);

		this._manager.start();
	},

	_onMouseMove: function (e) {
		if (!this._moved) {
			this._moved = true;

			this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._mapcontainer);
			L.DomUtil.addClass(this._mapcontainer, 'leaflet-crosshair');
		}

		this._point = this._map.mouseEventToContainerPoint(e);

		var bounds = new L.Bounds(this._point, this._startPoint),
		    size = bounds.getSize();

		L.DomUtil.setPosition(this._box, bounds.min);

		this._box.style.width  = size.x + 'px';
		this._box.style.height = size.y + 'px';

		if (this.options.highlightOnDrag) {
			var latLngBounds = new L.LatLngBounds(
				this._map.containerPointToLatLng(this._startPoint),
				this._map.containerPointToLatLng(this._point));

			this._manager.update(latLngBounds);
		}
	},

	_finish: function () {
		if (this._moved) {
			L.DomUtil.remove(this._box);
			L.DomUtil.removeClass(this._mapcontainer, 'leaflet-crosshair');
		}

		L.DomUtil.enableTextSelection();
		L.DomUtil.enableImageDrag();

		L.DomEvent.off(document, {
			contextmenu: L.DomEvent.stop,
			mousemove: this._onMouseMove,
			mouseup: this._onMouseUp,
			keydown: this._onKeyDown
		}, this);
	},

	_onMouseUp: function (e) {
		if ((e.which !== 1) && (e.button !== 1)) { return; }

		this._finish();

		if (!this._moved) { return; }
		// Postpone to next JS tick so internal click event handling
		// still see it as "moved".
		setTimeout(L.bind(this._resetState, this), 0);

		var bounds = new L.LatLngBounds(
			this._map.containerPointToLatLng(this._startPoint),
			this._map.containerPointToLatLng(this._point));

		if (!this.options.highlightOnDrag) {
			//if we aren't highlighting on drag, fool the manager into updating
			this._manager.update(bounds);
		}
		this._manager.finish(bounds);
	},

	getSelectedMarkers: function() {
		return this._manager.getSelectedMarkers();
	},

	_onKeyDown: function (e) {
		if (e.keyCode === 27) {
			this._finish();
		}
	}
});
L.Control.BoxSelector.Actions = {};
L.Control.BoxSelector.Actions.Alert = function(selectedMarkers) {
	var output = "";
	for (var i = 0; i < selectedMarkers.length; i++) {
		var marker = selectedMarkers[i];
		output += marker.name
		output += ": ";
		output += marker.getLatLng().lat;
		output += ",";
		output += marker.getLatLng().lng;
		output += "\n";
	}
	alert(output);
}
<commit_msg> Fixing bug when marker cluster plugin not available<commit_after>//selection logic separated into another class for clarity, but included in this file for simplicity
var SelectionManager = L.Class.extend({

	initialize: function (map) {
		this._map = map;
	},

	getSelectedMarkers: function() {
		var markers = [];
		Object.keys(this._allSelectedMarkers).sort().forEach(function(markerId) { //sort is just so the order is predictable, makes debugging easier
			var marker = this._allSelectedMarkers[markerId];
			if (L.MarkerCluster != undefined && marker instanceof L.MarkerCluster) { //the actual cluster icon itself
				markers = markers.concat(marker.getAllChildMarkers());
			} else { // standard marker hopefully
				markers.push(marker);
			}
		}.bind(this));
		return markers;
	},

	enable: function() {
		this._clearExistingSelection();
	},

	_clearExistingSelection: function() {
		this._allSelectedMarkers = {};
		this._previouslySelectedMarkers = [];
		this._currentlySelectedMarkers = [];
	},

	disable: function() {
		for (key in this._allSelectedMarkers) {
			var marker = this._allSelectedMarkers[key];
			this._toggleSelection(marker, false);
		}
		this._clearExistingSelection();
	},

	start: function() {
		this._currentlySelectedMarkers = [];
		this._findAllLoadedMarkers();
	},

	_findAllLoadedMarkers: function() {
		this._allLoadedMarkers = {};
		var fakeBounds = {contains: function() {return true;}};//TODO refactor enumerate to remove this
		this._enumerateMarkers(fakeBounds, this._map._layers, function(marker) {
			var key = L.Util.stamp(marker);
			this._allLoadedMarkers[key] = marker; // include the cluster icons, as those are what we want to highlight
		}.bind(this));
	},

	update: function(latLngBounds) {
		var newlySelectedMarkers = {};
		this._enumerateMarkers(latLngBounds, this._map._layers, function(marker) {
			var key = L.Util.stamp(marker);
			newlySelectedMarkers[key] = marker;
		}.bind(this));
		var mismatches = this._outerJoinArrays(Object.keys(this._currentlySelectedMarkers), Object.keys(newlySelectedMarkers));
		var idToSelect = mismatches.in2ButNot1;
		var idToDeselect = mismatches.in1ButNot2;
		for (var i = 0; i < idToSelect.length; i++) {
			var marker = this._allLoadedMarkers[idToSelect[i]];
			this._toggleSelection(marker, true);
		}
		for (var i = 0; i < idToDeselect.length; i++) {
			//if this marker was selected in a previous run, don't de-select it now
			if (this._previouslySelectedMarkers.indexOf(idToDeselect[i]) == -1) {
				var marker = this._allLoadedMarkers[idToDeselect[i]];
				this._toggleSelection(marker, false);
			}
		}
		this._currentlySelectedMarkers = newlySelectedMarkers;
	},

	finish: function(bounds) {
		this._enumerateMarkers(bounds, this._map._layers, function(marker) {
			if (this._allSelectedMarkers == null) {
				this._allSelectedMarkers = {};
			}
			var key = L.Util.stamp(marker);
			this._allSelectedMarkers[key] = marker; //may already be there, but just override it
		}.bind(this));
		this._previouslySelectedMarkers = this._previouslySelectedMarkers.concat(Object.keys(this._currentlySelectedMarkers));
	},

	_isIgnoredLayer: function(layer) {
		//it isn't obvious that there's a clever way to work out if we should ignore a layer, so for now we'll just have to have a list of 'known bad' layer types
		//sub groups (https://github.com/ghybs/Leaflet.FeatureGroup.SubGroup) essentially duplicate the markers and don't appear on the UI, so ignore them
		if (L.FeatureGroup.SubGroup != undefined) {
			return layer instanceof L.FeatureGroup.SubGroup;
		} else {
			return false;
		}
	},

	_enumerateMarkers: function(bounds, layers, callback) {
		Object.keys(layers).forEach(function (key) {
			var layer = layers[key];
			if (!this._isIgnoredLayer(layer)) {
				if (layer instanceof L.Marker) {
					if (bounds.contains(layer.getLatLng())) {
						callback(layer);
					}
				} else if (layer._layers != undefined) {
					this._enumerateMarkers(bounds, layer._layers, callback);
				}
			}
		}, this);
	},

	_toggleSelection(marker, select) {
		if (select) {
			L.DomUtil.addClass(marker._icon, 'marker-highlight');
		} else {
			L.DomUtil.removeClass(marker._icon, 'marker-highlight');
		}
	},

	//both arrays must contain only numbers
	_outerJoinArrays: function (arr1, arr2) {
		function numericalSort(a, b) {
			return a - b;
		}
		arr1 = arr1.sort(numericalSort);
		arr2 = arr2.sort(numericalSort);
		var i = 0;
		var j = 0;
		var in1ButNot2 = [];
		var in2ButNot1 = [];
		while (i < arr1.length || j < arr2.length) {
			if (i == arr1.length) {
				//run out of array 1, so everything left in array 2 must be missing from 1
				in2ButNot1.push(arr2[j]); //TODO could just merge the arrays to be more efficient here
				j++;
			} else if (j == arr2.length) {
				//run out of array 2, so everything left in array 1 must be missing from 2
				in1ButNot2.push(arr1[i]); //TODO could just merge the arrays to be more efficient here
				i++;
			} else if (arr1[i] == arr2[j]) {
				//in both, move on
				i++;
				j++;
			} else if (arr1[i] < arr2[j]) {
				//must be missing from arr2
				in1ButNot2.push(arr1[i]);
				i++;
			} else {//if (arr1[i] > arr2[j]) {
				//must be missing from arr1
				in2ButNot1.push(arr2[j]);
				j++;
			}
		}
		if (i != arr1.length || j != arr2.length) {
			console.error("not all the things were consumed");
			console.error("arr1.length=" + arr1.length + ", i=" + i);
			console.error("arr2.length=" + arr2.length + ", j=" + j);
		}
		return {
			in1ButNot2: in1ButNot2,
			in2ButNot1: in2ButNot1
		};
	}
});

L.Control.BoxSelector = L.Control.extend({

	options: {
		highlightOnDrag: true //on by default but could cause poor performance if there is a large number of markers
	},

	initialize: function (options) {
		L.setOptions(this, options);
		if (this.options.actions == null) {
			//set default action
			this.options.actions = {
				alert: {
					display: "Display selected coords",
					action:	L.Control.BoxSelector.Actions.Alert
				}
			}
		}
	},

	onAdd: function (map) {
		this._map = map;
		this._mapcontainer = map._container;
		this._pane = map._panes.overlayPane;
		this._addHooks();
		this._manager = new SelectionManager(this._map);

		//set up select icon
		this._selectorContainer = L.DomUtil.create('div', 'leaflet-control-box-selector leaflet-bar leaflet-control boxselector-control boxselector-hidden');
		this._buttonBar = L.DomUtil.create('div', 'leaflet-bar-part leaflet-bar-part-single boxselector-button-bar', this._selectorContainer);
		this._toggleElement = L.DomUtil.create('a', 'boxselector-icon', this._buttonBar);
		this._toggleElement.id = 'boxselector-icon';
		this._toggleElement.href = '#';
		var iconWrapper = L.DomUtil.create('div', 'icon-wrapper', this._toggleElement);
		var icon = L.DomUtil.create('div', 'icon', iconWrapper);

		//set up dropdown icon
		var dropdownButton = L.DomUtil.create('a', 'boxselector-dropdown-button', this._buttonBar);
		dropdownButton.id = 'boxselector-dropdown-button';
		dropdownButton.href = '#';
		var dropdownIconWrapper = L.DomUtil.create('div', 'boxselector-dropdown-icon-wrapper', dropdownButton);
		var dropdownIcon = L.DomUtil.create('div', 'boxselector-dropdown-icon', dropdownIconWrapper);
		L.DomEvent.on(dropdownButton, 'click', this._expandCollapse, this)

		//set up drop down (hidden to start with)
		this._expanded = false;
		this._dropdown = L.DomUtil.create('div', 'boxselector-dropdown', this._selectorContainer);
		for (var actionId in this.options.actions) {
			var dropdownEntry = L.DomUtil.create('a', 'boxselector-dropdown-entry', this._dropdown);
			dropdownEntry.actionId = actionId;
			dropdownEntry.href = '#';
			var label = L.DomUtil.create('span', '', dropdownEntry);
			label.innerHTML = this.options.actions[actionId].display;
			label.actionId = actionId;

			L.DomEvent.on(dropdownEntry, 'click', this._actionItemClick, this);
		}

		L.DomEvent.on(this._toggleElement, 'click', this._toggle, this);

		//ensure we tear down
		this._map.on('unload', this._removeHooks, this);

		return this._selectorContainer;
	},

	_expandCollapse: function() {
		if (this._expanded) {
			L.DomUtil.addClass(this._selectorContainer, 'boxselector-hidden');
			L.DomUtil.removeClass(this._selectorContainer, 'boxselector-expanded');
			this._expanded = false;
		} else {
			L.DomUtil.addClass(this._selectorContainer, 'boxselector-expanded');
			L.DomUtil.removeClass(this._selectorContainer, 'boxselector-hidden');
			this._expanded = true;
		}
	},

	_actionItemClick: function(e) {
		var actionItemElement = e.target;
		var actionId = actionItemElement.actionId;
		var action = this.options.actions[actionId].action;
		this._expandCollapse();
		action(this.getSelectedMarkers());
	},

	_addHooks: function () {
		L.DomEvent.on(this._mapcontainer, 'mousedown', this._onMouseDown, this);
	},

	_removeHooks: function () {
		L.DomEvent.off(this._mapcontainer, 'mousedown', this._onMouseDown, this);
	},

	_toggle: function() {
		this._setEnabled(!this._isEnabled());
	},

	_isEnabled: function() {
		return this._selectionEnabled;
	},

	_setEnabled: function(enabled) {
		this._selectionEnabled = enabled;
		if (enabled) {
			//enable selection
			this._map.dragging.disable();
			L.DomUtil.addClass(this._buttonBar, 'selection-enabled');
			this._manager.enable();
		} else {
			//disable selection
			this._map.dragging.enable();
			L.DomUtil.removeClass(this._buttonBar, 'selection-enabled');
			this._manager.disable();
		}
	},

	_resetState: function () {
		this._moved = false;
	},

	_onMouseDown: function (e) {
		if (!this._isEnabled() || (e.button !== 0)) {
			return false;
		}

		this._resetState();

		L.DomUtil.disableTextSelection();
		L.DomUtil.disableImageDrag();

		this._startPoint = this._map.mouseEventToContainerPoint(e);

		L.DomEvent.on(document, {
			contextmenu: L.DomEvent.stop,
			mousemove: this._onMouseMove,
			mouseup: this._onMouseUp,
			keydown: this._onKeyDown
		}, this);

		this._manager.start();
	},

	_onMouseMove: function (e) {
		if (!this._moved) {
			this._moved = true;

			this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._mapcontainer);
			L.DomUtil.addClass(this._mapcontainer, 'leaflet-crosshair');
		}

		this._point = this._map.mouseEventToContainerPoint(e);

		var bounds = new L.Bounds(this._point, this._startPoint),
		    size = bounds.getSize();

		L.DomUtil.setPosition(this._box, bounds.min);

		this._box.style.width  = size.x + 'px';
		this._box.style.height = size.y + 'px';

		if (this.options.highlightOnDrag) {
			var latLngBounds = new L.LatLngBounds(
				this._map.containerPointToLatLng(this._startPoint),
				this._map.containerPointToLatLng(this._point));

			this._manager.update(latLngBounds);
		}
	},

	_finish: function () {
		if (this._moved) {
			L.DomUtil.remove(this._box);
			L.DomUtil.removeClass(this._mapcontainer, 'leaflet-crosshair');
		}

		L.DomUtil.enableTextSelection();
		L.DomUtil.enableImageDrag();

		L.DomEvent.off(document, {
			contextmenu: L.DomEvent.stop,
			mousemove: this._onMouseMove,
			mouseup: this._onMouseUp,
			keydown: this._onKeyDown
		}, this);
	},

	_onMouseUp: function (e) {
		if ((e.which !== 1) && (e.button !== 1)) { return; }

		this._finish();

		if (!this._moved) { return; }
		// Postpone to next JS tick so internal click event handling
		// still see it as "moved".
		setTimeout(L.bind(this._resetState, this), 0);

		var bounds = new L.LatLngBounds(
			this._map.containerPointToLatLng(this._startPoint),
			this._map.containerPointToLatLng(this._point));

		if (!this.options.highlightOnDrag) {
			//if we aren't highlighting on drag, fool the manager into updating
			this._manager.update(bounds);
		}
		this._manager.finish(bounds);
	},

	getSelectedMarkers: function() {
		return this._manager.getSelectedMarkers();
	},

	_onKeyDown: function (e) {
		if (e.keyCode === 27) {
			this._finish();
		}
	}
});
L.Control.BoxSelector.Actions = {};
L.Control.BoxSelector.Actions.Alert = function(selectedMarkers) {
	var output = "";
	for (var i = 0; i < selectedMarkers.length; i++) {
		var marker = selectedMarkers[i];
		output += marker.name
		output += ": ";
		output += marker.getLatLng().lat;
		output += ",";
		output += marker.getLatLng().lng;
		output += "\n";
	}
	alert(output);
}
<|endoftext|>


<commit_before>import AWS from 'aws-sdk';
import sinon from 'sinon';

export const ec2 = {
	describeInstances: () => {},
	startInstances: () => {}
};

sinon.stub(AWS, 'EC2').returns(ec2);
<commit_msg>fix tests with latest AVA<commit_after>'use strict';

var AWS = require('aws-sdk');
var sinon = require('sinon');

module.exports = {
	ec2: {
		describeInstances: function () {
			// do nothing
		},
		startInstances: function () {
			// do nothing
		}
	}
};

sinon.stub(AWS, 'EC2').returns(module.exports.ec2);
<|endoftext|>