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

/**
 * @param  {string} options.form     A unique key to identify your form throughout the app
 * @param  {array}  options.fields   An array of string or object to configure the form fields
 * @return {object} Enhanced React Component
 */
export function createForm({ form, fields }) {
  return (Component) => {
    class ReduxForm extends React.Component {
      static propTypes = {
        form: PropTypes.object,
      };

      static contextTypes = {
        store: React.PropTypes.object
      };

      constructor(props) {
        super(props);
        this.displayName = form + 'Form';
        this.state = {};

        this.dispatch = this.props.dispatch || this.context.dispatch;
        if (typeof this.dispatch !== 'function') {
          throw new ReferenceError(`[redux-form-utils] Please pass \`dispatch\` to ${form} as props or connect it with Redux's store.`);
        }
      }

      handleChange(key, e) {
        let value;
        if (e.target) {
          value = e.target.value;
        }

        this.dispatch({
          type: '@@form/VALUE_CHANGE',
          meta: {
            form: form,
            field: key,
            complex: value === undefined,
          },
          payload: value !== undefined ? value : { ...e },
        });
      }

      clearAll() {
        this.dispatch({
          type: '@@form/CLEAR_ALL',
          meta: {
            form: form,
          },
        });
      }

      clear(field) {
        if (field && fields.indexOf(field) > -1) {
          this.dispatch({
            type: '@@form/CLEAR',
            meta: {
              form: form,
              field: field,
            },
          });
        }
      }

      render() {
        return (
          <Component {...this.props} fields={fields.reduce((prev, curr) => {
            if (typeof curr === 'string') {
              prev[curr] = {
                value: this.props.form[curr].value,
                onChange: this.handleChange.bind(this, curr),
              };
            } else {
              const { key, valueKey = 'value', resovler, changeType = 'onChange' } = curr;

              if (!key || typeof key !== 'string') {
                throw new TypeError('[redux-form-utils] If you provide a object within \`fields\` options, make sure this object has a key which named \`key\`, and the type of it\'s value is string.');
              }

              prev[key] = {
                [valueKey]: this.props.form[key][valueKey],
                [changeType]: (a, b, c, d) => {
                  if (resovler) {
                    const payload = resovler(a, b, c, d);
                    this.handleChange.call(this, key, payload);
                  } else {
                    this.handleChange.call(this, key, a, b, c, d);
                  }
                },
              };
            }

            return prev;
          }, {})}
          clearAll={::this.clearAll}
          clear={::this.clear} />
        );
      }
    }

    return ReduxForm;
  };
}

export function bindRedux({ form, fields }) {
  return {
    state: {
      form: fields.reduce((prev, curr) => {
        if (typeof curr === 'string') {
          prev[curr] = {
            value: '',
          };
        } else {
          const { key, valueKey = 'value', initValue } = curr;
          prev[key] = {
            [valueKey]: initValue !== undefined ? initValue : '',
          };
        }

        return prev;
      }, {}),
    },

    setInitValue(initObj, state) {
      if (!state || !state.form) {
        return state;
      }

      return {
        ...state,
        form: {
          ...state.form,
          ...Object.keys(initObj).reduce((prev, curr) => {
            if (typeof initObj[curr] !== 'object') {
              return {
                ...prev,
                [curr]: {
                  value: initObj[curr],
                },
              };
            }

            return {
              ...prev,
              [curr]: initObj[curr],
            };
          }, {}),
        },
      };
    },

    reducer(state, action) {
      if (action.type.indexOf('@@form') !== 0 || action.meta.form !== form) {
        return state;
      }

      switch (action.type) {
        case '@@form/VALUE_CHANGE': {
          let newField;
          if (action.meta.complex) {
            return {
              ...state,
              form: {
                ...state.form,
                [action.meta.field]: {
                  ...state.form[action.meta.field],
                  ...action.payload,
                },
              },
            };
          }

          return {
            ...state,
            form: {
              ...state.form,
              [action.meta.field]: {
                ...state.form[action.meta.field],
                value: action.payload,
              },
            },
          };
        }

        case '@@form/CLEAR_ALL': {
          return {
            ...state,
            form: Object.keys(state.form).reduce((prev, curr) => {
              prev[curr] = {
                ...state.form[curr],
                value: '',
              };

              return prev;
            }, {}),
          };
        }

        case '@@form/CLEAR': {
          return {
            ...state,
            form: {
              ...state.form,
              [action.meta.field]: {
                ...state.form[action.meta.field],
                value: '',
              },
            },
          };
        }

        default:
          return state;
      }
    },

    actionCreators: {
      clear(field) {
        return {
          type: '@@form/CLEAR',
          meta: {
            form: form,
            field: field,
          },
        };
      },

      clearAll() {
        return {
          type: '@@form/CLEAR_ALL',
          meta: {
            form: form,
          },
        };
      }
    },
  };
}
<commit_msg>fix: fix a typo<commit_after>import React, { PropTypes } from 'react';

/**
 * @param  {string} options.form     A unique key to identify your form throughout the app
 * @param  {array}  options.fields   An array of string or object to configure the form fields
 * @return {object} Enhanced React Component
 */
export function createForm({ form, fields }) {
  return (Component) => {
    class ReduxForm extends React.Component {
      static propTypes = {
        form: PropTypes.object,
      };

      static contextTypes = {
        store: React.PropTypes.object
      };

      constructor(props) {
        super(props);
        this.displayName = form + 'Form';
        this.state = {};

        this.dispatch = this.props.dispatch || this.context.dispatch;
        if (typeof this.dispatch !== 'function') {
          throw new ReferenceError(`[redux-form-utils] Please pass \`dispatch\` to ${form} as props or connect it with Redux's store.`);
        }
      }

      handleChange(key, e) {
        let value;
        if (e.target) {
          value = e.target.value;
        }

        this.dispatch({
          type: '@@form/VALUE_CHANGE',
          meta: {
            form: form,
            field: key,
            complex: value === undefined,
          },
          payload: value !== undefined ? value : { ...e },
        });
      }

      clearAll() {
        this.dispatch({
          type: '@@form/CLEAR_ALL',
          meta: {
            form: form,
          },
        });
      }

      clear(field) {
        if (field && fields.indexOf(field) > -1) {
          this.dispatch({
            type: '@@form/CLEAR',
            meta: {
              form: form,
              field: field,
            },
          });
        }
      }

      render() {
        return (
          <Component {...this.props} fields={fields.reduce((prev, curr) => {
            if (typeof curr === 'string') {
              prev[curr] = {
                value: this.props.form[curr].value,
                onChange: this.handleChange.bind(this, curr),
              };
            } else {
              const { key, valueKey = 'value', changeType = 'onChange' } = curr;
              let { resolver, resovler } = curr;

              // backward compatible for a typo
              if (!resolver) {
                resolver = resovler;
              }

              if (!key || typeof key !== 'string') {
                throw new TypeError('[redux-form-utils] If you provide a object within \`fields\` options, make sure this object has a key which named \`key\`, and the type of it\'s value is string.');
              }

              prev[key] = {
                [valueKey]: this.props.form[key][valueKey],
                [changeType]: (a, b, c, d) => {
                  if (resolver) {
                    const payload = resolver(a, b, c, d);
                    this.handleChange.call(this, key, payload);
                  } else {
                    this.handleChange.call(this, key, a, b, c, d);
                  }
                },
              };
            }

            return prev;
          }, {})}
          clearAll={::this.clearAll}
          clear={::this.clear} />
        );
      }
    }

    return ReduxForm;
  };
}

export function bindRedux({ form, fields }) {
  return {
    state: {
      form: fields.reduce((prev, curr) => {
        if (typeof curr === 'string') {
          prev[curr] = {
            value: '',
          };
        } else {
          const { key, valueKey = 'value', initValue } = curr;
          prev[key] = {
            [valueKey]: initValue !== undefined ? initValue : '',
          };
        }

        return prev;
      }, {}),
    },

    setInitValue(initObj, state) {
      if (!state || !state.form) {
        return state;
      }

      return {
        ...state,
        form: {
          ...state.form,
          ...Object.keys(initObj).reduce((prev, curr) => {
            if (typeof initObj[curr] !== 'object') {
              return {
                ...prev,
                [curr]: {
                  value: initObj[curr],
                },
              };
            }

            return {
              ...prev,
              [curr]: initObj[curr],
            };
          }, {}),
        },
      };
    },

    reducer(state, action) {
      if (action.type.indexOf('@@form') !== 0 || action.meta.form !== form) {
        return state;
      }

      switch (action.type) {
        case '@@form/VALUE_CHANGE': {
          let newField;
          if (action.meta.complex) {
            return {
              ...state,
              form: {
                ...state.form,
                [action.meta.field]: {
                  ...state.form[action.meta.field],
                  ...action.payload,
                },
              },
            };
          }

          return {
            ...state,
            form: {
              ...state.form,
              [action.meta.field]: {
                ...state.form[action.meta.field],
                value: action.payload,
              },
            },
          };
        }

        case '@@form/CLEAR_ALL': {
          return {
            ...state,
            form: Object.keys(state.form).reduce((prev, curr) => {
              prev[curr] = {
                ...state.form[curr],
                value: '',
              };

              return prev;
            }, {}),
          };
        }

        case '@@form/CLEAR': {
          return {
            ...state,
            form: {
              ...state.form,
              [action.meta.field]: {
                ...state.form[action.meta.field],
                value: '',
              },
            },
          };
        }

        default:
          return state;
      }
    },

    actionCreators: {
      clear(field) {
        return {
          type: '@@form/CLEAR',
          meta: {
            form: form,
            field: field,
          },
        };
      },

      clearAll() {
        return {
          type: '@@form/CLEAR_ALL',
          meta: {
            form: form,
          },
        };
      }
    },
  };
}
<|endoftext|>


<commit_before>var util = require('util');
var request = require('request');
var pg = require('pg');

pg.defaults.ssl = true;

function withDb(cb) {
  var dbString = process.env['DATABASE_URL'];
  if(dbString == null) {
    throw new Error('You must set a DATABASE_URL enviroment variable containing the database connection string');
  }
  pg.connect(process.env.DATABASE_URL, function(err, client) {
    if(err) {
      console.log(err);
      throw err;
    }
    cb(client);
  });
}

function sanatizeNames(names) {
  return names.map(function(n) {
    if(n.contains(' ')) {
      throw new Error('Usernames cannot contain a space (' + n + ')');
    }
    return n.toLowerCase().Trim();
  });
}

function getNextStatuses(names, cb) {
  var safeNames = sanatizeNames(names);
  var apiUrl = 'https://api.twitch.tv/kraken/streams?channel=';
  var nameCsv = names.join();
  var requestUrl = apiUrl + nameCsv;
  request(requestUrl, function(error, response, body) {
    if(error || response.statusCode !== 200) {
      console.log('Request error (' + response.statusCode + '): ' + error);
    }
    cb(body);
  });
}

function getCurrentStatuses(names, cb) {
  withDb(function(db) {
    var nameCsv = names.join();
    db.query('SELECT ss.* FROM streamer_statuses ss INNER JOIN streamer s ON s.id = ss.streamer_id WHERE s.name IN (' + nameCsv + ') HAVING MAX(ss.timestamp) GROUP BY s.id')
      .on('row', function(row, result) { result.addRow(row); })
      .on('end', function(result) { cb(result.rows); });
  });
}

function getDifferingStatuses(statuses, nextStatuses) {

}

function saveStatuses(statuses) {

}

function getNames(cb) {
  withDb(function(db) {
    db.query('select name FROM streamer')
      .on('row', function(row, result) { result.addRow(row); })
      .on('end', function(result) { cb(result.rows); });
  });
}

var interval = process.env['TWITCH_STATUS_RECORDER_INTERVAL'] === null ? 60000 : process.env.TWITCH_STATUS_RECORDER_INTERVAL;
setInterval(function() {
  getNames(function(names) {
    if(names.length > 0) {
      getCurrentStatuses(names, function(currentStatuses) {
        getNextStatuses(names, function(nextStatuses) {
          var differentStatuses = getDifferingStatuses(currentStatuses, nextStatuses);
          saveStatuses(differentStatuses);
        });
      });
    }
  });
}, interval);

