<commit_before>'use strict';

(function() {

var Emitter = require('component-emitter');
var isNumber = require('is-number');
var tinycolor = require('tinycolor2');
var transform = require('dom-transform');

/**
 * Creates a new SimpleColorPicker
 * @param {Object} options
 * @param {String|Number|Object} options.color The default color that the picker will display. Default is #FFFFFF. It can be a hexadecimal number or an hex String.
 * @param {String|Number|Object} options.background The background color of the picker. Default is transparent. It can be a hexadecimal number or an hex String.
 * @param {HTMLElement} options.el A dom node to add the picker to. You can also use `colorPicker.appendTo(domNode)` afterwards if you prefer.
 * @param {Number} options.width Desired width of the color picker. Default is 175.
 * @param {Number} options.height Desired height of the color picker. Default is 150.
 */
function SimpleColorPicker(options) {
  // Options
  options = options || {};

  // Properties
  this.color = null;
  this.width = 0;
  this.widthUnits = 'px';
  this.height = 0;
  this.heightUnits = 'px';
  this.hue = 0;
  this.position = {x: 0, y: 0};
  this.huePosition = 0;
  this.saturationWidth = 0;
  this.hueHeight = 0;
  this.maxHue = 0;
  this.inputIsNumber = false;

  // Bind methods to scope (if needed)
  this._onSaturationMouseDown = this._onSaturationMouseDown.bind(this);
  this._onSaturationMouseMove = this._onSaturationMouseMove.bind(this);
  this._onSaturationMouseUp = this._onSaturationMouseUp.bind(this);
  this._onHueMouseDown = this._onHueMouseDown.bind(this);
  this._onHueMouseMove = this._onHueMouseMove.bind(this);
  this._onHueMouseUp = this._onHueMouseUp.bind(this);

  // Create DOM
  this.$el = document.createElement('div');
  this.$el.className = 'Scp';
  this.$el.innerHTML = [
    '
',
      '

',
      '

',
    '
',
    '
',
      '

',
    '
'
  ].join('');

  // DOM accessors
  this.$saturation = this.$el.querySelector('.Scp-saturation');
  this.$hue = this.$el.querySelector('.Scp-hue');
  this.$sbSelector = this.$el.querySelector('.Scp-sbSelector');
  this.$hSelector = this.$el.querySelector('.Scp-hSelector');

  // Event listeners
  this.$saturation.addEventListener('mousedown', this._onSaturationMouseDown);
  this.$saturation.addEventListener('touchstart', this._onSaturationMouseDown);
  this.$hue.addEventListener('mousedown', this._onHueMouseDown);
  this.$hue.addEventListener('touchstart', this._onHueMouseDown);

  // Some styling and DOMing from options
  if (options.el) {
    this.appendTo(options.el);
  }
  if (options.background) {
    this.setBackgroundColor(options.background);
  }
  if (options.widthUnits) {
    this.widthUnits = options.widthUnits;
  }
  if (options.heightUnits) {
    this.heightUnits = options.heightUnits;
  }
  this.setSize(options.width || 175, options.height || 150);
  this.setColor(options.color);

  return this;
}

Emitter(SimpleColorPicker.prototype);

/* =============================================================================
  Public API
============================================================================= */
/**
 * Add the SimpleColorPicker instance to a DOM element.
 * @param  {HTMLElement} el
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.appendTo = function(el) {
  el.appendChild(this.$el);
  return this;
};

/**
 * Removes picker from its parent and kill all listeners.
 * Call this method for proper destroy.
 */
SimpleColorPicker.prototype.remove = function() {
  this._onSaturationMouseUp();
  this._onHueMouseUp();

  this.$saturation.removeEventListener('mousedown', this._onSaturationMouseDown);
  this.$saturation.removeEventListener('touchstart', this._onSaturationMouseDown);
  this.$hue.removeEventListener('mousedown', this._onHueMouseDown);
  this.$hue.removeEventListener('touchstart', this._onHueMouseDown);

  this.off();

  if (this.$el.parentNode) {
    this.$el.parentNode.removeChild(this.$el);
  }
};

/**
 * Manually set the current color of the picker. This is the method
 * used on instantiation to convert `color` option to actual color for
 * the picker. Param can be a hexadecimal number or an hex String.
 * @param {String|Number} color hex color desired
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.setColor = function(color) {
  if(isNumber(color)) {
    this.inputIsNumber = true;
    color = numberToHex(color);
  } else {
    this.inputIsNumber = false;
  }
  this.color = tinycolor(color);

  var hsvColor = this.color.toHsv();

  if(!isNaN(hsvColor.h)) {
    this.hue = hsvColor.h;
  }

  this._moveSelectorTo(this.saturationWidth * hsvColor.s, (1 - hsvColor.v) * this.hueHeight);
  this._moveHueTo((1 - (this.hue / 360)) * this.hueHeight);

  this._updateHue();
  return this;
};

/**
 * Set size of the color picker for a given width and height. Note that
 * a padding of 5px will be added if you chose to use the background option
 * of the constructor.
 * @param {Number} width
 * @param {Number} height
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.setSize = function(width, height) {
  this.width = width;
  this.height = height;
  this.$el.style.width = this.width + this.widthUnits;
  this.$el.style.height = this.height + this.heightUnits;

  this.saturationWidth = this.width - 25;
  this.$saturation.style.width = this.saturationWidth + 'px';

  this.hueHeight = this.height;
  this.maxHue = this.hueHeight - 2;

  return this;
};

/**
 * Set the background color of the picker. It also adds a 5px padding
 * for design purpose.
 * @param {String|Number} color hex color desired for background
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.setBackgroundColor = function(color) {
  if(isNumber(color)) {
    color = numberToHex(color);
  }
  this.$el.style.padding = '5px';
  this.$el.style.background = tinycolor(color).toHexString();
  return this;
};

/**
 * Removes background of the picker if previously set. It's no use
 * calling this method if you didn't set the background option on start
 * or if you didn't call setBackgroundColor previously.
 */
SimpleColorPicker.prototype.setNoBackground = function() {
  this.$el.style.padding = '0px';
  this.$el.style.background = 'none';
};

/**
 * Registers callback to the update event of the picker.
 * picker inherits from [component/emitter](https://github.com/component/emitter)
 * so you could do the same thing by calling `colorPicker.on('update');`
 * @param  {Function} callback
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.onChange = function(callback) {
  this.on('update', callback);
  this.emit('update', this.getHexString());
  return this;
};

/* =============================================================================
  Color getters
============================================================================= */
/**
 * Main color getter, will return a formatted color string depending on input
 * or a number depending on the last setColor call.
 * @return {Number|String}
 */
SimpleColorPicker.prototype.getColor = function() {
  if(this.inputIsNumber) {
    return this.getHexNumber();
  }
  return this.color.toString();
};

/**
 * Returns color as css hex string (ex: '#FF0000').
 * @return {String}
 */
SimpleColorPicker.prototype.getHexString = function() {
  return this.color.toHexString().toUpperCase();
};

/**
 * Returns color as number (ex: 0xFF0000).
 * @return {Number}
 */
SimpleColorPicker.prototype.getHexNumber = function() {
  return parseInt(this.color.toHex(), 16);
};

/**
 * Returns color as {r: 255, g: 0, b: 0} object.
 * @return {Object}
 */
SimpleColorPicker.prototype.getRGB = function() {
  return this.color.toRgb();
};

/**
 * Returns color as {h: 100, s: 1, v: 1} object.
 * @return {Object}
 */
SimpleColorPicker.prototype.getHSV = function() {
  return this.color.toHsv();
};

/**
 * Returns true if color is perceived as dark
 * @return {Boolean}
 */
SimpleColorPicker.prototype.isDark = function() {
  return this.color.isDark();
};

/**
 * Returns true if color is perceived as light
 * @return {Boolean}
 */
SimpleColorPicker.prototype.isLight = function() {
  return this.color.isLight();
};

/* =============================================================================
  "Private" methods
============================================================================= */
SimpleColorPicker.prototype._moveSelectorTo = function(x, y) {
  this.position.x = clamp(x, 0, this.saturationWidth);
  this.position.y = clamp(y, 0, this.hueHeight);

  transform(this.$sbSelector, {
    x: this.position.x,
    y: this.position.y
  });
};

SimpleColorPicker.prototype._updateColorFromPosition = function() {
  this.color = tinycolor({h: this.hue, s: this.position.x / this.saturationWidth, v: 1 - (this.position.y / this.hueHeight)});
  this._updateColor();
};

SimpleColorPicker.prototype._moveHueTo = function(y) {
  this.huePosition = clamp(y, 0, this.maxHue);

  transform(this.$hSelector, {
    y: this.huePosition
  });
};

SimpleColorPicker.prototype._updateHueFromPosition = function() {
  var hsvColor = this.color.toHsv();
  this.hue = 360 * (1 - (this.huePosition / this.maxHue));
  this.color = tinycolor({h: this.hue, s: hsvColor.s, v: hsvColor.v});
  this._updateHue();
};

SimpleColorPicker.prototype._updateHue = function() {
  var hueColor = tinycolor({h: this.hue, s: 1, v: 1});
  this.$saturation.style.background = 'linear-gradient(to right, #fff, ' + hueColor.toHexString() + ')';
  this._updateColor();
};

SimpleColorPicker.prototype._updateColor = function() {
  this.$sbSelector.style.background = this.color.toHexString();
  this.$sbSelector.style.borderColor = this.color.isDark() ? '#fff' : '#000';
  this.emit('update', this.color.toHexString());
};

/* =============================================================================
  Events handlers
============================================================================= */
SimpleColorPicker.prototype._onSaturationMouseDown = function(e) {
  var sbOffset = this.$saturation.getBoundingClientRect();
  var xPos = getMousePosition(e).x;
  var yPos = getMousePosition(e).y;
  this._moveSelectorTo(xPos - sbOffset.left, yPos - sbOffset.top);
  this._updateColorFromPosition();
  window.addEventListener('mouseup', this._onSaturationMouseUp);
  window.addEventListener('touchend', this._onSaturationMouseUp);
  window.addEventListener('mousemove', this._onSaturationMouseMove);
  window.addEventListener('touchmove', this._onSaturationMouseMove);
  e.preventDefault();
};

SimpleColorPicker.prototype._onSaturationMouseMove = function(e) {
  var sbOffset = this.$saturation.getBoundingClientRect();
  var xPos = getMousePosition(e).x;
  var yPos = getMousePosition(e).y;
  this._moveSelectorTo(xPos - sbOffset.left, yPos - sbOffset.top);
  this._updateColorFromPosition();
};

SimpleColorPicker.prototype._onSaturationMouseUp = function() {
  window.removeEventListener('mouseup', this._onSaturationMouseUp);
  window.removeEventListener('touchend', this._onSaturationMouseUp);
  window.removeEventListener('mousemove', this._onSaturationMouseMove);
  window.removeEventListener('touchmove', this._onSaturationMouseMove);
};

SimpleColorPicker.prototype._onHueMouseDown = function(e) {
  var hOffset = this.$hue.getBoundingClientRect();
  var yPos = getMousePosition(e).y;
  this._moveHueTo(yPos - hOffset.top);
  this._updateHueFromPosition();
  window.addEventListener('mouseup', this._onHueMouseUp);
  window.addEventListener('touchend', this._onHueMouseUp);
  window.addEventListener('mousemove', this._onHueMouseMove);
  window.addEventListener('touchmove', this._onHueMouseMove);
  e.preventDefault();
};

SimpleColorPicker.prototype._onHueMouseMove = function(e) {
  var hOffset = this.$hue.getBoundingClientRect();
  var yPos = getMousePosition(e).y;
  this._moveHueTo(yPos - hOffset.top);
  this._updateHueFromPosition();
};

SimpleColorPicker.prototype._onHueMouseUp = function() {
  window.removeEventListener('mouseup', this._onHueMouseUp);
  window.removeEventListener('touchend', this._onHueMouseUp);
  window.removeEventListener('mousemove', this._onHueMouseMove);
  window.removeEventListener('touchmove', this._onHueMouseMove);
};

/* =============================================================================
  Helper functions
============================================================================= */
function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

function getMousePosition(e) {
  e = (e.type.indexOf('touch') === 0) ? e.touches[0] : e;
  return {
    x: e.clientX,
    y: e.clientY
  };
}

function numberToHex(color) {
  return color = '#' + ('00000' + (color | 0).toString(16)).substr(-6);
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = SimpleColorPicker;
}

})();
<commit_msg>Modified config to accept a 'window' reference in order to apply event listeners in the correct context<commit_after>'use strict';

