<commit_before>const simple = ['AB', 'AW', 'AE', 'B', 'CR', 'DD', 'MA', 'SL', 'SQ', 'TR', 'VW', 'W'];
const pointWithText = ['LB'];
const arrowish = ['AR', 'LN'];

const alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const alphaRev = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba';

exports.rotatePointClockwise = function(point, size) {

	// returns null on failure ; point is something like "aa"

	if (typeof point !== 'string') return null;
	if (typeof size !== 'number') return null;
	if (size < 1 || size > 52) return null;
	if (point.length !== 2) return null;

	let index0 = alpha.indexOf(point[0]);
	let index1 = alpha.indexOf(point[1]);

	if (index0 === -1 || index1 === -1) return null;

	let rev = alphaRev.slice(52 - size);
	return rev[index1] + point[0];
};

exports.rotateRectClockwise = function(rect, size) {

	// returns null on failure ; rect is something like "aa:cc"

	if (typeof size !== 'number') return null;
	if (typeof rect !== 'string') return null;
	if (size < 1 || size > 52) return null;
	if (rect.length !== 5) return null;
	if (rect[2] !== ':') return null;

	let first = rect.slice(0, 2);
	let second = rect.slice(3, 5);

	first = exports.rotatePointClockwise(first, size);
	second = exports.rotatePointClockwise(second, size);

	if (first === null || second === null) {
		return null;
	}

	return second[0] + first[1] + ":" + first[0] + second[1];
};

exports.rotateArrowClockwise = function(arrow, size) {

	// returns null on failure ; arrow is something like "aa:cc"

	if (typeof size !== 'number') return null;
	if (typeof arrow !== 'string') return null;
	if (size < 1 || size > 52) return null;
	if (arrow.length !== 5) return null;
	if (arrow[2] !== ':') return null;

	let first = arrow.slice(0, 2);
	let second = arrow.slice(3, 5);

	first = exports.rotatePointClockwise(first, size);
	second = exports.rotatePointClockwise(second, size);

	if (first === null || second === null) {
		return null;
	}

	return first + ":" + second;
};

exports.rotateNodeClockwise = function(node, size) {

	// Given a node and a board size, rotates all relevant properties in the node.
	// Does NOT do anything about the board though.

	if (typeof size !== 'number') {
		return;
	}

	// 'simple' cases are either a single point (e.g. "aa") or a rect (e.g. "aa:cc")

	for (let k = 0; k < simple.length; k++) {

		let key = simple[k];

		if (node[key] !== undefined) {

			let values = node[key];

			for (let v = 0; v < values.length; v++) {

				let value = values[v];

				if (value.length === 2) {

					let result = exports.rotatePointClockwise(value, size);
					if (result) {
						values[v] = result;
					}

				} else if (value.length === 5 && value[2] === ":") {

					let result = exports.rotateRectClockwise(value, size);
					if (result) {
						values[v] = result;
					}
				}
			}
		}
	}

	// 'pointWithText' means something like "aa:hi there"

	for (let k = 0; k < pointWithText.length; k++) {

		let key = pointWithText[k];

		if (node[key] !== undefined) {

			let values = node[key];

			for (let v = 0; v < values.length; v++) {

				let value = values[v];

				if (value[2] === ':') {

					let point = value.slice(0, 2);

					point = exports.rotatePointClockwise(point, size);
					if (point) {
						values[v] = point + value.slice(2);
					}

				}
			}
		}
	}

	// 'arrowish' things are formatted like rects, but with no requirement of topleft:bottomright

	for (let k = 0; k < arrowish.length; k++) {

		let key = arrowish[k];

		if (node[key] !== undefined) {

			let values = node[key];

			for (let v = 0; v < values.length; v++) {

				let value = values[v];

				if (value[2] === ':') {

					let result = exports.rotateArrowClockwise(value, size);
					if (result) {
						values[v] = result;
					}

				}
			}
		}
	}
};

<commit_msg>comment<commit_after>// Note that these functions are only valid for square boards.

const simple = ['AB', 'AW', 'AE', 'B', 'CR', 'DD', 'MA', 'SL', 'SQ', 'TR', 'VW', 'W'];
const pointWithText = ['LB'];
const arrowish = ['AR', 'LN'];

const alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
const alphaRev = 'ZYXWVUTSRQPONMLKJIHGFEDCBAzyxwvutsrqponmlkjihgfedcba';

exports.rotatePointClockwise = function(point, size) {

	// returns null on failure ; point is something like "aa"

	if (typeof point !== 'string') return null;
	if (typeof size !== 'number') return null;
	if (size < 1 || size > 52) return null;
	if (point.length !== 2) return null;

	let index0 = alpha.indexOf(point[0]);
	let index1 = alpha.indexOf(point[1]);

	if (index0 === -1 || index1 === -1) return null;

	let rev = alphaRev.slice(52 - size);
	return rev[index1] + point[0];
};

exports.rotateRectClockwise = function(rect, size) {

	// returns null on failure ; rect is something like "aa:cc"

	if (typeof size !== 'number') return null;
	if (typeof rect !== 'string') return null;
	if (size < 1 || size > 52) return null;
	if (rect.length !== 5) return null;
	if (rect[2] !== ':') return null;

	let first = rect.slice(0, 2);
	let second = rect.slice(3, 5);

	first = exports.rotatePointClockwise(first, size);
	second = exports.rotatePointClockwise(second, size);

	if (first === null || second === null) {
		return null;
	}

	return second[0] + first[1] + ":" + first[0] + second[1];
};

exports.rotateArrowClockwise = function(arrow, size) {

	// returns null on failure ; arrow is something like "aa:cc"

	if (typeof size !== 'number') return null;
	if (typeof arrow !== 'string') return null;
	if (size < 1 || size > 52) return null;
	if (arrow.length !== 5) return null;
	if (arrow[2] !== ':') return null;

	let first = arrow.slice(0, 2);
	let second = arrow.slice(3, 5);

	first = exports.rotatePointClockwise(first, size);
	second = exports.rotatePointClockwise(second, size);

	if (first === null || second === null) {
		return null;
	}

	return first + ":" + second;
};

exports.rotateNodeClockwise = function(node, size) {

	// Given a node and a board size, rotates all relevant properties in the node.
	// Does NOT do anything about the board though.

	if (typeof size !== 'number') {
		return;
	}

	// 'simple' cases are either a single point (e.g. "aa") or a rect (e.g. "aa:cc")

	for (let k = 0; k < simple.length; k++) {

		let key = simple[k];

		if (node[key] !== undefined) {

			let values = node[key];

			for (let v = 0; v < values.length; v++) {

				let value = values[v];

				if (value.length === 2) {

					let result = exports.rotatePointClockwise(value, size);
					if (result) {
						values[v] = result;
					}

				} else if (value.length === 5 && value[2] === ":") {

					let result = exports.rotateRectClockwise(value, size);
					if (result) {
						values[v] = result;
					}
				}
			}
		}
	}

	// 'pointWithText' means something like "aa:hi there"

	for (let k = 0; k < pointWithText.length; k++) {

		let key = pointWithText[k];

		if (node[key] !== undefined) {

			let values = node[key];

			for (let v = 0; v < values.length; v++) {

				let value = values[v];

				if (value[2] === ':') {

					let point = value.slice(0, 2);

					point = exports.rotatePointClockwise(point, size);
					if (point) {
						values[v] = point + value.slice(2);
					}

				}
			}
		}
	}

	// 'arrowish' things are formatted like rects, but with no requirement of topleft:bottomright

	for (let k = 0; k < arrowish.length; k++) {

		let key = arrowish[k];

		if (node[key] !== undefined) {

			let values = node[key];

			for (let v = 0; v < values.length; v++) {

				let value = values[v];

				if (value[2] === ':') {

					let result = exports.rotateArrowClockwise(value, size);
					if (result) {
						values[v] = result;
					}

				}
			}
		}
	}
};

<|endoftext|>


<commit_before>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebApp Tabs.
 *
 * The Initial Developer of the Original Code is
 * Dave Townsend <dtownsend@oxymoronical.com>
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

const EXPORTED_SYMBOLS = ["LogManager"];

Components.utils.import("resource://gre/modules/Services.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

function Logger(aScope, aName) {
  this.name = aName;

  aScope.LOG = this.log.bind(this);
  aScope.WARN = this.warn.bind(this);
  aScope.ERROR = this.error.bind(this);
}

Logger.prototype = {
  name: null,

  log: function(aStr, aException) {
    let message = this.formatLogMessage("error", this.name, aStr, aException);

    Services.console.logStringMessage(message)

    dump("***  LOG  *** " + message + "\n");
  },

  warn: function(aStr, aException) {
    let message = this.formatLogMessage("error", this.name, aStr, aException);

    let stack = this.getStackDetails(aException);

    let consoleMessage = Cc["@mozilla.org/scripterror;1"].
                         createInstance(Ci.nsIScriptError);
    consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
                        Ci.nsIScriptError.warningFlag, "component javascript");
    Services.console.logMessage(consoleMessage);

    dump("*** WARN  *** " + message + "\n");
  },

  error: function(aStr, aException) {
    let message = this.formatLogMessage("error", this.name, aStr, aException);

    let stack = this.getStackDetails(aException);

    let consoleMessage = Cc["@mozilla.org/scripterror;1"].
                         createInstance(Ci.nsIScriptError);
    consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
                        Ci.nsIScriptError.errorFlag, "component javascript");
    Services.console.logMessage(consoleMessage);

    dump("*** ERROR *** " + message + "\n");
  },

  formatLogMessage: function(aType, aName, aStr, aException) {
    let message = aName + ": " + aStr;
    if (aException) {
      try {
        if ("message" in aException)
          return message + ": " + aException.message;
      } catch (e) { }
      return message + ": " + aException;
    }
    return message;
  },

  getStackDetails: function(aException) {
    // Defensively wrap all this to ensure that failing to get the message source
    // doesn't stop the message from being logged
    try {
      if (aException) {
        if (aException instanceof Ci.nsIException) {
          return {
            sourceName: aException.filename,
            lineNumber: aException.lineNumber
          };
        }

        return {
          sourceName: aException.fileName,
          lineNumber: aException.lineNumber
        };
      }

      let stackFrame = Components.stack.caller.caller.caller;
      return {
        sourceName: stackFrame.filename,
        lineNumber: stackFrame.lineNumber
      };
    }
    catch (e) {
      return {
        sourceName: null,
        lineNumber: 0
      };
    }
  },
};

