<commit_before>goog.provide('ol.layer.Base');
goog.provide('ol.layer.LayerProperty');
goog.provide('ol.layer.LayerState');

goog.require('goog.events');
goog.require('goog.math');
goog.require('goog.object');
goog.require('ol.Object');
goog.require('ol.source.State');

/**
 * @enum {string}
 */
ol.layer.LayerProperty = {
  BRIGHTNESS: 'brightness',
  CONTRAST: 'contrast',
  HUE: 'hue',
  OPACITY: 'opacity',
  SATURATION: 'saturation',
  VISIBLE: 'visible',
  EXTENT: 'extent',
  MAX_RESOLUTION: 'maxResolution',
  MIN_RESOLUTION: 'minResolution',
  SOURCE: 'source'
};

/**
 * @typedef {{layer: ol.layer.Layer,
 *            brightness: number,
 *            contrast: number,
 *            hue: number,
 *            opacity: number,
 *            saturation: number,
 *            sourceState: ol.source.State,
 *            visible: boolean,
 *            extent: (ol.Extent|undefined),
 *            maxResolution: number,
 *            minResolution: number}}
 */
ol.layer.LayerState;

/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Note that with `ol.layer.Base` and all its subclasses, any property set in
 * the options is set as a {@link ol.Object} property on the layer object, so
 * is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.layer.BaseOptions} options Layer options.
 * @api stable
 */
ol.layer.Base = function(options) {

  goog.base(this);

  /**
   * @type {Object.<string, *>}
   */
  var properties = goog.object.clone(options);
  properties[ol.layer.LayerProperty.BRIGHTNESS] =
      goog.isDef(options.brightness) ? options.brightness : 0;
  properties[ol.layer.LayerProperty.CONTRAST] =
      goog.isDef(options.contrast) ? options.contrast : 1;
  properties[ol.layer.LayerProperty.HUE] =
      goog.isDef(options.hue) ? options.hue : 0;
  properties[ol.layer.LayerProperty.OPACITY] =
      goog.isDef(options.opacity) ? options.opacity : 1;
  properties[ol.layer.LayerProperty.SATURATION] =
      goog.isDef(options.saturation) ? options.saturation : 1;
  properties[ol.layer.LayerProperty.VISIBLE] =
      goog.isDef(options.visible) ? options.visible : true;
  properties[ol.layer.LayerProperty.MAX_RESOLUTION] =
      goog.isDef(options.maxResolution) ? options.maxResolution : Infinity;
  properties[ol.layer.LayerProperty.MIN_RESOLUTION] =
      goog.isDef(options.minResolution) ? options.minResolution : 0;

  this.setProperties(properties);
};
goog.inherits(ol.layer.Base, ol.Object);

/**
 * @return {number|undefined} The brightness of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getBrightness = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.BRIGHTNESS));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getBrightness',
    ol.layer.Base.prototype.getBrightness);

/**
 * @return {number|undefined} The contrast of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getContrast = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.CONTRAST));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getContrast',
    ol.layer.Base.prototype.getContrast);

/**
 * @return {number} The hue of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getHue = function() {
  return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getHue',
    ol.layer.Base.prototype.getHue);

/**
 * @return {ol.layer.LayerState} Layer state.
 */
ol.layer.Base.prototype.getLayerState = function() {
  var brightness = this.getBrightness();
  var contrast = this.getContrast();
  var hue = this.getHue();
  var opacity = this.getOpacity();
  var saturation = this.getSaturation();
  var sourceState = this.getSourceState();
  var visible = this.getVisible();
  var extent = this.getExtent();
  var maxResolution = this.getMaxResolution();
  var minResolution = this.getMinResolution();
  return {
    layer: /** @type {ol.layer.Layer} */ (this),
    brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
    contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
    hue: hue,
    opacity: goog.isDef(opacity) ? goog.math.clamp(opacity, 0, 1) : 1,
    saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
    sourceState: sourceState,
    visible: visible,
    extent: extent,
    maxResolution: goog.isDef(maxResolution) ? maxResolution : Infinity,
    minResolution: goog.isDef(minResolution) ? Math.max(minResolution, 0) : 0
  };
};

/**
 * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
 *     modified in place).
 * @return {Array.<ol.layer.Layer>} Array of layers.
 */
ol.layer.Base.prototype.getLayersArray = goog.abstractMethod;

/**
 * @param {Array.<ol.layer.LayerState>=} opt_states Optional list of layer
 *     states (to be modified in place).
 * @return {Array.<ol.layer.LayerState>} List of layer states.
 */
ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod;

/**
 * @return {ol.Extent|undefined} The layer extent.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getExtent = function() {
  return /** @type {ol.Extent|undefined} */ (
      this.get(ol.layer.LayerProperty.EXTENT));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getExtent',
    ol.layer.Base.prototype.getExtent);

/**
 * @return {number|undefined} The maximum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getMaxResolution = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getMaxResolution',
    ol.layer.Base.prototype.getMaxResolution);

/**
 * @return {number|undefined} The minimum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getMinResolution = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getMinResolution',
    ol.layer.Base.prototype.getMinResolution);

/**
 * @return {number|undefined} The opacity of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getOpacity = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.OPACITY));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getOpacity',
    ol.layer.Base.prototype.getOpacity);

/**
 * @return {number|undefined} The saturation of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getSaturation = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.SATURATION));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getSaturation',
    ol.layer.Base.prototype.getSaturation);

/**
 * @return {ol.source.State} Source state.
 */
ol.layer.Base.prototype.getSourceState = goog.abstractMethod;

/**
 * @return {boolean} The visibility of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getVisible = function() {
  return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getVisible',
    ol.layer.Base.prototype.getVisible);

/**
 * Adjust the layer brightness.  A value of -1 will render the layer completely
 * black.  A value of 0 will leave the brightness unchanged.  A value of 1 will
 * render the layer completely white.  Other values are linear multipliers on
 * the effect (values are clamped between -1 and 1).
 *
 * The filter effects draft [1] says the brightness function is supposed to
 * render 0 black, 1 unchanged, and all other values as a linear multiplier.
 *
 * The current WebKit implementation clamps values between -1 (black) and 1
 * (white) [2].  There is a bug open to change the filter effect spec [3].
 *
 * TODO: revisit this if the spec is still unmodified before we release
 *
 * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
 * [2] https://github.com/WebKit/webkit/commit/8f4765e569
 * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
 *
 * @param {number|undefined} brightness The brightness of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setBrightness = function(brightness) {
  this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setBrightness',
    ol.layer.Base.prototype.setBrightness);

/**
 * Adjust the layer contrast.  A value of 0 will render the layer completely
 * grey.  A value of 1 will leave the contrast unchanged.  Other values are
 * linear multipliers on the effect (and values over 1 are permitted).
 *
 * @param {number|undefined} contrast The contrast of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setContrast = function(contrast) {
  this.set(ol.layer.LayerProperty.CONTRAST, contrast);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setContrast',
    ol.layer.Base.prototype.setContrast);

/**
 * Apply a hue-rotation to the layer.  A value of 0 will leave the hue
 * unchanged.  Other values are radians around the color circle.
 * @param {number} hue The hue of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setHue = function(hue) {
  this.set(ol.layer.LayerProperty.HUE, hue);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setHue',
    ol.layer.Base.prototype.setHue);

/**
 * Set the extent at which the layer is visible.  If `undefined`, the layer
 * will be visible at all extents.
 * @param {ol.Extent|undefined} extent The extent of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setExtent = function(extent) {
  this.set(ol.layer.LayerProperty.EXTENT, extent);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setExtent',
    ol.layer.Base.prototype.setExtent);

/**
 * @param {number|undefined} maxResolution The maximum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
  this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setMaxResolution',
    ol.layer.Base.prototype.setMaxResolution);

/**
 * @param {number|undefined} minResolution The minimum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setMinResolution = function(minResolution) {
  this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setMinResolution',
    ol.layer.Base.prototype.setMinResolution);

/**
 * @param {number|undefined} opacity The opacity of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setOpacity = function(opacity) {
  this.set(ol.layer.LayerProperty.OPACITY, opacity);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setOpacity',
    ol.layer.Base.prototype.setOpacity);

/**
 * Adjust layer saturation.  A value of 0 will render the layer completely
 * unsaturated.  A value of 1 will leave the saturation unchanged.  Other
 * values are linear multipliers of the effect (and values over 1 are
 * permitted).
 *
 * @param {number|undefined} saturation The saturation of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setSaturation = function(saturation) {
  this.set(ol.layer.LayerProperty.SATURATION, saturation);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setSaturation',
    ol.layer.Base.prototype.setSaturation);

/**
 * @param {boolean} visible The visibility of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setVisible = function(visible) {
  this.set(ol.layer.LayerProperty.VISIBLE, visible);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setVisible',
    ol.layer.Base.prototype.setVisible);
<commit_msg>Disallowed undefined 'opacity' value<commit_after>goog.provide('ol.layer.Base');
goog.provide('ol.layer.LayerProperty');
goog.provide('ol.layer.LayerState');

goog.require('goog.events');
goog.require('goog.math');
goog.require('goog.object');
goog.require('ol.Object');
goog.require('ol.source.State');

/**
 * @enum {string}
 */
ol.layer.LayerProperty = {
  BRIGHTNESS: 'brightness',
  CONTRAST: 'contrast',
  HUE: 'hue',
  OPACITY: 'opacity',
  SATURATION: 'saturation',
  VISIBLE: 'visible',
  EXTENT: 'extent',
  MAX_RESOLUTION: 'maxResolution',
  MIN_RESOLUTION: 'minResolution',
  SOURCE: 'source'
};

/**
 * @typedef {{layer: ol.layer.Layer,
 *            brightness: number,
 *            contrast: number,
 *            hue: number,
 *            opacity: number,
 *            saturation: number,
 *            sourceState: ol.source.State,
 *            visible: boolean,
 *            extent: (ol.Extent|undefined),
 *            maxResolution: number,
 *            minResolution: number}}
 */
ol.layer.LayerState;

/**
 * @classdesc
 * Abstract base class; normally only used for creating subclasses and not
 * instantiated in apps.
 * Note that with `ol.layer.Base` and all its subclasses, any property set in
 * the options is set as a {@link ol.Object} property on the layer object, so
 * is observable, and has get/set accessors.
 *
 * @constructor
 * @extends {ol.Object}
 * @param {olx.layer.BaseOptions} options Layer options.
 * @api stable
 */