(function() {

var Emitter = require('component-emitter');
var isNumber = require('is-number');
var tinycolor = require('tinycolor2');
var transform = require('dom-transform');

/**
 * Creates a new SimpleColorPicker
 * @param {Object} options
 * @param {String|Number|Object} options.color The default color that the picker will display. Default is #FFFFFF. It can be a hexadecimal number or an hex String.
 * @param {String|Number|Object} options.background The background color of the picker. Default is transparent. It can be a hexadecimal number or an hex String.
 * @param {HTMLElement} options.el A dom node to add the picker to. You can also use `colorPicker.appendTo(domNode)` afterwards if you prefer.
 * @param {Number} options.width Desired width of the color picker. Default is 175.
 * @param {Number} options.height Desired height of the color picker. Default is 150.
 */
function SimpleColorPicker(options) {
  // Options
  options = options || {};

  // Properties
  this.color = null;
  this.width = 0;
  this.widthUnits = 'px';
  this.height = 0;
  this.heightUnits = 'px';
  this.hue = 0;
  this.position = {x: 0, y: 0};
  this.huePosition = 0;
  this.saturationWidth = 0;
  this.hueHeight = 0;
  this.maxHue = 0;
  this.inputIsNumber = false;

  // Bind methods to scope (if needed)
  this._onSaturationMouseDown = this._onSaturationMouseDown.bind(this);
  this._onSaturationMouseMove = this._onSaturationMouseMove.bind(this);
  this._onSaturationMouseUp = this._onSaturationMouseUp.bind(this);
  this._onHueMouseDown = this._onHueMouseDown.bind(this);
  this._onHueMouseMove = this._onHueMouseMove.bind(this);
  this._onHueMouseUp = this._onHueMouseUp.bind(this);

  // Register window and document references in case this is instantiated inside of an iframe
  this.window = options.window || window;
  this.document = this.window.document

  // Create DOM
  this.$el = this.document.createElement('div');
  this.$el.className = 'Scp';
  this.$el.innerHTML = [
    '
',
      '

',
      '

',
    '
',
    '
',
      '

',
    '
'
  ].join('');

  // DOM accessors
  this.$saturation = this.$el.querySelector('.Scp-saturation');
  this.$hue = this.$el.querySelector('.Scp-hue');
  this.$sbSelector = this.$el.querySelector('.Scp-sbSelector');
  this.$hSelector = this.$el.querySelector('.Scp-hSelector');

  // Event listeners
  this.$saturation.addEventListener('mousedown', this._onSaturationMouseDown);
  this.$saturation.addEventListener('touchstart', this._onSaturationMouseDown);
  this.$hue.addEventListener('mousedown', this._onHueMouseDown);
  this.$hue.addEventListener('touchstart', this._onHueMouseDown);

  // Some styling and DOMing from options
  if (options.el) {
    this.appendTo(options.el);
  }
  if (options.background) {
    this.setBackgroundColor(options.background);
  }
  if (options.widthUnits) {
    this.widthUnits = options.widthUnits;
  }
  if (options.heightUnits) {
    this.heightUnits = options.heightUnits;
  }
  this.setSize(options.width || 175, options.height || 150);
  this.setColor(options.color);

  return this;
}

Emitter(SimpleColorPicker.prototype);

/* =============================================================================
  Public API
============================================================================= */
/**
 * Add the SimpleColorPicker instance to a DOM element.
 * @param  {HTMLElement} el
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.appendTo = function(el) {
  el.appendChild(this.$el);
  return this;
};

/**
 * Removes picker from its parent and kill all listeners.
 * Call this method for proper destroy.
 */
SimpleColorPicker.prototype.remove = function() {
  this._onSaturationMouseUp();
  this._onHueMouseUp();

  this.$saturation.removeEventListener('mousedown', this._onSaturationMouseDown);
  this.$saturation.removeEventListener('touchstart', this._onSaturationMouseDown);
  this.$hue.removeEventListener('mousedown', this._onHueMouseDown);
  this.$hue.removeEventListener('touchstart', this._onHueMouseDown);

  this.off();

  if (this.$el.parentNode) {
    this.$el.parentNode.removeChild(this.$el);
  }
};

/**
 * Manually set the current color of the picker. This is the method
 * used on instantiation to convert `color` option to actual color for
 * the picker. Param can be a hexadecimal number or an hex String.
 * @param {String|Number} color hex color desired
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.setColor = function(color) {
  if(isNumber(color)) {
    this.inputIsNumber = true;
    color = numberToHex(color);
  } else {
    this.inputIsNumber = false;
  }
  this.color = tinycolor(color);

  var hsvColor = this.color.toHsv();

  if(!isNaN(hsvColor.h)) {
    this.hue = hsvColor.h;
  }

  this._moveSelectorTo(this.saturationWidth * hsvColor.s, (1 - hsvColor.v) * this.hueHeight);
  this._moveHueTo((1 - (this.hue / 360)) * this.hueHeight);

  this._updateHue();
  return this;
};

/**
 * Set size of the color picker for a given width and height. Note that
 * a padding of 5px will be added if you chose to use the background option
 * of the constructor.
 * @param {Number} width
 * @param {Number} height
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.setSize = function(width, height) {
  this.width = width;
  this.height = height;
  this.$el.style.width = this.width + this.widthUnits;
  this.$el.style.height = this.height + this.heightUnits;

  this.saturationWidth = this.width - 25;
  this.$saturation.style.width = this.saturationWidth + 'px';

  this.hueHeight = this.height;
  this.maxHue = this.hueHeight - 2;

  return this;
};

/**
 * Set the background color of the picker. It also adds a 5px padding
 * for design purpose.
 * @param {String|Number} color hex color desired for background
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.setBackgroundColor = function(color) {
  if(isNumber(color)) {
    color = numberToHex(color);
  }
  this.$el.style.padding = '5px';
  this.$el.style.background = tinycolor(color).toHexString();
  return this;
};

/**
 * Removes background of the picker if previously set. It's no use
 * calling this method if you didn't set the background option on start
 * or if you didn't call setBackgroundColor previously.
 */
SimpleColorPicker.prototype.setNoBackground = function() {
  this.$el.style.padding = '0px';
  this.$el.style.background = 'none';
};

/**
 * Registers callback to the update event of the picker.
 * picker inherits from [component/emitter](https://github.com/component/emitter)
 * so you could do the same thing by calling `colorPicker.on('update');`
 * @param  {Function} callback
 * @return {SimpleColorPicker} Returns itself for chaining purpose
 */
SimpleColorPicker.prototype.onChange = function(callback) {
  this.on('update', callback);
  this.emit('update', this.getHexString());
  return this;
};

/* =============================================================================
  Color getters
============================================================================= */
/**
 * Main color getter, will return a formatted color string depending on input
 * or a number depending on the last setColor call.
 * @return {Number|String}
 */
SimpleColorPicker.prototype.getColor = function() {
  if(this.inputIsNumber) {
    return this.getHexNumber();
  }
  return this.color.toString();
};

/**
 * Returns color as css hex string (ex: '#FF0000').
 * @return {String}
 */
SimpleColorPicker.prototype.getHexString = function() {
  return this.color.toHexString().toUpperCase();
};

/**
 * Returns color as number (ex: 0xFF0000).
 * @return {Number}
 */
SimpleColorPicker.prototype.getHexNumber = function() {
  return parseInt(this.color.toHex(), 16);
};

/**
 * Returns color as {r: 255, g: 0, b: 0} object.
 * @return {Object}
 */
SimpleColorPicker.prototype.getRGB = function() {
  return this.color.toRgb();
};

/**
 * Returns color as {h: 100, s: 1, v: 1} object.
 * @return {Object}
 */
SimpleColorPicker.prototype.getHSV = function() {
  return this.color.toHsv();
};

/**
 * Returns true if color is perceived as dark
 * @return {Boolean}
 */
SimpleColorPicker.prototype.isDark = function() {
  return this.color.isDark();
};

/**
 * Returns true if color is perceived as light
 * @return {Boolean}
 */
SimpleColorPicker.prototype.isLight = function() {
  return this.color.isLight();
};

/* =============================================================================
  "Private" methods
============================================================================= */
SimpleColorPicker.prototype._moveSelectorTo = function(x, y) {
  this.position.x = clamp(x, 0, this.saturationWidth);
  this.position.y = clamp(y, 0, this.hueHeight);

  transform(this.$sbSelector, {
    x: this.position.x,
    y: this.position.y
  });
};

SimpleColorPicker.prototype._updateColorFromPosition = function() {
  this.color = tinycolor({h: this.hue, s: this.position.x / this.saturationWidth, v: 1 - (this.position.y / this.hueHeight)});
  this._updateColor();
};

SimpleColorPicker.prototype._moveHueTo = function(y) {
  this.huePosition = clamp(y, 0, this.maxHue);

  transform(this.$hSelector, {
    y: this.huePosition
  });
};

SimpleColorPicker.prototype._updateHueFromPosition = function() {
  var hsvColor = this.color.toHsv();
  this.hue = 360 * (1 - (this.huePosition / this.maxHue));
  this.color = tinycolor({h: this.hue, s: hsvColor.s, v: hsvColor.v});
  this._updateHue();
};

SimpleColorPicker.prototype._updateHue = function() {
  var hueColor = tinycolor({h: this.hue, s: 1, v: 1});
  this.$saturation.style.background = 'linear-gradient(to right, #fff, ' + hueColor.toHexString() + ')';
  this._updateColor();
};

SimpleColorPicker.prototype._updateColor = function() {
  this.$sbSelector.style.background = this.color.toHexString();
  this.$sbSelector.style.borderColor = this.color.isDark() ? '#fff' : '#000';
  this.emit('update', this.color.toHexString());
};

/* =============================================================================
  Events handlers
============================================================================= */
SimpleColorPicker.prototype._onSaturationMouseDown = function(e) {
  var sbOffset = this.$saturation.getBoundingClientRect();
  var xPos = getMousePosition(e).x;
  var yPos = getMousePosition(e).y;
  this._moveSelectorTo(xPos - sbOffset.left, yPos - sbOffset.top);
  this._updateColorFromPosition();
  this.window.addEventListener('mouseup', this._onSaturationMouseUp);
  this.window.addEventListener('touchend', this._onSaturationMouseUp);
  this.window.addEventListener('mousemove', this._onSaturationMouseMove);
  this.window.addEventListener('touchmove', this._onSaturationMouseMove);
  e.preventDefault();
};

SimpleColorPicker.prototype._onSaturationMouseMove = function(e) {
  var sbOffset = this.$saturation.getBoundingClientRect();
  var xPos = getMousePosition(e).x;
  var yPos = getMousePosition(e).y;
  this._moveSelectorTo(xPos - sbOffset.left, yPos - sbOffset.top);
  this._updateColorFromPosition();
};

SimpleColorPicker.prototype._onSaturationMouseUp = function() {
  this.window.removeEventListener('mouseup', this._onSaturationMouseUp);
  this.window.removeEventListener('touchend', this._onSaturationMouseUp);
  this.window.removeEventListener('mousemove', this._onSaturationMouseMove);
  this.window.removeEventListener('touchmove', this._onSaturationMouseMove);
};

SimpleColorPicker.prototype._onHueMouseDown = function(e) {
  var hOffset = this.$hue.getBoundingClientRect();
  var yPos = getMousePosition(e).y;
  this._moveHueTo(yPos - hOffset.top);
  this._updateHueFromPosition();
  this.window.addEventListener('mouseup', this._onHueMouseUp);
  this.window.addEventListener('touchend', this._onHueMouseUp);
  this.window.addEventListener('mousemove', this._onHueMouseMove);
  this.window.addEventListener('touchmove', this._onHueMouseMove);
  e.preventDefault();
};

SimpleColorPicker.prototype._onHueMouseMove = function(e) {
  var hOffset = this.$hue.getBoundingClientRect();
  var yPos = getMousePosition(e).y;
  this._moveHueTo(yPos - hOffset.top);
  this._updateHueFromPosition();
};

SimpleColorPicker.prototype._onHueMouseUp = function() {
  this.window.removeEventListener('mouseup', this._onHueMouseUp);
  this.window.removeEventListener('touchend', this._onHueMouseUp);
  this.window.removeEventListener('mousemove', this._onHueMouseMove);
  this.window.removeEventListener('touchmove', this._onHueMouseMove);
};

/* =============================================================================
  Helper functions
============================================================================= */
function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}

function getMousePosition(e) {
  e = (e.type.indexOf('touch') === 0) ? e.touches[0] : e;
  return {
    x: e.clientX,
    y: e.clientY
  };
}

function numberToHex(color) {
  return color = '#' + ('00000' + (color | 0).toString(16)).substr(-6);
}

if (typeof module !== 'undefined' && module.exports) {
  module.exports = SimpleColorPicker;
}

})();
<|endoftext|>


