<commit_before>import classNames from 'classnames';
import DOMUtil from '../Util/DOMUtil';
import GeminiScrollbar from 'react-gemini-scrollbar';
import React, {PropTypes} from 'react/addons';
import Util from '../Util/Util';
import VirtualList from '../VirtualList/VirtualList';

const CSSTransitionGroup = React.addons.CSSTransitionGroup;

let sortData = (columns, data, sortBy) => {
  if (sortBy.order === undefined || sortBy.prop === undefined) {
    return data;
  }

  let sortFunction;

  columns.forEach((column) => {
    if (column.prop === sortBy.prop) {
      sortFunction = column.sortFunction;
    }
  });

  if (sortFunction) {
    // Use custom sort method if specified.
    data = Util.sortBy(data, sortFunction(sortBy.prop));
  } else {
    // Otherwise, use default sorting.
    data = Util.sortBy(data, sortBy.prop);
  }

  if (sortBy.order === 'asc') {
    data.reverse();
  }

  return data;
};

let getClassName = (column, sortBy, data) => {
  if (Util.isFunction(column.className)) {
    return column.className(
      column.prop, sortBy, data
    );
  }

  return column.className || '';
};

// Superficial array check
let arrayDiff = (a, b) => {
  if (!a || !b) {
    return true;
  }

  return a.length !== b.length;
};

export default class Table extends React.Component {
  constructor() {
    super();
    this.state = {
      sortBy: {}
    };
  }

  componentWillMount() {
    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop);
    }
  }

  componentDidMount() {
    this.updateHeight();
    this.forceUpdate();
  }

  componentWillReceiveProps() {
    this.updateHeight();

    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop, {toggle: false});
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    let props = this.props;
    let sortBy = this.state.sortBy;
    let nextSortBy = nextState.sortBy;

    // Only update on props that should cause a re-render
    return props.keys !== nextProps.keys ||
      sortBy.prop !== nextSortBy.prop ||
      sortBy.order !== nextSortBy.order ||
      arrayDiff(props.columns, nextProps.columns) ||
      arrayDiff(props.data, nextProps.data);
  }

  updateHeight() {
    if (this.refs.gemini != null) {
      this.containerNode = React.findDOMNode(
        this.refs.gemini.refs['scroll-view']
      );
    }

    // Calculate content height only once and when node is ready
    if (this.refs.itemHeightContainer != null && this.itemHeight == null) {
      this.itemHeight = DOMUtil.getComputedDimensions(
        React.findDOMNode(this.refs.itemHeightContainer).children[0]
      ).height;
    }
  }

  getHeaders(headers, sortBy) {
    let buildSortAttributes = (header) => {
      let sortEvent = this.handleSort.bind(this, header.prop);
      return {
        onClick: sortEvent,
        tabIndex: 0,
        'aria-sort': this.state.sortBy.order,
        'aria-label':
          `${header.prop}: activate to sort column ${this.state.sortBy.order}`
      };
    };

    return headers.map((header, index) => {
      let order, heading;
      let attributes = {};

      // Only add sorting events if the column has a value for 'prop'
      // and the 'sorting' property is true.
      if (header.sortable !== false && 'prop' in header) {
        attributes = buildSortAttributes(header);
        order = this.state.sortBy.order;
      }

      // If the heading property is a method, then pass to it the options and
      // render the result. Otherwise, display the value.
      if (Util.isFunction(header.heading)) {
        heading = header.heading(header.prop, order, sortBy);
      } else {
        heading = header.heading;
      }

      attributes.className = getClassName(header, this.state.sortBy, null);
      attributes.key = index;

      return (

          {heading}

      );
    });
  }

  getBufferItem(columns, styles) {
    return

;
  }

  getEmptyRowCell(columns) {
    return (

          No data

    );
  }

  getRowCells(columns, sortBy, buildRowOptions, keys, row) {
    let rowCells = columns.map((column, index) => {
      // For each column in the data, output a cell in each row with the value
      // specified by the data prop.
      let cellClassName = getClassName(column, sortBy, row);

      let cellValue = row[column.prop];

      if (column.render) {
        cellValue = column.render(column.prop, row);
      }

      if (cellValue === undefined) {
        cellValue = column.defaultContent;
        cellClassName += ' empty-cell';
      }

      return (

          {cellValue}

      );
    });

    // Create the custom row attributes object, always with a key.
    let rowAttributes = Util.extend(
      {key: Util.values(Util.pick(row, keys))},
      buildRowOptions(row, this)
    );

    return (

        {rowCells}

    );
  }

  getRows(data, columns, sortBy, buildRowOptions, keys) {
    if (data.length === 0) {
      return this.getEmptyRowCell(columns);
    }

    return data.map((row) => this.getRowCells(columns, sortBy, buildRowOptions, keys, row));
  }

  handleSort(prop, options) {
    let sortBy = this.state.sortBy;
    let onSortCallback = this.props.onSortCallback;
    options = Util.extend({
      toggle: true
    }, options);

    if (options.toggle) {
      let order;

      if (sortBy.order === 'desc') {
        order = 'asc';
      } else {
        order = 'desc';
      }

      this.setState({
        sortBy: {order, prop}
      });
    }

    if (Util.isFunction(onSortCallback)) {
      onSortCallback(sortBy);
    }
  }

  getTable(columns, data, sortBy) {
    let buildRowOptions = this.props.buildRowOptions;
    let keys = this.props.keys;
    let sortedData = sortData(columns, data, sortBy);

    let rows = this.getRows(sortedData, columns, sortBy, buildRowOptions, keys);

    // Can only use transitions in tables that does not scroll
    if (this.props.transition === true) {
      rows = (
        <CSSTransitionGroup component="tbody" transitionName="table-row">
          {rows}
        </CSSTransitionGroup>
      );
    }

    return (

        {this.props.colGroup}

          {rows}

    );
  }

  getScrollTable(columns, data, sortBy, containerHeight) {
    let classes = classNames(this.props.className, 'flush-bottom');
    let buildRowOptions = this.props.buildRowOptions;
    let keys = this.props.keys;

    let innerContent = (

        {this.getRowCells(columns, sortBy, buildRowOptions, keys, data[0])}

    );

    let style = {};

    if (this.containerNode != null) {
      style.height = containerHeight;
      let visibleItems = Math.ceil(containerHeight / this.itemHeight);

      // Re-render on ready, since VirtualList needs to be rendered on mount
      // and we need gemini to update accordingly.
      // Set itemBuffer and scrollDelay based on no. visible items.
      innerContent = (
        <VirtualList
          items={sortData(columns, data, sortBy)}
          container={this.containerNode}
          itemHeight={this.itemHeight}
          onReady={this.forceUpdate.bind(this)}
          tagName="tbody"
          renderBufferItem={this.getBufferItem.bind(this, columns)}
          renderItem={this.getRowCells.bind(this, columns, sortBy, buildRowOptions, keys)}
          itemBuffer={20 * visibleItems}
          scrollDelay={visibleItems / 4} />
      );
    }

    return (
      <GeminiScrollbar
        autoshow={true}
        ref="gemini"
        style={style}>

          {this.props.colGroup}
          {innerContent}

      </GeminiScrollbar>
    );
  }

  render() {
    let classes = classNames(this.props.className, 'flush-bottom');
    let columns = this.props.columns;
    let data = this.props.data;
    let sortBy = this.state.sortBy;
    let tableContent = null;

    let itemHeight = this.itemHeight || 0;
    let itemListHeight = itemHeight * data.length;

    // Can't grow beyond 80% of the viewport height
    let viewportHeight = 0.8 * DOMUtil.getViewportHeight();

    // Calculate the minimum of either the content or 80% of the window height
    let containerHeight = Math.min(itemListHeight, viewportHeight);

    // Use scroll table on first render to check if we need to scroll
    // and if content is bigger than its container
    if (itemHeight === 0 || itemListHeight > containerHeight) {
      tableContent = this.getScrollTable(columns, data, sortBy, containerHeight);
    } else {
      tableContent = this.getTable(columns, data, sortBy);
    }

    return (

          {this.props.colGroup}

              {this.getHeaders(columns, sortBy)}

        {tableContent}

    );
  }
}

Table.defaultProps = {
  buildRowOptions: () => { return {}; },
  sortBy: {}
};

Table.propTypes = {
  // Optional attributes to be passed to the row elements.
  buildRowOptions: PropTypes.func,

  className: PropTypes.string,

  // Optional colgroup component.
  colGroup: PropTypes.object,

  // Define how columns should be rendered and if they are sortable.
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      attributes: React.PropTypes.object,
      className: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]),
      defaultContent: PropTypes.string,
      heading: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]).isRequired,
      prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      sortable: PropTypes.bool,
      // Custom sorting function. If this function returns null,
      // it will fallback to default sorting.
      sortFunction: PropTypes.func
    })
  ).isRequired,

  // Data to display in the table.
  // Make sure to clone the data, cannot be modified!
  data: PropTypes.array.isRequired,

  // Provide what attributes in the data make a row unique.
  keys: PropTypes.arrayOf(PropTypes.string).isRequired,

  // Optional callback function when sorting is complete.
  onSortCallback: PropTypes.func,

  // Optional default sorting criteria.
  sortBy: PropTypes.shape({
    order: PropTypes.oneOf(['asc', 'desc']),
    prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
  }),

  // Optional property to add transitions or turn them off. Default is off.
  // Only available for tables that does not scroll
  transition: PropTypes.bool
};
<commit_msg>Using variable<commit_after>import classNames from 'classnames';
import DOMUtil from '../Util/DOMUtil';
import GeminiScrollbar from 'react-gemini-scrollbar';
import React, {PropTypes} from 'react/addons';
import Util from '../Util/Util';
import VirtualList from '../VirtualList/VirtualList';

const CSSTransitionGroup = React.addons.CSSTransitionGroup;