const LogManager = {
  createLogger: function(aScope, aName) {
    new Logger(aScope, aName);
  }
}
<commit_msg>Add rudimentary log filtering. Closes #11<commit_after>/* ***** BEGIN LICENSE BLOCK *****
 *   Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WebApp Tabs.
 *
 * The Initial Developer of the Original Code is
 * Dave Townsend <dtownsend@oxymoronical.com>
 * Portions created by the Initial Developer are Copyright (C) 2011
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

const EXPORTED_SYMBOLS = ["LogManager"];

Components.utils.import("resource://gre/modules/Services.jsm");

const Cc = Components.classes;
const Ci = Components.interfaces;

const LOGLEVEL = "extensions.webapptabs.loglevel";

function Logger(aScope, aName) {
  this.name = aName;

  aScope.LOG = this.log.bind(this);
  aScope.WARN = this.warn.bind(this);
  aScope.ERROR = this.error.bind(this);
}

Logger.prototype = {
  name: null,

  log: function(aStr, aException) {
    if (LogManager.logLevel > LogManager.LEVEL_LOG)
      return;

    let message = this.formatLogMessage("error", this.name, aStr, aException);

    Services.console.logStringMessage(message)

    dump("***  LOG  *** " + message + "\n");
  },

  warn: function(aStr, aException) {
    if (LogManager.logLevel > LogManager.LEVEL_WARN)
      return;

    let message = this.formatLogMessage("error", this.name, aStr, aException);

    let stack = this.getStackDetails(aException);

    let consoleMessage = Cc["@mozilla.org/scripterror;1"].
                         createInstance(Ci.nsIScriptError);
    consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
                        Ci.nsIScriptError.warningFlag, "component javascript");
    Services.console.logMessage(consoleMessage);

    dump("*** WARN  *** " + message + "\n");
  },

  error: function(aStr, aException) {
    if (LogManager.logLevel > LogManager.LEVEL_ERROR)
      return;

    let message = this.formatLogMessage("error", this.name, aStr, aException);

    let stack = this.getStackDetails(aException);

    let consoleMessage = Cc["@mozilla.org/scripterror;1"].
                         createInstance(Ci.nsIScriptError);
    consoleMessage.init(message, stack.sourceName, null, stack.lineNumber, 0,
                        Ci.nsIScriptError.errorFlag, "component javascript");
    Services.console.logMessage(consoleMessage);

    dump("*** ERROR *** " + message + "\n");
  },

  formatLogMessage: function(aType, aName, aStr, aException) {
    let message = aName + ": " + aStr;
    if (aException) {
      try {
        if ("message" in aException)
          return message + ": " + aException.message;
      } catch (e) { }
      return message + ": " + aException;
    }
    return message;
  },

  getStackDetails: function(aException) {
    // Defensively wrap all this to ensure that failing to get the message source
    // doesn't stop the message from being logged
    try {
      if (aException) {
        if (aException instanceof Ci.nsIException) {
          return {
            sourceName: aException.filename,
            lineNumber: aException.lineNumber
          };
        }

        return {
          sourceName: aException.fileName,
          lineNumber: aException.lineNumber
        };
      }

      let stackFrame = Components.stack.caller.caller.caller;
      return {
        sourceName: stackFrame.filename,
        lineNumber: stackFrame.lineNumber
      };
    }
    catch (e) {
      return {
        sourceName: null,
        lineNumber: 0
      };
    }
  },
};

var gLogLevel = null;

const LogManager = {
  LEVEL_DEBUG: 0,
  LEVEL_LOG: 1,
  LEVEL_WARN: 2,
  LEVEL_ERROR: 3,

  get logLevel() {
    if (gLogLevel)
      return gLogLevel;

    if (Services.prefs.getPrefType(LOGLEVEL) != Services.prefs.PREF_INT)
      gLogLevel = 2;
    else
      gLogLevel = Services.prefs.getIntPref(LOGLEVEL);

    return gLogLevel;
  },

  createLogger: function(aScope, aName) {
    new Logger(aScope, aName);
  }
}
<|endoftext|>


<commit_before>import React, { Component, PropTypes } from 'react'
import cx from 'classnames'
import _ from 'lodash'
import {
  getElementType,
  getUnhandledProps,
  isBrowser,
  makeDebugger,
  META,
  SUI,
  useKeyOnly,
  useKeyOrValueAndKey,
} from '../../lib'
import Portal from '../../addons/Portal'
import PopupContent from './PopupContent'
import PopupHeader from './PopupHeader'

const debug = makeDebugger('popup')

const _meta = {
  name: 'Popup',
  type: META.TYPES.MODULE,
  props: {
    content: [PropTypes.string, PropTypes.node],
    on: ['hover', 'click', 'focus'],
    positioning: [
      'top left',
      'top right',
      'bottom right',
      'bottom left',
      'right center',
      'left center',
      'top center',
      'bottom center',
    ],
    size: _.without(SUI.SIZES, 'medium', 'big', 'massive'),
    wide: [true, false, 'very'],
  },
}

/**
 * A Popup displays additional information on top of a page.
 */
export default class Popup extends Component {
  static propTypes = {
    /** Display the popup without the pointing arrow */
    basic: PropTypes.bool,

    /** You may pass a content as children of the Popup */
    children: PropTypes.node,

    /** Classes to add to the Popup className. */
    className: PropTypes.string,

    /** Simple text content for the popover */
    content: PropTypes.oneOfType(_meta.props.content),

    /** A Flowing popup have no maximum width and continue to flow to fit its content */
    flowing: PropTypes.bool,

    /** Takes up the entire width of its offset container */
    // TODO: implement the Popup fluid layout
    // fluid: PropTypes.bool,

    /** Header displayed above the content in bold */
    header: PropTypes.string,

    /** Whether the popup should not close on hover */
    hoverable: PropTypes.bool,

    /** Invert the colors of the popup */
    inverted: PropTypes.bool,

    /** The node where the popup should mount.. */
    hideOnScroll: PropTypes.bool,

    /** Horizontal offset in pixels to be applied to the popup */
    offset: PropTypes.number,

    /** Event triggering the popup */
    on: PropTypes.oneOf(_meta.props.on),

    /**
     * Called when a close event happens
     *
     * @param {SyntheticEvent} event - React's original SyntheticEvent.
     * @param {object} data - All props.
     */
    onClose: PropTypes.func,

    /**
     * Called when the portal is mounted on the DOM
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onMount: PropTypes.func,

    /**
     * Called when an open event happens
     *
     * @param {SyntheticEvent} event - React's original SyntheticEvent.
     * @param {object} data - All props.
     */
    onOpen: PropTypes.func,

    /**
     * Called when the portal is unmounted from the DOM
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onUnmount: PropTypes.func,

    /** Positioning for the popover */
    positioning: PropTypes.oneOf(_meta.props.positioning),

    /** Popup size */
    size: PropTypes.oneOf(_meta.props.size),

    /** custom popup style */
    style: PropTypes.object,

    /** Element to be rendered in-place where the popup is defined. */
    trigger: PropTypes.node,

    /** Popup width */
    wide: PropTypes.oneOf(_meta.props.wide),
  }

  static defaultProps = {
    positioning: 'top left',
    on: 'hover',
  }

  static _meta = _meta
  static Content = PopupContent
  static Header = PopupHeader

  state = {}

  computePopupStyle(positions) {
    const style = { position: 'absolute' }

    // Do not access window/document when server side rendering
    if (!isBrowser) return style

    const { offset } = this.props
    const { pageYOffset, pageXOffset } = window
    const { clientWidth, clientHeight } = document.documentElement

    if (_.includes(positions, 'right')) {
      style.right = Math.round(clientWidth - (this.coords.right + pageXOffset))
      style.left = 'auto'
    } else if (_.includes(positions, 'left')) {
      style.left = Math.round(this.coords.left + pageXOffset)
      style.right = 'auto'
    } else {  // if not left nor right, we are horizontally centering the element
      const xOffset = (this.coords.width - this.popupCoords.width) / 2
      style.left = Math.round(this.coords.left + xOffset + pageXOffset)
      style.right = 'auto'
    }

    if (_.includes(positions, 'top')) {
      style.bottom = Math.round(clientHeight - (this.coords.top + pageYOffset))
      style.top = 'auto'
    } else if (_.includes(positions, 'bottom')) {
      style.top = Math.round(this.coords.bottom + pageYOffset)
      style.bottom = 'auto'
    } else {  // if not top nor bottom, we are vertically centering the element
      const yOffset = (this.coords.height + this.popupCoords.height) / 2
      style.top = Math.round(this.coords.bottom + pageYOffset - yOffset)
      style.bottom = 'auto'

      const xOffset = this.popupCoords.width + 8
      if (_.includes(positions, 'right')) {
        style.right -= xOffset
      } else {
        style.left -= xOffset
      }
    }

    if (offset) {
      if (_.isNumber(style.right)) {
        style.right -= offset
      } else {
        style.left -= offset
      }
    }

    return style
  }

  // check if the style would display
  // the popup outside of the view port
  isStyleInViewport(style) {
    const { pageYOffset, pageXOffset } = window
    const { clientWidth, clientHeight } = document.documentElement

    const element = {
      top: style.top,
      left: style.left,
      width: this.popupCoords.width,
      height: this.popupCoords.height,
    }
    if (_.isNumber(style.right)) {
      element.left = clientWidth - style.right - element.width
    }
    if (_.isNumber(style.bottom)) {
      element.top = clientHeight - style.bottom - element.height
    }

    // hidden on top
    if (element.top < pageYOffset) return false
    // hidden on the bottom
    if (element.top + element.height > pageYOffset + clientHeight) return false
    // hidden the left
    if (element.left < pageXOffset) return false
    // hidden on the right
    if (element.left + element.width > pageXOffset + clientWidth) return false

    return true
  }

