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

module.exports = ChatMessage;

/**
 * ChatMessage Constructor
 * @param {String|Object} message content of ChatMessage
 */
function ChatMessage(message) {
  if(typeof message === 'string') {
    this.json = {text: message};
  } else if(typeof message === 'object' && !Array.isArray(message)) {
    this.json = message;
  } else {
    throw new Error('Expected String or Object for Message argument');
  }
  this.parse();
}

/**
 * Parses the this.json property to decorate the properties of the ChatMessage.
 * Called by the Constructor
 * @return {void}
 */
ChatMessage.prototype.parse = function() {
  var json = this.json;
  // Message scope for callback functions
  var that = this;

  // There is EITHER, a text property or a translate property
  // If there is no translate property, there is no with property
  // HOWEVER! If there is a translate property, there may not be a with property
  if(typeof json.text === 'string') {
    this.text = json.text;
  } else if(typeof json.translate === 'string') {
    this.translate = json.translate;
    if(typeof json.with === 'object' || Array.isArray(json.with)) {
      this.with = [];
      json.with.forEach(function(entry) {
        if(typeof entry === 'string') {
          that.with.push(entry);
        } else if(typeof entry === 'object') {
          // Parse ChatMessage
          var subChatMessage = new ChatMessage(entry);
          that.with.push(subChatMessage);
        }
      });
    }
  }
  // Parse extra property
  // Extras are appended to the initial text
  if(typeof json.extra === 'object') {
    if(!Array.isArray(json.extra)) {
      throw new Error('Expected extra property to be an Array in ChatMessage');
    }
    this.extra = [];
    json.extra.forEach(function(entry) {
      if(typeof entry === 'string') {
        that.extra.push(entry);
      } else if(typeof entry === 'object') {
        var subChatMessage = new ChatMessage(entry);
        that.extra.push(subChatMessage);
      }
    });
  }
  // Text modifiers
  this.bold = json.bold;
  this.italic = json.italic;
  this.underlined = json.underlined;
  this.strikethrough = json.strikethrough;
  this.obfuscated = json.obfuscated;

  // Supported constants @ 2014-04-21
  var supportedColors = ['black', 'dark_blue', 'dark_green', 'dark_aqua', 'dark_red', 'dark_purple',
    'gold', 'gray', 'dark_gray', 'blue', 'green', 'aqua', 'red', 'light_purple',
    'yellow', 'white', 'obfuscated', 'bold', 'strikethrough', 'underlined', 'italic',
    'reset'];
  var supportedClick = ['open_url', 'open_file', 'run_command', 'suggest_command'];
  var supportedHover = ['show_text', 'show_achievement', 'show_item', 'show_entity'];

  // Parse color
  this.color = json.color;
  switch(this.color) {
    case 'obfuscated':
      this.obfuscated = true;
      this.color = null;
      break;
    case 'bold':
      this.bold = true;
      this.color = null;
      break;
    case 'strikethrough':
      this.strikethrough = true;
      this.color = null;
      break;
    case 'underlined':
      this.underlined = true;
      this.color = null;
      break;
    case 'italic':
      this.italic = true;
      this.color = null;
      break;
    case 'reset':
      this.reset = true;
      this.color = null;
      break;
  }
  if(Array.prototype.indexOf && this.color &&
    supportedColors.indexOf(this.color) === -1) {
    console.warn('ChatMessage parsed with unsupported color', this.color);
  }

  // Parse click event
  if(typeof json.clickEvent === 'object') {
    this.clickEvent = json.clickEvent;
    if(typeof this.clickEvent.action !== 'string') {
      throw new Error('ClickEvent action missing in ChatMessage');
    } else if(Array.prototype.indexOf && supportedClick.indexOf(this.clickEvent.action) === -1) {
      console.warn('ChatMessage parsed with unsupported clickEvent', this.clickEvent.action);
    }
  }

  // Parse hover event
  if(typeof json.hoverEvent === 'object') {
    this.hoverEvent = json.hoverEvent;
    if(typeof this.hoverEvent.action !== 'string') {
      throw new Error('HoverEvent action missing in ChatMessage');
    } else if(Array.prototype.indexOf && supportedHover.indexOf(this.hoverEvent.action) === -1) {
      console.warn('ChatMessage parsed with unsupported hoverEvent', this.hoverEvent.action);
    }
    // Special case
    if(this.hoverEvent.action === 'show_item') {
      let content;
      if(this.hoverEvent.value instanceof Array) {
        if(this.hoverEvent.value[0] instanceof Object)
          content=this.hoverEvent.value[0].text;
        else
          content=this.hoverEvent.value[0];
      }
      else {
        if(this.hoverEvent.value instanceof Object)
          content=this.hoverEvent.value.text;
        else
          content=this.hoverEvent.value;
      }
      this.hoverEvent.value = mojangson.parse(content);
    }
  }
};

/**
 * Returns the count of text extras and child ChatMessages
 * Does not count recursively in to the children
 * @return {Number}
 */
ChatMessage.prototype.length = function() {
  var count = 0;
  if(this.text) count++;
  else if(this.translate) count += this.with.length;

  if(this.extra) count += this.extra.length;
  return count;
};

/**
 * Returns a text part from the message
 * @param  {Number} idx Index of the part
 * @return {String}
 */
ChatMessage.prototype.getText = function(idx) {
  // If the index is not defined is is invalid, return toString
  if(typeof idx !== 'number') return this.toString();
  // If we are not a translating message, return the text
  if(this.text && idx === 0) return this.text;
  // Else return the with child if it's in range
  else if(this.with.length > idx) return this.with[idx].toString();
  // Else return the extra if it's in range
  if(this.extra && this.extra.length + (this.text ? 1 : this.with.length) > idx)
    return this.extra[idx - (this.text ? 1 : this.with.length)].toString();
  // Not sure how you want to default this
  // Undefined, an error ?
  return "";
};

/**
 * Flattens the message in to plain-text
 * @return {String}
 */
ChatMessage.prototype.toString = function() {
  var message = "";
  if(typeof this.text === 'string') message += this.text + " ";
  else if(this.with) {
    this.with.forEach(function(entry) {
      message += entry.toString() + " ";
    })
  }
  if(this.extra) {
    this.extra.forEach(function(entry) {
      message += entry.toString() + " ";
    })
  }
  return message.trim();
};

ChatMessage.prototype.toMotd = function(motd) {
  var codes = {
    color:{
      black: '§0',
      dark_blue: '§1',
      dark_green: '§2',
      dark_aqua: '§3',
      dark_red: '§4',
      dark_purple: '§5',
      gold: '§6',
      gray: '§7',
      dark_gray: '§8',
      blue: '§9',
      green: '§a',
      aqua: '§b',
      red: '§c',
      light_purple: '§d',
      yellow: '§e',
      white: '§f',
    },
    bold: '§l',
    italic: '§o',
    underlined: '§n',
    strikethrough: '§m',
    obfuscated : '§k'
  }

  var message = Object.keys(codes).map((function (code) {
    if (!this[code] || this[code] === 'false') return null;
    if (code === 'color') return codes.color[this.color];
    return codes[code];
  }).bind(this)).join('');

  if(typeof this.text === 'string') message += this.text + "§r ";
  else if(this.with) {
    this.with.forEach(function(entry) {
      message += entry.toMotd() + " ";
    })
  }
  if(this.extra) {
    this.extra.forEach(function(entry) {
      message += entry.toMotd() + " ";
    })
  }
  return message.trim();
};

ChatMessage.prototype.toAnsi = function(motd) {
  var codes = {
    '§0': '\u001b[30m',
    '§1': '\u001b[34m',
    '§2': '\u001b[32m',
    '§3': '\u001b[36m',
    '§4': '\u001b[31m',
    '§5': '\u001b[35m',
    '§6': '\u001b[33m',
    '§7': '\u001b[37m',
    '§8': '\u001b[90m',
    '§9': '\u001b[94m',
    '§a': '\u001b[92m',
    '§b': '\u001b[96m',
    '§c': '\u001b[91m',
    '§d': '\u001b[95m',
    '§e': '\u001b[93m',
    '§f': '\u001b[97m',
    '§l': '\u001b[1m',
    '§o': '\u001b[3m',
    '§n': '\u001b[4m',
    '§m': '\u001b[9m',
    '§k': '\u001b[6m',
    '§r': '\u001b[0m'
  };

  var message = this.toMotd();
  for (k in codes) {
    message = message.replace(new RegExp(k, 'g'), codes[k]);
  }
  return message;
};

if(require.main === module) {
  testParseShowItemHoverEventValue();
}
<commit_msg>fixed ChatMessage this.with mapping<commit_after>var mojangson = require('mojangson');

module.exports = ChatMessage;

/**
 * ChatMessage Constructor
 * @param {String|Object} message content of ChatMessage
 */
function ChatMessage(message) {
  if(typeof message === 'string') {
    this.json = {text: message};
  } else if(typeof message === 'object' && !Array.isArray(message)) {
    this.json = message;
  } else {
    throw new Error('Expected String or Object for Message argument');
  }
  this.parse();
}

/**
 * Parses the this.json property to decorate the properties of the ChatMessage.
 * Called by the Constructor
 * @return {void}
 */
ChatMessage.prototype.parse = function() {
  var json = this.json;
  // Message scope for callback functions
  var that = this;

  // There is EITHER, a text property or a translate property
  // If there is no translate property, there is no with property
  // HOWEVER! If there is a translate property, there may not be a with property
  if(typeof json.text === 'string') {
    this.text = json.text;
  } else if(typeof json.translate === 'string') {
    this.translate = json.translate;
    if(typeof json.with === 'object' || Array.isArray(json.with)) {
      this.with = json.with.map(function(entry) {
        return new ChatMessage(entry);
      });
    }
  }
  // Parse extra property
  // Extras are appended to the initial text
  if(typeof json.extra === 'object') {
    if(!Array.isArray(json.extra)) {
      throw new Error('Expected extra property to be an Array in ChatMessage');
    }
    this.extra = [];
    json.extra.forEach(function(entry) {
      if(typeof entry === 'string') {
        that.extra.push(entry);
      } else if(typeof entry === 'object') {
        var subChatMessage = new ChatMessage(entry);
        that.extra.push(subChatMessage);
      }
    });
  }
  // Text modifiers
  this.bold = json.bold;
  this.italic = json.italic;
  this.underlined = json.underlined;
  this.strikethrough = json.strikethrough;
  this.obfuscated = json.obfuscated;

  // Supported constants @ 2014-04-21
  var supportedColors = ['black', 'dark_blue', 'dark_green', 'dark_aqua', 'dark_red', 'dark_purple',
    'gold', 'gray', 'dark_gray', 'blue', 'green', 'aqua', 'red', 'light_purple',
    'yellow', 'white', 'obfuscated', 'bold', 'strikethrough', 'underlined', 'italic',
    'reset'];
  var supportedClick = ['open_url', 'open_file', 'run_command', 'suggest_command'];
  var supportedHover = ['show_text', 'show_achievement', 'show_item', 'show_entity'];

  // Parse color
  this.color = json.color;
  switch(this.color) {
    case 'obfuscated':
      this.obfuscated = true;
      this.color = null;
      break;
    case 'bold':
      this.bold = true;
      this.color = null;
      break;
    case 'strikethrough':
      this.strikethrough = true;
      this.color = null;
      break;
    case 'underlined':
      this.underlined = true;
      this.color = null;
      break;
    case 'italic':
      this.italic = true;
      this.color = null;
      break;
    case 'reset':
      this.reset = true;
      this.color = null;
      break;
  }
  if(Array.prototype.indexOf && this.color &&
    supportedColors.indexOf(this.color) === -1) {
    console.warn('ChatMessage parsed with unsupported color', this.color);
  }

  // Parse click event
  if(typeof json.clickEvent === 'object') {
    this.clickEvent = json.clickEvent;
    if(typeof this.clickEvent.action !== 'string') {
      throw new Error('ClickEvent action missing in ChatMessage');
    } else if(Array.prototype.indexOf && supportedClick.indexOf(this.clickEvent.action) === -1) {
      console.warn('ChatMessage parsed with unsupported clickEvent', this.clickEvent.action);
    }
  }

  // Parse hover event
  if(typeof json.hoverEvent === 'object') {
    this.hoverEvent = json.hoverEvent;
    if(typeof this.hoverEvent.action !== 'string') {
      throw new Error('HoverEvent action missing in ChatMessage');
    } else if(Array.prototype.indexOf && supportedHover.indexOf(this.hoverEvent.action) === -1) {
      console.warn('ChatMessage parsed with unsupported hoverEvent', this.hoverEvent.action);
    }
    // Special case
    if(this.hoverEvent.action === 'show_item') {
      let content;
      if(this.hoverEvent.value instanceof Array) {
        if(this.hoverEvent.value[0] instanceof Object)
          content=this.hoverEvent.value[0].text;
        else
          content=this.hoverEvent.value[0];
      }
      else {
        if(this.hoverEvent.value instanceof Object)
          content=this.hoverEvent.value.text;
        else
          content=this.hoverEvent.value;
      }
      this.hoverEvent.value = mojangson.parse(content);
    }
  }
};

