<commit_before>/**
 * @fileOverview Render sectors of a pie
 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Animate from 'react-smooth';
import classNames from 'classnames';
import _ from 'lodash';
import pureRender from '../util/PureRender';
import Layer from '../container/Layer';
import Sector from '../shape/Sector';
import Curve from '../shape/Curve';
import Text from '../component/Text';
import Label from '../component/Label';
import LabelList from '../component/LabelList';
import Cell from '../component/Cell';
import { PRESENTATION_ATTRIBUTES, EVENT_ATTRIBUTES, LEGEND_TYPES,
  getPresentationAttributes, findAllByType, filterEventsOfChild, isSsr } from '../util/ReactUtils';
import { polarToCartesian, getMaxRadius } from '../util/PolarUtils';
import { isNumber, getPercentValue, mathSign, interpolateNumber, uniqueId } from '../util/DataUtils';
import { getValueByDataKey } from '../util/ChartUtils';
import { warn } from '../util/LogUtils';

@pureRender
class Pie extends Component {

  static displayName = 'Pie';

  static propTypes = {
    ...PRESENTATION_ATTRIBUTES,
    ...EVENT_ATTRIBUTES,
    className: PropTypes.string,
    animationId: PropTypes.number,
    cx: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    cy: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    startAngle: PropTypes.number,
    endAngle: PropTypes.number,
    paddingAngle: PropTypes.number,
    innerRadius: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    outerRadius: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    cornerRadius: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    dataKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired,
    nameKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
    valueKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
    data: PropTypes.arrayOf(PropTypes.object),
    minAngle: PropTypes.number,
    legendType: PropTypes.oneOf(LEGEND_TYPES),
    maxRadius: PropTypes.number,

    sectors: PropTypes.arrayOf(PropTypes.object),
    hide: PropTypes.bool,
    labelLine: PropTypes.oneOfType([
      PropTypes.object, PropTypes.func, PropTypes.element, PropTypes.bool,
    ]),
    label: PropTypes.oneOfType([
      PropTypes.shape({
        offsetRadius: PropTypes.number,
      }),
      PropTypes.func, PropTypes.element, PropTypes.bool,
    ]),
    activeShape: PropTypes.oneOfType([
      PropTypes.object, PropTypes.func, PropTypes.element,
    ]),
    activeIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]),

    isAnimationActive: PropTypes.bool,
    animationBegin: PropTypes.number,
    animationDuration: PropTypes.number,
    animationEasing: PropTypes.oneOf([
      'ease',
      'ease-in',
      'ease-out',
      'ease-in-out',
      'spring',
      'linear',
    ]),
    id: PropTypes.string,
  };

  static defaultProps = {
    stroke: '#fff',
    fill: '#808080',
    legendType: 'rect',
    // The abscissa of pole
    cx: '50%',
    // The ordinate of pole
    cy: '50%',
    // The start angle of first sector
    startAngle: 0,
    // The direction of drawing sectors
    endAngle: 360,
    // The inner radius of sectors
    innerRadius: 0,
    // The outer radius of sectors
    outerRadius: '80%',
    paddingAngle: 0,
    labelLine: true,
    hide: false,
    minAngle: 0,
    isAnimationActive: !isSsr(),
    animationBegin: 400,
    animationDuration: 1500,
    animationEasing: 'ease',
    nameKey: 'name',
  };

  static parseDeltaAngle = ({ startAngle, endAngle }) => {
    const sign = mathSign(endAngle - startAngle);
    const deltaAngle = Math.min(Math.abs(endAngle - startAngle), 360);

    return sign * deltaAngle;
  };

  static getRealPieData = (item) => {
    const { data, children } = item.props;
    const presentationProps = getPresentationAttributes(item.props);
    const cells = findAllByType(children, Cell);

    if (data && data.length) {
      return data.map((entry, index) => ({
        payload: entry,
        ...presentationProps,
        ...entry,
        ...(cells && cells[index] && cells[index].props),
      }));
    }

    if (cells && cells.length) {
      return cells.map(cell => ({ ...presentationProps, ...cell.props }));
    }

    return [];
  };

  static parseCoordinateOfPie = (item, offset) => {
    const { top, left, width, height } = offset;
    const maxPieRadius = getMaxRadius(width, height);
    const cx = left + getPercentValue(item.props.cx, width, width / 2);
    const cy = top + getPercentValue(item.props.cy, height, height / 2);
    const innerRadius = getPercentValue(item.props.innerRadius, maxPieRadius, 0);
    const outerRadius = getPercentValue(item.props.outerRadius, maxPieRadius, maxPieRadius * 0.8);
    const maxRadius = item.props.maxRadius || Math.sqrt(width * width + height * height) / 2;

    return { cx, cy, innerRadius, outerRadius, maxRadius };
  }

  static getComposedData = ({ item, offset, onItemMouseLeave, onItemMouseEnter }) => {
    const pieData = Pie.getRealPieData(item);
    if (!pieData || !pieData.length) { return []; }

    const { cornerRadius, startAngle, endAngle, paddingAngle, dataKey, nameKey,
      valueKey } = item.props;
    const minAngle = Math.abs(item.props.minAngle);
    const coordinate = Pie.parseCoordinateOfPie(item, offset);
    const len = pieData.length;
    const deltaAngle = Pie.parseDeltaAngle({ startAngle, endAngle });
    const absDeltaAngle = Math.abs(deltaAngle);
    const totalPadingAngle = (absDeltaAngle >= 360 ? len : (len - 1)) * paddingAngle;
    const realTotalAngle = absDeltaAngle - len * minAngle - totalPadingAngle;
    let realDataKey = dataKey;

    if (_.isNil(dataKey) && _.isNil(valueKey)) {
      warn(false,
        `Use "dataKey" to specify the value of pie,
      the props "valueKey" will be deprecated in 1.1.0`);
      realDataKey = 'value';
    } else if (_.isNil(dataKey)) {
      warn(false,
        `Use "dataKey" to specify the value of pie,
      the props "valueKey" will be deprecated in 1.1.0`);
      realDataKey = valueKey;
    }

    const sum = pieData.reduce((result, entry) => {
      const val = getValueByDataKey(entry, realDataKey, 0);
      return result + (isNumber(val) ? val : 0);
    }, 0);
    let sectors;

    if (sum > 0) {
      let prev;
      sectors = pieData.map((entry, i) => {
        const val = getValueByDataKey(entry, realDataKey, 0);
        const name = getValueByDataKey(entry, nameKey, i);
        const percent = (isNumber(val) ? val : 0) / sum;
        let tempStartAngle;

        if (i) {
          tempStartAngle = prev.endAngle + mathSign(deltaAngle) * paddingAngle;
        } else {
          tempStartAngle = startAngle;
        }

        const tempEndAngle = tempStartAngle + mathSign(deltaAngle) *
          (minAngle + percent * realTotalAngle);
        const midAngle = (tempStartAngle + tempEndAngle) / 2;
        const middleRadius = (coordinate.innerRadius + coordinate.outerRadius) / 2;
        const tooltipPayload = [{ name, value: val, payload: entry }];
        const tooltipPosition = polarToCartesian(
          coordinate.cx, coordinate.cy, middleRadius, midAngle
        );

        prev = {
          percent, cornerRadius, name, tooltipPayload, midAngle, middleRadius, tooltipPosition,
          ...entry,
          ...coordinate,
          value: getValueByDataKey(entry, realDataKey),
          startAngle: tempStartAngle,
          endAngle: tempEndAngle,
          payload: entry,
          paddingAngle: mathSign(deltaAngle) * paddingAngle,
        };

        return prev;
      });
    }

    return {
      ...coordinate,
      sectors,
      data: pieData,
      onMouseLeave: onItemMouseLeave,
      onMouseEnter: onItemMouseEnter,
    };
  }

  state = { isAnimationFinished: false };

  componentWillReceiveProps(nextProps) {
    const { animationId, sectors } = this.props;

    if (nextProps.isAnimationActive !== this.props.isAnimationActive) {
      this.cachePrevData([]);
    } else if (nextProps.animationId !== animationId) {
      this.cachePrevData(sectors);
    }
  }

  static getTextAnchor(x, cx) {
    if (x > cx) {
      return 'start';
    } else if (x < cx) {
      return 'end';
    }

    return 'middle';
  }

  id = uniqueId('recharts-pie-');

  cachePrevData = (sectors) => {
    this.setState({ prevSectors: sectors });
  };

  isActiveIndex(i) {
    const { activeIndex } = this.props;

    if (Array.isArray(activeIndex)) {
      return activeIndex.indexOf(i) !== -1;
    }

    return i === activeIndex;
  }

  handleAnimationEnd = () => {
    this.setState({
      isAnimationFinished: true,
    });
  };

  handleAnimationStart = () => {
    this.setState({
      isAnimationFinished: false,
    });
  }

  static renderLabelLineItem(option, props) {
    if (React.isValidElement(option)) {
      return React.cloneElement(option, props);
    } else if (_.isFunction(option)) {
      return option(props);
    }

    return <Curve {...props} type="linear" className="recharts-pie-label-line" />;
  }

  static renderLabelItem(option, props, value) {
    if (React.isValidElement(option)) {
      return React.cloneElement(option, props);
    }
    let label = value;
    if (_.isFunction(option)) {
      label = option(props);
      if (React.isValidElement(label)) {
        return label;
      }
    }

    return (
      <Text
        {...props}
        alignmentBaseline="middle"
        className="recharts-pie-label-text"
      >
        {label}
      </Text>
    );
  }

  renderLabels(sectors) {
    const { isAnimationActive } = this.props;

    if (isAnimationActive && !this.state.isAnimationFinished) {
      return null;
    }
    const { label, labelLine, dataKey, valueKey } = this.props;
    const pieProps = getPresentationAttributes(this.props);
    const customLabelProps = getPresentationAttributes(label);
    const customLabelLineProps = getPresentationAttributes(labelLine);
    const offsetRadius = (label && label.offsetRadius) || 20;

    const labels = sectors.map((entry, i) => {
      const midAngle = (entry.startAngle + entry.endAngle) / 2;
      const endPoint = polarToCartesian(
        entry.cx, entry.cy, entry.outerRadius + offsetRadius, midAngle
      );
      const labelProps = {
        ...pieProps,
        ...entry,
        stroke: 'none',
        ...customLabelProps,
        index: i,
        textAnchor: this.constructor.getTextAnchor(endPoint.x, entry.cx),
        ...endPoint,
      };
      const lineProps = {
        ...pieProps,
        ...entry,
        fill: 'none',
        stroke: entry.fill,
        ...customLabelLineProps,
        index: i,
        points: [polarToCartesian(entry.cx, entry.cy, entry.outerRadius, midAngle), endPoint],
      };
      let realDataKey = dataKey;
      // TODO: compatible to lower versions
      if (_.isNil(dataKey) && _.isNil(valueKey)) {
        realDataKey = 'value';
      } else if (_.isNil(dataKey)) {
        realDataKey = valueKey;
      }

      return (
        <Layer key={`label-${i}`}>
          {labelLine && this.constructor.renderLabelLineItem(labelLine, lineProps)}
          {this.constructor.renderLabelItem(
            label,
            labelProps,
            getValueByDataKey(entry, realDataKey)
          )}
        </Layer>
      );
    });

    return <Layer className="recharts-pie-labels">{labels}</Layer>;
  }

  static renderSectorItem(option, props) {
    if (React.isValidElement(option)) {
      return React.cloneElement(option, props);
    } else if (_.isFunction(option)) {
      return option(props);
    } else if (_.isPlainObject(option)) {
      return <Sector {...props} {...option} />;
    }

    return <Sector {...props} />;
  }

  renderSectorsStatically(sectors) {
    const { activeShape } = this.props;

    return sectors.map((entry, i) => (
      <Layer
        className="recharts-pie-sector"
        {...filterEventsOfChild(this.props, entry, i)}
        key={`sector-${i}`}
      >
        {this.constructor.renderSectorItem(this.isActiveIndex(i) ? activeShape : null, entry)}
      </Layer>
    ));
  }

  renderSectorsWithAnimation() {
    const { sectors, isAnimationActive, animationBegin, animationDuration,
      animationEasing, animationId } = this.props;
    const { prevSectors } = this.state;

    return (
      <Animate
        begin={animationBegin}
        duration={animationDuration}
        isActive={isAnimationActive}
        easing={animationEasing}
        from={{ t: 0 }}
        to={{ t: 1 }}
        key={`pie-${animationId}`}
        onAnimationStart={this.handleAnimationStart}
        onAnimationEnd={this.handleAnimationEnd}
      >
        {
          ({ t }) => {
            const stepData = [];
            const first = sectors && sectors[0];
            let curAngle = first.startAngle;

            sectors.forEach((entry, index) => {
              const prev = prevSectors && prevSectors[index];
              const paddingAngle = index > 0 ? entry.paddingAngle : 0;

              if (prev) {
                const angleIp = interpolateNumber(
                  prev.endAngle - prev.startAngle,
                  entry.endAngle - entry.startAngle
                );
                const latest = {
                  ...entry,
                  startAngle: curAngle + paddingAngle,
                  endAngle: curAngle + angleIp(t) + paddingAngle,
                };

                stepData.push(latest);
                curAngle = latest.endAngle;
              } else {
                const { endAngle, startAngle } = entry;
                const interpolatorAngle = interpolateNumber(0, endAngle - startAngle);
                const deltaAngle = interpolatorAngle(t);
                const latest = {
                  ...entry,
                  startAngle: curAngle + paddingAngle,
                  endAngle: curAngle + deltaAngle + paddingAngle,
                };

                stepData.push(latest);
                curAngle = latest.endAngle;
              }
            });

            return (
              <Layer>
                {this.renderSectorsStatically(stepData)}
              </Layer>
            );
          }
        }
      </Animate>
    );
  }

  renderSectors() {
    const { sectors, isAnimationActive } = this.props;
    const { prevSectors } = this.state;

    if (isAnimationActive && sectors && sectors.length &&
      (!prevSectors || !_.isEqual(prevSectors, sectors))) {
      return this.renderSectorsWithAnimation();
    }
    return this.renderSectorsStatically(sectors);
  }

  render() {
    const { hide, sectors, className, label, cx, cy, innerRadius,
      outerRadius, isAnimationActive, prevSectors, id } = this.props;

    if (hide || !sectors || !sectors.length || !isNumber(cx)
      || !isNumber(cy) || !isNumber(innerRadius)
      || !isNumber(outerRadius)) {
      return null;
    }

    const layerClass = classNames('recharts-pie', className);

    return (
      <Layer className={layerClass}>
        <g clipPath={`url(#${_.isNil(id) ? this.id : id})`}>
          {this.renderSectors()}
        </g>
        {label && this.renderLabels(sectors)}
        {Label.renderCallByParent(this.props, null, false)}
        {(!isAnimationActive || (prevSectors && _.isEqual(prevSectors, sectors))) &&
          LabelList.renderCallByParent(this.props, sectors, false)}
      </Layer>
    );
  }
}

export default Pie;
<commit_msg>Add blendStroke prop to Pie component<commit_after>/**
 * @fileOverview Render sectors of a pie
 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import Animate from 'react-smooth';
import classNames from 'classnames';
import _ from 'lodash';
import pureRender from '../util/PureRender';
import Layer from '../container/Layer';
import Sector from '../shape/Sector';
import Curve from '../shape/Curve';
import Text from '../component/Text';
import Label from '../component/Label';
import LabelList from '../component/LabelList';
import Cell from '../component/Cell';
import { PRESENTATION_ATTRIBUTES, EVENT_ATTRIBUTES, LEGEND_TYPES,
  getPresentationAttributes, findAllByType, filterEventsOfChild, isSsr } from '../util/ReactUtils';
import { polarToCartesian, getMaxRadius } from '../util/PolarUtils';
import { isNumber, getPercentValue, mathSign, interpolateNumber, uniqueId } from '../util/DataUtils';
import { getValueByDataKey } from '../util/ChartUtils';
import { warn } from '../util/LogUtils';

@pureRender
class Pie extends Component {

  static displayName = 'Pie';

  static propTypes = {
    ...PRESENTATION_ATTRIBUTES,
    ...EVENT_ATTRIBUTES,
    className: PropTypes.string,
    animationId: PropTypes.number,
    cx: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    cy: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    startAngle: PropTypes.number,
    endAngle: PropTypes.number,
    paddingAngle: PropTypes.number,
    innerRadius: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    outerRadius: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    cornerRadius: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
    dataKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]).isRequired,
    nameKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
    valueKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.func]),
    data: PropTypes.arrayOf(PropTypes.object),
    blendStroke: PropTypes.bool,
    minAngle: PropTypes.number,
    legendType: PropTypes.oneOf(LEGEND_TYPES),
    maxRadius: PropTypes.number,

    sectors: PropTypes.arrayOf(PropTypes.object),
    hide: PropTypes.bool,
    labelLine: PropTypes.oneOfType([
      PropTypes.object, PropTypes.func, PropTypes.element, PropTypes.bool,
    ]),
    label: PropTypes.oneOfType([
      PropTypes.shape({
        offsetRadius: PropTypes.number,
      }),
      PropTypes.func, PropTypes.element, PropTypes.bool,
    ]),
    activeShape: PropTypes.oneOfType([
      PropTypes.object, PropTypes.func, PropTypes.element,
    ]),
    activeIndex: PropTypes.oneOfType([PropTypes.number, PropTypes.arrayOf(PropTypes.number)]),

    isAnimationActive: PropTypes.bool,
    animationBegin: PropTypes.number,
    animationDuration: PropTypes.number,
    animationEasing: PropTypes.oneOf([
      'ease',
      'ease-in',
      'ease-out',
      'ease-in-out',
      'spring',
      'linear',
    ]),
    id: PropTypes.string,
  };

  static defaultProps = {
    stroke: '#fff',
    fill: '#808080',
    legendType: 'rect',
    // The abscissa of pole
    cx: '50%',
    // The ordinate of pole
    cy: '50%',
    // The start angle of first sector
    startAngle: 0,
    // The direction of drawing sectors
    endAngle: 360,
    // The inner radius of sectors
    innerRadius: 0,
    // The outer radius of sectors
    outerRadius: '80%',
    paddingAngle: 0,
    labelLine: true,
    hide: false,
    minAngle: 0,
    isAnimationActive: !isSsr(),
    animationBegin: 400,
    animationDuration: 1500,
    animationEasing: 'ease',
    nameKey: 'name',
    // Match each sector's stroke color to it's fill color
    blendStroke: false
  };

  static parseDeltaAngle = ({ startAngle, endAngle }) => {
    const sign = mathSign(endAngle - startAngle);
    const deltaAngle = Math.min(Math.abs(endAngle - startAngle), 360);

    return sign * deltaAngle;
  };

  static getRealPieData = (item) => {
    const { data, children } = item.props;
    const presentationProps = getPresentationAttributes(item.props);
    const cells = findAllByType(children, Cell);

    if (data && data.length) {
      return data.map((entry, index) => ({
        payload: entry,
        ...presentationProps,
        ...entry,
        ...(cells && cells[index] && cells[index].props),
      }));
    }

    if (cells && cells.length) {
      return cells.map(cell => ({ ...presentationProps, ...cell.props }));
    }

    return [];
  };

  static parseCoordinateOfPie = (item, offset) => {
    const { top, left, width, height } = offset;
    const maxPieRadius = getMaxRadius(width, height);
    const cx = left + getPercentValue(item.props.cx, width, width / 2);
    const cy = top + getPercentValue(item.props.cy, height, height / 2);
    const innerRadius = getPercentValue(item.props.innerRadius, maxPieRadius, 0);
    const outerRadius = getPercentValue(item.props.outerRadius, maxPieRadius, maxPieRadius * 0.8);
    const maxRadius = item.props.maxRadius || Math.sqrt(width * width + height * height) / 2;

    return { cx, cy, innerRadius, outerRadius, maxRadius };
  }

  static getComposedData = ({ item, offset, onItemMouseLeave, onItemMouseEnter }) => {
    const pieData = Pie.getRealPieData(item);
    if (!pieData || !pieData.length) { return []; }

    const { cornerRadius, startAngle, endAngle, paddingAngle, dataKey, nameKey,
      valueKey } = item.props;
    const minAngle = Math.abs(item.props.minAngle);
    const coordinate = Pie.parseCoordinateOfPie(item, offset);
    const len = pieData.length;
    const deltaAngle = Pie.parseDeltaAngle({ startAngle, endAngle });
    const absDeltaAngle = Math.abs(deltaAngle);
    const totalPadingAngle = (absDeltaAngle >= 360 ? len : (len - 1)) * paddingAngle;
    const realTotalAngle = absDeltaAngle - len * minAngle - totalPadingAngle;
    let realDataKey = dataKey;

    if (_.isNil(dataKey) && _.isNil(valueKey)) {
      warn(false,
        `Use "dataKey" to specify the value of pie,
      the props "valueKey" will be deprecated in 1.1.0`);
      realDataKey = 'value';
    } else if (_.isNil(dataKey)) {
      warn(false,
        `Use "dataKey" to specify the value of pie,
      the props "valueKey" will be deprecated in 1.1.0`);
      realDataKey = valueKey;
    }

    const sum = pieData.reduce((result, entry) => {
      const val = getValueByDataKey(entry, realDataKey, 0);
      return result + (isNumber(val) ? val : 0);
    }, 0);
    let sectors;

    if (sum > 0) {
      let prev;
      sectors = pieData.map((entry, i) => {
        const val = getValueByDataKey(entry, realDataKey, 0);
        const name = getValueByDataKey(entry, nameKey, i);
        const percent = (isNumber(val) ? val : 0) / sum;
        let tempStartAngle;

        if (i) {
          tempStartAngle = prev.endAngle + mathSign(deltaAngle) * paddingAngle;
        } else {
          tempStartAngle = startAngle;
        }

        const tempEndAngle = tempStartAngle + mathSign(deltaAngle) *
          (minAngle + percent * realTotalAngle);
        const midAngle = (tempStartAngle + tempEndAngle) / 2;
        const middleRadius = (coordinate.innerRadius + coordinate.outerRadius) / 2;
        const tooltipPayload = [{ name, value: val, payload: entry }];
        const tooltipPosition = polarToCartesian(
          coordinate.cx, coordinate.cy, middleRadius, midAngle
        );

        prev = {
          percent, cornerRadius, name, tooltipPayload, midAngle, middleRadius, tooltipPosition,
          ...entry,
          ...coordinate,
          value: getValueByDataKey(entry, realDataKey),
          startAngle: tempStartAngle,
          endAngle: tempEndAngle,
          payload: entry,
          paddingAngle: mathSign(deltaAngle) * paddingAngle,
        };

        return prev;
      });
    }

    return {
      ...coordinate,
      sectors,
      data: pieData,
      onMouseLeave: onItemMouseLeave,
      onMouseEnter: onItemMouseEnter,
    };
  }

  state = { isAnimationFinished: false };

  componentWillReceiveProps(nextProps) {
    const { animationId, sectors } = this.props;

    if (nextProps.isAnimationActive !== this.props.isAnimationActive) {
      this.cachePrevData([]);
    } else if (nextProps.animationId !== animationId) {
      this.cachePrevData(sectors);
    }
  }

  static getTextAnchor(x, cx) {
    if (x > cx) {
      return 'start';
    } else if (x < cx) {
      return 'end';
    }

    return 'middle';
  }

  id = uniqueId('recharts-pie-');

  cachePrevData = (sectors) => {
    this.setState({ prevSectors: sectors });
  };

  isActiveIndex(i) {
    const { activeIndex } = this.props;

    if (Array.isArray(activeIndex)) {
      return activeIndex.indexOf(i) !== -1;
    }

    return i === activeIndex;
  }

  handleAnimationEnd = () => {
    this.setState({
      isAnimationFinished: true,
    });
  };

  handleAnimationStart = () => {
    this.setState({
      isAnimationFinished: false,
    });
  }

  static renderLabelLineItem(option, props) {
    if (React.isValidElement(option)) {
      return React.cloneElement(option, props);
    } else if (_.isFunction(option)) {
      return option(props);
    }

    return <Curve {...props} type="linear" className="recharts-pie-label-line" />;
  }

  static renderLabelItem(option, props, value) {
    if (React.isValidElement(option)) {
      return React.cloneElement(option, props);
    }
    let label = value;
    if (_.isFunction(option)) {
      label = option(props);
      if (React.isValidElement(label)) {
        return label;
      }
    }

    return (
      <Text
        {...props}
        alignmentBaseline="middle"
        className="recharts-pie-label-text"
      >
        {label}
      </Text>
    );
  }

  renderLabels(sectors) {
    const { isAnimationActive } = this.props;

    if (isAnimationActive && !this.state.isAnimationFinished) {
      return null;
    }
    const { label, labelLine, dataKey, valueKey } = this.props;
    const pieProps = getPresentationAttributes(this.props);
    const customLabelProps = getPresentationAttributes(label);
    const customLabelLineProps = getPresentationAttributes(labelLine);
    const offsetRadius = (label && label.offsetRadius) || 20;

    const labels = sectors.map((entry, i) => {
      const midAngle = (entry.startAngle + entry.endAngle) / 2;
      const endPoint = polarToCartesian(
        entry.cx, entry.cy, entry.outerRadius + offsetRadius, midAngle
      );
      const labelProps = {
        ...pieProps,
        ...entry,
        stroke: 'none',
        ...customLabelProps,
        index: i,
        textAnchor: this.constructor.getTextAnchor(endPoint.x, entry.cx),
        ...endPoint,
      };
      const lineProps = {
        ...pieProps,
        ...entry,
        fill: 'none',
        stroke: entry.fill,
        ...customLabelLineProps,
        index: i,
        points: [polarToCartesian(entry.cx, entry.cy, entry.outerRadius, midAngle), endPoint],
      };
      let realDataKey = dataKey;
      // TODO: compatible to lower versions
      if (_.isNil(dataKey) && _.isNil(valueKey)) {
        realDataKey = 'value';
      } else if (_.isNil(dataKey)) {
        realDataKey = valueKey;
      }

      return (
        <Layer key={`label-${i}`}>
          {labelLine && this.constructor.renderLabelLineItem(labelLine, lineProps)}
          {this.constructor.renderLabelItem(
            label,
            labelProps,
            getValueByDataKey(entry, realDataKey)
          )}
        </Layer>
      );
    });

    return <Layer className="recharts-pie-labels">{labels}</Layer>;
  }

  static renderSectorItem(option, props) {
    if (React.isValidElement(option)) {
      return React.cloneElement(option, props);
    } else if (_.isFunction(option)) {
      return option(props);
    } else if (_.isPlainObject(option)) {
      return <Sector {...props} {...option} />;
    }

    return <Sector {...props} />;
  }

  renderSectorsStatically(sectors) {
    const { activeShape, blendStroke } = this.props;

    return sectors.map((entry, i) => {
      const sectorOptions = this.isActiveIndex(i) ? activeShape : null;
      const sectorProps = {
        ...entry,
        stroke: blendStroke ? entry.fill : entry.stroke
      };

      return (
        <Layer
          className="recharts-pie-sector"
          {...filterEventsOfChild(this.props, entry, i)}
          key={`sector-${i}`}
        >
          {this.constructor.renderSectorItem(sectorOptions, sectorProps)}
        </Layer>
      );
    });
  }

  renderSectorsWithAnimation() {
    const { sectors, isAnimationActive, animationBegin, animationDuration,
      animationEasing, animationId } = this.props;
    const { prevSectors } = this.state;

    return (
      <Animate
        begin={animationBegin}
        duration={animationDuration}
        isActive={isAnimationActive}
        easing={animationEasing}
        from={{ t: 0 }}
        to={{ t: 1 }}
        key={`pie-${animationId}`}
        onAnimationStart={this.handleAnimationStart}
        onAnimationEnd={this.handleAnimationEnd}
      >
        {
          ({ t }) => {
            const stepData = [];
            const first = sectors && sectors[0];
            let curAngle = first.startAngle;

            sectors.forEach((entry, index) => {
              const prev = prevSectors && prevSectors[index];
              const paddingAngle = index > 0 ? entry.paddingAngle : 0;

              if (prev) {
                const angleIp = interpolateNumber(
                  prev.endAngle - prev.startAngle,
                  entry.endAngle - entry.startAngle
                );
                const latest = {
                  ...entry,
                  startAngle: curAngle + paddingAngle,
                  endAngle: curAngle + angleIp(t) + paddingAngle,
                };

                stepData.push(latest);
                curAngle = latest.endAngle;
              } else {
                const { endAngle, startAngle } = entry;
                const interpolatorAngle = interpolateNumber(0, endAngle - startAngle);
                const deltaAngle = interpolatorAngle(t);
                const latest = {
                  ...entry,
                  startAngle: curAngle + paddingAngle,
                  endAngle: curAngle + deltaAngle + paddingAngle,
                };

                stepData.push(latest);
                curAngle = latest.endAngle;
              }
            });

            return (
              <Layer>
                {this.renderSectorsStatically(stepData)}
              </Layer>
            );
          }
        }
      </Animate>
    );
  }

  renderSectors() {
    const { sectors, isAnimationActive } = this.props;
    const { prevSectors } = this.state;

    if (isAnimationActive && sectors && sectors.length &&
      (!prevSectors || !_.isEqual(prevSectors, sectors))) {
      return this.renderSectorsWithAnimation();
    }
    return this.renderSectorsStatically(sectors);
  }

  render() {
    const { hide, sectors, className, label, cx, cy, innerRadius,
      outerRadius, isAnimationActive, prevSectors, id } = this.props;

    if (hide || !sectors || !sectors.length || !isNumber(cx)
      || !isNumber(cy) || !isNumber(innerRadius)
      || !isNumber(outerRadius)) {
      return null;
    }

    const layerClass = classNames('recharts-pie', className);

    return (
      <Layer className={layerClass}>
        <g clipPath={`url(#${_.isNil(id) ? this.id : id})`}>
          {this.renderSectors()}
        </g>
        {label && this.renderLabels(sectors)}
        {Label.renderCallByParent(this.props, null, false)}
        {(!isAnimationActive || (prevSectors && _.isEqual(prevSectors, sectors))) &&
          LabelList.renderCallByParent(this.props, sectors, false)}
      </Layer>
    );
  }
}

export default Pie;
<|endoftext|>


<commit_before>/* jshint onevar:false */