<commit_before>/**
 * IdeaHubPromptSVG component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { lazy, Suspense } from '@wordpress/element';

/**
 * Internal dependencies
 */
import PreviewBlock from '../../../../components/PreviewBlock';
const LazyIdeaHubPromptSVG = lazy( () =>
	import( '../../../../../svg/graphics/idea-hub-prompt.svg' )
);

export default function IdeaHubPromptSVG( props ) {
	return (
		<Suspense fallback={ <PreviewBlock width="100%" height="39.77%" /> }>
			<LazyIdeaHubPromptSVG { ...props } />
		</Suspense>
	);
}
<commit_msg>Wrap LazyIdeaHubPromptSVG with MediaErrorHandler.<commit_after>/**
 * IdeaHubPromptSVG component.
 *
 * Site Kit by Google, Copyright 2022 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { lazy, Suspense } from '@wordpress/element';
import { __ } from '@wordpress/i18n';

/**
 * Internal dependencies
 */
import PreviewBlock from '../../../../components/PreviewBlock';
import MediaErrorHandler from '../../../../components/MediaErrorHandler';
const LazyIdeaHubPromptSVG = lazy( () =>
	import( '../../../../../svg/graphics/idea-hub-prompt.svg' )
);

export default function IdeaHubPromptSVG( props ) {
	return (
		<Suspense fallback={ <PreviewBlock width="100%" height="39.77%" /> }>
			<MediaErrorHandler
				errorMessage={ __(
					'Failed to load graphic.',
					'google-site-kit'
				) }
			>
				<LazyIdeaHubPromptSVG { ...props } />
			</MediaErrorHandler>
		</Suspense>
	);
}
<|endoftext|>


