<commit_before>goog.provide('ol.MapBrowserEvent');
goog.provide('ol.MapBrowserEvent.EventType');
goog.provide('ol.MapBrowserEventHandler');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('ol.Coordinate');
goog.require('ol.FrameState');
goog.require('ol.MapEvent');
goog.require('ol.Pixel');

/**
 * @constructor
 * @extends {ol.MapEvent}
 * @param {string} type Event type.
 * @param {ol.Map} map Map.
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @param {?ol.FrameState=} opt_frameState Frame state.
 * @todo stability experimental
 */
ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {

  goog.base(this, type, map, opt_frameState);

  /**
   * @type {goog.events.BrowserEvent}
   */
  this.browserEvent = browserEvent;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.coordinate_ = null;

  /**
   * @private
   * @type {ol.Pixel}
   */
  this.pixel_ = null;

};
goog.inherits(ol.MapBrowserEvent, ol.MapEvent);

/**
 * @return {ol.Coordinate} Coordinate.
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.getCoordinate = function() {
  if (goog.isNull(this.coordinate_)) {
    this.coordinate_ = this.map.getEventCoordinate(
        this.browserEvent.getBrowserEvent());
  }
  return this.coordinate_;
};

/**
 * Get pixel offset of the event from the top-left corner of the map viewport.
 * @return {ol.Pixel} Pixel offset.
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.getPixel = function() {
  if (goog.isNull(this.pixel_)) {
    this.pixel_ = this.map.getEventPixel(this.browserEvent.getBrowserEvent());
  }
  return this.pixel_;
};

/**
 * Prevents the default browser action.
 * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
 * @override
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.preventDefault = function() {
  goog.base(this, 'preventDefault');
  this.browserEvent.preventDefault();
};

/**
 * Prevents further propagation of the current event.
 * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
 * @override
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.stopPropagation = function() {
  goog.base(this, 'stopPropagation');
  this.browserEvent.stopPropagation();
};

/**
 * @param {ol.Map} map The map with the viewport to listen to events on.
 * @constructor
 * @extends {goog.events.EventTarget}
 */