'use strict';

var async = require('async');
var opn = require('opn');
var yo = require('yeoman-generator');
var yosay = require('yosay');
var util = require('util');
var path = require('path');
var updateNotifier = require('update-notifier');
var chalk = require('chalk');
var findup = require('findup');
var fullname = require('fullname');

// The `yo yo` generator provides users with a few common, helpful commands.
var yoyo = module.exports = function yoyo(args, options) {
  yo.Base.apply(this, arguments);
  this.insight = options.insight;

  this.insight.track('yoyo', 'init');
  process.once('exit', this._exit.bind(this));
};

util.inherits(yoyo, yo.Base);

// Runs parallel `npm update -g`s for each selected generator.
yoyo.prototype._updateGenerators = function _updateGenerators(pkgs) {
  var self = this;

  var resolveGenerators = function (pkg) {
    return function (next) {
      self.spawnCommand('npm', ['update', '-g', pkg])
        .on('error', next)
        .on('exit', next);
    };
  };

  self.insight.track('yoyo', 'update');
  async.parallel(self._.map(pkgs, resolveGenerators), function (err) {
    if (err) {
      self.insight.track('yoyo:err', 'update');
      return self.emit('error', err);
    }

    self.insight.track('yoyo', 'updated');
    self.home({
      refresh: true,
      message:
        'I\'ve just updated your generators. Remember, you can update' +
        '\na specific generator with npm by running:\n' +
        chalk.magenta('\n    npm update -g generator-_______')
    });
  });
};