/**
 * Returns the count of text extras and child ChatMessages
 * Does not count recursively in to the children
 * @return {Number}
 */
ChatMessage.prototype.length = function() {
  var count = 0;
  if(this.text) count++;
  else if(this.translate) count += this.with.length;

  if(this.extra) count += this.extra.length;
  return count;
};

/**
 * Returns a text part from the message
 * @param  {Number} idx Index of the part
 * @return {String}
 */
ChatMessage.prototype.getText = function(idx) {
  // If the index is not defined is is invalid, return toString
  if(typeof idx !== 'number') return this.toString();
  // If we are not a translating message, return the text
  if(this.text && idx === 0) return this.text;
  // Else return the with child if it's in range
  else if(this.with.length > idx) return this.with[idx].toString();
  // Else return the extra if it's in range
  if(this.extra && this.extra.length + (this.text ? 1 : this.with.length) > idx)
    return this.extra[idx - (this.text ? 1 : this.with.length)].toString();
  // Not sure how you want to default this
  // Undefined, an error ?
  return "";
};

/**
 * Flattens the message in to plain-text
 * @return {String}
 */
ChatMessage.prototype.toString = function() {
  var message = "";
  if(typeof this.text === 'string') message += this.text + " ";
  else if(this.with) {
    this.with.forEach(function(entry) {
      message += entry.toString() + " ";
    })
  }
  if(this.extra) {
    this.extra.forEach(function(entry) {
      message += entry.toString() + " ";
    })
  }
  return message.trim();
};

ChatMessage.prototype.toMotd = function(motd) {
  var codes = {
    color:{
      black: '§0',
      dark_blue: '§1',
      dark_green: '§2',
      dark_aqua: '§3',
      dark_red: '§4',
      dark_purple: '§5',
      gold: '§6',
      gray: '§7',
      dark_gray: '§8',
      blue: '§9',
      green: '§a',
      aqua: '§b',
      red: '§c',
      light_purple: '§d',
      yellow: '§e',
      white: '§f',
    },
    bold: '§l',
    italic: '§o',
    underlined: '§n',
    strikethrough: '§m',
    obfuscated : '§k'
  }

  var message = Object.keys(codes).map((function (code) {
    if (!this[code] || this[code] === 'false') return null;
    if (code === 'color') return codes.color[this.color];
    return codes[code];
  }).bind(this)).join('');

  if(typeof this.text === 'string') message += this.text + "§r ";
  else if(this.with) {
    this.with.forEach(function(entry) {
      message += entry.toMotd() + " ";
    })
  }
  if(this.extra) {
    this.extra.forEach(function(entry) {
      message += entry.toMotd() + " ";
    })
  }
  return message.trim();
};

ChatMessage.prototype.toAnsi = function(motd) {
  var codes = {
    '§0': '\u001b[30m',
    '§1': '\u001b[34m',
    '§2': '\u001b[32m',
    '§3': '\u001b[36m',
    '§4': '\u001b[31m',
    '§5': '\u001b[35m',
    '§6': '\u001b[33m',
    '§7': '\u001b[37m',
    '§8': '\u001b[90m',
    '§9': '\u001b[94m',
    '§a': '\u001b[92m',
    '§b': '\u001b[96m',
    '§c': '\u001b[91m',
    '§d': '\u001b[95m',
    '§e': '\u001b[93m',
    '§f': '\u001b[97m',
    '§l': '\u001b[1m',
    '§o': '\u001b[3m',
    '§n': '\u001b[4m',
    '§m': '\u001b[9m',
    '§k': '\u001b[6m',
    '§r': '\u001b[0m'
  };

  var message = this.toMotd();
  for (k in codes) {
    message = message.replace(new RegExp(k, 'g'), codes[k]);
  }
  return message;
};

if(require.main === module) {
  testParseShowItemHoverEventValue();
}
<|endoftext|>


<commit_before>import React from 'react';
import Radium from 'radium';
import { Gateway } from 'react-gateway';

import Path from 'paths-js/path';

import {BASE_HEIGHT} from './constants';

@Radium
class KnobOverlay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  componentWillMount() {
    this.setState({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    });
  }

  render() {
    const {
      topPosition,
      xPosition,
      scale,
      knobCenter,
      cursorPos,
      overlayColor
    } = this.props;

    const {windowWidth, windowHeight} = this.state;

    const styles = {
      overlay: {
        position: 'fixed',
        zIndex: 100,
        top: 0, left: 0,
        cursor: 'move'
      }
    };

    const knobPath = Path()
      .moveto(...knobCenter)
      .lineto(...cursorPos)
      .closepath().print();

    const topPath = Path()
      .moveto(xPosition - 6, topPosition)
      .lineto(xPosition + 6, topPosition)
      .closepath().print();

    const bottomPath = Path()
      .moveto(xPosition - 6, topPosition + (BASE_HEIGHT * scale))
      .lineto(xPosition + 6, topPosition + (BASE_HEIGHT * scale))
      .closepath().print();

    const centerPath = Path()
      .moveto(xPosition - 6, topPosition + ((BASE_HEIGHT * scale) / 2))
      .lineto(xPosition + 6, topPosition + ((BASE_HEIGHT * scale) / 2))
      .closepath().print();

    const bodyPath = Path()
      .moveto(xPosition, topPosition)
      .lineto(xPosition, topPosition + (BASE_HEIGHT * scale))
      .closepath().print();

    const strokeProps = (d) => ({
      d, stroke: overlayColor, strokeWidth: 1
    });

    return (
      <Gateway into="knobOverlay">

          <svg width={windowWidth} height={windowHeight}>
            <path {...strokeProps(knobPath)} />
            <path {...strokeProps(topPath)} />
            <path {...strokeProps(centerPath)} />
            <path {...strokeProps(bottomPath)} />
            <path {...strokeProps(bodyPath)} />
          </svg>

      </Gateway>
    )
  }
}

/*
 <Surface
 style={styles.overlay}
 width={this.state.windowWidth}
 height={this.state.windowHeight}
 >
 <Shape {...strokeProps(knobPath)} />
 <Shape {...strokeProps(topPath)} />
 <Shape {...strokeProps(centerPath)} />
 <Shape {...strokeProps(bottomPath)} />
 <Shape {...strokeProps(bodyPath)} />
 </Surface>
 */

export default KnobOverlay;<commit_msg>Change stroke opacity in knobOverlay<commit_after>import React from 'react';
import Radium from 'radium';
import { Gateway } from 'react-gateway';

import Path from 'paths-js/path';

import {BASE_HEIGHT} from './constants';

@Radium
class KnobOverlay extends React.Component {
  constructor(props) {
    super(props);
    this.state = {};
  }

  componentWillMount() {
    this.setState({
      windowWidth: window.innerWidth,
      windowHeight: window.innerHeight
    });
  }

  render() {
    const {
      topPosition,
      xPosition,
      scale,
      knobCenter,
      cursorPos,
      overlayColor
    } = this.props;

    const {windowWidth, windowHeight} = this.state;

    const styles = {
      overlay: {
        position: 'fixed',
        zIndex: 100,
        top: 0, left: 0,
        cursor: 'move'
      }
    };

    const knobPath = Path()
      .moveto(...knobCenter)
      .lineto(...cursorPos)
      .closepath().print();

    const topPath = Path()
      .moveto(xPosition - 6, topPosition)
      .lineto(xPosition + 6, topPosition)
      .closepath().print();

    const bottomPath = Path()
      .moveto(xPosition - 6, topPosition + (BASE_HEIGHT * scale))
      .lineto(xPosition + 6, topPosition + (BASE_HEIGHT * scale))
      .closepath().print();

    const centerPath = Path()
      .moveto(xPosition - 6, topPosition + ((BASE_HEIGHT * scale) / 2))
      .lineto(xPosition + 6, topPosition + ((BASE_HEIGHT * scale) / 2))
      .closepath().print();

    const bodyPath = Path()
      .moveto(xPosition, topPosition)
      .lineto(xPosition, topPosition + (BASE_HEIGHT * scale))
      .closepath().print();

    const strokeProps = (d) => ({
      d, stroke: overlayColor, strokeWidth: 1
    });

    return (
      <Gateway into="knobOverlay">

          <svg width={windowWidth} height={windowHeight}>
            <path {...strokeProps(knobPath)} strokeOpacity={0.5} />
            <path {...strokeProps(topPath)} />
            <path {...strokeProps(centerPath)} />
            <path {...strokeProps(bottomPath)} />
            <path {...strokeProps(bodyPath)} />
          </svg>

      </Gateway>
    )
  }
}

/*
 <Surface
 style={styles.overlay}
 width={this.state.windowWidth}
 height={this.state.windowHeight}
 >
 <Shape {...strokeProps(knobPath)} />
 <Shape {...strokeProps(topPath)} />
 <Shape {...strokeProps(centerPath)} />
 <Shape {...strokeProps(bottomPath)} />
 <Shape {...strokeProps(bodyPath)} />
 </Surface>
 */

export default KnobOverlay;<|endoftext|>


<commit_before>'use strict';

var computedStyle = require('computed-style');

var CLIPPING_OVERFLOW_VALUES = ['scroll', 'hidden', 'auto', 'overlay'];
var FILTER_POSITION_VALUES = ['absolute', 'fixed'];

var rectStore = null;

function toArray (nodeList) {
    // Array.prototype.slice does not work on HTMLCollection in IE8
    var arr = [];
    if (nodeList) {
        var node;
        for (var i=0, l=nodeList.length; i<l; i++) {
            node = nodeList[i];
            // Filter out anything but elements
            if (node.nodeType === 1) {
                arr.push(node);
            }
        }
    }
    return arr;
}

function filterChildren (nodeList) {

    return toArray(nodeList).filter(function (child) {
        var position = computedStyle(child, 'position');
        child._rect = child.getBoundingClientRect(); // store rect for later

        return (
            FILTER_POSITION_VALUES.indexOf(position) === -1 &&
            !(child._rect.width === 0 && child._rect.height === 0)
        );
    });

}

var storeNodeRect = function (element) {

    var children = filterChildren(element.childnodes);
    var overflow = computedStyle(element, 'overflow');

    if (rectStore === null) {
        rectStore = {
            top: element._rect.top,
            right: element._rect.right,
            bottom: element._rect.bottom,
            left: element._rect.left
        };
    }

    rectStore.top       = Math.min(rectStore.top, element._rect.top);
    rectStore.right     = Math.max(rectStore.right, element._rect.right);
    rectStore.bottom    = Math.max(rectStore.bottom, element._rect.bottom);
    rectStore.left      = Math.min(rectStore.left, element._rect.left);

    if (CLIPPING_OVERFLOW_VALUES.indexOf(overflow) === -1) {

        children.forEach(function (el) {
            storeNodeRect(el);
        });
    }

};

function run (baseNode) {

    if (!baseNode) {
        return;
    }

    // reset
    rectStore = null;

    filterChildren(baseNode.children).forEach(function (el) {
        storeNodeRect(el);
    });

    return {
        width: rectStore ? rectStore.right - rectStore.left : 0,
        height: rectStore ? rectStore.bottom - rectStore.top : 0
    };
}

module.exports = run;
<commit_msg>issue-16 Spelling error<commit_after>'use strict';

var computedStyle = require('computed-style');

var CLIPPING_OVERFLOW_VALUES = ['scroll', 'hidden', 'auto', 'overlay'];
var FILTER_POSITION_VALUES = ['absolute', 'fixed'];