let sortData = (columns, data, sortBy) => {
  if (sortBy.order === undefined || sortBy.prop === undefined) {
    return data;
  }

  let sortFunction;

  columns.forEach((column) => {
    if (column.prop === sortBy.prop) {
      sortFunction = column.sortFunction;
    }
  });

  if (sortFunction) {
    // Use custom sort method if specified.
    data = Util.sortBy(data, sortFunction(sortBy.prop));
  } else {
    // Otherwise, use default sorting.
    data = Util.sortBy(data, sortBy.prop);
  }

  if (sortBy.order === 'asc') {
    data.reverse();
  }

  return data;
};

let getClassName = (column, sortBy, data) => {
  if (Util.isFunction(column.className)) {
    return column.className(
      column.prop, sortBy, data
    );
  }

  return column.className || '';
};

// Superficial array check
let arrayDiff = (a, b) => {
  if (!a || !b) {
    return true;
  }

  return a.length !== b.length;
};

export default class Table extends React.Component {
  constructor() {
    super();
    this.state = {
      sortBy: {}
    };
  }

  componentWillMount() {
    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop);
    }
  }

  componentDidMount() {
    this.updateHeight();
    this.forceUpdate();
  }

  componentWillReceiveProps() {
    this.updateHeight();

    if (this.props.sortBy.prop) {
      this.handleSort(this.props.sortBy.prop, {toggle: false});
    }
  }

  shouldComponentUpdate(nextProps, nextState) {
    let props = this.props;
    let sortBy = this.state.sortBy;
    let nextSortBy = nextState.sortBy;

    // Only update on props that should cause a re-render
    return props.keys !== nextProps.keys ||
      sortBy.prop !== nextSortBy.prop ||
      sortBy.order !== nextSortBy.order ||
      arrayDiff(props.columns, nextProps.columns) ||
      arrayDiff(props.data, nextProps.data);
  }

  updateHeight() {
    if (this.refs.gemini != null) {
      this.containerNode = React.findDOMNode(
        this.refs.gemini.refs['scroll-view']
      );
    }

    // Calculate content height only once and when node is ready
    if (this.refs.itemHeightContainer != null && this.itemHeight == null) {
      this.itemHeight = DOMUtil.getComputedDimensions(
        React.findDOMNode(this.refs.itemHeightContainer).children[0]
      ).height;
    }
  }

  getHeaders(headers, sortBy) {
    let buildSortAttributes = (header) => {
      let sortEvent = this.handleSort.bind(this, header.prop);
      return {
        onClick: sortEvent,
        tabIndex: 0,
        'aria-sort': this.state.sortBy.order,
        'aria-label':
          `${header.prop}: activate to sort column ${this.state.sortBy.order}`
      };
    };

    return headers.map((header, index) => {
      let order, heading;
      let attributes = {};

      // Only add sorting events if the column has a value for 'prop'
      // and the 'sorting' property is true.
      if (header.sortable !== false && 'prop' in header) {
        attributes = buildSortAttributes(header);
        order = this.state.sortBy.order;
      }

      // If the heading property is a method, then pass to it the options and
      // render the result. Otherwise, display the value.
      if (Util.isFunction(header.heading)) {
        heading = header.heading(header.prop, order, sortBy);
      } else {
        heading = header.heading;
      }

      attributes.className = getClassName(header, this.state.sortBy, null);
      attributes.key = index;

      return (

          {heading}

      );
    });
  }

  getBufferItem(columns, styles) {
    return

;
  }

  getEmptyRowCell(columns) {
    return (

          No data

    );
  }

  getRowCells(columns, sortBy, buildRowOptions, keys, row) {
    let rowCells = columns.map((column, index) => {
      // For each column in the data, output a cell in each row with the value
      // specified by the data prop.
      let cellClassName = getClassName(column, sortBy, row);

      let cellValue = row[column.prop];

      if (column.render) {
        cellValue = column.render(column.prop, row);
      }

      if (cellValue === undefined) {
        cellValue = column.defaultContent;
        cellClassName += ' empty-cell';
      }

      return (

          {cellValue}

      );
    });

    // Create the custom row attributes object, always with a key.
    let rowAttributes = Util.extend(
      {key: Util.values(Util.pick(row, keys))},
      buildRowOptions(row, this)
    );

    return (

        {rowCells}

    );
  }

  getRows(data, columns, sortBy, buildRowOptions, keys) {
    if (data.length === 0) {
      return this.getEmptyRowCell(columns);
    }

    return data.map((row) => this.getRowCells(columns, sortBy, buildRowOptions, keys, row));
  }

  handleSort(prop, options) {
    let sortBy = this.state.sortBy;
    let onSortCallback = this.props.onSortCallback;
    options = Util.extend({
      toggle: true
    }, options);

    if (options.toggle) {
      let order;

      if (sortBy.order === 'desc') {
        order = 'asc';
      } else {
        order = 'desc';
      }

      this.setState({
        sortBy: {order, prop}
      });
    }

    if (Util.isFunction(onSortCallback)) {
      onSortCallback(sortBy);
    }
  }

  getTable(columns, data, sortBy) {
    let buildRowOptions = this.props.buildRowOptions;
    let keys = this.props.keys;
    let sortedData = sortData(columns, data, sortBy);

    let rows = this.getRows(sortedData, columns, sortBy, buildRowOptions, keys);

    // Can only use transitions in tables that does not scroll
    if (this.props.transition === true) {
      rows = (
        <CSSTransitionGroup component="tbody" transitionName="table-row">
          {rows}
        </CSSTransitionGroup>
      );
    }

    return (

        {this.props.colGroup}

          {rows}

    );
  }

  getScrollTable(columns, data, sortBy, containerHeight) {
    let classes = classNames(this.props.className, 'flush-bottom');
    let containerNode = this.containerNode;
    let buildRowOptions = this.props.buildRowOptions;
    let keys = this.props.keys;

    let innerContent = (

        {this.getRowCells(columns, sortBy, buildRowOptions, keys, data[0])}

    );

    let style = {};

    if (containerNode != null) {
      style.height = containerHeight;
      let visibleItems = Math.ceil(containerHeight / this.itemHeight);

      // Re-render on ready, since VirtualList needs to be rendered on mount
      // and we need gemini to update accordingly.
      // Set itemBuffer and scrollDelay based on no. visible items.
      innerContent = (
        <VirtualList
          items={sortData(columns, data, sortBy)}
          container={containerNode}
          itemHeight={this.itemHeight}
          onReady={this.forceUpdate.bind(this)}
          tagName="tbody"
          renderBufferItem={this.getBufferItem.bind(this, columns)}
          renderItem={this.getRowCells.bind(this, columns, sortBy, buildRowOptions, keys)}
          itemBuffer={20 * visibleItems}
          scrollDelay={visibleItems / 4} />
      );
    }

    return (
      <GeminiScrollbar
        autoshow={true}
        ref="gemini"
        style={style}>

          {this.props.colGroup}
          {innerContent}

      </GeminiScrollbar>
    );
  }

  render() {
    let classes = classNames(this.props.className, 'flush-bottom');
    let columns = this.props.columns;
    let data = this.props.data;
    let sortBy = this.state.sortBy;
    let tableContent = null;

    let itemHeight = this.itemHeight || 0;
    let itemListHeight = itemHeight * data.length;

    // Can't grow beyond 80% of the viewport height
    let viewportHeight = 0.8 * DOMUtil.getViewportHeight();

    // Calculate the minimum of either the content or 80% of the window height
    let containerHeight = Math.min(itemListHeight, viewportHeight);

    // Use scroll table on first render to check if we need to scroll
    // and if content is bigger than its container
    if (itemHeight === 0 || itemListHeight > containerHeight) {
      tableContent = this.getScrollTable(columns, data, sortBy, containerHeight);
    } else {
      tableContent = this.getTable(columns, data, sortBy);
    }

    return (

          {this.props.colGroup}

              {this.getHeaders(columns, sortBy)}

        {tableContent}

    );
  }
}

Table.defaultProps = {
  buildRowOptions: () => { return {}; },
  sortBy: {}
};

Table.propTypes = {
  // Optional attributes to be passed to the row elements.
  buildRowOptions: PropTypes.func,

  className: PropTypes.string,

  // Optional colgroup component.
  colGroup: PropTypes.object,

  // Define how columns should be rendered and if they are sortable.
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      attributes: React.PropTypes.object,
      className: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]),
      defaultContent: PropTypes.string,
      heading: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]).isRequired,
      prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      sortable: PropTypes.bool,
      // Custom sorting function. If this function returns null,
      // it will fallback to default sorting.
      sortFunction: PropTypes.func
    })
  ).isRequired,

  // Data to display in the table.
  // Make sure to clone the data, cannot be modified!
  data: PropTypes.array.isRequired,

  // Provide what attributes in the data make a row unique.
  keys: PropTypes.arrayOf(PropTypes.string).isRequired,

  // Optional callback function when sorting is complete.
  onSortCallback: PropTypes.func,

  // Optional default sorting criteria.
  sortBy: PropTypes.shape({
    order: PropTypes.oneOf(['asc', 'desc']),
    prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
  }),

  // Optional property to add transitions or turn them off. Default is off.
  // Only available for tables that does not scroll
  transition: PropTypes.bool
};
<|endoftext|>


<commit_before>import React, {PropTypes} from 'react';
import _ from 'underscore';