  setPopupStyle() {
    if (!this.coords || !this.popupCoords) return
    let positioning = this.props.positioning
    let style = this.computePopupStyle(positioning)

    // Lets detect if the popup is out of the viewport and adjust
    // the position accordingly
    const positions = _.without(_meta.props.positioning, positioning)
    for (let i = 0; !this.isStyleInViewport(style) && i < positions.length; i++) {
      style = this.computePopupStyle(positions[i])
      positioning = positions[i]
    }

    // Append 'px' to every numerical values in the style
    style = _.mapValues(style, value => _.isNumber(value) ? value + 'px' : value)
    this.setState({ style, positioning })
  }

  getPortalProps() {
    const portalProps = {}

    const { on, hoverable } = this.props

    if (hoverable) {
      portalProps.closeOnPortalMouseLeave = true
      portalProps.mouseLeaveDelay = 300
    }

    if (on === 'click') {
      portalProps.openOnTriggerClick = true
      portalProps.closeOnTriggerClick = true
      portalProps.closeOnDocumentClick = true
    } else if (on === 'focus') {
      portalProps.openOnTriggerFocus = true
      portalProps.closeOnTriggerBlur = true
    } else if (on === 'hover') {
      portalProps.openOnTriggerMouseOver = true
      portalProps.closeOnTriggerMouseLeave = true
      // Taken from SUI: https://git.io/vPmCm
      portalProps.mouseLeaveDelay = 70
      portalProps.mouseOverDelay = 50
    }

    return portalProps
  }

  hideOnScroll = (e) => {
    this.setState({ closed: true })
    window.removeEventListener('scroll', this.hideOnScroll)
    setTimeout(() => this.setState({ closed: false }), 50)
  }

  handleClose = (e) => {
    debug('handleClose()')
    const { onClose } = this.props
    if (onClose) onClose(e, this.props)
  }

  handleOpen = (e) => {
    debug('handleOpen()')
    this.coords = e.currentTarget.getBoundingClientRect()

    const { onOpen } = this.props
    if (onOpen) onOpen(e, this.props)
  }

  handlePortalMount = (e) => {
    debug('handlePortalMount()')
    if (this.props.hideOnScroll) {
      window.addEventListener('scroll', this.hideOnScroll)
    }

    const { onMount } = this.props
    if (onMount) onMount(e, this.props)
  }

  handlePortalUnmount = (e) => {
    debug('handlePortalUnmount()')
    const { onUnmount } = this.props
    if (onUnmount) onUnmount(e, this.props)
  }

  popupMounted = (ref) => {
    debug('popupMounted()')
    this.popupCoords = ref ? ref.getBoundingClientRect() : null
    this.setPopupStyle()
  }

  render() {
    const {
      basic,
      children,
      className,
      content,
      flowing,
      header,
      inverted,
      size,
      trigger,
      wide,
    } = this.props

    const { positioning, closed } = this.state
    const style = _.assign({}, this.state.style, this.props.style)
    const classes = cx(
      'ui',
      positioning,
      size,
      useKeyOrValueAndKey(wide, 'wide'),
      useKeyOnly(basic, 'basic'),
      useKeyOnly(flowing, 'flowing'),
      useKeyOnly(inverted, 'inverted'),
      'popup transition visible',
      className,
    )

    if (closed) return trigger

    const unhandled = getUnhandledProps(Popup, this.props)
    const portalPropNames = _.keys(Portal.propTypes)

    const rest = _.omit(unhandled, portalPropNames)
    const portalProps = _.pick(unhandled, portalPropNames)
    const ElementType = getElementType(Popup, this.props)

    const popupJSX = (
      <ElementType {...rest} className={classes} style={style} ref={this.popupMounted}>
        {children}
        {!children && PopupHeader.create(header)}
        {!children && PopupContent.create(content)}
      </ElementType>
    )

    const mergedPortalProps = { ...this.getPortalProps(), ...portalProps }
    debug('portal props:', mergedPortalProps)

    return (
      <Portal
        {...mergedPortalProps}
        trigger={trigger}
        onClose={this.handleClose}
        onMount={this.handlePortalMount}
        onOpen={this.handleOpen}
        onUnmount={this.handlePortalUnmount}
      >
        {popupJSX}
      </Portal>
    )
  }
}
<commit_msg>fix(Popup): fix propTypes for content (#990)<commit_after>import React, { Component, PropTypes } from 'react'
import cx from 'classnames'
import _ from 'lodash'
import {
  getElementType,
  getUnhandledProps,
  isBrowser,
  makeDebugger,
  META,
  SUI,
  useKeyOnly,
  useKeyOrValueAndKey,
} from '../../lib'
import Portal from '../../addons/Portal'
import PopupContent from './PopupContent'
import PopupHeader from './PopupHeader'

const debug = makeDebugger('popup')

const _meta = {
  name: 'Popup',
  type: META.TYPES.MODULE,
  props: {
    on: ['hover', 'click', 'focus'],
    positioning: [
      'top left',
      'top right',
      'bottom right',
      'bottom left',
      'right center',
      'left center',
      'top center',
      'bottom center',
    ],
    size: _.without(SUI.SIZES, 'medium', 'big', 'massive'),
    wide: [true, false, 'very'],
  },
}

/**
 * A Popup displays additional information on top of a page.
 */
export default class Popup extends Component {
  static propTypes = {
    /** Display the popup without the pointing arrow */
    basic: PropTypes.bool,

    /** You may pass a content as children of the Popup */
    children: PropTypes.node,

    /** Classes to add to the Popup className. */
    className: PropTypes.string,

    /** Simple text content for the popover */
    content: PropTypes.node,

    /** A Flowing popup have no maximum width and continue to flow to fit its content */
    flowing: PropTypes.bool,

    /** Takes up the entire width of its offset container */
    // TODO: implement the Popup fluid layout
    // fluid: PropTypes.bool,

    /** Header displayed above the content in bold */
    header: PropTypes.string,

    /** Whether the popup should not close on hover */
    hoverable: PropTypes.bool,

    /** Invert the colors of the popup */
    inverted: PropTypes.bool,

    /** The node where the popup should mount.. */
    hideOnScroll: PropTypes.bool,

    /** Horizontal offset in pixels to be applied to the popup */
    offset: PropTypes.number,

    /** Event triggering the popup */
    on: PropTypes.oneOf(_meta.props.on),

    /**
     * Called when a close event happens
     *
     * @param {SyntheticEvent} event - React's original SyntheticEvent.
     * @param {object} data - All props.
     */
    onClose: PropTypes.func,

    /**
     * Called when the portal is mounted on the DOM
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onMount: PropTypes.func,

    /**
     * Called when an open event happens
     *
     * @param {SyntheticEvent} event - React's original SyntheticEvent.
     * @param {object} data - All props.
     */
    onOpen: PropTypes.func,

    /**
     * Called when the portal is unmounted from the DOM
     *
     * @param {null}
     * @param {object} data - All props.
     */
    onUnmount: PropTypes.func,

    /** Positioning for the popover */
    positioning: PropTypes.oneOf(_meta.props.positioning),

    /** Popup size */
    size: PropTypes.oneOf(_meta.props.size),

    /** custom popup style */
    style: PropTypes.object,

    /** Element to be rendered in-place where the popup is defined. */
    trigger: PropTypes.node,

    /** Popup width */
    wide: PropTypes.oneOf(_meta.props.wide),
  }

  static defaultProps = {
    positioning: 'top left',
    on: 'hover',
  }

  static _meta = _meta
  static Content = PopupContent
  static Header = PopupHeader

  state = {}

  computePopupStyle(positions) {
    const style = { position: 'absolute' }

    // Do not access window/document when server side rendering
    if (!isBrowser) return style

    const { offset } = this.props
    const { pageYOffset, pageXOffset } = window
    const { clientWidth, clientHeight } = document.documentElement

    if (_.includes(positions, 'right')) {
      style.right = Math.round(clientWidth - (this.coords.right + pageXOffset))
      style.left = 'auto'
    } else if (_.includes(positions, 'left')) {
      style.left = Math.round(this.coords.left + pageXOffset)
      style.right = 'auto'
    } else {  // if not left nor right, we are horizontally centering the element
      const xOffset = (this.coords.width - this.popupCoords.width) / 2
      style.left = Math.round(this.coords.left + xOffset + pageXOffset)
      style.right = 'auto'
    }

    if (_.includes(positions, 'top')) {
      style.bottom = Math.round(clientHeight - (this.coords.top + pageYOffset))
      style.top = 'auto'
    } else if (_.includes(positions, 'bottom')) {
      style.top = Math.round(this.coords.bottom + pageYOffset)
      style.bottom = 'auto'
    } else {  // if not top nor bottom, we are vertically centering the element
      const yOffset = (this.coords.height + this.popupCoords.height) / 2
      style.top = Math.round(this.coords.bottom + pageYOffset - yOffset)
      style.bottom = 'auto'

      const xOffset = this.popupCoords.width + 8
      if (_.includes(positions, 'right')) {
        style.right -= xOffset
      } else {
        style.left -= xOffset
      }
    }

    if (offset) {
      if (_.isNumber(style.right)) {
        style.right -= offset
      } else {
        style.left -= offset
      }
    }

    return style
  }

  // check if the style would display
  // the popup outside of the view port
  isStyleInViewport(style) {
    const { pageYOffset, pageXOffset } = window
    const { clientWidth, clientHeight } = document.documentElement

    const element = {
      top: style.top,
      left: style.left,
      width: this.popupCoords.width,
      height: this.popupCoords.height,
    }
    if (_.isNumber(style.right)) {
      element.left = clientWidth - style.right - element.width
    }
    if (_.isNumber(style.bottom)) {
      element.top = clientHeight - style.bottom - element.height
    }

    // hidden on top
    if (element.top < pageYOffset) return false
    // hidden on the bottom
    if (element.top + element.height > pageYOffset + clientHeight) return false
    // hidden the left
    if (element.left < pageXOffset) return false
    // hidden on the right
    if (element.left + element.width > pageXOffset + clientWidth) return false

    return true
  }

  setPopupStyle() {
    if (!this.coords || !this.popupCoords) return
    let positioning = this.props.positioning
    let style = this.computePopupStyle(positioning)

    // Lets detect if the popup is out of the viewport and adjust
    // the position accordingly
    const positions = _.without(_meta.props.positioning, positioning)
    for (let i = 0; !this.isStyleInViewport(style) && i < positions.length; i++) {
      style = this.computePopupStyle(positions[i])
      positioning = positions[i]
    }

    // Append 'px' to every numerical values in the style
    style = _.mapValues(style, value => _.isNumber(value) ? value + 'px' : value)
    this.setState({ style, positioning })
  }

  getPortalProps() {
    const portalProps = {}

    const { on, hoverable } = this.props

    if (hoverable) {
      portalProps.closeOnPortalMouseLeave = true
      portalProps.mouseLeaveDelay = 300
    }

    if (on === 'click') {
      portalProps.openOnTriggerClick = true
      portalProps.closeOnTriggerClick = true
      portalProps.closeOnDocumentClick = true
    } else if (on === 'focus') {
      portalProps.openOnTriggerFocus = true
      portalProps.closeOnTriggerBlur = true
    } else if (on === 'hover') {
      portalProps.openOnTriggerMouseOver = true
      portalProps.closeOnTriggerMouseLeave = true
      // Taken from SUI: https://git.io/vPmCm
      portalProps.mouseLeaveDelay = 70
      portalProps.mouseOverDelay = 50
    }

    return portalProps
  }

  hideOnScroll = (e) => {
    this.setState({ closed: true })
    window.removeEventListener('scroll', this.hideOnScroll)
    setTimeout(() => this.setState({ closed: false }), 50)
  }

  handleClose = (e) => {
    debug('handleClose()')
    const { onClose } = this.props
    if (onClose) onClose(e, this.props)
  }

  handleOpen = (e) => {
    debug('handleOpen()')
    this.coords = e.currentTarget.getBoundingClientRect()

    const { onOpen } = this.props
    if (onOpen) onOpen(e, this.props)
  }

  handlePortalMount = (e) => {
    debug('handlePortalMount()')
    if (this.props.hideOnScroll) {
      window.addEventListener('scroll', this.hideOnScroll)
    }

    const { onMount } = this.props
    if (onMount) onMount(e, this.props)
  }

  handlePortalUnmount = (e) => {
    debug('handlePortalUnmount()')
    const { onUnmount } = this.props
    if (onUnmount) onUnmount(e, this.props)
  }

  popupMounted = (ref) => {
    debug('popupMounted()')
    this.popupCoords = ref ? ref.getBoundingClientRect() : null
    this.setPopupStyle()
  }

  render() {
    const {
      basic,
      children,
      className,
      content,
      flowing,
      header,
      inverted,
      size,
      trigger,
      wide,
    } = this.props

    const { positioning, closed } = this.state
    const style = _.assign({}, this.state.style, this.props.style)
    const classes = cx(
      'ui',
      positioning,
      size,
      useKeyOrValueAndKey(wide, 'wide'),
      useKeyOnly(basic, 'basic'),
      useKeyOnly(flowing, 'flowing'),
      useKeyOnly(inverted, 'inverted'),
      'popup transition visible',
      className,
    )

    if (closed) return trigger

    const unhandled = getUnhandledProps(Popup, this.props)
    const portalPropNames = _.keys(Portal.propTypes)

    const rest = _.omit(unhandled, portalPropNames)
    const portalProps = _.pick(unhandled, portalPropNames)
    const ElementType = getElementType(Popup, this.props)

    const popupJSX = (
      <ElementType {...rest} className={classes} style={style} ref={this.popupMounted}>
        {children}
        {!children && PopupHeader.create(header)}
        {!children && PopupContent.create(content)}
      </ElementType>
    )

    const mergedPortalProps = { ...this.getPortalProps(), ...portalProps }
    debug('portal props:', mergedPortalProps)

    return (
      <Portal
        {...mergedPortalProps}
        trigger={trigger}
        onClose={this.handleClose}
        onMount={this.handlePortalMount}
        onOpen={this.handleOpen}
        onUnmount={this.handlePortalUnmount}
      >
        {popupJSX}
      </Portal>
    )
  }
}
<|endoftext|>


<commit_before>
const instanceofSymbol = Symbol('instanceofSymbol')

const ObservableMixin = base => {
    class Observable extends base {

        on(eventName, callback) {
            if (!this._eventMap)
                this._eventMap = new Map

            if (!this._eventMap.has(eventName))
                this._eventMap.set(eventName, [])

            this._eventMap.get(eventName).push(callback)
        }

        off(eventName, callback) {
            if (!this._eventMap || !this._eventMap.has(eventName)) return

            const callbacks = this._eventMap.get(eventName)

            if (callbacks.indexOf(callback) === -1) return

            callbacks.splice(callbacks.indexOf(callback), 1)

            if (callbacks.length === 0) this._eventMap.delete(eventName)

            if (this._eventMap.size === 0) this._eventMap = null
        }

        triggerEvent(eventName, data) {
            if (!this._eventMap || !this._eventMap.has(eventName)) return

            const callbacks = this._eventMap.get(eventName)

            for (let i=0, len=callbacks.length; i<len; i+=1) {
                callbacks[i](data)
            }
        }
    }

    Object.defineProperty(Observable, Symbol.hasInstance, {
        value: function(obj) {
            if (this !== Observable) return Object.getPrototypeOf(Observable)[Symbol.hasInstance].call(this, obj)

            let currentProto = obj

            while(currentProto) {
                let desc = Object.getOwnPropertyDescriptor(currentProto, "constructor")

                if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol))
                    return true

                currentProto = Object.getPrototypeOf(currentProto)
            }

            return false
        }
    })

    Observable[instanceofSymbol] = true

    return Observable
}