var rectStore = null;

function toArray (nodeList) {
    // Array.prototype.slice does not work on HTMLCollection in IE8
    var arr = [];
    if (nodeList) {
        var node;
        for (var i=0, l=nodeList.length; i<l; i++) {
            node = nodeList[i];
            // Filter out anything but elements
            if (node.nodeType === 1) {
                arr.push(node);
            }
        }
    }
    return arr;
}

function filterChildren (nodeList) {

    return toArray(nodeList).filter(function (child) {
        var position = computedStyle(child, 'position');
        child._rect = child.getBoundingClientRect(); // store rect for later

        return (
            FILTER_POSITION_VALUES.indexOf(position) === -1 &&
            !(child._rect.width === 0 && child._rect.height === 0)
        );
    });

}

var storeNodeRect = function (element) {

    var children = filterChildren(element.childNodes);
    var overflow = computedStyle(element, 'overflow');

    if (rectStore === null) {
        rectStore = {
            top: element._rect.top,
            right: element._rect.right,
            bottom: element._rect.bottom,
            left: element._rect.left
        };
    }

    rectStore.top       = Math.min(rectStore.top, element._rect.top);
    rectStore.right     = Math.max(rectStore.right, element._rect.right);
    rectStore.bottom    = Math.max(rectStore.bottom, element._rect.bottom);
    rectStore.left      = Math.min(rectStore.left, element._rect.left);

    if (CLIPPING_OVERFLOW_VALUES.indexOf(overflow) === -1) {

        children.forEach(function (el) {
            storeNodeRect(el);
        });
    }

};

function run (baseNode) {

    if (!baseNode) {
        return;
    }

    // reset
    rectStore = null;

    filterChildren(baseNode.children).forEach(function (el) {
        storeNodeRect(el);
    });

    return {
        width: rectStore ? rectStore.right - rectStore.left : 0,
        height: rectStore ? rectStore.bottom - rectStore.top : 0
    };
}

module.exports = run;
<|endoftext|>


<commit_before>var registerComponent = require('../../core/component').registerComponent;
var THREE = require('../../lib/three');
var utils = require('../../utils/');

var dummyDolly = new THREE.Object3D();
var controls = new THREE.VRControls(dummyDolly);

var ENTER_VR_CLASS = 'a-enter-vr';
var ENTER_VR_NO_HEADSET = 'data-a-enter-vr-no-headset';
var ENTER_VR_NO_WEBVR = 'data-a-enter-vr-no-webvr';
var ENTER_VR_BTN_CLASS = 'a-enter-vr-button';
var ENTER_VR_MODAL_CLASS = 'a-enter-vr-modal';
var HIDDEN_CLASS = 'a-hidden';
var ORIENTATION_MODAL_CLASS = 'a-orientation-modal';

/**
 * UI for entering VR mode.
 */
module.exports.Component = registerComponent('vr-mode-ui', {
  dependencies: [ 'canvas' ],

  schema: {
    enabled: { default: true }
  },

  init: function () {
    var self = this;
    var scene = this.el;

    this.enterVR = scene.enterVR.bind(scene);
    this.exitVR = scene.exitVR.bind(scene);
    this.insideLoader = false;
    this.enterVREl = null;
    this.orientationModalEl = null;

    // Hide/show VR UI when entering/exiting VR mode.
    scene.addEventListener('enter-vr', this.updateEnterVRInterface.bind(this));
    scene.addEventListener('exit-vr', this.updateEnterVRInterface.bind(this));

    window.addEventListener('message', function (event) {
      if (event.data.type === 'loaderReady') {
        self.insideLoader = true;
        self.remove();
      }
    });

    // Modal that tells the user to change orientation if in portrait.
    window.addEventListener('orientationchange', this.toggleOrientationModalIfNeeded.bind(this));
  },

  update: function () {
    var scene = this.el;

    if (!this.data.enabled || this.insideLoader) { return this.remove(); }
    if (this.enterVREl || this.orientationModalEl) { return; }

    // Add UI if enabled and not already present.
    this.enterVREl = createEnterVR(this.enterVR, scene.isMobile);
    this.el.appendChild(this.enterVREl);

    this.orientationModalEl = createOrientationModal(this.exitVR);
    this.el.appendChild(this.orientationModalEl);

    this.updateEnterVRInterface();
  },

  remove: function () {
    [this.enterVREl, this.orientationModalEl].forEach(function (uiElement) {
      if (uiElement) {
        uiElement.parentNode.removeChild(uiElement);
      }
    });
  },

  updateEnterVRInterface: function () {
    this.toggleEnterVRButtonIfNeeded();
    this.toggleOrientationModalIfNeeded();
  },

  toggleEnterVRButtonIfNeeded: function () {
    if (!this.enterVREl) { return; }
    var scene = this.el;
    if (scene.is('vr-mode')) {
      this.enterVREl.classList.add(HIDDEN_CLASS);
    } else {
      this.enterVREl.classList.remove(HIDDEN_CLASS);
    }
  },

  toggleOrientationModalIfNeeded: function () {
    if (!this.orientationModalEl || !this.isMobile) { return; }
    var scene = this.el;
    if (!utils.isLandscape() && scene.is('vr-mode')) {
      // Show if in VR mode on portrait.
      this.orientationModalEl.classList.remove(HIDDEN_CLASS);
    } else {
      this.orientationModalEl.classList.add(HIDDEN_CLASS);
    }
  }
});

/**
 * Creates Enter VR flow (button and compatibility modal).
 *
 * Creates a button that when clicked will enter into stereo-rendering mode for VR.
 *
 * For compatibility:
 *   - Mobile always has compatibility via polyfill.
 *   - If desktop browser does not have WebVR excluding polyfill, disable button, show modal.
 *   - If desktop browser has WebVR excluding polyfill but not headset connected,
 *     don't disable button, but show modal.
 *   - If desktop browser has WebVR excluding polyfill and has headset connected, then
 *     then no modal.
 *
 * Structure:
<modal/><button>

 *
 * @returns {Element} Wrapper
.
 */
function createEnterVR (enterVRHandler, isMobile) {
  var compatModal;
  var compatModalLink;
  var compatModalText;
  // window.hasNonPolyfillWebVRSupport is set in src/index.js.
  var hasWebVR = isMobile || window.hasNonPolyfillWebVRSupport;
  var orientation;
  var vrButton;
  var wrapper;

  // Create elements.
  wrapper = document.createElement('div');
  wrapper.classList.add(ENTER_VR_CLASS);
  compatModal = document.createElement('div');
  compatModal.className = ENTER_VR_MODAL_CLASS;
  compatModalText = document.createElement('p');
  compatModalLink = document.createElement('a');
  compatModalLink.setAttribute('href', 'http://mozvr.com/#start');
  compatModalLink.setAttribute('target', '_blank');
  compatModalLink.innerHTML = 'Learn more.';
  vrButton = document.createElement('button');
  vrButton.className = ENTER_VR_BTN_CLASS;

  // Insert elements.
  wrapper.appendChild(vrButton);
  if (compatModal) {
    compatModal.appendChild(compatModalText);
    compatModal.appendChild(compatModalLink);
    wrapper.appendChild(compatModal);
  }

  if (!checkHeadsetConnected() && !isMobile) {
    compatModalText.innerHTML = 'Your browser supports WebVR. To enter VR, connect a headset, or use a mobile phone.';
    wrapper.setAttribute(ENTER_VR_NO_HEADSET, '');
  }

  // Handle enter VR flows.
  if (!hasWebVR) {
    compatModalText.innerHTML = 'Your browser does not support WebVR. To enter VR, use a VR-compatible browser or a mobile phone.';
    wrapper.setAttribute(ENTER_VR_NO_WEBVR, '');
  } else {
    vrButton.addEventListener('click', enterVRHandler);
  }
  return wrapper;

  /**
   * Check for headset connection by looking at orientation {0 0 0}.
   */
  function checkHeadsetConnected () {
    controls.update();
    orientation = dummyDolly.quaternion;
    if (orientation._x !== 0 || orientation._y !== 0 || orientation._z !== 0) {
      return true;
    }
  }
}

/**
 * Create a modal that tells mobile users to orient the phone to landscape.
 * Add a close button that if clicked, exits VR and closes the modal.
 */
function createOrientationModal (exitVRHandler) {
  var modal = document.createElement('div');
  modal.className = ORIENTATION_MODAL_CLASS;
  modal.classList.add(HIDDEN_CLASS);

  var exit = document.createElement('button');
  exit.innerHTML = 'Exit VR';

  // Exit VR on close.
  exit.addEventListener('click', exitVRHandler);

  modal.appendChild(exit);

  return modal;
}
<commit_msg>fix typo for mobile check for showing orientation modal<commit_after>var registerComponent = require('../../core/component').registerComponent;
var THREE = require('../../lib/three');
var utils = require('../../utils/');

var dummyDolly = new THREE.Object3D();
var controls = new THREE.VRControls(dummyDolly);

var ENTER_VR_CLASS = 'a-enter-vr';
var ENTER_VR_NO_HEADSET = 'data-a-enter-vr-no-headset';
var ENTER_VR_NO_WEBVR = 'data-a-enter-vr-no-webvr';
var ENTER_VR_BTN_CLASS = 'a-enter-vr-button';
var ENTER_VR_MODAL_CLASS = 'a-enter-vr-modal';
var HIDDEN_CLASS = 'a-hidden';
var ORIENTATION_MODAL_CLASS = 'a-orientation-modal';

/**
 * UI for entering VR mode.
 */
module.exports.Component = registerComponent('vr-mode-ui', {
  dependencies: [ 'canvas' ],

  schema: {
    enabled: { default: true }
  },

  init: function () {
    var self = this;
    var scene = this.el;

    this.enterVR = scene.enterVR.bind(scene);
    this.exitVR = scene.exitVR.bind(scene);
    this.insideLoader = false;
    this.enterVREl = null;
    this.orientationModalEl = null;

    // Hide/show VR UI when entering/exiting VR mode.
    scene.addEventListener('enter-vr', this.updateEnterVRInterface.bind(this));
    scene.addEventListener('exit-vr', this.updateEnterVRInterface.bind(this));

    window.addEventListener('message', function (event) {
      if (event.data.type === 'loaderReady') {
        self.insideLoader = true;
        self.remove();
      }
    });

    // Modal that tells the user to change orientation if in portrait.
    window.addEventListener('orientationchange', this.toggleOrientationModalIfNeeded.bind(this));
  },

  update: function () {
    var scene = this.el;

    if (!this.data.enabled || this.insideLoader) { return this.remove(); }
    if (this.enterVREl || this.orientationModalEl) { return; }

    // Add UI if enabled and not already present.
    this.enterVREl = createEnterVR(this.enterVR, scene.isMobile);
    this.el.appendChild(this.enterVREl);

    this.orientationModalEl = createOrientationModal(this.exitVR);
    this.el.appendChild(this.orientationModalEl);

    this.updateEnterVRInterface();
  },

  remove: function () {
    [this.enterVREl, this.orientationModalEl].forEach(function (uiElement) {
      if (uiElement) {
        uiElement.parentNode.removeChild(uiElement);
      }
    });
  },

  updateEnterVRInterface: function () {
    this.toggleEnterVRButtonIfNeeded();
    this.toggleOrientationModalIfNeeded();
  },

  toggleEnterVRButtonIfNeeded: function () {
    if (!this.enterVREl) { return; }
    var scene = this.el;
    if (scene.is('vr-mode')) {
      this.enterVREl.classList.add(HIDDEN_CLASS);
    } else {
      this.enterVREl.classList.remove(HIDDEN_CLASS);
    }
  },

  toggleOrientationModalIfNeeded: function () {
    var scene = this.el;
    if (!this.orientationModalEl || !scene.isMobile) { return; }
    if (!utils.isLandscape() && scene.is('vr-mode')) {
      // Show if in VR mode on portrait.
      this.orientationModalEl.classList.remove(HIDDEN_CLASS);
    } else {
      this.orientationModalEl.classList.add(HIDDEN_CLASS);
    }
  }
});