function getHeaders(headers, sortBy, handleSort, context) {
  function buildSortProps(header) {
    var order = 'none';
    if (sortBy.prop === header.prop) {
      order = sortBy.order;
    }

    var nextOrder;
    if (order === 'desc') {
      nextOrder = 'asc';
    } else {
      nextOrder = 'desc';
    }

    // sort state data with new sortBy properties
    var sortEvent = handleSort.bind(
      context,
      {prop: header.prop, order: nextOrder}
    );

    return {
      onClick: sortEvent,
      onMouseDown: function(event) {
        event.preventDefault();
      },
      tabIndex: 0,
      'aria-sort': order,
      'aria-label': header.heading + ': activate to sort column ' + nextOrder
    };
  }

  return headers.map(function (header, index) {
    var sortProps, order, heading;
    // only add sorting events if the column has a value for 'prop'
    // and the 'sorting' property is true
    if (header.sortable !== false && 'prop' in header) {
      headingAttributes = buildSortProps(header);
      order = sortProps['aria-sort'];
    }

    // if the heading property is a method, then pass to it the options and
    // render the result. otherwise, display the value
    if (_.isFunction(header.heading)) {
      heading = header.heading(header.prop, order, sortBy);
    } else {
      heading = header.heading;
    }

    var attributes = _.extend({
      className: header.className,
      key: index
    }, headingAttributes);

    return {heading}	;
  });
}

function getRows(data, columns, keys, sortBy, buildRowOptions, context) {
  if (data.length === 0) {
    return (

          No data

    );
  }

  return data.map(function(row) {
    // create the custom row attributes object, always with a key
    var rowAttributes = _.extend(
      {key: _.values(_.pick(row, keys))},
      buildRowOptions(row, context));

    // for each column in the data, output a cell in each row with the value
    // specified by the data prop
    var rowCells = columns.map(function(column, index) {
      var cellAttributes = column.attributes;
      var cellValue = row[column.prop];
      var cellClassName = column.className;

      return (

          {cellValue}

      );
    });

    return (

        {rowCells}

    );
  });
}

function sortData(data, sortBy, customSort) {
  if (_.isFunction(customSort) && _.isFunction(customSort(sortBy.prop))) {
    // use custom sort method if specified
    data = _.sortBy(data, customSort(sortBy.prop));
  } else {
    // otherwise use default sorting
    data = _.sortBy(data, sortBy.prop);
  }

  if (sortBy.order === 'asc') {
    data.reverse();
  }

  return data;
}

export default class Table extends React.Component {
  constructor() {
    super();
    this.state = {
      sortBy: {}
    };
  }

  componentWillMount() {
    this.handleSort(this.props.sortBy);
  }

  componentWillReceiveProps() {
    this.handleSort();
  }

  handleSort(sortBy) {
    // if no sorting paramters or method are specified, use what's in state
    sortBy = sortBy || this.state.sortBy;

    var onSort = this.props.onSort;

    if (!_.isEqual(this.state.sortBy, sortBy)) {
      this.setState({sortBy: sortBy});
    }

    if (_.isFunction(onSort)) {
      onSort(sortBy);
    }
  }

  render() {
    var buildRowOptions = this.props.buildRowOptions;
    var columns = this.props.columns;
    var keys = this.props.keys;
    var sortBy = this.state.sortBy;
    var sortedData = sortData(this.props.data, sortBy, this.props.sortFunc);

    return (

        {this.props.colGroup}

            {getHeaders(columns, sortBy, this.handleSort, this)}

          {getRows(sortedData, columns, keys, sortBy, buildRowOptions, this)}

    );
  }
}

Table.defaultProps = {
  buildRowOptions: function () { return {}; },
  sortBy: {}
};

Table.propTypes = {
  // provide what makes a table unique
  keys: PropTypes.arrayOf(PropTypes.string).isRequired,

  // define how columns should be rendered and if they are sortable
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      className: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
      defaultContent: PropTypes.string,
      prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      render: PropTypes.func,
      sortable: PropTypes.bool,
      heading: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]).isRequired,
      attributes: React.PropTypes.object
    })
  ).isRequired,

  // optional colgroup component
  colGroup: PropTypes.object,

  // data to display in the table
  // make sure to clone if data, cannot be modified!
  data: PropTypes.array.isRequired,

  // attributes to be passed to the rows
  buildRowOptions: PropTypes.func,

  sortBy: PropTypes.shape({
    prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    order: PropTypes.oneOf(['asc', 'desc'])
  }),

  // custom sort function,
  // if function returns null it will fallback to default sorting
  sortFunc: PropTypes.func,

  // on sort callback function
  onSort: PropTypes.func
};
<commit_msg>Change anonymous functions to use ES6 syntax<commit_after>import React, {PropTypes} from 'react';
import _ from 'underscore';

function getHeaders(headers, sortBy, handleSort, context) {
  function buildSortProps(header) {
    var order = 'none';
    if (sortBy.prop === header.prop) {
      order = sortBy.order;
    }

    var nextOrder;
    if (order === 'desc') {
      nextOrder = 'asc';
    } else {
      nextOrder = 'desc';
    }

    // sort state data with new sortBy properties
    var sortEvent = handleSort.bind(
      context,
      {prop: header.prop, order: nextOrder}
    );

    return {
      onClick: sortEvent,
      onMouseDown: function(event) {
        event.preventDefault();
      },
      tabIndex: 0,
      'aria-sort': order,
      'aria-label': header.heading + ': activate to sort column ' + nextOrder
    };
  }

  return headers.map((header, index) => {
    var headingAttributes, order, heading;
    // only add sorting events if the column has a value for 'prop'
    // and the 'sorting' property is true
    if (header.sortable !== false && 'prop' in header) {
      headingAttributes = buildSortProps(header);
      order = headingAttributes['aria-sort'];
    }

    // if the heading property is a method, then pass to it the options and
    // render the result. otherwise, display the value
    if (_.isFunction(header.heading)) {
      heading = header.heading(header.prop, order, sortBy);
    } else {
      heading = header.heading;
    }

    var attributes = _.extend({
      className: header.className,
      key: index
    }, headingAttributes);

    return {heading}	;
  });
}

function getRows(data, columns, keys, sortBy, buildRowOptions, context) {
  if (data.length === 0) {
    return (

          No data

    );
  }

  return data.map((row) => {
    // create the custom row attributes object, always with a key
    var rowAttributes = _.extend(
      {key: _.values(_.pick(row, keys))},
      buildRowOptions(row, context));

    // for each column in the data, output a cell in each row with the value
    // specified by the data prop
    var rowCells = columns.map((column, index) => {
      var cellAttributes = column.attributes;
      var cellValue = row[column.prop];
      var cellClassName = column.className;

      return (

          {cellValue}

      );
    });

    return (

        {rowCells}

    );
  });
}

function sortData(data, sortBy, customSort) {
  if (_.isFunction(customSort) && _.isFunction(customSort(sortBy.prop))) {
    // use custom sort method if specified
    data = _.sortBy(data, customSort(sortBy.prop));
  } else {
    // otherwise use default sorting
    data = _.sortBy(data, sortBy.prop);
  }

  if (sortBy.order === 'asc') {
    data.reverse();
  }

  return data;
}

export default class Table extends React.Component {
  constructor() {
    super();
    this.state = {
      sortBy: {}
    };
  }

  componentWillMount() {
    this.handleSort(this.props.sortBy);
  }

  componentWillReceiveProps() {
    this.handleSort();
  }

  handleSort(sortBy) {
    // if no sorting paramters or method are specified, use what's in state
    sortBy = sortBy || this.state.sortBy;

    var onSort = this.props.onSort;

    if (!_.isEqual(this.state.sortBy, sortBy)) {
      this.setState({sortBy: sortBy});
    }

    if (_.isFunction(onSort)) {
      onSort(sortBy);
    }
  }

  render() {
    var buildRowOptions = this.props.buildRowOptions;
    var columns = this.props.columns;
    var keys = this.props.keys;
    var sortBy = this.state.sortBy;
    var sortedData = sortData(this.props.data, sortBy, this.props.sortFunc);

    return (

        {this.props.colGroup}

            {getHeaders(columns, sortBy, this.handleSort, this)}

          {getRows(sortedData, columns, keys, sortBy, buildRowOptions, this)}

    );
  }
}

Table.defaultProps = {
  buildRowOptions: function () { return {}; },
  sortBy: {}
};

Table.propTypes = {
  // provide what makes a table unique
  keys: PropTypes.arrayOf(PropTypes.string).isRequired,

  // define how columns should be rendered and if they are sortable
  columns: PropTypes.arrayOf(
    PropTypes.shape({
      className: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
      defaultContent: PropTypes.string,
      prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
      render: PropTypes.func,
      sortable: PropTypes.bool,
      heading: PropTypes.oneOfType([
        PropTypes.string,
        PropTypes.func
      ]).isRequired,
      attributes: React.PropTypes.object
    })
  ).isRequired,

  // optional colgroup component
  colGroup: PropTypes.object,

  // data to display in the table
  // make sure to clone if data, cannot be modified!
  data: PropTypes.array.isRequired,

  // attributes to be passed to the rows
  buildRowOptions: PropTypes.func,

  sortBy: PropTypes.shape({
    prop: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
    order: PropTypes.oneOf(['asc', 'desc'])
  }),

  // custom sort function,
  // if function returns null it will fallback to default sorting
  sortFunc: PropTypes.func,

  // on sort callback function
  onSort: PropTypes.func
};
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
	className: PropTypes.string,
	children: PropTypes.object,
	showHeader: PropTypes.bool,
	onRowClick: PropTypes.func,
	onSort: PropTypes.func,
	data: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(Map)]),
	defaultCellComponent: PropTypes.any,
	start: PropTypes.number,
	limit: PropTypes.number,
};

const nullFunc = () => {
};

const defaultProps = {
	className: '',
	children: {},
	showHeader: true,
	onRowClick: nullFunc,
	onSort: nullFunc,
	data: [],
	start: 0,
	limit: 0,
	defaultCellComponent: ({row, columnKey}) => {row[columnKey]}
};