<commit_msg>Adding some default stuff in while I get things working<commit_after>var util = require('util');
var request = require('request');
var pg = require('pg');

pg.defaults.ssl = true;

function withDb(cb) {
  var dbString = process.env['DATABASE_URL'];
  if(dbString == null) {
    throw new Error('You must set a DATABASE_URL enviroment variable containing the database connection string');
  }
  pg.connect(process.env.DATABASE_URL, function(err, client) {
    if(err) {
      console.log(err);
      throw err;
    }
    cb(client);
  });
}

function sanatizeNames(names) {
  return names.map(function(n) {
    if(n.contains(' ')) {
      throw new Error('Usernames cannot contain a space (' + n + ')');
    }
    return n.toLowerCase().Trim();
  });
}

function getNextStatuses(names, cb) {
  var safeNames = sanatizeNames(names);
  var apiUrl = 'https://api.twitch.tv/kraken/streams?channel=';
  var nameCsv = names.join();
  var requestUrl = apiUrl + nameCsv;
  request(requestUrl, function(error, response, body) {
    if(error || response.statusCode !== 200) {
      console.log('Request error (' + response.statusCode + '): ' + error);
    }
    cb(body);
  });
}

function getCurrentStatuses(names, cb) {
  withDb(function(db) {
    var nameCsv = names.join();
    db.query('SELECT ss.* FROM streamer_statuses ss INNER JOIN streamer s ON s.id = ss.streamer_id WHERE s.name IN (' + nameCsv + ') HAVING MAX(ss.timestamp) GROUP BY s.id')
      .on('row', function(row, result) { result.addRow(row); })
      .on('end', function(result) { cb(result.rows); });
  });
}

function getDifferingStatuses(statuses, nextStatuses) {
  return nextStatuses;
}

function saveStatuses(statuses) {
  console.log(statuses);
}

function getNames(cb) {
  withDb(function(db) {
    db.query('select name FROM streamer')
      .on('row', function(row, result) { result.addRow(row); })
      .on('end', function(result) { cb(result.rows); });
  });
}

var interval = process.env['TWITCH_STATUS_RECORDER_INTERVAL'] === null ? 60000 : process.env.TWITCH_STATUS_RECORDER_INTERVAL;
setInterval(function() {
  getNames(function(names) {
    if(names.length > 0) {
      getCurrentStatuses(names, function(currentStatuses) {
        getNextStatuses(names, function(nextStatuses) {
          var differentStatuses = getDifferingStatuses(currentStatuses, nextStatuses);
          saveStatuses(differentStatuses);
        });
      });
    }
  });
}, interval);

<|endoftext|>


<commit_before>const globby = require('globby')
const path = require('path')
const fs = require('fs')
const prettierx = require('prettierx')
const lintStaged = require('lint-staged')
const {
  createIgnorer,
  isSupportedExtension,
  getOptions,
  getScm,
  getRanges,
  getPathInHostNodeModules
} = require('./utils')

const DEFAULT_IGNORE = [
  '!**/*.min.js',
  '!**/{node_modules,coverage,vendor}/**',
  '!./{node_modules,coverage,vendor}/**',
  '!**/.{git,svn,hg}/**',
  '!./.{git,svn,hg}/**'
]

const LINT_REGEXP = /\.(js|mjs|cjs|jsx)$/

function format (source, options) {
  return prettierx.format(source, getOptions(options))
}

function check (source, options) {
  return prettierx.check(source, getOptions(options))
}

function getFileInfo (filePath, options) {
  return prettierx.getFileInfo(filePath, options)
}

function formatWithCursor (source, options) {
  return prettierx.formatWithCursor(source, getOptions(options))
}

function formatWithRanges (source, ranges, options) {
  if (!ranges || ranges.length === 0) {
    return format(source, options)
  }

  let output = source

  ranges.forEach(({ rangeStart, rangeEnd }) => {
    output = format(
      output,
      Object.assign({}, options, { rangeStart, rangeEnd })
    )
  })

  return output
}

function checkWithRanges (source, ranges, options) {
  if (!ranges || ranges.length === 0) {
    return check(source, options)
  }

  let valid = true
  ranges.forEach(({ rangeStart, rangeEnd }) => {
    valid =
      valid &&
      check(source, Object.assign({}, options, { rangeStart, rangeEnd }))
  })
  return valid
}

async function run (cwd, config) {
  // Filepaths will be relative to this directory
  let root = cwd

  let patterns = config.patterns || []

  if ((config.format || config.lint || config.check) && patterns.length === 0) {
    patterns.push('**/*')
  }

  if (!Array.isArray(patterns)) {
    return new Error('patterns should be an array')
  }

  const onStart = config.onStart || function () {}
  const onProcessed = config.onProcessed || function () {}

  // They ignore files relative to current directory
  const filters = [
    // Ignore .prettierignore entries
    createIgnorer(cwd),
    // Ignore unsupported extensions
    isSupportedExtension
  ]

  const runFilters = path => filters.reduce((a, b) => a && b(path), true)

  let engine

  if (config.lint) {
    const eslintPath = getPathInHostNodeModules('eslint')
    const babelEslintPath = getPathInHostNodeModules('babel-eslint')

    const configs = {
      prettier: getPathInHostNodeModules('eslint-config-prettier'),
      standard: getPathInHostNodeModules('eslint-config-standard'),
      'standard-jsx': getPathInHostNodeModules('eslint-config-standard-jsx')
    }

    const getConfig = (config, file) => path.join(configs[config], file)

    const eslint = require(eslintPath)
    engine = new eslint.CLIEngine({
      parser: babelEslintPath,
      resolvePluginsRelativeTo: path.join(__dirname, 'vendor'),
      baseConfig: {
        plugins: ['jest'],
        env: {
          'jest/globals': true
        },
        extends: [
          getConfig('standard', 'index.js'),
          getConfig('standard-jsx', 'index.js'),
          getConfig('prettier', 'index.js'),
          getConfig('prettier', '@typescript-eslint.js'),
          getConfig('prettier', 'babel.js'),
          getConfig('prettier', 'flowtype.js'),
          getConfig('prettier', 'react.js'),
          getConfig('prettier', 'standard.js'),
          getConfig('prettier', 'unicorn.js'),
          getConfig('prettier', 'vue.js')
        ]
      }
    })
  }

  onStart({ engine })

  let files

  if (config.changed || config.since || config.lines || config.staged) {
    const scm = getScm(cwd)

    if (!scm) {
      return new Error('No git repository detected...')
    }

    root = scm.dir

    const revision = scm.getRevision(config.since || 'HEAD')

    if (!revision) {
      return new Error(`Cannot find ${scm.name()} revision ${config.since}`)
    }

    files = scm
      .getChanges(revision, config.patterns)
      .filter(p => runFilters(p.filepath))
  } else {
    patterns = patterns.concat(DEFAULT_IGNORE)

    let filePaths = []

    try {
      filePaths = globby
        .sync(patterns, { dot: true, nodir: true, cwd })
        .map(filePath => path.relative(process.cwd(), filePath))
    } catch (error) {
      return new Error(`Unable to expand glob pattern: ${error.message}`)
    }

    files = filePaths.filter(runFilters).map(filepath => ({ filepath }))
  }

  if (config.staged) {
    const lintConfig = {}

    let command = 'node ' + path.join(__dirname, 'cli.js')

    if (config.lint) {
      command += ' --lint'
    }
    if (config.check) {
      command += ' --check'
    }
    if (config.lines) {
      command += ' --lines'
    }

    files.forEach(f => {
      const mask = '*' + path.extname(f.filepath)
      if (!lintConfig[mask]) {
        lintConfig[mask] = [command, 'git add']
      }
    })

    if (config.lint) {
      console.log('Formatting and linting all staged files...')
    } else {
      console.log('Formatting all staged files...')
    }

    const success = await lintStaged(
      {
        config: lintConfig,
        shell: false,
        quiet: true,
        debug: false
      },
      {
        log: msg => console.log(msg),
        error: msg =>
          console.error(
            msg.replace(new RegExp(command, 'g'), 'prettier-standard').trim()
          )
      }
    )

    if (!success) {
      process.exit(1)
    }

    return
  }

  for (const file of files) {
    const start = Date.now()
    const { filepath, changes } = file

    const fullpath = path.join(root, filepath)
    const input = fs.readFileSync(fullpath, 'utf8')

    let ranges
    if (config.lines && changes) {
      ranges = getRanges(input, changes)
    }

    const fileOptions = Object.assign(
      {},
      prettierx.resolveConfig.sync(fullpath, {
        editorconfig: true
      }),
      config.options,
      { filepath: fullpath }
    )

    const fileInfo = prettierx.getFileInfo.sync(fullpath, {})

    if (!fileOptions.parser) {
      fileOptions.parser = fileInfo.inferredParser
    }

    if (config.check) {
      const formatted = checkWithRanges(input, ranges, fileOptions)

      let report
      if (config.lint && fullpath.match(LINT_REGEXP)) {
        report = engine.executeOnText(input, fullpath)
      }

      onProcessed({
        file: filepath,
        runtime: Date.now() - start,
        formatted,
        report,
        engine,
        check: config.check
      })

      continue
    }

    const output = formatWithRanges(input, ranges, fileOptions)

    const formatted = output === input

    if (!formatted) {
      fs.writeFileSync(fullpath, output)
    }

    let report
    if (config.lint && fullpath.match(LINT_REGEXP)) {
      report = engine.executeOnText(output, fullpath)
    }

    onProcessed({
      file: filepath,
      runtime: Date.now() - start,
      formatted,
      report,
      check: config.check
    })
  }
}

module.exports = {
  format,
  check,
  run,
  getFileInfo,
  formatWithCursor,
  resolveConfig: prettierx.resolveConfig,
  clearConfigCache: prettierx.clearConfigCache,
  getSupportInfo: prettierx.getSupportInfo
}
<commit_msg>Prevent running lint-staged when no files changed<commit_after>const globby = require('globby')
const path = require('path')
const fs = require('fs')
const prettierx = require('prettierx')
const lintStaged = require('lint-staged')
const {
  createIgnorer,
  isSupportedExtension,
  getOptions,
  getScm,
  getRanges,
  getPathInHostNodeModules
} = require('./utils')

const DEFAULT_IGNORE = [
  '!**/*.min.js',
  '!**/{node_modules,coverage,vendor}/**',
  '!./{node_modules,coverage,vendor}/**',
  '!**/.{git,svn,hg}/**',
  '!./.{git,svn,hg}/**'
]

const LINT_REGEXP = /\.(js|mjs|cjs|jsx)$/

function format (source, options) {
  return prettierx.format(source, getOptions(options))
}

function check (source, options) {
  return prettierx.check(source, getOptions(options))
}

function getFileInfo (filePath, options) {
  return prettierx.getFileInfo(filePath, options)
}

function formatWithCursor (source, options) {
  return prettierx.formatWithCursor(source, getOptions(options))
}

function formatWithRanges (source, ranges, options) {
  if (!ranges || ranges.length === 0) {
    return format(source, options)
  }

  let output = source

  ranges.forEach(({ rangeStart, rangeEnd }) => {
    output = format(
      output,
      Object.assign({}, options, { rangeStart, rangeEnd })
    )
  })

  return output
}

function checkWithRanges (source, ranges, options) {
  if (!ranges || ranges.length === 0) {
    return check(source, options)
  }

  let valid = true
  ranges.forEach(({ rangeStart, rangeEnd }) => {
    valid =
      valid &&
      check(source, Object.assign({}, options, { rangeStart, rangeEnd }))
  })
  return valid
}