/**
 * Creates Enter VR flow (button and compatibility modal).
 *
 * Creates a button that when clicked will enter into stereo-rendering mode for VR.
 *
 * For compatibility:
 *   - Mobile always has compatibility via polyfill.
 *   - If desktop browser does not have WebVR excluding polyfill, disable button, show modal.
 *   - If desktop browser has WebVR excluding polyfill but not headset connected,
 *     don't disable button, but show modal.
 *   - If desktop browser has WebVR excluding polyfill and has headset connected, then
 *     then no modal.
 *
 * Structure:
<modal/><button>

 *
 * @returns {Element} Wrapper
.
 */
function createEnterVR (enterVRHandler, isMobile) {
  var compatModal;
  var compatModalLink;
  var compatModalText;
  // window.hasNonPolyfillWebVRSupport is set in src/index.js.
  var hasWebVR = isMobile || window.hasNonPolyfillWebVRSupport;
  var orientation;
  var vrButton;
  var wrapper;

  // Create elements.
  wrapper = document.createElement('div');
  wrapper.classList.add(ENTER_VR_CLASS);
  compatModal = document.createElement('div');
  compatModal.className = ENTER_VR_MODAL_CLASS;
  compatModalText = document.createElement('p');
  compatModalLink = document.createElement('a');
  compatModalLink.setAttribute('href', 'http://mozvr.com/#start');
  compatModalLink.setAttribute('target', '_blank');
  compatModalLink.innerHTML = 'Learn more.';
  vrButton = document.createElement('button');
  vrButton.className = ENTER_VR_BTN_CLASS;

  // Insert elements.
  wrapper.appendChild(vrButton);
  if (compatModal) {
    compatModal.appendChild(compatModalText);
    compatModal.appendChild(compatModalLink);
    wrapper.appendChild(compatModal);
  }

  if (!checkHeadsetConnected() && !isMobile) {
    compatModalText.innerHTML = 'Your browser supports WebVR. To enter VR, connect a headset, or use a mobile phone.';
    wrapper.setAttribute(ENTER_VR_NO_HEADSET, '');
  }

  // Handle enter VR flows.
  if (!hasWebVR) {
    compatModalText.innerHTML = 'Your browser does not support WebVR. To enter VR, use a VR-compatible browser or a mobile phone.';
    wrapper.setAttribute(ENTER_VR_NO_WEBVR, '');
  } else {
    vrButton.addEventListener('click', enterVRHandler);
  }
  return wrapper;

  /**
   * Check for headset connection by looking at orientation {0 0 0}.
   */
  function checkHeadsetConnected () {
    controls.update();
    orientation = dummyDolly.quaternion;
    if (orientation._x !== 0 || orientation._y !== 0 || orientation._z !== 0) {
      return true;
    }
  }
}

/**
 * Create a modal that tells mobile users to orient the phone to landscape.
 * Add a close button that if clicked, exits VR and closes the modal.
 */
function createOrientationModal (exitVRHandler) {
  var modal = document.createElement('div');
  modal.className = ORIENTATION_MODAL_CLASS;
  modal.classList.add(HIDDEN_CLASS);

  var exit = document.createElement('button');
  exit.innerHTML = 'Exit VR';

  // Exit VR on close.
  exit.addEventListener('click', exitVRHandler);

  modal.appendChild(exit);

  return modal;
}
<|endoftext|>


<commit_before>var React = require('react');
var { array, bool, number, objectOf, oneOf, string } = React.PropTypes;
var LinearDomain = require('../domains/linear-domain');
var TimeDomain = require('../domains/time-domain');
var Range = require('../ranges/range');
var LinearScale = require('../scales/linear-scale');
var TimeScale = require('../scales/time-scale');
var BarSeries = require('./bar-series');
var ClusteredBarSeries = require('./clustered-bar-series');
var DifferenceBarSeries = require('./difference-bar-series');
var LineSeries = require('./line-series');
var PlotSeries = require('./plot-series');
var StackedBarSeries = require('./stacked-bar-series');
var LinearAxis = require('./linear-axis');
var HorizontalGridLines = require('./horizontal-grid-lines');
var TimeAxis = require('./time-axis');
var ClipShape = require('./clip-shape');
var classNames = require('classnames');

module.exports = React.createClass({

  propTypes: {
    clampToZero: bool.isRequired,
    className: string,
    clipShapeId: string.isRequired,
    height: number.isRequired,
    index: number.isRequired,
    label: string.isRequired,
    margins: objectOf(number).isRequired,
    numTicksX: number.isRequired,
    numTicksY: number.isRequired,
    series: array.isRequired,
    tickWidth: number.isRequired,
    type: oneOf([
      'area',
      'bar',
      'clusteredBar',
      'differenceBar',
      'line',
      'plot',
      'stackedBar'
    ]),
    width: number.isRequired
  },

  getDefaultProps() {
    return {
      clampToZero: true,
      clipShapeId: 'timeseriesClipShape',
      height: 0,
      index: 0,
      label: '',
      margins: {
        bottom: 0,
        left: 0,
        right: 0,
        top: 0
      },
      numTicks: 0,
      numTicksY: 5,
      series: [],
      tickWidth: 0,
      width: 0
    };
  },

  componentDidMount() {
    this.setClipShape();
  },

  componentDidUpdate() {
    this.setClipShape();
  },

  getOffset(groupType, series, tickWidth) {
    var offset = Math.floor(tickWidth / 2);

    if (this.isBarBased(groupType)) {
      return offset;
    }

    for (var ii = 0, nn = series.length; ii < nn; ii++) {
      if (this.isBarBased(series[ii].type)) {
        return offset;
      }
    }

    return 0;
  },

  getTimeSeries(groupType, seriesType) {
    var type = seriesType ? seriesType : groupType;
    switch (type) {
      case 'area':
        return LineSeries;
      case 'bar':
        return BarSeries;
      case 'clusteredBar':
        return ClusteredBarSeries;
      case 'differenceBar':
        return DifferenceBarSeries;
      case 'line':
        return LineSeries;
      case 'plot':
        return PlotSeries;
      case 'stackedBar':
        return StackedBarSeries;
      default:
        return '';
    }
  },

  isBarBased(type) {
    switch (type) {
      case 'bar':
      case 'clusteredBar':
      case 'differenceBar':
      case 'plot':
      case 'stackedBar':
        return true;
      default:
        return false;
    }
  },

  setClipShape() {
    /*
    React does not currently whitelist the clip-path
    attribute for SVG elements, so we set it manually
    whenever the component mounts or updates.
    */
    React.findDOMNode(this.refs.timeseriesContainer)
      .setAttribute('clip-path', 'url(#' + this.props.clipShapeId + ')');
  },

  render() {
    var domainX = new TimeDomain(this.props.series);
    var domainY = new LinearDomain(
      this.props.series,
      this.props.clampToZero);

    var rangeX = new Range(this.props.width);
    var rangeY = new Range(this.props.height, true);

    var scaleX = new TimeScale(domainX, rangeX);
    var scaleY = new LinearScale(domainY, rangeY);

    var zeroY = Math.round(scaleY(0));

    var tickPaddingX = Math.ceil(
      this.props.margins.bottom / 10);
    var tickPaddingY = this.props.index ?
      Math.ceil(this.props.margins.right / 10) :
      Math.floor(this.props.margins.left / 10);

    var offset = this.getOffset(
      this.props.type,
      this.props.series,
      this.props.tickWidth);

    var stretch = offset ? false : true;

    return (
      <g className={classNames('timeseries-group', this.props.className)}>

        {this.props.index === 0 ? (
          <HorizontalGridLines
            numTicks={this.props.numTicksY}
            scale={scaleY}
            width={this.props.width} />
        ) : null}

        <ClipShape
          height={this.props.height}
          id={this.props.clipShapeId}
          width={this.props.width} />

        <g
          className={'timeseries-container'}
          ref={'timeseriesContainer'}>

          {this.props.series.map((datum, index) => {
            var TimeSeries = this.getTimeSeries(this.props.type, datum.type);
            return (
              <TimeSeries
                className={datum.className}
                clipShapeId={this.props.clipShapeId}
                comparisonData={datum.comparisonData}
                data={datum.data}
                height={this.props.height}
                id={datum.id}
                index={index}
                interaction={datum.interaction}
                key={index}
                marker={datum.marker}
                numSeries={this.props.series.length}
                offset={offset}
                scaleX={scaleX}
                scaleY={scaleY}
                stretch={datum.stretch ? datum.stretch : stretch}
                style={datum.style}
                tickWidth={this.props.tickWidth}
                type={this.props.type ? this.props.type : datum.type}
                width={this.props.width}
                zeroY={zeroY} />
            );
          })}

        </g>

        {this.props.index < 2 ? (
          <LinearAxis
            contentWidth={this.props.width}
            height={this.props.height}
            label={this.props.label}
            numTicks={this.props.numTicksY}
            orient={this.props.index ? 'right' : 'left'}
            scale={scaleY}
            showDividerAtZero={this.props.index ? false : true}
            tickPadding={tickPaddingY}
            x={this.props.index ? this.props.width : 0}
            y={-this.props.margins.top}
            zeroY={zeroY} />
        ) : null}

        {this.props.index === 0 ? (
          <TimeAxis
            data={this.props.series[0].data}
            domain={domainX}
            numTicks={this.props.numTicksX}
            scale={scaleX}
            tickPadding={tickPaddingX}
            tickWidth={this.props.tickWidth}
            width={this.props.width}
            x={0}
            y={this.props.height} />
        ) : null}

      </g>
    );
  }

});
<commit_msg>fixed typo in timeseries group proptypes, refs #11775<commit_after>var React = require('react');
var { array, bool, number, objectOf, oneOf, string } = React.PropTypes;
var LinearDomain = require('../domains/linear-domain');
var TimeDomain = require('../domains/time-domain');
var Range = require('../ranges/range');
var LinearScale = require('../scales/linear-scale');
var TimeScale = require('../scales/time-scale');
var BarSeries = require('./bar-series');
var ClusteredBarSeries = require('./clustered-bar-series');
var DifferenceBarSeries = require('./difference-bar-series');
var LineSeries = require('./line-series');
var PlotSeries = require('./plot-series');
var StackedBarSeries = require('./stacked-bar-series');
var LinearAxis = require('./linear-axis');
var HorizontalGridLines = require('./horizontal-grid-lines');
var TimeAxis = require('./time-axis');
var ClipShape = require('./clip-shape');
var classNames = require('classnames');