const Observable = ObservableMixin(class{})
Observable.mixin = ObservableMixin

export {Observable as default}
<commit_msg>Throw an error when passing a non-function to Observable#on.<commit_after>
const instanceofSymbol = Symbol('instanceofSymbol')

const ObservableMixin = base => {
    class Observable extends base {

        on(eventName, callback) {
            if (!this._eventMap)
                this._eventMap = new Map

            if (!this._eventMap.has(eventName))
                this._eventMap.set(eventName, [])

            if (typeof callback == 'function')
                this._eventMap.get(eventName).push(callback)
            else
                throw new Error('Expected a function in callback argument of Observable#on.')
        }

        off(eventName, callback) {
            if (!this._eventMap || !this._eventMap.has(eventName)) return

            const callbacks = this._eventMap.get(eventName)

            if (callbacks.indexOf(callback) === -1) return

            callbacks.splice(callbacks.indexOf(callback), 1)

            if (callbacks.length === 0) this._eventMap.delete(eventName)

            if (this._eventMap.size === 0) this._eventMap = null
        }

        triggerEvent(eventName, data) {
            if (!this._eventMap || !this._eventMap.has(eventName)) return

            const callbacks = this._eventMap.get(eventName)

            for (let i=0, len=callbacks.length; i<len; i+=1) {
                callbacks[i](data)
            }
        }
    }

    Object.defineProperty(Observable, Symbol.hasInstance, {
        value: function(obj) {
            if (this !== Observable) return Object.getPrototypeOf(Observable)[Symbol.hasInstance].call(this, obj)

            let currentProto = obj

            while(currentProto) {
                let desc = Object.getOwnPropertyDescriptor(currentProto, "constructor")

                if (desc && desc.value && desc.value.hasOwnProperty(instanceofSymbol))
                    return true

                currentProto = Object.getPrototypeOf(currentProto)
            }

            return false
        }
    })

    Observable[instanceofSymbol] = true

    return Observable
}

const Observable = ObservableMixin(class{})
Observable.mixin = ObservableMixin

export {Observable as default}
<|endoftext|>


<commit_before>"use strict";

let mongoose = require("mongoose");
let net = require("../net");
let util = require("util");

let ResultSchema = new mongoose.Schema({
  season: Number,
  round: Number,
  date: Date,
  results: [{
    _id: false,
    number: Number,
    round: Number,
    code: String,
    firstName: String,
    lastName: String,
    points: Number,
  }],
});

ResultSchema.statics.latestResult = function() {
  return this.findOne().sort({season: -1, round: -1});
};

ResultSchema.statics.driverStandings = function() {
  return this.latestResult()
    .then(last => last || {})
    .then(last => this.find({season: last.season}))
    .then(races => races.reduce((standings, race) => {
      race.results.forEach(item => {
        let name = util.format("%s %s", item.firstName, item.lastName);
        let points = (standings.get(name) || 0) + item.points;
        standings.set(name, points);
      });
      return standings;
    }, new Map))
  .then(standings => [...standings.entries()].sort((a, b) => b[1] - a[1]))
  .then(entries => entries.length ? entries : Promise.reject(new Error("No points data found.")));
};

let Result = mongoose.model("result", ResultSchema);

class ErgastParser {
  parse(json) {
    let data = JSON.parse(json).MRData.RaceTable.Races[0];
    return {
      season: parseInt(data.season),
      round: parseInt(data.round),
      date: new Date(util.format("%sT%s", data.date, data.time)),
      results: data.Results.map(item => ({
        number: parseInt(item.number),
        points: parseInt(item.points),
        code: item.Driver.code,
        firstName: item.Driver.givenName,
        lastName: item.Driver.familyName,
      })),
    };
  }
}