ol.MapBrowserEventHandler = function(map) {

  goog.base(this);

  /**
   * This is the element that we will listen to the real events on.
   * @type {ol.Map}
   * @private
   */
  this.map_ = map;

  /**
   * @type {number}
   * @private
   */
  this.clickTimeoutId_ = 0;

  /**
   * @type {boolean}
   * @private
   */
  this.dragged_ = false;

  /**
   * @type {Array.<number>}
   * @private
   */
  this.dragListenerKeys_ = null;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.mousedownListenerKey_ = null;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.pointerdownListenerKey_ = null;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.touchstartListenerKey_ = null;

  /**
   * @type {goog.events.BrowserEvent}
   * @private
   */
  this.down_ = null;

  var element = this.map_.getViewport();

  this.relayedListenerKeys_ = [
    goog.events.listen(element,
        goog.events.EventType.MOUSEMOVE,
        this.relayEvent_, false, this),
    goog.events.listen(element,
        goog.events.EventType.CLICK,
        this.relayEvent_, false, this)
  ];

  this.mousedownListenerKey_ = goog.events.listen(element,
      goog.events.EventType.MOUSEDOWN,
      this.handleMouseDown_, false, this);

  this.pointerdownListenerKey_ = goog.events.listen(element,
      goog.events.EventType.MSPOINTERDOWN,
      this.handlePointerDown_, false, this);

  this.touchstartListenerKey_ = goog.events.listen(element,
      goog.events.EventType.TOUCHSTART,
      this.handleTouchStart_, false, this);

};
goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.emulateClick_ = function(browserEvent) {
  if (this.clickTimeoutId_ !== 0) {
    // double-click
    goog.global.clearTimeout(this.clickTimeoutId_);
    this.clickTimeoutId_ = 0;
    var newEvent = new ol.MapBrowserEvent(
        ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, browserEvent);
    this.dispatchEvent(newEvent);
  } else {
    // click
    this.clickTimeoutId_ = goog.global.setTimeout(goog.bind(function() {
      this.clickTimeoutId_ = 0;
      var newEvent = new ol.MapBrowserEvent(
          ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, browserEvent);
      this.dispatchEvent(newEvent);
    }, this), 250);
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleMouseUp_ = function(browserEvent) {
  if (this.down_) {
    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
    this.dragListenerKeys_ = null;
    if (this.dragged_) {
      var newEvent = new ol.MapBrowserEvent(
          ol.MapBrowserEvent.EventType.DRAGEND, this.map_, browserEvent);
      this.dispatchEvent(newEvent);
      this.down_ = null;
    } else if (browserEvent.isMouseActionButton()) {
      this.emulateClick_(browserEvent);
    }
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleMouseDown_ = function(browserEvent) {
  if (!goog.isNull(this.pointerdownListenerKey_)) {
    // mouse device detected - unregister the pointerdown and touchstart
    // listeners
    goog.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;

    goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_));
    goog.events.unlistenByKey(this.touchstartListenerKey_);
    this.touchstartListenerKey_ = null;
  }

  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.DOWN, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
  this.down_ = browserEvent;
  this.dragged_ = false;
  this.dragListenerKeys_ = [
    goog.events.listen(goog.global.document, goog.events.EventType.MOUSEMOVE,
        this.handleMouseMove_, false, this),
    goog.events.listen(goog.global.document, goog.events.EventType.MOUSEUP,
        this.handleMouseUp_, false, this)
  ];
  // prevent browser image dragging with the dom renderer
  browserEvent.preventDefault();
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) {
  var newEvent;
  if (!this.dragged_) {
    this.dragged_ = true;
    newEvent = new ol.MapBrowserEvent(
        ol.MapBrowserEvent.EventType.DRAGSTART, this.map_, this.down_);
    this.dispatchEvent(newEvent);
  }
  newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.DRAG, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
    function(browserEvent) {
  if (!goog.isNull(this.mousedownListenerKey_)) {
    // pointer device detected - unregister the mousedown and touchstart
    // listeners
    goog.events.unlistenByKey(this.mousedownListenerKey_);
    this.mousedownListenerKey_ = null;

    goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_));
    goog.events.unlistenByKey(this.touchstartListenerKey_);
    this.touchstartListenerKey_ = null;
  }

  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent);
  this.dispatchEvent(newEvent);

  this.down_ = browserEvent;
  this.dragged_ = false;
  this.dragListenerKeys_ = [
    goog.events.listen(goog.global.document,
        goog.events.EventType.MSPOINTERMOVE,
        this.handlePointerMove_, false, this),
    goog.events.listen(goog.global.document, goog.events.EventType.MSPOINTERUP,
        this.handlePointerUp_, false, this)
  ];

  // FIXME check if/when this is necessary
  // prevent context menu
  browserEvent.preventDefault();
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerMove_ =
    function(browserEvent) {
  // Fix IE10 on windows Surface : When you tap the tablet, it triggers
  // multiple pointermove events between pointerdown and pointerup with
  // the exact same coordinates of the pointerdown event. To avoid a
  // 'false' touchmove event to be dispatched , we test if the pointermove
  // event has different coordinates.
  if (browserEvent.clientX != this.down_.clientX ||
      browserEvent.clientY != this.down_.clientY) {
    this.dragged_ = true;
    var newEvent = new ol.MapBrowserEvent(
        ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent);
    this.dispatchEvent(newEvent);
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(browserEvent) {
  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
  goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
  if (!this.dragged_) {
    goog.asserts.assert(!goog.isNull(this.down_));
    this.emulateClick_(this.down_);
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) {
  if (!goog.isNull(this.mousedownListenerKey_)) {
    // touch device detected - unregister the mousedown and pointerdown
    // listeners
    goog.events.unlistenByKey(this.mousedownListenerKey_);
    this.mousedownListenerKey_ = null;

    goog.asserts.assert(!goog.isNull(this.pointerdownListenerKey_));
    goog.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;
  }

  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent);
  this.dispatchEvent(newEvent);

  this.down_ = browserEvent;
  this.dragged_ = false;
  this.dragListenerKeys_ = [
    goog.events.listen(goog.global.document, goog.events.EventType.TOUCHMOVE,
        this.handleTouchMove_, false, this),
    goog.events.listen(goog.global.document, goog.events.EventType.TOUCHEND,
        this.handleTouchEnd_, false, this)
  ];

  // FIXME check if/when this is necessary
  browserEvent.preventDefault();
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleTouchMove_ = function(browserEvent) {
  this.dragged_ = true;
  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleTouchEnd_ = function(browserEvent) {
  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
  goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
  if (!this.dragged_) {
    goog.asserts.assert(!goog.isNull(this.down_));
    this.emulateClick_(this.down_);
  }
};

/**
 * FIXME empty description for jsdoc
 */
ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
  if (!goog.isNull(this.relayedListenerKeys_)) {
    goog.array.forEach(this.relayedListenerKeys_, goog.events.unlistenByKey);
    this.relayedListenerKeys_ = null;
  }
  if (!goog.isNull(this.mousedownListenerKey_)) {
    goog.events.unlistenByKey(this.mousedownListenerKey_);
    this.mousedownListenerKey_ = null;
  }
  if (!goog.isNull(this.pointerdownListenerKey_)) {
    goog.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;
  }
  if (!goog.isNull(this.touchstartListenerKey_)) {
    goog.events.unlistenByKey(this.touchstartListenerKey_);
    this.touchstartListenerKey_ = null;
  }
  if (!goog.isNull(this.dragListenerKeys_)) {
    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
    this.dragListenerKeys_ = null;
  }
  goog.base(this, 'disposeInternal');
};

/**
 * Wrap and relay a browser event.  Note that this requires that the type
 * string for the MapBrowserEvent matches the BrowserEvent type.
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.relayEvent_ = function(browserEvent) {
  this.dispatchEvent(new ol.MapBrowserEvent(
      browserEvent.type, this.map_, browserEvent));
};

/**
 * Constants for event names.
 * @enum {string}
 */
ol.MapBrowserEvent.EventType = {
  CLICK: goog.events.EventType.CLICK,
  DBLCLICK: goog.events.EventType.DBLCLICK,
  MOUSEMOVE: goog.events.EventType.MOUSEMOVE,
  DOWN: 'down',
  DRAGSTART: 'dragstart',
  DRAG: 'drag',
  DRAGEND: 'dragend',
  SINGLECLICK: 'singleclick',
  TOUCHSTART: goog.events.EventType.TOUCHSTART,
  TOUCHMOVE: goog.events.EventType.TOUCHMOVE,
  TOUCHEND: goog.events.EventType.TOUCHEND
};
<commit_msg>Forbid right click to trigger singleclick events on IE10<commit_after>goog.provide('ol.MapBrowserEvent');
goog.provide('ol.MapBrowserEvent.EventType');
goog.provide('ol.MapBrowserEventHandler');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.events.BrowserEvent');
goog.require('goog.events.EventTarget');
goog.require('goog.events.EventType');
goog.require('ol.Coordinate');
goog.require('ol.FrameState');
goog.require('ol.MapEvent');
goog.require('ol.Pixel');

/**
 * @constructor
 * @extends {ol.MapEvent}
 * @param {string} type Event type.
 * @param {ol.Map} map Map.
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @param {?ol.FrameState=} opt_frameState Frame state.
 * @todo stability experimental
 */
ol.MapBrowserEvent = function(type, map, browserEvent, opt_frameState) {

  goog.base(this, type, map, opt_frameState);

  /**
   * @type {goog.events.BrowserEvent}
   */
  this.browserEvent = browserEvent;

  /**
   * @private
   * @type {ol.Coordinate}
   */
  this.coordinate_ = null;

  /**
   * @private
   * @type {ol.Pixel}
   */
  this.pixel_ = null;

};
goog.inherits(ol.MapBrowserEvent, ol.MapEvent);

/**
 * @return {ol.Coordinate} Coordinate.
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.getCoordinate = function() {
  if (goog.isNull(this.coordinate_)) {
    this.coordinate_ = this.map.getEventCoordinate(
        this.browserEvent.getBrowserEvent());
  }
  return this.coordinate_;
};

/**
 * Get pixel offset of the event from the top-left corner of the map viewport.
 * @return {ol.Pixel} Pixel offset.
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.getPixel = function() {
  if (goog.isNull(this.pixel_)) {
    this.pixel_ = this.map.getEventPixel(this.browserEvent.getBrowserEvent());
  }
  return this.pixel_;
};

/**
 * Prevents the default browser action.
 * @see https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault
 * @override
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.preventDefault = function() {
  goog.base(this, 'preventDefault');
  this.browserEvent.preventDefault();
};

/**
 * Prevents further propagation of the current event.
 * @see https://developer.mozilla.org/en-US/docs/Web/API/event.stopPropagation
 * @override
 * @todo stability experimental
 */
ol.MapBrowserEvent.prototype.stopPropagation = function() {
  goog.base(this, 'stopPropagation');
  this.browserEvent.stopPropagation();
};

/**
 * @param {ol.Map} map The map with the viewport to listen to events on.
 * @constructor
 * @extends {goog.events.EventTarget}
 */
ol.MapBrowserEventHandler = function(map) {

  goog.base(this);

  /**
   * This is the element that we will listen to the real events on.
   * @type {ol.Map}
   * @private
   */
  this.map_ = map;

  /**
   * @type {number}
   * @private
   */
  this.clickTimeoutId_ = 0;

  /**
   * @type {boolean}
   * @private
   */
  this.dragged_ = false;

  /**
   * @type {Array.<number>}
   * @private
   */
  this.dragListenerKeys_ = null;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.mousedownListenerKey_ = null;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.pointerdownListenerKey_ = null;

  /**
   * @type {goog.events.Key}
   * @private
   */
  this.touchstartListenerKey_ = null;

  /**
   * @type {goog.events.BrowserEvent}
   * @private
   */
  this.down_ = null;

  var element = this.map_.getViewport();

  this.relayedListenerKeys_ = [
    goog.events.listen(element,
        goog.events.EventType.MOUSEMOVE,
        this.relayEvent_, false, this),
    goog.events.listen(element,
        goog.events.EventType.CLICK,
        this.relayEvent_, false, this)
  ];

  this.mousedownListenerKey_ = goog.events.listen(element,
      goog.events.EventType.MOUSEDOWN,
      this.handleMouseDown_, false, this);

  this.pointerdownListenerKey_ = goog.events.listen(element,
      goog.events.EventType.MSPOINTERDOWN,
      this.handlePointerDown_, false, this);

  this.touchstartListenerKey_ = goog.events.listen(element,
      goog.events.EventType.TOUCHSTART,
      this.handleTouchStart_, false, this);

};
goog.inherits(ol.MapBrowserEventHandler, goog.events.EventTarget);

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.emulateClick_ = function(browserEvent) {
  if (this.clickTimeoutId_ !== 0) {
    // double-click
    goog.global.clearTimeout(this.clickTimeoutId_);
    this.clickTimeoutId_ = 0;
    var newEvent = new ol.MapBrowserEvent(
        ol.MapBrowserEvent.EventType.DBLCLICK, this.map_, browserEvent);
    this.dispatchEvent(newEvent);
  } else {
    // click
    this.clickTimeoutId_ = goog.global.setTimeout(goog.bind(function() {
      this.clickTimeoutId_ = 0;
      var newEvent = new ol.MapBrowserEvent(
          ol.MapBrowserEvent.EventType.SINGLECLICK, this.map_, browserEvent);
      this.dispatchEvent(newEvent);
    }, this), 250);
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleMouseUp_ = function(browserEvent) {
  if (this.down_) {
    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
    this.dragListenerKeys_ = null;
    if (this.dragged_) {
      var newEvent = new ol.MapBrowserEvent(
          ol.MapBrowserEvent.EventType.DRAGEND, this.map_, browserEvent);
      this.dispatchEvent(newEvent);
      this.down_ = null;
    } else if (browserEvent.isMouseActionButton()) {
      this.emulateClick_(browserEvent);
    }
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleMouseDown_ = function(browserEvent) {
  if (!goog.isNull(this.pointerdownListenerKey_)) {
    // mouse device detected - unregister the pointerdown and touchstart
    // listeners
    goog.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;

    goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_));
    goog.events.unlistenByKey(this.touchstartListenerKey_);
    this.touchstartListenerKey_ = null;
  }

  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.DOWN, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
  this.down_ = browserEvent;
  this.dragged_ = false;
  this.dragListenerKeys_ = [
    goog.events.listen(goog.global.document, goog.events.EventType.MOUSEMOVE,
        this.handleMouseMove_, false, this),
    goog.events.listen(goog.global.document, goog.events.EventType.MOUSEUP,
        this.handleMouseUp_, false, this)
  ];
  // prevent browser image dragging with the dom renderer
  browserEvent.preventDefault();
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleMouseMove_ = function(browserEvent) {
  var newEvent;
  if (!this.dragged_) {
    this.dragged_ = true;
    newEvent = new ol.MapBrowserEvent(
        ol.MapBrowserEvent.EventType.DRAGSTART, this.map_, this.down_);
    this.dispatchEvent(newEvent);
  }
  newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.DRAG, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerDown_ =
    function(browserEvent) {
  if (!goog.isNull(this.mousedownListenerKey_)) {
    // pointer device detected - unregister the mousedown and touchstart
    // listeners
    goog.events.unlistenByKey(this.mousedownListenerKey_);
    this.mousedownListenerKey_ = null;

    goog.asserts.assert(!goog.isNull(this.touchstartListenerKey_));
    goog.events.unlistenByKey(this.touchstartListenerKey_);
    this.touchstartListenerKey_ = null;
  }

  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent);
  this.dispatchEvent(newEvent);

  this.down_ = browserEvent;
  this.dragged_ = false;
  this.dragListenerKeys_ = [
    goog.events.listen(goog.global.document,
        goog.events.EventType.MSPOINTERMOVE,
        this.handlePointerMove_, false, this),
    goog.events.listen(goog.global.document, goog.events.EventType.MSPOINTERUP,
        this.handlePointerUp_, false, this)
  ];

  // FIXME check if/when this is necessary
  // prevent context menu
  browserEvent.preventDefault();
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerMove_ =
    function(browserEvent) {
  // Fix IE10 on windows Surface : When you tap the tablet, it triggers
  // multiple pointermove events between pointerdown and pointerup with
  // the exact same coordinates of the pointerdown event. To avoid a
  // 'false' touchmove event to be dispatched , we test if the pointermove
  // event has different coordinates.
  if (browserEvent.clientX != this.down_.clientX ||
      browserEvent.clientY != this.down_.clientY) {
    this.dragged_ = true;
    var newEvent = new ol.MapBrowserEvent(
        ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent);
    this.dispatchEvent(newEvent);
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handlePointerUp_ = function(browserEvent) {
  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
  goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);

  // We emulate click event only on mouse left button action,
  // on touch devices isMouseActionButton always returns true.
  if (!this.dragged_ && browserEvent.isMouseActionButton()) {
    goog.asserts.assert(!goog.isNull(this.down_));
    this.emulateClick_(this.down_);
  }
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleTouchStart_ = function(browserEvent) {
  if (!goog.isNull(this.mousedownListenerKey_)) {
    // touch device detected - unregister the mousedown and pointerdown
    // listeners
    goog.events.unlistenByKey(this.mousedownListenerKey_);
    this.mousedownListenerKey_ = null;

    goog.asserts.assert(!goog.isNull(this.pointerdownListenerKey_));
    goog.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;
  }

  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHSTART, this.map_, browserEvent);
  this.dispatchEvent(newEvent);

  this.down_ = browserEvent;
  this.dragged_ = false;
  this.dragListenerKeys_ = [
    goog.events.listen(goog.global.document, goog.events.EventType.TOUCHMOVE,
        this.handleTouchMove_, false, this),
    goog.events.listen(goog.global.document, goog.events.EventType.TOUCHEND,
        this.handleTouchEnd_, false, this)
  ];

  // FIXME check if/when this is necessary
  browserEvent.preventDefault();
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleTouchMove_ = function(browserEvent) {
  this.dragged_ = true;
  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHMOVE, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
};

/**
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.handleTouchEnd_ = function(browserEvent) {
  var newEvent = new ol.MapBrowserEvent(
      ol.MapBrowserEvent.EventType.TOUCHEND, this.map_, browserEvent);
  this.dispatchEvent(newEvent);
  goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
  if (!this.dragged_) {
    goog.asserts.assert(!goog.isNull(this.down_));
    this.emulateClick_(this.down_);
  }
};

/**
 * FIXME empty description for jsdoc
 */
ol.MapBrowserEventHandler.prototype.disposeInternal = function() {
  if (!goog.isNull(this.relayedListenerKeys_)) {
    goog.array.forEach(this.relayedListenerKeys_, goog.events.unlistenByKey);
    this.relayedListenerKeys_ = null;
  }
  if (!goog.isNull(this.mousedownListenerKey_)) {
    goog.events.unlistenByKey(this.mousedownListenerKey_);
    this.mousedownListenerKey_ = null;
  }
  if (!goog.isNull(this.pointerdownListenerKey_)) {
    goog.events.unlistenByKey(this.pointerdownListenerKey_);
    this.pointerdownListenerKey_ = null;
  }
  if (!goog.isNull(this.touchstartListenerKey_)) {
    goog.events.unlistenByKey(this.touchstartListenerKey_);
    this.touchstartListenerKey_ = null;
  }
  if (!goog.isNull(this.dragListenerKeys_)) {
    goog.array.forEach(this.dragListenerKeys_, goog.events.unlistenByKey);
    this.dragListenerKeys_ = null;
  }
  goog.base(this, 'disposeInternal');
};

/**
 * Wrap and relay a browser event.  Note that this requires that the type
 * string for the MapBrowserEvent matches the BrowserEvent type.
 * @param {goog.events.BrowserEvent} browserEvent Browser event.
 * @private
 */
ol.MapBrowserEventHandler.prototype.relayEvent_ = function(browserEvent) {
  this.dispatchEvent(new ol.MapBrowserEvent(
      browserEvent.type, this.map_, browserEvent));
};

/**
 * Constants for event names.
 * @enum {string}
 */
ol.MapBrowserEvent.EventType = {
  CLICK: goog.events.EventType.CLICK,
  DBLCLICK: goog.events.EventType.DBLCLICK,
  MOUSEMOVE: goog.events.EventType.MOUSEMOVE,
  DOWN: 'down',
  DRAGSTART: 'dragstart',
  DRAG: 'drag',
  DRAGEND: 'dragend',
  SINGLECLICK: 'singleclick',
  TOUCHSTART: goog.events.EventType.TOUCHSTART,
  TOUCHMOVE: goog.events.EventType.TOUCHMOVE,
  TOUCHEND: goog.events.EventType.TOUCHEND
};
<|endoftext|>


<commit_before>// Potential optimization is use ES3 across browser and Meteor, and have one compile
// NOTE: We must obey the order that the files are handed to us (otherwise Meteor's guarantees about load order aren't true)

var fs = Npm.require('fs');
var Fiber = Npm.require('fibers');
var Future = Npm.require('fibers/future');
var ts = Npm.require('ts-compiler');
var storage = Npm.require('node-persist');

var fsStat = Future.wrap(fs.stat);
storage.initSync({
  dir: 'typescript-cache'
});

this.archs = this.archs || {};
function initArch(archName) {
  archs[archName] = {name: archName};
  var arch = archs[archName];
  arch.modTimes = {};
  arch.cachedErrorReplays = [];
  resetCompilationScopedArch(arch);
}

var tsErrorRegex = /(.*[.]ts)\((\d+),(\d)+\): (.+)/;
var placeholderFileName = "main.tsc_placeholder.ts";

checkForPlaceholderFile();

Plugin.registerSourceHandler("ts", function (compileStep) {
  if (typeof(archs[compileStep.arch]) === 'undefined') {
    initArch(compileStep.arch);
  }

  if (compileStep.inputPath !== placeholderFileName) {
    handleSourceFile(compileStep);
    return;
  }

  // Typically, Meteor hands us all of the files pertaining to an arch in clumps (i.e. all files for browser, then all files for os), so most compiles are only two phases (client and server).
  for (var archName in archs) {
    var arch = archs[archName];
    if (arch.compileSteps.length == 0) {
      continue;
    }

    var hadMod = checkAgainstModTime(arch);
    compile(arch, compileStep, hadMod);
    resetCompilationScopedArch(arch);
  }
});

// Save the file input path, and return back to Meteor
function handleSourceFile(compileStep) {
  var arch = archs[compileStep.arch];

  // This ensures the placeholder file exists. If the placeholder file is absent, we would have never triggered compilation, so the inputPaths would never have gotten cleared.
  if (arch.inputPaths.indexOf(compileStep.inputPath) != -1) {
    checkForPlaceholderFile(compileStep);
    return;
  }

  arch.inputPaths.push(compileStep.inputPath);
  arch.compileSteps.push(compileStep);
  arch.fullPathToCompileSteps[compileStep._fullInputPath] = compileStep;
}

function checkAgainstModTime(arch) {
  var hadModifications = false;
  arch.inputPaths.forEach(function(path) {
    stats = fsStat(path).wait();
    if (typeof(arch.modTimes[path]) === 'undefined' || arch.modTimes[path].toString() !== stats.mtime.toString()) {
      hadModifications = true;
    }

    arch.modTimes[path] = stats.mtime;
  });

  return hadModifications;
}

function compile(arch, placeholderCompileStep, hadModifications) {
  if (!hadModifications) {
    // Short-circuit via cache
    addJavaScriptFromCacheInOrder(arch);

    // Replay errors
    arch.cachedErrorReplays.forEach(function(errReplay) {
      recordError(errReplay.err, placeholderCompileStep, errReplay.errorNumber, arch, true);
    });

    return;
  }

  console.log("\nCompiling TypeScript " + arch.name + " files...");

  // Clear cached errors since we're about to re-compile
  arch.cachedErrorReplays = [];
  var errorCount = 0;
  var compileOptions = {
    'target': (arch.name === 'browser' ? 'ES3' : 'ES5'),
    'skipWrite': true,
    'removeComments': true
  };

  // This is synchronous (and our callback will get called multiple times if there are errors)
  ts.compile(arch.inputPaths, compileOptions, function(err, results) {
    if (err) {
      // TODO
      recordError(err, placeholderCompileStep, ++errorCount, arch, false);
      if (typeof(results) === 'undefined') {
        return;
      }
    }

    results.forEach(function(res) {
      // res.name is the theoretically-generated js filename
      var tsFullPath = res.name.substr(0, res.name.length-2) + "ts";
      var compileStep = arch.fullPathToCompileSteps[tsFullPath];
      storage.setItem(b64encode(compileStep.inputPath), res.text || "");
    });
  });

  addJavaScriptFromCacheInOrder(arch);
}

function addJavaScriptFromCacheInOrder(arch) {
  arch.compileSteps.forEach(function(compileStep) {
    console.log("Adding " + compileStep.inputPath);
    compileStep.addJavaScript({
      path: compileStep.inputPath + ".js",
      sourcePath: compileStep.inputPath,
      data: storage.getItem(b64encode(compileStep.inputPath)) || ""
    })
  });
}

function recordError(err, placeholderCompileStep, errorNumber, arch, isFromCache) {
  if (!isFromCache) {
    arch.cachedErrorReplays.push({err: err, errNumber: errorNumber});
  }

  if (match = tsErrorRegex.exec(err.toString())) {
    var compileStep = arch.fullPathToCompileSteps[match[1]];
    if (compileStep) {
      compileStep.error({
        message: match[4],
        sourcePath: match[1],
        line: match[2],
        column: match[3]
      });
      return;
    }
  }

  placeholderCompileStep.error({
    message: err.toString(),
    sourcePath: placeholderCompileStep.inputPath,
    line: errorNumber,
    column: 1
  });
}

function resetCompilationScopedArch(arch) {
  arch.inputPaths = [];
  arch.compileSteps = [];
  arch.fullPathToCompileSteps = {};
}

function checkForPlaceholderFile(compileStep) {
  if (!fs.existsSync(placeholderFileName)) {
    fs.writeFileSync(placeholderFileName, "");
    errorMsg = "Missing required \"" + placeholderFileName + "\" file; it has been created (make sure to add it to your .gitignore). You may have to touch a .ts file to trigger another compilation.";
    if (typeof(compileStep) !== 'undefined') {
      compileStep.error({message: errorMsg});
    } else {
      console.error(errorMsg);
    }
  }
}

function b64encode(s) {
  return new Buffer(s).toString('base64');
}
<commit_msg>Add comment<commit_after>// NOTE: This has inherent issues in a codebase of differing source languages.
// E.g. imagine I have lib/a.ts, something/foo.coffee (which depends on a.ts),
// and a bunch of other .ts files. The problem is we batch all of the .ts
// together and execute the TypeScript compiler when we get the latest .ts
// file, but by then the coffee compiler has already been compiled.
// At the moment, Meteor takes addJavaScript calls and places them directly
// into the index.html in that order, so the coffee will come before
// our lib/a.ts.js.

// TODO Potential optimization is use ES3 across browser and Meteor, and have one compile

var fs = Npm.require('fs');
var Fiber = Npm.require('fibers');
var Future = Npm.require('fibers/future');
var ts = Npm.require('ts-compiler');
var storage = Npm.require('node-persist');

var fsStat = Future.wrap(fs.stat);
storage.initSync({
  dir: 'typescript-cache'
});

this.archs = this.archs || {};
function initArch(archName) {
  archs[archName] = {name: archName};
  var arch = archs[archName];
  arch.modTimes = {};
  arch.cachedErrorReplays = [];
  resetCompilationScopedArch(arch);
}

var tsErrorRegex = /(.*[.]ts)\((\d+),(\d)+\): (.+)/;
var placeholderFileName = "main.tsc_placeholder.ts";

checkForPlaceholderFile();

Plugin.registerSourceHandler("ts", function (compileStep) {
  if (typeof(archs[compileStep.arch]) === 'undefined') {
    initArch(compileStep.arch);
  }

  if (compileStep.inputPath !== placeholderFileName) {
    handleSourceFile(compileStep);
    return;
  }

  // Typically, Meteor hands us all of the files pertaining to an arch in clumps (i.e. all files for browser, then all files for os), so most compiles are only two phases (client and server).
  for (var archName in archs) {
    var arch = archs[archName];
    if (arch.compileSteps.length == 0) {
      continue;
    }

    var hadMod = checkAgainstModTime(arch);
    compile(arch, compileStep, hadMod);
    resetCompilationScopedArch(arch);
  }
});

// Save the file input path, and return back to Meteor
function handleSourceFile(compileStep) {
  var arch = archs[compileStep.arch];

  // This ensures the placeholder file exists. If the placeholder file is absent, we would have never triggered compilation, so the inputPaths would never have gotten cleared.
  if (arch.inputPaths.indexOf(compileStep.inputPath) != -1) {
    checkForPlaceholderFile(compileStep);
    return;
  }

  arch.inputPaths.push(compileStep.inputPath);
  arch.compileSteps.push(compileStep);
  arch.fullPathToCompileSteps[compileStep._fullInputPath] = compileStep;
}

function checkAgainstModTime(arch) {
  var hadModifications = false;
  arch.inputPaths.forEach(function(path) {
    stats = fsStat(path).wait();
    if (typeof(arch.modTimes[path]) === 'undefined' || arch.modTimes[path].toString() !== stats.mtime.toString()) {
      hadModifications = true;
    }

    arch.modTimes[path] = stats.mtime;
  });

  return hadModifications;
}

function compile(arch, placeholderCompileStep, hadModifications) {
  if (!hadModifications) {
    // Short-circuit via cache
    addJavaScriptFromCacheInOrder(arch);

    // Replay errors
    arch.cachedErrorReplays.forEach(function(errReplay) {
      recordError(errReplay.err, placeholderCompileStep, errReplay.errorNumber, arch, true);
    });

    return;
  }

  console.log("\nCompiling TypeScript " + arch.name + " files...");

  // Clear cached errors since we're about to re-compile
  arch.cachedErrorReplays = [];
  var errorCount = 0;
  var compileOptions = {
    'target': (arch.name === 'browser' ? 'ES3' : 'ES5'),
    'skipWrite': true,
    'removeComments': true
  };

  // This is synchronous (and our callback will get called multiple times if there are errors)
  ts.compile(arch.inputPaths, compileOptions, function(err, results) {
    if (err) {
      // TODO
      recordError(err, placeholderCompileStep, ++errorCount, arch, false);
      if (typeof(results) === 'undefined') {
        return;
      }
    }

    results.forEach(function(res) {
      // res.name is the theoretically-generated js filename
      var tsFullPath = res.name.substr(0, res.name.length-2) + "ts";
      var compileStep = arch.fullPathToCompileSteps[tsFullPath];
      storage.setItem(b64encode(compileStep.inputPath), res.text || "");
    });
  });

  addJavaScriptFromCacheInOrder(arch);
}

function addJavaScriptFromCacheInOrder(arch) {
  arch.compileSteps.forEach(function(compileStep) {
    console.log("Adding " + compileStep.inputPath);
    compileStep.addJavaScript({
      path: compileStep.inputPath + ".js",
      sourcePath: compileStep.inputPath,
      data: storage.getItem(b64encode(compileStep.inputPath)) || ""
    })
  });
}

function recordError(err, placeholderCompileStep, errorNumber, arch, isFromCache) {
  if (!isFromCache) {
    arch.cachedErrorReplays.push({err: err, errNumber: errorNumber});
  }

  if (match = tsErrorRegex.exec(err.toString())) {
    var compileStep = arch.fullPathToCompileSteps[match[1]];
    if (compileStep) {
      compileStep.error({
        message: match[4],
        sourcePath: match[1],
        line: match[2],
        column: match[3]
      });
      return;
    }
  }

  placeholderCompileStep.error({
    message: err.toString(),
    sourcePath: placeholderCompileStep.inputPath,
    line: errorNumber,
    column: 1
  });
}

function resetCompilationScopedArch(arch) {
  arch.inputPaths = [];
  arch.compileSteps = [];
  arch.fullPathToCompileSteps = {};
}

function checkForPlaceholderFile(compileStep) {
  if (!fs.existsSync(placeholderFileName)) {
    fs.writeFileSync(placeholderFileName, "");
    errorMsg = "Missing required \"" + placeholderFileName + "\" file; it has been created (make sure to add it to your .gitignore). You may have to touch a .ts file to trigger another compilation.";
    if (typeof(compileStep) !== 'undefined') {
      compileStep.error({message: errorMsg});
    } else {
      console.error(errorMsg);
    }
  }
}

function b64encode(s) {
  return new Buffer(s).toString('base64');
}
<|endoftext|>


<commit_before>goog.provide('ol.style.Text');

/**
 * @constructor
 * @param {ol.style.TextOptions} options Options.
 */
ol.style.Text = function(options) {

  /**
   * @type {string|undefined}
   */
  this.font = options.font;

  /**
   * @type {string|undefined}
   */
  this.text = options.text;

  /**
   * @type {string|undefined}
   */
  this.textAlign = options.textAlign;

  /**
   * @type {string|undefined}
   */
  this.textBaseline = options.textBaseline;

  /**
   * @type {number}
   */
  this.width = options.width;
};

/**
 * @param {ol.style.Text} textStyle1 Text style 1.
 * @param {ol.style.Text} textStyle2 Text style 2.
 * @return {boolean} Equals.
 */
ol.style.Text.equals = function(textStyle1, textStyle2) {
  if (!goog.isNull(textStyle1)) {
    if (!goog.isNull(textStyle2)) {
      return textStyle1 === textStyle2 || (
          textStyle1.font == textStyle2.font &&
          textStyle1.text == textStyle2.text &&
          textStyle1.textAlign == textStyle2.textAlign &&
          textStyle1.textBaseline == textStyle2.textBaseline);
    } else {
      return false;
    }
  } else {
    if (!goog.isNull(textStyle2)) {
      return false;
    } else {
      return true;
    }
  }
};
<commit_msg>Remove unused ol.style.Text width property<commit_after>goog.provide('ol.style.Text');

/**
 * @constructor
 * @param {ol.style.TextOptions} options Options.
 */
ol.style.Text = function(options) {

  /**
   * @type {string|undefined}
   */
  this.font = options.font;

  /**
   * @type {string|undefined}
   */
  this.text = options.text;

  /**
   * @type {string|undefined}
   */
  this.textAlign = options.textAlign;

  /**
   * @type {string|undefined}
   */
  this.textBaseline = options.textBaseline;
};

/**
 * @param {ol.style.Text} textStyle1 Text style 1.
 * @param {ol.style.Text} textStyle2 Text style 2.
 * @return {boolean} Equals.
 */
ol.style.Text.equals = function(textStyle1, textStyle2) {
  if (!goog.isNull(textStyle1)) {
    if (!goog.isNull(textStyle2)) {
      return textStyle1 === textStyle2 || (
          textStyle1.font == textStyle2.font &&
          textStyle1.text == textStyle2.text &&
          textStyle1.textAlign == textStyle2.textAlign &&
          textStyle1.textBaseline == textStyle2.textBaseline);
    } else {
      return false;
    }
  } else {
    if (!goog.isNull(textStyle2)) {
      return false;
    } else {
      return true;
    }
  }
};
<|endoftext|>


<commit_before>// @flow
import type {Key} from 'reactive-di/interfaces/deps' // eslint-disable-line
import type {Adapter, Atom, Derivable} from 'reactive-di/interfaces/atom'
import type {IContext} from 'reactive-di/interfaces/internal'
import {atomKey} from 'reactive-di/core/common'

import type {
    MultiAsyncUpdate,
    MultiUpdate,
    MultiAsyncUpdateResult,

    SingleAsyncUpdate,
    SingleUpdate,
    SingleAsyncUpdateResult
} from 'reactive-di/interfaces/updater'
import promiseToObservable from 'reactive-di/utils/promiseToObservable'
import debugName from 'reactive-di/utils/debugName'

function cloneInstance<Instance: Object>(target: Instance, props: $Shape<Instance>): Instance {
    return new target.constructor({...target, ...props})
}

type NormalizedSyncUpdate = [Key, Object]

class NormalizedMultiAsyncUpdate {
    type: 'multi' = 'multi'
    op: MultiAsyncUpdate
    constructor(op: MultiAsyncUpdate) {
        this.op = op
    }
}

class NormalizedSingleAsyncUpdate {
    type: 'single' = 'single'
    target: Key
    op: SingleAsyncUpdate

    constructor(target: Key, op: SingleAsyncUpdate) {
        this.op = op
        this.target = target
    }
}

type NormalizedAsyncUpdate = NormalizedMultiAsyncUpdate | NormalizedSingleAsyncUpdate

class SingleUpdateBucket {
    type: 'single' = 'single'
    syncs: NormalizedSyncUpdate[]
    asyncs: NormalizedAsyncUpdate[]

    constructor(rawUpdate: SingleUpdate, target: Key) {
        if (!target) {
            throw new Error('Target Class is not set for SingleUpdateBucket')
        }
        if (Array.isArray(rawUpdate)) {
            if (rawUpdate.length !== 2) {
                throw new Error('Wrong update rec: need a tuple [SingleSyncUpdate, SingleAsyncUpdate]')
            }
            this.syncs = [[target, (rawUpdate[0]: Object)]]
            this.asyncs = [new NormalizedSingleAsyncUpdate(target, rawUpdate[1])]
        } else if (typeof rawUpdate === 'function') {
            this.asyncs = [new NormalizedSingleAsyncUpdate(target, rawUpdate)]
            this.syncs = []
        } else {
            this.asyncs = []
            this.syncs = [[target, rawUpdate]]
        }
    }
}

class MultiUpdateBucket {
    type: 'multi' = 'multi'
    syncs: NormalizedSyncUpdate[]
    asyncs: NormalizedAsyncUpdate[]

    constructor(updates: MultiUpdate[]) {
        const asyncs: NormalizedAsyncUpdate[] = this.asyncs = []
        const syncs: NormalizedSyncUpdate[] = this.syncs = []
        for (let i = 0; i < updates.length; i++) {
            const update: MultiUpdate = updates[i]
            if (typeof update === 'function') {
                asyncs.push(new NormalizedMultiAsyncUpdate(update))
            } else if (Array.isArray(update)) {
                if (update.length !== 2) {
                    throw new Error(`Need a tuple: [Key, Object] in ${debugName(update)}`)
                }
                syncs.push(update)
            } else {
                if (update.constructor === Object) {
                    throw new Error(`Can't be a raw object: ${debugName(update)}`)
                }
                syncs.push([update.constructor, update])
            }
        }
    }
}

type UpdateBucket = SingleUpdateBucket | MultiUpdateBucket

type UpdaterStatusType = 'error' | 'pending' | 'complete'

export class UpdaterStatus {
    complete: boolean
    pending: boolean
    error: ?Error
    retry: ?(() => void)

    constructor(
        type?: UpdaterStatusType,
        error?: ?Error,
        retry?: ?() => void
    ) {
        this.complete = type === 'complete'
        this.pending = !type || type === 'pending'
        this.retry = retry || null
        this.error = error || null
    }
}

interface OperationErrorRec {
    error: Error;
    op: NormalizedAsyncUpdate;
}

type Update = MultiUpdate[] | SingleUpdate

class OperationObserver {
    _parentObserver: Observer<UpdateBucket, OperationErrorRec>
    _unsubscribe: () => void
    _cancel: () => void
    _op: NormalizedAsyncUpdate
    _target: ?Key

    constructor(
        parentObserver: Observer<UpdateBucket, OperationErrorRec>,
        unsubscribe: () => void,
        cancel: () => void,
        op: NormalizedAsyncUpdate
    ) {
        this._parentObserver = parentObserver
        this._unsubscribe = unsubscribe
        this._cancel = cancel
        this._op = op
    }

    next(ops: ?Update): void {
        if (ops) {
            this._parentObserver.next(
                this._op.type === 'single'
                    ? new SingleUpdateBucket((ops: any), this._op.target)
                    : new MultiUpdateBucket(((ops: any): MultiUpdate[]))
            )
        }
    }

    error(err: Error): void {
        // this._unsubscribe()
        this._cancel()
        this._parentObserver.error({
            error: err,
            op: this._op
        })
    }

    complete(ops?: Update): void {
        this._unsubscribe()
        if (!ops) {
            this._parentObserver.complete(null)
            return
        }

        this._parentObserver.complete(
            this._op.type === 'single'
                ? new SingleUpdateBucket((ops: any), this._op.target)
                : new MultiUpdateBucket(((ops: any): MultiUpdate[]))
        )
    }
}

class AsyncQeue {
    _maxQeueSize: number
    _qeue: NormalizedAsyncUpdate[] = []
    _subscriptions: Subscription[] = []
    _parentObserver: Observer<UpdateBucket, OperationErrorRec>

    // Readonly qeue size
    size: number = 0

    constructor(
        parentObserver: Observer<UpdateBucket, OperationErrorRec>,
        maxQeueSize: number = 1
    ) {
        this._maxQeueSize = maxQeueSize
        this._parentObserver = parentObserver
    }

    add(ops: NormalizedAsyncUpdate[]): void {
        this._qeue = this._qeue.concat(ops)
        this.size = this.size + ops.length
        this._run()
    }

    _run(): void {
        if (this.size === 0) {
            this._parentObserver.complete()
            return
        }
        if (this._subscriptions.length >= this._maxQeueSize) {
            return
        }
        const asyncUpdate: NormalizedAsyncUpdate = this._qeue.pop()
        let observable: Observable<Update, Error>
        const update: SingleAsyncUpdateResult | MultiAsyncUpdateResult = asyncUpdate.op()
        if (update.then) {
            observable = promiseToObservable(((update: any): Promise<Update>))
        } else if (update.subscribe) {
            observable = ((update: any): Observable<Update, Error>)
        } else {
            throw new Error(`Unknown operation: ${debugName(asyncUpdate.op)}`)
        }

        let subscription: Subscription

        const unsubscribe: () => void = () => this._removeSubscription(subscription)
        const cancel: () => void = () => this.cancel()

        const observer: Observer<Update, Error> = new OperationObserver(
            this._parentObserver,
            unsubscribe,
            cancel,
            asyncUpdate
        )

        subscription = observable.subscribe(observer)
        this._subscriptions.push(subscription)
    }

    _removeSubscription(item: Subscription): void {
        this.size = this.size - 1
        this._subscriptions = this._subscriptions.filter((target: Subscription) => target !== item)
        if (this.size) {
            this._run()
        }
    }

    cancel(): void {
        const subscriptions = this._subscriptions
        for (let i = 0, l = subscriptions.length; i < l; i++) {
            subscriptions[i].unsubscribe()
        }
        this._subscriptions = []
        this._qeue = []
        this.size = 0
    }
}

class UpdaterObserver {
    displayName: string
    status: Atom<UpdaterStatus>

    _di: IContext
    _adapter: Adapter
    _qeue: AsyncQeue
    _rollbackOnError: boolean

    _errorUpdates: NormalizedAsyncUpdate[] = []
    _pendingUpdates: NormalizedSyncUpdate[] = []

    constructor(
        di: IContext,
        maxSize: number,
        rollbackOnError: boolean,
        pending: boolean
    ) {
        this._di = di
        this._rollbackOnError = rollbackOnError
        this._adapter = di.adapter
        this._qeue = new AsyncQeue((this: Observer<UpdateBucket, OperationErrorRec>), maxSize)
        this.status = this._adapter.atom(new UpdaterStatus(pending ? 'pending' : 'complete'))
    }

    cancel(): void {
        this._errorUpdates = []
        this._qeue.cancel()
    }

    complete(updates: ?UpdateBucket): void {
        if (updates) {
            this.next(updates)
        } else if (this._qeue.size === 0) {
            this.status.set(new UpdaterStatus('complete'))
            this._pendingUpdates = []
        }
    }

    _retry: () => void = () => {
        if (this._errorUpdates.length) {
            this._qeue.add(this._errorUpdates)
            this._errorUpdates = []
        }
    };

    error({error, op}: OperationErrorRec) {
        this._errorUpdates.push(op)
        const onError = () => {
            this.status.set(new UpdaterStatus('error', error, this._retry))
            if (this._rollbackOnError) {
                this._updateSync(this._pendingUpdates, false)
            }
        }
        this._adapter.transact(onError)
        this._pendingUpdates = []
    }

    next({syncs, asyncs}: UpdateBucket): void {
        const adapter = this._adapter
        const qeue = this._qeue
        const status = this.status
        if (asyncs.length) {
            if (!status.get().pending) {
                status.set(new UpdaterStatus('pending'))
            }
            qeue.add(asyncs)
        }
        if (syncs.length) {
            adapter.transact(() => this._updateSync((syncs: NormalizedSyncUpdate[]), qeue.size > 0))
        }
        if (qeue.size === 0) {
            this._errorUpdates = []
        }
    }

    _updateSync(updates: NormalizedSyncUpdate[], needRollback: boolean): void {
        const di = this._di
        const pd = this._pendingUpdates
        for (let i = 0, l = updates.length; i < l; i++) {
            const rec = updates[i]
            if (!Array.isArray(rec)) {
                throw new Error('Wrong updates rec: must by an NormalizedSyncUpdate tuple')
            }
            const [key, props] = rec
            if (!key) {
                throw new Error('NormalizedSyncUpdate key can\'t be null')
            }
            if (!props) {
                throw new Error('NormalizedSyncUpdate props can\'t be null')
            }
            if (needRollback) {
                pd.push(rec)
            }
            const atom = props[atomKey] ? props[atomKey].value : di.val(key)
            atom.swap(cloneInstance, props)
        }
        if (!needRollback) {
            this.status.set(new UpdaterStatus('complete'))
            this._pendingUpdates = []
        }
    }
}
if (0) ((new UpdaterObserver(...(0: any))): Observer<UpdateBucket, OperationErrorRec>) // eslint-disable-line

export default class Updater {
    displayName: string
    status: Derivable<UpdaterStatus>

    _uo: UpdaterObserver

    static maxSize: number = 10
    static rollbackOnError: boolean = false
    static pending: boolean = false

    constructor(di: IContext) {
        const c = this.constructor
        this.displayName = `${di.displayName}#${debugName(c)}`
        this._uo = new UpdaterObserver(
            di,
            c.maxSize,
            c.rollbackOnError,
            c.pending
        )
        this._uo.displayName = this.displayName + '#updater'
        this.status = this._uo.status
        const unsubscribeUpdater = (isStopped: boolean) => {
            if (isStopped) {
                this._uo.cancel()
            }
        }
        di.stopped.react(unsubscribeUpdater, {
            skipFirst: true,
            once: true
        })
    }

    cancel(): Updater {
        this._uo.cancel()
        return this
    }

    setSingle(rawUpdate: SingleUpdate, target: Key): Updater {
        this._uo.next(new SingleUpdateBucket(rawUpdate, target))
        return this
    }

    set(updates: MultiUpdate[]): Updater {
        this._uo.next(new MultiUpdateBucket(updates))
        return this
    }
}
<commit_msg>some constructor changes fix<commit_after>// @flow
import type {Key} from 'reactive-di/interfaces/deps' // eslint-disable-line
import type {Adapter, Atom, Derivable} from 'reactive-di/interfaces/atom'
import type {IContext} from 'reactive-di/interfaces/internal'
import {atomKey} from 'reactive-di/core/common'

import type {
    MultiAsyncUpdate,
    MultiUpdate,
    MultiAsyncUpdateResult,

    SingleAsyncUpdate,
    SingleUpdate,
    SingleAsyncUpdateResult
} from 'reactive-di/interfaces/updater'
import promiseToObservable from 'reactive-di/utils/promiseToObservable'
import debugName from 'reactive-di/utils/debugName'

function cloneInstance<Instance: Object>(target: Instance, props: $Shape<Instance>): Instance {
    return props instanceof target.constructor
        ? props
        : new target.constructor({...target, ...props})
}

type NormalizedSyncUpdate = [Key, Object]

class NormalizedMultiAsyncUpdate {
    type: 'multi' = 'multi'
    op: MultiAsyncUpdate
    constructor(op: MultiAsyncUpdate) {
        this.op = op
    }
}

class NormalizedSingleAsyncUpdate {
    type: 'single' = 'single'
    target: Key
    op: SingleAsyncUpdate

    constructor(target: Key, op: SingleAsyncUpdate) {
        this.op = op
        this.target = target
    }
}

type NormalizedAsyncUpdate = NormalizedMultiAsyncUpdate | NormalizedSingleAsyncUpdate

class SingleUpdateBucket {
    type: 'single' = 'single'
    syncs: NormalizedSyncUpdate[]
    asyncs: NormalizedAsyncUpdate[]

    constructor(rawUpdate: SingleUpdate, target: Key) {
        if (!target) {
            throw new Error('Target Class is not set for SingleUpdateBucket')
        }
        if (Array.isArray(rawUpdate)) {
            if (rawUpdate.length !== 2) {
                throw new Error('Wrong update rec: need a tuple [SingleSyncUpdate, SingleAsyncUpdate]')
            }
            this.syncs = [[target, (rawUpdate[0]: Object)]]
            this.asyncs = [new NormalizedSingleAsyncUpdate(target, rawUpdate[1])]
        } else if (typeof rawUpdate === 'function') {
            this.asyncs = [new NormalizedSingleAsyncUpdate(target, rawUpdate)]
            this.syncs = []
        } else {
            this.asyncs = []
            this.syncs = [[target, rawUpdate]]
        }
    }
}

class MultiUpdateBucket {
    type: 'multi' = 'multi'
    syncs: NormalizedSyncUpdate[]
    asyncs: NormalizedAsyncUpdate[]

    constructor(updates: MultiUpdate[]) {
        const asyncs: NormalizedAsyncUpdate[] = this.asyncs = []
        const syncs: NormalizedSyncUpdate[] = this.syncs = []
        for (let i = 0; i < updates.length; i++) {
            const update: MultiUpdate = updates[i]
            if (typeof update === 'function') {
                asyncs.push(new NormalizedMultiAsyncUpdate(update))
            } else if (Array.isArray(update)) {
                if (update.length !== 2) {
                    throw new Error(`Need a tuple: [Key, Object] in ${debugName(update)}`)
                }
                syncs.push(update)
            } else {
                if (update.constructor === Object) {
                    throw new Error(`Can't be a raw object: ${debugName(update)}`)
                }
                syncs.push([update.constructor, update])
            }
        }
    }
}

type UpdateBucket = SingleUpdateBucket | MultiUpdateBucket

type UpdaterStatusType = 'error' | 'pending' | 'complete'

export class UpdaterStatus {
    complete: boolean
    pending: boolean
    error: ?Error
    retry: ?(() => void)

    constructor(
        type?: UpdaterStatusType,
        error?: ?Error,
        retry?: ?() => void
    ) {
        this.complete = type === 'complete'
        this.pending = !type || type === 'pending'
        this.retry = retry || null
        this.error = error || null
    }
}

interface OperationErrorRec {
    error: Error;
    op: NormalizedAsyncUpdate;
}

type Update = MultiUpdate[] | SingleUpdate

class OperationObserver {
    _parentObserver: Observer<UpdateBucket, OperationErrorRec>
    _unsubscribe: () => void
    _cancel: () => void
    _op: NormalizedAsyncUpdate
    _target: ?Key

    constructor(
        parentObserver: Observer<UpdateBucket, OperationErrorRec>,
        unsubscribe: () => void,
        cancel: () => void,
        op: NormalizedAsyncUpdate
    ) {
        this._parentObserver = parentObserver
        this._unsubscribe = unsubscribe
        this._cancel = cancel
        this._op = op
    }

    next(ops: ?Update): void {
        if (ops) {
            this._parentObserver.next(
                this._op.type === 'single'
                    ? new SingleUpdateBucket((ops: any), this._op.target)
                    : new MultiUpdateBucket(((ops: any): MultiUpdate[]))
            )
        }
    }

    error(err: Error): void {
        // this._unsubscribe()
        this._cancel()
        this._parentObserver.error({
            error: err,
            op: this._op
        })
    }

    complete(ops?: Update): void {
        this._unsubscribe()
        if (!ops) {
            this._parentObserver.complete(null)
            return
        }

        this._parentObserver.complete(
            this._op.type === 'single'
                ? new SingleUpdateBucket((ops: any), this._op.target)
                : new MultiUpdateBucket(((ops: any): MultiUpdate[]))
        )
    }
}

class AsyncQeue {
    _maxQeueSize: number
    _qeue: NormalizedAsyncUpdate[] = []
    _subscriptions: Subscription[] = []
    _parentObserver: Observer<UpdateBucket, OperationErrorRec>

    // Readonly qeue size
    size: number = 0

    constructor(
        parentObserver: Observer<UpdateBucket, OperationErrorRec>,
        maxQeueSize: number = 1
    ) {
        this._maxQeueSize = maxQeueSize
        this._parentObserver = parentObserver
    }

    add(ops: NormalizedAsyncUpdate[]): void {
        this._qeue = this._qeue.concat(ops)
        this.size = this.size + ops.length
        this._run()
    }

    _run(): void {
        if (this.size === 0) {
            this._parentObserver.complete()
            return
        }
        if (this._subscriptions.length >= this._maxQeueSize) {
            return
        }
        const asyncUpdate: NormalizedAsyncUpdate = this._qeue.pop()
        let observable: Observable<Update, Error>
        const update: SingleAsyncUpdateResult | MultiAsyncUpdateResult = asyncUpdate.op()
        if (update.then) {
            observable = promiseToObservable(((update: any): Promise<Update>))
        } else if (update.subscribe) {
            observable = ((update: any): Observable<Update, Error>)
        } else {
            throw new Error(`Unknown operation: ${debugName(asyncUpdate.op)}`)
        }

        let subscription: Subscription

        const unsubscribe: () => void = () => this._removeSubscription(subscription)
        const cancel: () => void = () => this.cancel()

        const observer: Observer<Update, Error> = new OperationObserver(
            this._parentObserver,
            unsubscribe,
            cancel,
            asyncUpdate
        )

        subscription = observable.subscribe(observer)
        this._subscriptions.push(subscription)
    }

    _removeSubscription(item: Subscription): void {
        this.size = this.size - 1
        this._subscriptions = this._subscriptions.filter((target: Subscription) => target !== item)
        if (this.size) {
            this._run()
        }
    }

    cancel(): void {
        const subscriptions = this._subscriptions
        for (let i = 0, l = subscriptions.length; i < l; i++) {
            subscriptions[i].unsubscribe()
        }
        this._subscriptions = []
        this._qeue = []
        this.size = 0
    }
}

class UpdaterObserver {
    displayName: string
    status: Atom<UpdaterStatus>

    _di: IContext
    _adapter: Adapter
    _qeue: AsyncQeue
    _rollbackOnError: boolean

    _errorUpdates: NormalizedAsyncUpdate[] = []
    _pendingUpdates: NormalizedSyncUpdate[] = []

    constructor(
        di: IContext,
        maxSize: number,
        rollbackOnError: boolean,
        pending: boolean
    ) {
        this._di = di
        this._rollbackOnError = rollbackOnError
        this._adapter = di.adapter
        this._qeue = new AsyncQeue((this: Observer<UpdateBucket, OperationErrorRec>), maxSize)
        this.status = this._adapter.atom(new UpdaterStatus(pending ? 'pending' : 'complete'))
    }

    cancel(): void {
        this._errorUpdates = []
        this._qeue.cancel()
    }

    complete(updates: ?UpdateBucket): void {
        if (updates) {
            this.next(updates)
        } else if (this._qeue.size === 0) {
            this.status.set(new UpdaterStatus('complete'))
            this._pendingUpdates = []
        }
    }

    _retry: () => void = () => {
        if (this._errorUpdates.length) {
            this._qeue.add(this._errorUpdates)
            this._errorUpdates = []
        }
    };

    error({error, op}: OperationErrorRec) {
        this._errorUpdates.push(op)
        const onError = () => {
            this.status.set(new UpdaterStatus('error', error, this._retry))
            if (this._rollbackOnError) {
                this._updateSync(this._pendingUpdates, false)
            }
        }
        this._adapter.transact(onError)
        this._pendingUpdates = []
    }

    next({syncs, asyncs}: UpdateBucket): void {
        const adapter = this._adapter
        const qeue = this._qeue
        const status = this.status
        if (asyncs.length) {
            if (!status.get().pending) {
                status.set(new UpdaterStatus('pending'))
            }
            qeue.add(asyncs)
        }
        if (syncs.length) {
            adapter.transact(() => this._updateSync((syncs: NormalizedSyncUpdate[]), qeue.size > 0))
        }
        if (qeue.size === 0) {
            this._errorUpdates = []
        }
    }

    _updateSync(updates: NormalizedSyncUpdate[], needRollback: boolean): void {
        const di = this._di
        const pd = this._pendingUpdates
        for (let i = 0, l = updates.length; i < l; i++) {
            const rec = updates[i]
            if (!Array.isArray(rec)) {
                throw new Error('Wrong updates rec: must by an NormalizedSyncUpdate tuple')
            }
            const [key, props] = rec
            if (!key) {
                throw new Error('NormalizedSyncUpdate key can\'t be null')
            }
            if (!props) {
                throw new Error('NormalizedSyncUpdate props can\'t be null')
            }
            if (needRollback) {
                pd.push(rec)
            }
            const atom = props[atomKey] ? props[atomKey].value : di.val(key)
            atom.swap(cloneInstance, props)
        }
        if (!needRollback) {
            this.status.set(new UpdaterStatus('complete'))
            this._pendingUpdates = []
        }
    }
}
if (0) ((new UpdaterObserver(...(0: any))): Observer<UpdateBucket, OperationErrorRec>) // eslint-disable-line

export default class Updater {
    displayName: string
    status: Derivable<UpdaterStatus>

    _uo: UpdaterObserver

    static maxSize: number = 10
    static rollbackOnError: boolean = false
    static pending: boolean = false

    constructor(di: IContext) {
        const c = this.constructor
        this.displayName = `${di.displayName}#${debugName(c)}`
        this._uo = new UpdaterObserver(
            di,
            c.maxSize,
            c.rollbackOnError,
            c.pending
        )
        this._uo.displayName = this.displayName + '#updater'
        this.status = this._uo.status
        const unsubscribeUpdater = (isStopped: boolean) => {
            if (isStopped) {
                this._uo.cancel()
            }
        }
        di.stopped.react(unsubscribeUpdater, {
            skipFirst: true,
            once: true
        })
    }

    cancel(): Updater {
        this._uo.cancel()
        return this
    }

    setSingle(rawUpdate: SingleUpdate, target: Key): Updater {
        this._uo.next(new SingleUpdateBucket(rawUpdate, target))
        return this
    }

    set(updates: MultiUpdate[]): Updater {
        this._uo.next(new MultiUpdateBucket(updates))
        return this
    }
}
<|endoftext|>


<commit_before>goog.provide('ol.TileUrlFunction');
goog.provide('ol.TileUrlFunctionType');

goog.require('goog.array');
goog.require('goog.math');
goog.require('ol.TileCoord');
goog.require('ol.extent');

/**
 * @typedef {function(this: ol.source.TileImage, ol.TileCoord,
 *     ol.Projection): (string|undefined)}
 */
ol.TileUrlFunctionType;

/**
 * @param {string} template Template.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTemplate = function(template) {
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          return template.replace('{z}', '' + tileCoord.z)
                         .replace('{x}', '' + tileCoord.x)
                         .replace('{y}', '' + tileCoord.y);
        }
      });
};

/**
 * @param {Array.<string>} templates Templates.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTemplates = function(templates) {
  return ol.TileUrlFunction.createFromTileUrlFunctions(
      goog.array.map(templates, ol.TileUrlFunction.createFromTemplate));
};

/**
 * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
  if (tileUrlFunctions.length === 1) {
    return tileUrlFunctions[0];
  }
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          var index =
              goog.math.modulo(tileCoord.hash(), tileUrlFunctions.length);
          return tileUrlFunctions[index].call(this, tileCoord, projection);
        }
      });
};

/**
 * @param {string} baseUrl Base URL (may have query data).
 * @param {Object.<string,*>} params to encode in the url.
 * @param {function(this: ol.source.TileImage, string, Object.<string,*>,
 *     ol.Extent, ol.Size, ol.Projection)} paramsFunction params function.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromParamsFunction =
    function(baseUrl, params, paramsFunction) {
  var tmpExtent = ol.extent.createEmpty();
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          var tileGrid = this.getTileGrid();
          if (goog.isNull(tileGrid)) {
            tileGrid = ol.tilegrid.getForProjection(projection);
          }
          var size = tileGrid.getTileSize(tileCoord.z);
          var extent = tileGrid.getTileCoordExtent(tileCoord, tmpExtent);
          return paramsFunction.call(this, baseUrl, params,
              extent, size, projection);
        }
      });
};

/**
 * @this {ol.source.TileImage}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Projection} projection Projection.
 * @return {string|undefined} Tile URL.
 */
ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, projection) {
  return undefined;
};

/**
 * @param {function(ol.TileCoord, ol.Projection, ol.TileCoord=): ol.TileCoord}
 *     transformFn Transform function.
 * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.withTileCoordTransform =
    function(transformFn, tileUrlFunction) {
  var tmpTileCoord = new ol.TileCoord(0, 0, 0);
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          return tileUrlFunction.call(
              this,
              transformFn.call(this, tileCoord, projection, tmpTileCoord),
              projection);
        }
      });
};

/**
 * @param {string} url Url.
 * @return {Array.<string>} Array of urls.
 */
ol.TileUrlFunction.expandUrl = function(url) {
  var urls = [];
  var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url);
  if (match) {
    var startCharCode = match[1].charCodeAt(0);
    var stopCharCode = match[2].charCodeAt(0);
    var charCode;
    for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
      urls.push(url.replace(match[0], String.fromCharCode(charCode)));
    }
  } else {
    urls.push(url);
  }
  return urls;
};
<commit_msg>Factor out ol.TileCoordTransformType<commit_after>goog.provide('ol.TileUrlFunction');
goog.provide('ol.TileUrlFunctionType');

goog.require('goog.array');
goog.require('goog.math');
goog.require('ol.TileCoord');
goog.require('ol.extent');

/**
 * @typedef {function(this: ol.source.TileImage, ol.TileCoord,
 *     ol.Projection): (string|undefined)}
 */
ol.TileUrlFunctionType;

/**
 * @typedef {function(ol.TileCoord, ol.Projection, ol.TileCoord=): ol.TileCoord}
 */
ol.TileCoordTransformType;

/**
 * @param {string} template Template.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTemplate = function(template) {
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          return template.replace('{z}', '' + tileCoord.z)
                         .replace('{x}', '' + tileCoord.x)
                         .replace('{y}', '' + tileCoord.y);
        }
      });
};

/**
 * @param {Array.<string>} templates Templates.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTemplates = function(templates) {
  return ol.TileUrlFunction.createFromTileUrlFunctions(
      goog.array.map(templates, ol.TileUrlFunction.createFromTemplate));
};

/**
 * @param {Array.<ol.TileUrlFunctionType>} tileUrlFunctions Tile URL Functions.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromTileUrlFunctions = function(tileUrlFunctions) {
  if (tileUrlFunctions.length === 1) {
    return tileUrlFunctions[0];
  }
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          var index =
              goog.math.modulo(tileCoord.hash(), tileUrlFunctions.length);
          return tileUrlFunctions[index].call(this, tileCoord, projection);
        }
      });
};

/**
 * @param {string} baseUrl Base URL (may have query data).
 * @param {Object.<string,*>} params to encode in the url.
 * @param {function(this: ol.source.TileImage, string, Object.<string,*>,
 *     ol.Extent, ol.Size, ol.Projection)} paramsFunction params function.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.createFromParamsFunction =
    function(baseUrl, params, paramsFunction) {
  var tmpExtent = ol.extent.createEmpty();
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          var tileGrid = this.getTileGrid();
          if (goog.isNull(tileGrid)) {
            tileGrid = ol.tilegrid.getForProjection(projection);
          }
          var size = tileGrid.getTileSize(tileCoord.z);
          var extent = tileGrid.getTileCoordExtent(tileCoord, tmpExtent);
          return paramsFunction.call(this, baseUrl, params,
              extent, size, projection);
        }
      });
};

/**
 * @this {ol.source.TileImage}
 * @param {ol.TileCoord} tileCoord Tile coordinate.
 * @param {ol.Projection} projection Projection.
 * @return {string|undefined} Tile URL.
 */
ol.TileUrlFunction.nullTileUrlFunction = function(tileCoord, projection) {
  return undefined;
};

/**
 * @param {ol.TileCoordTransformType} transformFn Transform function.
 * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function.
 * @return {ol.TileUrlFunctionType} Tile URL function.
 */
ol.TileUrlFunction.withTileCoordTransform =
    function(transformFn, tileUrlFunction) {
  var tmpTileCoord = new ol.TileCoord(0, 0, 0);
  return (
      /**
       * @this {ol.source.TileImage}
       * @param {ol.TileCoord} tileCoord Tile Coordinate.
       * @param {ol.Projection} projection Projection.
       * @return {string|undefined} Tile URL.
       */
      function(tileCoord, projection) {
        if (goog.isNull(tileCoord)) {
          return undefined;
        } else {
          return tileUrlFunction.call(
              this,
              transformFn.call(this, tileCoord, projection, tmpTileCoord),
              projection);
        }
      });
};

/**
 * @param {string} url Url.
 * @return {Array.<string>} Array of urls.
 */
ol.TileUrlFunction.expandUrl = function(url) {
  var urls = [];
  var match = /\{(\d)-(\d)\}/.exec(url) || /\{([a-z])-([a-z])\}/.exec(url);
  if (match) {
    var startCharCode = match[1].charCodeAt(0);
    var stopCharCode = match[2].charCodeAt(0);
    var charCode;
    for (charCode = startCharCode; charCode <= stopCharCode; ++charCode) {
      urls.push(url.replace(match[0], String.fromCharCode(charCode)));
    }
  } else {
    urls.push(url);
  }
  return urls;
};
<|endoftext|>


<commit_before>'use strict';

module.exports = {
    truncate: function (string) {
        var MAX_LENGTH = 80;

        if (string.length > MAX_LENGTH) {
            return string.substring(0, MAX_LENGTH) + ' [...]';
        }

        return string;
    },

    getPossiblyUndefinedKeyValue: function (obj, keySequence) {
        var keys = keySequence.split('.');

        while (obj && keys.length) {
            obj = obj[keys.shift()];
        }

        return obj || undefined;
    },

    COLORS: {
        red: '#ff0000',
        green: '#00ff00',
        blue: '#0000ff',
        yellow: '#ffff00'
    }
};

<commit_msg>Adding some more chars before truncating<commit_after>'use strict';

module.exports = {
    truncate: function (string) {
        var MAX_LENGTH = 100;

        if (string.length > MAX_LENGTH) {
            return string.substring(0, MAX_LENGTH) + ' [...]';
        }

        return string;
    },

    getPossiblyUndefinedKeyValue: function (obj, keySequence) {
        var keys = keySequence.split('.');

        while (obj && keys.length) {
            obj = obj[keys.shift()];
        }

        return obj || undefined;
    },

    COLORS: {
        red: '#ff0000',
        green: '#00ff00',
        blue: '#0000ff',
        yellow: '#ffff00'
    }
};

<|endoftext|>


<commit_before>var myPath = require('./path'),
    fs = require('fs');

exports.chdirOptParse = function() {
    return this.opt()
        .name('dir').short('C').long('chdir')
        .title('рабочая директория, по умолчанию текущая')
        .def(process.cwd())
        .validate(function(d) {
            d = myPath.join(d, '/');
            process.chdir(d);
            return d;
        })
        .end();
};

exports.techsOptParse = function() {
    return this
        .opt()
            .name('addTech').short('t').long('add-tech')
            .title('добавить технологию')
            .push()
            .end()
        .opt()
            .name('forceTech').short('T').long('force-tech')
            .title('использовать только эту технологию')
            .push()
            .end()
        .opt()
            .name('noTech').short('n').long('no-tech')
            .title('исключить технологию из использования')
            .push()
            .end()
};

exports.levelOptParse = function() {
    return this.opt()
        .name('levelDir').short('l').long('level')
        .title('директория уровня переопределения, по умолчанию текущая')
        .def(process.cwd())
        .validate(function(d) { return myPath.join(d, '/') })
        .end();
};

exports.mergeTechs = function(level, opts) {
    // NOTE: если при создании блока/элемента/модификатора
    // указали --force-tech <name> или --no-tech, и в level.js
    // определена технология с таким именем/файлом на диске,
    // нужно использовать именно её
    var techs = opts.forceTech? {} : level.techs,
        optsTechs = [];

    opts.forceTech && optsTechs.push.apply(optsTechs, opts.forceTech);
    opts.addTech && optsTechs.push.apply(optsTechs, opts.addTech);

    optsTechs.forEach(function(t) {
        var tech = level.getTech(t),
            name = tech.getTechName();
        techs[name] || (techs[name] = tech);
    });

    opts.noTech && opts.noTech.forEach(function(t) {
        delete techs[level.getTech(t).getTechName()];
    });

    return techs;
};

exports.mkdir = function(path) {
    try { fs.mkdirSync(path, 0777) } catch(ignore) {}
};

exports.isFile = function(path) {
    try {
        return fs.statSync(path).isFile();
    } catch(ignore) {}
    return false;
};

exports.isDirectory = function(path) {
    try {
        return fs.statSync(path).isDirectory();
    } catch(ignore) {}
    return false;
};

exports.fsWalkTree = function(root, fileCb, filterCb) {
    var files = fs.readdirSync(root);
    while (files.length > 0) {
        var path = myPath.join(root, files.shift());
        if(filterCb && !filterCb(path)) continue;
        fileCb(path);
        if(exports.isDirectory(path)) exports.fsWalkTree(path, fileCb, filterCb);
    }
};

exports.getDirs = function(path) {
    try {
        return exports.isDirectory(path)?
            fs.readdirSync(path)
                .filter(function(d) {
                    return !(/^\.svn$/.test(d)) && exports.isDirectory(myPath.join(path, d));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.getFiles = function(path) {
    try {
        return exports.isDirectory(path)?
            fs.readdirSync(path)
                .filter(function(f) {
                    return exports.isFile(myPath.join(path, f));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.isEmptyObject = function(obj) {
    for(var i in obj) return false;
    return true;
};

exports.isRequireError = function(e) {
    return /^Cannot find module/.test(e.message);
};

exports.isPath = function(str) {
    return str.indexOf('/') !== -1;
};

exports.isRequireable = function(path) {
    try {
        require(path);
        return true;
    } catch (e) {
        if(! isRequireError(e)) throw e;
        return false;
    }
};

exports.arrayUnique = function(arr) {
    return arr.reduce(function(prev, cur) {
        if(prev.indexOf(cur) + 1) return prev;
        return prev.concat([cur]);
    }, []);
};

exports.arrayReverse = function(arr) {
    return arr.reduceRight(function(prev, cur) {
        prev.push(cur);
        return prev;
    }, []);
};

exports.getBemTechPath = function(name) {
    var bemTechs = 'bem/techs',
        path = myPath.join(bemTechs, name);
    if(isRequireable(path)) {
        return path;
    }
    return myPath.join(bemTechs, 'default');
};

exports.mergeDecls = function mergeDecls(d1, d2) {
    d1 || (d1 = []);
    var keys = {};
    d1.forEach(function(o){ keys[o.name || o] = o });

    d2.forEach(function(o2){
        if (keys.hasOwnProperty(o2.name || o2)) {
            var o1 = keys[o2.name || o2];
            o2.elems && (o1.elems = mergeDecls(o1.elems, o2.elems));
            o2.mods && (o1.mods = mergeDecls(o1.mods, o2.mods));
            o2.vals && (o1.vals = mergeDecls(o1.vals, o2.vals));
            o2.techs && (o1.techs = mergeDecls(o1.techs, o2.techs));
        } else {
            d1.push(o2);
        }
    });

    return d1;
};
<commit_msg>Более правильное объединение деклараций для случая, когда во второ декларации есть уникальные блоки.<commit_after>var myPath = require('./path'),
    fs = require('fs');

exports.chdirOptParse = function() {
    return this.opt()
        .name('dir').short('C').long('chdir')
        .title('рабочая директория, по умолчанию текущая')
        .def(process.cwd())
        .validate(function(d) {
            d = myPath.join(d, '/');
            process.chdir(d);
            return d;
        })
        .end();
};

exports.techsOptParse = function() {
    return this
        .opt()
            .name('addTech').short('t').long('add-tech')
            .title('добавить технологию')
            .push()
            .end()
        .opt()
            .name('forceTech').short('T').long('force-tech')
            .title('использовать только эту технологию')
            .push()
            .end()
        .opt()
            .name('noTech').short('n').long('no-tech')
            .title('исключить технологию из использования')
            .push()
            .end()
};

exports.levelOptParse = function() {
    return this.opt()
        .name('levelDir').short('l').long('level')
        .title('директория уровня переопределения, по умолчанию текущая')
        .def(process.cwd())
        .validate(function(d) { return myPath.join(d, '/') })
        .end();
};

exports.mergeTechs = function(level, opts) {
    // NOTE: если при создании блока/элемента/модификатора
    // указали --force-tech <name> или --no-tech, и в level.js
    // определена технология с таким именем/файлом на диске,
    // нужно использовать именно её
    var techs = opts.forceTech? {} : level.techs,
        optsTechs = [];

    opts.forceTech && optsTechs.push.apply(optsTechs, opts.forceTech);
    opts.addTech && optsTechs.push.apply(optsTechs, opts.addTech);

    optsTechs.forEach(function(t) {
        var tech = level.getTech(t),
            name = tech.getTechName();
        techs[name] || (techs[name] = tech);
    });

    opts.noTech && opts.noTech.forEach(function(t) {
        delete techs[level.getTech(t).getTechName()];
    });

    return techs;
};

exports.mkdir = function(path) {
    try { fs.mkdirSync(path, 0777) } catch(ignore) {}
};

exports.isFile = function(path) {
    try {
        return fs.statSync(path).isFile();
    } catch(ignore) {}
    return false;
};

exports.isDirectory = function(path) {
    try {
        return fs.statSync(path).isDirectory();
    } catch(ignore) {}
    return false;
};

exports.fsWalkTree = function(root, fileCb, filterCb) {
    var files = fs.readdirSync(root);
    while (files.length > 0) {
        var path = myPath.join(root, files.shift());
        if(filterCb && !filterCb(path)) continue;
        fileCb(path);
        if(exports.isDirectory(path)) exports.fsWalkTree(path, fileCb, filterCb);
    }
};

exports.getDirs = function(path) {
    try {
        return exports.isDirectory(path)?
            fs.readdirSync(path)
                .filter(function(d) {
                    return !(/^\.svn$/.test(d)) && exports.isDirectory(myPath.join(path, d));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.getFiles = function(path) {
    try {
        return exports.isDirectory(path)?
            fs.readdirSync(path)
                .filter(function(f) {
                    return exports.isFile(myPath.join(path, f));
                })
                .sort() :
            [];
    } catch (e) {
        return [];
    }
};

exports.isEmptyObject = function(obj) {
    for(var i in obj) return false;
    return true;
};

exports.isRequireError = function(e) {
    return /^Cannot find module/.test(e.message);
};

exports.isPath = function(str) {
    return str.indexOf('/') !== -1;
};

exports.isRequireable = function(path) {
    try {
        require(path);
        return true;
    } catch (e) {
        if(! isRequireError(e)) throw e;
        return false;
    }
};

exports.arrayUnique = function(arr) {
    return arr.reduce(function(prev, cur) {
        if(prev.indexOf(cur) + 1) return prev;
        return prev.concat([cur]);
    }, []);
};

exports.arrayReverse = function(arr) {
    return arr.reduceRight(function(prev, cur) {
        prev.push(cur);
        return prev;
    }, []);
};

exports.getBemTechPath = function(name) {
    var bemTechs = 'bem/techs',
        path = myPath.join(bemTechs, name);
    if(isRequireable(path)) {
        return path;
    }
    return myPath.join(bemTechs, 'default');
};

exports.mergeDecls = function mergeDecls(d1, d2) {
    var keys = {};
    d1?
        d1.forEach(function(o) { keys[o.name || o] = o }) :
        d1 = [];

    d2.forEach(function(o2) {
        var name = o2.name || o2;
        if (keys.hasOwnProperty(name)) {
            var o1 = keys[name];
            o2.elems && (o1.elems = mergeDecls(o1.elems, o2.elems));
            o2.mods && (o1.mods = mergeDecls(o1.mods, o2.mods));
            o2.vals && (o1.vals = mergeDecls(o1.vals, o2.vals));
            o2.techs && (o1.techs = mergeDecls(o1.techs, o2.techs));
        } else {
            d1.push(o2);
            keys[name] = o2;
        }
    });

    return d1;
};
<|endoftext|>


<commit_before>const path = require('path');
const common = require('./config/common');
const packages = require('./package.json');

/**
 * Define build options environment variables here
 * format: [var name]: default value
 * check README for variable names and purpose
 */
const buildEnvVarDefaults = {
  EXPLORER_URL: (
    'https://static-cdn.kloudless.com/p/platform/explorer/explorer.html'),
  BASE_URL: 'https://api.kloudless.com',
  // for development only
  KLOUDLESS_APP_ID: null,
};

const transformDefines = {
  VERSION: packages.version,
};

Object.keys(buildEnvVarDefaults).forEach((varName) => {
  transformDefines[varName] = (
    process.env[varName] || buildEnvVarDefaults[varName]);
});

module.exports = {
  presets: [
    ['@babel/preset-env', {
      useBuiltIns: 'usage',
      corejs: 3,
    }],
    // Used by storybook-react
    '@babel/preset-react',
  ],
  ignore: common.ignorePaths,
  plugins: [
    [
      'module-resolver', {
        // avoid node_modules path being relative path
        root: common.resolvePaths.filter(p => p !== 'node_modules'),
        alias: {
          'explorer-config': path.resolve(__dirname, './src/explorer/js/',
            (process.env.NODE_ENV === 'production'
             && !process.env.DEBUG) ? './config_prod.json' : './config.json'),
        },
      },
    ],
    [
      'transform-define', transformDefines,
    ],
    [
      path.resolve(__dirname, './config/babel-modal-style-script'),
    ],
  ],
  env: {
    transpile: {
      presets: [
        ['@babel/preset-env', {
          useBuiltIns: false,
        }],
        '@babel/preset-react',
      ],
    },
  },
};
<commit_msg>fix(babel.config): fix inproper parse of DEBUG environment variable<commit_after>const path = require('path');
const common = require('./config/common');
const packages = require('./package.json');

/**
 * Define build options environment variables here
 * format: [var name]: default value
 * check README for variable names and purpose
 */
const buildEnvVarDefaults = {
  EXPLORER_URL: (
    'https://static-cdn.kloudless.com/p/platform/explorer/explorer.html'),
  BASE_URL: 'https://api.kloudless.com',
  // for development only
  KLOUDLESS_APP_ID: null,
};

const transformDefines = {
  VERSION: packages.version,
};

Object.keys(buildEnvVarDefaults).forEach((varName) => {
  transformDefines[varName] = (
    process.env[varName] || buildEnvVarDefaults[varName]);
});

module.exports = {
  presets: [
    ['@babel/preset-env', {
      useBuiltIns: 'usage',
      corejs: 3,
    }],
    // Used by storybook-react
    '@babel/preset-react',
  ],
  ignore: common.ignorePaths,
  plugins: [
    [
      'module-resolver', {
        // avoid node_modules path being relative path
        root: common.resolvePaths.filter(p => p !== 'node_modules'),
        alias: {
          'explorer-config': path.resolve(__dirname, './src/explorer/js/',
            (process.env.NODE_ENV === 'production'
              && !JSON.parse(process.env.DEBUG)) ?
              './config_prod.json' : './config.json'),
        },
      },
    ],
    [
      'transform-define', transformDefines,
    ],
    [
      path.resolve(__dirname, './config/babel-modal-style-script'),
    ],
  ],
  env: {
    transpile: {
      presets: [
        ['@babel/preset-env', {
          useBuiltIns: false,
        }],
        '@babel/preset-react',
      ],
    },
  },
};
<|endoftext|>