module.exports = React.createClass({

  propTypes: {
    clampToZero: bool.isRequired,
    className: string,
    clipShapeId: string.isRequired,
    height: number.isRequired,
    index: number.isRequired,
    label: string.isRequired,
    margins: objectOf(number).isRequired,
    numTicksX: number.isRequired,
    numTicksY: number.isRequired,
    series: array.isRequired,
    tickWidth: number.isRequired,
    type: oneOf([
      'area',
      'bar',
      'clusteredBar',
      'differenceBar',
      'line',
      'plot',
      'stackedBar'
    ]),
    width: number.isRequired
  },

  getDefaultProps() {
    return {
      clampToZero: true,
      clipShapeId: 'timeseriesClipShape',
      height: 0,
      index: 0,
      label: '',
      margins: {
        bottom: 0,
        left: 0,
        right: 0,
        top: 0
      },
      numTicksX: 0,
      numTicksY: 5,
      series: [],
      tickWidth: 0,
      width: 0
    };
  },

  componentDidMount() {
    this.setClipShape();
  },

  componentDidUpdate() {
    this.setClipShape();
  },

  getOffset(groupType, series, tickWidth) {
    var offset = Math.floor(tickWidth / 2);

    if (this.isBarBased(groupType)) {
      return offset;
    }

    for (var ii = 0, nn = series.length; ii < nn; ii++) {
      if (this.isBarBased(series[ii].type)) {
        return offset;
      }
    }

    return 0;
  },

  getTimeSeries(groupType, seriesType) {
    var type = seriesType ? seriesType : groupType;
    switch (type) {
      case 'area':
        return LineSeries;
      case 'bar':
        return BarSeries;
      case 'clusteredBar':
        return ClusteredBarSeries;
      case 'differenceBar':
        return DifferenceBarSeries;
      case 'line':
        return LineSeries;
      case 'plot':
        return PlotSeries;
      case 'stackedBar':
        return StackedBarSeries;
      default:
        return '';
    }
  },

  isBarBased(type) {
    switch (type) {
      case 'bar':
      case 'clusteredBar':
      case 'differenceBar':
      case 'plot':
      case 'stackedBar':
        return true;
      default:
        return false;
    }
  },

  setClipShape() {
    /*
    React does not currently whitelist the clip-path
    attribute for SVG elements, so we set it manually
    whenever the component mounts or updates.
    */
    React.findDOMNode(this.refs.timeseriesContainer)
      .setAttribute('clip-path', 'url(#' + this.props.clipShapeId + ')');
  },

  render() {
    var domainX = new TimeDomain(this.props.series);
    var domainY = new LinearDomain(
      this.props.series,
      this.props.clampToZero);

    var rangeX = new Range(this.props.width);
    var rangeY = new Range(this.props.height, true);

    var scaleX = new TimeScale(domainX, rangeX);
    var scaleY = new LinearScale(domainY, rangeY);

    var zeroY = Math.round(scaleY(0));

    var tickPaddingX = Math.ceil(
      this.props.margins.bottom / 10);
    var tickPaddingY = this.props.index ?
      Math.ceil(this.props.margins.right / 10) :
      Math.floor(this.props.margins.left / 10);

    var offset = this.getOffset(
      this.props.type,
      this.props.series,
      this.props.tickWidth);

    var stretch = offset ? false : true;

    return (
      <g className={classNames('timeseries-group', this.props.className)}>

        {this.props.index === 0 ? (
          <HorizontalGridLines
            numTicks={this.props.numTicksY}
            scale={scaleY}
            width={this.props.width} />
        ) : null}

        <ClipShape
          height={this.props.height}
          id={this.props.clipShapeId}
          width={this.props.width} />

        <g
          className={'timeseries-container'}
          ref={'timeseriesContainer'}>

          {this.props.series.map((datum, index) => {
            var TimeSeries = this.getTimeSeries(this.props.type, datum.type);
            return (
              <TimeSeries
                className={datum.className}
                clipShapeId={this.props.clipShapeId}
                comparisonData={datum.comparisonData}
                data={datum.data}
                height={this.props.height}
                id={datum.id}
                index={index}
                interaction={datum.interaction}
                key={index}
                marker={datum.marker}
                numSeries={this.props.series.length}
                offset={offset}
                scaleX={scaleX}
                scaleY={scaleY}
                stretch={datum.stretch ? datum.stretch : stretch}
                style={datum.style}
                tickWidth={this.props.tickWidth}
                type={this.props.type ? this.props.type : datum.type}
                width={this.props.width}
                zeroY={zeroY} />
            );
          })}

        </g>

        {this.props.index < 2 ? (
          <LinearAxis
            contentWidth={this.props.width}
            height={this.props.height}
            label={this.props.label}
            numTicks={this.props.numTicksY}
            orient={this.props.index ? 'right' : 'left'}
            scale={scaleY}
            showDividerAtZero={this.props.index ? false : true}
            tickPadding={tickPaddingY}
            x={this.props.index ? this.props.width : 0}
            y={-this.props.margins.top}
            zeroY={zeroY} />
        ) : null}

        {this.props.index === 0 ? (
          <TimeAxis
            data={this.props.series[0].data}
            domain={domainX}
            numTicks={this.props.numTicksX}
            scale={scaleX}
            tickPadding={tickPaddingX}
            tickWidth={this.props.tickWidth}
            width={this.props.width}
            x={0}
            y={this.props.height} />
        ) : null}

      </g>
    );
  }

});
<|endoftext|>


<commit_before>/* Creates an object with x and y defined,
 * set to the mouse position relative to the element state
 * If you wanna be super-correct this can be tricky,
 * we have to worry about padding and borders
 * takes an event and a reference to the element
 */
function getMouse (e, elementData, element) {
  let offsetX = 0
  let offsetY = 0
  let mx
  let my

  // Compute the total offset. It's possible to cache this if you want
  if (element.offsetParent !== undefined) {
    do {
      offsetX += element.offsetLeft
      offsetY += element.offsetTop
    } while ((element = element.offsetParent))
  }

  // Add padding and border style widths to offset
  // Also add the <html> offsets in case there's a position:fixed bar (like the stumbleupon bar)
  // This part is not strictly necessary, it depends on your styling
  offsetX += elementData.stylePaddingLeft + elementData.styleBorderLeft + elementData.htmlLeft
  offsetY += elementData.stylePaddingTop + elementData.styleBorderTop + elementData.htmlTop

  mx = e.pageX - offsetX
  my = e.pageY - offsetY

  // We return a simple javascript object with x and y defined
  return {x: mx, y: my}
}

this.getMouse = getMouse
<commit_msg>Fixing mouse click offset calculus<commit_after>/* Creates an object with x and y defined,
 * set to the mouse position relative to the element state
 * If you wanna be super-correct this can be tricky,
 * we have to worry about padding and borders
 * takes an event and a reference to the element
 */
function getMouse (e, elementData, element) {
  let offsetX = 0
  let offsetY = 0
  let mx
  let my

  // Compute the total offset. It's possible to cache this if you want
  let intermediateElement = element
  if (intermediateElement.offsetParent !== undefined) {
    do {
      offsetX += intermediateElement.offsetLeft
      offsetY += intermediateElement.offsetTop
    } while ((intermediateElement = intermediateElement.offsetParent))
  }

  // Add padding and border style widths to offset
  // Also add the <html> offsets in case there's a position:fixed bar (like the stumbleupon bar)
  // This part is not strictly necessary, it depends on your styling
  offsetX += elementData.stylePaddingLeft + elementData.styleBorderLeft + elementData.htmlLeft
  offsetY += elementData.stylePaddingTop + elementData.styleBorderTop + elementData.htmlTop

  mx = e.pageX - offsetX + element.parentElement.scrollLeft
  my = e.pageY - offsetY + element.parentElement.scrollTop

  // We return a simple javascript object with x and y defined
  return {x: mx, y: my}
}

this.getMouse = getMouse
<|endoftext|>


<commit_before>var emitter = require('./emitter');

// Queries are live requests to the database for particular sets of fields.
//
// The server actively tells the client when there's new data that matches
// a set of conditions.
var Query = exports.Query = function(type, connection, id, collection, query, options, callback) {
  emitter.EventEmitter.call(this);

  // 'fetch' or 'sub'
  this.type = type;

  this.connection = connection;
  this.id = id;
  this.collection = collection;

  // The query itself. For mongo, this should look something like {"data.x":5}
  this.query = query;

  // Resultant document action for the server. Fetch mode will automatically
  // fetch all results. Subscribe mode will automatically subscribe all
  // results. Results are never unsubscribed.
  this.docMode = options.docMode; // undefined, 'fetch' or 'sub'.
  if (this.docMode === 'subscribe') this.docMode = 'sub';

  // Do we repoll the entire query whenever anything changes? (As opposed to
  // just polling the changed item). This needs to be enabled to be able to use
  // ordered queries (sortby:) and paginated queries. Set to undefined, it will
  // be enabled / disabled automatically based on the query's properties.
  this.poll = options.poll;

  // The backend we actually hit. If this isn't defined, it hits the snapshot
  // database. Otherwise this can be used to hit another configured query
  // index.
  this.backend = options.backend || options.source;

  // A list of resulting documents. These are actual documents, complete with
  // data and all the rest. If fetch is false, these documents will not
  // have any data. You should manually call fetch() or subscribe() on them.
  //
  // Calling subscribe() might be a good idea anyway, as you won't be
  // subscribed to the documents by default.
  this.knownDocs = options.knownDocs || [];
  this.results = [];

  // Do we have some initial data?
  this.ready = false;

  this.callback = callback;
};
emitter.mixin(Query);

Query.prototype.action = 'qsub';

// Helper for subscribe & fetch, since they share the same message format.
//
// This function actually issues the query.
Query.prototype._execute = function() {
  if (!this.connection.canSend) return;

  if (this.docMode) {
    var collectionVersions = {};
    // Collect the version of all the documents in the current result set so we
    // don't need to be sent their snapshots again.
    for (var i = 0; i < this.knownDocs.length; i++) {
      var doc = this.knownDocs[i];
      var c = collectionVersions[doc.collection] =
        (collectionVersions[doc.collection] || {});
      c[doc.name] = doc.version;
    }
  }

  var msg = {
    a: 'q' + this.type,
    id: this.id,
    c: this.collection,
    o: {},
    q: this.query,
  };

  if (this.docMode) {
    msg.o.m = this.docMode;
    // This should be omitted if empty, but whatever.
    msg.o.vs = collectionVersions;
  }
  if (this.backend != null) msg.o.b = this.backend;
  if (this.poll !== undefined) msg.o.p = this.poll;

  this.connection.send(msg);
};

// Make a list of documents from the list of server-returned data objects
Query.prototype._dataToDocs = function(data) {
  var results = [];
  var lastType;
  for (var i = 0; i < data.length; i++) {
    var docData = data[i];

    // Types are only put in for the first result in the set and every time the type changes in the list.
    if (docData.type) {
      lastType = docData.type;
    } else {
      docData.type = lastType;
    }

    // This will ultimately call doc.ingestData(), which is what populates
    // the doc snapshot and version with the data returned by the query
    var doc = this.connection.get(docData.c || this.collection, docData.d, docData);
    results.push(doc);
  }
  return results;
};

// Destroy the query object. Any subsequent messages for the query will be
// ignored by the connection. You should unsubscribe from the query before
// destroying it.
Query.prototype.destroy = function() {
  if (this.connection.canSend && this.type === 'sub') {
    this.connection.send({a:'qunsub', id:this.id});
  }

  this.connection._destroyQuery(this);
};

Query.prototype._onConnectionStateChanged = function(state, reason) {
  if (this.connection.state === 'connecting') {
    this._execute();
  }
};

// Internal method called from connection to pass server messages to the query.
Query.prototype._onMessage = function(msg) {
  if ((msg.a === 'qfetch') !== (this.type === 'fetch')) {
    console.warn('Invalid message sent to query', msg, this);
    return;
  }

  if (msg.error) this.emit('error', msg.error);

  switch (msg.a) {
    case 'qfetch':
      var results = msg.data ? this._dataToDocs(msg.data) : undefined;
      if (this.callback) this.callback(msg.error, results, msg.extra);
      // Once a fetch query gets its data, it is destroyed.
      this.connection._destroyQuery(this);
      break;

    case 'q':
      // Query diff data (inserts and removes)
      if (msg.diff) {
        // We need to go through the list twice. First, we'll ingest all the
        // new documents and set them as subscribed.  After that we'll emit
        // events and actually update our list. This avoids race conditions
        // around setting documents to be subscribed & unsubscribing documents
        // in event callbacks.
        for (var i = 0; i < msg.diff.length; i++) {
          var d = msg.diff[i];
          if (d.type === 'insert') d.values = this._dataToDocs(d.values);
        }

        for (var i = 0; i < msg.diff.length; i++) {
          var d = msg.diff[i];
          switch (d.type) {
            case 'insert':
              var newDocs = d.values;
              Array.prototype.splice.apply(this.results, [d.index, 0].concat(newDocs));
              this.emit('insert', newDocs, d.index);
              break;
            case 'remove':
              var howMany = d.howMany || 1;
              var removed = this.results.splice(d.index, howMany);
              this.emit('remove', removed, d.index);
              break;
            case 'move':
              var howMany = d.howMany || 1;
              var docs = this.results.splice(d.from, howMany);
              Array.prototype.splice.apply(this.results, [d.to, 0].concat(docs));
              this.emit('move', docs, d.from, d.to);
              break;
          }
        }
      }

      if (msg.extra !== void 0) {
        this.emit('extra', msg.extra);
      }
      break;
    case 'qsub':
      // This message replaces the entire result set with the set passed.
      if (!msg.error) {
        var previous = this.results;

        // Then add everything in the new result set.
        this.results = this.knownDocs = this._dataToDocs(msg.data);
        this.extra = msg.extra;

        this.ready = true;
        this.emit('change', this.results, previous);
      }
      if (this.callback) {
        this.callback(msg.error, this.results, this.extra);
        delete this.callback;
      }
      break;
  }
};