export default class Table extends React.Component {
	constructor(props) {
		super(props);

		const c = props.children;
		const firstSorter = Object.keys(c).reduce((acc, key) => acc ? acc : c[key].sortFunc ? key : null, null);
		this.lastData = props.data;

		this.sortCallbacks = {};
		this.state = {
			sortField: firstSorter,
			sortDirection: 1
		};

		this.updateSort = (key) => {
			if (this.sortCallbacks[key]) {
				return this.sortCallbacks[key];
			}

			const cb = () => {
				if (this.state.sortField === key) {
					this.setState({sortDirection: this.state.sortDirection * -1});
					return;
				}
				this.setState({sortField: key, sortDirection: 1});
				props.onSort();
			};

			this.sortCallbacks[key] = cb;

			return cb;
		};

		this.handleRowClick = (e) => {
			const index = parseInt(e.currentTarget.getAttribute('data-index'), 10);
			if (props.onRowClick) {
				props.onRowClick(this.lastData[index], index);
			}
		};
	}

	render() {
		const {
			data: inData,
			children,
			className,
			showHeader,
			onRowClick,
			start,
			limit,
			defaultCellComponent: DefaultCellComponent
		} = this.props;

		const fullData = (inData instanceof Map)
			? [...inData.values()]
			: inData.slice();

		const {
			sortField,
			sortDirection
		} = this.state;

		if (sortField && children[sortField].sortFunc) {
			fullData.sort((rowA, rowB) => children[sortField].sortFunc(rowA[sortField], rowB[sortField]));
			if (sortDirection === -1) {
				fullData.reverse();
			}
		}

		const data = fullData.slice(start, limit ? Math.min(limit, fullData.length) : fullData.length);

		const classNames = ['mdo-table'];
		const columns = Object.keys(children).map(key => Object.assign(children[key], {key}));

		if (onRowClick !== nullFunc) {
			classNames.push('mdo-clickable');
		}

		if (className) {
			classNames.push(className);
		}

		this.lastData = data;

		return (

				{showHeader && (

						{columns.map(c => {
							const classNames = [`mdo-table-head mdo-column-${c.key}`];

							if (c.sortFunc) {
								classNames.push('mdo-sortable');
							}

							if (sortField === c.key) {
								classNames.push('mdo-sort-' + (sortDirection === 1 ? 'asc' : 'desc'));
							}
							return {c.title}	;
						})}

				)}

{data.map((row, rowIndex) => {
					const rowKey = row.id ? row.id : `r${rowIndex}`;

					return (

							{
								columns.map((c, ci) => (
									<td className={`mdo-table-cell mdo-column-${c.key}`} key={c.key}>
										{c.component
											? <c.component
												row={row}
												rowIndex={rowIndex}
												columnIndex={ci}
												columnKey={c.key}
											>
												{row[c.key]}
											</c.component>
											: <DefaultCellComponent
												row={row}
												rowIndex={rowIndex}
												columnIndex={ci}
												columnKey={c.key}
											>
												{row[c.key]}
											</DefaultCellComponent>
										}

								))
							}

					);
				})}

		);
	}
}

Table.propTypes = propTypes;
Table.defaultProps = defaultProps;

/**
 * This is a simple basic sort function you may use
 * for your table rows.
 * @param a
 * @param b
 * @returns {number}
 */
Table.basicSort = (a, b) => {
	return a > b
		? 1
		: a  Table.basicSort(parseInt(a, 10), parseInt(b, 10));
<commit_msg>Add new props to Table component<commit_after>import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
	className: PropTypes.string,
	children: PropTypes.object,
	showHeader: PropTypes.bool,
	onRowClick: PropTypes.func,
	onSort: PropTypes.func,
	data: PropTypes.oneOfType([PropTypes.array, PropTypes.instanceOf(Map)]),
	defaultCellComponent: PropTypes.any,
	start: PropTypes.number,
	limit: PropTypes.number,
	defaultSortColumn: PropTypes.string,
	defaultSortOrder: PropTypes.string,
};

const nullFunc = () => {
};

const defaultProps = {
	className: '',
	children: {},
	showHeader: true,
	onRowClick: nullFunc,
	onSort: nullFunc,
	data: [],
	start: 0,
	limit: 0,
	defaultSortColumn: null,
	defaultSortOrder: 'DESC',
	defaultCellComponent: ({row, columnKey}) => {row[columnKey]}
};

export default class Table extends React.Component {
	constructor(props) {
		super(props);

		const c = props.children;
		const firstSorter = props.defaultSortColumn || Object.keys(c).reduce((acc, key) => acc ? acc : c[key].sortFunc ? key : null, null);
		this.lastData = props.data;

		this.sortCallbacks = {};
		this.state = {
			sortField: firstSorter,
			sortDirection: props.defaultSortOrder === 'DESC' ? -1 : 1,
		};

		this.updateSort = (key) => {
			if (this.sortCallbacks[key]) {
				return this.sortCallbacks[key];
			}

			const cb = () => {
				if (this.state.sortField === key) {
					this.setState({sortDirection: this.state.sortDirection * -1});
					return;
				}
				this.setState({sortField: key, sortDirection: 1});
				props.onSort();
			};

			this.sortCallbacks[key] = cb;

			return cb;
		};

		this.handleRowClick = (e) => {
			const index = parseInt(e.currentTarget.getAttribute('data-index'), 10);
			if (props.onRowClick) {
				props.onRowClick(this.lastData[index], index);
			}
		};
	}

	render() {
		const {
			data: inData,
			children,
			className,
			showHeader,
			onRowClick,
			start,
			limit,
			defaultCellComponent: DefaultCellComponent
		} = this.props;

		const fullData = (inData instanceof Map)
			? [...inData.values()]
			: inData.slice();

		const {
			sortField,
			sortDirection
		} = this.state;

		if (sortField && children[sortField].sortFunc) {
			fullData.sort((rowA, rowB) => children[sortField].sortFunc(rowA[sortField], rowB[sortField]));
			if (sortDirection === -1) {
				fullData.reverse();
			}
		}

		const data = fullData.slice(start, limit ? Math.min(limit, fullData.length) : fullData.length);

		const classNames = ['mdo-table'];
		const columns = Object.keys(children).map(key => Object.assign(children[key], {key}));

		if (onRowClick !== nullFunc) {
			classNames.push('mdo-clickable');
		}

		if (className) {
			classNames.push(className);
		}

		this.lastData = data;

		return (

				{showHeader && (

						{columns.map(c => {
							const classNames = [`mdo-table-head mdo-column-${c.key}`];

							if (c.sortFunc) {
								classNames.push('mdo-sortable');
							}

							if (sortField === c.key) {
								classNames.push('mdo-sort-' + (sortDirection === 1 ? 'asc' : 'desc'));
							}
							return {c.title}	;
						})}

				)}

{data.map((row, rowIndex) => {
					const rowKey = row.id ? row.id : `r${rowIndex}`;
					const rowClasses = ['mdo-table-row'];

					const cells = columns.map((c, ci) => {

						if (c.precalc) {
							c.precalc({row, rowIndex, rowClasses});
						}

						return (

							<td className={`mdo-table-cell mdo-column-${c.key}`} key={c.key}>
								{c.component
									? <c.component
										row={row}
										rowIndex={rowIndex}
										columnIndex={ci}
										columnKey={c.key}
									>
										{row[c.key]}
									</c.component>
									: <DefaultCellComponent
										row={row}
										rowIndex={rowIndex}
										columnIndex={ci}
										columnKey={c.key}
									>
										{row[c.key]}
									</DefaultCellComponent>
								}

						);
					});

					return (

							{
								cells
							}

					);
				})}

		);
	}
}

Table.propTypes = propTypes;
Table.defaultProps = defaultProps;

/**
 * This is a simple basic sort function you may use
 * for your table rows.
 * @param a
 * @param b
 * @returns {number}
 */