ol.layer.Base = function(options) {

  goog.base(this);

  /**
   * @type {Object.<string, *>}
   */
  var properties = goog.object.clone(options);
  properties[ol.layer.LayerProperty.BRIGHTNESS] =
      goog.isDef(options.brightness) ? options.brightness : 0;
  properties[ol.layer.LayerProperty.CONTRAST] =
      goog.isDef(options.contrast) ? options.contrast : 1;
  properties[ol.layer.LayerProperty.HUE] =
      goog.isDef(options.hue) ? options.hue : 0;
  properties[ol.layer.LayerProperty.OPACITY] =
      goog.isDef(options.opacity) ? options.opacity : 1;
  properties[ol.layer.LayerProperty.SATURATION] =
      goog.isDef(options.saturation) ? options.saturation : 1;
  properties[ol.layer.LayerProperty.VISIBLE] =
      goog.isDef(options.visible) ? options.visible : true;
  properties[ol.layer.LayerProperty.MAX_RESOLUTION] =
      goog.isDef(options.maxResolution) ? options.maxResolution : Infinity;
  properties[ol.layer.LayerProperty.MIN_RESOLUTION] =
      goog.isDef(options.minResolution) ? options.minResolution : 0;

  this.setProperties(properties);
};
goog.inherits(ol.layer.Base, ol.Object);

/**
 * @return {number|undefined} The brightness of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getBrightness = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.BRIGHTNESS));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getBrightness',
    ol.layer.Base.prototype.getBrightness);

/**
 * @return {number|undefined} The contrast of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getContrast = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.CONTRAST));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getContrast',
    ol.layer.Base.prototype.getContrast);

/**
 * @return {number} The hue of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getHue = function() {
  return /** @type {number} */ (this.get(ol.layer.LayerProperty.HUE));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getHue',
    ol.layer.Base.prototype.getHue);

/**
 * @return {ol.layer.LayerState} Layer state.
 */
ol.layer.Base.prototype.getLayerState = function() {
  var brightness = this.getBrightness();
  var contrast = this.getContrast();
  var hue = this.getHue();
  var opacity = this.getOpacity();
  var saturation = this.getSaturation();
  var sourceState = this.getSourceState();
  var visible = this.getVisible();
  var extent = this.getExtent();
  var maxResolution = this.getMaxResolution();
  var minResolution = this.getMinResolution();
  return {
    layer: /** @type {ol.layer.Layer} */ (this),
    brightness: goog.isDef(brightness) ? goog.math.clamp(brightness, -1, 1) : 0,
    contrast: goog.isDef(contrast) ? Math.max(contrast, 0) : 1,
    hue: hue,
    opacity: goog.math.clamp(opacity, 0, 1),
    saturation: goog.isDef(saturation) ? Math.max(saturation, 0) : 1,
    sourceState: sourceState,
    visible: visible,
    extent: extent,
    maxResolution: goog.isDef(maxResolution) ? maxResolution : Infinity,
    minResolution: goog.isDef(minResolution) ? Math.max(minResolution, 0) : 0
  };
};

/**
 * @param {Array.<ol.layer.Layer>=} opt_array Array of layers (to be
 *     modified in place).
 * @return {Array.<ol.layer.Layer>} Array of layers.
 */
ol.layer.Base.prototype.getLayersArray = goog.abstractMethod;

/**
 * @param {Array.<ol.layer.LayerState>=} opt_states Optional list of layer
 *     states (to be modified in place).
 * @return {Array.<ol.layer.LayerState>} List of layer states.
 */
ol.layer.Base.prototype.getLayerStatesArray = goog.abstractMethod;

/**
 * @return {ol.Extent|undefined} The layer extent.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getExtent = function() {
  return /** @type {ol.Extent|undefined} */ (
      this.get(ol.layer.LayerProperty.EXTENT));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getExtent',
    ol.layer.Base.prototype.getExtent);

/**
 * @return {number|undefined} The maximum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getMaxResolution = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.MAX_RESOLUTION));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getMaxResolution',
    ol.layer.Base.prototype.getMaxResolution);

/**
 * @return {number|undefined} The minimum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getMinResolution = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.MIN_RESOLUTION));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getMinResolution',
    ol.layer.Base.prototype.getMinResolution);

/**
 * @return {number} The opacity of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getOpacity = function() {
  return /** @type {number} */ (this.get(ol.layer.LayerProperty.OPACITY));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getOpacity',
    ol.layer.Base.prototype.getOpacity);

/**
 * @return {number|undefined} The saturation of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.getSaturation = function() {
  return /** @type {number|undefined} */ (
      this.get(ol.layer.LayerProperty.SATURATION));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getSaturation',
    ol.layer.Base.prototype.getSaturation);

/**
 * @return {ol.source.State} Source state.
 */
ol.layer.Base.prototype.getSourceState = goog.abstractMethod;

/**
 * @return {boolean} The visibility of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.getVisible = function() {
  return /** @type {boolean} */ (this.get(ol.layer.LayerProperty.VISIBLE));
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'getVisible',
    ol.layer.Base.prototype.getVisible);

/**
 * Adjust the layer brightness.  A value of -1 will render the layer completely
 * black.  A value of 0 will leave the brightness unchanged.  A value of 1 will
 * render the layer completely white.  Other values are linear multipliers on
 * the effect (values are clamped between -1 and 1).
 *
 * The filter effects draft [1] says the brightness function is supposed to
 * render 0 black, 1 unchanged, and all other values as a linear multiplier.
 *
 * The current WebKit implementation clamps values between -1 (black) and 1
 * (white) [2].  There is a bug open to change the filter effect spec [3].
 *
 * TODO: revisit this if the spec is still unmodified before we release
 *
 * [1] https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
 * [2] https://github.com/WebKit/webkit/commit/8f4765e569
 * [3] https://www.w3.org/Bugs/Public/show_bug.cgi?id=15647
 *
 * @param {number|undefined} brightness The brightness of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setBrightness = function(brightness) {
  this.set(ol.layer.LayerProperty.BRIGHTNESS, brightness);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setBrightness',
    ol.layer.Base.prototype.setBrightness);

/**
 * Adjust the layer contrast.  A value of 0 will render the layer completely
 * grey.  A value of 1 will leave the contrast unchanged.  Other values are
 * linear multipliers on the effect (and values over 1 are permitted).
 *
 * @param {number|undefined} contrast The contrast of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setContrast = function(contrast) {
  this.set(ol.layer.LayerProperty.CONTRAST, contrast);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setContrast',
    ol.layer.Base.prototype.setContrast);

/**
 * Apply a hue-rotation to the layer.  A value of 0 will leave the hue
 * unchanged.  Other values are radians around the color circle.
 * @param {number} hue The hue of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setHue = function(hue) {
  this.set(ol.layer.LayerProperty.HUE, hue);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setHue',
    ol.layer.Base.prototype.setHue);

/**
 * Set the extent at which the layer is visible.  If `undefined`, the layer
 * will be visible at all extents.
 * @param {ol.Extent|undefined} extent The extent of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setExtent = function(extent) {
  this.set(ol.layer.LayerProperty.EXTENT, extent);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setExtent',
    ol.layer.Base.prototype.setExtent);

/**
 * @param {number|undefined} maxResolution The maximum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setMaxResolution = function(maxResolution) {
  this.set(ol.layer.LayerProperty.MAX_RESOLUTION, maxResolution);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setMaxResolution',
    ol.layer.Base.prototype.setMaxResolution);

/**
 * @param {number|undefined} minResolution The minimum resolution of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setMinResolution = function(minResolution) {
  this.set(ol.layer.LayerProperty.MIN_RESOLUTION, minResolution);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setMinResolution',
    ol.layer.Base.prototype.setMinResolution);

/**
 * @param {number} opacity The opacity of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setOpacity = function(opacity) {
  this.set(ol.layer.LayerProperty.OPACITY, opacity);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setOpacity',
    ol.layer.Base.prototype.setOpacity);

/**
 * Adjust layer saturation.  A value of 0 will render the layer completely
 * unsaturated.  A value of 1 will leave the saturation unchanged.  Other
 * values are linear multipliers of the effect (and values over 1 are
 * permitted).
 *
 * @param {number|undefined} saturation The saturation of the layer.
 * @observable
 * @api
 */
ol.layer.Base.prototype.setSaturation = function(saturation) {
  this.set(ol.layer.LayerProperty.SATURATION, saturation);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setSaturation',
    ol.layer.Base.prototype.setSaturation);

/**
 * @param {boolean} visible The visibility of the layer.
 * @observable
 * @api stable
 */
ol.layer.Base.prototype.setVisible = function(visible) {
  this.set(ol.layer.LayerProperty.VISIBLE, visible);
};
goog.exportProperty(
    ol.layer.Base.prototype,
    'setVisible',
    ol.layer.Base.prototype.setVisible);
<|endoftext|>


<commit_before>/**
 * @module ol/style/IconImage
 */

import {createCanvasContext2D} from '../dom.js';
import {listenOnce, unlistenByKey} from '../events.js';
import EventTarget from '../events/Target.js';
import EventType from '../events/EventType.js';
import ImageState from '../ImageState.js';
import {shared as iconImageCache} from '../style/IconImageCache.js';

class IconImage extends EventTarget {
  /**
   * @param {HTMLImageElement|HTMLCanvasElement} image Image.
   * @param {string|undefined} src Src.
   * @param {import("../size.js").Size} size Size.
   * @param {?string} crossOrigin Cross origin.
   * @param {import("../ImageState.js").default} imageState Image state.
   * @param {import("../color.js").Color} color Color.
   */
  constructor(image, src, size, crossOrigin, imageState, color) {

    super();

    /**
     * @private
     * @type {HTMLImageElement|HTMLCanvasElement}
     */
    this.hitDetectionImage_ = null;

    /**
     * @private
     * @type {HTMLImageElement|HTMLCanvasElement}
     */
    this.image_ = !image ? new Image() : image;

    if (crossOrigin !== null) {
      this.image_.crossOrigin = crossOrigin;
    }

    /**
     * @private
     * @type {HTMLCanvasElement}
     */
    this.canvas_ = color ?
      /** @type {HTMLCanvasElement} */ (document.createElement('canvas')) :
      null;

    /**
     * @private
     * @type {import("../color.js").Color}
     */
    this.color_ = color;

    /**
     * @private
     * @type {Array<import("../events.js").EventsKey>}
     */
    this.imageListenerKeys_ = null;

    /**
     * @private
     * @type {import("../ImageState.js").default}
     */
    this.imageState_ = imageState;

    /**
     * @private
     * @type {import("../size.js").Size}
     */
    this.size_ = size;

    /**
     * @private
     * @type {string|undefined}
     */
    this.src_ = src;

    /**
     * @private
     * @type {boolean}
     */
    this.tainting_ = false;
    if (this.imageState_ == ImageState.LOADED) {
      this.determineTainting_();
    }

  }

  /**
   * @private
   */
  determineTainting_() {
    const context = createCanvasContext2D(1, 1);
    try {
      context.drawImage(this.image_, 0, 0);
      context.getImageData(0, 0, 1, 1);
    } catch (e) {
      this.tainting_ = true;
    }
  }

  /**
   * @private
   */
  dispatchChangeEvent_() {
    this.dispatchEvent(EventType.CHANGE);
  }

  /**
   * @private
   */
  handleImageError_() {
    this.imageState_ = ImageState.ERROR;
    this.unlistenImage_();
    this.dispatchChangeEvent_();
  }