async function run (cwd, config) {
  // Filepaths will be relative to this directory
  let root = cwd

  let patterns = config.patterns || []

  if ((config.format || config.lint || config.check) && patterns.length === 0) {
    patterns.push('**/*')
  }

  if (!Array.isArray(patterns)) {
    return new Error('patterns should be an array')
  }

  const onStart = config.onStart || function () {}
  const onProcessed = config.onProcessed || function () {}

  // They ignore files relative to current directory
  const filters = [
    // Ignore .prettierignore entries
    createIgnorer(cwd),
    // Ignore unsupported extensions
    isSupportedExtension
  ]

  const runFilters = path => filters.reduce((a, b) => a && b(path), true)

  let engine

  if (config.lint) {
    const eslintPath = getPathInHostNodeModules('eslint')
    const babelEslintPath = getPathInHostNodeModules('babel-eslint')

    const configs = {
      prettier: getPathInHostNodeModules('eslint-config-prettier'),
      standard: getPathInHostNodeModules('eslint-config-standard'),
      'standard-jsx': getPathInHostNodeModules('eslint-config-standard-jsx')
    }

    const getConfig = (config, file) => path.join(configs[config], file)

    const eslint = require(eslintPath)
    engine = new eslint.CLIEngine({
      parser: babelEslintPath,
      resolvePluginsRelativeTo: path.join(__dirname, 'vendor'),
      baseConfig: {
        plugins: ['jest'],
        env: {
          'jest/globals': true
        },
        extends: [
          getConfig('standard', 'index.js'),
          getConfig('standard-jsx', 'index.js'),
          getConfig('prettier', 'index.js'),
          getConfig('prettier', '@typescript-eslint.js'),
          getConfig('prettier', 'babel.js'),
          getConfig('prettier', 'flowtype.js'),
          getConfig('prettier', 'react.js'),
          getConfig('prettier', 'standard.js'),
          getConfig('prettier', 'unicorn.js'),
          getConfig('prettier', 'vue.js')
        ]
      }
    })
  }

  onStart({ engine })

  let files

  if (config.changed || config.since || config.lines || config.staged) {
    const scm = getScm(cwd)

    if (!scm) {
      return new Error('No git repository detected...')
    }

    root = scm.dir

    const revision = scm.getRevision(config.since || 'HEAD')

    if (!revision) {
      return new Error(`Cannot find ${scm.name()} revision ${config.since}`)
    }

    files = scm
      .getChanges(revision, config.patterns)
      .filter(p => runFilters(p.filepath))
  } else {
    patterns = patterns.concat(DEFAULT_IGNORE)

    let filePaths = []

    try {
      filePaths = globby
        .sync(patterns, { dot: true, nodir: true, cwd })
        .map(filePath => path.relative(process.cwd(), filePath))
    } catch (error) {
      return new Error(`Unable to expand glob pattern: ${error.message}`)
    }

    files = filePaths.filter(runFilters).map(filepath => ({ filepath }))
  }

  if (config.staged) {
    const lintConfig = {}

    let command = 'node ' + path.join(__dirname, 'cli.js')

    if (config.lint) {
      command += ' --lint'
    }
    if (config.check) {
      command += ' --check'
    }
    if (config.lines) {
      command += ' --lines'
    }

    files.forEach(f => {
      const mask = '*' + path.extname(f.filepath)
      if (!lintConfig[mask]) {
        lintConfig[mask] = [command, 'git add']
      }
    })

    if (Object.keys(lintConfig).length > 0) {
      if (config.lint) {
        console.log('Formatting and linting all staged files...')
      } else {
        console.log('Formatting all staged files...')
      }

      const success = await lintStaged(
        {
          config: lintConfig,
          shell: false,
          quiet: true,
          debug: false
        },
        {
          log: msg => console.log(msg),
          error: msg =>
            console.error(
              msg.replace(new RegExp(command, 'g'), 'prettier-standard').trim()
            )
        }
      )

      if (!success) {
        process.exit(1)
      }
    }

    return
  }

  for (const file of files) {
    const start = Date.now()
    const { filepath, changes } = file

    const fullpath = path.join(root, filepath)
    const input = fs.readFileSync(fullpath, 'utf8')

    let ranges
    if (config.lines && changes) {
      ranges = getRanges(input, changes)
    }

    const fileOptions = Object.assign(
      {},
      prettierx.resolveConfig.sync(fullpath, {
        editorconfig: true
      }),
      config.options,
      { filepath: fullpath }
    )

    const fileInfo = prettierx.getFileInfo.sync(fullpath, {})

    if (!fileOptions.parser) {
      fileOptions.parser = fileInfo.inferredParser
    }

    if (config.check) {
      const formatted = checkWithRanges(input, ranges, fileOptions)

      let report
      if (config.lint && fullpath.match(LINT_REGEXP)) {
        report = engine.executeOnText(input, fullpath)
      }

      onProcessed({
        file: filepath,
        runtime: Date.now() - start,
        formatted,
        report,
        engine,
        check: config.check
      })

      continue
    }

    const output = formatWithRanges(input, ranges, fileOptions)

    const formatted = output === input

    if (!formatted) {
      fs.writeFileSync(fullpath, output)
    }

    let report
    if (config.lint && fullpath.match(LINT_REGEXP)) {
      report = engine.executeOnText(output, fullpath)
    }

    onProcessed({
      file: filepath,
      runtime: Date.now() - start,
      formatted,
      report,
      check: config.check
    })
  }
}

module.exports = {
  format,
  check,
  run,
  getFileInfo,
  formatWithCursor,
  resolveConfig: prettierx.resolveConfig,
  clearConfigCache: prettierx.clearConfigCache,
  getSupportInfo: prettierx.getSupportInfo
}
<|endoftext|>


<commit_before>var octonode   = require('octonode'),
		path       = require('path'),
		sh         = require('execSync'),
		pkg_config = require('strangelove-config');

// Github authentication
var config,
		gh_client,
		gh_entity;

/*    I N I T  C O M M A N D S   */
function configClient(){
	var dir = path.dirname(__dirname);
	pkg_config.config(dir)
}

/*    C R E A T I O N  C O M M A N D S   */
function setGitHubOrgType(gh_c){
	if (config.github.type == 'org'){
		return gh_c.org(config.github.account_name)
	} else if (config.github.type == 'individual'){
		return gh_c.me()
	}
}
function gitInit(current_dir, cb){
	var git_init  = sh.run('git init && git remote add origin https://github.com/' + config.github.account_name + '/' + current_dir + '.git');
}
function createGitHubRepo(repo_name, cb){
	gh_entity.repo({
	  "name": repo_name,
	  "private": config.github.private_repos
	}, function(err, response){
		cb(err, response)
	});
}
function createGitHubHook(repo_name, cb){
	var gh_repo = gh_client.repo(config.github.account_name + '/' + repo_name);

	gh_repo.hook({
	  "name": "web",
	  "active": true,
	  "events": ["push", "status"],
	  "config": {
	    "url": config.server.url
	  }
	}, function(err, response){
		cb(err, response)
	});
}
function setConfig(){
  config  = config || require('../config.json');
	gh_client = gh_client || octonode.client(config.github.access_token);
	gh_entity = gh_entity || setGitHubOrgType(gh_client);
}
function initAll(){
	setConfig();
	var current_dir = path.basename(path.resolve('./'));
	gitInit(current_dir);

	createGitHubRepo(current_dir, function(err){
		if (err) reportError(err, 'GitHub repo creation failed!');
		console.log('GitHub repo created...')

		createGitHubHook(current_dir, function(err){
			if (err) reportError(err, 'GitHub hook failed');
			console.log('GitHub hook created. Preview at ' + config.server.url.split(':')[0] + ':3000/' + current_dir);
		});

	});
}

function reportError(err, msg){
	throw new Error(err);
	console.log(msg);
}

module.exports = {
	config: configClient,
	init: initAll
}<commit_msg>use new pkg name<commit_after>var octonode   = require('octonode'),
		path       = require('path'),
		sh         = require('execSync'),
		pkg_config = require('snowy-owl-config');

// Github authentication
var config,
		gh_client,
		gh_entity;

/*    I N I T  C O M M A N D S   */
function configClient(){
	var dir = path.dirname(__dirname);
	pkg_config.config(dir)
}

/*    C R E A T I O N  C O M M A N D S   */
function setGitHubOrgType(gh_c){
	if (config.github.type == 'org'){
		return gh_c.org(config.github.account_name)
	} else if (config.github.type == 'individual'){
		return gh_c.me()
	}
}
function gitInit(current_dir, cb){
	var git_init  = sh.run('git init && git remote add origin https://github.com/' + config.github.account_name + '/' + current_dir + '.git');
}
function createGitHubRepo(repo_name, cb){
	gh_entity.repo({
	  "name": repo_name,
	  "private": config.github.private_repos
	}, function(err, response){
		cb(err, response)
	});
}
function createGitHubHook(repo_name, cb){
	var gh_repo = gh_client.repo(config.github.account_name + '/' + repo_name);

	gh_repo.hook({
	  "name": "web",
	  "active": true,
	  "events": ["push", "status"],
	  "config": {
	    "url": config.server.url
	  }
	}, function(err, response){
		cb(err, response)
	});
}
function setConfig(){
  config  = config || require('../config.json');
	gh_client = gh_client || octonode.client(config.github.access_token);
	gh_entity = gh_entity || setGitHubOrgType(gh_client);
}
function initAll(){
	setConfig();
	var current_dir = path.basename(path.resolve('./'));
	gitInit(current_dir);

	createGitHubRepo(current_dir, function(err){
		if (err) reportError(err, 'GitHub repo creation failed!');
		console.log('GitHub repo created...')

		createGitHubHook(current_dir, function(err){
			if (err) reportError(err, 'GitHub hook failed');
			console.log('GitHub hook created. Preview at ' + config.server.url.split(':')[0] + ':3000/' + current_dir);
		});

	});
}

function reportError(err, msg){
	throw new Error(err);
	console.log(msg);
}

module.exports = {
	config: configClient,
	init: initAll
}<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-modal';
import { CSSTransitionGroup } from 'react-transition-group';

import './index.styl';

const CLOSE_TIMEOUT = 500;

export default function ReactSlidingPane({
    isOpen,
    title,
    subtitle,
    onRequestClose,
    onAfterOpen,
    children,
    className,
    overlayClassName,
    from = 'right',
    width }) {
    const directionClass = `slide-pane_from_${from}`;

    return <Modal
        className={ `slide-pane ${directionClass} ${className || ''}` }
        style={{
            content: { width: width || '80%' }
        }}
        overlayClassName={ `slide-pane__overlay ${overlayClassName || ''}`}
        closeTimeoutMS={ CLOSE_TIMEOUT }
        isOpen={ isOpen }
        onAfterOpen={ onAfterOpen }
        onRequestClose={ onRequestClose }
        contentLabel={ `Modal "${title || ''}"` }>

                <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 22'><path fill='currentColor' fillRule='evenodd' d='M4 11l8 8c.6.5.6 1.5 0 2-.5.6-1.5.6-2 0l-9-9c-.6-.5-.6-1.5 0-2l9-9c.5-.6 1.5-.6 2 0 .6.5.6 1.5 0 2l-8 8z'/></svg>

{ title }

{ subtitle }

            {/* Render pane content only when pane is visible, hide after close animation */}
             <CSSTransitionGroup
                transitionName='content-appear'
                transitionEnter={ false }
                transitionLeaveTimeout={ CLOSE_TIMEOUT }>
                { isOpen ? children : null }
            </CSSTransitionGroup>

    </Modal>;
}

ReactSlidingPane.propTypes = {
    isOpen: PropTypes.bool.isRequired,
    title: PropTypes.any,
    subtitle: PropTypes.any,
    onRequestClose: PropTypes.func,
    onAfterOpen: PropTypes.func,
    children: PropTypes.any.isRequired,
    className: PropTypes.string,
    overlayClassName: PropTypes.string,
    from: PropTypes.oneOf(['left', 'right']),
    width: PropTypes.string
};
<commit_msg>Bugfix: Form inputs within the modal content were not being tabbed correctly because CSSTransitionGroup was rendering content inside span<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Modal from 'react-modal';
import { CSSTransitionGroup } from 'react-transition-group';