// Change the thing we're searching for. This isn't fully supported on the
// backend (it destroys the old query and makes a new one) - but its
// programatically useful and I might add backend support at some point.
Query.prototype.setQuery = function(q) {
  if (this.type !== 'sub') throw new Error('cannot change a fetch query');

  this.query = q;
  if (this.connection.canSend) {
    // There's no 'change' message to send to the server. Just resubscribe.
    this.connection.send({a:'qunsub', id:this.id});
    this._execute();
  }
};
<commit_msg>don't send a null version with query<commit_after>var emitter = require('./emitter');

// Queries are live requests to the database for particular sets of fields.
//
// The server actively tells the client when there's new data that matches
// a set of conditions.
var Query = exports.Query = function(type, connection, id, collection, query, options, callback) {
  emitter.EventEmitter.call(this);

  // 'fetch' or 'sub'
  this.type = type;

  this.connection = connection;
  this.id = id;
  this.collection = collection;

  // The query itself. For mongo, this should look something like {"data.x":5}
  this.query = query;

  // Resultant document action for the server. Fetch mode will automatically
  // fetch all results. Subscribe mode will automatically subscribe all
  // results. Results are never unsubscribed.
  this.docMode = options.docMode; // undefined, 'fetch' or 'sub'.
  if (this.docMode === 'subscribe') this.docMode = 'sub';

  // Do we repoll the entire query whenever anything changes? (As opposed to
  // just polling the changed item). This needs to be enabled to be able to use
  // ordered queries (sortby:) and paginated queries. Set to undefined, it will
  // be enabled / disabled automatically based on the query's properties.
  this.poll = options.poll;

  // The backend we actually hit. If this isn't defined, it hits the snapshot
  // database. Otherwise this can be used to hit another configured query
  // index.
  this.backend = options.backend || options.source;

  // A list of resulting documents. These are actual documents, complete with
  // data and all the rest. If fetch is false, these documents will not
  // have any data. You should manually call fetch() or subscribe() on them.
  //
  // Calling subscribe() might be a good idea anyway, as you won't be
  // subscribed to the documents by default.
  this.knownDocs = options.knownDocs || [];
  this.results = [];

  // Do we have some initial data?
  this.ready = false;

  this.callback = callback;
};
emitter.mixin(Query);

Query.prototype.action = 'qsub';

// Helper for subscribe & fetch, since they share the same message format.
//
// This function actually issues the query.
Query.prototype._execute = function() {
  if (!this.connection.canSend) return;

  if (this.docMode) {
    var collectionVersions = {};
    // Collect the version of all the documents in the current result set so we
    // don't need to be sent their snapshots again.
    for (var i = 0; i < this.knownDocs.length; i++) {
      var doc = this.knownDocs[i];
      if (doc.version == null) continue;
      var c = collectionVersions[doc.collection] =
        (collectionVersions[doc.collection] || {});
      c[doc.name] = doc.version;
    }
  }

  var msg = {
    a: 'q' + this.type,
    id: this.id,
    c: this.collection,
    o: {},
    q: this.query,
  };

  if (this.docMode) {
    msg.o.m = this.docMode;
    // This should be omitted if empty, but whatever.
    msg.o.vs = collectionVersions;
  }
  if (this.backend != null) msg.o.b = this.backend;
  if (this.poll !== undefined) msg.o.p = this.poll;

  this.connection.send(msg);
};

// Make a list of documents from the list of server-returned data objects
Query.prototype._dataToDocs = function(data) {
  var results = [];
  var lastType;
  for (var i = 0; i < data.length; i++) {
    var docData = data[i];

    // Types are only put in for the first result in the set and every time the type changes in the list.
    if (docData.type) {
      lastType = docData.type;
    } else {
      docData.type = lastType;
    }

    // This will ultimately call doc.ingestData(), which is what populates
    // the doc snapshot and version with the data returned by the query
    var doc = this.connection.get(docData.c || this.collection, docData.d, docData);
    results.push(doc);
  }
  return results;
};

// Destroy the query object. Any subsequent messages for the query will be
// ignored by the connection. You should unsubscribe from the query before
// destroying it.
Query.prototype.destroy = function() {
  if (this.connection.canSend && this.type === 'sub') {
    this.connection.send({a:'qunsub', id:this.id});
  }

  this.connection._destroyQuery(this);
};

Query.prototype._onConnectionStateChanged = function(state, reason) {
  if (this.connection.state === 'connecting') {
    this._execute();
  }
};

// Internal method called from connection to pass server messages to the query.
Query.prototype._onMessage = function(msg) {
  if ((msg.a === 'qfetch') !== (this.type === 'fetch')) {
    console.warn('Invalid message sent to query', msg, this);
    return;
  }

  if (msg.error) this.emit('error', msg.error);

  switch (msg.a) {
    case 'qfetch':
      var results = msg.data ? this._dataToDocs(msg.data) : undefined;
      if (this.callback) this.callback(msg.error, results, msg.extra);
      // Once a fetch query gets its data, it is destroyed.
      this.connection._destroyQuery(this);
      break;

    case 'q':
      // Query diff data (inserts and removes)
      if (msg.diff) {
        // We need to go through the list twice. First, we'll ingest all the
        // new documents and set them as subscribed.  After that we'll emit
        // events and actually update our list. This avoids race conditions
        // around setting documents to be subscribed & unsubscribing documents
        // in event callbacks.
        for (var i = 0; i < msg.diff.length; i++) {
          var d = msg.diff[i];
          if (d.type === 'insert') d.values = this._dataToDocs(d.values);
        }

        for (var i = 0; i < msg.diff.length; i++) {
          var d = msg.diff[i];
          switch (d.type) {
            case 'insert':
              var newDocs = d.values;
              Array.prototype.splice.apply(this.results, [d.index, 0].concat(newDocs));
              this.emit('insert', newDocs, d.index);
              break;
            case 'remove':
              var howMany = d.howMany || 1;
              var removed = this.results.splice(d.index, howMany);
              this.emit('remove', removed, d.index);
              break;
            case 'move':
              var howMany = d.howMany || 1;
              var docs = this.results.splice(d.from, howMany);
              Array.prototype.splice.apply(this.results, [d.to, 0].concat(docs));
              this.emit('move', docs, d.from, d.to);
              break;
          }
        }
      }

      if (msg.extra !== void 0) {
        this.emit('extra', msg.extra);
      }
      break;
    case 'qsub':
      // This message replaces the entire result set with the set passed.
      if (!msg.error) {
        var previous = this.results;

        // Then add everything in the new result set.
        this.results = this.knownDocs = this._dataToDocs(msg.data);
        this.extra = msg.extra;

        this.ready = true;
        this.emit('change', this.results, previous);
      }
      if (this.callback) {
        this.callback(msg.error, this.results, this.extra);
        delete this.callback;
      }
      break;
  }
};

// Change the thing we're searching for. This isn't fully supported on the
// backend (it destroys the old query and makes a new one) - but its
// programatically useful and I might add backend support at some point.
Query.prototype.setQuery = function(q) {
  if (this.type !== 'sub') throw new Error('cannot change a fetch query');

  this.query = q;
  if (this.connection.canSend) {
    // There's no 'change' message to send to the server. Just resubscribe.
    this.connection.send({a:'qunsub', id:this.id});
    this._execute();
  }
};
<|endoftext|>


<commit_before>'use strict';

const fs = require('fs-extra');
const util = require('util');
const chalk = require('chalk');
const Command = require('../models/command');
const Project = require('../models/project');
const SilentError = require('silent-error');
const isValidProjectName = require('../utilities/valid-project-name');
const normalizeBlueprint = require('../utilities/normalize-blueprint-option');
const mergeBlueprintOptions = require('../utilities/merge-blueprint-options');

const rmdir = util.promisify(fs.remove);

module.exports = Command.extend({
  name: 'new',
  description: `Creates a new directory and runs ${chalk.green('ember init')} in it.`,
  works: 'outsideProject',

  availableOptions: [
    { name: 'dry-run', type: Boolean, default: false, aliases: ['d'] },
    { name: 'verbose', type: Boolean, default: false, aliases: ['v'] },
    { name: 'blueprint', type: String, default: 'app', aliases: ['b'] },
    { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] },
    { name: 'skip-bower', type: Boolean, default: false, aliases: ['sb'] },
    { name: 'skip-git', type: Boolean, default: false, aliases: ['sg'] },
    {
      name: 'welcome',
      type: Boolean,
      default: true,
      description: 'Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it.',
    },
    { name: 'yarn', type: Boolean }, // no default means use yarn if the blueprint has a yarn.lock
    { name: 'directory', type: String, aliases: ['dir'] },
  ],

  anonymousOptions: ['<app-name>'],

  beforeRun: mergeBlueprintOptions,

  async run(commandOptions, rawArgs) {
    let projectName = rawArgs[0],
      message;

    commandOptions.name = rawArgs.shift();

    if (!projectName) {
      message = `The \`ember ${this.name}\` command requires a name to be specified. For more details, use \`ember help\`.`;

      return Promise.reject(new SilentError(message));
    }

    if (commandOptions.dryRun) {
      commandOptions.skipGit = true;
    }

    if (projectName === '.') {
      let blueprintName = commandOptions.blueprint === 'app' ? 'application' : commandOptions.blueprint;
      message = `Trying to generate an ${blueprintName} structure in this directory? Use \`ember init\` instead.`;

      return Promise.reject(new SilentError(message));
    }

    if (!isValidProjectName(projectName)) {
      message = `We currently do not support a name of \`${projectName}\`.`;

      return Promise.reject(new SilentError(message));
    }

    commandOptions.blueprint = normalizeBlueprint(commandOptions.blueprint);

    let InitCommand = this.commands.Init;

    let initCommand = new InitCommand({
      ui: this.ui,
      analytics: this.analytics,
      tasks: this.tasks,
      project: Project.nullProject(this.ui, this.cli),
    });

    let opts = await this.runTask('CreateAndStepIntoDirectory', {
      projectName,
      directoryName: commandOptions.directory,
      dryRun: commandOptions.dryRun,
    });

    initCommand.project.root = process.cwd();

    try {
      let response = await initCommand.run(commandOptions, rawArgs);
      return response;
    } catch (err) {
      let { initialDirectory, projectDirectory } = opts;

      process.chdir(initialDirectory);
      await rmdir(projectDirectory);

      console.log(chalk.red(`Error creating new application. Removing generated directory \`./${projectDirectory}\``));
      throw err;
    }
  },
});
<commit_msg>use fs.remove directly since it is already a promise<commit_after>'use strict';

const fs = require('fs-extra');
const chalk = require('chalk');
const Command = require('../models/command');
const Project = require('../models/project');
const SilentError = require('silent-error');
const isValidProjectName = require('../utilities/valid-project-name');
const normalizeBlueprint = require('../utilities/normalize-blueprint-option');
const mergeBlueprintOptions = require('../utilities/merge-blueprint-options');

module.exports = Command.extend({
  name: 'new',
  description: `Creates a new directory and runs ${chalk.green('ember init')} in it.`,
  works: 'outsideProject',

  availableOptions: [
    { name: 'dry-run', type: Boolean, default: false, aliases: ['d'] },
    { name: 'verbose', type: Boolean, default: false, aliases: ['v'] },
    { name: 'blueprint', type: String, default: 'app', aliases: ['b'] },
    { name: 'skip-npm', type: Boolean, default: false, aliases: ['sn'] },
    { name: 'skip-bower', type: Boolean, default: false, aliases: ['sb'] },
    { name: 'skip-git', type: Boolean, default: false, aliases: ['sg'] },
    {
      name: 'welcome',
      type: Boolean,
      default: true,
      description: 'Installs and uses {{ember-welcome-page}}. Use --no-welcome to skip it.',
    },
    { name: 'yarn', type: Boolean }, // no default means use yarn if the blueprint has a yarn.lock
    { name: 'directory', type: String, aliases: ['dir'] },
  ],

  anonymousOptions: ['<app-name>'],

  beforeRun: mergeBlueprintOptions,

  async run(commandOptions, rawArgs) {
    let projectName = rawArgs[0],
      message;

    commandOptions.name = rawArgs.shift();

    if (!projectName) {
      message = `The \`ember ${this.name}\` command requires a name to be specified. For more details, use \`ember help\`.`;

      return Promise.reject(new SilentError(message));
    }

    if (commandOptions.dryRun) {
      commandOptions.skipGit = true;
    }

    if (projectName === '.') {
      let blueprintName = commandOptions.blueprint === 'app' ? 'application' : commandOptions.blueprint;
      message = `Trying to generate an ${blueprintName} structure in this directory? Use \`ember init\` instead.`;

      return Promise.reject(new SilentError(message));
    }

    if (!isValidProjectName(projectName)) {
      message = `We currently do not support a name of \`${projectName}\`.`;

      return Promise.reject(new SilentError(message));
    }

    commandOptions.blueprint = normalizeBlueprint(commandOptions.blueprint);

    let InitCommand = this.commands.Init;

    let initCommand = new InitCommand({
      ui: this.ui,
      analytics: this.analytics,
      tasks: this.tasks,
      project: Project.nullProject(this.ui, this.cli),
    });

    let opts = await this.runTask('CreateAndStepIntoDirectory', {
      projectName,
      directoryName: commandOptions.directory,
      dryRun: commandOptions.dryRun,
    });

    initCommand.project.root = process.cwd();

    try {
      let response = await initCommand.run(commandOptions, rawArgs);
      return response;
    } catch (err) {
      let { initialDirectory, projectDirectory } = opts;

      process.chdir(initialDirectory);
      await fs.remove(projectDirectory);

      console.log(chalk.red(`Error creating new application. Removing generated directory \`./${projectDirectory}\``));
      throw err;
    }
  },
});
<|endoftext|>