  /**
   * @private
   */
  handleImageLoad_() {
    this.imageState_ = ImageState.LOADED;
    if (this.size_) {
      this.image_.width = this.size_[0];
      this.image_.height = this.size_[1];
    }
    this.size_ = [this.image_.width, this.image_.height];
    this.unlistenImage_();
    this.determineTainting_();
    this.replaceColor_();
    this.dispatchChangeEvent_();
  }

  /**
   * @param {number} pixelRatio Pixel ratio.
   * @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element.
   */
  getImage(pixelRatio) {
    return this.canvas_ ? this.canvas_ : this.image_;
  }

  /**
   * @return {import("../ImageState.js").default} Image state.
   */
  getImageState() {
    return this.imageState_;
  }

  /**
   * @param {number} pixelRatio Pixel ratio.
   * @return {HTMLImageElement|HTMLCanvasElement} Image element.
   */
  getHitDetectionImage(pixelRatio) {
    if (!this.hitDetectionImage_) {
      if (this.tainting_) {
        const width = this.size_[0];
        const height = this.size_[1];
        const context = createCanvasContext2D(width, height);
        context.fillRect(0, 0, width, height);
        this.hitDetectionImage_ = context.canvas;
      } else {
        this.hitDetectionImage_ = this.image_;
      }
    }
    return this.hitDetectionImage_;
  }

  /**
   * @return {import("../size.js").Size} Image size.
   */
  getSize() {
    return this.size_;
  }

  /**
   * @return {string|undefined} Image src.
   */
  getSrc() {
    return this.src_;
  }

  /**
   * Load not yet loaded URI.
   */
  load() {
    if (this.imageState_ == ImageState.IDLE) {
      this.imageState_ = ImageState.LOADING;
      this.imageListenerKeys_ = [
        listenOnce(this.image_, EventType.ERROR,
          this.handleImageError_, this),
        listenOnce(this.image_, EventType.LOAD,
          this.handleImageLoad_, this)
      ];
      try {
        this.image_.src = this.src_;
      } catch (e) {
        this.handleImageError_();
      }
    }
  }

  /**
   * @private
   */
  replaceColor_() {
    if (this.tainting_ || this.color_ === null) {
      return;
    }

    this.canvas_.width = this.image_.width;
    this.canvas_.height = this.image_.height;

    const ctx = this.canvas_.getContext('2d');
    ctx.drawImage(this.image_, 0, 0);

    const imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
    const data = imgData.data;
    const r = this.color_[0] / 255.0;
    const g = this.color_[1] / 255.0;
    const b = this.color_[2] / 255.0;

    for (let i = 0, ii = data.length; i < ii; i += 4) {
      data[i] *= r;
      data[i + 1] *= g;
      data[i + 2] *= b;
    }
    ctx.putImageData(imgData, 0, 0);
  }

  /**
   * Discards event handlers which listen for load completion or errors.
   *
   * @private
   */
  unlistenImage_() {
    this.imageListenerKeys_.forEach(unlistenByKey);
    this.imageListenerKeys_ = null;
  }
}

/**
 * @param {HTMLImageElement|HTMLCanvasElement} image Image.
 * @param {string} src Src.
 * @param {import("../size.js").Size} size Size.
 * @param {?string} crossOrigin Cross origin.
 * @param {import("../ImageState.js").default} imageState Image state.
 * @param {import("../color.js").Color} color Color.
 * @return {IconImage} Icon image.
 */
export function get(image, src, size, crossOrigin, imageState, color) {
  let iconImage = iconImageCache.get(src, crossOrigin, color);
  if (!iconImage) {
    iconImage = new IconImage(image, src, size, crossOrigin, imageState, color);
    iconImageCache.set(src, crossOrigin, color, iconImage);
  }
  return iconImage;
}

export default IconImage;
<commit_msg>Lazily detect tainted canvas<commit_after>/**
 * @module ol/style/IconImage
 */

import {createCanvasContext2D} from '../dom.js';
import {listenOnce, unlistenByKey} from '../events.js';
import EventTarget from '../events/Target.js';
import EventType from '../events/EventType.js';
import ImageState from '../ImageState.js';
import {shared as iconImageCache} from '../style/IconImageCache.js';

class IconImage extends EventTarget {
  /**
   * @param {HTMLImageElement|HTMLCanvasElement} image Image.
   * @param {string|undefined} src Src.
   * @param {import("../size.js").Size} size Size.
   * @param {?string} crossOrigin Cross origin.
   * @param {import("../ImageState.js").default} imageState Image state.
   * @param {import("../color.js").Color} color Color.
   */
  constructor(image, src, size, crossOrigin, imageState, color) {

    super();

    /**
     * @private
     * @type {HTMLImageElement|HTMLCanvasElement}
     */
    this.hitDetectionImage_ = null;

    /**
     * @private
     * @type {HTMLImageElement|HTMLCanvasElement}
     */
    this.image_ = !image ? new Image() : image;

    if (crossOrigin !== null) {
      this.image_.crossOrigin = crossOrigin;
    }

    /**
     * @private
     * @type {HTMLCanvasElement}
     */
    this.canvas_ = color ?
      /** @type {HTMLCanvasElement} */ (document.createElement('canvas')) :
      null;

    /**
     * @private
     * @type {import("../color.js").Color}
     */
    this.color_ = color;

    /**
     * @private
     * @type {Array<import("../events.js").EventsKey>}
     */
    this.imageListenerKeys_ = null;

    /**
     * @private
     * @type {import("../ImageState.js").default}
     */
    this.imageState_ = imageState;

    /**
     * @private
     * @type {import("../size.js").Size}
     */
    this.size_ = size;

    /**
     * @private
     * @type {string|undefined}
     */
    this.src_ = src;

    /**
     * @private
     * @type {boolean|undefined}
     */
    this.tainted_;

  }

  /**
   * @private
   * @return {boolean} The image canvas is tainted.
   */
  isTainted_() {
    if (this.tainted_ === undefined && this.imageState_ === ImageState.LOADED) {
      this.tainted_ = false;
      const context = createCanvasContext2D(1, 1);
      try {
        context.drawImage(this.image_, 0, 0);
        context.getImageData(0, 0, 1, 1);
      } catch (e) {
        this.tainted_ = true;
      }
    }
    return this.tainted_ === true;
  }

  /**
   * @private
   */
  dispatchChangeEvent_() {
    this.dispatchEvent(EventType.CHANGE);
  }

  /**
   * @private
   */
  handleImageError_() {
    this.imageState_ = ImageState.ERROR;
    this.unlistenImage_();
    this.dispatchChangeEvent_();
  }

  /**
   * @private
   */
  handleImageLoad_() {
    this.imageState_ = ImageState.LOADED;
    if (this.size_) {
      this.image_.width = this.size_[0];
      this.image_.height = this.size_[1];
    }
    this.size_ = [this.image_.width, this.image_.height];
    this.unlistenImage_();
    this.replaceColor_();
    this.dispatchChangeEvent_();
  }

  /**
   * @param {number} pixelRatio Pixel ratio.
   * @return {HTMLImageElement|HTMLCanvasElement} Image or Canvas element.
   */
  getImage(pixelRatio) {
    return this.canvas_ ? this.canvas_ : this.image_;
  }

  /**
   * @return {import("../ImageState.js").default} Image state.
   */
  getImageState() {
    return this.imageState_;
  }

  /**
   * @param {number} pixelRatio Pixel ratio.
   * @return {HTMLImageElement|HTMLCanvasElement} Image element.
   */
  getHitDetectionImage(pixelRatio) {
    if (!this.hitDetectionImage_) {
      if (this.isTainted_()) {
        const width = this.size_[0];
        const height = this.size_[1];
        const context = createCanvasContext2D(width, height);
        context.fillRect(0, 0, width, height);
        this.hitDetectionImage_ = context.canvas;
      } else {
        this.hitDetectionImage_ = this.image_;
      }
    }
    return this.hitDetectionImage_;
  }

  /**
   * @return {import("../size.js").Size} Image size.
   */
  getSize() {
    return this.size_;
  }

  /**
   * @return {string|undefined} Image src.
   */
  getSrc() {
    return this.src_;
  }

  /**
   * Load not yet loaded URI.
   */
  load() {
    if (this.imageState_ == ImageState.IDLE) {
      this.imageState_ = ImageState.LOADING;
      this.imageListenerKeys_ = [
        listenOnce(this.image_, EventType.ERROR,
          this.handleImageError_, this),
        listenOnce(this.image_, EventType.LOAD,
          this.handleImageLoad_, this)
      ];
      try {
        this.image_.src = this.src_;
      } catch (e) {
        this.handleImageError_();
      }
    }
  }

  /**
   * @private
   */
  replaceColor_() {
    if (!this.color_ || this.isTainted_()) {
      return;
    }

    this.canvas_.width = this.image_.width;
    this.canvas_.height = this.image_.height;

    const ctx = this.canvas_.getContext('2d');
    ctx.drawImage(this.image_, 0, 0);

    const imgData = ctx.getImageData(0, 0, this.image_.width, this.image_.height);
    const data = imgData.data;
    const r = this.color_[0] / 255.0;
    const g = this.color_[1] / 255.0;
    const b = this.color_[2] / 255.0;

    for (let i = 0, ii = data.length; i < ii; i += 4) {
      data[i] *= r;
      data[i + 1] *= g;
      data[i + 2] *= b;
    }
    ctx.putImageData(imgData, 0, 0);
  }

  /**
   * Discards event handlers which listen for load completion or errors.
   *
   * @private
   */
  unlistenImage_() {
    this.imageListenerKeys_.forEach(unlistenByKey);
    this.imageListenerKeys_ = null;
  }
}

/**
 * @param {HTMLImageElement|HTMLCanvasElement} image Image.
 * @param {string} src Src.
 * @param {import("../size.js").Size} size Size.
 * @param {?string} crossOrigin Cross origin.
 * @param {import("../ImageState.js").default} imageState Image state.
 * @param {import("../color.js").Color} color Color.
 * @return {IconImage} Icon image.
 */
export function get(image, src, size, crossOrigin, imageState, color) {
  let iconImage = iconImageCache.get(src, crossOrigin, color);
  if (!iconImage) {
    iconImage = new IconImage(image, src, size, crossOrigin, imageState, color);
    iconImageCache.set(src, crossOrigin, color, iconImage);
  }
  return iconImage;
}

export default IconImage;
<|endoftext|>