Table.basicSort = (a, b) => {
	return a > b
		? 1
		: a  Table.basicSort(parseInt(a, 10), parseInt(b, 10));
<|endoftext|>


<commit_before>define(function(require) {

    var Texture = require('./Texture');
    var glinfo = require('./core/glinfo');
    var glenum = require('./core/glenum');
    var util = require('./core/util');

    var targetList = ['px', 'nx', 'py', 'ny', 'pz', 'nz'];

    /**
     * @constructor qtek.TextureCube
     * @extends qtek.Texture
     *
     * @example
     *     ...
     *     var mat = new qtek.Material({
     *         shader: qtek.shader.library.get('buildin.phong', 'environmentMap')
     *     });
     *     var envMap = new qtek.TextureCube();
     *     envMap.load({
     *         'px': 'assets/textures/sky/px.jpg',
     *         'nx': 'assets/textures/sky/nx.jpg'
     *         'py': 'assets/textures/sky/py.jpg'
     *         'ny': 'assets/textures/sky/ny.jpg'
     *         'pz': 'assets/textures/sky/pz.jpg'
     *         'nz': 'assets/textures/sky/nz.jpg'
     *     });
     *     mat.set('environmentMap', envMap);
     *     ...
     *     envMap.success(function() {
     *         // Wait for the sky texture loaded
     *         animation.on('frame', function(frameTime) {
     *             renderer.render(scene, camera);
     *         });
     *     });
     */
    var TextureCube = Texture.derive(function() {
        return /** @lends qtek.TextureCube# */{
            /**
             * @type {Object}
             * @property {HTMLImageElement|HTMLCanvasElemnet} px
             * @property {HTMLImageElement|HTMLCanvasElemnet} nx
             * @property {HTMLImageElement|HTMLCanvasElemnet} py
             * @property {HTMLImageElement|HTMLCanvasElemnet} ny
             * @property {HTMLImageElement|HTMLCanvasElemnet} pz
             * @property {HTMLImageElement|HTMLCanvasElemnet} nz
             */
            image: {
                px: null,
                nx: null,
                py: null,
                ny: null,
                pz: null,
                nz: null
            },
            /**
             * @type {Object}
             * @property {Uint8Array} px
             * @property {Uint8Array} nx
             * @property {Uint8Array} py
             * @property {Uint8Array} ny
             * @property {Uint8Array} pz
             * @property {Uint8Array} nz
             */
            pixels: {
                px: null,
                nx: null,
                py: null,
                ny: null,
                pz: null,
                nz: null
            },

            /**
             * @type {Array.<Object>}
             */
            mipmaps: []
       };
    }, {
        update: function(_gl) {

            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this._cache.get('webgl_texture'));

            this.beforeUpdate(_gl);

            var glFormat = this.format;
            var glType = this.type;

            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_S, this.wrapS);
            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_T, this.wrapT);

            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_MAG_FILTER, this.magFilter);
            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_MIN_FILTER, this.minFilter);

            var anisotropicExt = glinfo.getExtension(_gl, 'EXT_texture_filter_anisotropic');
            if (anisotropicExt && this.anisotropic > 1) {
                _gl.texParameterf(_gl.TEXTURE_CUBE_MAP, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic);
            }

            // Fallback to float type if browser don't have half float extension
            if (glType === 36193) {
                var halfFloatExt = glinfo.getExtension(_gl, 'OES_texture_half_float');
                if (!halfFloatExt) {
                    glType = glenum.FLOAT;
                }
            }

            if (this.mipmaps.length) {
                var width = this.width;
                var height = this.height;
                for (var i = 0; i < this.mipmaps.length; i++) {
                    var mipmap = this.mipmaps[i];
                    this._updateTextureData(_gl, mipmap, i, width, height, glFormat, glType);
                    width /= 2;
                    height /= 2;
                }
            }
            else {
                this._updateTextureData(_gl, this, 0, this.width, this.height, glFormat, glType);

                if (!this.NPOT && this.useMipmap) {
                    _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP);
                }
            }

            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, null);
        },

        _updateTextureData: function (_gl, data, level, width, height, glFormat, glType) {
            for (var i = 0; i < 6; i++) {
                var target = targetList[i];
                var img = data.image && data.image[target];
                if (img) {
                    _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, glFormat, glFormat, glType, img);
                }
                else {
                    _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, glFormat, width, height, 0, glFormat, glType, data.pixels && data.pixels[target]);
                }
            }
        },

        /**
         * @param  {WebGLRenderingContext} _gl
         * @memberOf qtek.TextureCube.prototype
         */
        generateMipmap: function(_gl) {
            if (this.useMipmap && !this.NPOT) {
                _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this._cache.get('webgl_texture'));
                _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP);
            }
        },

        bind: function(_gl) {

            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this.getWebGLTexture(_gl));
        },

        unbind: function(_gl) {
            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, null);
        },

        // Overwrite the isPowerOfTwo method
        isPowerOfTwo: function() {
            if (this.image.px) {
                return isPowerOfTwo(this.image.px.width)
                    && isPowerOfTwo(this.image.px.height);
            } else {
                return isPowerOfTwo(this.width)
                    && isPowerOfTwo(this.height);
            }

            function isPowerOfTwo(value) {
                return value & (value-1) === 0;
            }
        },

        isRenderable: function() {
            if (this.image.px) {
                return isImageRenderable(this.image.px)
                    && isImageRenderable(this.image.nx)
                    && isImageRenderable(this.image.py)
                    && isImageRenderable(this.image.ny)
                    && isImageRenderable(this.image.pz)
                    && isImageRenderable(this.image.nz);
            } else {
                return this.width && this.height;
            }
        },

        load: function(imageList) {
            var loading = 0;
            var self = this;
            util.each(imageList, function(src, target){
                var image = new Image();
                image.onload = function() {
                    loading --;
                    if (loading === 0){
                        self.dirty();
                        self.trigger('success', self);
                    }
                    image.onload = null;
                };
                image.onerror = function() {
                    loading --;
                    image.onerror = null;
                };

                loading++;
                image.src = src;
                self.image[target] = image;
            });

            return this;
        }
    });

    function isImageRenderable(image) {
        return image.nodeName === 'CANVAS' ||
                image.complete;
    }

    return TextureCube;
});<commit_msg>Texture cube isPowerOfTwo bugfix<commit_after>define(function(require) {

    var Texture = require('./Texture');
    var glinfo = require('./core/glinfo');
    var glenum = require('./core/glenum');
    var util = require('./core/util');

    var targetList = ['px', 'nx', 'py', 'ny', 'pz', 'nz'];

    /**
     * @constructor qtek.TextureCube
     * @extends qtek.Texture
     *
     * @example
     *     ...
     *     var mat = new qtek.Material({
     *         shader: qtek.shader.library.get('buildin.phong', 'environmentMap')
     *     });
     *     var envMap = new qtek.TextureCube();
     *     envMap.load({
     *         'px': 'assets/textures/sky/px.jpg',
     *         'nx': 'assets/textures/sky/nx.jpg'
     *         'py': 'assets/textures/sky/py.jpg'
     *         'ny': 'assets/textures/sky/ny.jpg'
     *         'pz': 'assets/textures/sky/pz.jpg'
     *         'nz': 'assets/textures/sky/nz.jpg'
     *     });
     *     mat.set('environmentMap', envMap);
     *     ...
     *     envMap.success(function() {
     *         // Wait for the sky texture loaded
     *         animation.on('frame', function(frameTime) {
     *             renderer.render(scene, camera);
     *         });
     *     });
     */
    var TextureCube = Texture.derive(function() {
        return /** @lends qtek.TextureCube# */{
            /**
             * @type {Object}
             * @property {HTMLImageElement|HTMLCanvasElemnet} px
             * @property {HTMLImageElement|HTMLCanvasElemnet} nx
             * @property {HTMLImageElement|HTMLCanvasElemnet} py
             * @property {HTMLImageElement|HTMLCanvasElemnet} ny
             * @property {HTMLImageElement|HTMLCanvasElemnet} pz
             * @property {HTMLImageElement|HTMLCanvasElemnet} nz
             */
            image: {
                px: null,
                nx: null,
                py: null,
                ny: null,
                pz: null,
                nz: null
            },
            /**
             * @type {Object}
             * @property {Uint8Array} px
             * @property {Uint8Array} nx
             * @property {Uint8Array} py
             * @property {Uint8Array} ny
             * @property {Uint8Array} pz
             * @property {Uint8Array} nz
             */
            pixels: {
                px: null,
                nx: null,
                py: null,
                ny: null,
                pz: null,
                nz: null
            },

            /**
             * @type {Array.<Object>}
             */
            mipmaps: []
       };
    }, {
        update: function(_gl) {

            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this._cache.get('webgl_texture'));

            this.beforeUpdate(_gl);

            var glFormat = this.format;
            var glType = this.type;

            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_S, this.wrapS);
            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_WRAP_T, this.wrapT);

            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_MAG_FILTER, this.magFilter);
            _gl.texParameteri(_gl.TEXTURE_CUBE_MAP, _gl.TEXTURE_MIN_FILTER, this.minFilter);

            var anisotropicExt = glinfo.getExtension(_gl, 'EXT_texture_filter_anisotropic');
            if (anisotropicExt && this.anisotropic > 1) {
                _gl.texParameterf(_gl.TEXTURE_CUBE_MAP, anisotropicExt.TEXTURE_MAX_ANISOTROPY_EXT, this.anisotropic);
            }

            // Fallback to float type if browser don't have half float extension
            if (glType === 36193) {
                var halfFloatExt = glinfo.getExtension(_gl, 'OES_texture_half_float');
                if (!halfFloatExt) {
                    glType = glenum.FLOAT;
                }
            }

            if (this.mipmaps.length) {
                var width = this.width;
                var height = this.height;
                for (var i = 0; i < this.mipmaps.length; i++) {
                    var mipmap = this.mipmaps[i];
                    this._updateTextureData(_gl, mipmap, i, width, height, glFormat, glType);
                    width /= 2;
                    height /= 2;
                }
            }
            else {
                this._updateTextureData(_gl, this, 0, this.width, this.height, glFormat, glType);

                if (!this.NPOT && this.useMipmap) {
                    _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP);
                }
            }

            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, null);
        },

        _updateTextureData: function (_gl, data, level, width, height, glFormat, glType) {
            for (var i = 0; i < 6; i++) {
                var target = targetList[i];
                var img = data.image && data.image[target];
                if (img) {
                    _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, glFormat, glFormat, glType, img);
                }
                else {
                    _gl.texImage2D(_gl.TEXTURE_CUBE_MAP_POSITIVE_X + i, level, glFormat, width, height, 0, glFormat, glType, data.pixels && data.pixels[target]);
                }
            }
        },

        /**
         * @param  {WebGLRenderingContext} _gl
         * @memberOf qtek.TextureCube.prototype
         */
        generateMipmap: function(_gl) {
            if (this.useMipmap && !this.NPOT) {
                _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this._cache.get('webgl_texture'));
                _gl.generateMipmap(_gl.TEXTURE_CUBE_MAP);
            }
        },

        bind: function(_gl) {

            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, this.getWebGLTexture(_gl));
        },

        unbind: function(_gl) {
            _gl.bindTexture(_gl.TEXTURE_CUBE_MAP, null);
        },

        // Overwrite the isPowerOfTwo method
        isPowerOfTwo: function() {
            if (this.image.px) {
                return isPowerOfTwo(this.image.px.width)
                    && isPowerOfTwo(this.image.px.height);
            } else {
                return isPowerOfTwo(this.width)
                    && isPowerOfTwo(this.height);
            }

            function isPowerOfTwo(value) {
                return (value & (value-1)) === 0;
            }
        },

        isRenderable: function() {
            if (this.image.px) {
                return isImageRenderable(this.image.px)
                    && isImageRenderable(this.image.nx)
                    && isImageRenderable(this.image.py)
                    && isImageRenderable(this.image.ny)
                    && isImageRenderable(this.image.pz)
                    && isImageRenderable(this.image.nz);
            } else {
                return this.width && this.height;
            }
        },

        load: function(imageList) {
            var loading = 0;
            var self = this;
            util.each(imageList, function(src, target){
                var image = new Image();
                image.onload = function() {
                    loading --;
                    if (loading === 0){
                        self.dirty();
                        self.trigger('success', self);
                    }
                    image.onload = null;
                };
                image.onerror = function() {
                    loading --;
                    image.onerror = null;
                };

                loading++;
                image.src = src;
                self.image[target] = image;
            });

            return this;
        }
    });

    function isImageRenderable(image) {
        return image.nodeName === 'CANVAS' ||
                image.complete;
    }

    return TextureCube;
});<|endoftext|>