<commit_before>'use strict';

var execAbort = require('../execAbort');
var projectMeta = require('../projectMeta');
var fs = require('fs-extra');
var hbs = require('handlebars');
var path = require('path');
var getTemplates = require('../tasks/getTemplates');
var renameTemplate = require('../tasks/renameTemplate');
var trackCommand = require('../tasks/trackCommand');
var PleasantProgress = require('pleasant-progress');
var chalk = require('chalk');
var checkEnvironment = require('../tasks/checkEnvironment');
var spawnPromise = require('superspawn').spawn;
var execPromise = require('child-process-promise').exec;
var {question} = require('readline-sync');
var projectMeta = require('../projectMeta');
require('shelljs/global');
var getAppNames = require('../tasks/getAppNames');

var serverName = getAppNames.server();
var clientName = getAppNames.client();

function normalizeOption(option) {
  if (option === 'mongodb') {
    return 'mongo';
  }
  if (option === 'postgres') {
    return 'postgresql';
  }
  return option;
}

function prepareFigTemplate(name, option) {
  var figDatabase = null;
  var figPort = 0;
  var figIsMysql = false;

  if (option !== 'disk'){
    figDatabase = option;
  }

  switch (option) {
    case 'mysql':
      figPort = 3306;
      figIsMysql = true;
    break;
    case 'postgresql':
      figPort = 5432;
      figDatabase = 'postgres';
    break;
    case 'mongo':
      figPort = 27017;
      break;
    case 'redis':
      figPort = 6379;
      figDatabase = 'redis';
    break;
  }

  var figVariables = { database: figDatabase, port: figPort, isMysql: figIsMysql, serverName: serverName};
  return prepareTemplate(name, figVariables);
}

function prepareTemplate(name, variables) {
  var template = fs.readFileSync(path.join(projectMeta.sanePath(), 'templates', name), 'utf8');
  template = hbs.compile(template);
  return template(variables);
}

/*
 * Make sure the executed commands work with docker enabled/disabled
 */
function dockerExec(cmd, runsWithDocker, silent, dryRun) {
  if (silent === undefined) {
    silent = false;
  }

  if (dryRun === undefined) {
    dryRun = false;
  }

  var options = { stdio: 'inherit', env: process.env };
  if (silent) {
    options = { env: process.env };
  }

  var cmdMain;
  var cmdArgs;
  var cmds = cmd.split(' ');

  if (runsWithDocker) {
    cmdMain = 'fig';
    cmdArgs = ['run', serverName].concat(cmds);
  } else {
    cmdMain = cmds[0];
    cmdArgs = cmds.slice(1);
    //Note(markus): this assumes that dockerExec is only used for executing serverCommands
    options.cwd = serverName;
  }

  if (dryRun) {

    //simulate an npm i --save dry-run, since npm does not currently have that
    if (cmds[0] === 'npm' &&
    (cmds[1] === 'i' || cmds[1] === 'install')) {
      var filename = path.join(serverName, 'package.json');
      var packageInfo = fs.readFileSync(filename);
      var content = JSON.parse(packageInfo);
      for (var i = 2; i <= cmds.length; i++) {
        if (cmds[i] !== '--save') {
          content.dependencies[cmds[i]] = '*';
        }
      };
      //have to return a promise
      return new Promise(
        function(resolve, reject) {
          fs.writeFileSync(filename, JSON.stringify(content, null, 2));
          resolve();
      });
    }

    //return an empty promise
    return new Promise(
      function(resolve, reject) {
        resolve();
    });
  } else {
    return spawnPromise(cmdMain, cmdArgs, options);
  }
}

function getMigrateType(database) {
  var migrateType = 'safe';
  switch (database) {
    case 'mysql':
      migrateType = 'alter';
      break;
    case 'postgresql':
      migrateType = 'alter';
      break;
    case 'mongo':
      migrateType = 'safe';
      break;
    case 'redis':
      migrateType = 'safe';
      break;
  }

  return migrateType;
}

function isWrongProjectName(name) {
  if(name.includes('.') && name.length > 1){
    return true;
  }
  return false;
}

function cleanUpSails(options, silent){
  //clean up sails to be API only
  fs.removeSync(path.join('server', 'views'));
  fs.removeSync(path.join('server', 'tasks'));
  fs.removeSync(path.join('server', 'Gruntfile.js'));

  var sailsPackages = ['grunt', 'ejs', 'grunt-contrib-clean', 'grunt-contrib-concat',
  'grunt-contrib-copy', 'grunt-contrib-cssmin', 'grunt-contrib-jst', 'grunt-contrib-less',
  'grunt-contrib-uglify', 'grunt-contrib-watch', 'grunt-sails-linker', 'grunt-sync'];

  return dockerExec(`npm rm ${sailsPackages.join(' ')} --save`, options.docker, silent);
}

module.exports = async function newProject(name, options, leek) {
  var ember;
  //use global ember-cli if exists, otherwise the local one
  //TODO(markus): If global version differs from local one, then ask which one to use
  var localEmber = path.join(__dirname, '..', '..', 'node_modules', 'ember-cli', 'bin', 'ember');
  if (!checkEnvironment.emberExists()) {
    ember = localEmber;
  } else {
    ember = 'ember';
    //regex to get semantic version string
    var semVer = new RegExp(/([0-9]+\.[0-9]+\.[0-9]+)/);
    //Gets the first version number that the global ember spits out.
    var emberVersion = exec(`${ember} version`, { silent: true }).output.match(semVer)[0];

    //Note(markus): Somehow do not seem to be able to get access to the globally installed ember-cli package.json
    // var globalEmber = path.relative(process.cwd(), path.join(exec('npm root -g', { silent: true }).output, 'ember-cli', 'package.json'));
    // var emberVersion = require(globalEmber).version;

    //take the local ember version from the package.json. Much faster
    var localEmberVersion = require(path.join(__dirname, '..', '..', 'node_modules', 'ember-cli', 'package.json')).version;
    //compare the different ember versions
    if ( emberVersion !== localEmberVersion){
      var answer = question(chalk.gray(`Detected different versions for sane's locally installed ember-cli (${localEmberVersion}) and your global one (${emberVersion}). Want to use sane's one? (y/n):`) + '  (y) ') || 'y';
      if (answer === 'y' || answer === 'Y') {
        ember = localEmber;
      }
    }
  }

  var installMsg;
  //--docker is set
  if (options.docker) {
    installMsg = 'Setting up Sails project and downloading latest Docker Containers.';
    if (!checkEnvironment.dockerExists()) {
      console.log('sane requires the latest docker/boot2docker/fig to be installed. Check https://github.com/artificialio/sane/blob/master/README.md for more details.');
      console.log('Exitting now.');
      process.exit(1);
    }
  } else {
    installMsg = 'Setting up Sails project locally.';
    if (!checkEnvironment.sailsExists()) {
      console.log('sane requires the latest sails to be installed. Run npm install -g ember-cli.');
      console.log('Exitting now.');
      process.exit(1);
    }
  }

  if(isWrongProjectName(name)) {
    throw new Error(`Sane currently does not support a projectname of '${name}'.`);
  }

  options.database = normalizeOption(options.database);
  var silent = true;
  if (options.verbose) {
    silent = false;
  }

  //All checks are done, log command to analytics, then start command
  //when running tests leek will be undefined
  if (typeof leek !== 'undefined') {
    trackCommand(`new ${name}`, options, leek);
  }

  console.log(`Sane version: ${projectMeta.version()}\n`);

  //if . is passed it creates a new project in the current folder with the name taken from the parent
  if (name !== '.') {
    //Creates the new folder
    execAbort.sync('mkdir ' + name,
      'Error: Creating a new folder failed. Check if the folder \'' + name + '\' already exists.',
      silent);
    //change directories into projectRoot
    cd(name);
  }

  var projectName = path.basename(process.cwd());

  fs.writeFileSync(path.join('fig.yml'), prepareFigTemplate('fig.yml', options.database));
  fs.writeFileSync(path.join('package.json'), prepareTemplate('package.json', { name: projectName, version: projectMeta.version() }));

  var cliConfig = {};
  for (var opt in options) {
    //exclude properties that are not cli options
    if (options.hasOwnProperty(opt) &&
      !opt.startsWith('_') &&
      ['commands', 'options', 'parent'].indexOf(opt) === -1) {
      cliConfig[opt] = options[opt];
  }

  cliConfig['apps'] = [clientName, serverName];
  cliConfig['disableAnalytics'] = false;
}

  //creating a default .sane-cli based on the parameters used in the new command
  fs.writeFileSync(path.join('.sane-cli'), JSON.stringify(cliConfig, null, 2));

  mkdir(serverName);

  //TODO(markus): If we use spawn with stdio inherit we can print the proper output for fog
  //should also fix the ember-cli output
  console.log(chalk.green(installMsg));

  if (!options.docker) {
    process.stdout.write(`sails version: ${exec('sails version', { silent: true }).output}`);
  } else {
    process.stdout.write(`sails version: ${exec('fig run server sails version', { silent: true }).output}`);
  }

  await dockerExec('sails new .', options.docker);

  if (!options.skipNpm) {
    var progress = new PleasantProgress();
    progress.start(chalk.green('Installing Sails packages for tooling via npm.'));
  }

  var sailsPackages = ['sails-generate-ember-blueprints', 'lodash@^2.4.1',
  'morgan', 'pluralize', `sails-${options.database}`];
  await dockerExec(`npm i ${sailsPackages.join(' ')} --save`, options.docker, silent, options.skipNpm);
  await dockerExec('sails generate ember-blueprints', options.docker, silent, options.skipNpm);

  if (!options.skipNpm) {
    progress.stop();
    console.log(chalk.green('Installed Sails packages for tooling via npm.') + '\n');
  } else {
    console.log(chalk.yellow('Warning: skipping npm tooling so you have to manually run the sails generator:'));
    console.log(chalk.yellow(`'sails-generate-ember-blueprints' (${chalk.underline('https://github.com/mphasize/sails-generate-ember-blueprints')})\n`));
  }

  //Creating new ember project
  console.log(chalk.green('Setting up Ember project locally.'));

  var emberArgs = ['new', clientName, '--skip-git'];

  if (options.skipNpm) {
    emberArgs.push('--skip-npm');
  }

  if (options.skipBower) {
    emberArgs.push('--skip-bower');
  }

  process.stdout.write('ember-cli ');
  await spawnPromise(ember, emberArgs, { stdio: 'inherit', env: process.env });

  progress.start(chalk.green('Running tooling scrips for Sane.'));

  //copy over template files
  var templates = getTemplates(projectMeta.sanePath());
  for (var i = 0; i < templates.length; i++) {
    var templateInPath = path.join(projectMeta.sanePath(), 'templates', templates[i]);
    var templateOutPath = renameTemplate(templates[i]);
    if (templates[i].indexOf('_models') > -1) {
      fs.writeFileSync(templateOutPath, prepareTemplate(templates[i],
        { database: options.database, migrateType: getMigrateType(options.database) }));
    } else if (templates[i].indexOf('_connections') > -1) {
      var host = 'localhost';
      if (options.docker) {
        host = 'db';
      }
      fs.writeFileSync(templateOutPath, prepareTemplate(templates[i],
        { host: host }));
    } else {
      fs.outputFileSync(templateOutPath, fs.readFileSync(templateInPath));
    }
  }

  await cleanUpSails(options, silent);

  await spawnPromise('npm', ['install'], { env: process.env });

  progress.stop();
  console.log(chalk.green('Sane Project \'' + projectName + '\' successfully created.'));
};
<commit_msg>Fixed sails dependency message.<commit_after>'use strict';

