<commit_before>import View from 'nbd/View';
import extend from 'nbd/util/extend';
import async from 'nbd/util/async';

import sax from './sax';
import transform from './transformTag';

import createElement from './virtual-dom/vdom/create-element';
import patch from './virtual-dom/vdom/patch';
import diff from './virtual-dom/vtree/diff';
import h from './virtual-dom/virtual-hyperscript/index';
import svg from './virtual-dom/virtual-hyperscript/svg';

const _parser = sax.parser(false, {
  html5: true,
  trim: true,
  normalize: true,
  lowercase: true
});
const tagstack = [];

const blocking = '__debounced__'; // Symbol('debounced');

function debounce(fn) {
  if (fn[blocking]) { return; }
  const retval = fn.call(this);
  fn[blocking] = true;
  async(() => delete fn[blocking]);
  return retval;
}

class EventHandler {
  constructor(handler) {
    this.handler = handler;
    this.listener = this.listener.bind(this);
  }

  listener(event) {
    const spec = this.handler.events[event.type];
    this.caller(spec, event);
  }

  caller(spec, event) {
    if (!spec) { return; }
    switch (typeof spec) {
      case 'function':
        spec.call(event.currentTarget, event);
      break;
      case 'string':
        this.handler[spec](event);
      break;
      case 'object':
        if (Array.isArray(spec)) {
          spec.forEach(s => this.caller(s, event));
        }
        else {
          for (let selector in spec) {
            if (this.match(event.currentTarget, selector, event.target)) {
              this.caller(spec[selector], event);
            }
          }
        }
      break;
    }
  }

  match(root, selector, target) {
    root = root || target.document || target.ownerDocument;

    for (let match of root.querySelectorAll(selector)) {
      if (match === target) {
        return true;
      }
    }
    return false;
  }

  hook(node) {
    for (let event in this.handler.events) {
      node.addEventListener(event, this.listener, false);
    }
  }

  unhook(node) {
    for (let event in this.handler.events) {
      node.removeEventListener(event, this.listener, false);
    }
  }
}

let tree, handler;

extend(_parser, {
  onopentag({ name, attributes }) {
    const tag = { name, attributes, children: [] };
    transform(tag);

    if (name.toLowerCase() === 'svg') {
      _parser.svg = true;
    }

    if (tagstack[tagstack.length - 1]) {
      tagstack[tagstack.length - 1].children.push(tag);
    }
    else if (handler && handler.events) {
      tag.properties['event-mappable'] = new EventHandler(handler);
    }
    tagstack.push(tag);
  },
  onclosetag() {
    const tag = tagstack.pop();

    // switch to svg() when in svg mode
    let vnode = (_parser.svg ? svg : h)(tag.name, tag.properties, tag.children);

    if (tag.name.toLowerCase() === 'svg') {
      _parser.svg = false;
    }
    const last = tagstack[tagstack.length - 1];
    if (last) {
      last.children.splice(last.children.lastIndexOf(tag), 1, vnode);
    }
    else {
      tree = vnode;
    }
  },
  ontext(text) {
    if (!text.trim()) {
      return;
    }
    if (tagstack[tagstack.length - 1]) {
      tagstack[tagstack.length - 1].children.push(text);
    }
  },
  onend() {
    tagstack.length = 0;
  }
});

export default View.extend({
  init(...args) {
    this._super(...args);
    if (this._model.on) {
      this.listenTo(this._model, 'all', () => debounce.call(this, this.render));
    }

    this.on({
      prerender() {
        handler = this;
      },
      postrender() {
        handler = null;
      }
    });
  }
}, {
  domify(html) {
    tree = null;
    _parser.write(html).close();
    return tree;
  },

  appendTo(tree, $context) {
    tree._el = createElement(tree);
    return View.appendTo(tree._el, $context);
  },

  replace(oldtree, newtree) {
    const p = diff(oldtree, newtree);
    if (newtree) {
      newtree._el = oldtree._el;
    }
    patch(oldtree._el, p);
  },

  find($root, selector) {
    return View.find($root._el || $root, selector);
  },

  remove($el) {
    this.replace($el);
  }
});
<commit_msg>Magic: proper event delegation<commit_after>import View from 'nbd/View';
import extend from 'nbd/util/extend';
import async from 'nbd/util/async';

import sax from './sax';
import transform from './transformTag';

import createElement from './virtual-dom/vdom/create-element';
import patch from './virtual-dom/vdom/patch';
import diff from './virtual-dom/vtree/diff';
import h from './virtual-dom/virtual-hyperscript/index';
import svg from './virtual-dom/virtual-hyperscript/svg';

const _parser = sax.parser(false, {
  html5: true,
  trim: true,
  normalize: true,
  lowercase: true
});
const tagstack = [];

const blocking = '__debounced__'; // Symbol('debounced');

function debounce(fn) {
  if (fn[blocking]) { return; }
  const retval = fn.call(this);
  fn[blocking] = true;
  async(() => delete fn[blocking]);
  return retval;
}

class EventHandler {
  constructor(handler) {
    this.handler = handler;
    this.listener = this.listener.bind(this);
  }

  listener(event) {
    this.caller(this.handler.events[event.type], event);
  }

  caller(spec, event) {
    if (!spec) { return; }
    switch (typeof spec) {
      case 'function':
        spec.call(event.delegateTarget || event.currentTarget, event);
      break;
      case 'string':
        this.handler[spec](event);
      break;
      case 'object':
        if (Array.isArray(spec)) {
          spec.forEach(s => this.caller(s, event));
        }
        else {
          for (let selector in spec) {
            // Find all matching elements
            const matches = event.currentTarget.querySelectorAll(selector);

            // If event.target is, or is a child of matching element
            let found = this.find(matches, event.target);
            if (found) {
              event.delegateTarget = found;
              this.caller(spec[selector], event);
              delete event.delegateTarget;
            }
          }
        }
      break;
    }
  }

  find(matches, target) {
    for (let match of matches) {
      // Is the target itself or contains the target
      if (match === target || match.contains(target)) {
        return match;
      }
    }
  }

  hook(node) {
    for (let event in this.handler.events) {
      node.addEventListener(event, this.listener, false);
    }
  }

  unhook(node) {
    for (let event in this.handler.events) {
      node.removeEventListener(event, this.listener, false);
    }
  }
}

let tree, handler;

extend(_parser, {
  onopentag({ name, attributes }) {
    const tag = { name, attributes, children: [] };
    transform(tag);

    if (name.toLowerCase() === 'svg') {
      _parser.svg = true;
    }

    if (tagstack[tagstack.length - 1]) {
      tagstack[tagstack.length - 1].children.push(tag);
    }
    else if (handler && handler.events) {
      tag.properties['event-mappable'] = new EventHandler(handler);
    }
    tagstack.push(tag);
  },
  onclosetag() {
    const tag = tagstack.pop();

    // switch to svg() when in svg mode
    let vnode = (_parser.svg ? svg : h)(tag.name, tag.properties, tag.children);

    if (tag.name.toLowerCase() === 'svg') {
      _parser.svg = false;
    }
    const last = tagstack[tagstack.length - 1];
    if (last) {
      last.children.splice(last.children.lastIndexOf(tag), 1, vnode);
    }
    else {
      tree = vnode;
    }
  },
  ontext(text) {
    if (!text.trim()) {
      return;
    }
    if (tagstack[tagstack.length - 1]) {
      tagstack[tagstack.length - 1].children.push(text);
    }
  },
  onend() {
    tagstack.length = 0;
  }
});

export default View.extend({
  init(...args) {
    this._super(...args);
    if (this._model.on) {
      this.listenTo(this._model, 'all', () => debounce.call(this, this.render));
    }

    this.on({
      prerender() {
        handler = this;
      },
      postrender() {
        handler = null;
      }
    });
  }
}, {
  domify(html) {
    tree = null;
    _parser.write(html).close();
    return tree;
  },

  appendTo(tree, $context) {
    tree._el = createElement(tree);
    return View.appendTo(tree._el, $context);
  },

  replace(oldtree, newtree) {
    const p = diff(oldtree, newtree);
    if (newtree) {
      newtree._el = oldtree._el;
    }
    patch(oldtree._el, p);
  },

  find($root, selector) {
    return View.find($root._el || $root, selector);
  },

  remove($el) {
    this.replace($el);
  }
});
<|endoftext|>


<commit_before>import Module    from './models/Module.js';
import Component from './models/Component.js';
import selectors from './helpers/Selectors.js';
import utility   from './helpers/Utility.js';
import events    from './helpers/Events.js';

(function main($window, $document) {

    "use strict";

    if (typeof System !== 'undefined') {
        System.transpiler   = 'babel';
        System.babelOptions = { blacklist: [] };
    }

    /**
     * @constant HAS_INITIATED
     * @type {Boolean}
     */
    let HAS_INITIATED = false;

    /**
     * @method isReady
     * @param {String} state
     * @return {Boolean}
     */
    function isReady(state) {
        let readyStates = ['interactive', 'complete'];
        return (!HAS_INITIATED && ~readyStates.indexOf(state));
    }

    /**
     * @module Maple
     * @link https://github.com/Wildhoney/Maple.js
     * @author Adam Timberlake
     */
    class Maple {

        /**
         * @constructor
         * @return {void}
         */
        constructor() {

            HAS_INITIATED = true;

            this.findLinks();
            this.findTemplates();

            // Configure the event delegation mappings.
            events.setupDelegation();

        }

        /**
         * Responsible for finding all of the external link elements, as well as the inline template elements
         * that can be handcrafted, or baked into the HTML document when compiling a project.
         *
         * @method findLinks
         * @return {void}
         */
        findLinks() {

            selectors.getLinks($document).forEach((linkElement) => {

                if (linkElement.import) {
                    return void new Module(linkElement);
                }

                linkElement.addEventListener('load', () => new Module(linkElement));

            });

        }

        /**
         * Responsible for finding all of the template HTML elements that contain the `ref` attribute which
         * is the component's original path before Mapleify.
         *
         * @method findTemplates
         * @return {void}
         */
        findTemplates() {

            selectors.getTemplates($document).forEach((templateElement) => {

                let scriptElements = selectors.getAllScripts(templateElement.content);
                let ref            = templateElement.getAttribute('ref');
                let path           = utility.resolver(ref, null).production;

                scriptElements.forEach((scriptElement) => {

                    if (path.isLocalPath(scriptElement.getAttribute('src'))) {
                        new Component(path, templateElement, scriptElement);
                    }

                });

            });

        }

    }

    // Support for the "async" attribute on the Maple script element.
    if (isReady($document.readyState)) {
        new Maple();
    }

    // No documents, no person.
    $document.addEventListener('DOMContentLoaded', () => new Maple());

})(window, document);<commit_msg>Better initialisation for 'async' and 'defer' attrs :wrench:<commit_after>import Module    from './models/Module.js';
import Component from './models/Component.js';
import selectors from './helpers/Selectors.js';
import utility   from './helpers/Utility.js';
import events    from './helpers/Events.js';