<commit_before>var Dashboard = React.createClass({
    getInitialState: function() {
        return {page: "dashboard", query: "", error: null, selected: null, loading: false, willUpdate: true,
                animateOnUpdate: false, sortRowsLabel: null, sortRowsAscending: null}
    },
    /*
     * For large dashboards, maintaining a websocket/socket.io connection for each row is expensive. Instead,
     * we use the shouldComponentUpdate method to do an intelligent update. If a query has not yet been
     * submitted, we return the default value of true. If a query *has* been submitted, then at the bottom of
     * the submitQuery method, a timer is executed that sets the value of shouldComponentUpdate to true
     * every second, so the entire component will only be updated then.
     */
    shouldComponentUpdate: function(nextState, nextProps) {
        if (this.state.query == null) { return true; }
        if (this.state.willUpdate) {
            this.setState({willUpdate: false});
            return true;
        }
        return false;
    },
    /*
     * Here we use React's immutable updates to give it some hints on how to update the full DOM
     */
    updateFromRepublish: function(newdata) {
        var self = this;
        _.each(_.values(newdata), function(obj) {
            if (obj.Readings == null) { return; }
            if (self.state.data[obj.uuid] == null) { return; }
            self.setState(React.addons.update(self.state, {
                data: makeProp(obj.uuid, {
                    latestValue: {$set : obj.Readings[obj.Readings.length-1][1] },
                    latestTime:  {$set : moment.unix(obj.Readings[obj.Readings.length-1][0]) }
                })
            }));
        });
    },
    componentDidMount: function() {
        this.submitQuery({});
    },
    /*
     * When a query is submitted we submit it to the archiver, and render any error that we receive.
     * If there is no error, then we set our internal state to a mapping of UUID -> timeseries object.
     * Each timeseries object contains the top level keys Metadata, Properties, Path, uuid and Readings
     */
    submitQuery: function(e) {
        // fetch metadata query
        var query = this.refs.queryInput.getValue();
        this.setState({query: query, error: null, loading: true});
        var self = this;
        run_query(query,
            function(data) {
                var newstate = {};
                _.each(data, function(obj) {
                    newstate[obj.uuid] = obj;
                });
                self.setState({data: newstate, loading: false});
            },
            function(xhr,status,err) {
                self.setState({error: xhr.responseText, loading:false});
                console.error(xhr.responseText);
            }
        );

        // fetch most recent data point query
        run_dataquery(query,
            function(data) {
                var newstate = self.state.data;
                _.each(data, function(obj) {
                    if (obj.Readings == null) { return; }
                    newstate[obj.uuid].latestValue = obj.Readings[obj.Readings.length-1][1];
                    newstate[obj.uuid].latestTime = moment.unix(obj.Readings[obj.Readings.length-1][0]);
                });
                self.setState({data: newstate, loading: false});
            },
            function(xhr,status,err) {
                self.setState({error: xhr.responseText, loading:false});
                console.error(xhr.responseText);
            }
        );

        // set up realtime data updates
        console.log("subscribe", query);
        var socket = io.connect();
        socket.emit('new subscribe', query);
        socket.on(query, function(data) {
            if (self.isMounted()) {
                self.updateFromRepublish(data);
            }
        });
        this.setState({socket: socket});
        setInterval(function () {
            self.setState({willUpdate: true});
        }, 250);
    },
    showDetail: function(point) {
        this.setState({selected: point});
    },
    toggleAnimateOnUpdate: function(e) {
        this.setState({animateOnUpdate: e.target.checked});
    },
    sortRows: function(label) {
        console.log("sort by", label);
        this.setState({sortRowsLabel: label});
        if (this.state.sortRowsAscending == null) {
            this.setState({sortRowsAscending: false});
        } else if (!this.state.sortRowsAscending) {
            this.setState({sortRowsAscending: true});
        } else {
            this.setState({sortRowsAscending: null});
        }
    },
    render: function() {
        var self = this;

        // render error if one occurs during any query
        var error = ();
        if (this.state.error != null) {
            error = (
                <Panel header="Query Error" bsStyle="danger">
                    {this.state.error}
                </Panel>
            );
        }

        // set up the point detail pane on the right
        var detail = ();
        if (this.state.selected != null) {
           detail =  <PointDetail {...this.state.selected} />
        }

        // create the rows resulting from the points
        // thresholds is built off of the global threshold/color values
        var rows = _.map(this.state.data, function(point) {
            return (
                <PointRow key={point.uuid} thresholds={_.sortBy(this.props.valueLink.value, function(o) { return -o.time})} onClick={this.showDetail.bind(this, point)} {...point} />
            )
        }, this);

        if (this.state.sortRowsLabel != null) {
            rows = _.sortBy(rows, function(r) {
                switch (self.state.sortRowsLabel) {
                case "Path":
                    return r.props.Path == null ? -Number.MIN_VALUE : r.props.Path
                case "Value":
                    return r.props.latestValue == null ? -Number.MIN_VALUE : r.props.latestValue
                case "Time":
                    return r.props.latestTime == null ? -Number.MIN_VALUE : r.props.latestTime
                }
            });
            if (!this.state.sortRowsAscending) {
                rows.reverse();
            }
        }

        return (

                        <Input
                            type='text'
                            placeholder="where clause"
                            ref="queryInput"
                            hasFeedback
                        />

                        <Button onClick={this.submitQuery} disabled={this.state.loading} bsStyle='success'>
                            {this.state.loading ? "Loading..." : "Query"}
                        </Button>

                {error}

                        <ListGroup>
                            <ListGroupItem>

                                    Path

                                    Latest Value

                                    Latest Time

                            </ListGroupItem>
                            {rows}
                        </ListGroup>

                        {detail}

        );
    }
});

var PointRow = React.createClass({
    render: function() {
        //console.log("props", this.props);
        var color = 'danger';
        if (this.props.latestTime != null) {
            var difference = moment().diff(this.props.latestTime, 'seconds');
            _.each(this.props.thresholds, function(t) {
                if (difference < t.time) {
                    color = t.color;
                }
            });
        }
        return (
        <ListGroupItem href="#" onClick={this.props.onClick} bsStyle={color}>

                        {this.props.Path}

                        {this.props.latestValue}

                        {this.props.latestTime == null ? null : this.props.latestTime.format("dddd, MMMM Do YYYY, h:mm:ss a")}

                        {this.props.latestTime == null ? null : this.props.latestTime.from(moment()) }

        </ListGroupItem>
        );
    }
});

var PointDetail = React.createClass({
    render: function() {
        return (

Detail

{JSON.stringify(this.props, null, 2) }

        );
    }
});
<commit_msg>extend column header<commit_after>var Dashboard = React.createClass({
    getInitialState: function() {
        return {page: "dashboard", query: "", error: null, selected: null, loading: false, willUpdate: true,
                animateOnUpdate: false, sortRowsLabel: null, sortRowsAscending: null}
    },
    /*
     * For large dashboards, maintaining a websocket/socket.io connection for each row is expensive. Instead,
     * we use the shouldComponentUpdate method to do an intelligent update. If a query has not yet been
     * submitted, we return the default value of true. If a query *has* been submitted, then at the bottom of
     * the submitQuery method, a timer is executed that sets the value of shouldComponentUpdate to true
     * every second, so the entire component will only be updated then.
     */
    shouldComponentUpdate: function(nextState, nextProps) {
        if (this.state.query == null) { return true; }
        if (this.state.willUpdate) {
            this.setState({willUpdate: false});
            return true;
        }
        return false;
    },
    /*
     * Here we use React's immutable updates to give it some hints on how to update the full DOM
     */
    updateFromRepublish: function(newdata) {
        var self = this;
        _.each(_.values(newdata), function(obj) {
            if (obj.Readings == null) { return; }
            if (self.state.data[obj.uuid] == null) { return; }
            self.setState(React.addons.update(self.state, {
                data: makeProp(obj.uuid, {
                    latestValue: {$set : obj.Readings[obj.Readings.length-1][1] },
                    latestTime:  {$set : moment.unix(obj.Readings[obj.Readings.length-1][0]) }
                })
            }));
        });
    },
    componentDidMount: function() {
        this.submitQuery({});
    },
    /*
     * When a query is submitted we submit it to the archiver, and render any error that we receive.
     * If there is no error, then we set our internal state to a mapping of UUID -> timeseries object.
     * Each timeseries object contains the top level keys Metadata, Properties, Path, uuid and Readings
     */
    submitQuery: function(e) {
        // fetch metadata query
        var query = this.refs.queryInput.getValue();
        this.setState({query: query, error: null, loading: true});
        var self = this;
        run_query(query,
            function(data) {
                var newstate = {};
                _.each(data, function(obj) {
                    newstate[obj.uuid] = obj;
                });
                self.setState({data: newstate, loading: false});
            },
            function(xhr,status,err) {
                self.setState({error: xhr.responseText, loading:false});
                console.error(xhr.responseText);
            }
        );

        // fetch most recent data point query
        run_dataquery(query,
            function(data) {
                var newstate = self.state.data;
                _.each(data, function(obj) {
                    if (obj.Readings == null) { return; }
                    newstate[obj.uuid].latestValue = obj.Readings[obj.Readings.length-1][1];
                    newstate[obj.uuid].latestTime = moment.unix(obj.Readings[obj.Readings.length-1][0]);
                });
                self.setState({data: newstate, loading: false});
            },
            function(xhr,status,err) {
                self.setState({error: xhr.responseText, loading:false});
                console.error(xhr.responseText);
            }
        );

        // set up realtime data updates
        console.log("subscribe", query);
        var socket = io.connect();
        socket.emit('new subscribe', query);
        socket.on(query, function(data) {
            if (self.isMounted()) {
                self.updateFromRepublish(data);
            }
        });
        this.setState({socket: socket});
        setInterval(function () {
            self.setState({willUpdate: true});
        }, 250);
    },
    showDetail: function(point) {
        this.setState({selected: point});
    },
    toggleAnimateOnUpdate: function(e) {
        this.setState({animateOnUpdate: e.target.checked});
    },
    sortRows: function(label) {
        console.log("sort by", label);
        this.setState({sortRowsLabel: label});
        if (this.state.sortRowsAscending == null) {
            this.setState({sortRowsAscending: false});
        } else if (!this.state.sortRowsAscending) {
            this.setState({sortRowsAscending: true});
        } else {
            this.setState({sortRowsAscending: null});
        }
    },
    render: function() {
        var self = this;

        // render error if one occurs during any query
        var error = ();
        if (this.state.error != null) {
            error = (
                <Panel header="Query Error" bsStyle="danger">
                    {this.state.error}
                </Panel>
            );
        }

        // set up the point detail pane on the right
        var detail = ();
        if (this.state.selected != null) {
           detail =  <PointDetail {...this.state.selected} />
        }

        // create the rows resulting from the points
        // thresholds is built off of the global threshold/color values
        var rows = _.map(this.state.data, function(point) {
            return (
                <PointRow key={point.uuid} thresholds={_.sortBy(this.props.valueLink.value, function(o) { return -o.time})} onClick={this.showDetail.bind(this, point)} {...point} />
            )
        }, this);

        if (this.state.sortRowsLabel != null) {
            rows = _.sortBy(rows, function(r) {
                switch (self.state.sortRowsLabel) {
                case "Path":
                    return r.props.Path == null ? -Number.MIN_VALUE : r.props.Path
                case "Value":
                    return r.props.latestValue == null ? -Number.MIN_VALUE : r.props.latestValue
                case "Time":
                    return r.props.latestTime == null ? -Number.MIN_VALUE : r.props.latestTime
                }
            });
            if (!this.state.sortRowsAscending) {
                rows.reverse();
            }
        }

        return (

                        <Input
                            type='text'
                            placeholder="where clause"
                            ref="queryInput"
                            hasFeedback
                        />

                        <Button onClick={this.submitQuery} disabled={this.state.loading} bsStyle='success'>
                            {this.state.loading ? "Loading..." : "Query"}
                        </Button>

                {error}

                        <ListGroup>
                            <ListGroupItem>

                                    Path

                                    Latest Value

                                    Latest Time

                            </ListGroupItem>
                            {rows}
                        </ListGroup>

                        {detail}

        );
    }
});