// Prompts the user to select which generators to update
yoyo.prototype._promptToUpdateGenerators = function _promptToUpdateGenerators() {
  this.prompt([{
    name: '_updateSelectedGenerators',
    message: 'Generators to update',
    type: 'checkbox',
    choices: this._.map(this.pkgs, function (generator) {
      return {name: generator.name, checked: true};
    })
  }], function (answer) {
    this._updateGenerators.call(this, answer._updateSelectedGenerators);
  }.bind(this));
};

// Initializes a generator.
//
// - generator - (string) The generator to initialize.
yoyo.prototype._initGenerator = function _initGenerator(generator, done) {
  console.log(
    chalk.yellow('\nMake sure you are in the directory you want to scaffold into.\n') +
    'This generator can also be run with: ' +
    chalk.blue.bold('yo ' + generator.split(':')[0])
  );

  this.insight.track('yoyo', 'run', generator);
  this.env.run(generator, done);
};

// Serves as the response prompt for "Install a generator" as well as simply
// installs a generator if a string is passed in.
//
// - pkgName - (optional) A string that matches the NPM package name.
yoyo.prototype._installGenerator = function _installGenerator(pkgName) {
  if (this._.isString(pkgName)) {
    this.insight.track('yoyo', 'install', pkgName);

    // We know what generator we want to install
    return this.spawnCommand('npm', ['install', '-g', pkgName])
      .on('error', function (err) {
        this.insight.track('yoyo:err', 'install', pkgName);
        this.emit('error', err);
      }.bind(this))
      .on('exit', function () {
        this.insight.track('yoyo', 'installed', pkgName);
        this.home({
          refresh: true,
          message:
            '\nI just installed your generator by running:\n' +
            chalk.blue.bold('\n    npm install -g ' + pkgName)
        });
      }.bind(this));
  }

  this.insight.track('yoyo', 'install');

  this.prompt([{
    name: 'searchTerm',
    message: 'Search NPM for generators'
  }], this._searchNpm.bind(this));
};