<commit_before>(function (window, angular, undefined) {
  'use strict';

  var orbweaver = angular.module('orbResource', ['ngResource']);

  orbweaver.factory("orbRestfulResource", ['$resource', function ($resource) {
    return function (url, params, methods) {
      var idProperty = 'id';
      if( params && params.idProperty ) { idProperty = params.idProperty; }

      var defaults = {
        update: {method: 'put', isArray: false},
        create: {method: 'post'}
      };

      methods = angular.extend(defaults, methods);

      var resource = $resource(url, params, methods);

      resource.prototype.$save = function (params, success, failure) {
        if (!this[idProperty]) {
          this.$create(params, success, failure);
        } else {
          this.$update(params, success, failure);
        }
      };

      return resource;
    };
  }]);

  orbweaver.factory("orbRestfulService", ['$q', function ($q) {
    var defer = function (fn, params) {
      params = params || {};
      var deferred = $q.defer();
      fn(params,
        function (response) {
          deferred.resolve(response);
        },
        function (response) {
          deferred.reject(response);
        });
      return deferred.promise;
    };

    var deferInstance = function (inst, fn, params) {
      params = params || {};
      var deferred = $q.defer();
      inst[fn](params,
        function (response) {
          deferred.resolve(response);
        },
        function (response) {
          deferred.reject(response);
        });
      return deferred.promise;
    };

    return function (RestfulResource) {
      return {
        empty: function () {
          return new RestfulResource();
        },
        all: function (params) {
          return defer(RestfulResource.query, params);
        },
        find: function (id, params) {
          params = params || {};
          params = angular.extend(params, {id: id});
          return defer(RestfulResource.get, params);
        },
        save: function (res, params) {
          params = params || {};
          if (res.id) {
            params = angular.extend(params, {id: res.id});
            return deferInstance(res, "$update", params);
          } else {
            return deferInstance(res, "$create", params);
          }
        },
        'delete': function (res, params) {
          params = params || {};
          params = angular.extend(params, {id: res.id});
          return defer(RestfulResource['delete'], params);
        }
      };
    };
  }]);
})(window, window.angular);
<commit_msg>fix(orbResource): use idProperty in orbRestfulService from orbRestfulResource<commit_after>(function (window, angular, undefined) {
  'use strict';

  var orbweaver = angular.module('orbResource', ['ngResource']);

  orbweaver.factory("orbRestfulResource", ['$resource', function ($resource) {
    return function (url, params, methods, options) {
      var idProperty = 'id';
      if( options && options.idProperty ) { idProperty = options.idProperty; }

      var defaults = {
        update: {method: 'put', isArray: false},
        create: {method: 'post'}
      };

      methods = angular.extend(defaults, methods);

      var resource = $resource(url, params, methods);
      resource.idProperty = idProperty;

      resource.prototype.$save = function (params, success, failure) {
        if (!this[idProperty]) {
          this.$create(params, success, failure);
        } else {
          this.$update(params, success, failure);
        }
      };

      return resource;
    };
  }]);

  orbweaver.factory("orbRestfulService", ['$q', function ($q) {
    var defer = function (fn, params) {
      params = params || {};
      var deferred = $q.defer();
      fn(params,
          function (response) {
            deferred.resolve(response);
          },
          function (response) {
            deferred.reject(response);
          });
      return deferred.promise;
    };

    var deferInstance = function (inst, fn, params) {
      params = params || {};
      var deferred = $q.defer();
      inst[fn](params,
          function (response) {
            deferred.resolve(response);
          },
          function (response) {
            deferred.reject(response);
          });
      return deferred.promise;
    };

    return function (RestfulResource) {
      var idProperty = RestfulResource.idProperty;

      return {
        empty: function () {
          return new RestfulResource();
        },
        all: function (params) {
          return defer(RestfulResource.query, params);
        },
        find: function (id, params) {
          params = params || {};
          var options = {};
          options[idProperty] = id;
          params = angular.extend(params, options);
          return defer(RestfulResource.get, params);
        },
        save: function (res, params) {
          params = params || {};
          if (res.id) {
            var options = {};
            options[idProperty] = res[idProperty];
            params = angular.extend(params, options);
            return deferInstance(res, "$update", params);
          } else {
            return deferInstance(res, "$create", params);
          }
        },
        'delete': function (res, params) {
          params = params || {};
          var options = {};
          options[idProperty] = res[idProperty];
          params = angular.extend(params, options);
          return defer(RestfulResource['delete'], params);
        }
      };
    };
  }]);
})(window, window.angular);
<|endoftext|>


<commit_before>import React, { Component } from 'react';

import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery,
	setQueryOptions,
	setQueryListener,
	setComponentProps,
	updateComponentProps,
} from '@appbaseio/reactivecore/lib/actions';
import {
	pushToAndClause,
	parseHits,
	isEqual,
	checkPropChange,
	checkSomePropChange,
	getOptionsFromQuery,
} from '@appbaseio/reactivecore/lib/utils/helper';
import types from '@appbaseio/reactivecore/lib/utils/types';

import { connect, getComponent, hasCustomRenderer, getValidPropsKeys } from '../../utils';

class ReactiveComponent extends Component {
	constructor(props) {
		super(props);
		this.internalComponent = null;
		this.defaultQuery = null;
		props.setQueryListener(props.componentId, props.onQueryChange, props.onError);

		this.setQuery = (obj) => {
			this.props.updateQuery({
				...obj,
				componentId: props.componentId,
				label: props.filterLabel,
				showFilter: props.showFilter,
				URLParams: props.URLParams,
			});
		};

		if (props.defaultQuery) {
			this.internalComponent = `${props.componentId}__internal`;
		}

		props.addComponent(props.componentId);
		props.setComponentProps(props.componentId, props);
		if (this.internalComponent) {
			props.addComponent(this.internalComponent);
		}

		this.setReact(props);

		// set query for internal component if customQuery is absent
		if (!props.customQuery && this.internalComponent && props.defaultQuery) {
			this.defaultQuery = props.defaultQuery();
			const { query } = this.defaultQuery || {};
			const customQueryOptions = this.defaultQuery
				? getOptionsFromQuery(this.defaultQuery)
				: null;

			if (customQueryOptions) {
				props.setQueryOptions(this.internalComponent, customQueryOptions, false);
			}

			props.updateQuery({
				componentId: this.internalComponent,
				query: query || null,
			});
		}
	}

	componentDidMount() {
		const {
			customQuery,
			selectedValue,
			value,
			defaultValue,
			componentId,
			filterLabel,
			showFilter,
			URLParams,
		} = this.props;
		const initialValue = selectedValue || value || defaultValue || null;

		if (customQuery) {
			const { query } = customQuery(this.props) || {};
			this.props.updateQuery({
				componentId,
				query,
				value: initialValue,
				label: filterLabel,
				showFilter,
				URLParams,
			});
		}
	}

	componentDidUpdate(prevProps) {
		checkSomePropChange(this.props, prevProps, getValidPropsKeys(this.props), () => {
			this.props.updateComponentProps(this.props.componentId, this.props);
		});
		if (!this.props.customQuery) {
			// only consider hits and defaultQuery when customQuery is absent
			if (
				this.props.onData
				&& (!isEqual(prevProps.hits, this.props.hits)
					|| !isEqual(prevProps.aggregations, this.props.aggregations))
			) {
				this.props.onData(this.getData());
			}

			if (this.props.defaultQuery && !isEqual(this.props.defaultQuery(), this.defaultQuery)) {
				this.defaultQuery = this.props.defaultQuery();
				const { query, ...queryOptions } = this.defaultQuery || {};

				if (queryOptions) {
					this.props.setQueryOptions(this.internalComponent, queryOptions, false);
				}

				this.props.updateQuery({
					componentId: this.internalComponent,
					query: query || null,
				});
			}
		}

		checkPropChange(this.props.react, prevProps.react, () => {
			this.setReact(this.props);
		});
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);

		if (this.internalComponent) {
			this.props.removeComponent(this.internalComponent);
		}
	}

	setReact = (props) => {
		const { react } = props;

		if (react) {
			if (this.internalComponent) {
				const newReact = pushToAndClause(react, this.internalComponent);
				props.watchComponent(props.componentId, newReact);
			} else {
				props.watchComponent(props.componentId, react);
			}
		} else if (this.internalComponent) {
			props.watchComponent(props.componentId, {
				and: this.internalComponent,
			});
		}
	};

	getData() {
		const { hits, aggregations } = this.props;
		return {
			data: parseHits(hits),
			rawData: hits,
			aggregations,
		};
	}

	getComponent() {
		const { error, isLoading, selectedValue } = this.props;
		const data = {
			error,
			loading: isLoading,
			...this.getData(),
			value: selectedValue,
			setQuery: this.setQuery,
		};
		return getComponent(data, this.props);
	}

	render() {
		if (hasCustomRenderer(this.props)) {
			return this.getComponent();
		}
		return null;
	}
}

ReactiveComponent.defaultProps = {
	showFilter: true,
	URLParams: false,
};

ReactiveComponent.propTypes = {
	addComponent: types.funcRequired,
	error: types.title,
	removeComponent: types.funcRequired,
	setQueryListener: types.funcRequired,
	setQueryOptions: types.funcRequired,
	updateQuery: types.funcRequired,
	watchComponent: types.funcRequired,
	aggregations: types.selectedValues,
	hits: types.data,
	isLoading: types.bool,
	selectedValue: types.selectedValue,
	setComponentProps: types.funcRequired,
	updateComponentProps: types.funcRequired,
	// component props
	children: types.func,
	componentId: types.stringRequired,
	defaultQuery: types.func,
	customQuery: types.func,
	defaultValue: types.any, // eslint-disable-line
	value: types.any, // eslint-disable-line
	filterLabel: types.string,
	onQueryChange: types.func,
	onError: types.func,
	react: types.react,
	render: types.func,
	showFilter: types.bool,
	URLParams: types.bool,
	onData: types.func,
};

const mapStateToProps = (state, props) => ({
	aggregations:
		(state.aggregations[props.componentId] && state.aggregations[props.componentId]) || null,
	hits: (state.hits[props.componentId] && state.hits[props.componentId].hits) || [],
	selectedValue:
		(state.selectedValues[props.componentId]
			&& state.selectedValues[props.componentId].value)
		|| null,
	isLoading: state.isLoading[props.componentId],
	error: state.error[props.componentId],
});