(function main($window, $document) {

    "use strict";

    if (typeof System !== 'undefined') {
        System.transpiler   = 'babel';
        System.babelOptions = { blacklist: [] };
    }

    /**
     * @constant HAS_INITIATED
     * @type {Boolean}
     */
    let HAS_INITIATED = false;

    /**
     * @module Maple
     * @link https://github.com/Wildhoney/Maple.js
     * @author Adam Timberlake
     */
    class Maple {

        /**
         * @constructor
         * @return {void}
         */
        constructor() {

            this.findLinks();
            this.findTemplates();

            // Configure the event delegation mappings.
            events.setupDelegation();

        }

        /**
         * Responsible for finding all of the external link elements, as well as the inline template elements
         * that can be handcrafted, or baked into the HTML document when compiling a project.
         *
         * @method findLinks
         * @return {void}
         */
        findLinks() {

            selectors.getLinks($document).forEach((linkElement) => {

                if (linkElement.import) {
                    return void new Module(linkElement);
                }

                linkElement.addEventListener('load', () => new Module(linkElement));

            });

        }

        /**
         * Responsible for finding all of the template HTML elements that contain the `ref` attribute which
         * is the component's original path before Mapleify.
         *
         * @method findTemplates
         * @return {void}
         */
        findTemplates() {

            selectors.getTemplates($document).forEach((templateElement) => {

                let scriptElements = selectors.getAllScripts(templateElement.content);
                let ref            = templateElement.getAttribute('ref');
                let path           = utility.resolver(ref, null).production;

                scriptElements.forEach((scriptElement) => {

                    if (path.isLocalPath(scriptElement.getAttribute('src'))) {
                        new Component(path, templateElement, scriptElement);
                    }

                });

            });

        }

    }

    /**
     * @method initialise
     * @return {Boolean}
     */
    function initialise() {

        let state       = $document.readyState,
            readyStates = ['interactive', 'complete'];

        if (!HAS_INITIATED && ~readyStates.indexOf(state)) {

            HAS_INITIATED = true;

            // No documents, no person.
            new Maple();

        }

    }

    // Support for async, defer, and normal inclusion.
    initialise();
    $document.addEventListener('DOMContentLoaded', initialise);

})(window, document);<|endoftext|>


<commit_before>class GPhotosPhoto {
  /**
   * @ignore
   */
  constructor ({
    id, uploadedAt, createdAt, type = 'photo',
    title, length, width, height, rawUrl, uploadInfo, _parent
  }) {
    /** @type {String} */
    this.id = id;
    /** @type {?Date} */
    this.uploadedAt = new Date(uploadedAt);
    /** @type {?Date} */
    this.createdAt = new Date(createdAt);
    /** @type {?String} */
    this.title = title;
    /** @type {?number} */
    this.length = length;
    /** @type {?number} */
    this.width = width;
    /** @type {?number} */
    this.height = height;
    /** @type {?String} */
    this.rawUrl = rawUrl;
    /** @type {String} */
    this.type = type;

    Object.defineProperties(this, {
      '_gphotos': {
        value: _parent
      },
      '_logger': {
        value: _parent._logger
      },
      '_uploadInfo': {
        value: uploadInfo,
        writable: true
      }
    });
  }

  /**
   * @return {Promise<boolean,Error>}
   */
  async removeFromAlbum () {
    const query = [ [ this.id ], [] ];

    await this._gphotos._sendMutateQuery(85381832, query, true)
      .catch((_err) => {
        this._logger.error(`Failed to remove photo. ${_err.message}`);
        return Promise.reject(_err);
      });

    return true;
  }

  /**
   * @return {Promise<boolean,Error>}
   */
  async remove () {
    const query = [[], 1, [this.id], 4, null, []];

    await this._gphotos._sendMutateQuery(73931313, query, true)
      .catch((_err) => {
        this._logger.error(`Failed to remove photo. ${_err.message}`);
        return Promise.reject(_err);
      });

    return true;
  }
}

export default GPhotosPhoto;
<commit_msg>Add update method<commit_after>class GPhotosPhoto {
  /**
   * @ignore
   */
  constructor ({
    id, uploadedAt, createdAt, type = 'photo',
    title, length, width, height, filesize, rawUrl, uploadInfo, _parent
  }) {
    /** @type {String} */
    this.id = id;
    /** @type {?Date} */
    this.uploadedAt = new Date(uploadedAt);
    /** @type {?Date} */
    this.createdAt = new Date(createdAt);
    /** @type {?String} */
    this.title = title;
    /** @type {?number} */
    this.length = length;
    /** @type {?number} */
    this.width = width;
    /** @type {?number} */
    this.height = height;
    /** @type {?number} */
    this.filesize = filesize;
    /** @type {?String} */
    this.rawUrl = rawUrl;
    /** @type {String} */
    this.type = type;

    Object.defineProperties(this, {
      '_gphotos': {
        value: _parent
      },
      '_logger': {
        value: _parent._logger
      },
      '_uploadInfo': {
        value: uploadInfo,
        writable: true
      }
    });
  }

  /**
   * @return {Promise<boolean,Error>}
   */
  async removeFromAlbum () {
    const query = [ [ this.id ], [] ];

    await this._gphotos._sendMutateQuery(85381832, query, true)
      .catch((_err) => {
        this._logger.error(`Failed to remove photo. ${_err.message}`);
        return Promise.reject(_err);
      });

    return true;
  }

  /**
   * @return {Promise<boolean,Error>}
   */
  async remove () {
    const query = [[], 1, [this.id], 4, null, []];

    await this._gphotos._sendMutateQuery(73931313, query, true)
      .catch((_err) => {
        this._logger.error(`Failed to remove photo. ${_err.message}`);
        return Promise.reject(_err);
      });

    return true;
  }

  /**
   * @return {Promise<boolean,Error>}
   */
  async update () {
    const queries = [[this.id, 1],
                     [this.id, null, null, true]];
    const results =
      await Promise.all([
        this._gphotos._sendDataQuery(73756775, queries[0]),
        this._gphotos._sendDataQuery(74881883, queries[1])
      ])
      .catch((_err) => {
        this._logger.error(`Failed to update. ${_err.message}`);
        return Promise.reject(_err);
      });
    this.title = results[0][0][2];
    this.filesize = results[0][0][5];
    this.type = (!results[1][0][12]) ? 'photo' : 'video';
    this.createdAt = new Date(results[1][0][2]);
    this.uploadedAt = new Date(results[1][0][5]);
    this.length = (this.type === 'video') ? results[1][0][12]['76647426'][0] : null;
    this.width = (this.type === 'photo') ? results[1][0][1][1] : results[1][0][12]['76647426'][2];
    this.height = (this.type === 'photo') ? results[1][0][1][2] : results[1][0][12]['76647426'][3];
    this.rawUrl = results[1][0][1][0];
    return true;
  }
}

export default GPhotosPhoto;
<|endoftext|>


<commit_before>'use strict';

const util = require('util');
const _ = require('lodash');
const chalk = require('chalk');
const yargs = require('yargs').argv;

const Config = require('./Libs/Config');
const Log = require('./Libs/Log');
const Queue = require('./Libs/Queue');
const Utils = require('./Libs/Utils');

const ScenarioInputInvalid = require('./Errors/ScenarioInputInvalid');

class Syrup {
    constructor() {
        this._config = new Config;
        this._debugging = false;
        this._globalsFile = false;
        this._logFile = false;
        this._log = new Log;
        this._queue = new Queue;
        this._utils = new Utils;

        if (yargs.debug) {
            this.enableDebug();
        }

        if (yargs.output) {
            this.enableLogToFile(yargs.output);
        }
    }
    enableDebug() {
        this._debugging = true;

        return this;
    }
    enableLogToFile(path) {
        this._logFile = require('path').resolve(path);

        return this;
    }
    registerGlobals(path) {
        this._globalsFile = require('path').resolve(path);

        return this;
    }
    config(path) {
        this._config.load(path);

        return this;
    }
    scenario(scenarioInputOptions) {
        let scenarioOptions = {
            dependsOn: [],
            worker: 'Console',
            config: this._config.data,
            debug: this._debugging,
            globals: this._globalsFile,
        };

        this._queue.add(
            this._utils.deepExtend(
                scenarioOptions,
                _.pick(
                    scenarioInputOptions,
                    [
                        'name',
                        'entrypoint',
                        'dependsOn',
                        'notes',
                        'worker'
                    ]
                )
            )
        );

        return this;
    }
    pour(donePouring, pourProgressUpdate) {
        if (typeof donePouring != 'function') {
            donePouring = (error, results) => {};
            this._debugging = true;
        }
        if (typeof pourProgressUpdate != 'function') {
            pourProgressUpdate = (error, progress) => {};
        }

        this._queue.initialise(function (error, prorgess) {
            pourProgressUpdate(error, prorgess);
        });
        if (this._debugging) {
            this._log.log(`\b${chalk.magenta('[runs]')} ${JSON.stringify(this._queue._runOrder)}`);
            this._log.log(`\b${chalk.magenta('[config]')} ${JSON.stringify(this._config.data)}`);
        }
        this._queue.run((error, results) => {
            if (error) {
                this._log.log(`Tests finished with error: ${error} and results: ${JSON.stringify(results)}`);
            }
            if (this._debugging) {
                this._log.results(JSON.stringify(results));
            }
            donePouring(error, results);
        });
    }
}

module.exports = Syrup;
<commit_msg>Write results to file if --output argument given<commit_after>'use strict';

const util = require('util');
const fs = require('fs');
const _ = require('lodash');
const chalk = require('chalk');
const yargs = require('yargs').argv;

const Config = require('./Libs/Config');
const Log = require('./Libs/Log');
const Queue = require('./Libs/Queue');
const Utils = require('./Libs/Utils');

const ScenarioInputInvalid = require('./Errors/ScenarioInputInvalid');