<commit_before>import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import './static/bootstrap/css/bootstrap.css';

import App from './Main/App';

render(
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="report/:reportCode" />
      <Route path="report/:reportCode/:fightId" />
      <Route path="report/:reportCode/:fightId/:playerName" />
      <Route path="report/:reportCode/:fightId/:playerName/:resultTab" />
    </Route>
  </Router>,
  document.getElementById('root')
);
<commit_msg>We need to unregister the service worker for browsers to forget about it<commit_after>import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

import './static/bootstrap/css/bootstrap.css';

import App from './Main/App';

import { unregister } from './registerServiceWorker';

render(
  <Router history={browserHistory}>
    <Route path="/" component={App}>
      <Route path="report/:reportCode" />
      <Route path="report/:reportCode/:fightId" />
      <Route path="report/:reportCode/:fightId/:playerName" />
      <Route path="report/:reportCode/:fightId/:playerName/:resultTab" />
    </Route>
  </Router>,
  document.getElementById('root')
);
unregister();
<|endoftext|>


<commit_before>/**
 * InstructionInformation component tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import InstructionInformation from './InstructionInformation';
import { render } from '../../../../../tests/js/test-utils';
import { STORE_NAME } from '../datastore/constants';
import { STORE_NAME as CORE_MODULE } from '../../../googlesitekit/modules/datastore/constants';
import { STORE_NAME as MODULES_ANALYTICS } from '../../analytics/datastore/constants';
import { STORE_NAME as MODULES_TAGMANAGER } from '../../tagmanager/datastore/constants';
import fixtures from '../../../googlesitekit/modules/datastore/fixtures.json';

describe( 'InstructionInformation', () => {
	it( 'should render with analytics active and no useSnippet', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
			registry.dispatch( CORE_MODULE ).receiveGetModules( fixtures );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		const selectedText = container.querySelector( 'p' );
		expect( selectedText ).toHaveTextContent( 'You disabled analytics auto insert snippet. If You are using Google Analytics code snippet, add the code below:' );
	} );

	it( 'should not render tagmanger and anylytics inactive', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		expect( container.querySelector( 'p' ) ).toEqual( null );
	} );

	it( 'should not render with analytics active and a useSnippet', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
			registry.dispatch( CORE_MODULE ).receiveGetModules( fixtures );
			registry.dispatch( MODULES_ANALYTICS ).setUseSnippet( true );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		expect( container.querySelector( 'p' ) ).toEqual( null );
	} );

	it( 'should render with analytics active and no analytics useSnippet, also with tagmanager active and a gtm useSnippet', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
			registry.dispatch( CORE_MODULE ).receiveGetModules( fixtures.analyticsGtmActivate );
			registry.dispatch( MODULES_TAGMANAGER ).setUseSnippet( true );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		const selectedText = container.querySelector( 'p' );
		expect( selectedText ).toHaveTextContent( 'You are using auto insert snippet with Tag Manager' );
	} );
} );
<commit_msg>Fix test instructioninformation with anylytics and tagmanager enabled.<commit_after>/**
 * InstructionInformation component tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import InstructionInformation from './InstructionInformation';
import { render } from '../../../../../tests/js/test-utils';
import { STORE_NAME } from '../datastore/constants';
import { STORE_NAME as CORE_MODULE } from '../../../googlesitekit/modules/datastore/constants';
import { STORE_NAME as MODULES_ANALYTICS } from '../../analytics/datastore/constants';
import { STORE_NAME as MODULES_TAGMANAGER } from '../../tagmanager/datastore/constants';
import fixtures from '../../../googlesitekit/modules/datastore/fixtures.json';

describe( 'InstructionInformation', () => {
	it( 'should render with analytics active and no useSnippet', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
			registry.dispatch( CORE_MODULE ).receiveGetModules( fixtures );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		const selectedText = container.querySelector( 'p' );
		expect( selectedText ).toHaveTextContent( 'You disabled analytics auto insert snippet. If You are using Google Analytics code snippet, add the code below:' );
	} );

	it( 'should not render tagmanger and anylytics inactive', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		expect( container.querySelector( 'p' ) ).toEqual( null );
	} );

	it( 'should not render with analytics active and a useSnippet', () => {
		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
			registry.dispatch( CORE_MODULE ).receiveGetModules( fixtures );
			registry.dispatch( MODULES_ANALYTICS ).setUseSnippet( true );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		expect( container.querySelector( 'p' ) ).toEqual( null );
	} );

	it( 'should render with analytics active and no analytics useSnippet, also with tagmanager active and a gtm useSnippet', () => {
		const newFixtures = fixtures.map( ( fixture ) => {
			if ( fixture.slug !== 'tagmanager' && fixture.slug !== 'optimize' ) {
				return fixture;
			}
			return { ...fixture, active: true, connected: true };
		} );

		const setupRegistry = ( registry ) => {
			registry.dispatch( STORE_NAME ).setOptimizeID( 'OPT-1234567' );
			registry.dispatch( CORE_MODULE ).receiveGetModules( newFixtures );
			registry.dispatch( MODULES_TAGMANAGER ).setUseSnippet( true );
		};

		const { container } = render( <InstructionInformation />, { setupRegistry } );

		const selectedText = container.querySelector( 'p' );
		expect( selectedText ).toHaveTextContent( 'You are using auto insert snippet with Tag Manager' );
	} );
} );
<|endoftext|>


<commit_before>/* global angular */
'use strict'; // jshint ignore:line