class ErgastWatcher {
  constructor(season, round) {
    this.season = season;
    this.round = round;
    this.url = util.format("http://ergast.com/api/f1/%d/%d/results.json", season, round);
    this.interval = 10 * 1000;
  }

  watch() {
    return new Promise((resolve, reject) => {
      this.fetch().then(resolve, error => {
        console.error(error.stack);
        console.log("retry results", this.season, this.round);
        setTimeout(() => this.watch(), this.interval);
      });
    });
  }

  fetch() {
    return net.download(this.url).then(response => {
      let result = (new ErgastParser).parse(response.data);
      return result.results.length ? result : Promise.reject(new Error("Got invalid data"));
    });
  }
}

exports.configure = services => {
  let commands = services.get("command.manager");
  let database = services.get("database");
  let events = services.get("event.manager");
  let Event = database.model("event");
  let season = (new Date).getFullYear();

  commands.add("points", () => {
    return Result.driverStandings()
      .then(standings => standings.slice(0, 10).map((row, i) => util.format("%d. %s (%d)", i+1, row[0], row[1])))
      .then(standings => standings.join(" "));
  });

  let watchers = new Map;

  let watchResults = () => {
    Result.find({season: season}).sort("-round")
      .then(results => results.map(r => r.round))
      .then(rounds => Event.find({
        season: season,
        type: "race",
        end: {$lt: new Date},
        round: {$not: {$in: rounds}}
      }))
      .then(races => races.map(race => {
        let wid = util.format("%d:%d", race.season, race.round);
        if (!watchers.has(wid)) {
          let watcher = new ErgastWatcher(race.season, race.round);
          watcher.watch()
            .then(result => Result.update({season: result.season, round: result.round}, result, {upsert: true}))
            .then(() => events.emit("raceresults.result", {season: race.season, round: race.round}))
            .then(() => watchers.remove(wid))
            .then(() => console.log("Updated result for round", race.round));

          watchers.set(wid, watcher);
        }
      }));
  };

  watchResults();

  setInterval(watchResults, 1000 * 60 * 10);
};
<commit_msg>Increase Ergast watcher timeout from 10 s to 10 m<commit_after>"use strict";

let mongoose = require("mongoose");
let net = require("../net");
let util = require("util");

let ResultSchema = new mongoose.Schema({
  season: Number,
  round: Number,
  date: Date,
  results: [{
    _id: false,
    number: Number,
    round: Number,
    code: String,
    firstName: String,
    lastName: String,
    points: Number,
  }],
});

ResultSchema.statics.latestResult = function() {
  return this.findOne().sort({season: -1, round: -1});
};

ResultSchema.statics.driverStandings = function() {
  return this.latestResult()
    .then(last => last || {})
    .then(last => this.find({season: last.season}))
    .then(races => races.reduce((standings, race) => {
      race.results.forEach(item => {
        let name = util.format("%s %s", item.firstName, item.lastName);
        let points = (standings.get(name) || 0) + item.points;
        standings.set(name, points);
      });
      return standings;
    }, new Map))
  .then(standings => [...standings.entries()].sort((a, b) => b[1] - a[1]))
  .then(entries => entries.length ? entries : Promise.reject(new Error("No points data found.")));
};

let Result = mongoose.model("result", ResultSchema);

class ErgastParser {
  parse(json) {
    let data = JSON.parse(json).MRData.RaceTable.Races[0];
    return {
      season: parseInt(data.season),
      round: parseInt(data.round),
      date: new Date(util.format("%sT%s", data.date, data.time)),
      results: data.Results.map(item => ({
        number: parseInt(item.number),
        points: parseInt(item.points),
        code: item.Driver.code,
        firstName: item.Driver.givenName,
        lastName: item.Driver.familyName,
      })),
    };
  }
}

class ErgastWatcher {
  constructor(season, round) {
    this.season = season;
    this.round = round;
    this.url = util.format("http://ergast.com/api/f1/%d/%d/results.json", season, round);
    this.interval = 1000 * 60 * 10;
  }

  watch() {
    return new Promise((resolve, reject) => {
      this.fetch().then(resolve, error => {
        console.error(error.stack);
        console.log("retry results", this.season, this.round);
        setTimeout(() => this.watch(), this.interval);
      });
    });
  }

  fetch() {
    return net.download(this.url).then(response => {
      let result = (new ErgastParser).parse(response.data);
      return result.results.length ? result : Promise.reject(new Error("Got invalid data"));
    });
  }
}

exports.configure = services => {
  let commands = services.get("command.manager");
  let database = services.get("database");
  let events = services.get("event.manager");
  let Event = database.model("event");
  let season = (new Date).getFullYear();

  commands.add("points", () => {
    return Result.driverStandings()
      .then(standings => standings.slice(0, 10).map((row, i) => util.format("%d. %s (%d)", i+1, row[0], row[1])))
      .then(standings => standings.join(" "));
  });

  let watchers = new Map;

  let watchResults = () => {
    Result.find({season: season}).sort("-round")
      .then(results => results.map(r => r.round))
      .then(rounds => Event.find({
        season: season,
        type: "race",
        end: {$lt: new Date},
        round: {$not: {$in: rounds}}
      }))
      .then(races => races.map(race => {
        let wid = util.format("%d:%d", race.season, race.round);
        if (!watchers.has(wid)) {
          let watcher = new ErgastWatcher(race.season, race.round);
          watcher.watch()
            .then(result => Result.update({season: result.season, round: result.round}, result, {upsert: true}))
            .then(() => events.emit("raceresults.result", {season: race.season, round: race.round}))
            .then(() => watchers.remove(wid))
            .then(() => console.log("Updated result for round", race.round));

          watchers.set(wid, watcher);
        }
      }));
  };

  watchResults();

  setInterval(watchResults, 1000 * 60 * 10);
};
<|endoftext|>


<commit_before>/*!
 * Abricos Platform (http://abricos.org)
 * Copyright 2008-2014 Alexander Kuzmin <roosit@abricos.org>
 * Licensed under the MIT license
 */

var Component = new Brick.Component();
Component.requires = {
    yui: ['widget']
};
Component.entryPoint = function(NS){

    var Y = Brick.YUI,
        RENDERUI = 'renderUI',
        BINDUI = 'bindUI',
        SYNCUI = 'syncUI',

        UI = Y.Widget.UI_SRC,

        BOUNDING_BOX = 'boundingBox';

    var Form = function(){
    };
    Form.ATTRS = {
        boundingBox: {
            setter: Y.one
        },
        fieldsClass: {
            value: NS.Structure
        },
        fields: {
            value: {},
            setter: function(val){
                var fieldsClass = this.get('fieldsClass');
                return new fieldsClass(val);
            }
        }
    };
    Form.NAME = 'form';
    Form.prototype = {
        initializer: function(){
            Y.after(this._bindUIForm, this, 'bindUI');
        },
        _bindUIForm: function(){
            this._syncUIFromFieldsForm();
            this._bindFieldsUIForm();
        },
        _bindFieldsUIForm: function(){
            var instance = this,
                fields = this.get('fields'),
                attrs = fields.getAttrs();

            Y.Object.each(attrs, function(v, n){
                fields.after(n + 'Change', instance._syncFieldUIForm, instance);
            }, instance);
        },
        _syncFieldUIForm: function(e){
            this._syncUIFromFieldsForm();
        },
        _syncFieldsFromUIForm: function(){
            var boundingBox = this.get(BOUNDING_BOX),
                fields = this.get('fields');

            boundingBox.all('.form-control').each(function(fieldNode){
                var name = fieldNode.get('name'),
                    value = fieldNode.get('value');

                if (fields.attrAdded(name)){
                    fields.set(name, value);
                }
            }, this);
        },
        _syncUIFromFieldsForm: function(){
            var boundingBox = this.get(BOUNDING_BOX),
                fields = this.get('fields');

            boundingBox.all('.form-control').each(function(fieldNode){
                var name = fieldNode.get('name');

                if (fields.attrAdded(name)){
                    fieldNode.set('value', fields.get(name));
                }
            }, this);
        },
        getNodeByFieldName: function(name){
            var boundingBox = this.get(BOUNDING_BOX),
                findNode = null;

            boundingBox.all('.form-control').each(function(node){
                if (node.get('name') === name){
                    findNode = node;
                }
            }, this);

            return findNode;
        }
    };
    NS.Form = Form;

    var FormAction = function(){
    };
    FormAction.prototype = {
        initializer: function(){
            Y.after(this._bindUIFormAction, this, 'bindUI');
        },
        _bindUIFormAction: function(){
            var boundingBox = this.get(BOUNDING_BOX);
            boundingBox.on({
                reset: Y.bind(this._onResetFormAction, this),
                submit: Y.bind(this._onSubmitFormAction, this)
            });

            this.publish({
                resetForm: this._defResetFormAction,
                submitForm: this._defSubmitFormAction
            });
        },
        _onResetFormAction: function(e){
            this.fire('resetForm');
        },
        _onSubmitFormAction: function(e){

            e.halt();

            this._syncFieldsFromUIForm();
            var res = this.fire('submitForm');
            if (!res){
                e.halt();
            }
        },
        _defResetFormAction: function(e){
        },
        _defSubmitFormAction: function(e){
        }
    };
    NS.FormAction = FormAction;

    var WAITING = 'waiting';

    var Waiting = function(){
    };
    Waiting.ATTRS = {
        waiting: {
            value: false
        }
    };
    Waiting.WAITING_CLASS_NAME = Y.Widget.getClassName(WAITING);
    Waiting.prototype = {
        initializer: function(){
            Y.after(this._syncUIWaiting, this, SYNCUI);
            Y.after(this._bindUIWaiting, this, BINDUI);
        },
        _syncUIWaiting: function(){
            this._uiSetWaiting(this.get('waiting'));
        },
        _bindUIWaiting: function(){
            this.after('waitingChange', this._afterWaitingChange);
        },
        _afterWaitingChange: function(e){
            if (e.src != UI){
                this._uiSetWaiting(e.newVal);
            }
        },
        _uiSetWaiting: function(val){
            var boundingBox = this.get(BOUNDING_BOX);
            boundingBox.all('[data-wait]').each(function(node){
                var flag = node.getData('wait');
                switch (flag) {
                    case 'show':
                        node.setStyle('display', val ? '' : 'none');
                        break;
                    case 'hide':
                        node.setStyle('display', val ? 'none' : '');
                        break;
                    case 'disable':
                        node.set('disabled', val);
                        break;
                    case 'enable':
                        node.set('disabled', !val);
                        break;
                }
            }, this);
        }
    };
    NS.WidgetWaiting = Waiting;

};<commit_msg>bugfix: submit form provider<commit_after>/*!
 * Abricos Platform (http://abricos.org)
 * Copyright 2008-2014 Alexander Kuzmin <roosit@abricos.org>
 * Licensed under the MIT license
 */