var PointRow = React.createClass({
    render: function() {
        //console.log("props", this.props);
        var color = 'danger';
        if (this.props.latestTime != null) {
            var difference = moment().diff(this.props.latestTime, 'seconds');
            _.each(this.props.thresholds, function(t) {
                if (difference < t.time) {
                    color = t.color;
                }
            });
        }
        return (
        <ListGroupItem href="#" onClick={this.props.onClick} bsStyle={color}>

                        {this.props.Path}

                        {this.props.latestValue}

                        {this.props.latestTime == null ? null : this.props.latestTime.format("dddd, MMMM Do YYYY, h:mm:ss a")}

                        {this.props.latestTime == null ? null : this.props.latestTime.from(moment()) }

        </ListGroupItem>
        );
    }
});

var PointDetail = React.createClass({
    render: function() {
        return (

Detail

{JSON.stringify(this.props, null, 2) }

        );
    }
});
<|endoftext|>


<commit_before>var gulp = require('gulp'),
    gutil = require('gulp-util'),
    jscs = require('gulp-jscs'),
    jshint = require('gulp-jshint'),
    cached = require('gulp-cached'),
    coverage = require('gulp-jsx-coverage'),
    fs = require('fs'),
    React = require('react-tools'),
    through = require('through2'),
    buffer = require('vinyl-buffer'),
    source = require('vinyl-source-stream'),
    aliasify = require('aliasify'),
    browserify = require('browserify'),
    uglify = require('gulp-uglify'),
    nodemon = require('nodemon'),
    browserSync = require('browser-sync'),
    serverStarted = false,

// If you use vim and watch tasks be triggered 2 times when saving
// You can :set nowritebackup in vim to prevent this
// Reference: https://github.com/joyent/node/issues/3172
configs = {
    static_dir: 'static/',
    mainjs: require(process.cwd() + '/package.json').main,
    appjs: process.cwd() + '/fluxexapp.js',
    nodemon_restart_delay: 200,
    nodemon_delay: 2000,
    gulp_watch: {debounceDelay: 500},
    watchify: {debug: true, delay: 500},
    jshint_jsx: {quotmark: false},
    jshint_fail: false,
    jscs_fail: false,
    aliasify: {
        aliases: {
            request: 'browser-request'
        }
    },
    test_coverage: {
        default: {
            src: ['test/**/*.js', 'test/components/*.jsx', 'test/components/*.js'],
            istanbul: {
                coverageVariable: '__FLUXEX_COVERAGE__',
                exclude: /node_modules\/|test\//
            },
            coverage: {
                directory: 'coverage'
            },
            mocha: {},
            react: {
                sourceMap: true
            },
            coffee: {
                sourceMap: true
            }
        },
        console: {
            coverage: {
                reporters: ['text-summary']
            },
            mocha: {
                reporter: 'spec'
            }
        },
        report: {
            coverage: {
                reporters: ['lcov', 'json']
            },
            mocha: {
                reporter: 'tap'
            }
        }
    }
},

build_files = {
    js: ['actions/*.js', 'stores/*.js'],
    jsx: ['components/*.jsx']
},

restart_nodemon = function () {
    setTimeout(function () {
        nodemon.emit('restart');
    }, configs.nodemon_restart_delay);
},

lint_chain = function (task) {
    task = task.pipe(jshint.reporter('jshint-stylish'));

    if (configs.github) {
        task = task.pipe(require('gulp-github')(configs.github));
    }

    if (configs.jshint_fail) {
        task = task.pipe(('object' === typeof configs.jshint_fail) ? configs.jshint_fail : jshint.reporter('fail'));
    }

    return task;
},

// Stop using gulp-react because it do not keep original file name
react_compiler = function (options) {
    return through.obj(function (file, enc, callback) {
        try {
            file.contents = new Buffer(React.transform(file.contents.toString(), options));
            this.push(file);
        } catch (E) {
            gutil.log('[jsx ERROR]', gutil.colors.red(file.path));
            gutil.log('[jsx ERROR]', gutil.colors.red(E.message));
        }
        callback();
    });
},

// Do testing tasks
get_testing_task = function (options) {
    var cfg = JSON.parse(JSON.stringify(configs.test_coverage.default));

    cfg.istanbul.exclude = configs.test_coverage.default.istanbul.exclude;
    cfg.coverage.reporters = options.coverage.reporters;
    cfg.mocha = options.mocha;
    cfg.cleanup = configs.test_coverage.default.cleanup;

    return coverage.createTask(cfg);
},

handleJSCSError = function (E) {
    if (!configs.jscs_fail) {
        return;
    }

    if ('function' === typeof configs.jscs_fail) {
        return configs.jscs_fail(E);
    }

    this.emit('error', E);
},

bundleAll = function (b, noSave) {
    var B = b.bundle()
    .on('error', function (E) {
        gutil.log('[browserify ERROR]', gutil.colors.red(E));
    });

    if (!noSave) {
        B.pipe(source('main.js'))
        .pipe(gulp.dest(configs.static_dir + 'js/'))
        .on('end', restart_nodemon);
    }

    return B;
},

buildApp = function (watch, fullpath, nosave, disc) {
    var b = browserify(configs.appjs, {
        cache: {},
        packageCache: {},
        require: (disc ? process.cwd() : '.') + '/components/Html.jsx',
        standalone: 'Fluxex',
        fullPaths: fullpath ? true: false,
        debug: watch
    });

    b.transform('reactify');
    b.transform(aliasify.configure(configs.aliasify), {global: true});

    if (watch) {
        b = require('watchify')(b, configs.watchify);
        b.on('update', function (F) {
            gutil.log('[browserify] ' + F[0] + ' updated');
            bundleAll(b);
        });
    }

    return bundleAll(b, nosave);
};

gulp.task('build_app', function () {
    return buildApp(false, false, true)
        .pipe(source('main.js'))
        .pipe(buffer())
        .pipe(uglify())
        .pipe(gulp.dest(configs.static_dir + 'js/'));
});

gulp.task('disc_app', function () {
    return buildApp(false, true, true, true)
        .pipe(require('disc')())
        .pipe(fs.createWriteStream(configs.static_dir + 'disc.html'));
});

gulp.task('watch_app', function () {
    return buildApp(true, true);
});

gulp.task('watch_flux_js', ['lint_flux_js'], function () {
    gulp.watch(build_files.js, configs.gulp_watch, ['lint_flux_js']);
});

gulp.task('lint_flux_js', function () {
    return lint_chain(
        gulp.src(build_files.js)
        .pipe(cached('jshint'))
        .pipe(jscs()).on('error', handleJSCSError)
        .pipe(jshint())
    );
});

gulp.task('watch_jsx', ['lint_jsx'], function () {
    gulp.watch(build_files.jsx, configs.gulp_watch, ['lint_jsx']);
});

gulp.task('lint_jsx', function () {
    return lint_chain(
        gulp.src(build_files.jsx)
        .pipe(cached('jshint'))
        .pipe(react_compiler({sourceMap: true}))
        .pipe(jscs()).on('error', handleJSCSError)
        .pipe(jshint(configs.jshint_jsx))
    );
});