import './index.styl';

const CLOSE_TIMEOUT = 500;

export default function ReactSlidingPane({
    isOpen,
    title,
    subtitle,
    onRequestClose,
    onAfterOpen,
    children,
    className,
    overlayClassName,
    from = 'right',
    width }) {
    const directionClass = `slide-pane_from_${from}`;

    return <Modal
        className={ `slide-pane ${directionClass} ${className || ''}` }
        style={{
            content: { width: width || '80%' }
        }}
        overlayClassName={ `slide-pane__overlay ${overlayClassName || ''}`}
        closeTimeoutMS={ CLOSE_TIMEOUT }
        isOpen={ isOpen }
        onAfterOpen={ onAfterOpen }
        onRequestClose={ onRequestClose }
        contentLabel={ `Modal "${title || ''}"` }>

                <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 13 22'><path fill='currentColor' fillRule='evenodd' d='M4 11l8 8c.6.5.6 1.5 0 2-.5.6-1.5.6-2 0l-9-9c-.6-.5-.6-1.5 0-2l9-9c.5-.6 1.5-.6 2 0 .6.5.6 1.5 0 2l-8 8z'/></svg>

{ title }

{ subtitle }

            {/* Render pane content only when pane is visible, hide after close animation */}
             <CSSTransitionGroup
                component='div'
                transitionName='content-appear'
                transitionEnter={ false }
                transitionLeaveTimeout={ CLOSE_TIMEOUT }>
                { isOpen ? children : null }
            </CSSTransitionGroup>

    </Modal>;
}

ReactSlidingPane.propTypes = {
    isOpen: PropTypes.bool.isRequired,
    title: PropTypes.any,
    subtitle: PropTypes.any,
    onRequestClose: PropTypes.func,
    onAfterOpen: PropTypes.func,
    children: PropTypes.any.isRequired,
    className: PropTypes.string,
    overlayClassName: PropTypes.string,
    from: PropTypes.oneOf(['left', 'right']),
    width: PropTypes.string
};
<|endoftext|>


<commit_before>import isNumber from './isNumber';
import isPlainObject from './isPlainObject';
import isFunction from './isFunction';
import isString from './isString';
import isArray from './isArray';
import isEmptyString from './isEmptyString';
import checkNumberLength from './checkNumberLength';
import isEmail from './isEmail';
import isPhone from './isPhone';
import toggle from './toggle';
import formatDate from './formatDate';

export {
  isNumber,
  isPlainObject,
  isFunction,
  isString,
  isArray,
  toggle,
  isEmptyString,
  checkNumberLength,
  isEmail,
  isPhone,
  formatDate
};
<commit_msg>imported arrayMax correctly<commit_after>import isNumber from './isNumber';
import isPlainObject from './isPlainObject';
import isFunction from './isFunction';
import isString from './isString';
import isArray from './isArray';
import isEmptyString from './isEmptyString';
import checkNumberLength from './checkNumberLength';
import isEmail from './isEmail';
import isPhone from './isPhone';
import toggle from './toggle';
import formatDate from './formatDate';
import arrayMax from './arrayMax';

export {
  isNumber,
  isPlainObject,
  isFunction,
  isString,
  isArray,
  toggle,
  isEmptyString,
  checkNumberLength,
  isEmail,
  isPhone,
  formatDate,
  arrayMax
};
<|endoftext|>


<commit_before>var Alexa = require('alexa-sdk');

//creates an Alexa object
exports.handler = function(event, context, callback){
	var alexa = Alexa.handler(event, context);
	alexa.registerHandlers(handlers);
	alexa.execute();
}

//
var handlers = {

    'GetDiagnonisIntent': function () {
        this.emit(':tell', 'You are gonna die');
    }

};
/*
var states = {

	SYMPTOMMODE: '_SYMPTOMMODE';,

};

var symptomModeHandler = Alexa.CreateStateHandler(states.SYMPTOMMODE, {

	'SymptomSession': function(){
		this.handler.state = '';
		this.emitWithState('New Session');
	},

	'SymptomIntent': function(){
		var guestSaid = this.event.
	}
}
*/
<commit_msg>merging<commit_after>'use strict'

var APP_ID = undefined;
var Alexa = require('./alexa-sdk');

//creates an Alexa object
exports.handler = function(event, context, callback){
	var alexa = Alexa.handler(event, context);
	alexa.registerHandlers(handlers);
	alexa.execute();
}

//
var handlers = {

	'OpenSymptomCheckerIntent': function(){
		this.emit(':tell', 'Welcome to symptom checker. please list your symptoms.')
	},

    'GetDiagnonisIntent': function() {
        this.emit(':tell', 'You are gonna die');
    }

};
/*
var states = {

	SYMPTOMMODE: '_SYMPTOMMODE';,

};

var symptomModeHandler = Alexa.CreateStateHandler(states.SYMPTOMMODE, {

	'SymptomSession': function(){
		this.handler.state = '';
		this.emitWithState('New Session');
	},

	'SymptomIntent': function(){
		var guestSaid = this.event.
	}
}
*/
<|endoftext|>


<commit_before>export default class MicroEmitter {
  constructor() {
    this._listeners = {};
  }

  /**
   * Adds a listener function to the specified event.
   * @param {String} type
   * @param {Function} listener
   * @param {Boolean} once
   */
  _addListener(type, listener, once) {
    this._listeners[type] = this._listeners[type] || [];
    this._listeners[type].push({ listener: listener, once: once });
    return this;
  }

  /**
   * Adds a listener function to the specified event.
   * @param {String} type
   * @param {Function} listener
   * @return {Object} Current instance of MicroEmitter for chaining.
   */
  addListener(type, listener) {
    return this._addListener(type, listener, false);
  }

  /* Alias of addListener */
  on(type, listener) {
    return this.addListener(type, listener);
  }

  addOnceListener(type, listener) {
    return this._addListener(type, listener, true);
  }

  /* Alias of addOnceListener */
  once(type, listener) {
    return this.addOnceListener(type, listener);
  }

  /**
   * Removes a listener function to the specified event.
   * @param {String} type
   * @param {Function} listener
   * @return {Object} Current instance of MicroEmitter for chaining.
   */
  removeListener(type, listener) { // alias
    let isRemoved = false;

    if (!this._listeners[type].length) return this;
    if (!listener) {
      delete this._listeners[type];
      return this;
    }
    for (let index = 0; index < this._listeners[type].length; index++) {
      if (this._listeners[type][index].listener === listener) {
        this._listeners[type].splice(index, 1);
        index--;
        isRemoved = true;
      }
    }
    if (!isRemoved) console.warn('not registered this listener.');
    return this;
  }

  /* Alias of removeListener */
  off(type, listener) {
    return this.removeListener(type, listener);
  }

  /**
   * Emits an specified event.
   * @param {String} type
   * @param {Object} payload
   * @return {Object} Current instance of MicroEmitter for chaining.
   */
  emit(type, payload) {
    if (!this._listeners[type]) return this;
    for (let index = 0; index < this._listeners[type].length; index++) {
      this._listeners[type][index].listener.apply(this, [payload]);
      if (this._listeners[type][index].once) {
        this.removeListener(type, this._listeners[type][index].listener);
        index--;
      }
    }
    return this;
  }

  /* Alias of emit */
  trigger(type, payload) {
    return this.emit(type, payload);
  }
}
<commit_msg>Refactor removeEventListener<commit_after>export default class MicroEmitter {
  constructor() {
    this._listeners = {};
  }

  /**
   * Adds a listener function to the specified event.
   * @param {String} type
   * @param {Function} listener
   * @param {Boolean} once
   */
  _addListener(type, listener, once) {
    this._listeners[type] = this._listeners[type] || [];
    this._listeners[type].push({ listener: listener, once: once });
    return this;
  }

  /**
   * Adds a listener function to the specified event.
   * @param {String} type
   * @param {Function} listener
   * @return {Object} Current instance of MicroEmitter for chaining.
   */
  addListener(type, listener) {
    return this._addListener(type, listener, false);
  }

  /* Alias of addListener */
  on(type, listener) {
    return this.addListener(type, listener);
  }

  addOnceListener(type, listener) {
    return this._addListener(type, listener, true);
  }

  /* Alias of addOnceListener */
  once(type, listener) {
    return this.addOnceListener(type, listener);
  }

  /**
   * Removes a listener function to the specified event.
   * @param {String} type
   * @param {Function} listener
   * @return {Object} Current instance of MicroEmitter for chaining.
   */
  removeListener(type, listener) { // alias
    let isRemoved = false;

    if (!this._listeners[type].length) return this;
    if (!listener) {
      delete this._listeners[type];
      return this;
    }
    this._listeners[type] = this._listeners[type].filter((_listener) => {
      if (_listener.listener === listener) {
        isRemoved = true;
        return false;
      } else {
        return true;
      }
    });
    if (!isRemoved) console.warn('not registered this listener.');
    return this;
  }

  /* Alias of removeListener */
  off(type, listener) {
    return this.removeListener(type, listener);
  }

  /**
   * Emits an specified event.
   * @param {String} type
   * @param {Object} payload
   * @return {Object} Current instance of MicroEmitter for chaining.
   */
  emit(type, payload) {
    if (!this._listeners[type]) return this;
    for (let index = 0; index < this._listeners[type].length; index++) {
      this._listeners[type][index].listener.apply(this, [payload]);
      if (this._listeners[type][index].once) {
        this.removeListener(type, this._listeners[type][index].listener);
        index--;
      }
    }
    return this;
  }

  /* Alias of emit */
  trigger(type, payload) {
    return this.emit(type, payload);
  }
}
<|endoftext|>


<commit_before>import PropTypes from 'proptypes';
import { render as preactRender, cloneElement as preactCloneElement, h, Component as PreactComponent, options } from 'preact';

const version = '15.1.0'; // trick libraries to think we are react

const ELEMENTS = 'a abbr address area article aside audio b base bdi bdo big blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td textarea tfoot th thead time title tr track u ul var video wbr circle clipPath defs ellipse g image line linearGradient mask path pattern polygon polyline radialGradient rect stop svg text tspan'.split(' ');

const REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) || 0xeac7;

// don't autobind these methods since they already have guaranteed context.
const AUTOBIND_BLACKLIST = {
	constructor: 1,
	render: 1,
	shouldComponentUpdate: 1,
	componentWillReceiveProps: 1,
	componentWillUpdate: 1,
	componentDidUpdate: 1,
	componentWillMount: 1,
	componentDidMount: 1,
	componentWillUnmount: 1,
	componentDidUnmount: 1
};

const BYPASS_HOOK = {};

/*global process*/
const DEV = typeof process!=='undefined' && process.env && process.env.NODE_ENV!=='production';

// a component that renders nothing. Used to replace components for unmountComponentAtNode.
const EmptyComponent = () => null;

// make react think we're react.
let VNode = h('').constructor;
VNode.prototype.$$typeof = REACT_ELEMENT_TYPE;

Object.defineProperty(VNode.prototype, 'type', {
	get() { return this.nodeName; },
	set(v) { this.nodeName = v; },
	configurable:true
});

Object.defineProperty(VNode.prototype, 'props', {
	get() { return this.attributes; },
	set(v) { this.attributes = v; },
	configurable:true
});

let oldVnodeHook = options.vnode || EmptyComponent;
options.vnode = vnode => {
	let a = vnode.attributes;
	if (!a) a = vnode.attributes = {};
	// clone if needed (fixes #105):
	if (Object.isExtensible && !Object.isExtensible(a)) {
		a = extend({}, a, true);
	}
	a.children = vnode.children;
	oldVnodeHook(vnode);
};

// proxy render() since React returns a Component reference.
function render(vnode, parent, callback) {
	let prev = parent._preactCompatRendered;
	if (prev && prev.parentNode!==parent) prev = null;
	let out = preactRender(vnode, parent, prev);
	parent._preactCompatRendered = out;
	if (typeof callback==='function') callback();
	return out && out._component || out.base;
}