class Syrup {
    constructor() {
        this._config = new Config;
        this._debugging = false;
        this._globalsFile = false;
        this._logFile = false;
        this._log = new Log;
        this._queue = new Queue;
        this._utils = new Utils;

        if (yargs.debug) {
            this.enableDebug();
        }

        if (yargs.output) {
            this.enableLogToFile(yargs.output);
        }
    }
    enableDebug() {
        this._debugging = true;

        return this;
    }
    enableLogToFile(path) {
        this._logFile = require('path').resolve(path);

        return this;
    }
    registerGlobals(path) {
        this._globalsFile = require('path').resolve(path);

        return this;
    }
    config(path) {
        this._config.load(path);

        return this;
    }
    scenario(scenarioInputOptions) {
        let scenarioOptions = {
            dependsOn: [],
            worker: 'Console',
            config: this._config.data,
            debug: this._debugging,
            globals: this._globalsFile,
        };

        this._queue.add(
            this._utils.deepExtend(
                scenarioOptions,
                _.pick(
                    scenarioInputOptions,
                    [
                        'name',
                        'entrypoint',
                        'dependsOn',
                        'notes',
                        'worker'
                    ]
                )
            )
        );

        return this;
    }
    pour(donePouring, pourProgressUpdate) {
        if (typeof donePouring != 'function') {
            donePouring = (error, results) => {};
            this._debugging = true;
        }
        if (typeof pourProgressUpdate != 'function') {
            pourProgressUpdate = (error, progress) => {};
        }

        this._queue.initialise(function (error, prorgess) {
            pourProgressUpdate(error, prorgess);
        });
        if (this._debugging) {
            this._log.log(`\b${chalk.magenta('[runs]')} ${JSON.stringify(this._queue._runOrder)}`);
            this._log.log(`\b${chalk.magenta('[config]')} ${JSON.stringify(this._config.data)}`);
        }
        this._queue.run((error, results) => {
            if (error) {
                this._log.log(`Tests finished with error: ${error} and results: ${JSON.stringify(results)}`);
            }
            if (this._debugging) {
                this._log.results(JSON.stringify(results));
            }
            if (this._logFile) {
                fs.writeFileSync(this._logFile, JSON.stringify(results), 'utf8');
            }
            donePouring(error, results);
        });
    }
}

module.exports = Syrup;
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import shallowequal from 'shallowequal';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import { Provider, create } from 'mini-store';
import merge from 'lodash/merge';
import classes from 'component-classes';
import { polyfill } from 'react-lifecycles-compat';
import { debounce, warningOnce } from './utils';
import ColumnManager from './ColumnManager';
import HeadTable from './HeadTable';
import BodyTable from './BodyTable';
import ExpandableTable from './ExpandableTable';

class Table extends React.Component {
  static propTypes = {
    data: PropTypes.array,
    useFixedHeader: PropTypes.bool,
    columns: PropTypes.array,
    prefixCls: PropTypes.string,
    bodyStyle: PropTypes.object,
    style: PropTypes.object,
    rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
    rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
    onRow: PropTypes.func,
    onHeaderRow: PropTypes.func,
    onRowClick: PropTypes.func,
    onRowDoubleClick: PropTypes.func,
    onRowContextMenu: PropTypes.func,
    onRowMouseEnter: PropTypes.func,
    onRowMouseLeave: PropTypes.func,
    showHeader: PropTypes.bool,
    title: PropTypes.func,
    id: PropTypes.string,
    footer: PropTypes.func,
    emptyText: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
    scroll: PropTypes.object,
    rowRef: PropTypes.func,
    getBodyWrapper: PropTypes.func,
    children: PropTypes.node,
    components: PropTypes.shape({
      table: PropTypes.any,
      header: PropTypes.shape({
        wrapper: PropTypes.any,
        row: PropTypes.any,
        cell: PropTypes.any,
      }),
      body: PropTypes.shape({
        wrapper: PropTypes.any,
        row: PropTypes.any,
        cell: PropTypes.any,
      }),
    }),
    ...ExpandableTable.PropTypes,
  };

  static childContextTypes = {
    table: PropTypes.any,
    components: PropTypes.any,
  };