gulp.task('watch_server', ['lint_server'], function () {
    gulp.watch(configs.mainjs, configs.gulp_watch, ['lint_server'])
    .on('change', function () {
        nodemon.emit('restart');
    });
});

gulp.task('lint_server', function () {
    return gulp.src(configs.mainjs)
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});

gulp.task('nodemon_server', ['watch_flux_js', 'watch_jsx', 'watch_app', 'watch_server'], function () {
    nodemon({
        ignore: '*',
        script: configs.mainjs,
        ext: 'do_not_watch'
    })
    .on('log', function (log) {
        gutil.log(log.colour);
    })
    .on('start', function () {
        if (serverStarted) {
            setTimeout(browserSync.reload, configs.nodemon_delay);
        } else {
            browserSync.init(null, {
                proxy: 'http://localhost:3000',
                files: [configs.static_dir + 'css/*.css'],
                port: 3001,
                online: false,
                open: false
            });

            serverStarted = true;
        }
    });
});

gulp.task('watch_tests', ['test_app'], function () {
    gulp.watch([
        configs.test_coverage.default.src,
        build_files.js,
        build_files.jsx
    ], ['test_app']);
});
gulp.task('test_app', function (cb) {
    get_testing_task(configs.test_coverage.console)(cb).on('error', function (E) {
        // skip error
    }).pipe(buffer()); // pipe as nop to eat gulp error
});
gulp.task('save_test_app', function () {
    return get_testing_task(configs.test_coverage.report)();
});
gulp.task('develop', ['nodemon_server']);
gulp.task('lint_all', ['lint_server', 'lint_flux_js', 'lint_jsx']);
gulp.task('buildall', ['lint_all', 'build_app']);
gulp.task('default',['buildall']);

module.exports = configs;
<commit_msg>fix for test gone bug<commit_after>var gulp = require('gulp'),
    gutil = require('gulp-util'),
    jscs = require('gulp-jscs'),
    jshint = require('gulp-jshint'),
    cached = require('gulp-cached'),
    coverage = require('gulp-jsx-coverage'),
    fs = require('fs'),
    React = require('react-tools'),
    through = require('through2'),
    buffer = require('vinyl-buffer'),
    source = require('vinyl-source-stream'),
    aliasify = require('aliasify'),
    browserify = require('browserify'),
    uglify = require('gulp-uglify'),
    nodemon = require('nodemon'),
    browserSync = require('browser-sync'),
    serverStarted = false,

// If you use vim and watch tasks be triggered 2 times when saving
// You can :set nowritebackup in vim to prevent this
// Reference: https://github.com/joyent/node/issues/3172
configs = {
    static_dir: 'static/',
    mainjs: require(process.cwd() + '/package.json').main,
    appjs: process.cwd() + '/fluxexapp.js',
    nodemon_restart_delay: 200,
    nodemon_delay: 2000,
    gulp_watch: {debounceDelay: 500},
    watchify: {debug: true, delay: 500},
    jshint_jsx: {quotmark: false},
    jshint_fail: false,
    jscs_fail: false,
    aliasify: {
        aliases: {
            request: 'browser-request'
        }
    },
    test_coverage: {
        default: {
            src: ['test/**/*.js', 'test/components/*.jsx', 'test/components/*.js'],
            istanbul: {
                coverageVariable: '__FLUXEX_COVERAGE__',
                exclude: /node_modules\/|test\//
            },
            coverage: {
                directory: 'coverage'
            },
            mocha: {},
            react: {
                sourceMap: true
            },
            coffee: {
                sourceMap: true
            }
        },
        console: {
            coverage: {
                reporters: ['text-summary']
            },
            mocha: {
                reporter: 'spec'
            }
        },
        report: {
            coverage: {
                reporters: ['lcov', 'json']
            },
            mocha: {
                reporter: 'tap'
            }
        }
    }
},

build_files = {
    js: ['actions/*.js', 'stores/*.js'],
    jsx: ['components/*.jsx']
},

restart_nodemon = function () {
    setTimeout(function () {
        nodemon.emit('restart');
    }, configs.nodemon_restart_delay);
},

lint_chain = function (task) {
    task = task.pipe(jshint.reporter('jshint-stylish'));

    if (configs.github) {
        task = task.pipe(require('gulp-github')(configs.github));
    }

    if (configs.jshint_fail) {
        task = task.pipe(('object' === typeof configs.jshint_fail) ? configs.jshint_fail : jshint.reporter('fail'));
    }

    return task;
},

// Stop using gulp-react because it do not keep original file name
react_compiler = function (options) {
    return through.obj(function (file, enc, callback) {
        try {
            file.contents = new Buffer(React.transform(file.contents.toString(), options));
            this.push(file);
        } catch (E) {
            gutil.log('[jsx ERROR]', gutil.colors.red(file.path));
            gutil.log('[jsx ERROR]', gutil.colors.red(E.message));
        }
        callback();
    });
},

// Do testing tasks
get_testing_task = function (options) {
    var cfg = JSON.parse(JSON.stringify(configs.test_coverage.default));

    cfg.istanbul.exclude = configs.test_coverage.default.istanbul.exclude;
    cfg.coverage.reporters = options.coverage.reporters;
    cfg.mocha = options.mocha;
    cfg.cleanup = configs.test_coverage.default.cleanup;

    return coverage.createTask(cfg);
},

handleJSCSError = function (E) {
    if (!configs.jscs_fail) {
        return;
    }

    if ('function' === typeof configs.jscs_fail) {
        return configs.jscs_fail(E);
    }

    this.emit('error', E);
},

bundleAll = function (b, noSave) {
    var B = b.bundle()
    .on('error', function (E) {
        gutil.log('[browserify ERROR]', gutil.colors.red(E));
    });

    if (!noSave) {
        B.pipe(source('main.js'))
        .pipe(gulp.dest(configs.static_dir + 'js/'))
        .on('end', restart_nodemon);
    }

    return B;
},

buildApp = function (watch, fullpath, nosave, disc) {
    var b = browserify(configs.appjs, {
        cache: {},
        packageCache: {},
        require: (disc ? process.cwd() : '.') + '/components/Html.jsx',
        standalone: 'Fluxex',
        fullPaths: fullpath ? true: false,
        debug: watch
    });

    b.transform('reactify');
    b.transform(aliasify.configure(configs.aliasify), {global: true});

    if (watch) {
        b = require('watchify')(b, configs.watchify);
        b.on('update', function (F) {
            gutil.log('[browserify] ' + F[0] + ' updated');
            bundleAll(b);
        });
    }

    return bundleAll(b, nosave);
};

gulp.task('build_app', function () {
    return buildApp(false, false, true)
        .pipe(source('main.js'))
        .pipe(buffer())
        .pipe(uglify())
        .pipe(gulp.dest(configs.static_dir + 'js/'));
});

gulp.task('disc_app', function () {
    return buildApp(false, true, true, true)
        .pipe(require('disc')())
        .pipe(fs.createWriteStream(configs.static_dir + 'disc.html'));
});

gulp.task('watch_app', function () {
    return buildApp(true, true);
});

gulp.task('watch_flux_js', ['lint_flux_js'], function () {
    gulp.watch(build_files.js, configs.gulp_watch, ['lint_flux_js']);
});

gulp.task('lint_flux_js', function () {
    return lint_chain(
        gulp.src(build_files.js)
        .pipe(cached('jshint'))
        .pipe(jscs()).on('error', handleJSCSError)
        .pipe(jshint())
    );
});

gulp.task('watch_jsx', ['lint_jsx'], function () {
    gulp.watch(build_files.jsx, configs.gulp_watch, ['lint_jsx']);
});

gulp.task('lint_jsx', function () {
    return lint_chain(
        gulp.src(build_files.jsx)
        .pipe(cached('jshint'))
        .pipe(react_compiler({sourceMap: true}))
        .pipe(jscs()).on('error', handleJSCSError)
        .pipe(jshint(configs.jshint_jsx))
    );
});

gulp.task('watch_server', ['lint_server'], function () {
    gulp.watch(configs.mainjs, configs.gulp_watch, ['lint_server'])
    .on('change', function () {
        nodemon.emit('restart');
    });
});

gulp.task('lint_server', function () {
    return gulp.src(configs.mainjs)
    .pipe(jshint())
    .pipe(jshint.reporter('jshint-stylish'));
});

gulp.task('nodemon_server', ['watch_flux_js', 'watch_jsx', 'watch_app', 'watch_server'], function () {
    nodemon({
        ignore: '*',
        script: configs.mainjs,
        ext: 'do_not_watch'
    })
    .on('log', function (log) {
        gutil.log(log.colour);
    })
    .on('start', function () {
        if (serverStarted) {
            setTimeout(browserSync.reload, configs.nodemon_delay);
        } else {
            browserSync.init(null, {
                proxy: 'http://localhost:3000',
                files: [configs.static_dir + 'css/*.css'],
                port: 3001,
                online: false,
                open: false
            });

            serverStarted = true;
        }
    });
});

gulp.task('watch_tests', ['test_app'], function () {
    gulp.watch([
        configs.test_coverage.default.src,
        build_files.js,
        build_files.jsx
    ], ['test_app']);
});
gulp.task('test_app', function (cb) {
    get_testing_task(configs.test_coverage.console)(cb).on('error', function (E) {
        console.log(E.message);
    }).on('end', function () {
        cb(); // prevent failed ending
    });
});
gulp.task('save_test_app', function () {
    return get_testing_task(configs.test_coverage.report)();
});
gulp.task('develop', ['nodemon_server']);
gulp.task('lint_all', ['lint_server', 'lint_flux_js', 'lint_jsx']);
gulp.task('buildall', ['lint_all', 'build_app']);
gulp.task('default',['buildall']);

module.exports = configs;
<|endoftext|>