var Component = new Brick.Component();
Component.requires = {
    yui: ['widget']
};
Component.entryPoint = function(NS){

    var Y = Brick.YUI,
        RENDERUI = 'renderUI',
        BINDUI = 'bindUI',
        SYNCUI = 'syncUI',

        UI = Y.Widget.UI_SRC,

        BOUNDING_BOX = 'boundingBox';

    var Form = function(){
    };
    Form.ATTRS = {
        boundingBox: {
            setter: Y.one
        },
        fieldsClass: {
            value: NS.Structure
        },
        fields: {
            value: {},
            setter: function(val){
                var fieldsClass = this.get('fieldsClass');
                return new fieldsClass(val);
            }
        }
    };
    Form.NAME = 'form';
    Form.prototype = {
        initializer: function(){
            Y.after(this._bindUIForm, this, 'bindUI');
        },
        _bindUIForm: function(){
            this._syncUIFromFieldsForm();
            this._bindFieldsUIForm();
        },
        _bindFieldsUIForm: function(){
            var instance = this,
                fields = this.get('fields'),
                attrs = fields.getAttrs();

            Y.Object.each(attrs, function(v, n){
                fields.after(n + 'Change', instance._afterFieldChange, instance);
            }, instance);
        },
        _afterFieldChange: function(e){
            if (!this._filedsEventDisabled){
                this._syncUIFromFieldsForm();
            }
        },
        _syncFieldsFromUIForm: function(){

            this._filedsEventDisabled = true;

            var boundingBox = this.get(BOUNDING_BOX),
                fields = this.get('fields');

            boundingBox.all('.form-control').each(function(fieldNode){
                var name = fieldNode.get('name'),
                    value = fieldNode.get('value');

                if (fields.attrAdded(name)){
                    fields.set(name, value);
                }
            }, this);

            this._filedsEventDisabled = false;
        },
        _syncUIFromFieldsForm: function(){

            var boundingBox = this.get(BOUNDING_BOX),
                fields = this.get('fields');

            boundingBox.all('.form-control').each(function(fieldNode){
                var name = fieldNode.get('name');

                if (fields.attrAdded(name)){
                    fieldNode.set('value', fields.get(name));
                }
            }, this);
        },
        getNodeByFieldName: function(name){
            var boundingBox = this.get(BOUNDING_BOX),
                findNode = null;

            boundingBox.all('.form-control').each(function(node){
                if (node.get('name') === name){
                    findNode = node;
                }
            }, this);

            return findNode;
        }
    };
    NS.Form = Form;

    var FormAction = function(){
    };
    FormAction.prototype = {
        initializer: function(){
            Y.after(this._bindUIFormAction, this, 'bindUI');
        },
        _bindUIFormAction: function(){
            var boundingBox = this.get(BOUNDING_BOX);
            boundingBox.on({
                reset: Y.bind(this._onResetFormAction, this),
                submit: Y.bind(this._onSubmitFormAction, this)
            });

            this.publish({
                resetForm: this._defResetFormAction,
                submitForm: this._defSubmitFormAction
            });
        },
        _onResetFormAction: function(e){
            this.fire('resetForm');
        },
        _onSubmitFormAction: function(e){
            e.halt();

            this._syncFieldsFromUIForm();
            var res = this.fire('submitForm');
            if (!res){
                e.halt();
            }
        },
        _defResetFormAction: function(e){
        },
        _defSubmitFormAction: function(e){
        }
    };
    NS.FormAction = FormAction;

    var WAITING = 'waiting';

    var Waiting = function(){
    };
    Waiting.ATTRS = {
        waiting: {
            value: false
        }
    };
    Waiting.WAITING_CLASS_NAME = Y.Widget.getClassName(WAITING);
    Waiting.prototype = {
        initializer: function(){
            Y.after(this._syncUIWaiting, this, SYNCUI);
            Y.after(this._bindUIWaiting, this, BINDUI);
        },
        _syncUIWaiting: function(){
            this._uiSetWaiting(this.get('waiting'));
        },
        _bindUIWaiting: function(){
            this.after('waitingChange', this._afterWaitingChange);
        },
        _afterWaitingChange: function(e){
            if (e.src != UI){
                this._uiSetWaiting(e.newVal);
            }
        },
        _uiSetWaiting: function(val){
            var boundingBox = this.get(BOUNDING_BOX);
            boundingBox.all('[data-wait]').each(function(node){
                var flag = node.getData('wait');
                switch (flag) {
                    case 'show':
                        node.setStyle('display', val ? '' : 'none');
                        break;
                    case 'hide':
                        node.setStyle('display', val ? 'none' : '');
                        break;
                    case 'disable':
                        node.set('disabled', val);
                        break;
                    case 'enable':
                        node.set('disabled', !val);
                        break;
                }
            }, this);
        }
    };
    NS.WidgetWaiting = Waiting;

};<|endoftext|>


<commit_before>// Copied from Vuex’s util.js
function deepClone (obj) {
  if (Array.isArray(obj)) {
    return obj.map(deepClone)
  } else if (obj && typeof obj === 'object') {
    var cloned = {}
    var keys = Object.keys(obj)
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i]
      cloned[key] = deepClone(obj[key])
    }
    return cloned
  } else {
    return obj
  }
}