class ContextProvider {
	getChildContext() {
		return this.props.context;
	}
	render(props) {
		return props.children[0];
	}
}

function renderSubtreeIntoContainer(parentComponent, vnode, container, callback) {
	let c = render((
		<ContextProvider context={parentComponent.context}>
			{vnode}
		</ContextProvider>
	), container);
	if (callback) callback(c);
	return c;
}

function unmountComponentAtNode(container) {
	let existing = container._preactCompatRendered;
	if (existing && existing.parentNode===container) {
		preactRender(h(EmptyComponent), container, existing);
		return true;
	}
	return false;
}

const ARR = [];

// This API is completely unnecessary for Preact, so it's basically passthrough.
let Children = {
	map(children, fn, ctx) {
		children = Children.toArray(children);
		if (ctx && ctx!==children) fn = fn.bind(ctx);
		return children.map(fn);
	},
	forEach(children, fn, ctx) {
		children = Children.toArray(children);
		if (ctx && ctx!==children) fn = fn.bind(ctx);
		children.forEach(fn);
	},
	count(children) {
		children = Children.toArray(children);
		return children.length;
	},
	only(children) {
		children = Children.toArray(children);
		if (children.length!==1) throw new Error('Children.only() expects only one child.');
		return children[0];
	},
	toArray(children) {
		return Array.isArray && Array.isArray(children) ? children : ARR.concat(children);
	}
};

/** Track current render() component for ref assignment */
let currentComponent;

function createFactory(type) {
	return createElement.bind(null, type);
}

let DOM = {};
for (let i=ELEMENTS.length; i--; ) {
	DOM[ELEMENTS[i]] = createFactory(ELEMENTS[i]);
}

function isStatelessComponent(c) {
	return typeof c === 'function' && !(c.prototype instanceof Component);
}

const STATELESS_WRAPPER_KEY = typeof Symbol!=='undefined' ? Symbol.for('__preactCompatStatelessWrapper') : '__preactCompatStatelessWrapper';

// wraps stateless functional components in a PropTypes validator
function statelessComponentHook(WrappedComponent) {
	// cache wrapper creation
	if (WrappedComponent[STATELESS_WRAPPER_KEY]) {
		return WrappedComponent[STATELESS_WRAPPER_KEY];
	}

	function StatelessComponent(props, context) {
		propsHook.call(WrappedComponent, props, context);
		return WrappedComponent(props, context);
	}

	StatelessComponent.displayName = WrappedComponent.displayName;
	StatelessComponent.propTypes = WrappedComponent.propTypes;
	StatelessComponent.defaultProps = WrappedComponent.defaultProps;

	Object.defineProperty(WrappedComponent, STATELESS_WRAPPER_KEY, { configurable:true, value:StatelessComponent });

	return StatelessComponent;
}

function createElement(...args) {
	let vnode = h(...args);

	applyClassName(vnode);

	if (isStatelessComponent(vnode.nodeName)) {
		vnode.nodeName = statelessComponentHook(vnode.nodeName);
	}

	let ref = vnode.attributes && vnode.attributes.ref,
		type = ref && typeof ref;
	if (currentComponent && (type==='string' || type==='number')) {
		vnode.attributes.ref = createStringRefProxy(ref, currentComponent);
	}

	return vnode;
}

function cloneElement(element, props, ...children) {
	let node = h(
		element.nodeName || element.type,
		element.attributes || element.props,
		element.children || element.props.children
	);
	return preactCloneElement(node, props, ...children);
}

function isValidElement(element) {
	return element && ((element instanceof VNode) || element.$$typeof===REACT_ELEMENT_TYPE);
}

function createStringRefProxy(name, component) {
	return component._refProxies[name] || (component._refProxies[name] = resolved => {
		if (component && component.refs) {
			component.refs[name] = resolved;
			if (resolved===null) {
				delete component._refProxies[name];
				component = null;
			}
		}
	});
}

function applyClassName({ attributes }) {
	if (!attributes) return;
	let cl = attributes.className || attributes.class;
	if (cl) attributes.className = cl;
}

function extend(base, props, all) {
	for (let key in props) {
		if (all===true || props[key]!=null) {
			base[key] = props[key];
		}
	}
	return base;
}

let findDOMNode = component => component && component.base || component;

function F(){}

function createClass(obj) {
	let mixins = obj.mixins && collateMixins(obj.mixins);

	function cl(props, context) {
		extend(this, obj);
		if (mixins) applyMixins(this, mixins);
		Component.call(this, props, context, BYPASS_HOOK);
		bindAll(this);
		newComponentHook.call(this, props, context);
	}

	if (obj.statics) {
		extend(cl, obj.statics);
	}
	if (obj.propTypes) {
		cl.propTypes = obj.propTypes;
	}
	if (obj.defaultProps) {
		cl.defaultProps = obj.defaultProps;
	}
	if (obj.getDefaultProps) {
		cl.defaultProps = obj.getDefaultProps();
	}

	F.prototype = Component.prototype;
	cl.prototype = new F();
	cl.prototype.constructor = cl;

	cl.displayName = obj.displayName || 'Component';

	return cl;
}

// Flatten an Array of mixins to a map of method name to mixin implementations
function collateMixins(mixins) {
	let keyed = {};
	for (let i=0; i<mixins.length; i++) {
		let mixin = mixins[i];
		for (let key in mixin) {
			if (mixin.hasOwnProperty(key) && typeof mixin[key]==='function') {
				(keyed[key] || (keyed[key]=[])).push(mixin[key]);
			}
		}
	}
	return keyed;
}

// apply a mapping of Arrays of mixin methods to a component instance
function applyMixins(inst, mixins) {
	for (let key in mixins) if (mixins.hasOwnProperty(key)) {
		inst[key] = multihook(...mixins[key].concat(inst[key] || key));
	}
}

function bindAll(ctx) {
	for (let i in ctx) {
		let v = ctx[i];
		if (typeof v==='function' && !v.__bound && !AUTOBIND_BLACKLIST.hasOwnProperty(i)) {
			(ctx[i] = v.bind(ctx)).__bound = true;
		}
	}
}

function callMethod(ctx, m, args) {
	if (typeof m==='string') {
		m = ctx.constructor.prototype[m];
	}
	if (typeof m==='function') {
		return m.apply(ctx, args);
	}
}

function multihook() {
	let hooks = arguments;
	return function() {
		let ret;
		for (let i=0; i<hooks.length; i++) {
			let r = callMethod(this, hooks[i], arguments);
			if (r!==undefined) ret = r;
		}
		return ret;
	};
}

function newComponentHook(props, context) {
	propsHook.call(this, props, context);
	this.componentWillReceiveProps = multihook(this.componentWillReceiveProps || 'componentWillReceiveProps', propsHook);
	this.render = multihook(beforeRender, this.render || 'render', afterRender);
}

function propsHook(props, context) {
	if (!props) return;

	// React annoyingly special-cases single children, and some react components are ridiculously strict about this.
	let c = props.children;
	if (c && c.length===1) {
		props.children = c[0];

		// but its totally still going to be an Array.
		if (props.children && typeof props.children==='object') {
			props.children.length = 1;
			props.children[0] = props.children;
		}
	}

	// add proptype checking
	if (DEV) {
		let ctor = typeof this==='function' ? this : this.constructor,
			propTypes = this.propTypes || ctor.propTypes;
		if (propTypes) {
			for (let prop in propTypes) {
				if (propTypes.hasOwnProperty(prop) && typeof propTypes[prop]==='function') {
					const displayName = this.displayName || ctor.name;
					let err = propTypes[prop](props, prop, displayName, 'prop');
					if (err) console.error(new Error(err.message || err));
				}
			}
		}
	}
}

function beforeRender() {
	currentComponent = this;
}

function afterRender() {
	if (currentComponent===this) {
		currentComponent = null;
	}
}

function Component(props, context, opts) {
	PreactComponent.call(this, props, context);
	this.refs = {};
	this._refProxies = {};
	if (opts!==BYPASS_HOOK) {
		newComponentHook.call(this, props, context);
	}
}
Component.prototype = new PreactComponent();
extend(Component.prototype, {
	constructor: Component,

	isReactComponent: {},

	getDOMNode() {
		return this.base;
	},

	isMounted() {
		return !!this.base;
	}
});

export {
	version,
	DOM,
	PropTypes,
	Children,
	render,
	createClass,
	createFactory,
	createElement,
	cloneElement,
	isValidElement,
	findDOMNode,
	unmountComponentAtNode,
	Component,
	renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer
};

export default {
	version,
	DOM,
	PropTypes,
	Children,
	render,
	createClass,
	createFactory,
	createElement,
	cloneElement,
	isValidElement,
	findDOMNode,
	unmountComponentAtNode,
	Component,
	unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer
};
<commit_msg>Patch in support for consuming VNodes cloned/modified via `extend()` (shallow clone)<commit_after>import PropTypes from 'proptypes';
import { render as preactRender, cloneElement as preactCloneElement, h, Component as PreactComponent, options } from 'preact';

const version = '15.1.0'; // trick libraries to think we are react

const ELEMENTS = 'a abbr address area article aside audio b base bdi bdo big blockquote body br button canvas caption cite code col colgroup data datalist dd del details dfn dialog div dl dt em embed fieldset figcaption figure footer form h1 h2 h3 h4 h5 h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link main map mark menu menuitem meta meter nav noscript object ol optgroup option output p param picture pre progress q rp rt ruby s samp script section select small source span strong style sub summary sup table tbody td textarea tfoot th thead time title tr track u ul var video wbr circle clipPath defs ellipse g image line linearGradient mask path pattern polygon polyline radialGradient rect stop svg text tspan'.split(' ');

const REACT_ELEMENT_TYPE = (typeof Symbol === 'function' && Symbol.for && Symbol.for('react.element')) || 0xeac7;

// don't autobind these methods since they already have guaranteed context.
const AUTOBIND_BLACKLIST = {
	constructor: 1,
	render: 1,
	shouldComponentUpdate: 1,
	componentWillReceiveProps: 1,
	componentWillUpdate: 1,
	componentDidUpdate: 1,
	componentWillMount: 1,
	componentDidMount: 1,
	componentWillUnmount: 1,
	componentDidUnmount: 1
};

const BYPASS_HOOK = {};

/*global process*/
const DEV = typeof process!=='undefined' && process.env && process.env.NODE_ENV!=='production';

// a component that renders nothing. Used to replace components for unmountComponentAtNode.
const EmptyComponent = () => null;

// make react think we're react.
let VNode = h('').constructor;
VNode.prototype.$$typeof = REACT_ELEMENT_TYPE;

Object.defineProperty(VNode.prototype, 'type', {
	get() { return this.nodeName; },
	set(v) { this.nodeName = v; },
	configurable:true
});

Object.defineProperty(VNode.prototype, 'props', {
	get() { return this.attributes; },
	set(v) { this.attributes = v; },
	configurable:true
});

let oldVnodeHook = options.vnode || EmptyComponent;
options.vnode = vnode => {
	let a = vnode.attributes;
	if (!a) a = vnode.attributes = {};
	// clone if needed (fixes #105):
	if (Object.isExtensible && !Object.isExtensible(a)) {
		a = extend({}, a, true);
	}
	a.children = vnode.children;
	oldVnodeHook(vnode);
};

// proxy render() since React returns a Component reference.
function render(vnode, parent, callback) {
	let prev = parent._preactCompatRendered;
	if (prev && prev.parentNode!==parent) prev = null;
	let out = preactRender(vnode, parent, prev);
	parent._preactCompatRendered = out;
	if (typeof callback==='function') callback();
	return out && out._component || out.base;
}

class ContextProvider {
	getChildContext() {
		return this.props.context;
	}
	render(props) {
		return props.children[0];
	}
}