angular.module('lumx.dropdown', [])
    .service('LxDropdownService', ['$document', function($document)
    {
        var openScope = null;

        function open(dropdownScope)
        {
            if (!openScope)
            {
                $document.bind('click', closeDropdown);
            }

            if (openScope && openScope !== dropdownScope)
            {
                openScope.isOpened = false;
            }

            openScope = dropdownScope;
        }

        function close(dropdownScope)
        {
            if (openScope === dropdownScope)
            {
                openScope = null;
                $document.unbind('click', closeDropdown);
            }
        }

        function closeDropdown()
        {
            if (!openScope) { return; }

            openScope.$apply(function()
            {
                openScope.isOpened = false;
            });
        }

        return {
            open: open,
            close: close
        };
    }])
    .controller('LxDropdownController', ['$scope', '$timeout', '$window', 'LxDropdownService', function($scope, $timeout, $window, LxDropdownService)
    {
        var dropdown,
            dropdownMenu;
        var dropdownMenuHeight;

        $scope.isOpened = false;
        $scope.isDropped = false;

        this.registerDropdown = function(element)
        {
            dropdown = element;

            $scope.position = angular.isDefined($scope.position) ? $scope.position : 'left';
        };

        this.registerDropdownMenu = function(element)
        {
            dropdownMenu = element;
        };

        this.toggle = function()
        {
            $scope.isOpened = !$scope.isOpened;
        };

        function linkList()
        {
            $scope.isDropped = false;

            closeDropdownMenu();
        }

        function unlinkList()
        {
            $scope.isDropped = true;

            dropdownMenu.appendTo('body');

            $timeout(function()
            {
                setDropdownMenuCss();
                openDropdownMenu();
            });
        }

        function fromTop(toTop)
        {
            if (angular.isUndefined($scope.overToggle) && angular.isDefined($scope.fromTop))
            {
                return $scope.fromTop === 'true';
            }

            if ($scope.overToggle === 'true')
            {
                return !toTop;
            }
            else
            {
                return toTop;
            }
        }

        function setDropdownMenuCss()
        {
            dropdownMenu.removeAttr('style');
            dropdownMenu.css({
                opacity: 1,
                overflow: 'auto'
            });

            var windowScrollTop = angular.element($window).scrollTop();
            var dropdownMenuWidth = dropdownMenu.outerWidth();
            dropdownMenuHeight = dropdownMenu.outerHeight();
            var origin = {
                x: dropdown.offset().left,
                y: (dropdown.offset().top - windowScrollTop) + dropdown.outerHeight() // dropdown is the button at the moment
            };
            var width = dropdownMenuWidth;
            var height, bottomOffset, topOffset;

            if (angular.isDefined($scope.width))
            {
                if ($scope.width === 'full')
                {
                    width = dropdown.outerWidth();
                }
                else
                {
                    width = dropdown.outerWidth() + parseInt($scope.width);
                }
            }

            if ($scope.position === 'right')
            {
                origin.x = $window.innerWidth - (dropdown.offset().left + dropdown.outerWidth());
            }
            else if ($scope.position === 'center')
            {
                origin.x = dropdown.offset().left + (dropdown.outerWidth() - width) / 2;
            }

            if (origin.y + dropdownMenuHeight >= $window.innerHeight && origin.y - dropdownMenuHeight > 0)
            { // To top
                bottomOffset = fromTop(true) ? dropdown.outerHeight() : 0;

                if (bottomOffset && origin.y - bottomOffset - dropdownMenuHeight <= 0)
                {
                    height = origin.y - bottomOffset - 8;
                }

                dropdownMenu.css({
                    left: $scope.position !== 'right' ? origin.x : undefined,
                    right: $scope.position === 'right' ? origin.x : undefined,
                    bottom: $window.innerHeight - origin.y + bottomOffset - windowScrollTop,
                    width: width,
                    height: height
                });
            }
            else if (origin.y + (fromTop(false) ? -dropdown.outerHeight() : 0) + dropdownMenuHeight < $window.innerHeight)
            { // To bottom
                topOffset = fromTop(false) ? -dropdown.outerHeight() : 0;

                dropdownMenu.css({
                    left: $scope.position !== 'right' ? origin.x : undefined,
                    right: $scope.position === 'right' ? origin.x : undefined,
                    top: origin.y + topOffset + windowScrollTop,
                    width: width
                });
            }
            else // Dropdown too big, check the biggest space between up or down and use it with a padding
            {
                if (origin.y > $window.innerHeight / 2) // Middle of the screen
                { // To top
                    bottomOffset = fromTop(true) ? dropdown.outerHeight() : 0;
                    height = origin.y - (fromTop(true) ? dropdown.outerHeight() : 0) - 8;

                    dropdownMenu.css({
                        left: $scope.position !== 'right' ? origin.x : undefined,
                        right: $scope.position === 'right' ? origin.x : undefined,
                        bottom: $window.innerHeight - origin.y + bottomOffset - windowScrollTop,
                        width: width,
                        height: height - bottomOffset
                    });
                }
                else
                { // To bottom
                    topOffset = fromTop(false) ?  -dropdown.outerHeight() : 0;
                    height = $window.innerHeight - (fromTop(false) ? dropdown.outerHeight() : 0) - origin.y - 8;

                    dropdownMenu.css({
                        left: $scope.position !== 'right' ? origin.x : undefined,
                        right: $scope.position === 'right' ? origin.x : undefined,
                        top: origin.y + topOffset + windowScrollTop,
                        width: width,
                        height: height - 2*topOffset
                    });
                }
            }
        }

        function openDropdownMenu()
        {
            var width = dropdownMenu.outerWidth();
            var height = dropdownMenu.outerHeight();

            dropdownMenu.css({
                width: 0,
                height: 0,
                opacity: 1
            });

            dropdownMenu.find('.dropdown-dropdownMenu__content').css({
                width: width,
                height: height
            });

            dropdownMenu.velocity({
                width: width
            }, {
                duration: 200,
                easing: 'easeOutQuint',
                queue: false
            });

            dropdownMenu.velocity({
                height: height
            }, {
                duration: 500,
                easing: 'easeOutQuint',
                queue: false,
                complete: function()
                {
                    if (height === dropdownMenuHeight)
                    {
                        dropdownMenu.css({ height: 'auto' });
                    }
                    else
                    {
                        dropdownMenu.css({ overflow: 'auto' });
                    }

                    if (!angular.isDefined($scope.width))
                    {
                        dropdownMenu.css({ width: 'auto' });
                    }

                    dropdownMenu.find('.dropdown-menu__content').removeAttr('style');
                }
            });

            dropdown.addClass('dropdown--is-active');
        }

        function closeDropdownMenu()
        {
            dropdownMenu.velocity({
                width: 0,
                height: 0,
            }, {
                duration: 200,
                easing: 'easeOutQuint',
                complete: function()
                {
                    dropdownMenu
                        .appendTo(dropdown)
                        .removeAttr('style');

                    dropdown.removeClass('dropdown--is-active');
                }
            });
        }

        $scope.$watch('isOpened', function(isOpened)
        {
            if (isOpened)
            {
                unlinkList();
                LxDropdownService.open($scope);
            }
            else
            {
                linkList();
                LxDropdownService.close($scope);
            }
        });

        angular.element($window).on('resize scroll', function()
        {
            if ($scope.isDropped)
            {
                setDropdownMenuCss();
            }
        });

        $scope.$on('$locationChangeSuccess', function()
        {
            $scope.isOpened = false;
        });

        $scope.$on('$destroy', function()
        {
            dropdownMenu.remove();
            LxDropdownService.close($scope);
        });
    }])
    .directive('lxDropdown', function()
    {
        return {
            restrict: 'E',
            controller: 'LxDropdownController',
            templateUrl: 'dropdown.html',
            transclude: true,
            replace: true,
            scope: true,
            link: function(scope, element, attrs, ctrl)
            {
                ctrl.registerDropdown(element);

                attrs.$observe('position', function(newValue)
                {
                    scope.position = newValue;
                });

                attrs.$observe('width', function(newValue)
                {
                    scope.width = newValue;
                });

                attrs.$observe('fromTop', function(newValue)
                {
                    scope.fromTop = newValue;
                });

                attrs.$observe('overToggle', function(newValue)
                {
                    scope.overToggle = newValue;
                });
            }
        };
    })
    .directive('lxDropdownToggle', function()
    {
        return {
            restrict: 'AE',
            require: '^lxDropdown',
            templateUrl: 'dropdown-toggle.html',
            replace: true,
            transclude: true,
            link: function(scope, element, attrs, ctrl)
            {
                element.bind('click', function(event)
                {
                    event.stopPropagation();

                    scope.$apply(function()
                    {
                        ctrl.toggle();
                    });
                });
            }
        };
    })
    .directive('lxDropdownMenu', function()
    {
        return {
            restrict: 'E',
            require: '^lxDropdown',
            templateUrl: 'dropdown-menu.html',
            transclude: true,
            replace: true,
            link: function(scope, element, attrs, ctrl)
            {
                ctrl.registerDropdownMenu(element);
                element.on('click', function(event)
                {
                    event.stopPropagation();

                    scope.$apply(function()
                    {
                        ctrl.toggle();
                    });
                });
            }
        };
    })
    .directive('lxDropdownFilter', ['$timeout', function($timeout)
    {
        return {
            restrict: 'A',
            link: function(scope, element)
            {
                element.bind('click', function(event)
                {
                    event.stopPropagation();
                });

                $timeout(function()
                {
                    element.find('input').focus();
                }, 200);
            }
        };
    }]);
<commit_msg>fix dropdown: keep scroll position after resize<commit_after>/* global angular */
'use strict'; // jshint ignore:line