  static defaultProps = {
    data: [],
    useFixedHeader: false,
    rowKey: 'key',
    rowClassName: () => '',
    onRow() {},
    onHeaderRow() {},
    prefixCls: 'rc-table',
    bodyStyle: {},
    style: {},
    showHeader: true,
    scroll: {},
    rowRef: () => null,
    emptyText: () => 'No Data',
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.columns && nextProps.columns !== prevState.columns) {
      return {
        columns: nextProps.columns,
        children: null,
      };
    } else if (nextProps.children !== prevState.children) {
      return {
        columns: null,
        children: nextProps.children,
      };
    }
    return null;
  }

  constructor(props) {
    super(props);

    [
      'onRowClick',
      'onRowDoubleClick',
      'onRowContextMenu',
      'onRowMouseEnter',
      'onRowMouseLeave',
    ].forEach(name => {
      warningOnce(props[name] === undefined, `${name} is deprecated, please use onRow instead.`);
    });

    warningOnce(
      props.getBodyWrapper === undefined,
      'getBodyWrapper is deprecated, please use custom components instead.',
    );

    this.columnManager = new ColumnManager(props.columns, props.children);

    this.store = create({
      currentHoverKey: null,
      fixedColumnsHeadRowsHeight: [],
      fixedColumnsBodyRowsHeight: [],
    });

    this.setScrollPosition('left');

    this.debouncedWindowResize = debounce(this.handleWindowResize, 150);
  }

  state = {};

  getChildContext() {
    return {
      table: {
        props: this.props,
        columnManager: this.columnManager,
        saveRef: this.saveRef,
        components: merge(
          {
            table: 'table',
            header: {
              wrapper: 'thead',
              row: 'tr',
              cell: 'th',
            },
            body: {
              wrapper: 'tbody',
              row: 'tr',
              cell: 'td',
            },
          },
          this.props.components,
        ),
      },
    };
  }

  componentDidMount() {
    if (this.columnManager.isAnyColumnsFixed()) {
      this.handleWindowResize();
      this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize);
    }
  }

  componentDidUpdate(prevProps) {
    if (this.columnManager.isAnyColumnsFixed()) {
      this.handleWindowResize();
      if (!this.resizeEvent) {
        this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize);
      }
    }
    // when table changes to empty, reset scrollLeft
    if (prevProps.data.length > 0 && this.props.data.length === 0 && this.hasScrollX()) {
      this.resetScrollX();
    }
  }

  componentWillUnmount() {
    if (this.resizeEvent) {
      this.resizeEvent.remove();
    }
    if (this.debouncedWindowResize) {
      this.debouncedWindowResize.cancel();
    }
  }

  getRowKey = (record, index) => {
    const rowKey = this.props.rowKey;
    const key = typeof rowKey === 'function' ? rowKey(record, index) : record[rowKey];
    warningOnce(
      key !== undefined,
      'Each record in table should have a unique `key` prop,' +
        'or set `rowKey` to an unique primary key.',
    );
    return key === undefined ? index : key;
  };

  setScrollPosition(position) {
    this.scrollPosition = position;
    if (this.tableNode) {
      const { prefixCls } = this.props;
      if (position === 'both') {
        classes(this.tableNode)
          .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
          .add(`${prefixCls}-scroll-position-left`)
          .add(`${prefixCls}-scroll-position-right`);
      } else {
        classes(this.tableNode)
          .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
          .add(`${prefixCls}-scroll-position-${position}`);
      }
    }
  }

  setScrollPositionClassName() {
    const node = this.bodyTable;
    const scrollToLeft = node.scrollLeft === 0;
    const scrollToRight =
      node.scrollLeft + 1 >=
      node.children[0].getBoundingClientRect().width - node.getBoundingClientRect().width;
    if (scrollToLeft && scrollToRight) {
      this.setScrollPosition('both');
    } else if (scrollToLeft) {
      this.setScrollPosition('left');
    } else if (scrollToRight) {
      this.setScrollPosition('right');
    } else if (this.scrollPosition !== 'middle') {
      this.setScrollPosition('middle');
    }
  }

  handleWindowResize = () => {
    this.syncFixedTableRowHeight();
    this.setScrollPositionClassName();
  };

  syncFixedTableRowHeight = () => {
    const tableRect = this.tableNode.getBoundingClientRect();
    // If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight.
    // see: https://github.com/ant-design/ant-design/issues/4836
    if (tableRect.height !== undefined && tableRect.height <= 0) {
      return;
    }
    const { prefixCls } = this.props;
    const headRows = this.headTable
      ? this.headTable.querySelectorAll('thead')
      : this.bodyTable.querySelectorAll('thead');
    const bodyRows = this.bodyTable.querySelectorAll(`.${prefixCls}-row`) || [];
    const fixedColumnsHeadRowsHeight = [].map.call(
      headRows,
      row => row.getBoundingClientRect().height || 'auto',
    );
    const fixedColumnsBodyRowsHeight = [].map.call(
      bodyRows,
      row => row.getBoundingClientRect().height || 'auto',
    );
    const state = this.store.getState();
    if (
      shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
      shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)
    ) {
      return;
    }

    this.store.setState({
      fixedColumnsHeadRowsHeight,
      fixedColumnsBodyRowsHeight,
    });
  };

  resetScrollX() {
    if (this.headTable) {
      this.headTable.scrollLeft = 0;
    }
    if (this.bodyTable) {
      this.bodyTable.scrollLeft = 0;
    }
  }

  hasScrollX() {
    const { scroll = {} } = this.props;
    return 'x' in scroll;
  }

  handleBodyScrollLeft = e => {
    // Fix https://github.com/ant-design/ant-design/issues/7635
    if (e.currentTarget !== e.target) {
      return;
    }
    const target = e.target;
    const { scroll = {} } = this.props;
    const { headTable, bodyTable } = this;
    if (target.scrollLeft !== this.lastScrollLeft && scroll.x) {
      if (target === bodyTable && headTable) {
        headTable.scrollLeft = target.scrollLeft;
      } else if (target === headTable && bodyTable) {
        bodyTable.scrollLeft = target.scrollLeft;
      }
      this.setScrollPositionClassName();
    }
    // Remember last scrollLeft for scroll direction detecting.
    this.lastScrollLeft = target.scrollLeft;
  };

  handleBodyScrollTop = e => {
    const target = e.target;
    // Fix https://github.com/ant-design/ant-design/issues/9033
    if (e.currentTarget !== target) {
      return;
    }
    const { scroll = {} } = this.props;
    const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
    if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) {
      const scrollTop = target.scrollTop;
      if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) {
        fixedColumnsBodyLeft.scrollTop = scrollTop;
      }
      if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) {
        fixedColumnsBodyRight.scrollTop = scrollTop;
      }
      if (bodyTable && target !== bodyTable) {
        bodyTable.scrollTop = scrollTop;
      }
    }
    // Remember last scrollTop for scroll direction detecting.
    this.lastScrollTop = target.scrollTop;
  };

  handleBodyScroll = e => {
    this.handleBodyScrollLeft(e);
    this.handleBodyScrollTop(e);
  };

  handleWheel = event => {
    const { scroll = {} } = this.props;
    if (navigator.userAgent.match(/Trident\/7\./) && scroll.y) {
      event.preventDefault();
      const wd = event.deltaY;
      const target = event.target;
      const { bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
      let scrollTop = 0;

      if (this.lastScrollTop) {
        scrollTop = this.lastScrollTop + wd;
      } else {
        scrollTop = wd;
      }

      if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) {
        fixedColumnsBodyLeft.scrollTop = scrollTop;
      }
      if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) {
        fixedColumnsBodyRight.scrollTop = scrollTop;
      }
      if (bodyTable && target !== bodyTable) {
        bodyTable.scrollTop = scrollTop;
      }
    }
  };

  saveRef = name => node => {
    this[name] = node;
  };

  renderMainTable() {
    const { scroll, prefixCls } = this.props;
    const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed();
    const scrollable = isAnyColumnsFixed || scroll.x || scroll.y;

    const table = [
      this.renderTable({
        columns: this.columnManager.groupedColumns(),
        isAnyColumnsFixed,
      }),
      this.renderEmptyText(),
      this.renderFooter(),
    ];

    return scrollable ? <div className={`${prefixCls}-scroll`}>{table}
 : table;
  }

  renderLeftFixedTable() {
    const { prefixCls } = this.props;

    return (
      <div className={`${prefixCls}-fixed-left`}>
        {this.renderTable({
          columns: this.columnManager.leftColumns(),
          fixed: 'left',
        })}

    );
  }

  renderRightFixedTable() {
    const { prefixCls } = this.props;

    return (
      <div className={`${prefixCls}-fixed-right`}>
        {this.renderTable({
          columns: this.columnManager.rightColumns(),
          fixed: 'right',
        })}

    );
  }

  renderTable(options) {
    const { columns, fixed, isAnyColumnsFixed } = options;
    const { prefixCls, scroll = {} } = this.props;
    const tableClassName = scroll.x || fixed ? `${prefixCls}-fixed` : '';

    const headTable = (
      <HeadTable
        key="head"
        columns={columns}
        fixed={fixed}
        tableClassName={tableClassName}
        handleBodyScrollLeft={this.handleBodyScrollLeft}
        expander={this.expander}
      />
    );

    const bodyTable = (
      <BodyTable
        key="body"
        columns={columns}
        fixed={fixed}
        tableClassName={tableClassName}
        getRowKey={this.getRowKey}
        handleWheel={this.handleWheel}
        handleBodyScroll={this.handleBodyScroll}
        expander={this.expander}
        isAnyColumnsFixed={isAnyColumnsFixed}
      />
    );

    return [headTable, bodyTable];
  }

  renderTitle() {
    const { title, prefixCls } = this.props;
    return title ? (
      <div className={`${prefixCls}-title`} key="title">
        {title(this.props.data)}

    ) : null;
  }

  renderFooter() {
    const { footer, prefixCls } = this.props;
    return footer ? (
      <div className={`${prefixCls}-footer`} key="footer">
        {footer(this.props.data)}

    ) : null;
  }

  renderEmptyText() {
    const { emptyText, prefixCls, data } = this.props;
    if (data.length) {
      return null;
    }
    const emptyClassName = `${prefixCls}-placeholder`;
    return (

        {typeof emptyText === 'function' ? emptyText() : emptyText}

    );
  }

  render() {
    const props = this.props;
    const prefixCls = props.prefixCls;

    if (this.state.columns) {
      this.columnManager.reset(props.columns);
    } else if (this.state.children) {
      this.columnManager.reset(null, props.children);
    }

    let className = props.prefixCls;
    if (props.className) {
      className += ` ${props.className}`;
    }
    if (props.useFixedHeader || (props.scroll && props.scroll.y)) {
      className += ` ${prefixCls}-fixed-header`;
    }
    if (this.scrollPosition === 'both') {
      className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right`;
    } else {
      className += ` ${prefixCls}-scroll-position-${this.scrollPosition}`;
    }
    const hasLeftFixed = this.columnManager.isAnyColumnsLeftFixed();
    const hasRightFixed = this.columnManager.isAnyColumnsRightFixed();

    return (
      <Provider store={this.store}>
        <ExpandableTable {...props} columnManager={this.columnManager} getRowKey={this.getRowKey}>
          {expander => {
            this.expander = expander;
            return (

                {this.renderTitle()}
                <div className={`${prefixCls}-content`}>
                  {this.renderMainTable()}
                  {hasLeftFixed && this.renderLeftFixedTable()}
                  {hasRightFixed && this.renderRightFixedTable()}

            );
          }}
        </ExpandableTable>
      </Provider>
    );
  }
}

polyfill(Table);

export default Table;
<commit_msg>Lint fix<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import shallowequal from 'shallowequal';
import addEventListener from 'rc-util/lib/Dom/addEventListener';
import { Provider, create } from 'mini-store';
import merge from 'lodash/merge';
import classes from 'component-classes';
import { polyfill } from 'react-lifecycles-compat';
import { debounce, warningOnce } from './utils';
import ColumnManager from './ColumnManager';
import HeadTable from './HeadTable';
import BodyTable from './BodyTable';
import ExpandableTable from './ExpandableTable';

class Table extends React.Component {
  static propTypes = {
    data: PropTypes.array,
    useFixedHeader: PropTypes.bool,
    columns: PropTypes.array,
    prefixCls: PropTypes.string,
    bodyStyle: PropTypes.object,
    style: PropTypes.object,
    rowKey: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
    rowClassName: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
    onRow: PropTypes.func,
    onHeaderRow: PropTypes.func,
    onRowClick: PropTypes.func,
    onRowDoubleClick: PropTypes.func,
    onRowContextMenu: PropTypes.func,
    onRowMouseEnter: PropTypes.func,
    onRowMouseLeave: PropTypes.func,
    showHeader: PropTypes.bool,
    title: PropTypes.func,
    id: PropTypes.string,
    footer: PropTypes.func,
    emptyText: PropTypes.oneOfType([PropTypes.node, PropTypes.func]),
    scroll: PropTypes.object,
    rowRef: PropTypes.func,
    getBodyWrapper: PropTypes.func,
    children: PropTypes.node,
    components: PropTypes.shape({
      table: PropTypes.any,
      header: PropTypes.shape({
        wrapper: PropTypes.any,
        row: PropTypes.any,
        cell: PropTypes.any,
      }),
      body: PropTypes.shape({
        wrapper: PropTypes.any,
        row: PropTypes.any,
        cell: PropTypes.any,
      }),
    }),
    ...ExpandableTable.PropTypes,
  };

  static childContextTypes = {
    table: PropTypes.any,
    components: PropTypes.any,
  };

  static defaultProps = {
    data: [],
    useFixedHeader: false,
    rowKey: 'key',
    rowClassName: () => '',
    onRow() {},
    onHeaderRow() {},
    prefixCls: 'rc-table',
    bodyStyle: {},
    style: {},
    showHeader: true,
    scroll: {},
    rowRef: () => null,
    emptyText: () => 'No Data',
  };

  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.columns && nextProps.columns !== prevState.columns) {
      return {
        columns: nextProps.columns,
        children: null,
      };
    } else if (nextProps.children !== prevState.children) {
      return {
        columns: null,
        children: nextProps.children,
      };
    }
    return null;
  }

  constructor(props) {
    super(props);

    [
      'onRowClick',
      'onRowDoubleClick',
      'onRowContextMenu',
      'onRowMouseEnter',
      'onRowMouseLeave',
    ].forEach(name => {
      warningOnce(props[name] === undefined, `${name} is deprecated, please use onRow instead.`);
    });

    warningOnce(
      props.getBodyWrapper === undefined,
      'getBodyWrapper is deprecated, please use custom components instead.',
    );

    this.columnManager = new ColumnManager(props.columns, props.children);

    this.store = create({
      currentHoverKey: null,
      fixedColumnsHeadRowsHeight: [],
      fixedColumnsBodyRowsHeight: [],
    });

    this.setScrollPosition('left');

    this.debouncedWindowResize = debounce(this.handleWindowResize, 150);
  }

  state = {};

  getChildContext() {
    return {
      table: {
        props: this.props,
        columnManager: this.columnManager,
        saveRef: this.saveRef,
        components: merge(
          {
            table: 'table',
            header: {
              wrapper: 'thead',
              row: 'tr',
              cell: 'th',
            },
            body: {
              wrapper: 'tbody',
              row: 'tr',
              cell: 'td',
            },
          },
          this.props.components,
        ),
      },
    };
  }

  componentDidMount() {
    if (this.columnManager.isAnyColumnsFixed()) {
      this.handleWindowResize();
      this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize);
    }
  }

  componentDidUpdate(prevProps) {
    if (this.columnManager.isAnyColumnsFixed()) {
      this.handleWindowResize();
      if (!this.resizeEvent) {
        this.resizeEvent = addEventListener(window, 'resize', this.debouncedWindowResize);
      }
    }
    // when table changes to empty, reset scrollLeft
    if (prevProps.data.length > 0 && this.props.data.length === 0 && this.hasScrollX()) {
      this.resetScrollX();
    }
  }

  componentWillUnmount() {
    if (this.resizeEvent) {
      this.resizeEvent.remove();
    }
    if (this.debouncedWindowResize) {
      this.debouncedWindowResize.cancel();
    }
  }

  getRowKey = (record, index) => {
    const rowKey = this.props.rowKey;
    const key = typeof rowKey === 'function' ? rowKey(record, index) : record[rowKey];
    warningOnce(
      key !== undefined,
      'Each record in table should have a unique `key` prop,' +
        'or set `rowKey` to an unique primary key.',
    );
    return key === undefined ? index : key;
  };

  setScrollPosition(position) {
    this.scrollPosition = position;
    if (this.tableNode) {
      const { prefixCls } = this.props;
      if (position === 'both') {
        classes(this.tableNode)
          .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
          .add(`${prefixCls}-scroll-position-left`)
          .add(`${prefixCls}-scroll-position-right`);
      } else {
        classes(this.tableNode)
          .remove(new RegExp(`^${prefixCls}-scroll-position-.+$`))
          .add(`${prefixCls}-scroll-position-${position}`);
      }
    }
  }

  setScrollPositionClassName() {
    const node = this.bodyTable;
    const scrollToLeft = node.scrollLeft === 0;
    const scrollToRight =
      node.scrollLeft + 1 >=
      node.children[0].getBoundingClientRect().width - node.getBoundingClientRect().width;
    if (scrollToLeft && scrollToRight) {
      this.setScrollPosition('both');
    } else if (scrollToLeft) {
      this.setScrollPosition('left');
    } else if (scrollToRight) {
      this.setScrollPosition('right');
    } else if (this.scrollPosition !== 'middle') {
      this.setScrollPosition('middle');
    }
  }

  handleWindowResize = () => {
    this.syncFixedTableRowHeight();
    this.setScrollPositionClassName();
  };

  syncFixedTableRowHeight = () => {
    const tableRect = this.tableNode.getBoundingClientRect();
    // If tableNode's height less than 0, suppose it is hidden and don't recalculate rowHeight.
    // see: https://github.com/ant-design/ant-design/issues/4836
    if (tableRect.height !== undefined && tableRect.height <= 0) {
      return;
    }
    const { prefixCls } = this.props;
    const headRows = this.headTable
      ? this.headTable.querySelectorAll('thead')
      : this.bodyTable.querySelectorAll('thead');
    const bodyRows = this.bodyTable.querySelectorAll(`.${prefixCls}-row`) || [];
    const fixedColumnsHeadRowsHeight = [].map.call(
      headRows,
      row => row.getBoundingClientRect().height || 'auto',
    );
    const fixedColumnsBodyRowsHeight = [].map.call(
      bodyRows,
      row => row.getBoundingClientRect().height || 'auto',
    );
    const state = this.store.getState();
    if (
      shallowequal(state.fixedColumnsHeadRowsHeight, fixedColumnsHeadRowsHeight) &&
      shallowequal(state.fixedColumnsBodyRowsHeight, fixedColumnsBodyRowsHeight)
    ) {
      return;
    }

    this.store.setState({
      fixedColumnsHeadRowsHeight,
      fixedColumnsBodyRowsHeight,
    });
  };

  resetScrollX() {
    if (this.headTable) {
      this.headTable.scrollLeft = 0;
    }
    if (this.bodyTable) {
      this.bodyTable.scrollLeft = 0;
    }
  }

  hasScrollX() {
    const { scroll = {} } = this.props;
    return 'x' in scroll;
  }

  handleBodyScrollLeft = e => {
    // Fix https://github.com/ant-design/ant-design/issues/7635
    if (e.currentTarget !== e.target) {
      return;
    }
    const target = e.target;
    const { scroll = {} } = this.props;
    const { headTable, bodyTable } = this;
    if (target.scrollLeft !== this.lastScrollLeft && scroll.x) {
      if (target === bodyTable && headTable) {
        headTable.scrollLeft = target.scrollLeft;
      } else if (target === headTable && bodyTable) {
        bodyTable.scrollLeft = target.scrollLeft;
      }
      this.setScrollPositionClassName();
    }
    // Remember last scrollLeft for scroll direction detecting.
    this.lastScrollLeft = target.scrollLeft;
  };

  handleBodyScrollTop = e => {
    const target = e.target;
    // Fix https://github.com/ant-design/ant-design/issues/9033
    if (e.currentTarget !== target) {
      return;
    }
    const { scroll = {} } = this.props;
    const { headTable, bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
    if (target.scrollTop !== this.lastScrollTop && scroll.y && target !== headTable) {
      const scrollTop = target.scrollTop;
      if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) {
        fixedColumnsBodyLeft.scrollTop = scrollTop;
      }
      if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) {
        fixedColumnsBodyRight.scrollTop = scrollTop;
      }
      if (bodyTable && target !== bodyTable) {
        bodyTable.scrollTop = scrollTop;
      }
    }
    // Remember last scrollTop for scroll direction detecting.
    this.lastScrollTop = target.scrollTop;
  };

  handleBodyScroll = e => {
    this.handleBodyScrollLeft(e);
    this.handleBodyScrollTop(e);
  };

  handleWheel = event => {
    const { scroll = {} } = this.props;
    if (window.navigator.userAgent.match(/Trident\/7\./) && scroll.y) {
      event.preventDefault();
      const wd = event.deltaY;
      const target = event.target;
      const { bodyTable, fixedColumnsBodyLeft, fixedColumnsBodyRight } = this;
      let scrollTop = 0;

      if (this.lastScrollTop) {
        scrollTop = this.lastScrollTop + wd;
      } else {
        scrollTop = wd;
      }

      if (fixedColumnsBodyLeft && target !== fixedColumnsBodyLeft) {
        fixedColumnsBodyLeft.scrollTop = scrollTop;
      }
      if (fixedColumnsBodyRight && target !== fixedColumnsBodyRight) {
        fixedColumnsBodyRight.scrollTop = scrollTop;
      }
      if (bodyTable && target !== bodyTable) {
        bodyTable.scrollTop = scrollTop;
      }
    }
  };

  saveRef = name => node => {
    this[name] = node;
  };

  renderMainTable() {
    const { scroll, prefixCls } = this.props;
    const isAnyColumnsFixed = this.columnManager.isAnyColumnsFixed();
    const scrollable = isAnyColumnsFixed || scroll.x || scroll.y;

    const table = [
      this.renderTable({
        columns: this.columnManager.groupedColumns(),
        isAnyColumnsFixed,
      }),
      this.renderEmptyText(),
      this.renderFooter(),
    ];

    return scrollable ? <div className={`${prefixCls}-scroll`}>{table}
 : table;
  }

  renderLeftFixedTable() {
    const { prefixCls } = this.props;

    return (
      <div className={`${prefixCls}-fixed-left`}>
        {this.renderTable({
          columns: this.columnManager.leftColumns(),
          fixed: 'left',
        })}

    );
  }

  renderRightFixedTable() {
    const { prefixCls } = this.props;

    return (
      <div className={`${prefixCls}-fixed-right`}>
        {this.renderTable({
          columns: this.columnManager.rightColumns(),
          fixed: 'right',
        })}

    );
  }

  renderTable(options) {
    const { columns, fixed, isAnyColumnsFixed } = options;
    const { prefixCls, scroll = {} } = this.props;
    const tableClassName = scroll.x || fixed ? `${prefixCls}-fixed` : '';

    const headTable = (
      <HeadTable
        key="head"
        columns={columns}
        fixed={fixed}
        tableClassName={tableClassName}
        handleBodyScrollLeft={this.handleBodyScrollLeft}
        expander={this.expander}
      />
    );

    const bodyTable = (
      <BodyTable
        key="body"
        columns={columns}
        fixed={fixed}
        tableClassName={tableClassName}
        getRowKey={this.getRowKey}
        handleWheel={this.handleWheel}
        handleBodyScroll={this.handleBodyScroll}
        expander={this.expander}
        isAnyColumnsFixed={isAnyColumnsFixed}
      />
    );

    return [headTable, bodyTable];
  }

  renderTitle() {
    const { title, prefixCls } = this.props;
    return title ? (
      <div className={`${prefixCls}-title`} key="title">
        {title(this.props.data)}

    ) : null;
  }

  renderFooter() {
    const { footer, prefixCls } = this.props;
    return footer ? (
      <div className={`${prefixCls}-footer`} key="footer">
        {footer(this.props.data)}

    ) : null;
  }

  renderEmptyText() {
    const { emptyText, prefixCls, data } = this.props;
    if (data.length) {
      return null;
    }
    const emptyClassName = `${prefixCls}-placeholder`;
    return (

        {typeof emptyText === 'function' ? emptyText() : emptyText}

    );
  }

  render() {
    const props = this.props;
    const prefixCls = props.prefixCls;

    if (this.state.columns) {
      this.columnManager.reset(props.columns);
    } else if (this.state.children) {
      this.columnManager.reset(null, props.children);
    }

    let className = props.prefixCls;
    if (props.className) {
      className += ` ${props.className}`;
    }
    if (props.useFixedHeader || (props.scroll && props.scroll.y)) {
      className += ` ${prefixCls}-fixed-header`;
    }
    if (this.scrollPosition === 'both') {
      className += ` ${prefixCls}-scroll-position-left ${prefixCls}-scroll-position-right`;
    } else {
      className += ` ${prefixCls}-scroll-position-${this.scrollPosition}`;
    }
    const hasLeftFixed = this.columnManager.isAnyColumnsLeftFixed();
    const hasRightFixed = this.columnManager.isAnyColumnsRightFixed();

    return (
      <Provider store={this.store}>
        <ExpandableTable {...props} columnManager={this.columnManager} getRowKey={this.getRowKey}>
          {expander => {
            this.expander = expander;
            return (

                {this.renderTitle()}
                <div className={`${prefixCls}-content`}>
                  {this.renderMainTable()}
                  {hasLeftFixed && this.renderLeftFixedTable()}
                  {hasRightFixed && this.renderRightFixedTable()}

            );
          }}
        </ExpandableTable>
      </Provider>
    );
  }
}

polyfill(Table);

export default Table;
<|endoftext|>


<commit_before>/**
 * Titon: The Mootools UI/Utility Framework
 *
 * @copyright	Copyright 2010+, Titon
 * @link		http://github.com/titon
 * @license		http://opensource.org/licenses/bsd-license.php (BSD License)
 */

/**
 * The base object for all Titon classes. Contains global functionality and configuration.
 *
 * @version	0.2.1 ALPHA
 */
var Titon = {

	/**
	 * Current version.
	 */
	version: '0.1.1 ALPHA',

	/**
	 * Options for all classes.
	 *
	 *	prefix 			- (string) String to prepend to all created element containers
	 *	activeClass		- (string) Class name to append to active elements
	 *	disabledClass	- (string) Class name to append to disabled elements
	 */
	options: {
		prefix: 'titon-',
		activeClass: 'active',
		disabledClass: 'disabled'
	},

	/**
	 * Localization messages.
	 */
	msg: {
		loading: 'Loading...'
	},

	/**
	 * Converts a value to a specific scalar type.
	 * The value is extracted via parseOptions().
	 *
	 * @param value
	 * @return mixed
	 */
	convertType: function(value) {
		if (value === 'true') {
			value = true;

		} else if (value === 'false') {
			value = false;

		} else if (value === 'null') {
			value = null;

		} else if (isNaN(value)) {
			value = String.from(value);

		} else {
			value = Number.from(value);
		}

		return value;
	},

	/**
	 * Merge custom options into the base. Clone the base as to not reference the original.
	 *
	 * @param {object} base
	 * @param {object} options
	 * @return {object}
	 */
	mergeOptions: function(base, options) {
		return Object.merge(Object.clone(base || {}), options || {});
	},

	/**
	 * Parse options out of the data-options attributes.
	 * Format: key1:value1;key2:value2
	 *
	 * @param {object} data
	 * @return {object}
	 */
	parseOptions: function(data) {
		var options = {};

		if (data) {
			data.split(';').each(function(item) {
				var pieces = item.split(':');

				if (pieces.length) {
					options[pieces[0]] = Titon.convertType(pieces[1]);
				}
			});
		}

		return options;
	},

	/**
	 * Apply custom options.
	 *
	 * @param {object} options
	 */
	setup: function(options) {
		Titon.options = Object.merge(Titon.options, options);
	}

};

/**
 * Prototype overrides.
 */
Element.implement({

	/**
	 * Returns an object representation of the data-options attribute located on the element.
	 *
	 * @param {string} scope
	 * @return {object}
	 */
	getOptions: function(scope) {
		return Titon.parseOptions(this.get('data-' + scope + '-options'));
	},

	/**
	 * Show an element using its default display type, or pass a forced type.
	 *
	 * @param {string} force
	 */
	show: function(force) {
		this.setStyle('display', force || '');
	},

	/**
	 * Hide an element.
	 */
	hide: function() {
		this.setStyle('display', 'none');
	},

	/**
	 * Fade in an element and set its display type.
	 *
	 * @param {int} duration
	 */
	fadeIn: function(duration) {
		duration = duration || 600;

		this.setStyles({
			display: '',
			opacity: 0
		}).set('tween', {
			duration: duration,
			link: 'cancel'
		}).fade('in');
	},

	/**
	 * Fade out an element and remove from DOM.
	 *
	 * @param {int} duration
	 */
	fadeOut: function(duration) {
		duration = duration || 600;

		this.set('tween', {
			duration: duration,
			link: 'cancel'
		}).fade('out').get('tween').chain(function() {
			this.element.dispose();
		});
	}

});<commit_msg>Added a String.remove() method Made all prototypes return their instance<commit_after>/**
 * Titon: The Mootools UI/Utility Framework
 *
 * @copyright	Copyright 2010+, Titon
 * @link		http://github.com/titon
 * @license		http://opensource.org/licenses/bsd-license.php (BSD License)
 */

/**
 * The base object for all Titon classes. Contains global functionality and configuration.
 *
 * @version	0.2.1 ALPHA
 */
var Titon = {

	/**
	 * Current version.
	 */
	version: '0.1.1 ALPHA',

	/**
	 * Options for all classes.
	 *
	 *	prefix 			- (string) String to prepend to all created element containers
	 *	activeClass		- (string) Class name to append to active elements
	 *	disabledClass	- (string) Class name to append to disabled elements
	 */
	options: {
		prefix: 'titon-',
		activeClass: 'active',
		disabledClass: 'disabled'
	},

	/**
	 * Localization messages.
	 */
	msg: {
		loading: 'Loading...'
	},

	/**
	 * Converts a value to a specific scalar type.
	 * The value is extracted via parseOptions().
	 *
	 * @param {string} value
	 * @return {boolean|string|number}
	 */
	convertType: function(value) {
		if (value === 'true') {
			value = true;

		} else if (value === 'false') {
			value = false;

		} else if (value === 'null') {
			value = null;

		} else if (isNaN(value)) {
			value = String.from(value);

		} else {
			value = Number.from(value);
		}

		return value;
	},

	/**
	 * Merge custom options into the base. Clone the base as to not reference the original.
	 *
	 * @param {object} base
	 * @param {object} options
	 * @return {object}
	 */
	mergeOptions: function(base, options) {
		return Object.merge(Object.clone(base || {}), options || {});
	},

	/**
	 * Parse options out of the data-options attributes.
	 * Format: key1:value1;key2:value2
	 *
	 * @param {object} data
	 * @return {object}
	 */
	parseOptions: function(data) {
		var options = {};

		if (data) {
			data.split(';').each(function(item) {
				var pieces = item.split(':');

				if (pieces.length) {
					options[pieces[0]] = Titon.convertType(pieces[1]);
				}
			});
		}

		return options;
	},

	/**
	 * Apply custom options.
	 *
	 * @param {object} options
	 */
	setup: function(options) {
		Titon.options = Object.merge(Titon.options, options);
	}

};

/**
 * Prototype overrides.
 */
Element.implement({

	/**
	 * Returns an object representation of the data-options attribute located on the element.
	 *
	 * @param {string} scope
	 * @return {object}
	 */
	getOptions: function(scope) {
		return Titon.parseOptions(this.get('data-' + scope + '-options'));
	},

	/**
	 * Show an element using its default display type, or pass a forced type.
	 *
	 * @param {string} force
	 * @return {Element}
	 */
	show: function(force) {
		return this.setStyle('display', force || '');
	},

	/**
	 * Hide an element.
	 * @return {Element}
	 */
	hide: function() {
		return this.setStyle('display', 'none');
	},

	/**
	 * Fade in an element and set its display type.
	 *
	 * @param {int} duration
	 * @return {Element}
	 */
	fadeIn: function(duration) {
		duration = duration || 600;

		return this.setStyles({
			display: '',
			opacity: 0
		}).set('tween', {
			duration: duration,
			link: 'cancel'
		}).fade('in');
	},

	/**
	 * Fade out an element and remove from DOM.
	 *
	 * @param {int} duration
	 * @return {Element}
	 */
	fadeOut: function(duration) {
		duration = duration || 600;

		return this.set('tween', {
			duration: duration,
			link: 'cancel'
		}).fade('out').get('tween').chain(function() {
			this.element.dispose();
		});
	}

});

String.implement({

	/**
	 * Remove specific characters from a string.
	 *
	 * @param {string|array} chars
	 * @return {String}
	 */
	remove: function(chars) {
		if (typeOf(chars) !== 'array') {
			chars = chars.toString().split('');
		}

		return this.replace(new RegExp('[' + chars.join('|') + ']+', 'ig'), '');
	}

});<|endoftext|>


<commit_before>import React, { Component } from "react";
import PropTypes from "prop-types";

import ProgressBar from "./ProgressBar";
import config from "./config";
import objectValues from "./util/objectValues";
import { falseOrElement, falseOrNumber } from "./util/propValidator";

class Toast extends Component {
  static propTypes = {
    closeButton: falseOrElement.isRequired,
    autoClose: falseOrNumber.isRequired,
    children: PropTypes.node.isRequired,
    closeToast: PropTypes.func.isRequired,
    position: PropTypes.oneOf(objectValues(config.POSITION)).isRequired,
    pauseOnHover: PropTypes.bool.isRequired,
    closeOnClick: PropTypes.bool.isRequired,
    transition: PropTypes.func.isRequired,
    in: PropTypes.bool,
    hideProgressBar: PropTypes.bool,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    type: PropTypes.oneOf(objectValues(config.TYPE)),
    className: PropTypes.string,
    bodyClassName: PropTypes.string,
    progressClassName: PropTypes.string
  };

  static defaultProps = {
    type: config.TYPE.DEFAULT,
    in: true,
    hideProgressBar: false,
    onOpen: null,
    onClose: null,
    className: "",
    bodyClassName: "",
    progressClassName: ""
  };

  constructor(props) {
    super(props);
    this.state = {
      isRunning: true
    };
  }

  componentDidMount() {
    this.props.onOpen !== null && this.props.onOpen(this.getChildrenProps());
  }

  componentWillUnmount() {
    this.props.onClose !== null && this.props.onClose(this.getChildrenProps());
  }

  getChildrenProps() {
    return this.props.children.props;
  }

  getToastProps() {
    const toastProps = {
      className: `toastify-content toastify-content--${this.props.type} ${this
        .props.className}`
    };

    if (this.props.autoClose !== false && this.props.pauseOnHover === true) {
      toastProps.onMouseEnter = this.pauseToast;
      toastProps.onMouseLeave = this.playToast;
    }

    this.props.closeOnClick && (toastProps.onClick = this.props.closeToast);

    return toastProps;
  }

  pauseToast = () => {
    this.setState({ isRunning: false });
  };

  playToast = () => {
    this.setState({ isRunning: true });
  };

  render() {
    const {
      closeButton,
      children,
      autoClose,
      type,
      hideProgressBar,
      closeToast,
      transition,
      position
    } = this.props;

    const Transition = transition;

    return (
      <Transition in={this.props.in} appear position={position}>

          <div className={`toastify__body ${this.props.bodyClassName}`}>
            {children}

          {closeButton !== false && closeButton}
          {autoClose !== false && (
            <ProgressBar
              delay={autoClose}
              isRunning={this.state.isRunning}
              closeToast={closeToast}
              hide={hideProgressBar}
              type={type}
              className={this.props.progressClassName}
            />
          )}

      </Transition>
    );
  }
}

export default Toast;
<commit_msg>Unnmount on transition exit<commit_after>import React, { Component } from "react";
import PropTypes from "prop-types";

import ProgressBar from "./ProgressBar";
import config from "./config";
import objectValues from "./util/objectValues";
import { falseOrElement, falseOrNumber } from "./util/propValidator";

class Toast extends Component {
  static propTypes = {
    closeButton: falseOrElement.isRequired,
    autoClose: falseOrNumber.isRequired,
    children: PropTypes.node.isRequired,
    closeToast: PropTypes.func.isRequired,
    position: PropTypes.oneOf(objectValues(config.POSITION)).isRequired,
    pauseOnHover: PropTypes.bool.isRequired,
    closeOnClick: PropTypes.bool.isRequired,
    transition: PropTypes.func.isRequired,
    in: PropTypes.bool,
    hideProgressBar: PropTypes.bool,
    onOpen: PropTypes.func,
    onClose: PropTypes.func,
    type: PropTypes.oneOf(objectValues(config.TYPE)),
    className: PropTypes.string,
    bodyClassName: PropTypes.string,
    progressClassName: PropTypes.string
  };

  static defaultProps = {
    type: config.TYPE.DEFAULT,
    in: true,
    hideProgressBar: false,
    onOpen: null,
    onClose: null,
    className: "",
    bodyClassName: "",
    progressClassName: ""
  };

  constructor(props) {
    super(props);
    this.state = {
      isRunning: true
    };
  }

  componentDidMount() {
    this.props.onOpen !== null && this.props.onOpen(this.getChildrenProps());
  }

  componentWillUnmount() {
    this.props.onClose !== null && this.props.onClose(this.getChildrenProps());
  }

  getChildrenProps() {
    return this.props.children.props;
  }

  getToastProps() {
    const toastProps = {
      className: `toastify-content toastify-content--${this.props.type} ${this
        .props.className}`
    };

    if (this.props.autoClose !== false && this.props.pauseOnHover === true) {
      toastProps.onMouseEnter = this.pauseToast;
      toastProps.onMouseLeave = this.playToast;
    }

    this.props.closeOnClick && (toastProps.onClick = this.props.closeToast);

    return toastProps;
  }

  pauseToast = () => {
    this.setState({ isRunning: false });
  };

  playToast = () => {
    this.setState({ isRunning: true });
  };

  render() {
    const {
      closeButton,
      children,
      autoClose,
      type,
      hideProgressBar,
      closeToast,
      transition,
      position
    } = this.props;

    const Transition = transition;

    return (
      <Transition in={this.props.in} appear unmountOnExit position={position}>

          <div className={`toastify__body ${this.props.bodyClassName}`}>
            {children}

          {closeButton !== false && closeButton}
          {autoClose !== false && (
            <ProgressBar
              delay={autoClose}
              isRunning={this.state.isRunning}
              closeToast={closeToast}
              hide={hideProgressBar}
              type={type}
              className={this.props.progressClassName}
            />
          )}

      </Transition>
    );
  }
}

export default Toast;
<|endoftext|>


<commit_before>'use strict';

import _assign from 'lodash/object/assign';
import _forOwn from 'lodash/object/forOwn';

import uuid from 'uuid';
import h from 'virtual-dom/h';

import {secondsToPixels} from './utils/conversions';
import extractPeaks from './utils/peaks';
import stateClasses from './track/states';
import CanvasHook from './render/CanvasHook';

const FADEIN = "FadeIn";
const FADEOUT = "FadeOut";

const MAX_CANVAS_WIDTH = 1000;

export default class {

    constructor(name="Untitled") {

        this.name = name;
        this.gain = 1;
        this.fades = {};
        this.peakData = {
            type: "WebAudio",
            mono: true
        };
    }

    setEventEmitter(ee) {
        this.ee = ee;
    }

    setName(name) {
        this.name = name;
    }

    setCues(cueIn, cueOut) {
        if (cueOut < cueIn) {
            throw new Error("cue out cannot be less than cue in");
        }

        this.cueIn = cueIn;
        this.cueOut = cueOut;
        this.duration = this.cueOut - this.cueIn;
    }

    setStartTime(start) {
        if (this.duration === undefined) {
            throw new Error("Must set track cues before start time.");
        }

        this.startTime = start;
        this.endTime = start + this.duration;
    }

    setPlayout(playout) {
        this.playout = playout;
    }

    setEnabledStates(enabledStates={}) {
        let defaultStatesEnabled = {
            'cursor': true,
            'fadein': true,
            'fadeout': true,
            'select': true,
            'shift': true
        };

        this.enabledStates = _assign(defaultStatesEnabled, enabledStates);
    }

    setFadeIn(options={}) {
        let d = {
            "shape": "logarithmic",
            "start": 0,
            "end": this.duration
        };

        if (this.fadeIn) {
            this.removeFade(this.fadeIn);
            this.fadeIn = undefined;
        }

        let fade = _assign(d, options);
        this.fadeIn = this.saveFade("FadeIn", fade.shape, fade.start, fade.end);
    }

    setFadeOut(options={}) {
        let d = {
            "shape": "logarithmic",
            "start": 0,
            "end": this.duration
        };

        if (this.fadeOut) {
            this.removeFade(this.fadeOut);
            this.fadeOut = undefined;
        }

        let fade = _assign(d, options);
        this.fadeOut = this.saveFade("FadeOut", fade.shape, fade.start, fade.end);
    }

    saveFade(type, shape, start, end) {
        let id = uuid.v4();

        this.fades[id] = {
            type: type,
            shape: shape,
            start: start,
            end: end
        };

        return id;
    }

    removeFade(id) {
        delete this.fades[id];
    }

    setBuffer(buffer) {
        this.buffer = buffer;
    }

    setPeakData(data) {
        this.peakData = data;
    }

    calculatePeaks(samplesPerPixel) {
        this.peaks = extractPeaks(this.buffer, samplesPerPixel, this.peakData.mono);
    }

    setState(state) {
        this.state = state;
    }

    getStartTime() {
        return this.startTime;
    }

    getEndTime() {
        return this.endTime;
    }

    getDuration() {
        return this.duration;
    }

    isPlaying() {
        return this.playout.isPlaying();
    }

    setGainLevel(level) {
        this.gain = level;
        this.playout.setGainLevel(level);
    }

    setMasterGainLevel(level) {
        this.playout.setMasterGainLevel(level);
    }

    /*
        startTime, endTime in seconds (float).
        segment is for a highlighted section in the UI.

        returns a Promise that will resolve when the AudioBufferSource
        is either stopped or plays out naturally.
    */
    schedulePlay(now, startTime, endTime, options) {
        var start,
            duration,
            relPos,
            when = now,
            segment = (endTime) ? (endTime - startTime) : undefined,
            sourcePromise;

        //1) track has no content to play.
        //2) track does not play in this selection.
        if ((this.endTime <= startTime) || (segment && (startTime + segment) < this.startTime)) {
            //return a resolved promise since this track is technically "stopped".
            return Promise.resolve();
        }

        //track should have something to play if it gets here.

        //the track starts in the future or on the cursor position
        if (this.startTime >= startTime) {
            start = 0;
            when = when + this.startTime - startTime; //schedule additional delay for this audio node.

            if (endTime) {
                segment = segment - (this.startTime - startTime);
                duration = Math.min(segment, this.duration);
            }
            else {
                duration = this.duration;
            }
        }
        else {
            start = startTime - this.startTime;

            if (endTime) {
                duration = Math.min(segment, this.duration - start);
            }
            else {
                duration = this.duration - start;
            }
        }

        start = start + this.cueIn;
        relPos = startTime - this.startTime;

        sourcePromise = this.playout.setUpSource();

        //param relPos: cursor position in seconds relative to this track.
        //can be negative if the cursor is placed before the start of this track etc.
        _forOwn(this.fades, (fade) => {
            let startTime;
            let duration;

            //only apply fade if it's ahead of the cursor.
            if (relPos < fade.end) {
                if (relPos <= fade.start) {
                    startTime = now + (fade.start - relPos);
                    duration = fade.end - fade.start;
                }
                else if (relPos > fade.start && relPos < fade.end) {
                    startTime = now - (relPos - fade.start);
                    duration = fade.end - fade.start;
                }

                switch (fade.type) {
                    case FADEIN:
                        this.playout.applyFadeIn(startTime, duration, fade.shape);
                        break;
                    case FADEOUT:
                        this.playout.applyFadeOut(startTime, duration, fade.shape);
                        break;
                    default:
                        throw new Error("Invalid fade type saved on track.");
                }
            }
        });

        this.playout.setGainLevel(this.gain);
        this.playout.setMasterGainLevel(options.masterGain);
        this.playout.play(when, start, duration);

        return sourcePromise;
    }

    scheduleStop(when=0) {
        this.playout.stop(when);
    }

    renderTimeSelection(data) {
        let startX = secondsToPixels(data.timeSelection.start, data.resolution, data.sampleRate);
        let endX = secondsToPixels(data.timeSelection.end, data.resolution, data.sampleRate);
        let width = endX - startX + 1;
        let className = (width > 1) ? "segment" : "point";

        return h(`div.selection.${className}`, {
            attributes: {
                "style": `position: absolute; width: ${width}px; bottom: 0; top: 0; left: ${startX}px; z-index: 999;`
            }
        });
    }

    renderOverlay(data) {
        let channelPixels = secondsToPixels(data.playlistLength, data.resolution, data.sampleRate);

        let config = {
            attributes: {
                "style": `position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: ${channelPixels}px; z-index: 9;`
            }
        };

        let stateClass = "";

        if (this.enabledStates[this.state]) {
            let state = new stateClasses[this.state](this, data.resolution, data.sampleRate);
            let stateEvents = state.getEvents();

            Object.keys(stateEvents).map((event) => {
                config[`ev-${event}`] = stateEvents[event].bind(state);
            });

            stateClass = state.getClasses();
        }
        //use this overlay for track event cursor position calculations.
        return h(`div.playlist-overlay${stateClass}`, config);
    }

    renderControls(data) {
        let muteClass = data.muted ? ".active" : "";
        let soloClass = data.soloed ? ".active" : "";

        return h("div.controls", {
            attributes: {
                "style": `height: ${data.height}px; width: ${data.controls.width}px; position: absolute; left: 0; z-index: 10;`
            }},
            [
                h("header", [ this.name ]),
                h("div.btn-group", [
                    h(`span.btn.btn-default.btn-xs.btn-mute${muteClass}`, {"ev-click": () => {
                        this.ee.emit("mute", this);
                    }}, [ "Mute" ]),
                    h(`span.btn.btn-default.btn-xs.btn-solo${soloClass}`, {"ev-click": () => {
                        this.ee.emit("solo", this);
                    }}, [ "Solo" ])
                ]),
                h("label", [
                    h("input.volume-slider", {
                        attributes: {
                            "type": "range",
                            "min": 0,
                            "max": 100,
                            "value": 100
                        },
                        "ev-input": (e) => {
                            this.ee.emit("volumechange", e.target.value, this);
                        }
                    })
                ])
            ]
        );
    }

    render(data) {
        let width = secondsToPixels(this.duration, data.resolution, data.sampleRate);
        let playbackX = secondsToPixels(data.playbackSeconds, data.resolution, data.sampleRate);
        let startX = secondsToPixels(this.startTime, data.resolution, data.sampleRate);
        let endX = secondsToPixels(this.endTime, data.resolution, data.sampleRate);
        let progressWidth = 0;

        if (playbackX > 0 && playbackX > startX) {
            if (playbackX < endX) {
                progressWidth = playbackX - startX;
            }
            else {
                progressWidth = width;
            }
        }

        let waveformChildren = [
            h("div.cursor", {attributes: {
                "style": `position: absolute; width: 1px; margin: 0; padding: 0; top: 0; left: ${playbackX}px; bottom: 0; z-index: 5;`
            }})
        ];

        let channels = Object.keys(this.peaks.data).map((channelNum) => {

            let channelChildren = [
                h("div.channel-progress", {attributes: {
                    "style": `position: absolute; width: ${progressWidth}px; height: ${data.height}px; z-index: 2;`
                }})
            ];
            let offset = 0;
            let totalWidth = width;
            let peaks = this.peaks.data[channelNum];

            while (totalWidth > 0) {
                let currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH);

                channelChildren.push(h("canvas", {
                    attributes: {
                        "width": currentWidth,
                        "height": data.height,
                        "data-offset": offset,
                        "style": "float: left; position: relative; margin: 0; padding: 0; z-index: 3;"
                    },
                    "hook": new CanvasHook(peaks, offset, data.colors.waveOutlineColor)
                }));

                totalWidth -= currentWidth;
                offset += MAX_CANVAS_WIDTH;
            }

            return h(`div.channel.channel-${channelNum}`, {attributes: {
                    "style": `height: ${data.height}px; width: ${width}px; top: 0; left: ${startX}px; position: absolute; margin: 0; padding: 0; z-index: 1;`
                }},
                channelChildren
            );
        });

        let audibleClass = data.masterGain ? "" : ".silent";

        waveformChildren.push(channels);
        waveformChildren.push(this.renderOverlay(data));

        //draw cursor selection on active track.
        if (data.isActive === true) {
            waveformChildren.push(this.renderTimeSelection(data));
        }

        let waveform = h("div.waveform", {
            attributes: {
                "style": `height: ${data.height}px; position: relative;`
            }},
            waveformChildren
        );

        let channelChildren = [];
        let channelMargin = 0;

        if (data.controls.show) {
            channelChildren.push(this.renderControls(data));
            channelMargin = data.controls.width;
        }

        channelChildren.push(waveform);

        return h(`div.channel-wrapper${audibleClass}`, {
            attributes: {
                "style": `margin-left: ${channelMargin}px; height: ${data.height}px;`
            }},
            channelChildren
        );
    }
}<commit_msg>Improve z-index of selection.<commit_after>'use strict';

import _assign from 'lodash/object/assign';
import _forOwn from 'lodash/object/forOwn';

import uuid from 'uuid';
import h from 'virtual-dom/h';

import {secondsToPixels} from './utils/conversions';
import extractPeaks from './utils/peaks';
import stateClasses from './track/states';
import CanvasHook from './render/CanvasHook';

const FADEIN = "FadeIn";
const FADEOUT = "FadeOut";

const MAX_CANVAS_WIDTH = 1000;

export default class {

    constructor(name="Untitled") {

        this.name = name;
        this.gain = 1;
        this.fades = {};
        this.peakData = {
            type: "WebAudio",
            mono: true
        };
    }

    setEventEmitter(ee) {
        this.ee = ee;
    }

    setName(name) {
        this.name = name;
    }

    setCues(cueIn, cueOut) {
        if (cueOut < cueIn) {
            throw new Error("cue out cannot be less than cue in");
        }

        this.cueIn = cueIn;
        this.cueOut = cueOut;
        this.duration = this.cueOut - this.cueIn;
    }

    setStartTime(start) {
        if (this.duration === undefined) {
            throw new Error("Must set track cues before start time.");
        }

        this.startTime = start;
        this.endTime = start + this.duration;
    }

    setPlayout(playout) {
        this.playout = playout;
    }

    setEnabledStates(enabledStates={}) {
        let defaultStatesEnabled = {
            'cursor': true,
            'fadein': true,
            'fadeout': true,
            'select': true,
            'shift': true
        };

        this.enabledStates = _assign(defaultStatesEnabled, enabledStates);
    }

    setFadeIn(options={}) {
        let d = {
            "shape": "logarithmic",
            "start": 0,
            "end": this.duration
        };

        if (this.fadeIn) {
            this.removeFade(this.fadeIn);
            this.fadeIn = undefined;
        }

        let fade = _assign(d, options);
        this.fadeIn = this.saveFade("FadeIn", fade.shape, fade.start, fade.end);
    }

    setFadeOut(options={}) {
        let d = {
            "shape": "logarithmic",
            "start": 0,
            "end": this.duration
        };

        if (this.fadeOut) {
            this.removeFade(this.fadeOut);
            this.fadeOut = undefined;
        }

        let fade = _assign(d, options);
        this.fadeOut = this.saveFade("FadeOut", fade.shape, fade.start, fade.end);
    }

    saveFade(type, shape, start, end) {
        let id = uuid.v4();

        this.fades[id] = {
            type: type,
            shape: shape,
            start: start,
            end: end
        };

        return id;
    }

    removeFade(id) {
        delete this.fades[id];
    }

    setBuffer(buffer) {
        this.buffer = buffer;
    }

    setPeakData(data) {
        this.peakData = data;
    }

    calculatePeaks(samplesPerPixel) {
        this.peaks = extractPeaks(this.buffer, samplesPerPixel, this.peakData.mono);
    }

    setState(state) {
        this.state = state;
    }

    getStartTime() {
        return this.startTime;
    }

    getEndTime() {
        return this.endTime;
    }

    getDuration() {
        return this.duration;
    }

    isPlaying() {
        return this.playout.isPlaying();
    }

    setGainLevel(level) {
        this.gain = level;
        this.playout.setGainLevel(level);
    }

    setMasterGainLevel(level) {
        this.playout.setMasterGainLevel(level);
    }

    /*
        startTime, endTime in seconds (float).
        segment is for a highlighted section in the UI.

        returns a Promise that will resolve when the AudioBufferSource
        is either stopped or plays out naturally.
    */
    schedulePlay(now, startTime, endTime, options) {
        var start,
            duration,
            relPos,
            when = now,
            segment = (endTime) ? (endTime - startTime) : undefined,
            sourcePromise;

        //1) track has no content to play.
        //2) track does not play in this selection.
        if ((this.endTime <= startTime) || (segment && (startTime + segment) < this.startTime)) {
            //return a resolved promise since this track is technically "stopped".
            return Promise.resolve();
        }

        //track should have something to play if it gets here.

        //the track starts in the future or on the cursor position
        if (this.startTime >= startTime) {
            start = 0;
            when = when + this.startTime - startTime; //schedule additional delay for this audio node.

            if (endTime) {
                segment = segment - (this.startTime - startTime);
                duration = Math.min(segment, this.duration);
            }
            else {
                duration = this.duration;
            }
        }
        else {
            start = startTime - this.startTime;

            if (endTime) {
                duration = Math.min(segment, this.duration - start);
            }
            else {
                duration = this.duration - start;
            }
        }

        start = start + this.cueIn;
        relPos = startTime - this.startTime;

        sourcePromise = this.playout.setUpSource();

        //param relPos: cursor position in seconds relative to this track.
        //can be negative if the cursor is placed before the start of this track etc.
        _forOwn(this.fades, (fade) => {
            let startTime;
            let duration;

            //only apply fade if it's ahead of the cursor.
            if (relPos < fade.end) {
                if (relPos <= fade.start) {
                    startTime = now + (fade.start - relPos);
                    duration = fade.end - fade.start;
                }
                else if (relPos > fade.start && relPos < fade.end) {
                    startTime = now - (relPos - fade.start);
                    duration = fade.end - fade.start;
                }

                switch (fade.type) {
                    case FADEIN:
                        this.playout.applyFadeIn(startTime, duration, fade.shape);
                        break;
                    case FADEOUT:
                        this.playout.applyFadeOut(startTime, duration, fade.shape);
                        break;
                    default:
                        throw new Error("Invalid fade type saved on track.");
                }
            }
        });

        this.playout.setGainLevel(this.gain);
        this.playout.setMasterGainLevel(options.masterGain);
        this.playout.play(when, start, duration);

        return sourcePromise;
    }

    scheduleStop(when=0) {
        this.playout.stop(when);
    }

    renderTimeSelection(data) {
        let startX = secondsToPixels(data.timeSelection.start, data.resolution, data.sampleRate);
        let endX = secondsToPixels(data.timeSelection.end, data.resolution, data.sampleRate);
        let width = endX - startX + 1;
        let className = (width > 1) ? "segment" : "point";

        return h(`div.selection.${className}`, {
            attributes: {
                "style": `position: absolute; width: ${width}px; bottom: 0; top: 0; left: ${startX}px; z-index: 4;`
            }
        });
    }

    renderOverlay(data) {
        let channelPixels = secondsToPixels(data.playlistLength, data.resolution, data.sampleRate);

        let config = {
            attributes: {
                "style": `position: absolute; top: 0; right: 0; bottom: 0; left: 0; width: ${channelPixels}px; z-index: 9;`
            }
        };

        let stateClass = "";

        if (this.enabledStates[this.state]) {
            let state = new stateClasses[this.state](this, data.resolution, data.sampleRate);
            let stateEvents = state.getEvents();

            Object.keys(stateEvents).map((event) => {
                config[`ev-${event}`] = stateEvents[event].bind(state);
            });

            stateClass = state.getClasses();
        }
        //use this overlay for track event cursor position calculations.
        return h(`div.playlist-overlay${stateClass}`, config);
    }

    renderControls(data) {
        let muteClass = data.muted ? ".active" : "";
        let soloClass = data.soloed ? ".active" : "";

        return h("div.controls", {
            attributes: {
                "style": `height: ${data.height}px; width: ${data.controls.width}px; position: absolute; left: 0; z-index: 10;`
            }},
            [
                h("header", [ this.name ]),
                h("div.btn-group", [
                    h(`span.btn.btn-default.btn-xs.btn-mute${muteClass}`, {"ev-click": () => {
                        this.ee.emit("mute", this);
                    }}, [ "Mute" ]),
                    h(`span.btn.btn-default.btn-xs.btn-solo${soloClass}`, {"ev-click": () => {
                        this.ee.emit("solo", this);
                    }}, [ "Solo" ])
                ]),
                h("label", [
                    h("input.volume-slider", {
                        attributes: {
                            "type": "range",
                            "min": 0,
                            "max": 100,
                            "value": 100
                        },
                        "ev-input": (e) => {
                            this.ee.emit("volumechange", e.target.value, this);
                        }
                    })
                ])
            ]
        );
    }

    render(data) {
        let width = secondsToPixels(this.duration, data.resolution, data.sampleRate);
        let playbackX = secondsToPixels(data.playbackSeconds, data.resolution, data.sampleRate);
        let startX = secondsToPixels(this.startTime, data.resolution, data.sampleRate);
        let endX = secondsToPixels(this.endTime, data.resolution, data.sampleRate);
        let progressWidth = 0;

        if (playbackX > 0 && playbackX > startX) {
            if (playbackX < endX) {
                progressWidth = playbackX - startX;
            }
            else {
                progressWidth = width;
            }
        }

        let waveformChildren = [
            h("div.cursor", {attributes: {
                "style": `position: absolute; width: 1px; margin: 0; padding: 0; top: 0; left: ${playbackX}px; bottom: 0; z-index: 5;`
            }})
        ];

        let channels = Object.keys(this.peaks.data).map((channelNum) => {

            let channelChildren = [
                h("div.channel-progress", {attributes: {
                    "style": `position: absolute; width: ${progressWidth}px; height: ${data.height}px; z-index: 2;`
                }})
            ];
            let offset = 0;
            let totalWidth = width;
            let peaks = this.peaks.data[channelNum];

            while (totalWidth > 0) {
                let currentWidth = Math.min(totalWidth, MAX_CANVAS_WIDTH);

                channelChildren.push(h("canvas", {
                    attributes: {
                        "width": currentWidth,
                        "height": data.height,
                        "data-offset": offset,
                        "style": "float: left; position: relative; margin: 0; padding: 0; z-index: 3;"
                    },
                    "hook": new CanvasHook(peaks, offset, data.colors.waveOutlineColor)
                }));

                totalWidth -= currentWidth;
                offset += MAX_CANVAS_WIDTH;
            }

            return h(`div.channel.channel-${channelNum}`, {attributes: {
                    "style": `height: ${data.height}px; width: ${width}px; top: 0; left: ${startX}px; position: absolute; margin: 0; padding: 0; z-index: 1;`
                }},
                channelChildren
            );
        });

        let audibleClass = data.masterGain ? "" : ".silent";

        waveformChildren.push(channels);
        waveformChildren.push(this.renderOverlay(data));

        //draw cursor selection on active track.
        if (data.isActive === true) {
            waveformChildren.push(this.renderTimeSelection(data));
        }

        let waveform = h("div.waveform", {
            attributes: {
                "style": `height: ${data.height}px; position: relative;`
            }},
            waveformChildren
        );

        let channelChildren = [];
        let channelMargin = 0;

        if (data.controls.show) {
            channelChildren.push(this.renderControls(data));
            channelMargin = data.controls.width;
        }

        channelChildren.push(waveform);

        return h(`div.channel-wrapper${audibleClass}`, {
            attributes: {
                "style": `margin-left: ${channelMargin}px; height: ${data.height}px;`
            }},
            channelChildren
        );
    }
}<|endoftext|>