// Grabs all of the packages with a `yeoman-generator` keyword on NPM.
//
// - term - (object) Contains the search term & gets passed back to callback().
// - cb   - Callback to execute once generators have been found.
yoyo.prototype._findAllNpmGenerators = function _findAllNpmGenerators(term, cb) {
  var url = 'http://isaacs.iriscouch.com/registry/_design/' +
    'app/_view/byKeyword?startkey=[%22yeoman-generator%22]' +
    '&endkey=[%22yeoman-generator%22,{}]&group_level=3';

  this.request(url, function (err, res, body) {
    if (err) {
      return this.emit('error', err);
    }

    try {
      this.npmGenerators = JSON.parse(body);
    } catch (err) {
      return this.emit('error', new Error(chalk.bold(
        'A problem occurred contacting the registry.' +
        '\nUnable to parse response: not valid JSON.'
      )));
    }

    cb(term);
  }.bind(this));
};

// Takes a search term, looks it up in the registry, prompts the user with the
// results, allowing them to choose to install it, or go back home.
//
// - term - Object with a 'searchTerm' property containing the term to search
//          NPM for.
yoyo.prototype._searchNpm = function _searchNpm(term) {
  if (!this.npmGenerators) {
    return this._findAllNpmGenerators(term, this._searchNpm.bind(this));
  }

  // Find any matches from NPM.
  var choices = this._.chain(this.npmGenerators.rows).map(function (generator) {
    // Make sure it's not already installed.
    if (
      !this.pkgs[generator.key[1]] &&
      generator.key.join(' ').indexOf(term.searchTerm) > -1
    ) {
      return {
        name: generator.key[1],
        value: generator.key[1]
      };
    }
  }.bind(this)).compact().value();

  var resultsPrompt = [{
    name: '_installGenerator',
    type: 'list',
    message: choices.length > 0 ?
      'Here\'s what I found. Install one?' : 'Sorry, nothing was found',
    choices: this._.union(choices, [{
      name: 'Search again',
      value: '_installGenerator'
    }, {
      name: 'Return home',
      value: 'home'
    }])
  }];

  this.prompt(resultsPrompt, function (answer) {
    if (this._.isFunction(this[answer._installGenerator])) {
      return this[answer._installGenerator]();
    }

    this._installGenerator(answer._installGenerator);
  }.bind(this));
};

// Prompts user with a few helpful resources, then opens it in their browser.
yoyo.prototype._findHelp = function _findHelp() {
  this.insight.track('yoyo', 'help');
  this.prompt([{
    name: 'whereTo',
    type: 'list',
    message:
      'Here are a few helpful resources.\n' +
      '\nI will open the link you select in your browser for you',
    choices: [{
      name: 'Take me to the documentation',
      value: 'http://yeoman.io/learning/index.html'
    }, {
      name: 'View Frequently Asked Questions',
      value: 'http://yeoman.io/learning/faq.html'
    }, {
      name: 'File an issue on GitHub',
      value: 'http://yeoman.io/contributing/opening-issues.html'
    }, {
      name: 'Take me back home, Yo!',
      value: {
        method: 'home',
        args: {
          message: 'I get it, you like learning on your own. I respect that.'
        }
      }
    }]
  }], function (answer) {
    this.insight.track('yoyo', 'help', answer);

    if (this._.isFunction(this[answer.whereTo.method])) {
      this[answer.whereTo.method](answer.whereTo.args);
    } else {
      opn(answer.whereTo);
    }
  }.bind(this));
};

// Serves as a quick escape from the `yo yo` prompts.
yoyo.prototype._exit = function _exit() {
  this.insight.track('yoyo', 'exit');

  var url = 'https://github.com/yeoman/yeoman#team';
  var maxLength = url.length;
  var newLine = new Array(maxLength).join(' ');

  console.log(
    '\n' +
    yosay(
      'Bye from us! Chat soon.' +
      newLine +
      newLine +
      'The Yeoman Team ' + url,
      { maxLength: maxLength }
    )
  );
};

// I'm sorry...
yoyo.prototype._noop = function _noop() {};

// Rolls through all of the generators provided by `env.generators`, finding
// their `package.json` files, then storing them internally in `this.pkgs`.
yoyo.prototype.findGenerators = function findGenerators() {
  this.pkgs = {};

  var resolveGenerators = function (generator) {
    if (!/(app|all)$/.test(generator.namespace)) {
      return;
    }

    var dir = findup.sync(generator.resolved, 'package.json');
    if (!dir) {
      return;
    }

    var pkg = yo.file.readJSON(path.join(dir, 'package.json'));
    pkg.namespace = generator.namespace;
    pkg.appGenerator = true;
    pkg.prettyName = generator.namespace.replace(/(\w+):\w+/, '$1');
    pkg.prettyName = pkg.prettyName.charAt(0).toUpperCase() + pkg.prettyName.slice(1);

    pkg.update = updateNotifier({
      packageName: pkg.name,
      packageVersion: pkg.version
    }).update;

    if (pkg.update && pkg.version !== pkg.update.latest) {
      pkg.updateAvailable = true;
    }

    this.pkgs[pkg.name] = pkg;
  };

  this._.each(this.env.getGeneratorsMeta(), resolveGenerators, this);
};

// Display the home `yo` screen, with the intial set of options.
//
// - options - (optional)
//           - message (string) - String to print before prompt.
//           - refresh (bool) - Spawn a new `yo` command.
yoyo.prototype.home = function home(options) {
  var done = this.async();

  options = options || {};

  if (options.refresh) {
    this.env.lookup();
    this.findGenerators();
  }

  if (options.message) {
    console.log('\n' + chalk.cyan(options.message) + '\n');
  }

  var defaultChoices = [{
    name: 'Install a generator',
    value: {
      method: '_installGenerator'
    }
  }, {
    name: 'Find some help',
    value: {
      method: '_findHelp'
    }
  }, {
    name: 'Get me out of here!',
    value: {
      method: '_noop'
    }
  }];

  var generatorList = this._.chain(this.pkgs).map(function (generator) {
    if (!generator.appGenerator) {
      return;
    }

    var versionInfo = chalk.gray('(' + generator.version + ')');

    if (generator.updateAvailable) {
      versionInfo += chalk.yellow(' Update Available! ') +
        chalk.red('(' + generator.update.latest + ')');
    }

    return {
      name: 'Run the ' + generator.prettyName + ' generator ' + versionInfo,
      value: {
        method: '_initGenerator',
        args: generator.namespace
      }
    };
  }).compact().value();

  if (generatorList.length) {
    defaultChoices.unshift({
      name: 'Update your generators',
      value: {
        method: '_promptToUpdateGenerators'
      }
    });
  }

  this.insight.track('yoyo', 'home');

  fullname(function (err, name) {
    if (err) {
      return done(err);
    }

    var allo = name ? ('\'Allo ' + name.split(' ')[0] + '! ') : '\'Allo! ';

    this.prompt([{
      name: 'whatNext',
      type: 'list',
      message: allo + 'What would you like to do?',
      choices: this._.union(generatorList, new yo.inquirer.Separator(), defaultChoices)
    }], function (answer) {
      this[answer.whatNext.method](answer.whatNext.args, done);
    }.bind(this));
  }.bind(this));
};
<commit_msg>Fix bug preventing running generator based on v0.17<commit_after>/* jshint onevar:false */

'use strict';

var async = require('async');
var opn = require('opn');
var yo = require('yeoman-generator');
var yosay = require('yosay');
var util = require('util');
var path = require('path');
var updateNotifier = require('update-notifier');
var chalk = require('chalk');
var findup = require('findup');
var fullname = require('fullname');

// The `yo yo` generator provides users with a few common, helpful commands.
var yoyo = module.exports = function yoyo(args, options) {
  yo.Base.apply(this, arguments);
  this.insight = options.insight;

  this.insight.track('yoyo', 'init');
  process.once('exit', this._exit.bind(this));
};

util.inherits(yoyo, yo.Base);

// Runs parallel `npm update -g`s for each selected generator.
yoyo.prototype._updateGenerators = function _updateGenerators(pkgs) {
  var self = this;

  var resolveGenerators = function (pkg) {
    return function (next) {
      self.spawnCommand('npm', ['update', '-g', pkg])
        .on('error', next)
        .on('exit', next);
    };
  };

  self.insight.track('yoyo', 'update');
  async.parallel(self._.map(pkgs, resolveGenerators), function (err) {
    if (err) {
      self.insight.track('yoyo:err', 'update');
      return self.emit('error', err);
    }

    self.insight.track('yoyo', 'updated');
    self.home({
      refresh: true,
      message:
        'I\'ve just updated your generators. Remember, you can update' +
        '\na specific generator with npm by running:\n' +
        chalk.magenta('\n    npm update -g generator-_______')
    });
  });
};

// Prompts the user to select which generators to update
yoyo.prototype._promptToUpdateGenerators = function _promptToUpdateGenerators() {
  this.prompt([{
    name: '_updateSelectedGenerators',
    message: 'Generators to update',
    type: 'checkbox',
    choices: this._.map(this.pkgs, function (generator) {
      return {name: generator.name, checked: true};
    })
  }], function (answer) {
    this._updateGenerators.call(this, answer._updateSelectedGenerators);
  }.bind(this));
};

// Initializes a generator.
//
// - generator - (string) The generator to initialize.
yoyo.prototype._initGenerator = function _initGenerator(generator, done) {
  console.log(
    chalk.yellow('\nMake sure you are in the directory you want to scaffold into.\n') +
    'This generator can also be run with: ' +
    chalk.blue.bold('yo ' + generator.split(':')[0])
  );

  this.insight.track('yoyo', 'run', generator);
  this.composeWith(generator);
  done();
};