module.exports = {
  data () {
    return {
      search: '',
      isOpen: false,
      value: [],
      loading: false
    }
  },
  props: {
    /**
     * Array of available options: Objects, Strings or Integers.
     * If array of objects, visible label will default to option.label.
     * If `labal` prop is passed, label will equal option['label']
     * @type {Array}
     */
    options: {
      type: Array,
      required: true
    },
    /**
     * Equivalent to the `multiple` attribute on a `<select>` input.
     * @default false
     * @type {Boolean}
     */
    multiple: {
      type: Boolean,
      default: false
    },
    /**
     * Required. Presets the selected options. Add `.sync` to
     * update parent value. If this.onChange callback is present,
     * this will not update. In that case, the parent is responsible
     * for updating this value.
     * @type {Object||Array||String||Integer}
     */
    selected: {
      required: true
    },
    /**
     * Key to compare objects
     * @default 'id'
     * @type {String}
     */
    key: {
      type: String,
      default: false
    },
    /**
     * Label to look for in option Object
     * @default 'label'
     * @type {String}
     */
    label: {
      type: String,
      default: false
    },
    /**
     * Enable/disable search in options
     * @default true
     * @type {Boolean}
     */
    searchable: {
      type: Boolean,
      default: true
    },
    /**
     * Clear the search input after select()
     * @default true
     * @type {Boolean}
     */
    clearOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Hide already selected options
     * @default false
     * @type {Boolean}
     */
    hideSelected: {
      type: Boolean,
      default: false
    },
    /**
     * Equivalent to the `placeholder` attribute on a `<select>` input.
     * @default 'Select option'
     * @type {String}
     */
    placeholder: {
      type: String,
      default: 'Select option'
    },
    /**
     * Sets maxHeight style value of the dropdown
     * @default 300
     * @type {Integer}
     */
    maxHeight: {
      type: Number,
      default: 300
    },
    /**
     * Allow to remove all selected values
     * @default true
     * @type {Boolean}
     */
    allowEmpty: {
      type: Boolean,
      default: true
    },
    /**
     * Callback function to call after this.value changes
     * @callback onChange
     * @default false
     * @param {Array||Object||String||Integer} Current this.value
     * @param {Integer} $index of current selection
     * @type {Function}
     */
    onChange: {
      type: Function,
      default: false
    },
    /**
     * Callback function to call after this.search changes
     * @callback onSearchChange
     * @default false
     * @param {String} Pass current search String
     * @type {Function}
     */
    onSearchChange: {
      type: Function,
      default: false
    },
    /**
     * Value that indicates if the dropdown has been used.
     * Useful for validation.
     * @default false
     * @type {Boolean}
     */
    touched: {
      type: Boolean,
      default: false
    },
    /**
     * Reset this.value, this.search, this.selected after this.value changes.
     * Useful if want to create a stateless dropdown, that fires the this.onChange
     * callback function with different params.
     * @default false
     * @type {Boolean}
     */
    resetAfter: {
      type: Boolean,
      default: false
    },
    /**
     * Enable/disable closing after selecting an option
     * @default true
     * @type {Boolean}
     */
    closeOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Function to interpolate the custom label
     * @default false
     * @type {Function}
     */
    customLabel: {
      type: Function,
      default: false
    },
    /**
     * Disable / Enable tagging
     * @default false
     * @type {Boolean}
     */
    taggable: {
      type: Boolean,
      default: false
    },
    /**
     * Callback function to run when attemting to add a tag
     * @default suitable for primitive values
     * @param {String} Tag string to build a tag
     * @type {Function}
     */
    onTag: {
      type: Function,
      default: function (tag) {
        this.options.push(tag)
        this.value.push(tag)
      }
    },
    /**
     * String to show when highlighting a potential tag
     * @default 'Press enter to create a tag'
     * @type {String}
    */
    tagPlaceholder: {
      type: String,
      default: 'Press enter to create a tag'
    },
    /**
     * Number of allowed selected options. No limit if false.
     * @default False
     * @type {Number}
    */
    max: {
      type: Number,
      default: false
    }
  },
  created () {
    if (!this.selected) {
      this.$set('value', this.multiple ? [] : null)
    } else {
      this.value = deepClone(this.selected)
    }
    if (this.searchable && !this.multiple) {
      this.search = this.getOptionLabel(this.value)
    }
  },
  computed: {
    filteredOptions () {
      var options = this.hideSelected
        ? this.options.filter(this.isNotSelected)
        : this.options
      options = this.$options.filters.filterBy(options, this.search)
      if (this.taggable && this.search.length > 0 && !this.isExistingOption(this.search)) {
        options.unshift({ isTag: true, label: this.search })
      }
      return options
    },
    valueKeys () {
      if (this.key) {
        return this.multiple
          ? this.value.map(element => element[this.key])
          : this.value[this.key]
      } else {
        return this.value
      }
    },
    optionKeys () {
      return this.label
        ? this.options.map(element => element[this.label])
        : this.options
    }
  },
  watch: {
    'value' () {
      if (this.onChange && JSON.stringify(this.value) !== JSON.stringify(this.selected)) {
        this.onChange(deepClone(this.value))
      } else {
        this.$set('selected', deepClone(this.value))
      }
      if (this.resetAfter) {
        this.$set('value', null)
        this.$set('search', null)
        this.$set('selected', null)
      }
      if (!this.multiple && this.searchable && this.clearOnSelect) {
        this.search = this.getOptionLabel(this.value)
      }
    },
    'search' () {
      if (this.onSearchChange) {
        this.onSearchChange(this.search)
        this.loading = true
      }
    },
    'options' () {
      this.onSearchChange && (this.loading = false)
    },
    'selected' (newVal, oldVal) {
      if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
        this.value = deepClone(this.selected)
      }
    }
  },
  methods: {
    /**
     * Finds out if the given query is already present
     * in the available options
     * @param  {String}
     * @returns {Boolean} returns true if element is available
     */
    isExistingOption (query) {
      return !this.options
        ? false
        : this.optionKeys.indexOf(query) > -1
    },
    /**
     * Finds out if the given element is already present
     * in the result value
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is selected
     */
    isSelected (option) {
      /* istanbul ignore else */
      if (!this.value) return false
      const opt = this.key
        ? option[this.key]
        : option

      if (this.multiple) {
        return this.valueKeys.indexOf(opt) > -1
      } else {
        return this.valueKeys === opt
      }
    },
    /**
     * Finds out if the given element is NOT already present
     * in the result value. Negated isSelected method.
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is not selected
     */
    isNotSelected (option) {
      return !this.isSelected(option)
    },
    /**
     * Returns the option[this.label]
     * if option is Object. Otherwise check for option.label.
     * If non is found, return entrie option.
     *
     * @param  {Object||String||Integer} Passed option
     * @returns {Object||String}
     */
    getOptionLabel (option) {
      if (typeof option === 'object' && option !== null) {
        if (this.customLabel) {
          return this.customLabel(option)
        } else {
          if (this.label && option[this.label]) {
            return option[this.label]
          } else if (option.label) {
            return option.label
          }
        }
      } else {
        return option
      }
    },
    /**
     * Add the given option to the list of selected options
     * or sets the option as the selected option.
     * If option is already selected -> remove it from the results.
     *
     * @param  {Object||String||Integer} option to select/deselect
     */
    select (option) {
      if (this.max && this.multiple && this.value.length === this.max) return
      if (option.isTag) {
        this.onTag(option.label)
        this.search = ''
      } else {
        if (this.multiple) {
          if (!this.isNotSelected(option)) {
            this.removeElement(option)
          } else {
            this.value.push(option)
            if (this.clearOnSelect) { this.search = '' }
          }
        } else {
          this.$set('value',
            !this.isNotSelected(option) && this.allowEmpty
              ? null
              : option
          )
          if (this.closeOnSelect) {
            this.searchable
              ? this.$els.search.blur()
              : this.$el.blur()
          }
        }
      }
    },
    /**
     * Removes the given option from the selected options.
     * Additionally checks this.allowEmpty prop if option can be removed when
     * it is the last selected option.
     *
     * @param  {type} option description
     * @returns {type}        description
     */
    removeElement (option) {
      /* istanbul ignore else */
      if (this.allowEmpty || this.value.length > 1) {
        if (this.multiple && typeof option === 'object') {
          const index = this.valueKeys.indexOf(option[this.key])
          this.value.splice(index, 1)
        } else {
          this.value.$remove(option)
        }
      }
    },
    /**
     * Calls this.removeElement() with the last element
     * from this.value (selected element Array)
     *
     * @fires this#removeElement
     */
    removeLastElement () {
      /* istanbul ignore else */
      if (this.search.length === 0 && Array.isArray(this.value)) {
        this.removeElement(this.value[this.value.length - 1])
      }
    },
    /**
     * Opens the multiselect’s dropdown.
     * Sets this.isOpen to TRUE
     */
    activate () {
      /* istanbul ignore else */
      if (!this.isOpen) {
        this.isOpen = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.search = ''
          this.$els.search.focus()
        } else {
          this.$el.focus()
        }
      }
    },
    /**
     * Closes the multiselect’s dropdown.
     * Sets this.isOpen to FALSE
     */
    deactivate () {
      /* istanbul ignore else */
      if (this.isOpen) {
        this.isOpen = false
        this.touched = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.$els.search.blur()
          this.search = this.multiple
            ? ''
            : this.getOptionLabel(this.value)
        } else {
          this.$el.blur()
        }
      }
    },
    /**
     * Call this.activate() or this.deactivate()
     * depending on this.isOpen value.
     *
     * @fires this#activate || this#deactivate
     * @property {Boolean} isOpen indicates if dropdown is open
     */
    toggle () {
      this.isOpen
        ? this.deactivate()
        : this.activate()
    }
  }
}
<commit_msg>Null search doesn't throw exception.<commit_after>// Copied from Vuex’s util.js
function deepClone (obj) {
  if (Array.isArray(obj)) {
    return obj.map(deepClone)
  } else if (obj && typeof obj === 'object') {
    var cloned = {}
    var keys = Object.keys(obj)
    for (var i = 0, l = keys.length; i < l; i++) {
      var key = keys[i]
      cloned[key] = deepClone(obj[key])
    }
    return cloned
  } else {
    return obj
  }
}