angular.module('lumx.dropdown', [])
    .service('LxDropdownService', ['$document', function($document)
    {
        var openScope = null;

        function open(dropdownScope)
        {
            if (!openScope)
            {
                $document.bind('click', closeDropdown);
            }

            if (openScope && openScope !== dropdownScope)
            {
                openScope.isOpened = false;
            }

            openScope = dropdownScope;
        }

        function close(dropdownScope)
        {
            if (openScope === dropdownScope)
            {
                openScope = null;
                $document.unbind('click', closeDropdown);
            }
        }

        function closeDropdown()
        {
            if (!openScope) { return; }

            openScope.$apply(function()
            {
                openScope.isOpened = false;
            });
        }

        return {
            open: open,
            close: close
        };
    }])
    .controller('LxDropdownController', ['$scope', '$timeout', '$window', 'LxDropdownService', function($scope, $timeout, $window, LxDropdownService)
    {
        var dropdown,
            dropdownMenu;
        var dropdownMenuHeight;

        $scope.isOpened = false;
        $scope.isDropped = false;

        this.registerDropdown = function(element)
        {
            dropdown = element;

            $scope.position = angular.isDefined($scope.position) ? $scope.position : 'left';
        };

        this.registerDropdownMenu = function(element)
        {
            dropdownMenu = element;
        };

        this.toggle = function()
        {
            $scope.isOpened = !$scope.isOpened;
        };

        function linkList()
        {
            $scope.isDropped = false;

            closeDropdownMenu();
        }

        function unlinkList()
        {
            $scope.isDropped = true;

            dropdownMenu.appendTo('body');

            $timeout(function()
            {
                setDropdownMenuCss();
                openDropdownMenu();
            });
        }

        function fromTop(toTop)
        {
            if (angular.isUndefined($scope.overToggle) && angular.isDefined($scope.fromTop))
            {
                return $scope.fromTop === 'true';
            }

            if ($scope.overToggle === 'true')
            {
                return !toTop;
            }
            else
            {
                return toTop;
            }
        }

        function setDropdownMenuCss()
        {
            var scrollTop = dropdownMenu.scrollTop();
            dropdownMenu.removeAttr('style');
            dropdownMenu.css({
                opacity: 1,
                overflow: 'auto'
            });

            var windowScrollTop = angular.element($window).scrollTop();
            var dropdownMenuWidth = dropdownMenu.outerWidth();
            dropdownMenuHeight = dropdownMenu.outerHeight();
            var origin = {
                x: dropdown.offset().left,
                y: (dropdown.offset().top - windowScrollTop) + dropdown.outerHeight() // dropdown is the button at the moment
            };
            var width = dropdownMenuWidth;
            var height, bottomOffset, topOffset;

            if (angular.isDefined($scope.width))
            {
                if ($scope.width === 'full')
                {
                    width = dropdown.outerWidth();
                }
                else
                {
                    width = dropdown.outerWidth() + parseInt($scope.width);
                }
            }

            if ($scope.position === 'right')
            {
                origin.x = $window.innerWidth - (dropdown.offset().left + dropdown.outerWidth());
            }
            else if ($scope.position === 'center')
            {
                origin.x = dropdown.offset().left + (dropdown.outerWidth() - width) / 2;
            }

            if (origin.y + dropdownMenuHeight >= $window.innerHeight && origin.y - dropdownMenuHeight > 0)
            { // To top
                bottomOffset = fromTop(true) ? dropdown.outerHeight() : 0;

                if (bottomOffset && origin.y - bottomOffset - dropdownMenuHeight <= 0)
                {
                    height = origin.y - bottomOffset - 8;
                }

                dropdownMenu.css({
                    left: $scope.position !== 'right' ? origin.x : undefined,
                    right: $scope.position === 'right' ? origin.x : undefined,
                    bottom: $window.innerHeight - origin.y + bottomOffset - windowScrollTop,
                    width: width,
                    height: height
                });
            }
            else if (origin.y + (fromTop(false) ? -dropdown.outerHeight() : 0) + dropdownMenuHeight < $window.innerHeight)
            { // To bottom
                topOffset = fromTop(false) ? -dropdown.outerHeight() : 0;

                dropdownMenu.css({
                    left: $scope.position !== 'right' ? origin.x : undefined,
                    right: $scope.position === 'right' ? origin.x : undefined,
                    top: origin.y + topOffset + windowScrollTop,
                    width: width
                });
            }
            else // Dropdown too big, check the biggest space between up or down and use it with a padding
            {
                if (origin.y > $window.innerHeight / 2) // Middle of the screen
                { // To top
                    bottomOffset = fromTop(true) ? dropdown.outerHeight() : 0;
                    height = origin.y - (fromTop(true) ? dropdown.outerHeight() : 0) - 8;

                    dropdownMenu.css({
                        left: $scope.position !== 'right' ? origin.x : undefined,
                        right: $scope.position === 'right' ? origin.x : undefined,
                        bottom: $window.innerHeight - origin.y + bottomOffset - windowScrollTop,
                        width: width,
                        height: height - bottomOffset
                    });
                }
                else
                { // To bottom
                    topOffset = fromTop(false) ?  -dropdown.outerHeight() : 0;
                    height = $window.innerHeight - (fromTop(false) ? dropdown.outerHeight() : 0) - origin.y - 8;

                    dropdownMenu.css({
                        left: $scope.position !== 'right' ? origin.x : undefined,
                        right: $scope.position === 'right' ? origin.x : undefined,
                        top: origin.y + topOffset + windowScrollTop,
                        width: width,
                        height: height - 2*topOffset
                    });
                }

                dropdownMenu.scrollTop(scrollTop);
            }
        }

        function openDropdownMenu()
        {
            var width = dropdownMenu.outerWidth();
            var height = dropdownMenu.outerHeight();

            dropdownMenu.css({
                width: 0,
                height: 0,
                opacity: 1
            });

            dropdownMenu.find('.dropdown-dropdownMenu__content').css({
                width: width,
                height: height
            });

            dropdownMenu.velocity({
                width: width
            }, {
                duration: 200,
                easing: 'easeOutQuint',
                queue: false
            });

            dropdownMenu.velocity({
                height: height
            }, {
                duration: 500,
                easing: 'easeOutQuint',
                queue: false,
                complete: function()
                {
                    if (height === dropdownMenuHeight)
                    {
                        dropdownMenu.css({ height: 'auto' });
                    }
                    else
                    {
                        dropdownMenu.css({ overflow: 'auto' });
                    }

                    if (!angular.isDefined($scope.width))
                    {
                        dropdownMenu.css({ width: 'auto' });
                    }

                    dropdownMenu.find('.dropdown-menu__content').removeAttr('style');
                }
            });

            dropdown.addClass('dropdown--is-active');
        }

        function closeDropdownMenu()
        {
            dropdownMenu.velocity({
                width: 0,
                height: 0,
            }, {
                duration: 200,
                easing: 'easeOutQuint',
                complete: function()
                {
                    dropdownMenu
                        .appendTo(dropdown)
                        .removeAttr('style');

                    dropdown.removeClass('dropdown--is-active');
                }
            });
        }

        $scope.$watch('isOpened', function(isOpened)
        {
            if (isOpened)
            {
                unlinkList();
                LxDropdownService.open($scope);
            }
            else
            {
                linkList();
                LxDropdownService.close($scope);
            }
        });

        angular.element($window).on('resize scroll', function()
        {
            if ($scope.isDropped)
            {
                setDropdownMenuCss();
            }
        });

        $scope.$on('$locationChangeSuccess', function()
        {
            $scope.isOpened = false;
        });

        $scope.$on('$destroy', function()
        {
            dropdownMenu.remove();
            LxDropdownService.close($scope);
        });
    }])
    .directive('lxDropdown', function()
    {
        return {
            restrict: 'E',
            controller: 'LxDropdownController',
            templateUrl: 'dropdown.html',
            transclude: true,
            replace: true,
            scope: true,
            link: function(scope, element, attrs, ctrl)
            {
                ctrl.registerDropdown(element);

                attrs.$observe('position', function(newValue)
                {
                    scope.position = newValue;
                });

                attrs.$observe('width', function(newValue)
                {
                    scope.width = newValue;
                });

                attrs.$observe('fromTop', function(newValue)
                {
                    scope.fromTop = newValue;
                });

                attrs.$observe('overToggle', function(newValue)
                {
                    scope.overToggle = newValue;
                });
            }
        };
    })
    .directive('lxDropdownToggle', function()
    {
        return {
            restrict: 'AE',
            require: '^lxDropdown',
            templateUrl: 'dropdown-toggle.html',
            replace: true,
            transclude: true,
            link: function(scope, element, attrs, ctrl)
            {
                element.bind('click', function(event)
                {
                    event.stopPropagation();

                    scope.$apply(function()
                    {
                        ctrl.toggle();
                    });
                });
            }
        };
    })
    .directive('lxDropdownMenu', function()
    {
        return {
            restrict: 'E',
            require: '^lxDropdown',
            templateUrl: 'dropdown-menu.html',
            transclude: true,
            replace: true,
            link: function(scope, element, attrs, ctrl)
            {
                ctrl.registerDropdownMenu(element);
                element.on('click', function(event)
                {
                    event.stopPropagation();

                    scope.$apply(function()
                    {
                        ctrl.toggle();
                    });
                });
            }
        };
    })
    .directive('lxDropdownFilter', ['$timeout', function($timeout)
    {
        return {
            restrict: 'A',
            link: function(scope, element)
            {
                element.bind('click', function(event)
                {
                    event.stopPropagation();
                });

                $timeout(function()
                {
                    element.find('input').focus();
                }, 200);
            }
        };
    }]);
<|endoftext|>


<commit_before>/**
 * Created by Serhii Maksymchuk from Ubrainians for imCode
 * 04.09.17
 */