// Serves as the response prompt for "Install a generator" as well as simply
// installs a generator if a string is passed in.
//
// - pkgName - (optional) A string that matches the NPM package name.
yoyo.prototype._installGenerator = function _installGenerator(pkgName) {
  if (this._.isString(pkgName)) {
    this.insight.track('yoyo', 'install', pkgName);

    // We know what generator we want to install
    return this.spawnCommand('npm', ['install', '-g', pkgName])
      .on('error', function (err) {
        this.insight.track('yoyo:err', 'install', pkgName);
        this.emit('error', err);
      }.bind(this))
      .on('exit', function () {
        this.insight.track('yoyo', 'installed', pkgName);
        this.home({
          refresh: true,
          message:
            '\nI just installed your generator by running:\n' +
            chalk.blue.bold('\n    npm install -g ' + pkgName)
        });
      }.bind(this));
  }

  this.insight.track('yoyo', 'install');

  this.prompt([{
    name: 'searchTerm',
    message: 'Search NPM for generators'
  }], this._searchNpm.bind(this));
};

// Grabs all of the packages with a `yeoman-generator` keyword on NPM.
//
// - term - (object) Contains the search term & gets passed back to callback().
// - cb   - Callback to execute once generators have been found.
yoyo.prototype._findAllNpmGenerators = function _findAllNpmGenerators(term, cb) {
  var url = 'http://isaacs.iriscouch.com/registry/_design/' +
    'app/_view/byKeyword?startkey=[%22yeoman-generator%22]' +
    '&endkey=[%22yeoman-generator%22,{}]&group_level=3';

  this.request(url, function (err, res, body) {
    if (err) {
      return this.emit('error', err);
    }

    try {
      this.npmGenerators = JSON.parse(body);
    } catch (err) {
      return this.emit('error', new Error(chalk.bold(
        'A problem occurred contacting the registry.' +
        '\nUnable to parse response: not valid JSON.'
      )));
    }

    cb(term);
  }.bind(this));
};

// Takes a search term, looks it up in the registry, prompts the user with the
// results, allowing them to choose to install it, or go back home.
//
// - term - Object with a 'searchTerm' property containing the term to search
//          NPM for.
yoyo.prototype._searchNpm = function _searchNpm(term) {
  if (!this.npmGenerators) {
    return this._findAllNpmGenerators(term, this._searchNpm.bind(this));
  }

  // Find any matches from NPM.
  var choices = this._.chain(this.npmGenerators.rows).map(function (generator) {
    // Make sure it's not already installed.
    if (
      !this.pkgs[generator.key[1]] &&
      generator.key.join(' ').indexOf(term.searchTerm) > -1
    ) {
      return {
        name: generator.key[1],
        value: generator.key[1]
      };
    }
  }.bind(this)).compact().value();

  var resultsPrompt = [{
    name: '_installGenerator',
    type: 'list',
    message: choices.length > 0 ?
      'Here\'s what I found. Install one?' : 'Sorry, nothing was found',
    choices: this._.union(choices, [{
      name: 'Search again',
      value: '_installGenerator'
    }, {
      name: 'Return home',
      value: 'home'
    }])
  }];

  this.prompt(resultsPrompt, function (answer) {
    if (this._.isFunction(this[answer._installGenerator])) {
      return this[answer._installGenerator]();
    }

    this._installGenerator(answer._installGenerator);
  }.bind(this));
};

// Prompts user with a few helpful resources, then opens it in their browser.
yoyo.prototype._findHelp = function _findHelp() {
  this.insight.track('yoyo', 'help');
  this.prompt([{
    name: 'whereTo',
    type: 'list',
    message:
      'Here are a few helpful resources.\n' +
      '\nI will open the link you select in your browser for you',
    choices: [{
      name: 'Take me to the documentation',
      value: 'http://yeoman.io/learning/index.html'
    }, {
      name: 'View Frequently Asked Questions',
      value: 'http://yeoman.io/learning/faq.html'
    }, {
      name: 'File an issue on GitHub',
      value: 'http://yeoman.io/contributing/opening-issues.html'
    }, {
      name: 'Take me back home, Yo!',
      value: {
        method: 'home',
        args: {
          message: 'I get it, you like learning on your own. I respect that.'
        }
      }
    }]
  }], function (answer) {
    this.insight.track('yoyo', 'help', answer);

    if (this._.isFunction(this[answer.whereTo.method])) {
      this[answer.whereTo.method](answer.whereTo.args);
    } else {
      opn(answer.whereTo);
    }
  }.bind(this));
};

// Serves as a quick escape from the `yo yo` prompts.
yoyo.prototype._exit = function _exit() {
  this.insight.track('yoyo', 'exit');

  var url = 'https://github.com/yeoman/yeoman#team';
  var maxLength = url.length;
  var newLine = new Array(maxLength).join(' ');

  console.log(
    '\n' +
    yosay(
      'Bye from us! Chat soon.' +
      newLine +
      newLine +
      'The Yeoman Team ' + url,
      { maxLength: maxLength }
    )
  );
};

// I'm sorry...
yoyo.prototype._noop = function _noop() {};

// Rolls through all of the generators provided by `env.generators`, finding
// their `package.json` files, then storing them internally in `this.pkgs`.
yoyo.prototype.findGenerators = function findGenerators() {
  this.pkgs = {};

  var resolveGenerators = function (generator) {
    if (!/(app|all)$/.test(generator.namespace)) {
      return;
    }

    var dir = findup.sync(generator.resolved, 'package.json');
    if (!dir) {
      return;
    }

    var pkg = yo.file.readJSON(path.join(dir, 'package.json'));
    pkg.namespace = generator.namespace;
    pkg.appGenerator = true;
    pkg.prettyName = generator.namespace.replace(/(\w+):\w+/, '$1');
    pkg.prettyName = pkg.prettyName.charAt(0).toUpperCase() + pkg.prettyName.slice(1);

    pkg.update = updateNotifier({
      packageName: pkg.name,
      packageVersion: pkg.version
    }).update;

    if (pkg.update && pkg.version !== pkg.update.latest) {
      pkg.updateAvailable = true;
    }

    this.pkgs[pkg.name] = pkg;
  };

  this._.each(this.env.getGeneratorsMeta(), resolveGenerators, this);
};

// Display the home `yo` screen, with the intial set of options.
//
// - options - (optional)
//           - message (string) - String to print before prompt.
//           - refresh (bool) - Spawn a new `yo` command.
yoyo.prototype.home = function home(options) {
  var done = this.async();

  options = options || {};

  if (options.refresh) {
    this.env.lookup();
    this.findGenerators();
  }

  if (options.message) {
    console.log('\n' + chalk.cyan(options.message) + '\n');
  }

  var defaultChoices = [{
    name: 'Install a generator',
    value: {
      method: '_installGenerator'
    }
  }, {
    name: 'Find some help',
    value: {
      method: '_findHelp'
    }
  }, {
    name: 'Get me out of here!',
    value: {
      method: '_noop'
    }
  }];

  var generatorList = this._.chain(this.pkgs).map(function (generator) {
    if (!generator.appGenerator) {
      return;
    }

    var versionInfo = chalk.gray('(' + generator.version + ')');

    if (generator.updateAvailable) {
      versionInfo += chalk.yellow(' Update Available! ') +
        chalk.red('(' + generator.update.latest + ')');
    }

    return {
      name: 'Run the ' + generator.prettyName + ' generator ' + versionInfo,
      value: {
        method: '_initGenerator',
        args: generator.namespace
      }
    };
  }).compact().value();

  if (generatorList.length) {
    defaultChoices.unshift({
      name: 'Update your generators',
      value: {
        method: '_promptToUpdateGenerators'
      }
    });
  }

  this.insight.track('yoyo', 'home');

  fullname(function (err, name) {
    if (err) {
      return done(err);
    }

    var allo = name ? ('\'Allo ' + name.split(' ')[0] + '! ') : '\'Allo! ';

    this.prompt([{
      name: 'whatNext',
      type: 'list',
      message: allo + 'What would you like to do?',
      choices: this._.union(generatorList, new yo.inquirer.Separator(), defaultChoices)
    }], function (answer) {
      this[answer.whatNext.method](answer.whatNext.args, done);
    }.bind(this));
  }.bind(this));
};
<|endoftext|>


<commit_before>b6d54ace-973a-11e6-b514-8086f2004150<commit_msg>Initial commit.2<commit_after>b6d5dd2c-973a-11e6-b514-8086f2004150<|endoftext|>


