<commit_before>goog.provide('ol.test.interaction.Modify');

describe('ol.interaction.Modify', function() {

  var target, map, source, features;

  var width = 360;
  var height = 180;

  beforeEach(function(done) {
    target = document.createElement('div');

    var style = target.style;
    style.position = 'absolute';
    style.left = '-1000px';
    style.top = '-1000px';
    style.width = width + 'px';
    style.height = height + 'px';
    document.body.appendChild(target);

    features = [
      new ol.Feature({
        geometry: new ol.geom.Polygon([
          [[0, 0], [10, 20], [0, 40], [40, 40], [40, 0]]
        ])
      })
    ];

    source = new ol.source.Vector({
      features: features
    });

    var layer = new ol.layer.Vector({source: source});

    map = new ol.Map({
      target: target,
      layers: [layer],
      view: new ol.View({
        projection: 'EPSG:4326',
        center: [0, 0],
        resolution: 1
      })
    });

    map.on('postrender', function() {
      done();
    });
  });

  afterEach(function() {
    goog.dispose(map);
    document.body.removeChild(target);
  });

  /**
   * Simulates a browser event on the map viewport.  The client x/y location
   * will be adjusted as if the map were centered at 0,0.
   * @param {string} type Event type.
   * @param {number} x Horizontal offset from map center.
   * @param {number} y Vertical offset from map center.
   * @param {boolean=} opt_shiftKey Shift key is pressed.
   */
  function simulateEvent(type, x, y, opt_shiftKey, button) {
    var viewport = map.getViewport();
    // calculated in case body has top < 0 (test runner with small window)
    var position = goog.style.getClientPosition(viewport);
    var shiftKey = goog.isDef(opt_shiftKey) ? opt_shiftKey : false;
    var event = new ol.MapBrowserPointerEvent(type, map,
        new ol.pointer.PointerEvent(type,
            new goog.events.BrowserEvent({
              type: type,
              button: button,
              clientX: position.x + x + width / 2,
              clientY: position.y + y + height / 2,
              shiftKey: shiftKey
            })));
    event.pointerEvent.pointerId = 1;
    map.handleMapBrowserEvent(event);
  }

  /**
   * Tracks events triggered by the interaction as well as feature
   * modifications. Helper function to
   * @param {ol.Feature} feature Modified feature.
   * @param {ol.interaction.Modify} interaction
   * @return {Array<ol.ModifyEvent|string>} events
   */
  function trackEvents(feature, interaction) {
    var events = [];
    feature.on('change', function(event) {
      events.push('change');
    });
    interaction.on('modifystart', function(event) {
      events.push(event);
    });
    interaction.on('modifyend', function(event) {
      events.push(event);
    });
    return events;
  }

  /**
  * Validates the event array to verify proper event sequence. Checks
  * that first and last event are correct ModifyEvents and that feature
  * modifications event are in between.
  * @param {Array<ol.ModifyEvent|string>} event
  * @param {Array<ol.Feature>} features
  */
  function validateEvents(events, features) {

    var startevent = events[0];
    var endevent = events[events.length - 1];

    // first event should be modifystary
    expect(startevent).to.be.an(ol.ModifyEvent);
    expect(startevent.type).to.eql('modifystart');

    // last event should be modifyend
    expect(endevent).to.be.an(ol.ModifyEvent);
    expect(endevent.type).to.eql('modifyend');

    // make sure we get change events to events array
    expect(events.length > 2).to.be(true);
    // middle events should be feature modification events
    for (var i = 1; i < events.length - 2; i++) {
      expect(events[i]).to.equal('change');
    }

    // ModifyEvents should include the expected features
    expect(startevent.features.getArray()).to.eql(features);
    expect(endevent.features.getArray()).to.eql(features);
  }

  describe('constructor', function() {
    it('adds features to the RTree', function() {
      var feature = new ol.Feature(
          new ol.geom.Point([0, 0]));
      var features = new ol.Collection([feature]);
      var modify = new ol.interaction.Modify({
        features: features
      });
      var rbushEntries = modify.rBush_.getAll();
      expect(rbushEntries.length).to.be(1);
      expect(rbushEntries[0].feature).to.be(feature);
    });
  });

  describe('vertex deletion', function() {

    it('works when clicking on a shared vertex', function() {
      features.push(features[0].clone());

      var modify = new ol.interaction.Modify({
        features: new ol.Collection(features)
      });
      map.addInteraction(modify);

      var first = features[0];
      var second = features[0];

      events = trackEvents(first, modify);

      expect(first.getGeometry().getRevision()).to.equal(1);
      expect(first.getGeometry().getCoordinates()[0]).to.have.length(5);
      expect(second.getGeometry().getRevision()).to.equal(1);
      expect(second.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('singleclick', 10, -20, false, 0);

      expect(first.getGeometry().getRevision()).to.equal(2);
      expect(first.getGeometry().getCoordinates()[0]).to.have.length(4);
      expect(second.getGeometry().getRevision()).to.equal(2);
      expect(second.getGeometry().getCoordinates()[0]).to.have.length(4);

      validateEvents(events, features);
    });

  });

  describe('boundary modification', function() {
    var modify, feature, events;

    beforeEach(function() {
      modify = new ol.interaction.Modify({
        features: new ol.Collection(features)
      });
      map.addInteraction(modify);

      feature = features[0];

      events = trackEvents(feature, modify);
    });

    it('clicking vertex should delete it and +r1', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('singleclick', 10, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(4);

      validateEvents(events, [feature]);
    });

    it('single clicking boundary should add vertex and +r1', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointerup', 40, -20, false, 0);
      simulateEvent('click', 40, -20, false, 0);
      simulateEvent('singleclick', 40, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(6);

      validateEvents(events, [feature]);
    });

    it('single clicking on created vertex should delete it again', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointerup', 40, -20, false, 0);
      simulateEvent('click', 40, -20, false, 0);
      simulateEvent('singleclick', 40, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(6);

      validateEvents(events, [feature]);
      events.length = 0;

      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointerup', 40, -20, false, 0);
      simulateEvent('click', 40, -20, false, 0);
      simulateEvent('singleclick', 40, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(3);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      validateEvents(events, [feature]);
    });

    it('clicking with drag should add vertex and +r3', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointermove', 40, -20, false, 0);
      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointermove', 30, -20, false, 0);
      simulateEvent('pointerdrag', 30, -20, false, 0);
      simulateEvent('pointerup', 30, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(4);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(6);

      validateEvents(events, [feature]);
    });
  });

  describe('double click deleteCondition', function() {

    var modify, feature, events;

    beforeEach(function() {
      modify = new ol.interaction.Modify({
        features: new ol.Collection(features),
        deleteCondition: ol.events.condition.doubleClick
      });
      map.addInteraction(modify);

      feature = features[0];

      events = trackEvents(feature, modify);
    });

    it('should delete vertex on double click', function() {

      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('dblclick', 10, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(4);

      validateEvents(events, features);
    });

    it('should do nothing on single click', function() {

      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('singleclick', 10, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      expect(events.length).to.eql(0);
    });
  });

  describe('handle feature change', function() {
    var getListeners;

    beforeEach(function() {
      getListeners = function(feature, modify) {
        var listeners = goog.events.getListeners(
            feature, goog.events.EventType.CHANGE, false);
        return goog.array.filter(listeners, function(listener) {
          return listener.handler == modify;
        });
      };
    });

    it('updates the segment data', function() {
      var modify = new ol.interaction.Modify({
        features: new ol.Collection(features)
      });
      map.addInteraction(modify);

      var feature = features[0];
      var listeners, listener;

      listeners = getListeners(feature, modify);
      expect(listeners).to.have.length(1);

      var firstSegmentData;

      firstSegmentData = modify.rBush_.forEachInExtent([0, 0, 5, 5],
          function(node) {
            return node;
          });
      expect(firstSegmentData.segment[0]).to.eql([0, 0]);
      expect(firstSegmentData.segment[1]).to.eql([10, 20]);

      var coordinates = feature.getGeometry().getCoordinates();
      var firstVertex = coordinates[0][0];
      firstVertex[0] = 1;
      firstVertex[1] = 1;
      feature.getGeometry().setCoordinates(coordinates);

      firstSegmentData = modify.rBush_.forEachInExtent([0, 0, 5, 5],
          function(node) {
            return node;
          });
      expect(firstSegmentData.segment[0]).to.eql([1, 1]);
      expect(firstSegmentData.segment[1]).to.eql([10, 20]);

      listeners = getListeners(feature, modify);
      expect(listeners).to.have.length(1);
    });
  });

});

goog.require('goog.array');
goog.require('goog.dispose');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.BrowserEvent');
goog.require('goog.style');
goog.require('ol.Collection');
goog.require('ol.Feature');
goog.require('ol.Map');
goog.require('ol.MapBrowserPointerEvent');
goog.require('ol.View');
goog.require('ol.events.condition');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Modify');
goog.require('ol.layer.Vector');
goog.require('ol.pointer.PointerEvent');
goog.require('ol.source.Vector');
<commit_msg>Fix miswritten test<commit_after>goog.provide('ol.test.interaction.Modify');

describe('ol.interaction.Modify', function() {

  var target, map, source, features;

  var width = 360;
  var height = 180;

  beforeEach(function(done) {
    target = document.createElement('div');

    var style = target.style;
    style.position = 'absolute';
    style.left = '-1000px';
    style.top = '-1000px';
    style.width = width + 'px';
    style.height = height + 'px';
    document.body.appendChild(target);

    features = [
      new ol.Feature({
        geometry: new ol.geom.Polygon([
          [[0, 0], [10, 20], [0, 40], [40, 40], [40, 0]]
        ])
      })
    ];

    source = new ol.source.Vector({
      features: features
    });

    var layer = new ol.layer.Vector({source: source});

    map = new ol.Map({
      target: target,
      layers: [layer],
      view: new ol.View({
        projection: 'EPSG:4326',
        center: [0, 0],
        resolution: 1
      })
    });

    map.on('postrender', function() {
      done();
    });
  });

  afterEach(function() {
    goog.dispose(map);
    document.body.removeChild(target);
  });

  /**
   * Simulates a browser event on the map viewport.  The client x/y location
   * will be adjusted as if the map were centered at 0,0.
   * @param {string} type Event type.
   * @param {number} x Horizontal offset from map center.
   * @param {number} y Vertical offset from map center.
   * @param {boolean=} opt_shiftKey Shift key is pressed.
   */
  function simulateEvent(type, x, y, opt_shiftKey, button) {
    var viewport = map.getViewport();
    // calculated in case body has top < 0 (test runner with small window)
    var position = goog.style.getClientPosition(viewport);
    var shiftKey = goog.isDef(opt_shiftKey) ? opt_shiftKey : false;
    var event = new ol.MapBrowserPointerEvent(type, map,
        new ol.pointer.PointerEvent(type,
            new goog.events.BrowserEvent({
              type: type,
              button: button,
              clientX: position.x + x + width / 2,
              clientY: position.y + y + height / 2,
              shiftKey: shiftKey
            })));
    event.pointerEvent.pointerId = 1;
    map.handleMapBrowserEvent(event);
  }

  /**
   * Tracks events triggered by the interaction as well as feature
   * modifications. Helper function to
   * @param {ol.Feature} feature Modified feature.
   * @param {ol.interaction.Modify} interaction
   * @return {Array<ol.ModifyEvent|string>} events
   */
  function trackEvents(feature, interaction) {
    var events = [];
    feature.on('change', function(event) {
      events.push('change');
    });
    interaction.on('modifystart', function(event) {
      events.push(event);
    });
    interaction.on('modifyend', function(event) {
      events.push(event);
    });
    return events;
  }

  /**
  * Validates the event array to verify proper event sequence. Checks
  * that first and last event are correct ModifyEvents and that feature
  * modifications event are in between.
  * @param {Array<ol.ModifyEvent|string>} event
  * @param {Array<ol.Feature>} features
  */
  function validateEvents(events, features) {

    var startevent = events[0];
    var endevent = events[events.length - 1];

    // first event should be modifystary
    expect(startevent).to.be.an(ol.ModifyEvent);
    expect(startevent.type).to.eql('modifystart');

    // last event should be modifyend
    expect(endevent).to.be.an(ol.ModifyEvent);
    expect(endevent.type).to.eql('modifyend');

    // make sure we get change events to events array
    expect(events.length > 2).to.be(true);
    // middle events should be feature modification events
    for (var i = 1; i < events.length - 2; i++) {
      expect(events[i]).to.equal('change');
    }

    // ModifyEvents should include the expected features
    expect(startevent.features.getArray()).to.eql(features);
    expect(endevent.features.getArray()).to.eql(features);
  }

  describe('constructor', function() {
    it('adds features to the RTree', function() {
      var feature = new ol.Feature(
          new ol.geom.Point([0, 0]));
      var features = new ol.Collection([feature]);
      var modify = new ol.interaction.Modify({
        features: features
      });
      var rbushEntries = modify.rBush_.getAll();
      expect(rbushEntries.length).to.be(1);
      expect(rbushEntries[0].feature).to.be(feature);
    });
  });

  describe('vertex deletion', function() {

    it('works when clicking on a shared vertex', function() {
      features.push(features[0].clone());

      var modify = new ol.interaction.Modify({
        features: new ol.Collection(features)
      });
      map.addInteraction(modify);

      var first = features[0];
      var second = features[1];

      events = trackEvents(first, modify);

      expect(first.getGeometry().getRevision()).to.equal(1);
      expect(first.getGeometry().getCoordinates()[0]).to.have.length(5);
      expect(second.getGeometry().getRevision()).to.equal(2);
      expect(second.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('singleclick', 10, -20, false, 0);

      expect(first.getGeometry().getRevision()).to.equal(2);
      expect(first.getGeometry().getCoordinates()[0]).to.have.length(4);
      expect(second.getGeometry().getRevision()).to.equal(3);
      expect(second.getGeometry().getCoordinates()[0]).to.have.length(4);

      validateEvents(events, features);
    });

  });

  describe('boundary modification', function() {
    var modify, feature, events;

    beforeEach(function() {
      modify = new ol.interaction.Modify({
        features: new ol.Collection(features)
      });
      map.addInteraction(modify);

      feature = features[0];

      events = trackEvents(feature, modify);
    });

    it('clicking vertex should delete it and +r1', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('singleclick', 10, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(4);

      validateEvents(events, [feature]);
    });

    it('single clicking boundary should add vertex and +r1', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointerup', 40, -20, false, 0);
      simulateEvent('click', 40, -20, false, 0);
      simulateEvent('singleclick', 40, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(6);

      validateEvents(events, [feature]);
    });

    it('single clicking on created vertex should delete it again', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointerup', 40, -20, false, 0);
      simulateEvent('click', 40, -20, false, 0);
      simulateEvent('singleclick', 40, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(6);

      validateEvents(events, [feature]);
      events.length = 0;

      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointerup', 40, -20, false, 0);
      simulateEvent('click', 40, -20, false, 0);
      simulateEvent('singleclick', 40, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(3);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      validateEvents(events, [feature]);
    });

    it('clicking with drag should add vertex and +r3', function() {
      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointermove', 40, -20, false, 0);
      simulateEvent('pointerdown', 40, -20, false, 0);
      simulateEvent('pointermove', 30, -20, false, 0);
      simulateEvent('pointerdrag', 30, -20, false, 0);
      simulateEvent('pointerup', 30, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(4);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(6);

      validateEvents(events, [feature]);
    });
  });

  describe('double click deleteCondition', function() {

    var modify, feature, events;

    beforeEach(function() {
      modify = new ol.interaction.Modify({
        features: new ol.Collection(features),
        deleteCondition: ol.events.condition.doubleClick
      });
      map.addInteraction(modify);

      feature = features[0];

      events = trackEvents(feature, modify);
    });

    it('should delete vertex on double click', function() {

      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('dblclick', 10, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(2);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(4);

      validateEvents(events, features);
    });

    it('should do nothing on single click', function() {

      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      simulateEvent('pointerdown', 10, -20, false, 0);
      simulateEvent('pointerup', 10, -20, false, 0);
      simulateEvent('click', 10, -20, false, 0);
      simulateEvent('singleclick', 10, -20, false, 0);

      expect(feature.getGeometry().getRevision()).to.equal(1);
      expect(feature.getGeometry().getCoordinates()[0]).to.have.length(5);

      expect(events.length).to.eql(0);
    });
  });

  describe('handle feature change', function() {
    var getListeners;

    beforeEach(function() {
      getListeners = function(feature, modify) {
        var listeners = goog.events.getListeners(
            feature, goog.events.EventType.CHANGE, false);
        return goog.array.filter(listeners, function(listener) {
          return listener.handler == modify;
        });
      };
    });

    it('updates the segment data', function() {
      var modify = new ol.interaction.Modify({
        features: new ol.Collection(features)
      });
      map.addInteraction(modify);

      var feature = features[0];
      var listeners, listener;

      listeners = getListeners(feature, modify);
      expect(listeners).to.have.length(1);

      var firstSegmentData;

      firstSegmentData = modify.rBush_.forEachInExtent([0, 0, 5, 5],
          function(node) {
            return node;
          });
      expect(firstSegmentData.segment[0]).to.eql([0, 0]);
      expect(firstSegmentData.segment[1]).to.eql([10, 20]);

      var coordinates = feature.getGeometry().getCoordinates();
      var firstVertex = coordinates[0][0];
      firstVertex[0] = 1;
      firstVertex[1] = 1;
      feature.getGeometry().setCoordinates(coordinates);

      firstSegmentData = modify.rBush_.forEachInExtent([0, 0, 5, 5],
          function(node) {
            return node;
          });
      expect(firstSegmentData.segment[0]).to.eql([1, 1]);
      expect(firstSegmentData.segment[1]).to.eql([10, 20]);

      listeners = getListeners(feature, modify);
      expect(listeners).to.have.length(1);
    });
  });

});

goog.require('goog.array');
goog.require('goog.dispose');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.events.BrowserEvent');
goog.require('goog.style');
goog.require('ol.Collection');
goog.require('ol.Feature');
goog.require('ol.Map');
goog.require('ol.MapBrowserPointerEvent');
goog.require('ol.View');
goog.require('ol.events.condition');
goog.require('ol.geom.Point');
goog.require('ol.geom.Polygon');
goog.require('ol.interaction.Modify');
goog.require('ol.layer.Vector');
goog.require('ol.pointer.PointerEvent');
goog.require('ol.source.Vector');
<|endoftext|>


<commit_before>// Category View
// =============

// Includes file dependencies
define([
	"jquery",
	"backbone",
    "moment",
    "../models/ExperienceNotation",
    "../collections/ExperienceNotationCollection",
    "mobiledatepicker",
    "../collections/VampireChangeCollection"
], function( $, Backbone, moment, ExperienceNotation, ExperienceNotationCollection, mobiledatepicker, VampireChangeCollection) {

    // Extends Backbone.View
    var View = Backbone.View.extend( {

        // The View Constructor
        initialize: function() {
            var self = this;

            self.changes = new VampireChangeCollection;
            self.listenTo(self.changes, "add", self.render);
            self.listenTo(self.changes, "reset", self.render);

            self.start = 0;
            self.changeBy = 10;
        },

        register: function(character, start, changeBy) {
            var self = this;
            var p = Parse.Promise.as([]);

            if (character !== self.character) {
                if (self.character) {
                    self.stopListening(self.character);
                    self.stopListening(self.collection);
                }
                self.character = character;
                p = self.character.get_experience_notations(function (rc) {
                    self.listenTo(rc, "add", self.render);
                    self.listenTo(rc, "reset", self.render);
                    self.listenTo(rc, "remove", self.render);
                    self.listenTo(self.character, "begin_experience_notation_propagation", function() {
                        $.mobile.loading("show");
                    });
                    self.listenTo(self.character, "finish_experience_notation_propagation", function() {
                        self.render();
                        $.mobile.loading("hide");
                    })
                    self.collection = rc;
                });
            }

            return p.then(function () {
                Parse.Promise.as(self);
            });
        },

        events: {
            "click .previous": "previous",
            "click .next": "next",
            "click .add": "add",
            "click .experience-notation-edit": "edit_experience_notation",
            "submit #edit-entered-popup-form": "submit_experience_notation_entered",
            "submit #edit-reason-popup-form": "submit_experience_notation_reason",
            "submit #edit-alteration-popup-form": "submit_experience_notation_alteration",
            "click .experience-notation-delete": "delete_experience_notation"
        },

        submit_experience_notation_entered: function(event, a, b, c, d) {
            var self = this;
            event.preventDefault();
            var id = self.$("#popupEditEntered #date-id").val();
            var d = self.$("#popupEditEntered #date-input").val();
            var en = self.collection.getByCid(id);
            var updatedEntered = moment(d);
            if (updatedEntered.isValid()) {
                en.set("entered", updatedEntered.toDate());
                en.save();
                $("#popupEditEntered").popup("close");
            } else {
                // Can't do validation this way because then we would have to watch for
                // change to update the state ourselves
                //self.$("#popupEditEntered #date-input")[0].setCustomValidity("Can't parse date and/or time input");
            }
        },

        submit_experience_notation_reason: function(event) {
            var self = this;
            event.preventDefault();
            var id = self.$("#popupEditReason #reason-id").val();
            var txt = self.$("#popupEditReason #reason-input").val();
            var en = self.collection.getByCid(id);
            en.set("reason", txt);
            en.save();
            $("#popupEditReason").popup("close");
        },

        submit_experience_notation_alteration: function(event) {
            var self = this;
            event.preventDefault();
            var id = self.$("#alterationpopupEdit #alteration-id").val();
            var n = _.parseInt(self.$("#alterationpopupEdit #alteration-input").val());
            var en = self.collection.getByCid(id);
            var type = self.$("#alterationpopupEdit #alteration-type").val();
            en.set("alteration_" + type, n);
            en.save();
            $("#alterationpopupEdit").popup("close");
        },

        edit_experience_notation: function(event) {
            var self = this;
            var t = self.$(event.target);
            var clickedNotationId = t.attr("notation-id");
            var headerName = t.attr("header");
            var en = self.collection.getByCid(clickedNotationId);
            event.preventDefault();
            if ("entered" === headerName) {
                var popup = $("#popupEditEntered");
                $("#popupEditEntered #date-input").val(moment(en.get("entered")).format());
                //self.$("#popupEditEntered #date-input")[0].setCustomValidity("");
                $("#popupEditEntered #date-id").val(clickedNotationId);
                popup.enhanceWithin().popup("open");
            } else if ("reason" === headerName) {
                var popup = $("#popupEditReason");
                $("#popupEditReason #reason-input").val(en.get("reason"));
                $("#popupEditReason #reason-id").val(clickedNotationId);
                popup.enhanceWithin().popup("open");
            } else if ("alteration_spent" === headerName) {
                var popup = $("#alterationpopupEdit");
                $("#alterationpopupEdit #alteration-input").val(en.get("alteration_spent"));
                $("#alterationpopupEdit #alteration-id").val(clickedNotationId);
                $("#alterationpopupEdit #alteration-type").val("spent");
                popup.enhanceWithin().popup("open");
            } else if ("alteration_earned" === headerName) {
                var popup = $("#alterationpopupEdit");
                $("#alterationpopupEdit #alteration-input").val(en.get("alteration_earned"));
                $("#alterationpopupEdit #alteration-id").val(clickedNotationId);
                $("#alterationpopupEdit #alteration-type").val("earned");
                popup.enhanceWithin().popup("open");
            }
        },

        delete_experience_notation: function(event) {
            event.preventDefault();
            var self = this;
            var t = self.$(event.target);
            var clickedNotationId = t.attr("notation-id");
            var cidgot = self.collection.getByCid(clickedNotationId);
            var idgot = self.collection.get(clickedNotationId);
            var en = self.collection.getByCid(clickedNotationId) || self.collection.get(clickedNotationId);
            if (!en) {
                return self;
            }
            self.collection.remove(en);
            en.destroy();
        },

        previous: function() {
            var self = this;
            var incr = this.start - this.changeBy;
            this.start = _.max([0, incr]);
            window.location.hash = "#character/" + self.character.id + "/experience/" + this.start + "/10";
            $.mobile.loading("show");
            this.update_collection_query_and_fetch().then(function() {
                $.mobile.loading("hide");
            })
        },

        next: function() {
            var self = this;
            this.start += self.changeBy;
            window.location.hash = "#character/" + self.character.id + "/experience/" + this.start + "/10";
            $.mobile.loading("show");
            this.update_collection_query_and_fetch().then(function() {
                $.mobile.loading("hide");
            })
        },

        add: function() {
            var self = this;
            var en = new ExperienceNotation({
                entered: new Date,
                reason: "Unspecified reason",
                earned: 0,
                spent: 0,
                alteration_earned: 0,
                alteration_spent: 0,
                owner: self.character,
            });
            en.setACL(self.character.get_me_acl());
            self.collection.add(en);
            en.save();
        },

        update_collection_query_and_fetch: function () {
            var self = this;
            var options = {reset: true};
            var q = new Parse.Query(ExperienceNotation);
            q.equalTo("owner", self.character).addDescending("entered").addDescending("createdAt");
            /*
            q.skip(self.start);
            q.limit(self.changeBy);
            */
            self.collection.query = q;
            return self.collection.fetch(options).then(function () {
                var q = new Parse.Query(VampireChange);
                q.equalTo("owner", self.character).addAscending("createdAt").limit(1000);
                self.changes.query = q;
                return self.changes.fetch(options);
            });
        },

        format_entry: function(log, entry) {
            if (log.has(entry)) {
                var v = log.get(entry);
                if (_.isDate(v)) {
                    return moment(v).format('lll');
                }
                return log.get(entry);
            }
            var attr = log[entry];
            if (_.isDate(attr)) {
                return moment(attr).format('lll');
            }
            return attr;
        },

        // Renders all of the Category models on the UI
        render: function() {
            // Sets the view's template property
            this.template = _.template(
                $( "script#experienceNotationsAllView" ).html())(
                { "character": this.character,
                  "logs": this.collection.models,
                  "format_entry": this.format_entry} );

            // Renders the view's template inside of the current listview element
            this.$el.find("div[role='main']").html(this.template);

            this.$el.enhanceWithin();

            // Maintains chainability
            return this;

        }

    } );

    // Returns the View class
    return View;

} );<commit_msg>Change experience view to use Vampire add experience<commit_after>// Category View
// =============

// Includes file dependencies
define([
	"jquery",
	"backbone",
    "moment",
    "../models/ExperienceNotation",
    "../collections/ExperienceNotationCollection",
    "mobiledatepicker",
    "../collections/VampireChangeCollection"
], function( $, Backbone, moment, ExperienceNotation, ExperienceNotationCollection, mobiledatepicker, VampireChangeCollection) {

    // Extends Backbone.View
    var View = Backbone.View.extend( {

        // The View Constructor
        initialize: function() {
            var self = this;

            self.changes = new VampireChangeCollection;
            self.listenTo(self.changes, "add", self.render);
            self.listenTo(self.changes, "reset", self.render);

            self.start = 0;
            self.changeBy = 10;
        },

        register: function(character, start, changeBy) {
            var self = this;
            var p = Parse.Promise.as([]);

            if (character !== self.character) {
                if (self.character) {
                    self.stopListening(self.character);
                    self.stopListening(self.collection);
                }
                self.character = character;
                p = self.character.get_experience_notations(function (rc) {
                    self.listenTo(rc, "add", self.render);
                    self.listenTo(rc, "reset", self.render);
                    self.listenTo(rc, "remove", self.render);
                    self.listenTo(self.character, "begin_experience_notation_propagation", function() {
                        $.mobile.loading("show");
                    });
                    self.listenTo(self.character, "finish_experience_notation_propagation", function() {
                        self.render();
                        $.mobile.loading("hide");
                    })
                    self.collection = rc;
                });
            }

            return p.then(function () {
                Parse.Promise.as(self);
            });
        },

        events: {
            "click .previous": "previous",
            "click .next": "next",
            "click .add": "add",
            "click .experience-notation-edit": "edit_experience_notation",
            "submit #edit-entered-popup-form": "submit_experience_notation_entered",
            "submit #edit-reason-popup-form": "submit_experience_notation_reason",
            "submit #edit-alteration-popup-form": "submit_experience_notation_alteration",
            "click .experience-notation-delete": "delete_experience_notation"
        },

        submit_experience_notation_entered: function(event, a, b, c, d) {
            var self = this;
            event.preventDefault();
            var id = self.$("#popupEditEntered #date-id").val();
            var d = self.$("#popupEditEntered #date-input").val();
            var en = self.collection.getByCid(id);
            var updatedEntered = moment(d);
            if (updatedEntered.isValid()) {
                en.set("entered", updatedEntered.toDate());
                en.save();
                $("#popupEditEntered").popup("close");
            } else {
                // Can't do validation this way because then we would have to watch for
                // change to update the state ourselves
                //self.$("#popupEditEntered #date-input")[0].setCustomValidity("Can't parse date and/or time input");
            }
        },

        submit_experience_notation_reason: function(event) {
            var self = this;
            event.preventDefault();
            var id = self.$("#popupEditReason #reason-id").val();
            var txt = self.$("#popupEditReason #reason-input").val();
            var en = self.collection.getByCid(id);
            en.set("reason", txt);
            en.save();
            $("#popupEditReason").popup("close");
        },

        submit_experience_notation_alteration: function(event) {
            var self = this;
            event.preventDefault();
            var id = self.$("#alterationpopupEdit #alteration-id").val();
            var n = _.parseInt(self.$("#alterationpopupEdit #alteration-input").val());
            var en = self.collection.getByCid(id);
            var type = self.$("#alterationpopupEdit #alteration-type").val();
            en.set("alteration_" + type, n);
            en.save();
            $("#alterationpopupEdit").popup("close");
        },

        edit_experience_notation: function(event) {
            var self = this;
            var t = self.$(event.target);
            var clickedNotationId = t.attr("notation-id");
            var headerName = t.attr("header");
            var en = self.collection.getByCid(clickedNotationId);
            event.preventDefault();
            if ("entered" === headerName) {
                var popup = $("#popupEditEntered");
                $("#popupEditEntered #date-input").val(moment(en.get("entered")).format());
                //self.$("#popupEditEntered #date-input")[0].setCustomValidity("");
                $("#popupEditEntered #date-id").val(clickedNotationId);
                popup.enhanceWithin().popup("open");
            } else if ("reason" === headerName) {
                var popup = $("#popupEditReason");
                $("#popupEditReason #reason-input").val(en.get("reason"));
                $("#popupEditReason #reason-id").val(clickedNotationId);
                popup.enhanceWithin().popup("open");
            } else if ("alteration_spent" === headerName) {
                var popup = $("#alterationpopupEdit");
                $("#alterationpopupEdit #alteration-input").val(en.get("alteration_spent"));
                $("#alterationpopupEdit #alteration-id").val(clickedNotationId);
                $("#alterationpopupEdit #alteration-type").val("spent");
                popup.enhanceWithin().popup("open");
            } else if ("alteration_earned" === headerName) {
                var popup = $("#alterationpopupEdit");
                $("#alterationpopupEdit #alteration-input").val(en.get("alteration_earned"));
                $("#alterationpopupEdit #alteration-id").val(clickedNotationId);
                $("#alterationpopupEdit #alteration-type").val("earned");
                popup.enhanceWithin().popup("open");
            }
        },

        delete_experience_notation: function(event) {
            event.preventDefault();
            var self = this;
            var t = self.$(event.target);
            var clickedNotationId = t.attr("notation-id");
            var cidgot = self.collection.getByCid(clickedNotationId);
            var idgot = self.collection.get(clickedNotationId);
            var en = self.collection.getByCid(clickedNotationId) || self.collection.get(clickedNotationId);
            if (!en) {
                return self;
            }
            self.collection.remove(en);
            en.destroy();
        },

        previous: function() {
            var self = this;
            var incr = this.start - this.changeBy;
            this.start = _.max([0, incr]);
            window.location.hash = "#character/" + self.character.id + "/experience/" + this.start + "/10";
            $.mobile.loading("show");
            this.update_collection_query_and_fetch().then(function() {
                $.mobile.loading("hide");
            })
        },

        next: function() {
            var self = this;
            this.start += self.changeBy;
            window.location.hash = "#character/" + self.character.id + "/experience/" + this.start + "/10";
            $.mobile.loading("show");
            this.update_collection_query_and_fetch().then(function() {
                $.mobile.loading("hide");
            })
        },

        add: function() {
            var self = this;
            self.character.add_experience_notation("Unspecified reason");
        },

        update_collection_query_and_fetch: function () {
            var self = this;
            var options = {reset: true};
            var q = new Parse.Query(ExperienceNotation);
            q.equalTo("owner", self.character).addDescending("entered").addDescending("createdAt");
            /*
            q.skip(self.start);
            q.limit(self.changeBy);
            */
            self.collection.query = q;
            return self.collection.fetch(options).then(function () {
                var q = new Parse.Query(VampireChange);
                q.equalTo("owner", self.character).addAscending("createdAt").limit(1000);
                self.changes.query = q;
                return self.changes.fetch(options);
            });
        },

        format_entry: function(log, entry) {
            if (log.has(entry)) {
                var v = log.get(entry);
                if (_.isDate(v)) {
                    return moment(v).format('lll');
                }
                return log.get(entry);
            }
            var attr = log[entry];
            if (_.isDate(attr)) {
                return moment(attr).format('lll');
            }
            return attr;
        },

        // Renders all of the Category models on the UI
        render: function() {
            // Sets the view's template property
            this.template = _.template(
                $( "script#experienceNotationsAllView" ).html())(
                { "character": this.character,
                  "logs": this.collection.models,
                  "format_entry": this.format_entry} );

            // Renders the view's template inside of the current listview element
            this.$el.find("div[role='main']").html(this.template);

            this.$el.enhanceWithin();

            // Maintains chainability
            return this;

        }

    } );

    // Returns the View class
    return View;

} );<|endoftext|>


<commit_before>pjax_config_page("new_account/realws", function(){

  return {
    onLoad: function() {
      Content.populate();
      if (!$.cookie('login')) {
          window.location.href = page.url.url_for('login');
          return;
      }
      if (page.client.type !== 'virtual') {
        window.location.href = page.url.url_for('user/myaccount');
        return;
      }
      for (i = 0; i < page.user.loginid_array.length; i++){
        if (page.user.loginid_array[i].real === true){
          window.location.href = page.url.url_for('user/myaccount');
          return;
        }
      }
      handle_residence_state_ws();
      getSettings();
      setResidenceWs();
      $('#real-form').submit(function(evt) {
        evt.preventDefault();
        if (RealAccOpeningUI.checkValidity()){
          BinarySocket.init({
            onmessage: function(msg){
              var response = JSON.parse(msg.data);
              if (response) {
                var type = response.msg_type;
                var error = response.error;

                if (type === 'new_account_real' && !error){
                  var loginid = response.new_account_real.client_id;

                  //set cookies
                  var oldCookieValue = $.cookie('loginid_list');
                  $.cookie('loginid_list', loginid + ':R:E+' + oldCookieValue, {domain: document.domain.substring(3), path:'/'});
                  $.cookie('loginid', loginid, {domain: document.domain.substring(3), path:'/'});

                  //push to gtm
                  var gtmDataLayer = document.getElementsByClassName('gtm_data_layer')[0];
                  var age = new Date().getFullYear() - document.getElementById('dobyy').value;
                  document.getElementById('event').innerHTML = 'new_account';
                  dataLayer.push({
                    'language': page.language(),
                    'event': 'new_account',
                    'visitorID': loginid,
                    'bom_age': age,
                    'bom_country': $('#residence-disabled option[value="' + residenceValue + '"]').html(),
                    'bom_today': Math.floor(Date.now() / 1000),
                    'bom_email': page.user.email,
                    'bom_firstname': document.getElementById('fname').value,
                    'bom_lastname': document.getElementById('lname').value,
                    'bom_phone': document.getElementById('tel').value
                  });
                  var affiliateToken = $.cookie('affiliate_tracking');
                  if (affiliateToken) {
                    dataLayer.push({'bom_affiliate_token': affiliateToken});
                  }

                  //generate dropdown list and switch
                  var option = new Option('Real Account (' + loginid + ')', loginid);
                  document.getElementById('client_loginid').appendChild(option);
                  $('#client_loginid option[value="' + page.client.loginid + '"]').removeAttr('selected');
                  option.setAttribute('selected', 'selected');
                  $('#loginid-switch-form').submit();
                } else if (error) {
                  if (/multiple real money accounts/.test(error.message)){
                    RealAccOpeningUI.showError('duplicate');
                  } else {
                    RealAccOpeningUI.showError();
                  }
                }
              }
            }
          });
        }
      });
    }
  };
});
<commit_msg>fix residenceValue<commit_after>pjax_config_page("new_account/realws", function(){

  return {
    onLoad: function() {
      Content.populate();
      if (!$.cookie('login')) {
          window.location.href = page.url.url_for('login');
          return;
      }
      if (page.client.type !== 'virtual') {
        window.location.href = page.url.url_for('user/myaccount');
        return;
      }
      for (i = 0; i < page.user.loginid_array.length; i++){
        if (page.user.loginid_array[i].real === true){
          window.location.href = page.url.url_for('user/myaccount');
          return;
        }
      }
      handle_residence_state_ws();
      getSettings();
      setResidenceWs();
      $('#real-form').submit(function(evt) {
        evt.preventDefault();
        if (RealAccOpeningUI.checkValidity()){
          BinarySocket.init({
            onmessage: function(msg){
              var response = JSON.parse(msg.data);
              if (response) {
                var type = response.msg_type;
                var error = response.error;

                if (type === 'new_account_real' && !error){
                  var loginid = response.new_account_real.client_id;

                  //set cookies
                  var oldCookieValue = $.cookie('loginid_list');
                  $.cookie('loginid_list', loginid + ':R:E+' + oldCookieValue, {domain: document.domain.substring(3), path:'/'});
                  $.cookie('loginid', loginid, {domain: document.domain.substring(3), path:'/'});

                  //push to gtm
                  var gtmDataLayer = document.getElementsByClassName('gtm_data_layer')[0];
                  var age = new Date().getFullYear() - document.getElementById('dobyy').value;
                  document.getElementById('event').innerHTML = 'new_account';
                  dataLayer.push({
                    'language': page.language(),
                    'event': 'new_account',
                    'visitorID': loginid,
                    'bom_age': age,
                    'bom_country': $('#residence-disabled option[value="' + page.client.residence + '"]').html(),
                    'bom_today': Math.floor(Date.now() / 1000),
                    'bom_email': page.user.email,
                    'bom_firstname': document.getElementById('fname').value,
                    'bom_lastname': document.getElementById('lname').value,
                    'bom_phone': document.getElementById('tel').value
                  });
                  var affiliateToken = $.cookie('affiliate_tracking');
                  if (affiliateToken) {
                    dataLayer.push({'bom_affiliate_token': affiliateToken});
                  }

                  //generate dropdown list and switch
                  var option = new Option('Real Account (' + loginid + ')', loginid);
                  document.getElementById('client_loginid').appendChild(option);
                  $('#client_loginid option[value="' + page.client.loginid + '"]').removeAttr('selected');
                  option.setAttribute('selected', 'selected');
                  $('#loginid-switch-form').submit();
                } else if (error) {
                  if (/multiple real money accounts/.test(error.message)){
                    RealAccOpeningUI.showError('duplicate');
                  } else {
                    RealAccOpeningUI.showError();
                  }
                }
              }
            }
          });
        }
      });
    }
  };
});
<|endoftext|>


<commit_before>import opacity from 'ember-animated/motions/opacity';

/**
  Fades inserted, removed, and kept sprites.

  ```js
  import fade from 'ember-animated/transitions/fade'

  export default Component.extend({
    transition: fade
  });
  ```

  ```hbs
  {{#animated-if use=transition}}
    ...
  {{/animated-if}}
  ```

  @function fade
  @export default
*/
export default function*({
  removedSprites,
  insertedSprites,
  keptSprites,
  duration,
}) {
  // We yield Promise.all here because we want to wait for this
  // step before starting what comes after.
  yield Promise.all(
    removedSprites.map(s => {
      if (s.revealed) {
        return opacity(s, {
          to: 0,
          duration: duration / 2,
        });
      }
    }),
  );

  // Once all fading out has happened, we can fade in the inserted
  // or kept sprites. Note that we get keptSprites if some things
  // were fading out and then we get interrupted and decide to
  // keep them around after all.
  insertedSprites.concat(keptSprites).map(s =>
    opacity(s, {
      to: 1,
      duration: duration / 2,
    }),
  );
}
<commit_msg>Add missing semicolon to example<commit_after>import opacity from 'ember-animated/motions/opacity';

/**
  Fades inserted, removed, and kept sprites.

  ```js
  import fade from 'ember-animated/transitions/fade';

  export default Component.extend({
    transition: fade
  });
  ```

  ```hbs
  {{#animated-if use=transition}}
    ...
  {{/animated-if}}
  ```

  @function fade
  @export default
*/
export default function*({
  removedSprites,
  insertedSprites,
  keptSprites,
  duration,
}) {
  // We yield Promise.all here because we want to wait for this
  // step before starting what comes after.
  yield Promise.all(
    removedSprites.map(s => {
      if (s.revealed) {
        return opacity(s, {
          to: 0,
          duration: duration / 2,
        });
      }
    }),
  );

  // Once all fading out has happened, we can fade in the inserted
  // or kept sprites. Note that we get keptSprites if some things
  // were fading out and then we get interrupted and decide to
  // keep them around after all.
  insertedSprites.concat(keptSprites).map(s =>
    opacity(s, {
      to: 1,
      duration: duration / 2,
    }),
  );
}
<|endoftext|>


<commit_before>var express = require('express');

var env     = require('../env');
var Subscription = require('../models/subscription.js');

var router = express.Router();
var subscriptionController = {};

subscriptionController.getSubscriptions = function(req, res) {
  Subscription.find(function(err, subscriptions) {
    if (err) {
      res.send(err);
    }

    res.json(subscriptions);
  });
};

subscriptionController.getSubscription = function(req, res) {
  Subscription.findById(req.params.subscriptionId, function(err, subscription) {
    if (!subscription) {
      return res.status(404).send({
        message: "Can't find the subscription."
      });
    }

    if (err) {
      return res.status(500).send(err);
    }

    return res.json(subscription);
  });
};

subscriptionController.postSubscription = function(req, res) {
  var subscription = new Subscription();
  if (!req.body.shopName || !req.body.frequency) {
    return res.status(400).json({message: 'Missing fields dawg'});
  }

  subscription.shopName = req.body.shopName;
  subscription.frequency = req.body.frequency;
  subscription.userId = req.body.userId;
  subscription.lastChecked = Date.now();

  subscription.save(function(err) {
    if (err) {
      return res.status(500).send(err);
    }

    resourceURI = env.apiURL + '/subscriptions/' + subscription._id;

    return res.status(201).header('Location', resourceURI).end();
  });
};

subscriptionController.putSubscription = function(req, res) {
  Subscription.find({_id: req.params.subscriptionId}, function(err, subscription) {
    if (err) {
      return res.status(500).send(err);
    }

    if (req.body.frequency === false && req.body.shopName === false) {
      return res.status(400).json({message: 'Missing fields, dawg'});
    }

    subscription.frequency = req.body.frequency || subscription.frequency;
    subscription.shopName = req.body.shopName || subscription.shopName;

    subscription.save(function(err) {
      if (err) {
        res.status(500).send(err);
      }

      return res.json(subscription);
    });
  });
};

subscriptionController.deleteSubscription = function(req, res) {
  Subscription.remove({_id: req.params.subscriptionId}, function(err) {
    if (err) {
      return res.status(500).send(err);
    }

    return res.status(204).end();
  });
};

module.exports = subscriptionController;
<commit_msg>Sending userId in subscription POST is now mandatory<commit_after>var express = require('express');

var env     = require('../env');
var Subscription = require('../models/subscription.js');

var router = express.Router();
var subscriptionController = {};

subscriptionController.getSubscriptions = function(req, res) {
  Subscription.find(function(err, subscriptions) {
    if (err) {
      res.send(err);
    }

    res.json(subscriptions);
  });
};

subscriptionController.getSubscription = function(req, res) {
  Subscription.findById(req.params.subscriptionId, function(err, subscription) {
    if (!subscription) {
      return res.status(404).send({
        message: "Can't find the subscription."
      });
    }

    if (err) {
      return res.status(500).send(err);
    }

    return res.json(subscription);
  });
};

subscriptionController.postSubscription = function(req, res) {
  var subscription = new Subscription();
  if (!req.body.shopName || !req.body.frequency || !req.body.userId) {
    return res.status(400).json({message: 'Missing fields dawg'});
  }

  subscription.shopName = req.body.shopName;
  subscription.frequency = req.body.frequency;
  subscription.userId = req.body.userId;
  subscription.lastChecked = Date.now();

  subscription.save(function(err) {
    if (err) {
      return res.status(500).send(err);
    }

    resourceURI = env.apiURL + '/subscriptions/' + subscription._id;

    return res.status(201).header('Location', resourceURI).end();
  });
};

subscriptionController.putSubscription = function(req, res) {
  Subscription.find({_id: req.params.subscriptionId}, function(err, subscription) {
    if (err) {
      return res.status(500).send(err);
    }

    if (req.body.frequency === false && req.body.shopName === false) {
      return res.status(400).json({message: 'Missing fields, dawg'});
    }

    subscription.frequency = req.body.frequency || subscription.frequency;
    subscription.shopName = req.body.shopName || subscription.shopName;

    subscription.save(function(err) {
      if (err) {
        res.status(500).send(err);
      }

      return res.json(subscription);
    });
  });
};

subscriptionController.deleteSubscription = function(req, res) {
  Subscription.remove({_id: req.params.subscriptionId}, function(err) {
    if (err) {
      return res.status(500).send(err);
    }

    return res.status(204).end();
  });
};

module.exports = subscriptionController;
<|endoftext|>


<commit_before>import { Buffer } from 'buffer';
import { open, read, close } from 'fs';
import http from 'http';
import https from 'https';
import urlMod from 'url';

function readRangeFromBlocks(blocks, rangeOffset, rangeLength) {
  const rangeTop = rangeOffset + rangeLength;
  const rangeData = new ArrayBuffer(rangeLength);
  const rangeView = new Uint8Array(rangeData);

  for (const block of blocks) {
    const delta = block.offset - rangeOffset;
    const topDelta = block.top - rangeTop;
    let blockInnerOffset = 0;
    let rangeInnerOffset = 0;
    let usedBlockLength;

    if (delta < 0) {
      blockInnerOffset = -delta;
    } else if (delta > 0) {
      rangeInnerOffset = delta;
    }

    if (topDelta < 0) {
      usedBlockLength = block.length - blockInnerOffset;
    } else if (topDelta > 0) {
      usedBlockLength = rangeTop - block.offset - blockInnerOffset;
    }

    const blockView = new Uint8Array(block.data, blockInnerOffset, usedBlockLength);
    rangeView.set(blockView, rangeInnerOffset);
  }

  return rangeData;
}

/**
 * Interface for Source objects.
 * @interface Source
 */

/**
 * @function Source#fetch
 * @summary The main method to retrieve the data from the source.
 * @param {number} offset The offset to read from in the source
 * @param {number} length The requested number of bytes
 */

/**
 * @typedef {object} Block
 * @property {ArrayBuffer} data The actual data of the block.
 * @property {number} offset The actual offset of the block within the file.
 * @property {number} length The actual size of the block in bytes.
 */

/**
 * Callback type for sources to request patches of data.
 * @callback requestCallback
 * @async
 * @param {number} offset The offset within the file.
 * @param {number} length The desired length of data to be read.
 * @returns {Promise<Block>} The block of data.
 */

/**
 * @module source
 */

/*
 * Split a list of identifiers to form groups of coherent ones
 */
function getCoherentBlockGroups(blockIds) {
  if (blockIds.length === 0) {
    return [];
  }

  const groups = [];
  let current = [];
  groups.push(current);

  for (let i = 0; i < blockIds.length; ++i) {
    if (i === 0 || blockIds[i] === blockIds[i - 1] + 1) {
      current.push(blockIds[i]);
    } else {
      current = [blockIds[i]];
      groups.push(current);
    }
  }
  return groups;
}

/*
 * Promisified wrapper around 'setTimeout' to allow 'await'
 */
async function wait(milliseconds) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

/**
 * BlockedSource - an abstraction of (remote) files.
 * @implements Source
 */
class BlockedSource {
  /**
   * @param {requestCallback} retrievalFunction Callback function to request data
   * @param {object} options Additional options
   * @param {object} options.blockSize Size of blocks to be fetched
   */
  constructor(retrievalFunction, { blockSize = 65536 } = {}) {
    this.retrievalFunction = retrievalFunction;
    this.blockSize = blockSize;

    // currently running block requests
    this.blockRequests = new Map();

    // already retrieved blocks
    this.blocks = new Map();

    // block ids waiting for a batched request. Either a Set or null
    this.blockIdsAwaitingRequest = null;
  }

  /**
   * Fetch a subset of the file.
   * @param {number} offset The offset within the file to read from.
   * @param {number} length The length in bytes to read from.
   * @returns {ArrayBuffer} The subset of the file.
   */
  async fetch(offset, length, immediate = false) {
    const top = offset + length;

    // calculate what blocks intersect the specified range (offset + length)
    // determine what blocks are already stored or beeing requested
    const firstBlockOffset = Math.floor(offset / this.blockSize) * this.blockSize;
    const allBlockIds = [];
    const missingBlockIds = [];
    const blockRequests = [];

    for (let current = firstBlockOffset; current < top; current += this.blockSize) {
      const blockId = Math.floor(current / this.blockSize);
      if (!this.blocks.has(blockId) && !this.blockRequests.has(blockId)) {
        missingBlockIds.push(blockId);
      }
      if (this.blockRequests.has(blockId)) {
        blockRequests.push(this.blockRequests.get(blockId));
      }
      allBlockIds.push(blockId);
    }

    // determine whether there are already blocks in the queue to be requested
    // if so, add the missing blocks to this list
    if (!this.blockIdsAwaitingRequest) {
      this.blockIdsAwaitingRequest = new Set(missingBlockIds);
    } else {
      for (let i = 0; i < missingBlockIds.length; ++i) {
        const id = missingBlockIds[i];
        this.blockIdsAwaitingRequest.add(id);
      }
    }

    // in immediate mode, we don't want to wait for possible additional requests coming in
    if (!immediate) {
      await wait();
    }

    // determine if we are the thread to start the requests.
    if (this.blockIdsAwaitingRequest) {
      // get all coherent blocks as groups to be requested in a single request
      const groups = getCoherentBlockGroups(
        Array.from(this.blockIdsAwaitingRequest).sort(),
      );

      // iterate over all blocks
      for (const group of groups) {
        // fetch a group as in a single request
        const request = this.requestData(
          group[0] * this.blockSize, group.length * this.blockSize,
        );

        // for each block in the request, make a small 'splitter',
        // i.e: wait for the request to finish, then cut out the bytes for
        // that block and store it there.
        // we keep that as a promise in 'blockRequests' to allow waiting on
        // a single block.
        for (let i = 0; i < group.length; ++i) {
          const id = group[i];
          this.blockRequests.set(id, (async () => {
            const response = await request;
            const o = i * this.blockSize;
            const t = Math.min(o + this.blockSize, response.data.byteLength);
            const data = response.data.slice(o, t);
            this.blockRequests.delete(id);
            this.blocks.set(id, {
              data,
              offset: response.offset + o,
              length: data.byteLength,
              top: response.offset + t,
            });
          })());
        }
      }
      this.blockIdsAwaitingRequest = null;
    }

    // get a list of currently running requests for the blocks still missing
    const missingRequests = [];
    for (const blockId of missingBlockIds) {
      if (this.blockRequests.has(blockId)) {
        missingRequests.push(this.blockRequests.get(blockId));
      }
    }

    // wait for all missing requests to finish
    await Promise.all(missingRequests);
    await Promise.all(blockRequests);

    // now get all blocks for the request and return a summary buffer
    const blocks = allBlockIds.map((id) => this.blocks.get(id));
    return readRangeFromBlocks(blocks, offset, length);
  }

  async requestData(requestedOffset, requestedLength) {
    const response = await this.retrievalFunction(requestedOffset, requestedLength);
    if (!response.length) {
      response.length = response.data.byteLength;
    } else if (response.length !== response.data.byteLength) {
      response.data = response.data.slice(0, response.length);
    }
    response.top = response.offset + response.length;
    return response;
  }
}

/**
 * Create a new source to read from a remote file using the
 * [fetch]{@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 * @returns The constructed source
 */
export function makeFetchSource(url, { headers = {}, blockSize } = {}) {
  return new BlockedSource(async (offset, length) => {
    const response = await fetch(url, {
      headers: {
        ...headers, Range: `bytes=${offset}-${offset + length - 1}`,
      },
    });

    // check the response was okay and if the server actually understands range requests
    if (!response.ok) {
      throw new Error('Error fetching data.');
    } else if (response.status === 206) {
      const data = response.arrayBuffer
        ? await response.arrayBuffer() : (await response.buffer()).buffer;
      return {
        data,
        offset,
        length,
      };
    } else {
      const data = response.arrayBuffer
        ? await response.arrayBuffer() : (await response.buffer()).buffer;
      return {
        data,
        offset: 0,
        length: data.byteLength,
      };
    }
  }, { blockSize });
}

/**
 * Create a new source to read from a remote file using the
 * [XHR]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest} API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 * @returns The constructed source
 */
export function makeXHRSource(url, { headers = {}, blockSize } = {}) {
  return new BlockedSource(async (offset, length) => {
    return new Promise((resolve, reject) => {
      const request = new XMLHttpRequest();
      request.open('GET', url);
      request.responseType = 'arraybuffer';
      const requestHeaders = { ...headers, Range: `bytes=${offset}-${offset + length - 1}` };
      for (const [key, value] of Object.entries(requestHeaders)) {
        request.setRequestHeader(key, value);
      }

      request.onload = () => {
        const data = request.response;
        if (request.status === 206) {
          resolve({
            data,
            offset,
            length,
          });
        } else {
          resolve({
            data,
            offset: 0,
            length: data.byteLength,
          });
        }
      };
      request.onerror = reject;
      request.send();
    });
  }, { blockSize });
}

/**
 * Create a new source to read from a remote file using the node
 * [http]{@link https://nodejs.org/api/http.html} API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 */
export function makeHttpSource(url, { headers = {}, blockSize } = {}) {
  return new BlockedSource(async (offset, length) => new Promise((resolve, reject) => {
    const parsed = urlMod.parse(url);
    const request = (parsed.protocol === 'http:' ? http : https).get(
      { ...parsed,
        headers: {
          ...headers, Range: `bytes=${offset}-${offset + length - 1}`,
        } }, (result) => {
        const chunks = [];
        // collect chunks
        result.on('data', (chunk) => {
          chunks.push(chunk);
        });

        // concatenate all chunks and resolve the promise with the resulting buffer
        result.on('end', () => {
          const data = Buffer.concat(chunks).buffer;
          resolve({
            data,
            offset,
            length: data.byteLength,
          });
        });
      },
    );
    request.on('error', reject);
  }), { blockSize });
}

/**
 * Create a new source to read from a remote file. Uses either XHR, fetch or nodes http API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Boolean} [options.forceXHR] Force the usage of XMLHttpRequest.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 * @returns The constructed source
 */
export function makeRemoteSource(url, options) {
  const { forceXHR } = options;
  if (typeof fetch === 'function' && !forceXHR) {
    return makeFetchSource(url, options);
  }
  if (typeof XMLHttpRequest !== 'undefined') {
    return makeXHRSource(url, options);
  }
  if (http.get) {
    return makeHttpSource(url, options);
  }
  throw new Error('No remote source available');
}

/**
 * Create a new source to read from a local
 * [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer}.
 * @param {ArrayBuffer} arrayBuffer The ArrayBuffer to parse the GeoTIFF from.
 * @returns The constructed source
 */
export function makeBufferSource(arrayBuffer) {
  return {
    async fetch(offset, length) {
      return arrayBuffer.slice(offset, offset + length);
    },
  };
}

function closeAsync(fd) {
  return new Promise((resolve, reject) => {
    close(fd, err => {
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    });
  });
}

function openAsync(path, flags, mode = undefined) {
  return new Promise((resolve, reject) => {
    open(path, flags, mode, (err, fd) => {
      if (err) {
        reject(err);
      } else {
        resolve(fd);
      }
    });
  });
}

function readAsync(...args) {
  return new Promise((resolve, reject) => {
    read(...args, (err, bytesRead, buffer) => {
      if (err) {
        reject(err);
      } else {
        resolve({ bytesRead, buffer });
      }
    });
  });
}

/**
 * Creates a new source using the node filesystem API.
 * @param {string} path The path to the file in the local filesystem.
 * @returns The constructed source
 */
export function makeFileSource(path) {
  const fileOpen = openAsync(path, 'r');

  return {
    async fetch(offset, length) {
      const fd = await fileOpen;
      const { buffer } = await readAsync(fd, Buffer.alloc(length), 0, length, offset);
      return buffer.buffer;
    },
    async close() {
      const fd = await fileOpen;
      return await closeAsync(fd);
    },
  };
}

/**
 * Create a new source from a given file/blob.
 * @param {Blob} file The file or blob to read from.
 * @returns The constructed source
 */
export function makeFileReaderSource(file) {
  return {
    async fetch(offset, length) {
      return new Promise((resolve, reject) => {
        const blob = file.slice(offset, offset + length);
        const reader = new FileReader();
        reader.onload = (event) => resolve(event.target.result);
        reader.onerror = reject;
        reader.readAsArrayBuffer(blob);
      });
    },
  };
}
<commit_msg>if topDelta = 0, use one of the conditions. (#158)<commit_after>import { Buffer } from 'buffer';
import { open, read, close } from 'fs';
import http from 'http';
import https from 'https';
import urlMod from 'url';

function readRangeFromBlocks(blocks, rangeOffset, rangeLength) {
  const rangeTop = rangeOffset + rangeLength;
  const rangeData = new ArrayBuffer(rangeLength);
  const rangeView = new Uint8Array(rangeData);

  for (const block of blocks) {
    const delta = block.offset - rangeOffset;
    const topDelta = block.top - rangeTop;
    let blockInnerOffset = 0;
    let rangeInnerOffset = 0;
    let usedBlockLength;

    if (delta < 0) {
      blockInnerOffset = -delta;
    } else if (delta > 0) {
      rangeInnerOffset = delta;
    }

    if (topDelta < 0) {
      usedBlockLength = block.length - blockInnerOffset;
    } else {
      usedBlockLength = rangeTop - block.offset - blockInnerOffset;
    }

    const blockView = new Uint8Array(block.data, blockInnerOffset, usedBlockLength);
    rangeView.set(blockView, rangeInnerOffset);
  }

  return rangeData;
}

/**
 * Interface for Source objects.
 * @interface Source
 */

/**
 * @function Source#fetch
 * @summary The main method to retrieve the data from the source.
 * @param {number} offset The offset to read from in the source
 * @param {number} length The requested number of bytes
 */

/**
 * @typedef {object} Block
 * @property {ArrayBuffer} data The actual data of the block.
 * @property {number} offset The actual offset of the block within the file.
 * @property {number} length The actual size of the block in bytes.
 */

/**
 * Callback type for sources to request patches of data.
 * @callback requestCallback
 * @async
 * @param {number} offset The offset within the file.
 * @param {number} length The desired length of data to be read.
 * @returns {Promise<Block>} The block of data.
 */

/**
 * @module source
 */

/*
 * Split a list of identifiers to form groups of coherent ones
 */
function getCoherentBlockGroups(blockIds) {
  if (blockIds.length === 0) {
    return [];
  }

  const groups = [];
  let current = [];
  groups.push(current);

  for (let i = 0; i < blockIds.length; ++i) {
    if (i === 0 || blockIds[i] === blockIds[i - 1] + 1) {
      current.push(blockIds[i]);
    } else {
      current = [blockIds[i]];
      groups.push(current);
    }
  }
  return groups;
}

/*
 * Promisified wrapper around 'setTimeout' to allow 'await'
 */
async function wait(milliseconds) {
  return new Promise((resolve) => setTimeout(resolve, milliseconds));
}

/**
 * BlockedSource - an abstraction of (remote) files.
 * @implements Source
 */
class BlockedSource {
  /**
   * @param {requestCallback} retrievalFunction Callback function to request data
   * @param {object} options Additional options
   * @param {object} options.blockSize Size of blocks to be fetched
   */
  constructor(retrievalFunction, { blockSize = 65536 } = {}) {
    this.retrievalFunction = retrievalFunction;
    this.blockSize = blockSize;

    // currently running block requests
    this.blockRequests = new Map();

    // already retrieved blocks
    this.blocks = new Map();

    // block ids waiting for a batched request. Either a Set or null
    this.blockIdsAwaitingRequest = null;
  }

  /**
   * Fetch a subset of the file.
   * @param {number} offset The offset within the file to read from.
   * @param {number} length The length in bytes to read from.
   * @returns {ArrayBuffer} The subset of the file.
   */
  async fetch(offset, length, immediate = false) {
    const top = offset + length;

    // calculate what blocks intersect the specified range (offset + length)
    // determine what blocks are already stored or beeing requested
    const firstBlockOffset = Math.floor(offset / this.blockSize) * this.blockSize;
    const allBlockIds = [];
    const missingBlockIds = [];
    const blockRequests = [];

    for (let current = firstBlockOffset; current < top; current += this.blockSize) {
      const blockId = Math.floor(current / this.blockSize);
      if (!this.blocks.has(blockId) && !this.blockRequests.has(blockId)) {
        missingBlockIds.push(blockId);
      }
      if (this.blockRequests.has(blockId)) {
        blockRequests.push(this.blockRequests.get(blockId));
      }
      allBlockIds.push(blockId);
    }

    // determine whether there are already blocks in the queue to be requested
    // if so, add the missing blocks to this list
    if (!this.blockIdsAwaitingRequest) {
      this.blockIdsAwaitingRequest = new Set(missingBlockIds);
    } else {
      for (let i = 0; i < missingBlockIds.length; ++i) {
        const id = missingBlockIds[i];
        this.blockIdsAwaitingRequest.add(id);
      }
    }

    // in immediate mode, we don't want to wait for possible additional requests coming in
    if (!immediate) {
      await wait();
    }

    // determine if we are the thread to start the requests.
    if (this.blockIdsAwaitingRequest) {
      // get all coherent blocks as groups to be requested in a single request
      const groups = getCoherentBlockGroups(
        Array.from(this.blockIdsAwaitingRequest).sort(),
      );

      // iterate over all blocks
      for (const group of groups) {
        // fetch a group as in a single request
        const request = this.requestData(
          group[0] * this.blockSize, group.length * this.blockSize,
        );

        // for each block in the request, make a small 'splitter',
        // i.e: wait for the request to finish, then cut out the bytes for
        // that block and store it there.
        // we keep that as a promise in 'blockRequests' to allow waiting on
        // a single block.
        for (let i = 0; i < group.length; ++i) {
          const id = group[i];
          this.blockRequests.set(id, (async () => {
            const response = await request;
            const o = i * this.blockSize;
            const t = Math.min(o + this.blockSize, response.data.byteLength);
            const data = response.data.slice(o, t);
            this.blockRequests.delete(id);
            this.blocks.set(id, {
              data,
              offset: response.offset + o,
              length: data.byteLength,
              top: response.offset + t,
            });
          })());
        }
      }
      this.blockIdsAwaitingRequest = null;
    }

    // get a list of currently running requests for the blocks still missing
    const missingRequests = [];
    for (const blockId of missingBlockIds) {
      if (this.blockRequests.has(blockId)) {
        missingRequests.push(this.blockRequests.get(blockId));
      }
    }

    // wait for all missing requests to finish
    await Promise.all(missingRequests);
    await Promise.all(blockRequests);

    // now get all blocks for the request and return a summary buffer
    const blocks = allBlockIds.map((id) => this.blocks.get(id));
    return readRangeFromBlocks(blocks, offset, length);
  }

  async requestData(requestedOffset, requestedLength) {
    const response = await this.retrievalFunction(requestedOffset, requestedLength);
    if (!response.length) {
      response.length = response.data.byteLength;
    } else if (response.length !== response.data.byteLength) {
      response.data = response.data.slice(0, response.length);
    }
    response.top = response.offset + response.length;
    return response;
  }
}

/**
 * Create a new source to read from a remote file using the
 * [fetch]{@link https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API} API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 * @returns The constructed source
 */
export function makeFetchSource(url, { headers = {}, blockSize } = {}) {
  return new BlockedSource(async (offset, length) => {
    const response = await fetch(url, {
      headers: {
        ...headers, Range: `bytes=${offset}-${offset + length - 1}`,
      },
    });

    // check the response was okay and if the server actually understands range requests
    if (!response.ok) {
      throw new Error('Error fetching data.');
    } else if (response.status === 206) {
      const data = response.arrayBuffer
        ? await response.arrayBuffer() : (await response.buffer()).buffer;
      return {
        data,
        offset,
        length,
      };
    } else {
      const data = response.arrayBuffer
        ? await response.arrayBuffer() : (await response.buffer()).buffer;
      return {
        data,
        offset: 0,
        length: data.byteLength,
      };
    }
  }, { blockSize });
}

/**
 * Create a new source to read from a remote file using the
 * [XHR]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest} API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 * @returns The constructed source
 */
export function makeXHRSource(url, { headers = {}, blockSize } = {}) {
  return new BlockedSource(async (offset, length) => {
    return new Promise((resolve, reject) => {
      const request = new XMLHttpRequest();
      request.open('GET', url);
      request.responseType = 'arraybuffer';
      const requestHeaders = { ...headers, Range: `bytes=${offset}-${offset + length - 1}` };
      for (const [key, value] of Object.entries(requestHeaders)) {
        request.setRequestHeader(key, value);
      }

      request.onload = () => {
        const data = request.response;
        if (request.status === 206) {
          resolve({
            data,
            offset,
            length,
          });
        } else {
          resolve({
            data,
            offset: 0,
            length: data.byteLength,
          });
        }
      };
      request.onerror = reject;
      request.send();
    });
  }, { blockSize });
}

/**
 * Create a new source to read from a remote file using the node
 * [http]{@link https://nodejs.org/api/http.html} API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 */
export function makeHttpSource(url, { headers = {}, blockSize } = {}) {
  return new BlockedSource(async (offset, length) => new Promise((resolve, reject) => {
    const parsed = urlMod.parse(url);
    const request = (parsed.protocol === 'http:' ? http : https).get(
      { ...parsed,
        headers: {
          ...headers, Range: `bytes=${offset}-${offset + length - 1}`,
        } }, (result) => {
        const chunks = [];
        // collect chunks
        result.on('data', (chunk) => {
          chunks.push(chunk);
        });

        // concatenate all chunks and resolve the promise with the resulting buffer
        result.on('end', () => {
          const data = Buffer.concat(chunks).buffer;
          resolve({
            data,
            offset,
            length: data.byteLength,
          });
        });
      },
    );
    request.on('error', reject);
  }), { blockSize });
}

/**
 * Create a new source to read from a remote file. Uses either XHR, fetch or nodes http API.
 * @param {string} url The URL to send requests to.
 * @param {Object} [options] Additional options.
 * @param {Boolean} [options.forceXHR] Force the usage of XMLHttpRequest.
 * @param {Number} [options.blockSize] The block size to use.
 * @param {object} [options.headers] Additional headers to be sent to the server.
 * @returns The constructed source
 */
export function makeRemoteSource(url, options) {
  const { forceXHR } = options;
  if (typeof fetch === 'function' && !forceXHR) {
    return makeFetchSource(url, options);
  }
  if (typeof XMLHttpRequest !== 'undefined') {
    return makeXHRSource(url, options);
  }
  if (http.get) {
    return makeHttpSource(url, options);
  }
  throw new Error('No remote source available');
}

/**
 * Create a new source to read from a local
 * [ArrayBuffer]{@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer}.
 * @param {ArrayBuffer} arrayBuffer The ArrayBuffer to parse the GeoTIFF from.
 * @returns The constructed source
 */
export function makeBufferSource(arrayBuffer) {
  return {
    async fetch(offset, length) {
      return arrayBuffer.slice(offset, offset + length);
    },
  };
}

function closeAsync(fd) {
  return new Promise((resolve, reject) => {
    close(fd, err => {
      if (err) {
        reject(err)
      } else {
        resolve()
      }
    });
  });
}

function openAsync(path, flags, mode = undefined) {
  return new Promise((resolve, reject) => {
    open(path, flags, mode, (err, fd) => {
      if (err) {
        reject(err);
      } else {
        resolve(fd);
      }
    });
  });
}

function readAsync(...args) {
  return new Promise((resolve, reject) => {
    read(...args, (err, bytesRead, buffer) => {
      if (err) {
        reject(err);
      } else {
        resolve({ bytesRead, buffer });
      }
    });
  });
}

/**
 * Creates a new source using the node filesystem API.
 * @param {string} path The path to the file in the local filesystem.
 * @returns The constructed source
 */
export function makeFileSource(path) {
  const fileOpen = openAsync(path, 'r');

  return {
    async fetch(offset, length) {
      const fd = await fileOpen;
      const { buffer } = await readAsync(fd, Buffer.alloc(length), 0, length, offset);
      return buffer.buffer;
    },
    async close() {
      const fd = await fileOpen;
      return await closeAsync(fd);
    },
  };
}

/**
 * Create a new source from a given file/blob.
 * @param {Blob} file The file or blob to read from.
 * @returns The constructed source
 */
export function makeFileReaderSource(file) {
  return {
    async fetch(offset, length) {
      return new Promise((resolve, reject) => {
        const blob = file.slice(offset, offset + length);
        const reader = new FileReader();
        reader.onload = (event) => resolve(event.target.result);
        reader.onerror = reject;
        reader.readAsArrayBuffer(blob);
      });
    },
  };
}
<|endoftext|>


<commit_before>/**
 * 快捷方式配置，
 * 此模块配合 loadContent模块 使用，提供 getHtml() 接口，并解析快捷方式配置（config/shortcut.js）
 */
define( function ( require ) {
    var
        $ = require( "jquery" ),
        MenuSource = require( "../common/menuSource" ),
        ArtTemplate = require( "artTemplate" ),
        SHORTCUT_CONFIG = require( "../config/shortcut" ),
        Shortcut = {}
    ;

    Shortcut.defaults = {
        src: "__CTX__/static/desktop/tpl/system/index.html",
        mode: "default"
    };

    Shortcut.template =
         "
"
        +"    {{if isFontIcon}} "
        +"        "
        +"    {{else}} "
        +"        "
        +"    {{/if}} "
        +"
{{menuName}}

"
    ;

    /**
     * 初始化方法
     * @private
     */
    Shortcut._init = function () {
        this.sysMenuSet = MenuSource.getSet( true );
        this.config = SHORTCUT_CONFIG;
        this.render = ArtTemplate.compile( this.template );
    };

    /**
     * 核心方法
     * @param callback
     */
    Shortcut.getHtml = function ( callback ) {
        callback( this._getHtml() );
    };

    /**
     * 获取
     * @private
     */
    Shortcut._getHtml = function () {
        var
            _this = this,
            sysMenuSet = this.sysMenuSet,
            defaultSrc = this.defaults.src,
            defaultMode = this.defaults.mode,
            html = ""
        ;
        $.each( this.config, function ( index, item ) {
            var
                shortcutHtml,
                isFontIcon = true,
                sysMenu,
                icon
            ;

            sysMenu = sysMenuSet[ item.menuId ];

            // 如果不在 sysMenuSet 中，则说明没有该菜单的权限
            if ( ! sysMenu ) {
                return true; // continue
            }

            icon = item.icon || sysMenu.icon || "fa fa-play";

            if ( icon.indexOf( ".png" ) !== -1  ) {
                isFontIcon = false;
            }

            // 翻译模板
            shortcutHtml = _this.render( {
                icon: icon,
                menuName: item.menuName || sysMenu.menuName,
                menuId: item.menuId,
                src: item.src || defaultSrc,
                mode: item.mode || defaultMode,
                isFontIcon: isFontIcon
            } );

            html += shortcutHtml;
        } );

        return html;
    };

    Shortcut._init();

    return Shortcut;
} );<commit_msg>【desktop】page/shortcut.js：处理 menuId 为字符串的情况<commit_after>/**
 * 快捷方式配置，
 * 此模块配合 loadContent模块 使用，提供 getHtml() 接口，并解析快捷方式配置（config/shortcut.js）
 */
define( function ( require ) {
    var
        $ = require( "jquery" ),
        MenuSource = require( "../common/menuSource" ),
        ArtTemplate = require( "artTemplate" ),
        SHORTCUT_CONFIG = require( "../config/shortcut" ),
        Shortcut = {}
    ;

    Shortcut.defaults = {
        src: "__CTX__/static/desktop/tpl/system/index.html",
        mode: "default"
    };

    Shortcut.template =
         "
"
        +"    {{if isFontIcon}} "
        +"        "
        +"    {{else}} "
        +"        "
        +"    {{/if}} "
        +"
{{menuName}}

"
    ;

    /**
     * 初始化方法
     * @private
     */
    Shortcut._init = function () {
        this.sysMenuSet = MenuSource.getSet( true );
        this.config = SHORTCUT_CONFIG;
        this.render = ArtTemplate.compile( this.template );
    };

    /**
     * 核心方法
     * @param callback
     */
    Shortcut.getHtml = function ( callback ) {
        callback( this._getHtml() );
    };

    /**
     * 获取
     * @private
     */
    Shortcut._getHtml = function () {
        var
            _this = this,
            sysMenuSet = this.sysMenuSet,
            defaultSrc = this.defaults.src,
            defaultMode = this.defaults.mode,
            html = ""
        ;
        $.each( this.config, function ( index, item ) {
            var
                shortcutHtml,
                isFontIcon = true,
                sysMenu,
                icon,
                menuId
            ;

            sysMenu = sysMenuSet[ item.menuId ];

            // 如果不在 sysMenuSet 中，则说明没有该菜单的权限
            if ( ! sysMenu ) {
                return true; // continue
            }

            icon = item.icon || sysMenu.icon || "fa fa-play";

            if ( icon.indexOf( ".png" ) !== -1  ) {
                isFontIcon = false;
            }

            menuId = item.menuId;
            if ( typeof menuId === "string" ) {
                menuId = '"' + menuId + '"';
            }

            // 翻译模板
            shortcutHtml = _this.render( {
                icon: icon,
                menuName: item.menuName || sysMenu.menuName,
                menuId: menuId,
                src: item.src || defaultSrc,
                mode: item.mode || defaultMode,
                isFontIcon: isFontIcon
            } );

            html += shortcutHtml;
        } );

        return html;
    };

    Shortcut._init();

    return Shortcut;
} );<|endoftext|>


<commit_before>jQuery( function( $ ) {

    var postID = $('.post-container').attr('id');
    postID = postID.replace("post-", "");

    $.ajax({
        async: false,
        type: "POST",
        dataType: "json",
        url: vars.ajaxurl,
        data: {
            action: 'rhs_add_stats_data',
            postID: postID,
            json : true,
            type: RHSNetworkJS.META_KEY_VIEW
        },
        success: function (data) {

        },
        error: function (data) {

        }
    });

    $('.facebook_share').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_FACEBOOK},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

    $('.twitter_share').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_TWITTER},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

    $('.whatsapp_share').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_WHATSAPP},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

    $('.share_print').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_PRINT},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

});
<commit_msg>Evita erro do JS quando template não possui elemento com classe 'post-container'<commit_after>jQuery( function( $ ) {

    var postID = $('.post-container').attr('id');

    if(!postID)
        return;

    postID = postID.replace("post-", "");

    $.ajax({
        async: false,
        type: "POST",
        dataType: "json",
        url: vars.ajaxurl,
        data: {
            action: 'rhs_add_stats_data',
            postID: postID,
            json : true,
            type: RHSNetworkJS.META_KEY_VIEW
        },
        success: function (data) {

        },
        error: function (data) {

        }
    });

    $('.facebook_share').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_FACEBOOK},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

    $('.twitter_share').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_TWITTER},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

    $('.whatsapp_share').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_WHATSAPP},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

    $('.share_print').click(function () {
            $.ajax({
                async: false,
                type: "POST",
                dataType: "json",
                url: vars.ajaxurl,
                data: {action: 'rhs_add_stats_data', postID: postID, 'json' : true, type: RHSNetworkJS.META_KEY_PRINT},
                success: function (data) {

                },
                error: function (data) {

                }
            });
    });

});
<|endoftext|>