const mapDispatchtoProps = dispatch => ({
	setComponentProps: (component, options) => dispatch(setComponentProps(component, options)),
	updateComponentProps: (component, options) =>
		dispatch(updateComponentProps(component, options)),
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	setQueryOptions: (component, props, execute) =>
		dispatch(setQueryOptions(component, props, execute)),
	setQueryListener: (component, onQueryChange, beforeQueryChange) =>
		dispatch(setQueryListener(component, onQueryChange, beforeQueryChange)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
	watchComponent: (component, react) => dispatch(watchComponent(component, react)),
});

const ConnectedComponent = connect(
	mapStateToProps,
	mapDispatchtoProps,
)(props => <ReactiveComponent ref={props.myForwardedRef} {...props} />);

// eslint-disable-next-line
const ForwardRefComponent = React.forwardRef((props, ref) => (
	<ConnectedComponent {...props} myForwardedRef={ref} />
));

ForwardRefComponent.name = 'ReactiveComponent';
export default ForwardRefComponent;
<commit_msg>fix(web): customQuery issue in ReactiveComponent #1023<commit_after>import React, { Component } from 'react';

import {
	addComponent,
	removeComponent,
	watchComponent,
	updateQuery,
	setQueryOptions,
	setQueryListener,
	setComponentProps,
	updateComponentProps,
} from '@appbaseio/reactivecore/lib/actions';
import {
	pushToAndClause,
	parseHits,
	isEqual,
	checkPropChange,
	checkSomePropChange,
	getOptionsFromQuery,
} from '@appbaseio/reactivecore/lib/utils/helper';
import types from '@appbaseio/reactivecore/lib/utils/types';

import { connect, getComponent, hasCustomRenderer, getValidPropsKeys } from '../../utils';

class ReactiveComponent extends Component {
	constructor(props) {
		super(props);
		this.internalComponent = null;
		this.defaultQuery = null;
		props.setQueryListener(props.componentId, props.onQueryChange, props.onError);

		this.setQuery = ({ options, ...obj }) => {
			if (options) {
				props.setQueryOptions(props.componentId, options, false);
			}
			this.props.updateQuery({
				...obj,
				componentId: props.componentId,
				label: props.filterLabel,
				showFilter: props.showFilter,
				URLParams: props.URLParams,
			});
		};

		if (props.defaultQuery) {
			this.internalComponent = `${props.componentId}__internal`;
		}

		props.addComponent(props.componentId);
		props.setComponentProps(props.componentId, props);
		if (this.internalComponent) {
			props.addComponent(this.internalComponent);
		}

		this.setReact(props);

		if (this.internalComponent && props.defaultQuery) {
			this.defaultQuery = props.defaultQuery();
			const { query } = this.defaultQuery || {};
			const defaultQueryOptions = this.defaultQuery
				? getOptionsFromQuery(this.defaultQuery)
				: null;

			if (defaultQueryOptions) {
				props.setQueryOptions(this.internalComponent, defaultQueryOptions, false);
			}

			props.updateQuery({
				componentId: this.internalComponent,
				query: query || null,
			});
		}
	}

	componentDidMount() {
		const {
			customQuery,
			selectedValue,
			value,
			defaultValue,
			componentId,
			filterLabel,
			showFilter,
			URLParams,
		} = this.props;
		const initialValue = selectedValue || value || defaultValue || null;

		if (customQuery) {
			const calcCustomQuery = customQuery(this.props);
			const { query } = calcCustomQuery || {};
			const customQueryOptions = calcCustomQuery
				? getOptionsFromQuery(calcCustomQuery)
				: null;
			if (customQueryOptions) {
				this.props.setQueryOptions(componentId, customQueryOptions, false);
			}
			this.props.updateQuery({
				componentId,
				query,
				value: initialValue,
				label: filterLabel,
				showFilter,
				URLParams,
			});
		}
	}

	componentDidUpdate(prevProps) {
		checkSomePropChange(this.props, prevProps, getValidPropsKeys(this.props), () => {
			this.props.updateComponentProps(this.props.componentId, this.props);
		});
		// only consider hits and defaultQuery when customQuery is absent
		if (
			this.props.onData
			&& (!isEqual(prevProps.hits, this.props.hits)
				|| !isEqual(prevProps.aggregations, this.props.aggregations))
		) {
			this.props.onData(this.getData());
		}

		if (this.props.defaultQuery && !isEqual(this.props.defaultQuery(), this.defaultQuery)) {
			this.defaultQuery = this.props.defaultQuery();
			const { query, ...queryOptions } = this.defaultQuery || {};

			if (queryOptions) {
				this.props.setQueryOptions(this.internalComponent, queryOptions, false);
			}

			this.props.updateQuery({
				componentId: this.internalComponent,
				query: query || null,
			});
		}

		if (
			this.props.customQuery
			&& !isEqual(this.props.customQuery(this.props), prevProps.customQuery(this.props))
		) {
			const { query, ...queryOptions } = this.props.customQuery(this.props) || {};

			if (queryOptions) {
				this.props.setQueryOptions(this.props.componentId, queryOptions, false);
			}

			this.props.updateQuery({
				componentId: this.props.componentId,
				query: query || null,
			});
		}

		checkPropChange(this.props.react, prevProps.react, () => {
			this.setReact(this.props);
		});
	}

	componentWillUnmount() {
		this.props.removeComponent(this.props.componentId);

		if (this.internalComponent) {
			this.props.removeComponent(this.internalComponent);
		}
	}

	setReact = (props) => {
		const { react } = props;

		if (react) {
			if (this.internalComponent) {
				const newReact = pushToAndClause(react, this.internalComponent);
				props.watchComponent(props.componentId, newReact);
			} else {
				props.watchComponent(props.componentId, react);
			}
		} else if (this.internalComponent) {
			props.watchComponent(props.componentId, {
				and: this.internalComponent,
			});
		}
	};

	getData() {
		const { hits, aggregations } = this.props;
		return {
			data: parseHits(hits),
			rawData: hits,
			aggregations,
		};
	}

	getComponent() {
		const { error, isLoading, selectedValue } = this.props;
		const data = {
			error,
			loading: isLoading,
			...this.getData(),
			value: selectedValue,
			setQuery: this.setQuery,
		};
		return getComponent(data, this.props);
	}

	render() {
		if (hasCustomRenderer(this.props)) {
			return this.getComponent();
		}
		return null;
	}
}

ReactiveComponent.defaultProps = {
	showFilter: true,
	URLParams: false,
};

ReactiveComponent.propTypes = {
	addComponent: types.funcRequired,
	error: types.title,
	removeComponent: types.funcRequired,
	setQueryListener: types.funcRequired,
	setQueryOptions: types.funcRequired,
	updateQuery: types.funcRequired,
	watchComponent: types.funcRequired,
	aggregations: types.selectedValues,
	hits: types.data,
	isLoading: types.bool,
	selectedValue: types.selectedValue,
	setComponentProps: types.funcRequired,
	updateComponentProps: types.funcRequired,
	// component props
	children: types.func,
	componentId: types.stringRequired,
	defaultQuery: types.func,
	customQuery: types.func,
	defaultValue: types.any, // eslint-disable-line
	value: types.any, // eslint-disable-line
	filterLabel: types.string,
	onQueryChange: types.func,
	onError: types.func,
	react: types.react,
	render: types.func,
	showFilter: types.bool,
	URLParams: types.bool,
	onData: types.func,
};

const mapStateToProps = (state, props) => ({
	aggregations:
		(state.aggregations[props.componentId] && state.aggregations[props.componentId]) || null,
	hits: (state.hits[props.componentId] && state.hits[props.componentId].hits) || [],
	selectedValue:
		(state.selectedValues[props.componentId]
			&& state.selectedValues[props.componentId].value)
		|| null,
	isLoading: state.isLoading[props.componentId],
	error: state.error[props.componentId],
});

const mapDispatchtoProps = dispatch => ({
	setComponentProps: (component, options) => dispatch(setComponentProps(component, options)),
	updateComponentProps: (component, options) =>
		dispatch(updateComponentProps(component, options)),
	addComponent: component => dispatch(addComponent(component)),
	removeComponent: component => dispatch(removeComponent(component)),
	setQueryOptions: (component, props, execute) =>
		dispatch(setQueryOptions(component, props, execute)),
	setQueryListener: (component, onQueryChange, beforeQueryChange) =>
		dispatch(setQueryListener(component, onQueryChange, beforeQueryChange)),
	updateQuery: updateQueryObject => dispatch(updateQuery(updateQueryObject)),
	watchComponent: (component, react) => dispatch(watchComponent(component, react)),
});

const ConnectedComponent = connect(
	mapStateToProps,
	mapDispatchtoProps,
)(props => <ReactiveComponent ref={props.myForwardedRef} {...props} />);

// eslint-disable-next-line
const ForwardRefComponent = React.forwardRef((props, ref) => (
	<ConnectedComponent {...props} myForwardedRef={ref} />
));

ForwardRefComponent.name = 'ReactiveComponent';
export default ForwardRefComponent;
<|endoftext|>


<commit_before>/**
 * 上传图片弹框
 */

import React, { Component, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import { DropTarget, DragSource } from 'react-dnd';

const ITEM = 'uploadImageItem';

class UploadImageItem extends (PureComponent || Component) {
  static propTypes = {
    // 组件所在位置的下标
    index: PropTypes.number.isRequired,
    // 是否可拖拽
    isDragable: PropTypes.bool.isRequired,
    // 删除组件的回调函数
    onDelete: PropTypes.func.isRequired,
    // 拖拽时移动组件的回调函数
    onMove: PropTypes.func.isRequired
  };

  state = {
    hideDeleteIcon: false
  };

  handleRemove = () => {
    let { index, onDelete } = this.props;
    onDelete(index);
  };

  handleDragStart = () => {
    this.setState({
      hideDeleteIcon: true
    });
  };

  handleDragEnd = () => {
    this.setState({
      hideDeleteIcon: false
    });
  };

  render() {
    let { data, connectDragSource, connectDropTarget, isDragging } = this.props;

    let { hideDeleteIcon } = this.state;

    const style = {
      opacity: isDragging ? 0.3 : 1
    };

    const node = (

        <div
          className="image-box"
          style={{
            backgroundImage: `url(${data.src})`
          }}
        />
        {!hideDeleteIcon && (

            ×

        )}
        {data.progress && (

{`${data.progress.toFixed(1)}%`}

        )}

    );

    return connectDragSource(connectDropTarget(node));
  }
}

const dndSource = {
  canDrag(props) {
    return props.isDragable;
  },

  beginDrag(props) {
    return {
      index: props.index
    };
  }
};

const dndTarget = {
  hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;
    const isInline = props.isInline;

    // Don't replace items with themselves
    if (dragIndex === hoverIndex) {
      return;
    }

    // Determine rectangle on screen
    const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();

    // Get vertical middle
    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
    const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;

    // Determine mouse position
    const clientOffset = monitor.getClientOffset();

    // Get pixels to the top
    const hoverClientY = clientOffset.y - hoverBoundingRect.top;
    const hoverClientX = clientOffset.x - hoverBoundingRect.left;

    // Only perform the move when the mouse has crossed half of the items height
    // When dragging downwards, only move when the cursor is below 50%
    // When dragging upwards, only move when the cursor is above 50%

    // Dragging downwards
    if (isInline) {
      if (
        dragIndex < hoverIndex &&
        hoverClientY < hoverMiddleY &&
        hoverClientX < hoverMiddleY
      ) {
        return;
      }
    } else if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
      return;
    }

    // Dragging upwards
    if (isInline) {
      if (
        dragIndex > hoverIndex &&
        hoverClientY > hoverMiddleY &&
        hoverClientX > hoverMiddleX
      ) {
        return;
      }
    } else if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
      return;
    }

    // Time to actually perform the action
    props.onMove(dragIndex, hoverIndex);

    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    monitor.getItem().index = hoverIndex;
  }
};

/* eslint-disable new-cap, no-use-before-define */
export default DropTarget(ITEM, dndTarget, connect => ({
  connectDropTarget: connect.dropTarget()
}))(
  DragSource(ITEM, dndSource, (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging()
  }))(UploadImageItem)
);
/* eslint-enable new-cap, no-use-before-define */
<commit_msg>feat: update<commit_after>/**
 * 上传图片弹框
 */