<commit_before>define(['apphost', 'pluginManager'], function (appHost, pluginManager) {

    return function () {

        var self = this;

        self.name = 'Windows Player';
        self.type = 'mediaplayer';
        self.id = 'directshowplayer';

        var currentSrc;
        var playerState = {};
        var ignoreEnded;

        self.getRoutes = function () {

            var routes = [];

            routes.push({
                path: pluginManager.mapPath(self, 'directshowplayer/audio.html'),
                transition: 'slide',
                dependencies: [
                    'emby-dropdown-menu'
                ],
                controller: pluginManager.mapPath(self, 'directshowplayer/audio.js'),
                type: 'settings',
                title: 'Audio',
                category: 'Playback',
                thumbImage: ''
            });

            if (appHost.supports('windowtransparency')) {
                routes.push({
                    path: pluginManager.mapPath(self, 'directshowplayer/video.html'),
                    transition: 'slide',
                    dependencies: [
                        'emby-dropdown-menu'
                    ],
                    controller: pluginManager.mapPath(self, 'directshowplayer/video.js'),
                    type: 'settings',
                    title: 'Video',
                    category: 'Playback',
                    thumbImage: ''
                });

                routes.push({
                    path: pluginManager.mapPath(self, 'directshowplayer/madvr.html'),
                    transition: 'slide',
                    dependencies: [
                        'emby-dropdown-menu'
                    ],
                    controller: pluginManager.mapPath(self, 'directshowplayer/madvr.js'),
                    thumbImage: ''
                });
            }

            return routes;
        };

        self.getTranslations = function () {

            var files = [];

            files.push({
                lang: 'en-us',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/en-US.json')
            });

            files.push({
                lang: 'fr',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/fr.json')
            });

            files.push({
                lang: 'nl',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/nl.json')
            });

            files.push({
                lang: 'pt-BR',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/pt-BR.json')
            });

            files.push({
                lang: 'pt-PT',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/pt-PT.json')
            });

            files.push({
                lang: 'ru',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/ru.json')
            });

            files.push({
                lang: 'sv',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/sv.json')
            });

            return files;
        };

        self.canPlayMediaType = function (mediaType) {

            if ((mediaType || '').toLowerCase() == 'video') {

                return appHost.supports('windowtransparency');
            }
            return (mediaType || '').toLowerCase() == 'audio';
        };

        self.getDeviceProfile = function () {

            var profile = {};

            profile.MaxStreamingBitrate = 100000000;
            profile.MaxStaticBitrate = 100000000;
            profile.MusicStreamingTranscodingBitrate = 192000;

            profile.DirectPlayProfiles = [];

            profile.DirectPlayProfiles.push({
                Container: 'm4v,3gp,ts,mpegts,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm',
                Type: 'Video'
            });

            profile.DirectPlayProfiles.push({
                Container: 'aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac',
                Type: 'Audio'
            });

            profile.TranscodingProfiles = [];

            profile.TranscodingProfiles.push({
                Container: 'mkv',
                Type: 'Video',
                AudioCodec: 'aac,mp3,ac3',
                VideoCodec: 'h264',
                Context: 'Streaming'
            });

            profile.TranscodingProfiles.push({
                Container: 'mp3',
                Type: 'Audio',
                AudioCodec: 'mp3',
                Context: 'Streaming',
                Protocol: 'http'
            });

            profile.ContainerProfiles = [];

            profile.CodecProfiles = [];

            // Subtitle profiles
            // External vtt or burn in
            profile.SubtitleProfiles = [];
            profile.SubtitleProfiles.push({
                Format: 'srt',
                Method: 'External'
            });
            profile.SubtitleProfiles.push({
                Format: 'srt',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'subrip',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'ass',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'ssa',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'pgs',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'pgssub',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'dvdsub',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'vtt',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'sub',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'idx',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'smi',
                Method: 'Embed'
            });

            profile.ResponseProfiles = [];

            return Promise.resolve(profile);
        };

        self.currentSrc = function () {
            return currentSrc;
        };

        self.play = function (options) {

            return new Promise(function (resolve, reject) {

                ignoreEnded = false;
                currentSrc = options.url;

                //var isVideo = options.mimeType.toLowerCase('video').indexOf() == 0;
                var isVideo = options.item.MediaType == 'Video';

                var enableFullscreen = options.fullscreen !== false;

                var mediaSource = JSON.parse(JSON.stringify(options.mediaSource));

                // Update the text url in the media source with the full url from the options object
                mediaSource.MediaStreams.forEach(function (ms) {
                    var textTrack = options.textTracks.filter(function (t) {
                        return t.index == ms.Index;

                    })[0];

                    if (textTrack) {
                        ms.DeliveryUrl = textTrack.url;
                    }
                });

                var requestBody = {
                    url: options.url,
                    isVideo: isVideo,
                    item: options.item,
                    mediaSource: mediaSource,
                    startPositionTicks: options.playerStartPositionTicks,
                    fullscreen: enableFullscreen
                };

                sendCommand('play', requestBody).then(function () {

                    Events.trigger(self, 'started');

                    if (isVideo) {
                        if (enableFullscreen) {

                            Emby.Page.showVideoOsd();

                        } else {
                            Emby.Page.setTransparency(Emby.TransparencyLevel.Backdrop);
                        }
                    }

                    startTimeUpdateInterval();

                    resolve();

                }, function () {
                    stopTimeUpdateInterval();
                    reject();
                });
            });
        };

        // Save this for when playback stops, because querying the time at that point might return 0
        self.currentTime = function (val) {

            if (val != null) {
                sendCommand('positionticks?val=' + (val * 10000)).then(onTimeUpdate);
                return;
            }

            return (playerState.positionTicks || 0) / 10000;
        };

        self.duration = function (val) {

            return playerState.durationTicks;
        };

        self.stop = function (destroyPlayer, reportEnded) {

            ignoreEnded = true;

            var cmd = destroyPlayer ? 'stopfade' : 'stop';
            return sendCommand(cmd).then(function () {

                onEnded(reportEnded);

                if (destroyPlayer) {
                    self.destroy();
                }
            });
        };

        self.destroy = function () {
            Emby.Page.setTransparency(Emby.TransparencyLevel.None);
        };

        self.pause = function () {
            sendCommand('pause').then(onPause);
        };

        self.unpause = function () {
            sendCommand('unpause').then(onPlaying);
        };

        self.paused = function () {

            return playerState.isPaused || false;
        };

        self.volume = function (val) {
            if (val != null) {
                sendCommand('volume?val=' + val).then(onVolumeChange);
                return;
            }

            return playerState.volume || 0;
        };

        self.setSubtitleStreamIndex = function (index) {
            sendCommand('setSubtitleStreamIndex?index=' + index);
        };

        self.setAudioStreamIndex = function (index) {
            sendCommand('setAudioStreamIndex?index=' + index);
        };

        self.canSetAudioStreamIndex = function () {
            return true;
        };

        self.setMute = function (mute) {

            var cmd = mute ? 'mute' : 'unmute';

            sendCommand(cmd).then(onVolumeChange);
        };

        self.isMuted = function () {
            return playerState.isMuted || false;
        };

        var timeUpdateInterval;
        function startTimeUpdateInterval() {
            stopTimeUpdateInterval();
            timeUpdateInterval = setInterval(onTimeUpdate, 500);
        }

        function stopTimeUpdateInterval() {
            if (timeUpdateInterval) {
                clearInterval(timeUpdateInterval);
                timeUpdateInterval = null;
            }
        }

        function onEnded(reportEnded) {
            stopTimeUpdateInterval();

            if (reportEnded) {
                Events.trigger(self, 'stopped');
            }
        }

        function onTimeUpdate() {

            updatePlayerState();
            Events.trigger(self, 'timeupdate');
        }

        function onVolumeChange() {
            Events.trigger(self, 'volumechange');
        }

        function onPlaying() {

            Events.trigger(self, 'playing');
        }

        function onPause() {
            Events.trigger(self, 'pause');
        }

        function onError() {

            stopTimeUpdateInterval();
            Events.trigger(self, 'error');
        }

        function getFetchPromise(request) {

            var headers = request.headers || {};

            if (request.dataType == 'json') {
                headers.accept = 'application/json';
            }

            var fetchRequest = {
                headers: headers,
                method: request.type
            };

            var contentType = request.contentType;

            if (request.data) {

                if (typeof request.data === 'string') {
                    fetchRequest.body = request.data;
                } else {
                    fetchRequest.body = paramsToString(request.data);

                    contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';
                }
            }

            if (contentType) {

                headers['Content-Type'] = contentType;
            }

            return fetch(request.url, fetchRequest);
        }

        function paramsToString(params) {

            var values = [];

            for (var key in params) {

                var value = params[key];

                if (value !== null && value !== undefined && value !== '') {
                    values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
                }
            }
            return values.join('&');
        }

        function sendCommand(name, body) {

            var request = {
                type: 'POST',
                url: 'http://localhost:8154/directshowplayer/' + name,
                dataType: 'json'
            };

            if (body) {
                request.contentType = 'application/json';
                request.data = JSON.stringify(body);
            }

            return getFetchPromise(request).then(function (response) {

                return response.json();

            }).then(function (state) {

                var previousPlayerState = playerState;

                playerState = state;

                if (previousPlayerState.isMuted != state.isMuted ||
                    previousPlayerState.volume != state.volume) {
                    onVolumeChange();
                }

                if (state.playstate == 'idle' && previousPlayerState.playstate != 'idle' && previousPlayerState.playstate) {
                    if (!ignoreEnded) {
                        ignoreEnded = true;
                        onEnded(true);
                    }
                }

                return state;
            });
        }

        function updatePlayerState() {

            return sendCommand('refresh');
        }
    }
});
<commit_msg>fixes #320 - DVR-MS files show black screen using EVR+<commit_after>define(['apphost', 'pluginManager'], function (appHost, pluginManager) {

    return function () {

        var self = this;

        self.name = 'Windows Player';
        self.type = 'mediaplayer';
        self.id = 'directshowplayer';

        var currentSrc;
        var playerState = {};
        var ignoreEnded;

        self.getRoutes = function () {

            var routes = [];

            routes.push({
                path: pluginManager.mapPath(self, 'directshowplayer/audio.html'),
                transition: 'slide',
                dependencies: [
                    'emby-dropdown-menu'
                ],
                controller: pluginManager.mapPath(self, 'directshowplayer/audio.js'),
                type: 'settings',
                title: 'Audio',
                category: 'Playback',
                thumbImage: ''
            });

            if (appHost.supports('windowtransparency')) {
                routes.push({
                    path: pluginManager.mapPath(self, 'directshowplayer/video.html'),
                    transition: 'slide',
                    dependencies: [
                        'emby-dropdown-menu'
                    ],
                    controller: pluginManager.mapPath(self, 'directshowplayer/video.js'),
                    type: 'settings',
                    title: 'Video',
                    category: 'Playback',
                    thumbImage: ''
                });

                routes.push({
                    path: pluginManager.mapPath(self, 'directshowplayer/madvr.html'),
                    transition: 'slide',
                    dependencies: [
                        'emby-dropdown-menu'
                    ],
                    controller: pluginManager.mapPath(self, 'directshowplayer/madvr.js'),
                    thumbImage: ''
                });
            }

            return routes;
        };

        self.getTranslations = function () {

            var files = [];

            files.push({
                lang: 'en-us',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/en-US.json')
            });

            files.push({
                lang: 'fr',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/fr.json')
            });

            files.push({
                lang: 'nl',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/nl.json')
            });

            files.push({
                lang: 'pt-BR',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/pt-BR.json')
            });

            files.push({
                lang: 'pt-PT',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/pt-PT.json')
            });

            files.push({
                lang: 'ru',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/ru.json')
            });

            files.push({
                lang: 'sv',
                path: pluginManager.mapPath(self, 'directshowplayer/strings/sv.json')
            });

            return files;
        };

        self.canPlayMediaType = function (mediaType) {

            if ((mediaType || '').toLowerCase() == 'video') {

                return appHost.supports('windowtransparency');
            }
            return (mediaType || '').toLowerCase() == 'audio';
        };

        self.getDeviceProfile = function () {

            var profile = {};

            profile.MaxStreamingBitrate = 100000000;
            profile.MaxStaticBitrate = 100000000;
            profile.MusicStreamingTranscodingBitrate = 192000;

            profile.DirectPlayProfiles = [];

            profile.DirectPlayProfiles.push({
                Container: 'm4v,3gp,ts,mpegts,mov,xvid,vob,mkv,wmv,asf,ogm,ogv,m2v,avi,mpg,mpeg,mp4,webm,wtv,dvr-ms',
                Type: 'Video'
            });

            profile.DirectPlayProfiles.push({
                Container: 'aac,mp3,mpa,wav,wma,mp2,ogg,oga,webma,ape,opus,flac',
                Type: 'Audio'
            });

            profile.TranscodingProfiles = [];

            profile.TranscodingProfiles.push({
                Container: 'mkv',
                Type: 'Video',
                AudioCodec: 'aac,mp3,ac3',
                VideoCodec: 'h264',
                Context: 'Streaming'
            });

            profile.TranscodingProfiles.push({
                Container: 'mp3',
                Type: 'Audio',
                AudioCodec: 'mp3',
                Context: 'Streaming',
                Protocol: 'http'
            });

            profile.ContainerProfiles = [];

            profile.CodecProfiles = [];

            // Subtitle profiles
            // External vtt or burn in
            profile.SubtitleProfiles = [];
            profile.SubtitleProfiles.push({
                Format: 'srt',
                Method: 'External'
            });
            profile.SubtitleProfiles.push({
                Format: 'srt',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'subrip',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'ass',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'ssa',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'pgs',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'pgssub',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'dvdsub',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'vtt',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'sub',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'idx',
                Method: 'Embed'
            });
            profile.SubtitleProfiles.push({
                Format: 'smi',
                Method: 'Embed'
            });

            profile.ResponseProfiles = [];

            return Promise.resolve(profile);
        };

        self.currentSrc = function () {
            return currentSrc;
        };

        self.play = function (options) {

            return new Promise(function (resolve, reject) {

                ignoreEnded = false;
                currentSrc = options.url;

                //var isVideo = options.mimeType.toLowerCase('video').indexOf() == 0;
                var isVideo = options.item.MediaType == 'Video';

                var enableFullscreen = options.fullscreen !== false;

                var mediaSource = JSON.parse(JSON.stringify(options.mediaSource));

                // Update the text url in the media source with the full url from the options object
                mediaSource.MediaStreams.forEach(function (ms) {
                    var textTrack = options.textTracks.filter(function (t) {
                        return t.index == ms.Index;

                    })[0];

                    if (textTrack) {
                        ms.DeliveryUrl = textTrack.url;
                    }
                });

                var requestBody = {
                    url: options.url,
                    isVideo: isVideo,
                    item: options.item,
                    mediaSource: mediaSource,
                    startPositionTicks: options.playerStartPositionTicks,
                    fullscreen: enableFullscreen
                };

                sendCommand('play', requestBody).then(function () {

                    Events.trigger(self, 'started');

                    if (isVideo) {
                        if (enableFullscreen) {

                            Emby.Page.showVideoOsd();

                        } else {
                            Emby.Page.setTransparency(Emby.TransparencyLevel.Backdrop);
                        }
                    }

                    startTimeUpdateInterval();

                    resolve();

                }, function () {
                    stopTimeUpdateInterval();
                    reject();
                });
            });
        };

        // Save this for when playback stops, because querying the time at that point might return 0
        self.currentTime = function (val) {

            if (val != null) {
                sendCommand('positionticks?val=' + (val * 10000)).then(onTimeUpdate);
                return;
            }

            return (playerState.positionTicks || 0) / 10000;
        };

        self.duration = function (val) {

            return playerState.durationTicks;
        };

        self.stop = function (destroyPlayer, reportEnded) {

            ignoreEnded = true;

            var cmd = destroyPlayer ? 'stopfade' : 'stop';
            return sendCommand(cmd).then(function () {

                onEnded(reportEnded);

                if (destroyPlayer) {
                    self.destroy();
                }
            });
        };

        self.destroy = function () {
            Emby.Page.setTransparency(Emby.TransparencyLevel.None);
        };

        self.pause = function () {
            sendCommand('pause').then(onPause);
        };

        self.unpause = function () {
            sendCommand('unpause').then(onPlaying);
        };

        self.paused = function () {

            return playerState.isPaused || false;
        };

        self.volume = function (val) {
            if (val != null) {
                sendCommand('volume?val=' + val).then(onVolumeChange);
                return;
            }

            return playerState.volume || 0;
        };

        self.setSubtitleStreamIndex = function (index) {
            sendCommand('setSubtitleStreamIndex?index=' + index);
        };

        self.setAudioStreamIndex = function (index) {
            sendCommand('setAudioStreamIndex?index=' + index);
        };

        self.canSetAudioStreamIndex = function () {
            return true;
        };

        self.setMute = function (mute) {

            var cmd = mute ? 'mute' : 'unmute';

            sendCommand(cmd).then(onVolumeChange);
        };

        self.isMuted = function () {
            return playerState.isMuted || false;
        };

        var timeUpdateInterval;
        function startTimeUpdateInterval() {
            stopTimeUpdateInterval();
            timeUpdateInterval = setInterval(onTimeUpdate, 500);
        }

        function stopTimeUpdateInterval() {
            if (timeUpdateInterval) {
                clearInterval(timeUpdateInterval);
                timeUpdateInterval = null;
            }
        }

        function onEnded(reportEnded) {
            stopTimeUpdateInterval();

            if (reportEnded) {
                Events.trigger(self, 'stopped');
            }
        }

        function onTimeUpdate() {

            updatePlayerState();
            Events.trigger(self, 'timeupdate');
        }

        function onVolumeChange() {
            Events.trigger(self, 'volumechange');
        }

        function onPlaying() {

            Events.trigger(self, 'playing');
        }

        function onPause() {
            Events.trigger(self, 'pause');
        }

        function onError() {

            stopTimeUpdateInterval();
            Events.trigger(self, 'error');
        }

        function getFetchPromise(request) {

            var headers = request.headers || {};

            if (request.dataType == 'json') {
                headers.accept = 'application/json';
            }

            var fetchRequest = {
                headers: headers,
                method: request.type
            };

            var contentType = request.contentType;

            if (request.data) {

                if (typeof request.data === 'string') {
                    fetchRequest.body = request.data;
                } else {
                    fetchRequest.body = paramsToString(request.data);

                    contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';
                }
            }

            if (contentType) {

                headers['Content-Type'] = contentType;
            }

            return fetch(request.url, fetchRequest);
        }

        function paramsToString(params) {

            var values = [];

            for (var key in params) {

                var value = params[key];

                if (value !== null && value !== undefined && value !== '') {
                    values.push(encodeURIComponent(key) + "=" + encodeURIComponent(value));
                }
            }
            return values.join('&');
        }

        function sendCommand(name, body) {

            var request = {
                type: 'POST',
                url: 'http://localhost:8154/directshowplayer/' + name,
                dataType: 'json'
            };

            if (body) {
                request.contentType = 'application/json';
                request.data = JSON.stringify(body);
            }

            return getFetchPromise(request).then(function (response) {

                return response.json();

            }).then(function (state) {

                var previousPlayerState = playerState;

                playerState = state;

                if (previousPlayerState.isMuted != state.isMuted ||
                    previousPlayerState.volume != state.volume) {
                    onVolumeChange();
                }

                if (state.playstate == 'idle' && previousPlayerState.playstate != 'idle' && previousPlayerState.playstate) {
                    if (!ignoreEnded) {
                        ignoreEnded = true;
                        onEnded(true);
                    }
                }

                return state;
            });
        }

        function updatePlayerState() {

            return sendCommand('refresh');
        }
    }
});
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import assign from 'object-assign';