var execAbort = require('../execAbort');
var projectMeta = require('../projectMeta');
var fs = require('fs-extra');
var hbs = require('handlebars');
var path = require('path');
var getTemplates = require('../tasks/getTemplates');
var renameTemplate = require('../tasks/renameTemplate');
var trackCommand = require('../tasks/trackCommand');
var PleasantProgress = require('pleasant-progress');
var chalk = require('chalk');
var checkEnvironment = require('../tasks/checkEnvironment');
var spawnPromise = require('superspawn').spawn;
var execPromise = require('child-process-promise').exec;
var {question} = require('readline-sync');
var projectMeta = require('../projectMeta');
require('shelljs/global');
var getAppNames = require('../tasks/getAppNames');

var serverName = getAppNames.server();
var clientName = getAppNames.client();

function normalizeOption(option) {
  if (option === 'mongodb') {
    return 'mongo';
  }
  if (option === 'postgres') {
    return 'postgresql';
  }
  return option;
}

function prepareFigTemplate(name, option) {
  var figDatabase = null;
  var figPort = 0;
  var figIsMysql = false;

  if (option !== 'disk'){
    figDatabase = option;
  }

  switch (option) {
    case 'mysql':
      figPort = 3306;
      figIsMysql = true;
    break;
    case 'postgresql':
      figPort = 5432;
      figDatabase = 'postgres';
    break;
    case 'mongo':
      figPort = 27017;
      break;
    case 'redis':
      figPort = 6379;
      figDatabase = 'redis';
    break;
  }

  var figVariables = { database: figDatabase, port: figPort, isMysql: figIsMysql, serverName: serverName};
  return prepareTemplate(name, figVariables);
}

function prepareTemplate(name, variables) {
  var template = fs.readFileSync(path.join(projectMeta.sanePath(), 'templates', name), 'utf8');
  template = hbs.compile(template);
  return template(variables);
}

/*
 * Make sure the executed commands work with docker enabled/disabled
 */
function dockerExec(cmd, runsWithDocker, silent, dryRun) {
  if (silent === undefined) {
    silent = false;
  }

  if (dryRun === undefined) {
    dryRun = false;
  }

  var options = { stdio: 'inherit', env: process.env };
  if (silent) {
    options = { env: process.env };
  }

  var cmdMain;
  var cmdArgs;
  var cmds = cmd.split(' ');

  if (runsWithDocker) {
    cmdMain = 'fig';
    cmdArgs = ['run', serverName].concat(cmds);
  } else {
    cmdMain = cmds[0];
    cmdArgs = cmds.slice(1);
    //Note(markus): this assumes that dockerExec is only used for executing serverCommands
    options.cwd = serverName;
  }

  if (dryRun) {

    //simulate an npm i --save dry-run, since npm does not currently have that
    if (cmds[0] === 'npm' &&
    (cmds[1] === 'i' || cmds[1] === 'install')) {
      var filename = path.join(serverName, 'package.json');
      var packageInfo = fs.readFileSync(filename);
      var content = JSON.parse(packageInfo);
      for (var i = 2; i <= cmds.length; i++) {
        if (cmds[i] !== '--save') {
          content.dependencies[cmds[i]] = '*';
        }
      };
      //have to return a promise
      return new Promise(
        function(resolve, reject) {
          fs.writeFileSync(filename, JSON.stringify(content, null, 2));
          resolve();
      });
    }

    //return an empty promise
    return new Promise(
      function(resolve, reject) {
        resolve();
    });
  } else {
    return spawnPromise(cmdMain, cmdArgs, options);
  }
}

function getMigrateType(database) {
  var migrateType = 'safe';
  switch (database) {
    case 'mysql':
      migrateType = 'alter';
      break;
    case 'postgresql':
      migrateType = 'alter';
      break;
    case 'mongo':
      migrateType = 'safe';
      break;
    case 'redis':
      migrateType = 'safe';
      break;
  }

  return migrateType;
}

function isWrongProjectName(name) {
  if(name.includes('.') && name.length > 1){
    return true;
  }
  return false;
}

function cleanUpSails(options, silent){
  //clean up sails to be API only
  fs.removeSync(path.join('server', 'views'));
  fs.removeSync(path.join('server', 'tasks'));
  fs.removeSync(path.join('server', 'Gruntfile.js'));

  var sailsPackages = ['grunt', 'ejs', 'grunt-contrib-clean', 'grunt-contrib-concat',
  'grunt-contrib-copy', 'grunt-contrib-cssmin', 'grunt-contrib-jst', 'grunt-contrib-less',
  'grunt-contrib-uglify', 'grunt-contrib-watch', 'grunt-sails-linker', 'grunt-sync'];

  return dockerExec(`npm rm ${sailsPackages.join(' ')} --save`, options.docker, silent);
}

module.exports = async function newProject(name, options, leek) {
  var ember;
  //use global ember-cli if exists, otherwise the local one
  //TODO(markus): If global version differs from local one, then ask which one to use
  var localEmber = path.join(__dirname, '..', '..', 'node_modules', 'ember-cli', 'bin', 'ember');
  if (!checkEnvironment.emberExists()) {
    ember = localEmber;
  } else {
    ember = 'ember';
    //regex to get semantic version string
    var semVer = new RegExp(/([0-9]+\.[0-9]+\.[0-9]+)/);
    //Gets the first version number that the global ember spits out.
    var emberVersion = exec(`${ember} version`, { silent: true }).output.match(semVer)[0];

    //Note(markus): Somehow do not seem to be able to get access to the globally installed ember-cli package.json
    // var globalEmber = path.relative(process.cwd(), path.join(exec('npm root -g', { silent: true }).output, 'ember-cli', 'package.json'));
    // var emberVersion = require(globalEmber).version;

    //take the local ember version from the package.json. Much faster
    var localEmberVersion = require(path.join(__dirname, '..', '..', 'node_modules', 'ember-cli', 'package.json')).version;
    //compare the different ember versions
    if ( emberVersion !== localEmberVersion){
      var answer = question(chalk.gray(`Detected different versions for sane's locally installed ember-cli (${localEmberVersion}) and your global one (${emberVersion}). Want to use sane's one? (y/n):`) + '  (y) ') || 'y';
      if (answer === 'y' || answer === 'Y') {
        ember = localEmber;
      }
    }
  }

  var installMsg;
  //--docker is set
  if (options.docker) {
    installMsg = 'Setting up Sails project and downloading latest Docker Containers.';
    if (!checkEnvironment.dockerExists()) {
      console.log('sane requires the latest docker/boot2docker/fig to be installed. Check https://github.com/artificialio/sane/blob/master/README.md for more details.');
      console.log('Exitting now.');
      process.exit(1);
    }
  } else {
    installMsg = 'Setting up Sails project locally.';
    if (!checkEnvironment.sailsExists()) {
      console.log('sane requires the latest sails to be installed. Run npm install -g sails.');
      console.log('Exitting now.');
      process.exit(1);
    }
  }

  if(isWrongProjectName(name)) {
    throw new Error(`Sane currently does not support a projectname of '${name}'.`);
  }

  options.database = normalizeOption(options.database);
  var silent = true;
  if (options.verbose) {
    silent = false;
  }

  //All checks are done, log command to analytics, then start command
  //when running tests leek will be undefined
  if (typeof leek !== 'undefined') {
    trackCommand(`new ${name}`, options, leek);
  }

  console.log(`Sane version: ${projectMeta.version()}\n`);

  //if . is passed it creates a new project in the current folder with the name taken from the parent
  if (name !== '.') {
    //Creates the new folder
    execAbort.sync('mkdir ' + name,
      'Error: Creating a new folder failed. Check if the folder \'' + name + '\' already exists.',
      silent);
    //change directories into projectRoot
    cd(name);
  }

  var projectName = path.basename(process.cwd());

  fs.writeFileSync(path.join('fig.yml'), prepareFigTemplate('fig.yml', options.database));
  fs.writeFileSync(path.join('package.json'), prepareTemplate('package.json', { name: projectName, version: projectMeta.version() }));

  var cliConfig = {};
  for (var opt in options) {
    //exclude properties that are not cli options
    if (options.hasOwnProperty(opt) &&
      !opt.startsWith('_') &&
      ['commands', 'options', 'parent'].indexOf(opt) === -1) {
      cliConfig[opt] = options[opt];
  }

  cliConfig['apps'] = [clientName, serverName];
  cliConfig['disableAnalytics'] = false;
}

  //creating a default .sane-cli based on the parameters used in the new command
  fs.writeFileSync(path.join('.sane-cli'), JSON.stringify(cliConfig, null, 2));

  mkdir(serverName);

  //TODO(markus): If we use spawn with stdio inherit we can print the proper output for fog
  //should also fix the ember-cli output
  console.log(chalk.green(installMsg));

  if (!options.docker) {
    process.stdout.write(`sails version: ${exec('sails version', { silent: true }).output}`);
  } else {
    process.stdout.write(`sails version: ${exec('fig run server sails version', { silent: true }).output}`);
  }

  await dockerExec('sails new .', options.docker);

  if (!options.skipNpm) {
    var progress = new PleasantProgress();
    progress.start(chalk.green('Installing Sails packages for tooling via npm.'));
  }

  var sailsPackages = ['sails-generate-ember-blueprints', 'lodash@^2.4.1',
  'morgan', 'pluralize', `sails-${options.database}`];
  await dockerExec(`npm i ${sailsPackages.join(' ')} --save`, options.docker, silent, options.skipNpm);
  await dockerExec('sails generate ember-blueprints', options.docker, silent, options.skipNpm);

  if (!options.skipNpm) {
    progress.stop();
    console.log(chalk.green('Installed Sails packages for tooling via npm.') + '\n');
  } else {
    console.log(chalk.yellow('Warning: skipping npm tooling so you have to manually run the sails generator:'));
    console.log(chalk.yellow(`'sails-generate-ember-blueprints' (${chalk.underline('https://github.com/mphasize/sails-generate-ember-blueprints')})\n`));
  }

  //Creating new ember project
  console.log(chalk.green('Setting up Ember project locally.'));

  var emberArgs = ['new', clientName, '--skip-git'];

  if (options.skipNpm) {
    emberArgs.push('--skip-npm');
  }

  if (options.skipBower) {
    emberArgs.push('--skip-bower');
  }

  process.stdout.write('ember-cli ');
  await spawnPromise(ember, emberArgs, { stdio: 'inherit', env: process.env });

  progress.start(chalk.green('Running tooling scrips for Sane.'));

  //copy over template files
  var templates = getTemplates(projectMeta.sanePath());
  for (var i = 0; i < templates.length; i++) {
    var templateInPath = path.join(projectMeta.sanePath(), 'templates', templates[i]);
    var templateOutPath = renameTemplate(templates[i]);
    if (templates[i].indexOf('_models') > -1) {
      fs.writeFileSync(templateOutPath, prepareTemplate(templates[i],
        { database: options.database, migrateType: getMigrateType(options.database) }));
    } else if (templates[i].indexOf('_connections') > -1) {
      var host = 'localhost';
      if (options.docker) {
        host = 'db';
      }
      fs.writeFileSync(templateOutPath, prepareTemplate(templates[i],
        { host: host }));
    } else {
      fs.outputFileSync(templateOutPath, fs.readFileSync(templateInPath));
    }
  }

  await cleanUpSails(options, silent);

  await spawnPromise('npm', ['install'], { env: process.env });

  progress.stop();
  console.log(chalk.green('Sane Project \'' + projectName + '\' successfully created.'));
};
<|endoftext|>