import React, { Component, PureComponent } from 'react';
import PropTypes from 'prop-types';
import { findDOMNode } from 'react-dom';
import { DropTarget, DragSource } from 'react-dnd';

const ITEM = 'uploadImageItem';

class UploadImageItem extends (PureComponent || Component) {
  static propTypes = {
    // 组件所在位置的下标
    index: PropTypes.number.isRequired,
    // 是否可拖拽
    isDragable: PropTypes.bool.isRequired,
    // 删除组件的回调函数
    onDelete: PropTypes.func.isRequired,
    // 拖拽时移动组件的回调函数
    onMove: PropTypes.func.isRequired
  };

  state = {
    hideDeleteIcon: false
  };

  handleRemove = () => {
    let { index, onDelete } = this.props;
    onDelete(index);
  };

  handleDragStart = () => {
    this.setState({
      hideDeleteIcon: true
    });
  };

  handleDragEnd = () => {
    this.setState({
      hideDeleteIcon: false
    });
  };

  render() {
    let { data, connectDragSource, connectDropTarget, isDragging } = this.props;

    let { hideDeleteIcon } = this.state;

    const style = {
      opacity: isDragging ? 0.3 : 1
    };

    const node = (

        <div
          className="image-box"
          style={{
            backgroundImage: `url(${data.src})`
          }}
        />
        {!hideDeleteIcon && (

            ×

        )}
        {data.progress && (

{`${data.progress.toFixed(1)}%`}

        )}

    );

    return connectDragSource(connectDropTarget(node));
  }
}

const dndSource = {
  canDrag(props) {
    return props.isDragable;
  },

  beginDrag(props) {
    return {
      index: props.index
    };
  }
};

const dndTarget = {
  hover(props, monitor, component) {
    const dragIndex = monitor.getItem().index;
    const hoverIndex = props.index;
    const isInline = props.isInline;

    // Don't replace items with themselves
    if (dragIndex === hoverIndex) {
      return;
    }

    // Determine rectangle on screen
    const hoverBoundingRect = findDOMNode(component).getBoundingClientRect();

    // Get vertical middle
    const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2;
    const hoverMiddleX = (hoverBoundingRect.right - hoverBoundingRect.left) / 2;

    // Determine mouse position
    const clientOffset = monitor.getClientOffset();

    // Get pixels to the top
    const hoverClientY = clientOffset.y - hoverBoundingRect.top;
    const hoverClientX = clientOffset.x - hoverBoundingRect.left;

    // Only perform the move when the mouse has crossed half of the items height
    // When dragging downwards, only move when the cursor is below 50%
    // When dragging upwards, only move when the cursor is above 50%

    // Dragging downwards
    if (isInline) {
      if (
        dragIndex < hoverIndex &&
        hoverClientY < hoverMiddleY &&
        hoverClientX < hoverMiddleX
      ) {
        return;
      }
    } else if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
      return;
    }

    // Dragging upwards
    if (isInline) {
      if (
        dragIndex > hoverIndex &&
        hoverClientY > hoverMiddleY &&
        hoverClientX > hoverMiddleX
      ) {
        return;
      }
    } else if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
      return;
    }

    // Time to actually perform the action
    props.onMove(dragIndex, hoverIndex);

    // Note: we're mutating the monitor item here!
    // Generally it's better to avoid mutations,
    // but it's good here for the sake of performance
    // to avoid expensive index searches.
    monitor.getItem().index = hoverIndex;
  }
};

/* eslint-disable new-cap, no-use-before-define */
export default DropTarget(ITEM, dndTarget, connect => ({
  connectDropTarget: connect.dropTarget()
}))(
  DragSource(ITEM, dndSource, (connect, monitor) => ({
    connectDragSource: connect.dragSource(),
    isDragging: monitor.isDragging()
  }))(UploadImageItem)
);
/* eslint-enable new-cap, no-use-before-define */
<|endoftext|>


<commit_before>Template.home.helpers({
	covers: function() {
		return MangasData.find({});
	}
});
<commit_msg>Sort cover by names.fr 1<commit_after>Template.home.helpers({
	covers: function() {
		return MangasData.find({}, {
			sort: {
				"names.fr": 1
			}
		});
	}
});
<|endoftext|>


<commit_before>/**
 * Shared functions
 **/

handleDetailsClick = function (evType, ev) {
  if (typeof handleDetailsClick.detailsType == 'undefined') {
    handleDetailsClick.detailsType = 'INITVALUE';
  }
  handleDetailsClick.swapKeyContent = function (index) {
    let ch = $('.changes:eq(' + index + ')');
    let dt = $('.details:eq(' + index + ')');

    if (handleDetailsClick.detailsType == 'CHANGES') {
      dt.hide();
      ch.show();
    }
    else if (handleDetailsClick.detailsType == 'KEYINFO'){
      ch.hide();
      dt.show();
    }
  }

  ev.stopImmediatePropagation();
  let acc = $('#key-display .ui.accordion');
  let thisBtn = $(ev.currentTarget);
  let clickIndex;
  if (evType == 'CHANGES') clickIndex = $('.changesBtn').index(thisBtn);
  if (evType == 'KEYINFO') clickIndex = $('.detailsBtn').index(thisBtn);

  // This is the first click after page loads
  if (typeof handleDetailsClick.activeIndex == 'undefined') {
    handleDetailsClick.detailsType = evType;
    handleDetailsClick.swapKeyContent(clickIndex);
    acc.accordion('open', clickIndex);
    handleDetailsClick.activeIndex = clickIndex;
  }
  // The same type of button as before was clicked
  else if (evType == handleDetailsClick.detailsType) {
    // Same button as before
    console.log(clickIndex + ", " + handleDetailsClick.activeIndex);
    if (clickIndex == handleDetailsClick.activeIndex) {
      acc.accordion('toggle', clickIndex);
    }
    // Corresponding button on another row
    else {
      handleDetailsClick.swapKeyContent(clickIndex);
      acc.accordion('open', clickIndex);
      handleDetailsClick.activeIndex = clickIndex;
    }
  }
  // The other type of button was clicked
  else {
    handleDetailsClick.detailsType = evType;

    // Other button on same row
    if (clickIndex == handleDetailsClick.activeIndex) {
      // Pane already open
      if ($('.details:eq(' + clickIndex + ')').parent().hasClass('active')) {
        acc.accordion('close', clickIndex);
        Meteor.setTimeout(function() {handleDetailsClick.swapKeyContent(clickIndex);}, 500);
        Meteor.setTimeout(function() {acc.accordion('open', clickIndex);}, 500);
      }
      // Pane closed
      else {
        handleDetailsClick.swapKeyContent(clickIndex);
        acc.accordion('open', clickIndex);
      }
    }
    // Other button on different row
    else {
      handleDetailsClick.swapKeyContent(clickIndex);
      acc.accordion('open', clickIndex);
      handleDetailsClick.activeIndex = clickIndex;
    }
  }
}

/**
 * Page Events
 **/

Template.keyDisplay.onRendered(function () {
  $('#key-display .ui.accordion').accordion({
    selector: {
      trigger: null
    },
    animateChildren: false
  });
})

/**
 * Helpers
 **/

Template.keyDisplay.helpers({
  keys: function () {
    return Keys.find({});
  },
  keyInfo: function (keyID) {
    return Keys.findOne({keyID: keyID});
  },
  hasChanges: function (keyID) {
    return Changes.findOne({keyID: keyID}) ? 'toggle' : 'disabled';
  },
  numChanges: function (keyID) {
    let changeCount = 0;
    let allChanges = Changes.findOne({keyID: keyID});
    if (allChanges) {
      let changeLog = allChanges.log
      changeLog.forEach(function (changesObj) {
        changesObj.changes.forEach(function () {
          changeCount++;
        });
      });
    }
    switch (changeCount) {
      case 0:
        return "No Change";
        break;
      case 1:
        return changeCount + " Change";
        break;
      default:
        return changeCount + " Changes";
    }
  },
  insertColorMarkerHlp: function (sev) {
    return insertColorMarker(sev, true);
  },
  logThis: function () {
    console.log(this);
  }
});

Template.keyDetails.helpers({
  parseCharacters: function () {
    let chars = this.characters;
    let charStr = '';
    for (let char in chars) {
      charStr += '
' + chars[char].characterName + '
\n';
    }
    return charStr;
  }
});

Template.changeDetails.helpers({
  keyChanges: function (keyID) {
    return Changes.find({keyID: keyID});
  },
  addBottomClass: function (log) {
    Meteor.setTimeout(function () {
      $( ".changeDetail:last-child" ).removeClass("attached").
      addClass("bottom attached");
    }, 1);
  },
  parseChangeHlp: function (changeType, sev, oldValStr, newValStr, oldValObj, newValObj, ctx, email) {
    return parseChange(changeType, sev, oldValStr, newValStr, oldValObj, newValObj, ctx, email);
  }
});

/**
 * Event Handlers
 **/

Template.keyDisplay.events({
  "click .validate-button": function() {
    // Fetch all keys from the database and validate them
    // TODO: Use "Async" library to process these in parallel
    Meteor.call('runChecks');
  },
  "click .toggle.changesBtn": function(ev) {
    handleDetailsClick('CHANGES', ev);
  },
  "click .toggle.detailsBtn": function(ev) {
    handleDetailsClick('KEYINFO', ev);
  },
  "click .rm-key": function() {
    Keys.remove(Keys.findOne({keyID: this.keyID})._id)
  },
});
<commit_msg>Fix removing key from Key Explorer doesn't remove changes for that key<commit_after>/**
 * Shared functions
 **/

handleDetailsClick = function (evType, ev) {
  if (typeof handleDetailsClick.detailsType == 'undefined') {
    handleDetailsClick.detailsType = 'INITVALUE';
  }
  handleDetailsClick.swapKeyContent = function (index) {
    let ch = $('.changes:eq(' + index + ')');
    let dt = $('.details:eq(' + index + ')');

    if (handleDetailsClick.detailsType == 'CHANGES') {
      dt.hide();
      ch.show();
    }
    else if (handleDetailsClick.detailsType == 'KEYINFO'){
      ch.hide();
      dt.show();
    }
  }

  ev.stopImmediatePropagation();
  let acc = $('#key-display .ui.accordion');
  let thisBtn = $(ev.currentTarget);
  let clickIndex;
  if (evType == 'CHANGES') clickIndex = $('.changesBtn').index(thisBtn);
  if (evType == 'KEYINFO') clickIndex = $('.detailsBtn').index(thisBtn);

  // This is the first click after page loads
  if (typeof handleDetailsClick.activeIndex == 'undefined') {
    handleDetailsClick.detailsType = evType;
    handleDetailsClick.swapKeyContent(clickIndex);
    acc.accordion('open', clickIndex);
    handleDetailsClick.activeIndex = clickIndex;
  }
  // The same type of button as before was clicked
  else if (evType == handleDetailsClick.detailsType) {
    // Same button as before
    if (clickIndex == handleDetailsClick.activeIndex) {
      acc.accordion('toggle', clickIndex);
    }
    // Corresponding button on another row
    else {
      handleDetailsClick.swapKeyContent(clickIndex);
      acc.accordion('open', clickIndex);
      handleDetailsClick.activeIndex = clickIndex;
    }
  }
  // The other type of button was clicked
  else {
    handleDetailsClick.detailsType = evType;

    // Other button on same row
    if (clickIndex == handleDetailsClick.activeIndex) {
      // Pane already open
      if ($('.details:eq(' + clickIndex + ')').parent().hasClass('active')) {
        acc.accordion('close', clickIndex);
        Meteor.setTimeout(function() {handleDetailsClick.swapKeyContent(clickIndex);}, 500);
        Meteor.setTimeout(function() {acc.accordion('open', clickIndex);}, 500);
      }
      // Pane closed
      else {
        handleDetailsClick.swapKeyContent(clickIndex);
        acc.accordion('open', clickIndex);
      }
    }
    // Other button on different row
    else {
      handleDetailsClick.swapKeyContent(clickIndex);
      acc.accordion('open', clickIndex);
      handleDetailsClick.activeIndex = clickIndex;
    }
  }
}