import AbstractMenu from './AbstractMenu';
import { cssClasses, hasOwnProp } from './helpers';
import listener from './globalEventListener';

export default class SubMenu extends AbstractMenu {
    static propTypes = {
        children: PropTypes.node.isRequired,
        title: PropTypes.node.isRequired,
        className: PropTypes.string,
        disabled: PropTypes.bool,
        hoverDelay: PropTypes.number,
        rtl: PropTypes.bool,
        selected: PropTypes.bool,
        onMouseMove: PropTypes.func,
        onMouseOut: PropTypes.func,
        forceOpen: PropTypes.bool,
        forceClose: PropTypes.func,
        parentKeyNavigationHandler: PropTypes.func
    };

    static defaultProps = {
        disabled: false,
        hoverDelay: 500,
        className: '',
        rtl: false,
        selected: false,
        onMouseMove: () => null,
        onMouseOut: () => null,
        forceOpen: false,
        forceClose: () => null,
        parentKeyNavigationHandler: () => null
    };

    constructor(props) {
        super(props);

        this.state = assign({}, this.state, {
            visible: false
        });
    }

    componentDidMount() {
        this.listenId = listener.register(() => {}, this.hideMenu);
    }

    getSubMenuType() { // eslint-disable-line class-methods-use-this
        return SubMenu;
    }

    shouldComponentUpdate(nextProps, nextState) {
        this.isVisibilityChange = (this.state.visible !== nextState.visible ||
                                  this.props.forceOpen !== nextProps.forceOpen) &&
                                  !(this.state.visible && nextProps.forceOpen) &&
                                  !(this.props.forceOpen && nextState.visible);
        return true;
    }

    componentDidUpdate() {
        if (!this.isVisibilityChange) return;
        if (this.props.forceOpen || this.state.visible) {
            const wrapper = window.requestAnimationFrame || setTimeout;
            wrapper(() => {
                const styles = this.props.rtl
                                ? this.getRTLMenuPosition()
                                : this.getMenuPosition();

                this.subMenu.style.removeProperty('top');
                this.subMenu.style.removeProperty('bottom');
                this.subMenu.style.removeProperty('left');
                this.subMenu.style.removeProperty('right');

                if (hasOwnProp(styles, 'top')) this.subMenu.style.top = styles.top;
                if (hasOwnProp(styles, 'left')) this.subMenu.style.left = styles.left;
                if (hasOwnProp(styles, 'bottom')) this.subMenu.style.bottom = styles.bottom;
                if (hasOwnProp(styles, 'right')) this.subMenu.style.right = styles.right;
                this.subMenu.classList.add(cssClasses.menuVisible);

                this.registerHandlers();
                this.setState({ selectedItem: null });
            });
        } else {
            const cleanup = () => {
                this.subMenu.removeEventListener('transitionend', cleanup);
                this.subMenu.style.removeProperty('bottom');
                this.subMenu.style.removeProperty('right');
                this.subMenu.style.top = 0;
                this.subMenu.style.left = '100%';
                this.unregisterHandlers();
            };
            this.subMenu.addEventListener('transitionend', cleanup);
            this.subMenu.classList.remove(cssClasses.menuVisible);
        }
    }

    componentWillUnmount() {
        if (this.listenId) {
            listener.unregister(this.listenId);
        }

        if (this.opentimer) clearTimeout(this.opentimer);

        if (this.closetimer) clearTimeout(this.closetimer);

        this.unregisterHandlers();
    }

    getMenuPosition = () => {
        const { innerWidth, innerHeight } = window;
        const rect = this.subMenu.getBoundingClientRect();
        const position = {};

        if (rect.bottom > innerHeight) {
            position.bottom = 0;
        } else {
            position.top = 0;
        }

        if (rect.right < innerWidth) {
            position.left = '100%';
        } else {
            position.right = '100%';
        }

        return position;
    }

    getRTLMenuPosition = () => {
        const { innerHeight } = window;
        const rect = this.subMenu.getBoundingClientRect();
        const position = {};

        if (rect.bottom > innerHeight) {
            position.bottom = 0;
        } else {
            position.top = 0;
        }

        if (rect.left < 0) {
            position.left = '100%';
        } else {
            position.right = '100%';
        }

        return position;
    }