function renderSubtreeIntoContainer(parentComponent, vnode, container, callback) {
	let c = render((
		<ContextProvider context={parentComponent.context}>
			{vnode}
		</ContextProvider>
	), container);
	if (callback) callback(c);
	return c;
}

function unmountComponentAtNode(container) {
	let existing = container._preactCompatRendered;
	if (existing && existing.parentNode===container) {
		preactRender(h(EmptyComponent), container, existing);
		return true;
	}
	return false;
}

const ARR = [];

// This API is completely unnecessary for Preact, so it's basically passthrough.
let Children = {
	map(children, fn, ctx) {
		children = Children.toArray(children);
		if (ctx && ctx!==children) fn = fn.bind(ctx);
		return children.map(fn);
	},
	forEach(children, fn, ctx) {
		children = Children.toArray(children);
		if (ctx && ctx!==children) fn = fn.bind(ctx);
		children.forEach(fn);
	},
	count(children) {
		children = Children.toArray(children);
		return children.length;
	},
	only(children) {
		children = Children.toArray(children);
		if (children.length!==1) throw new Error('Children.only() expects only one child.');
		return children[0];
	},
	toArray(children) {
		return Array.isArray && Array.isArray(children) ? children : ARR.concat(children);
	}
};

/** Track current render() component for ref assignment */
let currentComponent;

function createFactory(type) {
	return createElement.bind(null, type);
}

let DOM = {};
for (let i=ELEMENTS.length; i--; ) {
	DOM[ELEMENTS[i]] = createFactory(ELEMENTS[i]);
}

function upgradeToVNodes(arr, offset) {
	for (let i=offset || 0; i<arr.length; i++) {
		let obj = arr[i];
		if (Array.isArray(obj)) {
			upgradeToVNodes(obj);
		}
		else if (obj && typeof obj==='object' && !isValidElement(obj) && ((obj.props && obj.type) || (obj.attributes && obj.nodeName) || obj.children)) {
			arr[i] = createElement(obj.type || obj.nodeName, obj.props || obj.attributes, obj.children);
		}
	}
}

function isStatelessComponent(c) {
	return typeof c === 'function' && !(c.prototype instanceof Component);
}

const STATELESS_WRAPPER_KEY = typeof Symbol!=='undefined' ? Symbol.for('__preactCompatStatelessWrapper') : '__preactCompatStatelessWrapper';

// wraps stateless functional components in a PropTypes validator
function statelessComponentHook(WrappedComponent) {
	// cache wrapper creation
	if (WrappedComponent[STATELESS_WRAPPER_KEY]) {
		return WrappedComponent[STATELESS_WRAPPER_KEY];
	}

	function StatelessComponent(props, context) {
		propsHook.call(WrappedComponent, props, context);
		return WrappedComponent(props, context);
	}

	StatelessComponent.displayName = WrappedComponent.displayName;
	StatelessComponent.propTypes = WrappedComponent.propTypes;
	StatelessComponent.defaultProps = WrappedComponent.defaultProps;

	Object.defineProperty(WrappedComponent, STATELESS_WRAPPER_KEY, { configurable:true, value:StatelessComponent });

	return StatelessComponent;
}

function createElement(...args) {
	upgradeToVNodes(args, 2);
	let vnode = h(...args);

	applyClassName(vnode);

	if (isStatelessComponent(vnode.nodeName)) {
		vnode.nodeName = statelessComponentHook(vnode.nodeName);
	}

	let ref = vnode.attributes && vnode.attributes.ref,
		type = ref && typeof ref;
	if (currentComponent && (type==='string' || type==='number')) {
		vnode.attributes.ref = createStringRefProxy(ref, currentComponent);
	}

	return vnode;
}

function cloneElement(element, props, ...children) {
	let node = h(
		element.nodeName || element.type,
		element.attributes || element.props,
		element.children || element.props.children
	);
	return preactCloneElement(node, props, ...children);
}

function isValidElement(element) {
	return element && ((element instanceof VNode) || element.$$typeof===REACT_ELEMENT_TYPE);
}

function createStringRefProxy(name, component) {
	return component._refProxies[name] || (component._refProxies[name] = resolved => {
		if (component && component.refs) {
			component.refs[name] = resolved;
			if (resolved===null) {
				delete component._refProxies[name];
				component = null;
			}
		}
	});
}

function applyClassName({ attributes }) {
	if (!attributes) return;
	let cl = attributes.className || attributes.class;
	if (cl) attributes.className = cl;
}

function extend(base, props, all) {
	for (let key in props) {
		if (all===true || props[key]!=null) {
			base[key] = props[key];
		}
	}
	return base;
}

let findDOMNode = component => component && component.base || component;

function F(){}

function createClass(obj) {
	let mixins = obj.mixins && collateMixins(obj.mixins);

	function cl(props, context) {
		extend(this, obj);
		if (mixins) applyMixins(this, mixins);
		Component.call(this, props, context, BYPASS_HOOK);
		bindAll(this);
		newComponentHook.call(this, props, context);
	}

	if (obj.statics) {
		extend(cl, obj.statics);
	}
	if (obj.propTypes) {
		cl.propTypes = obj.propTypes;
	}
	if (obj.defaultProps) {
		cl.defaultProps = obj.defaultProps;
	}
	if (obj.getDefaultProps) {
		cl.defaultProps = obj.getDefaultProps();
	}

	F.prototype = Component.prototype;
	cl.prototype = new F();
	cl.prototype.constructor = cl;

	cl.displayName = obj.displayName || 'Component';

	return cl;
}

// Flatten an Array of mixins to a map of method name to mixin implementations
function collateMixins(mixins) {
	let keyed = {};
	for (let i=0; i<mixins.length; i++) {
		let mixin = mixins[i];
		for (let key in mixin) {
			if (mixin.hasOwnProperty(key) && typeof mixin[key]==='function') {
				(keyed[key] || (keyed[key]=[])).push(mixin[key]);
			}
		}
	}
	return keyed;
}

// apply a mapping of Arrays of mixin methods to a component instance
function applyMixins(inst, mixins) {
	for (let key in mixins) if (mixins.hasOwnProperty(key)) {
		inst[key] = multihook(...mixins[key].concat(inst[key] || key));
	}
}

function bindAll(ctx) {
	for (let i in ctx) {
		let v = ctx[i];
		if (typeof v==='function' && !v.__bound && !AUTOBIND_BLACKLIST.hasOwnProperty(i)) {
			(ctx[i] = v.bind(ctx)).__bound = true;
		}
	}
}

function callMethod(ctx, m, args) {
	if (typeof m==='string') {
		m = ctx.constructor.prototype[m];
	}
	if (typeof m==='function') {
		return m.apply(ctx, args);
	}
}

function multihook() {
	let hooks = arguments;
	return function() {
		let ret;
		for (let i=0; i<hooks.length; i++) {
			let r = callMethod(this, hooks[i], arguments);
			if (r!==undefined) ret = r;
		}
		return ret;
	};
}

function newComponentHook(props, context) {
	propsHook.call(this, props, context);
	this.componentWillReceiveProps = multihook(this.componentWillReceiveProps || 'componentWillReceiveProps', propsHook);
	this.render = multihook(beforeRender, this.render || 'render', afterRender);
}

function propsHook(props, context) {
	if (!props) return;

	// React annoyingly special-cases single children, and some react components are ridiculously strict about this.
	let c = props.children;
	if (c && c.length===1) {
		props.children = c[0];

		// but its totally still going to be an Array.
		if (props.children && typeof props.children==='object') {
			props.children.length = 1;
			props.children[0] = props.children;
		}
	}

	// add proptype checking
	if (DEV) {
		let ctor = typeof this==='function' ? this : this.constructor,
			propTypes = this.propTypes || ctor.propTypes;
		if (propTypes) {
			for (let prop in propTypes) {
				if (propTypes.hasOwnProperty(prop) && typeof propTypes[prop]==='function') {
					const displayName = this.displayName || ctor.name;
					let err = propTypes[prop](props, prop, displayName, 'prop');
					if (err) console.error(new Error(err.message || err));
				}
			}
		}
	}
}

function beforeRender() {
	currentComponent = this;
}

function afterRender() {
	if (currentComponent===this) {
		currentComponent = null;
	}
}

function Component(props, context, opts) {
	PreactComponent.call(this, props, context);
	this.refs = {};
	this._refProxies = {};
	if (opts!==BYPASS_HOOK) {
		newComponentHook.call(this, props, context);
	}
}
Component.prototype = new PreactComponent();
extend(Component.prototype, {
	constructor: Component,

	isReactComponent: {},

	getDOMNode() {
		return this.base;
	},

	isMounted() {
		return !!this.base;
	}
});

export {
	version,
	DOM,
	PropTypes,
	Children,
	render,
	createClass,
	createFactory,
	createElement,
	cloneElement,
	isValidElement,
	findDOMNode,
	unmountComponentAtNode,
	Component,
	renderSubtreeIntoContainer as unstable_renderSubtreeIntoContainer
};

export default {
	version,
	DOM,
	PropTypes,
	Children,
	render,
	createClass,
	createFactory,
	createElement,
	cloneElement,
	isValidElement,
	findDOMNode,
	unmountComponentAtNode,
	Component,
	unstable_renderSubtreeIntoContainer: renderSubtreeIntoContainer
};
<|endoftext|>


<commit_before>import React from 'react';
import arrayFindIndex from 'array-find-index';
import classNames from 'classnames';

import './index.scss';

const log = console.log.bind(console);
const logError = console.error ? console.error.bind(console) : log;
const logWarning = console.warn ? console.warn.bind(console) : log;

/* converts given number of seconds to standard time display format
 * http://goo.gl/kEvnKn
 */
function convertToTime (number) {
  const mins = Math.floor(number / 60);
  const secs = (number % 60).toFixed();
  return `${ mins < 10 ? '0' : '' }${ mins }:${ secs < 10 ? '0' : '' }${ secs }`;
}

/*
 * AudioPlayer
 *
 * Accepts 'playlist' prop of the form:
 *
 * [{ "url": "./path/to/file.mp3",
 *    "displayText": "ArtistA - Track 1" },
 *  { "url": "https://domain.com/track2.ogg",
 *    "displayText": "ArtistB - Track 2" }]
 *
 * Accepts 'autoplay' prop (true/[false]).
 *
 * Accepts 'autoplayDelayInSeconds' prop (default 0).
 *
 * Accepts 'gapLengthInSeconds' prop (default 0).
 * Specifies gap at end of one track before next
 * track begins (ignored for manual skip).
 *
 * Accepts 'hideBackSkip' prop (default false,
 * hides back skip button if true).
 *
 * Accepts 'hideForwardSkip' prop (default false,
 * hides forward skip button if true).
 *
 * Accepts 'disableSeek' prop (default false,
 * disables seeking through the audio if true).
 *
 * Accepts 'cycle' prop (default true,
 * starts playing at the beginning of the playlist
 * when finished if true).
 *
 * Accepts 'stayOnBackSkipThreshold' prop, default 5,
 * is number of seconds to progress until pressing back skip
 * restarts the current track.
 *
 * Accepts 'style' prop, object, is applied to
 * outermost div (React styles).
 *
 * Accepts 'onMediaEvent' prop, an object used for
 * listening to media events on the underlying audio element.
 *
 * Accepts 'audioElementRef' prop, a function called after
 * the component mounts and before it unmounts with the
 * internally-referenced HTML audio element as its only parameter.
 * Similar to: https://facebook.github.io/react/docs/refs-and-the-dom.html
 */
class AudioPlayer extends React.Component {