/**
 * Page Events
 **/

Template.keyDisplay.onRendered(function () {
  $('#key-display .ui.accordion').accordion({
    selector: {
      trigger: null
    },
    animateChildren: false
  });
})

/**
 * Helpers
 **/

Template.keyDisplay.helpers({
  keys: function () {
    return Keys.find({});
  },
  keyInfo: function (keyID) {
    return Keys.findOne({keyID: keyID});
  },
  hasChanges: function (keyID) {
    return Changes.findOne({keyID: keyID}) ? 'toggle' : 'disabled';
  },
  numChanges: function (keyID) {
    let changeCount = 0;
    let allChanges = Changes.findOne({keyID: keyID});
    if (allChanges) {
      let changeLog = allChanges.log
      changeLog.forEach(function (changesObj) {
        changesObj.changes.forEach(function () {
          changeCount++;
        });
      });
    }
    switch (changeCount) {
      case 0:
        return "No Change";
        break;
      case 1:
        return changeCount + " Change";
        break;
      default:
        return changeCount + " Changes";
    }
  },
  insertColorMarkerHlp: function (sev) {
    return insertColorMarker(sev, true);
  },
  logThis: function () {
    console.log(this);
  }
});

Template.keyDetails.helpers({
  parseCharacters: function () {
    let chars = this.characters;
    let charStr = '';
    for (let char in chars) {
      charStr += '
' + chars[char].characterName + '
\n';
    }
    return charStr;
  }
});

Template.changeDetails.helpers({
  keyChanges: function (keyID) {
    return Changes.find({keyID: keyID});
  },
  addBottomClass: function (log) {
    Meteor.setTimeout(function () {
      $( ".changeDetail:last-child" ).removeClass("attached").
      addClass("bottom attached");
    }, 1);
  },
  parseChangeHlp: function (changeType, sev, oldValStr, newValStr, oldValObj, newValObj, ctx, email) {
    return parseChange(changeType, sev, oldValStr, newValStr, oldValObj, newValObj, ctx, email);
  }
});

/**
 * Event Handlers
 **/

Template.keyDisplay.events({
  "click .validate-button": function() {
    // Fetch all keys from the database and validate them
    // TODO: Use "Async" library to process these in parallel
    Meteor.call('runChecks');
  },
  "click .toggle.changesBtn": function(ev) {
    handleDetailsClick('CHANGES', ev);
  },
  "click .toggle.detailsBtn": function(ev) {
    handleDetailsClick('KEYINFO', ev);
  },
  "click .rm-key": function() {
    let foundChanges = Changes.findOne({keyID: this.keyID});

    if (foundChanges) Changes.remove(foundChanges._id);
    Keys.remove(Keys.findOne({keyID: this.keyID})._id);
  },
});
<|endoftext|>


<commit_before>import * as THREE from 'three';
import Eventable from '../eventable';

export default class Scene extends THREE.Scene {
  constructor() {
    super();
    Eventable.call(this);
    Eventable.make(this);

    this._worker = new Worker(require('./worker.js'));
    this._worker.transferableMessage = this._worker.webkitPostMessage || this._worker.postMessage;
    this._materials_ref_counts = {};
    this._objects = {};
    this._vehicles = {};
    this._constraints = {};
    this._is_simulating = false;

    const ab = new ArrayBuffer(1);
    this._worker.transferableMessage(ab, [ab]);
    this.SUPPORT_TRANSFERABLE = (ab.byteLength === 0);

    this._worker.onmessage = function (event) {
      let _temp,
        data = event.data;

      if (data instanceof ArrayBuffer && data.byteLength !== 1)// byteLength === 1 is the worker making a SUPPORT_TRANSFERABLE test
        data = new Float32Array(data);

      if (data instanceof Float32Array) {
        // transferable object
        switch (data[0]) {
          case MESSAGE_TYPES.WORLDREPORT:
            self._updateScene(data);
            break;

          case MESSAGE_TYPES.COLLISIONREPORT:
            self._updateCollisions(data);
            break;

          case MESSAGE_TYPES.VEHICLEREPORT:
            self._updateVehicles(data);
            break;

          case MESSAGE_TYPES.CONSTRAINTREPORT:
            self._updateConstraints(data);
            break;
          default:
        }
      } else if (data.cmd) {
        // non-transferable object
        switch (data.cmd) {
          case 'objectReady':
            _temp = data.params;
            if (self._objects[_temp]) self._objects[_temp].dispatchEvent('ready');
            break;

          case 'worldReady':
            self.dispatchEvent('ready');
            break;

          case 'vehicle':
            window.test = data;
            break;

          default:
            // Do nothing, just show the message
            console.debug(`Received: ${data.cmd}`);
            console.dir(data.params);
            break;
        }
      } else {
        switch (data[0]) {
          case MESSAGE_TYPES.WORLDREPORT:
            self._updateScene(data);
            break;

          case MESSAGE_TYPES.COLLISIONREPORT:
            self._updateCollisions(data);
            break;

          case MESSAGE_TYPES.VEHICLEREPORT:
            self._updateVehicles(data);
            break;

          case MESSAGE_TYPES.CONSTRAINTREPORT:
            self._updateConstraints(data);
            break;
          default:
        }
      }
    };

    params = params || {};
    params.ammo = Physijs.scripts.ammo || 'ammo.js';
    params.fixedTimeStep = params.fixedTimeStep || 1 / 60;
    params.rateLimit = params.rateLimit || true;
    this.execute('init', params);
  }

  _updateScene(data) {
    const num_objects = data[1];
    let object, offset;

    for (let i = 0; i < num_objects; i++) {
      offset = 2 + i * REPORT_ITEMSIZE;
      object = this._objects[data[offset]];

      if (object === undefined) continue;

      if (object.__dirtyPosition === false) {
        object.position.set(
          data[offset + 1],
          data[offset + 2],
          data[offset + 3]
        );
      }

      if (object.__dirtyRotation === false) {
        object.quaternion.set(
          data[offset + 4],
          data[offset + 5],
          data[offset + 6],
          data[offset + 7]
        );
      }

      object._physijs.linearVelocity.set(
        data[offset + 8],
        data[offset + 9],
        data[offset + 10]
      );

      object._physijs.angularVelocity.set(
        data[offset + 11],
        data[offset + 12],
        data[offset + 13]
      );
    }

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker

    this._is_simulating = false;
    this.dispatchEvent('update');
  }

  _updateVehicles(data) {
    let vehicle, wheel, offset;

    for (let i = 0; i < (data.length - 1) / VEHICLEREPORT_ITEMSIZE; i++) {
      offset = 1 + i * VEHICLEREPORT_ITEMSIZE;
      vehicle = this._vehicles[data[offset]];

      if (vehicle === undefined) continue;

      wheel = vehicle.wheels[data[offset + 1]];

      wheel.position.set(
        data[offset + 2],
        data[offset + 3],
        data[offset + 4]
      );

      wheel.quaternion.set(
        data[offset + 5],
        data[offset + 6],
        data[offset + 7],
        data[offset + 8]
      );
    }

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker
  }

  _updateConstraints(data) {
    let constraint, object, offset;

    for (let i = 0; i < (data.length - 1) / CONSTRAINTREPORT_ITEMSIZE; i++) {
      offset = 1 + i * CONSTRAINTREPORT_ITEMSIZE;
      constraint = this._constraints[data[offset]];
      object = this._objects[data[offset + 1]];

      if (constraint === undefined || object === undefined) continue;

      temp1Vector3.set(
        data[offset + 2],
        data[offset + 3],
        data[offset + 4]
      );

      temp1Matrix4.extractRotation(object.matrix);
      temp1Vector3.applyMatrix4(temp1Matrix4);

      constraint.positiona.addVectors(object.position, temp1Vector3);
      constraint.appliedImpulse = data[offset + 5];
    }

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker
  }

  _updateCollisions(data) {
    /**
     * #TODO
     * This is probably the worst way ever to handle collisions. The inherent evilness is a residual
     * effect from the previous version's evilness which mutated when switching to transferable objects.
     *
     * If you feel inclined to make this better, please do so.
     */

    const collisions = {},
      normal_offsets = {};

    // Build collision manifest
    for (let i = 0; i < data[1]; i++) {
      const offset = 2 + i * COLLISIONREPORT_ITEMSIZE;
      const object = data[offset];
      const object2 = data[offset + 1];

      normal_offsets[`${object}-${object2}`] = offset + 2;
      normal_offsets[`${object2}-${object}`] = -1 * (offset + 2);

      // Register collisions for both the object colliding and the object being collided with
      if (!collisions[object]) collisions[object] = [];
      collisions[object].push(object2);

      if (!collisions[object2]) collisions[object2] = [];
      collisions[object2].push(object);
    }

    // Deal with collisions
    for (const id1 in this._objects) {
      if (!this._objects.hasOwnProperty(id1)) continue;
      const object = this._objects[id1];

      // If object touches anything, ...
      if (collisions[id1]) {
        // Clean up touches array
        for (let j = 0; j < object._physijs.touches.length; j++) {
          if (collisions[id1].indexOf(object._physijs.touches[j]) === -1)
            object._physijs.touches.splice(j--, 1);
        }

        // Handle each colliding object
        for (let j = 0; j < collisions[id1].length; j++) {
          const id2 = collisions[id1][j];
          const object2 = this._objects[id2];

          if (object2) {
            // If object was not already touching object2, notify object
            if (object._physijs.touches.indexOf(id2) === -1) {
              object._physijs.touches.push(id2);

              temp1Vector3.subVectors(object.getLinearVelocity(), object2.getLinearVelocity());
              temp1 = temp1Vector3.clone();

              temp1Vector3.subVectors(object.getAngularVelocity(), object2.getAngularVelocity());
              temp2 = temp1Vector3.clone();

              let normal_offset = normal_offsets[`${object._physijs.id}-${object2._physijs.id}`];

              if (normal_offset > 0) {
                temp1Vector3.set(
                  -data[normal_offset],
                  -data[normal_offset + 1],
                  -data[normal_offset + 2]
                );
              } else {
                normal_offset *= -1;

                temp1Vector3.set(
                  data[normal_offset],
                  data[normal_offset + 1],
                  data[normal_offset + 2]
                );
              }

              object.dispatchEvent('collision', object2, temp1, temp2, temp1Vector3);
            }
          }
        }
      } else object._physijs.touches.length = 0; // not touching other objects
    }

    this.collisions = collisions;

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker
  }