module.exports = {
  data () {
    return {
      search: '',
      isOpen: false,
      value: [],
      loading: false
    }
  },
  props: {
    /**
     * Array of available options: Objects, Strings or Integers.
     * If array of objects, visible label will default to option.label.
     * If `labal` prop is passed, label will equal option['label']
     * @type {Array}
     */
    options: {
      type: Array,
      required: true
    },
    /**
     * Equivalent to the `multiple` attribute on a `<select>` input.
     * @default false
     * @type {Boolean}
     */
    multiple: {
      type: Boolean,
      default: false
    },
    /**
     * Required. Presets the selected options. Add `.sync` to
     * update parent value. If this.onChange callback is present,
     * this will not update. In that case, the parent is responsible
     * for updating this value.
     * @type {Object||Array||String||Integer}
     */
    selected: {
      required: true
    },
    /**
     * Key to compare objects
     * @default 'id'
     * @type {String}
     */
    key: {
      type: String,
      default: false
    },
    /**
     * Label to look for in option Object
     * @default 'label'
     * @type {String}
     */
    label: {
      type: String,
      default: false
    },
    /**
     * Enable/disable search in options
     * @default true
     * @type {Boolean}
     */
    searchable: {
      type: Boolean,
      default: true
    },
    /**
     * Clear the search input after select()
     * @default true
     * @type {Boolean}
     */
    clearOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Hide already selected options
     * @default false
     * @type {Boolean}
     */
    hideSelected: {
      type: Boolean,
      default: false
    },
    /**
     * Equivalent to the `placeholder` attribute on a `<select>` input.
     * @default 'Select option'
     * @type {String}
     */
    placeholder: {
      type: String,
      default: 'Select option'
    },
    /**
     * Sets maxHeight style value of the dropdown
     * @default 300
     * @type {Integer}
     */
    maxHeight: {
      type: Number,
      default: 300
    },
    /**
     * Allow to remove all selected values
     * @default true
     * @type {Boolean}
     */
    allowEmpty: {
      type: Boolean,
      default: true
    },
    /**
     * Callback function to call after this.value changes
     * @callback onChange
     * @default false
     * @param {Array||Object||String||Integer} Current this.value
     * @param {Integer} $index of current selection
     * @type {Function}
     */
    onChange: {
      type: Function,
      default: false
    },
    /**
     * Callback function to call after this.search changes
     * @callback onSearchChange
     * @default false
     * @param {String} Pass current search String
     * @type {Function}
     */
    onSearchChange: {
      type: Function,
      default: false
    },
    /**
     * Value that indicates if the dropdown has been used.
     * Useful for validation.
     * @default false
     * @type {Boolean}
     */
    touched: {
      type: Boolean,
      default: false
    },
    /**
     * Reset this.value, this.search, this.selected after this.value changes.
     * Useful if want to create a stateless dropdown, that fires the this.onChange
     * callback function with different params.
     * @default false
     * @type {Boolean}
     */
    resetAfter: {
      type: Boolean,
      default: false
    },
    /**
     * Enable/disable closing after selecting an option
     * @default true
     * @type {Boolean}
     */
    closeOnSelect: {
      type: Boolean,
      default: true
    },
    /**
     * Function to interpolate the custom label
     * @default false
     * @type {Function}
     */
    customLabel: {
      type: Function,
      default: false
    },
    /**
     * Disable / Enable tagging
     * @default false
     * @type {Boolean}
     */
    taggable: {
      type: Boolean,
      default: false
    },
    /**
     * Callback function to run when attemting to add a tag
     * @default suitable for primitive values
     * @param {String} Tag string to build a tag
     * @type {Function}
     */
    onTag: {
      type: Function,
      default: function (tag) {
        this.options.push(tag)
        this.value.push(tag)
      }
    },
    /**
     * String to show when highlighting a potential tag
     * @default 'Press enter to create a tag'
     * @type {String}
    */
    tagPlaceholder: {
      type: String,
      default: 'Press enter to create a tag'
    },
    /**
     * Number of allowed selected options. No limit if false.
     * @default False
     * @type {Number}
    */
    max: {
      type: Number,
      default: false
    }
  },
  created () {
    if (!this.selected) {
      this.$set('value', this.multiple ? [] : null)
    } else {
      this.value = deepClone(this.selected)
    }
    if (this.searchable && !this.multiple) {
      this.search = this.getOptionLabel(this.value)
    }
  },
  computed: {
    filteredOptions () {
      let search = this.search ? this.search : ''
      var options = this.hideSelected
        ? this.options.filter(this.isNotSelected)
        : this.options
      options = this.$options.filters.filterBy(options, search)
      if (this.taggable && search > 0 && !this.isExistingOption(search)) {
        options.unshift({ isTag: true, label: search })
      }
      return options
    },
    valueKeys () {
      if (this.key) {
        return this.multiple
          ? this.value.map(element => element[this.key])
          : this.value[this.key]
      } else {
        return this.value
      }
    },
    optionKeys () {
      return this.label
        ? this.options.map(element => element[this.label])
        : this.options
    }
  },
  watch: {
    'value' () {
      if (this.onChange && JSON.stringify(this.value) !== JSON.stringify(this.selected)) {
        this.onChange(deepClone(this.value))
      } else {
        this.$set('selected', deepClone(this.value))
      }
      if (this.resetAfter) {
        this.$set('value', null)
        this.$set('search', null)
        this.$set('selected', null)
      }
      if (!this.multiple && this.searchable && this.clearOnSelect) {
        this.search = this.getOptionLabel(this.value)
      }
    },
    'search' () {
      if (this.onSearchChange) {
        this.onSearchChange(this.search)
        this.loading = true
      }
    },
    'options' () {
      this.onSearchChange && (this.loading = false)
    },
    'selected' (newVal, oldVal) {
      if (JSON.stringify(newVal) !== JSON.stringify(oldVal)) {
        this.value = deepClone(this.selected)
      }
    }
  },
  methods: {
    /**
     * Finds out if the given query is already present
     * in the available options
     * @param  {String}
     * @returns {Boolean} returns true if element is available
     */
    isExistingOption (query) {
      return !this.options
        ? false
        : this.optionKeys.indexOf(query) > -1
    },
    /**
     * Finds out if the given element is already present
     * in the result value
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is selected
     */
    isSelected (option) {
      /* istanbul ignore else */
      if (!this.value) return false
      const opt = this.key
        ? option[this.key]
        : option

      if (this.multiple) {
        return this.valueKeys.indexOf(opt) > -1
      } else {
        return this.valueKeys === opt
      }
    },
    /**
     * Finds out if the given element is NOT already present
     * in the result value. Negated isSelected method.
     * @param  {Object||String||Integer} option passed element to check
     * @returns {Boolean} returns true if element is not selected
     */
    isNotSelected (option) {
      return !this.isSelected(option)
    },
    /**
     * Returns the option[this.label]
     * if option is Object. Otherwise check for option.label.
     * If non is found, return entrie option.
     *
     * @param  {Object||String||Integer} Passed option
     * @returns {Object||String}
     */
    getOptionLabel (option) {
      if (typeof option === 'object' && option !== null) {
        if (this.customLabel) {
          return this.customLabel(option)
        } else {
          if (this.label && option[this.label]) {
            return option[this.label]
          } else if (option.label) {
            return option.label
          }
        }
      } else {
        return option
      }
    },
    /**
     * Add the given option to the list of selected options
     * or sets the option as the selected option.
     * If option is already selected -> remove it from the results.
     *
     * @param  {Object||String||Integer} option to select/deselect
     */
    select (option) {
      if (this.max && this.multiple && this.value.length === this.max) return
      if (option.isTag) {
        this.onTag(option.label)
        this.search = ''
      } else {
        if (this.multiple) {
          if (!this.isNotSelected(option)) {
            this.removeElement(option)
          } else {
            this.value.push(option)
            if (this.clearOnSelect) { this.search = '' }
          }
        } else {
          this.$set('value',
            !this.isNotSelected(option) && this.allowEmpty
              ? null
              : option
          )
          if (this.closeOnSelect) {
            this.searchable
              ? this.$els.search.blur()
              : this.$el.blur()
          }
        }
      }
    },
    /**
     * Removes the given option from the selected options.
     * Additionally checks this.allowEmpty prop if option can be removed when
     * it is the last selected option.
     *
     * @param  {type} option description
     * @returns {type}        description
     */
    removeElement (option) {
      /* istanbul ignore else */
      if (this.allowEmpty || this.value.length > 1) {
        if (this.multiple && typeof option === 'object') {
          const index = this.valueKeys.indexOf(option[this.key])
          this.value.splice(index, 1)
        } else {
          this.value.$remove(option)
        }
      }
    },
    /**
     * Calls this.removeElement() with the last element
     * from this.value (selected element Array)
     *
     * @fires this#removeElement
     */
    removeLastElement () {
      /* istanbul ignore else */
      if (this.search.length === 0 && Array.isArray(this.value)) {
        this.removeElement(this.value[this.value.length - 1])
      }
    },
    /**
     * Opens the multiselect’s dropdown.
     * Sets this.isOpen to TRUE
     */
    activate () {
      /* istanbul ignore else */
      if (!this.isOpen) {
        this.isOpen = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.search = ''
          this.$els.search.focus()
        } else {
          this.$el.focus()
        }
      }
    },
    /**
     * Closes the multiselect’s dropdown.
     * Sets this.isOpen to FALSE
     */
    deactivate () {
      /* istanbul ignore else */
      if (this.isOpen) {
        this.isOpen = false
        this.touched = true
        /* istanbul ignore else  */
        if (this.searchable) {
          this.$els.search.blur()
          this.search = this.multiple
            ? ''
            : this.getOptionLabel(this.value)
        } else {
          this.$el.blur()
        }
      }
    },
    /**
     * Call this.activate() or this.deactivate()
     * depending on this.isOpen value.
     *
     * @fires this#activate || this#deactivate
     * @property {Boolean} isOpen indicates if dropdown is open
     */
    toggle () {
      this.isOpen
        ? this.deactivate()
        : this.activate()
    }
  }
}
<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');
const logger = require('winston');

let spawnSync = require('npm-run').spawnSync;

class TaskRunner {
  constructor(task, vars) {
    this.task = task;
    this.vars = vars;
  }

  execute() {
    _.forEach(this.task, command => this.runCommand(command));
  }

  runCommand(command) {
    if(!command.cmd) throw new Error('Command not defined')
    const parsedCommand = this.expandTokens(command.cmd).split(" ");
    const parsedEnv = this.resolveKeyValuePairs(command.environment);
    const spawnOptions = this.buildSpawnOptions(command, parsedEnv)

    logger.info(`Executing command : ${parsedCommand.join(" ")}`);

    const result = spawnSync(parsedCommand[0], _.tail(parsedCommand), spawnOptions);

    if (command.register) {
      this.vars[command.register] = result.stdout;
    }

    if(this.shouldExecutionContinue(result, command)) {
      return true;
    }
    throw result.error;
  }

  shouldExecutionContinue(result, command) {
    return result.status === 0 || command.ignore_errors;
  }

  expandTokens(command) {
    const template = _.template(command);

    return template(this.vars);
  }

  resolveKeyValuePairs(array) {
    const templated = _.map(array, a => this.expandTokens(a, this.vars));
    const split = _.map(templated, a => a.split('='));
    const hashed = _.fromPairs(split);
    return hashed;
  }

  buildSpawnOptions(command, envOptions) {
    const stdio = command.register ? "pipe" : "inherit";
    const env = _.isEmpty(envOptions) ? process.env : _.merge(envOptions, process.env);
    return {
      stdio: stdio,
      env: env
    };
  }
};

module.exports = TaskRunner;
<commit_msg>Logged captured variables<commit_after>'use strict';

const _ = require('lodash');
const logger = require('winston');

let spawnSync = require('npm-run').spawnSync;

class TaskRunner {
  constructor(task, vars) {
    this.task = task;
    this.vars = vars;
  }

  execute() {
    _.forEach(this.task, command => this.runCommand(command));
  }

  runCommand(command) {
    if(!command.cmd) throw new Error('Command not defined')
    const parsedCommand = this.expandTokens(command.cmd).split(" ");
    const parsedEnv = this.resolveKeyValuePairs(command.environment);
    const spawnOptions = this.buildSpawnOptions(command, parsedEnv)

    logger.info(`Executing command : ${parsedCommand.join(" ")}`);

    const result = spawnSync(parsedCommand[0], _.tail(parsedCommand), spawnOptions);

    if (command.register && result.stdout) {
      const out = result.stdout.toString().trim();
      logger.info(`Saved ${out} to ${command.register}`);
      this.vars[command.register] = out;
    }

    if(this.shouldExecutionContinue(result, command)) {
      return true;
    }
    throw result.error;
  }

  shouldExecutionContinue(result, command) {
    return result.status === 0 || command.ignore_errors;
  }

  expandTokens(command) {
    const template = _.template(command);

    return template(this.vars);
  }

  resolveKeyValuePairs(array) {
    const templated = _.map(array, a => this.expandTokens(a, this.vars));
    const split = _.map(templated, a => a.split('='));
    const hashed = _.fromPairs(split);
    return hashed;
  }

  buildSpawnOptions(command, envOptions) {
    const stdio = command.register ? "pipe" : "inherit";
    const env = _.isEmpty(envOptions) ? process.env : _.merge(envOptions, process.env);
    return {
      stdio: stdio,
      env: env
    };
  }
};

module.exports = TaskRunner;
<|endoftext|>