Imcms.define("imcms-window-builder", ["imcms-window-components-builder", "jquery"], function (windowComponents, $) {

    function setBodyScrollingRule(overflowValue) {
        $("body").css("overflow", overflowValue);
    }

    function enableBackgroundPageScrolling(pageOverflow, scrollTop) {
        setBodyScrollingRule(pageOverflow);
        $(window).scrollTop(scrollTop);
    }

    function getScrollTopAndDisable() {
        var $window = $(window);
        var prevScrollTop = $window.scrollTop();
        $window.scrollTop(0);

        return prevScrollTop;
    }

    var WindowBuilder = function (opts) {
        this.factory = opts.factory;
        this.loadDataStrategy = opts.loadDataStrategy;
        this.clearDataStrategy = opts.clearDataStrategy;
        this.$editor = undefined;
    };

    WindowBuilder.prototype = {
        _scrollTop: 0,
        _pageOverflow: "auto",
        buildWindowWithShadow: function (windowInitData) {
            this.shadowBuilder = this.shadowBuilder || new WindowBuilder({
                factory: function () {
                    return $("<div>", {"class": "imcms-modal-layout"});
                }
            });

            this.shadowBuilder.buildWindow();
            this.buildWindow.applyAsync(arguments, this);
        },
        buildWindow: function (windowInitData) {
            try {
                this._scrollTop = getScrollTopAndDisable();
                this._pageOverflow = $("body").css("overflow") || "auto";
                setBodyScrollingRule("hidden");

                if (!this.$editor) {
                    this.$editor = this.factory.apply(null, arguments).appendTo("body");
                }

                this.loadDataStrategy && this.loadDataStrategy.applyAsync(arguments);
                this.$editor.css("display", "block");

            } catch (e) {
                console.error(e);
                alert("Error in window builder! Stacktrace in console.");
                this.shadowBuilder.closeWindow();
            }
        },
        closeWindow: function () {
            enableBackgroundPageScrolling(this._pageOverflow, this._scrollTop);
            this._scrollTop = 0;
            this.$editor.css("display", "none");

            if (this.shadowBuilder) {
                setTimeout(this.shadowBuilder.closeWindow.bind(this.shadowBuilder));
            }

            if (this.clearDataStrategy) {
                try {
                    this.clearDataStrategy.call();

                } catch (e) {
                    console.error(e);
                }
            }
        },
        /**
         * Builds head with specified title and close button
         * @param {string} title
         * @returns {*} head as jQuery element
         */
        buildHead: function (title) {
            return windowComponents.buildHead(title, this.closeWindow.bind(this));
        },
        /**
         * Builds footer with specified buttons as it's block elements
         * @param {[]} buttons - array of jQuery elements
         * @returns {*} footer as jQuery element
         */
        buildFooter: function (buttons) {
            return windowComponents.buildFooter.apply(windowComponents, arguments);
        }
    };

    return WindowBuilder;
});
<commit_msg>IMCMS-233 - Apply new UI to the admin panel and editors: - Added "onCloseClick" parameter.<commit_after>/**
 * Created by Serhii Maksymchuk from Ubrainians for imCode
 * 04.09.17
 */
Imcms.define("imcms-window-builder", ["imcms-window-components-builder", "jquery"], function (windowComponents, $) {

    function setBodyScrollingRule(overflowValue) {
        $("body").css("overflow", overflowValue);
    }

    function enableBackgroundPageScrolling(pageOverflow, scrollTop) {
        setBodyScrollingRule(pageOverflow);
        $(window).scrollTop(scrollTop);
    }

    function getScrollTopAndDisable() {
        var $window = $(window);
        var prevScrollTop = $window.scrollTop();
        $window.scrollTop(0);

        return prevScrollTop;
    }

    var WindowBuilder = function (opts) {
        this.factory = opts.factory;
        this.loadDataStrategy = opts.loadDataStrategy;
        this.clearDataStrategy = opts.clearDataStrategy;
        this.$editor = undefined;
    };

    WindowBuilder.prototype = {
        _scrollTop: 0,
        _pageOverflow: "auto",
        buildWindowWithShadow: function (windowInitData) {
            this.shadowBuilder = this.shadowBuilder || new WindowBuilder({
                factory: function () {
                    return $("<div>", {"class": "imcms-modal-layout"});
                }
            });

            this.shadowBuilder.buildWindow();
            this.buildWindow.applyAsync(arguments, this);
        },
        buildWindow: function (windowInitData) {
            try {
                this._scrollTop = getScrollTopAndDisable();
                this._pageOverflow = $("body").css("overflow") || "auto";
                setBodyScrollingRule("hidden");

                if (!this.$editor) {
                    this.$editor = this.factory.apply(null, arguments).appendTo("body");
                }

                this.loadDataStrategy && this.loadDataStrategy.applyAsync(arguments);
                this.$editor.css("display", "block");

            } catch (e) {
                console.error(e);
                alert("Error in window builder! Stacktrace in console.");
                this.shadowBuilder.closeWindow();
            }
        },
        closeWindow: function () {
            enableBackgroundPageScrolling(this._pageOverflow, this._scrollTop);
            this._scrollTop = 0;
            this.$editor.css("display", "none");

            if (this.shadowBuilder) {
                setTimeout(this.shadowBuilder.closeWindow.bind(this.shadowBuilder));
            }

            if (this.clearDataStrategy) {
                try {
                    this.clearDataStrategy.call();

                } catch (e) {
                    console.error(e);
                }
            }
        },
        /**
         * Builds head with specified title and close button
         * @param {string} title
         * @param {function} onCloseClick
         * @returns {*} head as jQuery element
         */
        buildHead: function (title, onCloseClick) {
            onCloseClick = onCloseClick || this.closeWindow.bind(this);
            return windowComponents.buildHead(title, onCloseClick);
        },
        /**
         * Builds footer with specified buttons as it's block elements
         * @param {[]} buttons - array of jQuery elements
         * @returns {*} footer as jQuery element
         */
        buildFooter: function (buttons) {
            return windowComponents.buildFooter.apply(windowComponents, arguments);
        }
    };

    return WindowBuilder;
});
<|endoftext|>


<commit_before>// Imports
var Entity = require('./entity');

// Init variables
var runMaster = false;
var runGame = true;
var showConsole = true;

var masterServer;
var gameServer;

process.argv.forEach(function(val) {
    if (val == "--master") {
        runMaster = true;
    } else if (val == "--game") {
        runGame = true;
    } else if (val == "--noconsole") {
        showConsole = false;
    } else if (val == "--help") {
        console.log("Proper Usage: %s [--master] [--game]", process.argv[0]);
        console.log("    --master            Run the Agar master server.");
        console.log("    --game              Run the Agar game server.");
        console.log("    --noconsole         Disables the console");
        console.log("    --help              Help menu.");
        console.log("");
        console.log("You can use both options simultaneously to run both the master and game server.");
        console.log("");
    }
});

if (runMaster) {
    // Initialize the master server
    MasterServer = require('./MasterServer');
    masterServer = new MasterServer(80);
    masterServer.start();
}

if (runGame) {
    // Initialize the game server
    GameServer = require('./GameServer');
    var gameServer = new GameServer();
    gameServer.start();

    // Initialize the server console
    var readline = require('readline');
    var in_ = readline.createInterface({ input: process.stdin, output: process.stdout });
    if (showConsole) {
        setTimeout(prompt, 100);
    }
}

// Console functions

function prompt() {
    in_.question(">", function(str) {
    	parseCommands(str);
    	prompt();
    });
}

function parseCommands(str) {
	// Splits the string
    var split = str.split(" ");

    // Process the first string value
    switch (split[0]) {
        case "gamemode":
        	try {
        		var n = parseInt(split[1]);
        		gameServer.gameMode = require('./gamemodes').get(n);
        		gameServer.gameMode.onServerInit(gameServer); // Resets the server
        		console.log("[Game] Changed game mode to " + gameServer.gameMode.name);
        	} catch (e) {
        		console.log("[Console] Invalid game mode selected");
        	}
        	break;
        case "change":
            var key = split[1];
            var value = parseInt(split[2]);
            if (typeof gameServer.config[key] != 'undefined') {
                gameServer.config[key] = value;
                console.log("[Console] Set " + key + " to " + value);
            } else {
                console.log("[Console] Invalid config value");
            }
            break;
        case "setmass":
        	var name = split[1];
        	var amount = parseInt(split[2]);

            for (var i in gameServer.clients) {
                if (gameServer.clients[i].playerTracker.getName() == name) {
                	var client = gameServer.clients[i].playerTracker;
                	for (var j in client.cells) {
                		client.cells[j].mass = Math.max(amount,9);
                	}
                }
            }
        	break;
        case "board":
        	var newLB = [];
        	for (var i = 1; i < split.length; i++) {
        		newLB[i - 1] = split[i];
        	}
        	gameServer.gameMode.packetLB = 48;
    		gameServer.gameMode.updateLB = function(gameServer) {gameServer.leaderboard = newLB}; // Clears the update leaderboard function
        	break;
        case "food":
        	if (typeof split[3] == 'undefined') {
        		split[3] = gameServer.config.foodStartMass;
        	}
        	try {
                var pos = {x: parseInt(split[1]), y: parseInt(split[2])};
                var f = new Entity.Food(gameServer.getNextNodeId(), null, pos, parseInt(split[3]));
                f.setColor(gameServer.getRandomColor());
                gameServer.addNode(f);
                gameServer.currentFood++;
                console.log("[Console] Spawned 1 food cell at ( "+pos.x+" , "+pos.y+" )");
        	} catch (e) {
                console.log("[Console] Error proccessing that!");
        	}
        	break;
        case "virus":
        	if (typeof split[3] == 'undefined') {
        		split[3] = gameServer.config.virusStartMass;
        	}
        	try {
                var pos = {x: parseInt(split[1]), y: parseInt(split[2])};
                var v = new Entity.Virus(gameServer.getNextNodeId(), null, pos, parseInt(split[3]));
                gameServer.addNode(v);
                console.log("[Console] Spawned 1 virus at ( "+pos.x+" , "+pos.y+" )");
        	} catch (e) {
                console.log("[Console] Error proccessing that!");
        	}
        	break;
        case "pause":
            gameServer.run = !gameServer.run;
            var s = gameServer.run ? "Unpaused" : "Paused";
            console.log("[Console] " + s + " the game.");
            break;
        default:
        	console.log("[Console] Error proccessing that!");
        	break;
    }
}