  constructor (props) {
    super(props);

    /* true if the user is currently dragging the mouse
     * to seek a new track position
     */
    this.seekInProgress = false;
    // index matching requested track (whether track has loaded or not)
    this.currentTrackIndex = 0;

    this.defaultState = {
      /* activeTrackIndex will change to match
       * this.currentTrackIndex once metadata has loaded
       */
      activeTrackIndex: -1,
      // indicates whether audio player should be paused
      paused: true,
      /* elapsed time for current track, in seconds -
       * DISPLAY ONLY! the actual elapsed time may
       * not match up if we're currently seeking, since
       * the new time is visually previewed before the
       * audio seeks.
       */
      displayedTime: 0
    };

    this.state = this.defaultState;

    // html audio element used for playback
    this.audio = null;
    this.audioProgressContainer = null;
    /* bounding rectangle used for calculating seek
     * position from mouse/touch coordinates
     */
    this.audioProgressBoundingRect = null;

    // event listeners to add on mount and remove on unmount
    this.seekReleaseListener = e => this.seek(e);
    this.resizeListener = () => this.fetchAudioProgressBoundingRect();
    this.audioPlayListener = () => this.setState({ paused: false });
    this.audioPauseListener = () => this.setState({ paused: true });
    this.audioEndListener = () => {
      const gapLengthInSeconds = this.props.gapLengthInSeconds || 0;
      setTimeout(() => this.skipToNextTrack(), gapLengthInSeconds * 1000);
    };
    this.audioStallListener = () => this.togglePause(true);
    this.audioTimeUpdateListener = () => this.handleTimeUpdate();
    this.audioMetadataLoadedListener = () => this.setState({
      activeTrackIndex: this.currentTrackIndex
    });
  }

  componentDidMount () {
    // add event listeners bound outside the scope of our component
    window.addEventListener('mouseup', this.seekReleaseListener);
    document.addEventListener('touchend', this.seekReleaseListener);
    window.addEventListener('resize', this.resizeListener);
    this.resizeListener();

    const audio = this.audio = document.createElement('audio');

    // add event listeners on the audio element
    audio.preload = 'metadata';
    audio.addEventListener('play', this.audioPlayListener);
    audio.addEventListener('pause', this.audioPauseListener);
    audio.addEventListener('ended', this.audioEndListener);
    audio.addEventListener('stalled', this.audioStallListener);
    audio.addEventListener('timeupdate', this.audioTimeUpdateListener);
    audio.addEventListener('loadedmetadata', this.audioMetadataLoadedListener);
    this.addMediaEventListeners(this.props.onMediaEvent);

    if (this.props.playlist && this.props.playlist.length) {
      this.updateSource();
      if (this.props.autoplay) {
        const delay = this.props.autoplayDelayInSeconds || 0;
        setTimeout(() => this.togglePause(false), delay * 1000);
      }
    }

    if (this.props.audioElementRef) {
      this.props.audioElementRef(audio);
    }
  }

  componentWillUnmount () {
    // remove event listeners bound outside the scope of our component
    window.removeEventListener('mouseup', this.seekReleaseListener);
    document.removeEventListener('touchend', this.seekReleaseListener);
    window.removeEventListener('resize', this.resizeListener);

    // remove event listeners on the audio element
    this.audio.removeEventListener('play', this.audioPlayListener);
    this.audio.removeEventListener('pause', this.audioPauseListener);
    this.audio.removeEventListener('ended', this.audioEndListener);
    this.audio.removeEventListener('stalled', this.audioStallListener);
    this.audio.removeEventListener('timeupdate', this.audioTimeUpdateListener);
    this.audio.removeEventListener('loadedmetadata', this.audioMetadataLoadedListener);
    this.removeMediaEventListeners(this.props.onMediaEvent);

    // pause the audio element before we unmount
    this.audio.pause();

    if (this.props.audioElementRef) {
      this.props.audioElementRef(this.audio);
    }
  }

  componentWillReceiveProps (nextProps) {
    // Update media event listeners that may have changed
    this.removeMediaEventListeners(this.props.onMediaEvent);
    this.addMediaEventListeners(nextProps.onMediaEvent);

    const newPlaylist = nextProps.playlist;
    if (!newPlaylist || !newPlaylist.length) {
      if (this.audio) {
        this.audio.src = '';
      }
      this.currentTrackIndex = 0;
      return this.setState(this.defaultState);
    }

    const oldPlaylist = this.props.playlist;

    const currentTrackUrl = ((oldPlaylist || [])[this.currentTrackIndex] || {}).url;
    this.currentTrackIndex = arrayFindIndex(newPlaylist, track => {
      return track.url && currentTrackUrl === track.url;
    });
    /* if the track we're already playing is in the new playlist, update the
     * activeTrackIndex.
     */
    if (this.currentTrackIndex !== -1) {
      this.setState({
        activeTrackIndex: this.currentTrackIndex
      });
    }
  }

  addMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.addEventListener(type, mediaEvents[type]);
    });
  }

  removeMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.removeEventListener(type, mediaEvents[type]);
    });
  }

  componentDidUpdate () {
    /* if we loaded a new playlist and reset the current track marker, we
     * should load up the first one.
     */
    if (this.audio && this.currentTrackIndex === -1) {
      this.skipToNextTrack(false);
    }
  }

  togglePause (value) {
    if (!this.audio) {
      return;
    }
    const pause = typeof value === 'boolean' ? value : !this.state.paused;
    if (pause) {
      return this.audio.pause();
    }
    if (!this.props.playlist || !this.props.playlist.length) {
      return;
    }
    try {
      this.audio.play();
    } catch (error) {
      logError(error);
      const warningMessage =
        'Audio playback failed at ' +
        new Date().toLocaleTimeString() +
        '! (Perhaps autoplay is disabled in this browser.)';
      logWarning(warningMessage);
    }
  }

  skipToNextTrack (shouldPlay) {
    if (!this.audio) {
      return;
    }
    this.audio.pause();
    if (!this.props.playlist || !this.props.playlist.length) {
      return;
    }
    let i = this.currentTrackIndex + 1;
    if (i >= this.props.playlist.length) {
      i = 0;
    }
    this.currentTrackIndex = i;
    this.setState({
      activeTrackIndex: -1,
      displayedTime: 0
    }, () => {
      this.updateSource();
      const shouldPauseOnCycle = (!this.props.cycle && i === 0);
      const shouldPause = shouldPauseOnCycle || (typeof shouldPlay === 'boolean' ? !shouldPlay : false);
      this.togglePause(shouldPause);
    });
  }

  backSkip () {
    if (!this.props.playlist || !this.props.playlist.length) {
      return;
    }
    const audio = this.audio;
    let stayOnBackSkipThreshold = this.props.stayOnBackSkipThreshold;
    if (isNaN(stayOnBackSkipThreshold)) {
      stayOnBackSkipThreshold = 5;
    }
    if (audio.currentTime >= stayOnBackSkipThreshold) {
      return audio.currentTime = 0;
    }
    let i = this.currentTrackIndex - 1;
    if (i < 0) {
      i = this.props.playlist.length - 1;
    }
    this.currentTrackIndex = i - 1;
    this.skipToNextTrack();
  }

  updateSource () {
    this.audio.src = this.props.playlist[this.currentTrackIndex].url;
  }

  fetchAudioProgressBoundingRect () {
    this.audioProgressBoundingRect = this.audioProgressContainer.getBoundingClientRect();
  }

  handleTimeUpdate () {
    if (!this.seekInProgress && this.audio) {
      this.setState({
        displayedTime: this.audio.currentTime
      });
    }
  }

  adjustDisplayedTime (event) {
    if (!this.props.playlist || !this.props.playlist.length || this.props.disableSeek) {
      return;
    }
    // make sure we don't select stuff in the background while seeking
    if (event.type === 'mousedown' || event.type === 'touchstart') {
      this.seekInProgress = true;
      document.body.classList.add('noselect');
    } else if (!this.seekInProgress) {
      return;
    }
    /* we don't want mouse handlers to receive the event
     * after touch handlers if we're seeking.
     */
    event.preventDefault();
    const boundingRect = this.audioProgressBoundingRect;
    const isTouch = event.type.slice(0, 5) === 'touch';
    const pageX = isTouch ? event.targetTouches.item(0).pageX : event.pageX;
    const position = pageX - boundingRect.left - document.body.scrollLeft;
    const containerWidth = boundingRect.width;
    const progressPercentage = position / containerWidth;
    this.setState({
      displayedTime: progressPercentage * this.audio.duration
    });
  }

  seek (event) {
    /* this function is activated when the user lets
     * go of the mouse, so if .noselect was applied
     * to the document body, get rid of it.
     */
    document.body.classList.remove('noselect');
    if (!this.seekInProgress) {
      return;
    }
    /* we don't want mouse handlers to receive the event
     * after touch handlers if we're seeking.
     */
    event.preventDefault();
    this.seekInProgress = false;
    const displayedTime = this.state.displayedTime;
    if (isNaN(displayedTime)) {
      return;
    }
    this.audio.currentTime = displayedTime;
  }

  render () {
    const activeIndex = this.state.activeTrackIndex;
    const displayText = this.props.playlist ? (
      activeIndex < 0 ? null : this.props.playlist[activeIndex].displayText
    ) : 'Please load a playlist';

    const displayedTime = this.state.displayedTime;
    const duration = this.audio && this.audio.duration || 0;

    const elapsedTime = convertToTime(displayedTime);
    const fullTime = convertToTime(duration);
    const timeRatio = `${ elapsedTime } / ${ fullTime }`;

    const progressBarWidth = `${ (displayedTime / duration) * 100 }%`;

    const adjustDisplayedTime = e => this.adjustDisplayedTime(e);

    return (

 this.backSkip()}
          >

 this.togglePause()}
          >

 this.skipToNextTrack()}
          >

 this.audioProgressContainer = ref}
          onMouseDown={adjustDisplayedTime}
          onMouseMove={adjustDisplayedTime}
          onTouchStart={adjustDisplayedTime}
          onTouchMove={adjustDisplayedTime}
        >

                {displayText}

              {timeRatio}

    );
  }

}

AudioPlayer.propTypes = {
  playlist: React.PropTypes.array,
  autoplay: React.PropTypes.bool,
  autoplayDelayInSeconds: React.PropTypes.number,
  gapLengthInSeconds: React.PropTypes.number,
  hideBackSkip: React.PropTypes.bool,
  hideForwardSkip: React.PropTypes.bool,
  cycle: React.PropTypes.bool,
  disableSeek: React.PropTypes.bool,
  stayOnBackSkipThreshold: React.PropTypes.number,
  style: React.PropTypes.object,
  onMediaEvent: React.PropTypes.object,
  audioElementRef: React.PropTypes.func
};

AudioPlayer.defaultProps = {
  cycle: true
};

module.exports = AudioPlayer;
<commit_msg>Clear timeouts when unmounting components<commit_after>import React from 'react';
import arrayFindIndex from 'array-find-index';
import classNames from 'classnames';

import './index.scss';

const log = console.log.bind(console);
const logError = console.error ? console.error.bind(console) : log;
const logWarning = console.warn ? console.warn.bind(console) : log;

/* converts given number of seconds to standard time display format
 * http://goo.gl/kEvnKn
 */
function convertToTime (number) {
  const mins = Math.floor(number / 60);
  const secs = (number % 60).toFixed();
  return `${ mins < 10 ? '0' : '' }${ mins }:${ secs < 10 ? '0' : '' }${ secs }`;
}

/*
 * AudioPlayer
 *
 * Accepts 'playlist' prop of the form:
 *
 * [{ "url": "./path/to/file.mp3",
 *    "displayText": "ArtistA - Track 1" },
 *  { "url": "https://domain.com/track2.ogg",
 *    "displayText": "ArtistB - Track 2" }]
 *
 * Accepts 'autoplay' prop (true/[false]).
 *
 * Accepts 'autoplayDelayInSeconds' prop (default 0).
 *
 * Accepts 'gapLengthInSeconds' prop (default 0).
 * Specifies gap at end of one track before next
 * track begins (ignored for manual skip).
 *
 * Accepts 'hideBackSkip' prop (default false,
 * hides back skip button if true).
 *
 * Accepts 'hideForwardSkip' prop (default false,
 * hides forward skip button if true).
 *
 * Accepts 'disableSeek' prop (default false,
 * disables seeking through the audio if true).
 *
 * Accepts 'cycle' prop (default true,
 * starts playing at the beginning of the playlist
 * when finished if true).
 *
 * Accepts 'stayOnBackSkipThreshold' prop, default 5,
 * is number of seconds to progress until pressing back skip
 * restarts the current track.
 *
 * Accepts 'style' prop, object, is applied to
 * outermost div (React styles).
 *
 * Accepts 'onMediaEvent' prop, an object used for
 * listening to media events on the underlying audio element.
 *
 * Accepts 'audioElementRef' prop, a function called after
 * the component mounts and before it unmounts with the
 * internally-referenced HTML audio element as its only parameter.
 * Similar to: https://facebook.github.io/react/docs/refs-and-the-dom.html
 */