    hideMenu = () => {
        if (this.props.forceOpen) {
            this.props.forceClose();
        }
        this.setState({ visible: false, selectedItem: null });
    }

    handleClick = (e) => {
        e.preventDefault();
    }

    handleMouseEnter = () => {
        if (this.closetimer) clearTimeout(this.closetimer);

        if (this.props.disabled || this.state.visible) return;

        this.opentimer = setTimeout(() => this.setState({
            visible: true,
            selectedItem: null
        }), this.props.hoverDelay);
    }

    handleMouseLeave = () => {
        if (this.opentimer) clearTimeout(this.opentimer);

        if (!this.state.visible) return;

        this.closetimer = setTimeout(() => this.setState({
            visible: false,
            selectedItem: null
        }), this.props.hoverDelay);
    }

    menuRef = (c) => {
        this.menu = c;
    }

    subMenuRef = (c) => {
        this.subMenu = c;
    }

    registerHandlers = () => {
        document.removeEventListener('keydown', this.props.parentKeyNavigationHandler);
        document.addEventListener('keydown', this.handleKeyNavigation);
    }

    unregisterHandlers = () => {
        document.removeEventListener('keydown', this.handleKeyNavigation);
        document.addEventListener('keydown', this.props.parentKeyNavigationHandler);
    }

    render() {
        const { children, disabled, title, selected } = this.props;
        const { visible } = this.state;
        const menuProps = {
            ref: this.menuRef,
            onMouseEnter: this.handleMouseEnter,
            onMouseLeave: this.handleMouseLeave,
            className: cx(cssClasses.menuItem, cssClasses.subMenu),
            style: {
                position: 'relative'
            }
        };
        const menuItemProps = {
            className: cx(cssClasses.menuItem, {
                [cssClasses.menuItemDisabled]: disabled,
                [cssClasses.menuItemActive]: visible,
                [cssClasses.menuItemSelected]: selected
            }),
            onMouseMove: this.props.onMouseMove,
            onMouseOut: this.props.onMouseOut,
            onClick: this.handleClick
        };
        const subMenuProps = {
            ref: this.subMenuRef,
            style: {
                position: 'absolute',
                transition: 'opacity 1ms', // trigger transitionend event
                top: 0,
                left: '100%'
            },
            className: cx(cssClasses.menu, this.props.className)
        };

        return (
            <nav {...menuProps} role='menuitem' tabIndex='-1' aria-haspopup='true'>
                <div {...menuItemProps}>
                    {title}
                </div>
                <nav {...subMenuProps} role='menu' tabIndex='-1'>
                    {this.renderChildren(children)}
                </nav>
            </nav>
        );
    }
}
<commit_msg>Adding support for SubMenu to have an onClick action (#171)<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import assign from 'object-assign';

import AbstractMenu from './AbstractMenu';
import { hideMenu } from './actions';
import { callIfExists, cssClasses, hasOwnProp, store } from './helpers';
import listener from './globalEventListener';

export default class SubMenu extends AbstractMenu {
    static propTypes = {
        children: PropTypes.node.isRequired,
        title: PropTypes.node.isRequired,
        className: PropTypes.string,
        disabled: PropTypes.bool,
        hoverDelay: PropTypes.number,
        rtl: PropTypes.bool,
        selected: PropTypes.bool,
        onMouseMove: PropTypes.func,
        onMouseOut: PropTypes.func,
        forceOpen: PropTypes.bool,
        forceClose: PropTypes.func,
        parentKeyNavigationHandler: PropTypes.func
    };

    static defaultProps = {
        disabled: false,
        hoverDelay: 500,
        className: '',
        rtl: false,
        selected: false,
        onMouseMove: () => null,
        onMouseOut: () => null,
        forceOpen: false,
        forceClose: () => null,
        parentKeyNavigationHandler: () => null
    };

    constructor(props) {
        super(props);

        this.state = assign({}, this.state, {
            visible: false
        });
    }

    componentDidMount() {
        this.listenId = listener.register(() => {}, this.hideMenu);
    }

    getSubMenuType() { // eslint-disable-line class-methods-use-this
        return SubMenu;
    }

    shouldComponentUpdate(nextProps, nextState) {
        this.isVisibilityChange = (this.state.visible !== nextState.visible ||
                                  this.props.forceOpen !== nextProps.forceOpen) &&
                                  !(this.state.visible && nextProps.forceOpen) &&
                                  !(this.props.forceOpen && nextState.visible);
        return true;
    }

    componentDidUpdate() {
        if (!this.isVisibilityChange) return;
        if (this.props.forceOpen || this.state.visible) {
            const wrapper = window.requestAnimationFrame || setTimeout;
            wrapper(() => {
                const styles = this.props.rtl
                                ? this.getRTLMenuPosition()
                                : this.getMenuPosition();

                this.subMenu.style.removeProperty('top');
                this.subMenu.style.removeProperty('bottom');
                this.subMenu.style.removeProperty('left');
                this.subMenu.style.removeProperty('right');

                if (hasOwnProp(styles, 'top')) this.subMenu.style.top = styles.top;
                if (hasOwnProp(styles, 'left')) this.subMenu.style.left = styles.left;
                if (hasOwnProp(styles, 'bottom')) this.subMenu.style.bottom = styles.bottom;
                if (hasOwnProp(styles, 'right')) this.subMenu.style.right = styles.right;
                this.subMenu.classList.add(cssClasses.menuVisible);

                this.registerHandlers();
                this.setState({ selectedItem: null });
            });
        } else {
            const cleanup = () => {
                this.subMenu.removeEventListener('transitionend', cleanup);
                this.subMenu.style.removeProperty('bottom');
                this.subMenu.style.removeProperty('right');
                this.subMenu.style.top = 0;
                this.subMenu.style.left = '100%';
                this.unregisterHandlers();
            };
            this.subMenu.addEventListener('transitionend', cleanup);
            this.subMenu.classList.remove(cssClasses.menuVisible);
        }
    }

    componentWillUnmount() {
        if (this.listenId) {
            listener.unregister(this.listenId);
        }

        if (this.opentimer) clearTimeout(this.opentimer);

        if (this.closetimer) clearTimeout(this.closetimer);

        this.unregisterHandlers();
    }

    getMenuPosition = () => {
        const { innerWidth, innerHeight } = window;
        const rect = this.subMenu.getBoundingClientRect();
        const position = {};

        if (rect.bottom > innerHeight) {
            position.bottom = 0;
        } else {
            position.top = 0;
        }

        if (rect.right < innerWidth) {
            position.left = '100%';
        } else {
            position.right = '100%';
        }

        return position;
    }

    getRTLMenuPosition = () => {
        const { innerHeight } = window;
        const rect = this.subMenu.getBoundingClientRect();
        const position = {};

        if (rect.bottom > innerHeight) {
            position.bottom = 0;
        } else {
            position.top = 0;
        }

        if (rect.left < 0) {
            position.left = '100%';
        } else {
            position.right = '100%';
        }

        return position;
    }

    hideMenu = () => {
        if (this.props.forceOpen) {
            this.props.forceClose();
        }
        this.setState({ visible: false, selectedItem: null });
    }

    handleClick = (event) => {
        event.preventDefault();

        if (this.props.disabled) return;

        callIfExists(
            this.props.onClick,
            event,
            assign({}, this.props.data, store.data),
            store.target
        );

        if (this.props.preventClose) return;

        hideMenu();
    }

    handleMouseEnter = () => {
        if (this.closetimer) clearTimeout(this.closetimer);

        if (this.props.disabled || this.state.visible) return;

        this.opentimer = setTimeout(() => this.setState({
            visible: true,
            selectedItem: null
        }), this.props.hoverDelay);
    }

    handleMouseLeave = () => {
        if (this.opentimer) clearTimeout(this.opentimer);

        if (!this.state.visible) return;

        this.closetimer = setTimeout(() => this.setState({
            visible: false,
            selectedItem: null
        }), this.props.hoverDelay);
    }

    menuRef = (c) => {
        this.menu = c;
    }

    subMenuRef = (c) => {
        this.subMenu = c;
    }

    registerHandlers = () => {
        document.removeEventListener('keydown', this.props.parentKeyNavigationHandler);
        document.addEventListener('keydown', this.handleKeyNavigation);
    }

    unregisterHandlers = () => {
        document.removeEventListener('keydown', this.handleKeyNavigation);
        document.addEventListener('keydown', this.props.parentKeyNavigationHandler);
    }

    render() {
        const { children, disabled, title, selected } = this.props;
        const { visible } = this.state;
        const menuProps = {
            ref: this.menuRef,
            onMouseEnter: this.handleMouseEnter,
            onMouseLeave: this.handleMouseLeave,
            className: cx(cssClasses.menuItem, cssClasses.subMenu),
            style: {
                position: 'relative'
            }
        };
        const menuItemProps = {
            className: cx(cssClasses.menuItem, {
                [cssClasses.menuItemDisabled]: disabled,
                [cssClasses.menuItemActive]: visible,
                [cssClasses.menuItemSelected]: selected
            }),
            onMouseMove: this.props.onMouseMove,
            onMouseOut: this.props.onMouseOut,
            onClick: this.handleClick
        };
        const subMenuProps = {
            ref: this.subMenuRef,
            style: {
                position: 'absolute',
                transition: 'opacity 1ms', // trigger transitionend event
                top: 0,
                left: '100%'
            },
            className: cx(cssClasses.menu, this.props.className)
        };

        return (
            <nav {...menuProps} role='menuitem' tabIndex='-1' aria-haspopup='true'>
                <div {...menuItemProps}>
                    {title}
                </div>
                <nav {...subMenuProps} role='menu' tabIndex='-1'>
                    {this.renderChildren(children)}
                </nav>
            </nav>
        );
    }
}
<|endoftext|>


<commit_before>// map.js

"use strict";

/**
 * map() with anonymous in-line function
 */

function animals() {
  let animals = ["cat", "dog", "fish"];
  let lengths = animals.map( (animal) => {
    return animal.length;
  });

  console.log(`animals(): ${lengths}`);
}

animals();

/**
 * map() with named function
 */
function animalsV1() {
  let animals = ["cat", "dog", "fish"];

  function _getLength(word) {
    return word.length;
  }

  console.log("animalsV1(): " + animals.map(_getLength));
}

animalsV1();
<commit_msg>Edit animalsV1<commit_after>// map.js

"use strict";

/**
 * map() with anonymous in-line function
 */

function animals() {
  let animals = ["cat", "dog", "fish"];
  let lengths = animals.map( (animal) => {
    return animal.length;
  });

  console.log(`animals(): ${lengths}`);
}

animals();

/**
 * map() with named function
 */
function animalsV1() {
  let animals = ["cat", "dog", "fish"];

  function _getLength(word) {
    return word.length;
  }

  console.log(`animalsV1(): ${animals.map(_getLength)}`);
}

animalsV1();
<|endoftext|>