<commit_msg>Update console commands<commit_after>// Imports
var Entity = require('./entity');

// Init variables
var runMaster = false;
var runGame = true;
var showConsole = true;

var masterServer;
var gameServer;

process.argv.forEach(function(val) {
    if (val == "--master") {
        runMaster = true;
    } else if (val == "--game") {
        runGame = true;
    } else if (val == "--noconsole") {
        showConsole = false;
    } else if (val == "--help") {
        console.log("Proper Usage: %s [--master] [--game]", process.argv[0]);
        console.log("    --master            Run the Agar master server.");
        console.log("    --game              Run the Agar game server.");
        console.log("    --noconsole         Disables the console");
        console.log("    --help              Help menu.");
        console.log("");
        console.log("You can use both options simultaneously to run both the master and game server.");
        console.log("");
    }
});

if (runMaster) {
    // Initialize the master server
    MasterServer = require('./MasterServer');
    masterServer = new MasterServer(80);
    masterServer.start();
}

if (runGame) {
    // Initialize the game server
    GameServer = require('./GameServer');
    var gameServer = new GameServer();
    gameServer.start();

    // Initialize the server console
    if (showConsole) {
        var readline = require('readline');
        var in_ = readline.createInterface({ input: process.stdin, output: process.stdout });
        setTimeout(prompt, 100);
    }
}

// Console functions

function prompt() {
    in_.question(">", function(str) {
    	parseCommands(str);
    	prompt();
    });
}

function parseCommands(str) {
    // Splits the string
    var split = str.split(" ");

    // Process the first string value
    var first = split[0].toLowerCase();
    switch (first) {
        case "gamemode":
            try {
                var n = parseInt(split[1]);
                gameServer.gameMode = require('./gamemodes').get(n);
                gameServer.gameMode.onServerInit(gameServer); // Resets the server
                console.log("[Game] Changed game mode to " + gameServer.gameMode.name);
            } catch (e) {
                console.log("[Console] Invalid game mode selected");
            }
            break;
        case "change":
            var key = split[1];
            var value = parseInt(split[2]);
            if (typeof gameServer.config[key] != 'undefined') {
                gameServer.config[key] = value;
                console.log("[Console] Set " + key + " to " + value);
            } else {
                console.log("[Console] Invalid config value");
            }
            break;
        case "mass":
            // Validation checks
            var name = split[1];
            if (typeof name == 'undefined') {
                console.log("[Console] Please specify a valid name");
                break;
            }

            var amount = Math.max(parseInt(split[2]),9);
            if (isNaN(amount)) {
                console.log("[Console] Please specify a valid number");
                break;
            }

	    // Sets mass to the specified amount
            for (var i in gameServer.clients) {
                if (gameServer.clients[i].playerTracker.getName() == name) {
                    var client = gameServer.clients[i].playerTracker;
                    for (var j in client.cells) {
                        client.cells[j].mass = amount;
                    }
                }
            }
            break;
        case "color":
            // Validation checks
            var name = split[1];
            if (typeof name == 'undefined') {
                console.log("[Console] Please specify a valid name");
                break;
            }

            var color = {r: 0, g: 0, b: 0};
            color.r = Math.max(Math.min(parseInt(split[2]), 255), 0);
            color.g = Math.max(Math.min(parseInt(split[3]), 255), 0);
            color.b = Math.max(Math.min(parseInt(split[4]), 255), 0);

            // Sets color to the specified amount
            for (var i in gameServer.clients) {
                if (gameServer.clients[i].playerTracker.getName() == name) {
                    var client = gameServer.clients[i].playerTracker;
                    client.setColor(color); // Set color
                    for (var j in client.cells) {
                        client.cells[j].setColor(color);
                    }
                }
            }
            break;
        case "board":
            // Create new leaderboard
        	var newLB = [];
        	for (var i = 1; i < split.length; i++) {
                    newLB[i - 1] = split[i];
        	}

            // Clears the update leaderboard function and replaces it with our own
            gameServer.gameMode.packetLB = 48;
            gameServer.gameMode.updateLB = function(gameServer) {gameServer.leaderboard = newLB};
            console.log("[Console] Successfully changed leaderboard values");
            break;
        case "food":
            var pos = {x: parseInt(split[1]), y: parseInt(split[2])};
            var mass = parseInt(split[3]);

            // Make sure the input values are numbers
            if (isNaN(pos.x) || isNaN(pos.y)) {
                console.log("[Console] Invalid coordinates");
                break;
            } if (isNaN(mass)) {
                mass = gameServer.config.foodStartMass;
            }

            // Spawn
            var f = new Entity.Food(gameServer.getNextNodeId(), null, pos, mass);
            f.setColor(gameServer.getRandomColor());
            gameServer.addNode(f);
            gameServer.currentFood++;
            console.log("[Console] Spawned 1 food cell at ( "+pos.x+" , "+pos.y+" )");
        	break;
        case "virus":
            var pos = {x: parseInt(split[1]), y: parseInt(split[2])};
            var mass = parseInt(split[3]);

            // Make sure the input values are numbers
            if (isNaN(pos.x) || isNaN(pos.y)) {
                console.log("[Console] Invalid coordinates");
                break;
            } if (isNaN(mass)) {
                mass = gameServer.config.virusStartMass;
            }

            // Spawn
            var v = new Entity.Virus(gameServer.getNextNodeId(), null, pos, mass);
            gameServer.addNode(v);
            console.log("[Console] Spawned 1 virus at ( "+pos.x+" , "+pos.y+" )");
        	break;
		case "addbot":
            var add = parseInt(split[1]);
            if (isNaN(add)) {
                add = 1; // Adds 1 bot if user doesnt specify a number
        	}

            for (var i = 0; i < add; i++) {
                gameServer.bots.addBot();
            }
            console.log("[Console] Added "+add+" player bots");
            break;
		case "kill":
            var name = split[1];
			var action = function (cell) {gameServer.removeNode(cell)};
            if (typeof name == 'undefined') {
                console.log("[Console] Please specify a valid name");
                break;
        	}

			var count = 0;
            for (var i in gameServer.clients) {
                if (gameServer.clients[i].playerTracker.getName() == name) {
                    var client = gameServer.clients[i].playerTracker;
                    for (var j in client.cells) {
                        gameServer.removeNode(client.cells[0]);
                        count++;
                    }
                }
            }
            console.log("[Console] Removed " + count + " cells");
            break;
		case "killall":
            var count = 0;
            for (var i in gameServer.nodesPlayer) {
                gameServer.removeNode(gameServer.nodesPlayer[0]);
				count++;
            }
			console.log("[Console] Removed " + count + " cells");
            break;
        case "pause":
            gameServer.run = !gameServer.run; // Switches the pause state
            var s = gameServer.run ? "Unpaused" : "Paused";
            console.log("[Console] " + s + " the game.");
            break;
		case "status":
            console.log("[Console] Current players: "+gameServer.clients.length+"/"+gameServer.config.serverMaxConnections);
            console.log("[Console] Current game mode is "+gameServer.gameMode.name);
            break;
        default:
            console.log("[Console] Invalid Command!");
            break;
    }
}

function searchForPlayers(name,action) {
    for (var i in gameServer.clients) {
        if (gameServer.clients[i].playerTracker.getName() == name) {
            var client = gameServer.clients[i].playerTracker;
            for (var j in client.cells) {
                action(client.cells[j]);
            }
        }
    }
}

<|endoftext|>