  addConstraint(constraint, show_marker) {
    this._constraints[constraint.id] = constraint;
    this.execute('addConstraint', constraint.getDefinition());

    if (show_marker) {
      let marker;

      switch (constraint.type) {
        case 'point':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;

        case 'hinge':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;

        case 'slider':
          marker = new THREE.Mesh(
            new THREE.BoxGeometry(10, 1, 1),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);

          // This rotation isn't right if all three axis are non-0 values
          // TODO: change marker's rotation order to ZYX
          marker.rotation.set(
            constraint.axis.y, // yes, y and
            constraint.axis.x, // x axis are swapped
            constraint.axis.z
          );
          this._objects[constraint.objecta].add(marker);
          break;

        case 'conetwist':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;

        case 'dof':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;
        default:
      }
    }

    return constraint;
  }

  onSimulationResume() {
    this.execute('onSimulationResume', {});
  }

  removeConstraint(constraint) {
    if (this._constraints[constraint.id] !== undefined) {
      this.execute('removeConstraint', {id: constraint.id});
      delete this._constraints[constraint.id];
    }
  }

  execute(cmd, params) {
    this._worker.postMessage({cmd, params});
  }
}
<commit_msg>Fix scene.<commit_after>import * as THREE from 'three';
import Eventable from '../eventable';

export default class Scene extends THREE.Scene {
  constructor() {
    super(this);
    Eventable.call(this);
    Eventable.make(this);

    this._worker = new Worker(require('./worker.js'));
    this._worker.transferableMessage = this._worker.webkitPostMessage || this._worker.postMessage;
    this._materials_ref_counts = {};
    this._objects = {};
    this._vehicles = {};
    this._constraints = {};
    this._is_simulating = false;

    const ab = new ArrayBuffer(1);
    this._worker.transferableMessage(ab, [ab]);
    this.SUPPORT_TRANSFERABLE = (ab.byteLength === 0);

    this._worker.onmessage = function (event) {
      let _temp,
        data = event.data;

      if (data instanceof ArrayBuffer && data.byteLength !== 1)// byteLength === 1 is the worker making a SUPPORT_TRANSFERABLE test
        data = new Float32Array(data);

      if (data instanceof Float32Array) {
        // transferable object
        switch (data[0]) {
          case MESSAGE_TYPES.WORLDREPORT:
            self._updateScene(data);
            break;

          case MESSAGE_TYPES.COLLISIONREPORT:
            self._updateCollisions(data);
            break;

          case MESSAGE_TYPES.VEHICLEREPORT:
            self._updateVehicles(data);
            break;

          case MESSAGE_TYPES.CONSTRAINTREPORT:
            self._updateConstraints(data);
            break;
          default:
        }
      } else if (data.cmd) {
        // non-transferable object
        switch (data.cmd) {
          case 'objectReady':
            _temp = data.params;
            if (self._objects[_temp]) self._objects[_temp].dispatchEvent('ready');
            break;

          case 'worldReady':
            self.dispatchEvent('ready');
            break;

          case 'vehicle':
            window.test = data;
            break;

          default:
            // Do nothing, just show the message
            console.debug(`Received: ${data.cmd}`);
            console.dir(data.params);
            break;
        }
      } else {
        switch (data[0]) {
          case MESSAGE_TYPES.WORLDREPORT:
            self._updateScene(data);
            break;

          case MESSAGE_TYPES.COLLISIONREPORT:
            self._updateCollisions(data);
            break;

          case MESSAGE_TYPES.VEHICLEREPORT:
            self._updateVehicles(data);
            break;

          case MESSAGE_TYPES.CONSTRAINTREPORT:
            self._updateConstraints(data);
            break;
          default:
        }
      }
    };

    params = params || {};
    params.ammo = Physijs.scripts.ammo || 'ammo.js';
    params.fixedTimeStep = params.fixedTimeStep || 1 / 60;
    params.rateLimit = params.rateLimit || true;
    this.execute('init', params);
  }

  _updateScene(data) {
    const num_objects = data[1];
    let object, offset;

    for (let i = 0; i < num_objects; i++) {
      offset = 2 + i * REPORT_ITEMSIZE;
      object = this._objects[data[offset]];

      if (object === undefined) continue;

      if (object.__dirtyPosition === false) {
        object.position.set(
          data[offset + 1],
          data[offset + 2],
          data[offset + 3]
        );
      }

      if (object.__dirtyRotation === false) {
        object.quaternion.set(
          data[offset + 4],
          data[offset + 5],
          data[offset + 6],
          data[offset + 7]
        );
      }

      object._physijs.linearVelocity.set(
        data[offset + 8],
        data[offset + 9],
        data[offset + 10]
      );

      object._physijs.angularVelocity.set(
        data[offset + 11],
        data[offset + 12],
        data[offset + 13]
      );
    }

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker

    this._is_simulating = false;
    this.dispatchEvent('update');
  }

  _updateVehicles(data) {
    let vehicle, wheel, offset;

    for (let i = 0; i < (data.length - 1) / VEHICLEREPORT_ITEMSIZE; i++) {
      offset = 1 + i * VEHICLEREPORT_ITEMSIZE;
      vehicle = this._vehicles[data[offset]];

      if (vehicle === undefined) continue;

      wheel = vehicle.wheels[data[offset + 1]];

      wheel.position.set(
        data[offset + 2],
        data[offset + 3],
        data[offset + 4]
      );

      wheel.quaternion.set(
        data[offset + 5],
        data[offset + 6],
        data[offset + 7],
        data[offset + 8]
      );
    }

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker
  }

  _updateConstraints(data) {
    let constraint, object, offset;

    for (let i = 0; i < (data.length - 1) / CONSTRAINTREPORT_ITEMSIZE; i++) {
      offset = 1 + i * CONSTRAINTREPORT_ITEMSIZE;
      constraint = this._constraints[data[offset]];
      object = this._objects[data[offset + 1]];

      if (constraint === undefined || object === undefined) continue;

      temp1Vector3.set(
        data[offset + 2],
        data[offset + 3],
        data[offset + 4]
      );

      temp1Matrix4.extractRotation(object.matrix);
      temp1Vector3.applyMatrix4(temp1Matrix4);

      constraint.positiona.addVectors(object.position, temp1Vector3);
      constraint.appliedImpulse = data[offset + 5];
    }

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker
  }

  _updateCollisions(data) {
    /**
     * #TODO
     * This is probably the worst way ever to handle collisions. The inherent evilness is a residual
     * effect from the previous version's evilness which mutated when switching to transferable objects.
     *
     * If you feel inclined to make this better, please do so.
     */

    const collisions = {},
      normal_offsets = {};

    // Build collision manifest
    for (let i = 0; i < data[1]; i++) {
      const offset = 2 + i * COLLISIONREPORT_ITEMSIZE;
      const object = data[offset];
      const object2 = data[offset + 1];

      normal_offsets[`${object}-${object2}`] = offset + 2;
      normal_offsets[`${object2}-${object}`] = -1 * (offset + 2);

      // Register collisions for both the object colliding and the object being collided with
      if (!collisions[object]) collisions[object] = [];
      collisions[object].push(object2);

      if (!collisions[object2]) collisions[object2] = [];
      collisions[object2].push(object);
    }

    // Deal with collisions
    for (const id1 in this._objects) {
      if (!this._objects.hasOwnProperty(id1)) continue;
      const object = this._objects[id1];

      // If object touches anything, ...
      if (collisions[id1]) {
        // Clean up touches array
        for (let j = 0; j < object._physijs.touches.length; j++) {
          if (collisions[id1].indexOf(object._physijs.touches[j]) === -1)
            object._physijs.touches.splice(j--, 1);
        }

        // Handle each colliding object
        for (let j = 0; j < collisions[id1].length; j++) {
          const id2 = collisions[id1][j];
          const object2 = this._objects[id2];

          if (object2) {
            // If object was not already touching object2, notify object
            if (object._physijs.touches.indexOf(id2) === -1) {
              object._physijs.touches.push(id2);

              temp1Vector3.subVectors(object.getLinearVelocity(), object2.getLinearVelocity());
              temp1 = temp1Vector3.clone();

              temp1Vector3.subVectors(object.getAngularVelocity(), object2.getAngularVelocity());
              temp2 = temp1Vector3.clone();

              let normal_offset = normal_offsets[`${object._physijs.id}-${object2._physijs.id}`];

              if (normal_offset > 0) {
                temp1Vector3.set(
                  -data[normal_offset],
                  -data[normal_offset + 1],
                  -data[normal_offset + 2]
                );
              } else {
                normal_offset *= -1;

                temp1Vector3.set(
                  data[normal_offset],
                  data[normal_offset + 1],
                  data[normal_offset + 2]
                );
              }

              object.dispatchEvent('collision', object2, temp1, temp2, temp1Vector3);
            }
          }
        }
      } else object._physijs.touches.length = 0; // not touching other objects
    }

    this.collisions = collisions;

    if (this.SUPPORT_TRANSFERABLE)
      this._worker.transferableMessage(data.buffer, [data.buffer]); // Give the typed array back to the worker
  }

  addConstraint(constraint, show_marker) {
    this._constraints[constraint.id] = constraint;
    this.execute('addConstraint', constraint.getDefinition());

    if (show_marker) {
      let marker;

      switch (constraint.type) {
        case 'point':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;

        case 'hinge':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;

        case 'slider':
          marker = new THREE.Mesh(
            new THREE.BoxGeometry(10, 1, 1),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);

          // This rotation isn't right if all three axis are non-0 values
          // TODO: change marker's rotation order to ZYX
          marker.rotation.set(
            constraint.axis.y, // yes, y and
            constraint.axis.x, // x axis are swapped
            constraint.axis.z
          );
          this._objects[constraint.objecta].add(marker);
          break;

        case 'conetwist':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;

        case 'dof':
          marker = new THREE.Mesh(
            new THREE.SphereGeometry(1.5),
            new THREE.MeshNormalMaterial()
          );

          marker.position.copy(constraint.positiona);
          this._objects[constraint.objecta].add(marker);
          break;
        default:
      }
    }

    return constraint;
  }

  onSimulationResume() {
    this.execute('onSimulationResume', {});
  }

  removeConstraint(constraint) {
    if (this._constraints[constraint.id] !== undefined) {
      this.execute('removeConstraint', {id: constraint.id});
      delete this._constraints[constraint.id];
    }
  }

  execute(cmd, params) {
    this._worker.postMessage({cmd, params});
  }
}
<|endoftext|>