class AudioPlayer extends React.Component {

  constructor (props) {
    super(props);

    /* true if the user is currently dragging the mouse
     * to seek a new track position
     */
    this.seekInProgress = false;
    // index matching requested track (whether track has loaded or not)
    this.currentTrackIndex = 0;

    this.defaultState = {
      /* activeTrackIndex will change to match
       * this.currentTrackIndex once metadata has loaded
       */
      activeTrackIndex: -1,
      // indicates whether audio player should be paused
      paused: true,
      /* elapsed time for current track, in seconds -
       * DISPLAY ONLY! the actual elapsed time may
       * not match up if we're currently seeking, since
       * the new time is visually previewed before the
       * audio seeks.
       */
      displayedTime: 0
    };

    this.state = this.defaultState;

    // html audio element used for playback
    this.audio = null;
    this.audioProgressContainer = null;
    /* bounding rectangle used for calculating seek
     * position from mouse/touch coordinates
     */
    this.audioProgressBoundingRect = null;

    // event listeners to add on mount and remove on unmount
    this.seekReleaseListener = e => this.seek(e);
    this.resizeListener = () => this.fetchAudioProgressBoundingRect();
    this.audioPlayListener = () => this.setState({ paused: false });
    this.audioPauseListener = () => this.setState({ paused: true });
    this.audioEndListener = () => {
      const gapLengthInSeconds = this.props.gapLengthInSeconds || 0;
      clearTimeout(this.gapLengthTimeout);
      this.gapLengthTimeout = setTimeout(() => this.skipToNextTrack(), gapLengthInSeconds * 1000);
    };
    this.audioStallListener = () => this.togglePause(true);
    this.audioTimeUpdateListener = () => this.handleTimeUpdate();
    this.audioMetadataLoadedListener = () => this.setState({
      activeTrackIndex: this.currentTrackIndex
    });
  }

  componentDidMount () {
    // add event listeners bound outside the scope of our component
    window.addEventListener('mouseup', this.seekReleaseListener);
    document.addEventListener('touchend', this.seekReleaseListener);
    window.addEventListener('resize', this.resizeListener);
    this.resizeListener();

    const audio = this.audio = document.createElement('audio');

    // add event listeners on the audio element
    audio.preload = 'metadata';
    audio.addEventListener('play', this.audioPlayListener);
    audio.addEventListener('pause', this.audioPauseListener);
    audio.addEventListener('ended', this.audioEndListener);
    audio.addEventListener('stalled', this.audioStallListener);
    audio.addEventListener('timeupdate', this.audioTimeUpdateListener);
    audio.addEventListener('loadedmetadata', this.audioMetadataLoadedListener);
    this.addMediaEventListeners(this.props.onMediaEvent);

    if (this.props.playlist && this.props.playlist.length) {
      this.updateSource();
      if (this.props.autoplay) {
        const delay = this.props.autoplayDelayInSeconds || 0;
        clearTimeout(this.delayTimeout);
        this.delayTimeout = setTimeout(() => this.togglePause(false), delay * 1000);
      }
    }

    if (this.props.audioElementRef) {
      this.props.audioElementRef(audio);
    }
  }

  componentWillUnmount () {
    // remove event listeners bound outside the scope of our component
    window.removeEventListener('mouseup', this.seekReleaseListener);
    document.removeEventListener('touchend', this.seekReleaseListener);
    window.removeEventListener('resize', this.resizeListener);

    // remove event listeners on the audio element
    this.audio.removeEventListener('play', this.audioPlayListener);
    this.audio.removeEventListener('pause', this.audioPauseListener);
    this.audio.removeEventListener('ended', this.audioEndListener);
    this.audio.removeEventListener('stalled', this.audioStallListener);
    this.audio.removeEventListener('timeupdate', this.audioTimeUpdateListener);
    this.audio.removeEventListener('loadedmetadata', this.audioMetadataLoadedListener);
    this.removeMediaEventListeners(this.props.onMediaEvent);
    clearTimeout(this.gapLengthTimeout);
    clearTimeout(this.delayTimeout);

    // pause the audio element before we unmount
    this.audio.pause();

    if (this.props.audioElementRef) {
      this.props.audioElementRef(this.audio);
    }
  }

  componentWillReceiveProps (nextProps) {
    // Update media event listeners that may have changed
    this.removeMediaEventListeners(this.props.onMediaEvent);
    this.addMediaEventListeners(nextProps.onMediaEvent);

    const newPlaylist = nextProps.playlist;
    if (!newPlaylist || !newPlaylist.length) {
      if (this.audio) {
        this.audio.src = '';
      }
      this.currentTrackIndex = 0;
      return this.setState(this.defaultState);
    }

    const oldPlaylist = this.props.playlist;

    const currentTrackUrl = ((oldPlaylist || [])[this.currentTrackIndex] || {}).url;
    this.currentTrackIndex = arrayFindIndex(newPlaylist, track => {
      return track.url && currentTrackUrl === track.url;
    });
    /* if the track we're already playing is in the new playlist, update the
     * activeTrackIndex.
     */
    if (this.currentTrackIndex !== -1) {
      this.setState({
        activeTrackIndex: this.currentTrackIndex
      });
    }
  }

  addMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.addEventListener(type, mediaEvents[type]);
    });
  }

  removeMediaEventListeners (mediaEvents) {
    if (!mediaEvents) {
      return;
    }
    Object.keys(mediaEvents).forEach((type) => {
      if (typeof mediaEvents[type] !== 'function') {
        return;
      }
      this.audio.removeEventListener(type, mediaEvents[type]);
    });
  }

  componentDidUpdate () {
    /* if we loaded a new playlist and reset the current track marker, we
     * should load up the first one.
     */
    if (this.audio && this.currentTrackIndex === -1) {
      this.skipToNextTrack(false);
    }
  }

  togglePause (value) {
    if (!this.audio) {
      return;
    }
    const pause = typeof value === 'boolean' ? value : !this.state.paused;
    if (pause) {
      return this.audio.pause();
    }
    if (!this.props.playlist || !this.props.playlist.length) {
      return;
    }
    try {
      this.audio.play();
    } catch (error) {
      logError(error);
      const warningMessage =
        'Audio playback failed at ' +
        new Date().toLocaleTimeString() +
        '! (Perhaps autoplay is disabled in this browser.)';
      logWarning(warningMessage);
    }
  }

  skipToNextTrack (shouldPlay) {
    if (!this.audio) {
      return;
    }
    this.audio.pause();
    if (!this.props.playlist || !this.props.playlist.length) {
      return;
    }
    let i = this.currentTrackIndex + 1;
    if (i >= this.props.playlist.length) {
      i = 0;
    }
    this.currentTrackIndex = i;
    this.setState({
      activeTrackIndex: -1,
      displayedTime: 0
    }, () => {
      this.updateSource();
      const shouldPauseOnCycle = (!this.props.cycle && i === 0);
      const shouldPause = shouldPauseOnCycle || (typeof shouldPlay === 'boolean' ? !shouldPlay : false);
      this.togglePause(shouldPause);
    });
  }

  backSkip () {
    if (!this.props.playlist || !this.props.playlist.length) {
      return;
    }
    const audio = this.audio;
    let stayOnBackSkipThreshold = this.props.stayOnBackSkipThreshold;
    if (isNaN(stayOnBackSkipThreshold)) {
      stayOnBackSkipThreshold = 5;
    }
    if (audio.currentTime >= stayOnBackSkipThreshold) {
      return audio.currentTime = 0;
    }
    let i = this.currentTrackIndex - 1;
    if (i < 0) {
      i = this.props.playlist.length - 1;
    }
    this.currentTrackIndex = i - 1;
    this.skipToNextTrack();
  }

  updateSource () {
    this.audio.src = this.props.playlist[this.currentTrackIndex].url;
  }

  fetchAudioProgressBoundingRect () {
    this.audioProgressBoundingRect = this.audioProgressContainer.getBoundingClientRect();
  }

  handleTimeUpdate () {
    if (!this.seekInProgress && this.audio) {
      this.setState({
        displayedTime: this.audio.currentTime
      });
    }
  }

  adjustDisplayedTime (event) {
    if (!this.props.playlist || !this.props.playlist.length || this.props.disableSeek) {
      return;
    }
    // make sure we don't select stuff in the background while seeking
    if (event.type === 'mousedown' || event.type === 'touchstart') {
      this.seekInProgress = true;
      document.body.classList.add('noselect');
    } else if (!this.seekInProgress) {
      return;
    }
    /* we don't want mouse handlers to receive the event
     * after touch handlers if we're seeking.
     */
    event.preventDefault();
    const boundingRect = this.audioProgressBoundingRect;
    const isTouch = event.type.slice(0, 5) === 'touch';
    const pageX = isTouch ? event.targetTouches.item(0).pageX : event.pageX;
    const position = pageX - boundingRect.left - document.body.scrollLeft;
    const containerWidth = boundingRect.width;
    const progressPercentage = position / containerWidth;
    this.setState({
      displayedTime: progressPercentage * this.audio.duration
    });
  }

  seek (event) {
    /* this function is activated when the user lets
     * go of the mouse, so if .noselect was applied
     * to the document body, get rid of it.
     */
    document.body.classList.remove('noselect');
    if (!this.seekInProgress) {
      return;
    }
    /* we don't want mouse handlers to receive the event
     * after touch handlers if we're seeking.
     */
    event.preventDefault();
    this.seekInProgress = false;
    const displayedTime = this.state.displayedTime;
    if (isNaN(displayedTime)) {
      return;
    }
    this.audio.currentTime = displayedTime;
  }

  render () {
    const activeIndex = this.state.activeTrackIndex;
    const displayText = this.props.playlist ? (
      activeIndex < 0 ? null : this.props.playlist[activeIndex].displayText
    ) : 'Please load a playlist';

    const displayedTime = this.state.displayedTime;
    const duration = this.audio && this.audio.duration || 0;

    const elapsedTime = convertToTime(displayedTime);
    const fullTime = convertToTime(duration);
    const timeRatio = `${ elapsedTime } / ${ fullTime }`;

    const progressBarWidth = `${ (displayedTime / duration) * 100 }%`;

    const adjustDisplayedTime = e => this.adjustDisplayedTime(e);

    return (

 this.backSkip()}
          >

 this.togglePause()}
          >

 this.skipToNextTrack()}
          >

 this.audioProgressContainer = ref}
          onMouseDown={adjustDisplayedTime}
          onMouseMove={adjustDisplayedTime}
          onTouchStart={adjustDisplayedTime}
          onTouchMove={adjustDisplayedTime}
        >

                {displayText}

              {timeRatio}

    );
  }

}

AudioPlayer.propTypes = {
  playlist: React.PropTypes.array,
  autoplay: React.PropTypes.bool,
  autoplayDelayInSeconds: React.PropTypes.number,
  gapLengthInSeconds: React.PropTypes.number,
  hideBackSkip: React.PropTypes.bool,
  hideForwardSkip: React.PropTypes.bool,
  cycle: React.PropTypes.bool,
  disableSeek: React.PropTypes.bool,
  stayOnBackSkipThreshold: React.PropTypes.number,
  style: React.PropTypes.object,
  onMediaEvent: React.PropTypes.object,
  audioElementRef: React.PropTypes.func
};

AudioPlayer.defaultProps = {
  cycle: true
};

module.exports = AudioPlayer;
<|endoftext|>