<commit_before>import React, {PropTypes, Component} from 'react';
import algoliasearch from 'algoliasearch';
import algoliasearchHelper, {SearchParameters} from 'algoliasearch-helper';
import {Provider} from 'react-algoliasearch-helper';
import omit from 'lodash/object/omit';
import isEqual from 'lodash/lang/isEqual';
import {createHistory} from 'history';

import createConfigManager from './createConfigManager';
import createStateManager from './createStateManager';
import {configManagerPropType, stateManagerPropType} from './propTypes';

class InstantSearch extends Component {
  static propTypes = {
    // @FIX: a few of these props are only applied on component initialization
    // and can't be updated afterwards. appId, apiKey, history, treshold...
    appId: PropTypes.string,
    apiKey: PropTypes.string,
    indexName: PropTypes.string,
    history: PropTypes.object,
    createURL: PropTypes.func,
    treshold: PropTypes.number,
    configureState: PropTypes.func,
  };

  static defaultProps = {
    treshold: 700,
  };

  static childContextTypes = {
    algoliaConfigManager: configManagerPropType.isRequired,
    algoliaStateManager: stateManagerPropType.isRequired,
  };

  constructor(props) {
    super(props);

    const client = algoliasearch(props.appId, props.apiKey);
    const helper = this.helper = algoliasearchHelper(client, props.indexName);

    const history = props.history || createHistory();

    const getState = () => {
      // Retrieve the state from the state manager.
      // This state is the result of all user actions on the page, starting from
      // an empty state.
      let state = new SearchParameters({
        ...this.stateManager.getState(),
        index: props.indexName,
      });
      if (this.props.configureState) {
        // Apply the user configuration to the state. This is notably useful for
        // applying URL parameters that are not controlled by the state manager.
        state = this.props.configureState(state);
      }

      // Apply the components configurations to the state.
      state = this.configManager.getState(state);

      return state;
    };

    const search = () => {
      const state = getState();

      if (isEqual(state, helper.getState())) {
        // Avoid updating the state and calling search if it hasn't changed.
        // Otherwise we end up in an infinite loop of
        // setState -> config update -> setState
        // There might be a better way to avoid this than deep equality check.
        // This is kinda sad: since our helper states are immutable, deep
        // equality check could be free.
        return;
      }

      helper.setState(state);
      helper.search();
    };

    this.configManager = createConfigManager(search);

    this.stateManager = createStateManager(history, search, {
      createURL: props.createURL,
      treshold: props.treshold,
    });

    // Apply the initial state from the history.
    // This setState won't trigger any updates anywhere. At this point we
    // haven't passed it to the Provider yet.
    helper.setState(getState());
  }

  componentWillUnmount() {
    // `stateManager` listens to the history, which can outlast this component
    // in the case where it was passed in from props. This means we need to
    // remove all listeners we added to it before unmounting. Otherwise, we'll
    // still get search calls after the component has unmounted.
    // This is also necessary for this component to be garbage collected.
    this.stateManager.unlisten();
  }

  getChildContext() {
    return {
      algoliaConfigManager: this.configManager,
      algoliaStateManager: this.stateManager,
    };
  }

  render() {
    return (
      <Provider
        {...omit(this.props, Object.keys(InstantSearch.propTypes))}
        helper={this.helper}
      />
    );
  }
}

export default InstantSearch;
<commit_msg>Preliminary no-urlsync support<commit_after>import React, {PropTypes, Component} from 'react';
import algoliasearch from 'algoliasearch';
import algoliasearchHelper, {SearchParameters} from 'algoliasearch-helper';
import {Provider} from 'react-algoliasearch-helper';
import omit from 'lodash/object/omit';
import isEqual from 'lodash/lang/isEqual';
import {createHistory, createMemoryHistory} from 'history';

import createConfigManager from './createConfigManager';
import createStateManager from './createStateManager';
import {configManagerPropType, stateManagerPropType} from './propTypes';

class InstantSearch extends Component {
  static propTypes = {
    // @FIX: a few of these props are only applied on component initialization
    // and can't be updated afterwards. appId, apiKey, history, treshold...
    appId: PropTypes.string,
    apiKey: PropTypes.string,
    indexName: PropTypes.string,
    history: PropTypes.object,
    createURL: PropTypes.func,
    treshold: PropTypes.number,
    configureState: PropTypes.func,
    urlSync: PropTypes.bool,
  };

  static defaultProps = {
    treshold: 700,
    urlSync: true,
  };

  static childContextTypes = {
    algoliaConfigManager: configManagerPropType.isRequired,
    algoliaStateManager: stateManagerPropType.isRequired,
  };

  constructor(props) {
    super(props);

    const client = algoliasearch(props.appId, props.apiKey);
    const helper = this.helper = algoliasearchHelper(client, props.indexName);

    const history =
      props.history ||
      // Easiest way, but probably not the best. We could also add code to
      // createStateManager to support no history at all.
      props.urlSync ? createHistory() : createMemoryHistory();

    const getState = () => {
      // Retrieve the state from the state manager.
      // This state is the result of all user actions on the page, starting from
      // an empty state.
      let state = new SearchParameters({
        ...this.stateManager.getState(),
        index: props.indexName,
      });
      if (this.props.configureState) {
        // Apply the user configuration to the state. This is notably useful for
        // applying URL parameters that are not controlled by the state manager.
        state = this.props.configureState(state);
      }

      // Apply the components configurations to the state.
      state = this.configManager.getState(state);

      return state;
    };

    const search = () => {
      const state = getState();

      if (isEqual(state, helper.getState())) {
        // Avoid updating the state and calling search if it hasn't changed.
        // Otherwise we end up in an infinite loop of
        // setState -> config update -> setState
        // There might be a better way to avoid this than deep equality check.
        // This is kinda sad: since our helper states are immutable, deep
        // equality check could be free.
        return;
      }

      helper.setState(state);
      helper.search();
    };

    this.configManager = createConfigManager(search);

    this.stateManager = createStateManager(history, search, {
      createURL: props.createURL,
      treshold: props.treshold,
    });

    // Apply the initial state from the history.
    // This setState won't trigger any updates anywhere. At this point we
    // haven't passed it to the Provider yet.
    helper.setState(getState());
  }

  componentWillUnmount() {
    // `stateManager` listens to the history, which can outlast this component
    // in the case where it was passed in from props. This means we need to
    // remove all listeners we added to it before unmounting. Otherwise, we'll
    // still get search calls after the component has unmounted.
    // This is also necessary for this component to be garbage collected.
    this.stateManager.unlisten();
  }

  getChildContext() {
    return {
      algoliaConfigManager: this.configManager,
      algoliaStateManager: this.stateManager,
    };
  }

  render() {
    return (
      <Provider
        {...omit(this.props, Object.keys(InstantSearch.propTypes))}
        helper={this.helper}
      />
    );
  }
}

export default InstantSearch;
<|endoftext|>


<commit_before>var caniuse = require('caniuse-db/data.json').agents;
var path    = require('path');
var fs      = require('fs');

var FLOAT = /^\d+(\.\d+)?$/;

function uniq(array) {
    var filtered = [];
    for ( var i = 0; i < array.length; i++ ) {
        if ( filtered.indexOf(array[i]) === -1 ) filtered.push(array[i]);
    }
    return filtered;
}

function BrowserslistError(message) {
    this.name = 'BrowserslistError';
    this.message = message || '';
    if ( Error.captureStackTrace ) {
        Error.captureStackTrace(this, BrowserslistError);
    }
}
BrowserslistError.prototype = Error.prototype;

function error(name) {
    var obj = new BrowserslistError(name);
    obj.browserslist = true;
    throw obj;
}

// Helpers

function normalize(versions) {
    return versions.filter(function (version) {
        return typeof version === 'string';
    });
}

function fillUsage(result, name, data) {
    for ( var i in data ) {
        result[name + ' ' + i] = data[i];
    }
}

function isFile(file) {
    return fs.existsSync(file) && fs.statSync(file).isFile();
}

// Return array of browsers by selection queries:
//
//   browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8']
var browserslist = function (selections, opts) {
    if ( typeof opts === 'undefined' ) opts = { };

    if ( typeof selections === 'undefined' || selections === null ) {

        if ( process.env.BROWSERSLIST ) {
            selections = process.env.BROWSERSLIST;
        } else if ( opts.config || process.env.BROWSERSLIST_CONFIG ) {
            var file = opts.config || process.env.BROWSERSLIST_CONFIG;
            if ( fs.existsSync(file) && fs.statSync(file).isFile() ) {
                selections = browserslist.parseConfig( fs.readFileSync(file) );
            } else {
                error('Can\'t read ' + file + ' config');
            }
        } else {
            var config = browserslist.readConfig(opts.path);
            if ( config !== false ) {
                selections = config;
            } else {
                selections = browserslist.defaults;
            }
        }
    }

    if ( typeof selections === 'string' ) {
        selections = selections.split(/,\s*/);
    }

    if ( opts.stats || process.env.BROWSERSLIST_STATS ) {
        browserslist.usage.custom = { };
        var stats = opts.stats || process.env.BROWSERSLIST_STATS;
        if ( typeof stats === 'string' ) {
            try {
                stats = JSON.parse(fs.readFileSync(stats));
            } catch (e) {
                error('Can\'t read ' + stats);
            }
        }
        if ( 'dataByBrowser' in stats ) {
            // Allow to use the data as-is from the caniuse.com website
            stats = stats.dataByBrowser;
        }
        for ( var browser in stats ) {
            fillUsage(browserslist.usage.custom, browser, stats[browser]);
        }
    }

    var result = [];

    var exclude, query, match, array, used;
    selections.forEach(function (selection) {
        if ( selection.trim() === '' ) return;
        exclude = false;
        used    = false;

        if ( selection.indexOf('not ') === 0 ) {
            selection = selection.slice(4);
            exclude   = true;
        }

        for ( var i in browserslist.queries ) {
            query = browserslist.queries[i];
            match = selection.match(query.regexp);
            if ( match ) {
                array = query.select.apply(browserslist, match.slice(1));
                if ( exclude ) {
                    result = result.filter(function (j) {
                        return array.indexOf(j) === -1;
                    });
                } else {
                    result = result.concat(array);
                }
                used   = true;
                break;
            }
        }

        if ( !used ) {
            error('Unknown browser query `' + selection + '`');
        }
    });

    result = uniq(result);

    return result.filter(function (i) {
        var version = i.split(' ')[1];
        if ( version === '0' ) {
            var name = i.split(' ')[0];
            return !result.some(function (j) {
                return j !== i && j.split(' ')[0] === name;
            });
        } else {
            return true;
        }
    }).sort(function (name1, name2) {
        name1 = name1.split(' ');
        name2 = name2.split(' ');
        if ( name1[0] === name2[0] ) {
            if ( FLOAT.test(name1[1]) && FLOAT.test(name2[1]) ) {
                var d = parseFloat(name2[1]) - parseFloat(name1[1]);
                if ( d > 0 ) {
                    return 1;
                } else if ( d < 0 ) {
                    return -1;
                } else {
                    return 0;
                }
            } else {
                return name2[1].localeCompare(name1[1]);
            }
        } else {
            return name1[0].localeCompare(name2[0]);
        }
    });
};

var normalizeVersion = function (data, version) {
    if ( data.versions.indexOf(version) !== -1 ) {
        return version;
    } else {
        return browserslist.versionAliases[data.name][version];
    }
};

var loadCountryStatistics = function (country) {
    if ( !browserslist.usage[country] ) {
        var usage = { };
        var data = require(
            'caniuse-db/region-usage-json/' + country + '.json');
        for ( var i in data.data ) {
            fillUsage(usage, i, data.data[i]);
        }
        browserslist.usage[country] = usage;
    }
};

// Will be filled by Can I Use data below
browserslist.data  = { };
browserslist.usage = {
    global: { },
    custom: null
};

// Default browsers query
browserslist.defaults = [
    '> 1%',
    'last 2 versions',
    'Firefox ESR'
];

// What browsers will be used in `last n version` query
browserslist.major = [
    'safari', 'opera', 'ios_saf', 'ie_mob', 'ie', 'edge', 'firefox', 'chrome'
];

// Browser names aliases
browserslist.aliases = {
    fx:             'firefox',
    ff:             'firefox',
    ios:            'ios_saf',
    explorer:       'ie',
    blackberry:     'bb',
    explorermobile: 'ie_mob',
    operamini:      'op_mini',
    operamobile:    'op_mob',
    chromeandroid:  'and_chr',
    firefoxandroid: 'and_ff',
    ucandroid:      'and_uc'
};

// Aliases to work with joined versions like `ios_saf 7.0-7.1`
browserslist.versionAliases = { };

// Get browser data by alias or case insensitive name
browserslist.byName = function (name) {
    name = name.toLowerCase();
    name = browserslist.aliases[name] || name;
    return browserslist.data[name];
};

// Get browser data by alias or case insensitive name and throw error
// on unknown browser
browserslist.checkName = function (name) {
    var data = browserslist.byName(name);
    if ( !data ) error('Unknown browser ' + name);
    return data;
};

// Find config, read file and parse it
browserslist.readConfig = function (from) {
    if ( from === false )   return false;
    if ( !fs.readFileSync || !fs.existsSync || !fs.statSync ) return false;
    if ( typeof from === 'undefined' ) from = '.';

    var dirs = path.resolve(from).split(path.sep);
    var config, pkgConfig;
    while ( dirs.length ) {
        var configPath = dirs.concat(['browserslist']).join(path.sep);
        var pkgPath = dirs.concat(['package.json']).join(path.sep);

        if ( isFile(configPath) ) {
            config = fs.readFileSync(configPath);
        }

        if ( isFile(pkgPath) ) {
            try {
                pkgConfig = JSON.parse(fs.readFileSync(pkgPath)).browserslist;
            } catch (e) {
                console.warn(
                    '[Browserslist] Could not parse ' + pkgPath + '. ' +
                    'Ignoring it.');
            }
        }

        if ( config && pkgConfig ) {
            error(
                dirs.join(path.sep) + ' contains ' +
                'both browserslist and package.json with browserslist key, ' +
                'this is potentially dangerous');
        } else if ( config ) {
            return browserslist.parseConfig(config);
        } else if ( pkgConfig ) {
            return pkgConfig;
        }

        dirs.pop();
    }

    return false;
};

// Return browsers market coverage
browserslist.coverage = function (browsers, country) {
    if ( country && country !== 'global') {
        country = country.toUpperCase();
        loadCountryStatistics(country);
    } else {
        country = 'global'; // Default value
    }

    return browsers.reduce(function (all, i) {
        var usage = browserslist.usage[country][i];
        if ( usage === undefined ) {
            // Sometimes, Caniuse consolidates country usage data into a single
            // "version 0" entry. This is usually when there is only 1 version.
            usage = browserslist.usage[country][i.replace(/ [\d.]+$/, ' 0')];
        }
        return all + (usage || 0);
    }, 0);
};

// Return array of queries from config content
browserslist.parseConfig = function (string) {
    return string.toString()
            .replace(/#[^\n]*/g, '')
            .split(/\n/)
            .map(function (i) {
                return i.trim();
            }).filter(function (i) {
                return i !== '';
            });
};

browserslist.queries = {

    lastVersions: {
        regexp: /^last\s+(\d+)\s+versions?$/i,
        select: function (versions) {
            var selected = [];
            browserslist.major.forEach(function (name) {
                var data  = browserslist.byName(name);
                if ( !data ) return;
                var array = data.released.slice(-versions);

                array = array.map(function (v) {
                    return data.name + ' ' + v;
                });
                selected = selected.concat(array);
            });
            return selected;
        }
    },

    lastByBrowser: {
        regexp: /^last\s+(\d+)\s+(\w+)\s+versions?$/i,
        select: function (versions, name) {
            var data = browserslist.checkName(name);
            return data.released.slice(-versions).map(function (v) {
                return data.name + ' ' + v;
            });
        }
    },

    globalStatistics: {
        regexp: /^>\s*(\d*\.?\d+)%$/,
        select: function (popularity) {
            popularity = parseFloat(popularity);
            var result = [];

            for ( var version in browserslist.usage.global ) {
                if ( browserslist.usage.global[version] > popularity ) {
                    result.push(version);
                }
            }

            return result;
        }
    },

    customStatistics: {
        regexp: /^>\s*(\d*\.?\d+)%\s+in\s+my\s+stats$/,
        select: function (popularity) {
            popularity = parseFloat(popularity);
            var result = [];

            var usage = browserslist.usage.custom;
            if ( !usage ) {
                error('Custom usage statistics was not provided');
            }

            for ( var version in usage ) {
                if ( usage[version] > popularity ) {
                    result.push(version);
                }
            }

            return result;
        }
    },

    countryStatistics: {
        regexp: /^>\s*(\d*\.?\d+)%\s+in\s+(\w\w)$/,
        select: function (popularity, country) {
            popularity = parseFloat(popularity);
            country    = country.toUpperCase();
            var result = [];

            loadCountryStatistics(country);
            var usage = browserslist.usage[country];

            for ( var version in usage ) {
                if ( usage[version] > popularity ) {
                    result.push(version);
                }
            }

            return result;
        }
    },

    range: {
        regexp: /^(\w+)\s+([\d\.]+)\s*-\s*([\d\.]+)$/i,
        select: function (name, from, to) {
            var data = browserslist.checkName(name);
            from = parseFloat(normalizeVersion(data, from) || from);
            to = parseFloat(normalizeVersion(data, to) || to);

            var filter = function (v) {
                var parsed = parseFloat(v);
                return parsed >= from && parsed <= to;
            };

            return data.released.filter(filter).map(function (v) {
                return data.name + ' ' + v;
            });
        }
    },

    versions: {
        regexp: /^(\w+)\s*(>=?|<=?)\s*([\d\.]+)$/,
        select: function (name, sign, version) {
            var data = browserslist.checkName(name);
            var alias = normalizeVersion(data, version);
            if ( alias ) {
                version = alias;
            }
            version = parseFloat(version);

            var filter;
            if ( sign === '>' ) {
                filter = function (v) {
                    return parseFloat(v) > version;
                };
            } else if ( sign === '>=' ) {
                filter = function (v) {
                    return parseFloat(v) >= version;
                };
            } else if ( sign === '<' ) {
                filter = function (v) {
                    return parseFloat(v) < version;
                };
            } else if ( sign === '<=' ) {
                filter = function (v) {
                    return parseFloat(v) <= version;
                };
            }
            return data.released.filter(filter).map(function (v) {
                return data.name + ' ' + v;
            });
        }
    },

    esr: {
        regexp: /^(firefox|ff|fx)\s+esr$/i,
        select: function () {
            return ['firefox 45'];
        }
    },

    opMini: {
        regexp: /(operamini|op_mini)\s+all/i,
        select: function () {
            return ['op_mini all'];
        }
    },

    direct: {
        regexp: /^(\w+)\s+(tp|[\d\.]+)$/i,
        select: function (name, version) {
            if ( /tp/i.test(version) ) version = 'TP';
            var data  = browserslist.checkName(name);
            var alias = normalizeVersion(data, version);
            if ( alias ) {
                version = alias;
            } else {
                if ( version.indexOf('.') === -1 ) {
                    alias = version + '.0';
                } else if ( /\.0$/.test(version) ) {
                    alias = version.replace(/\.0$/, '');
                }
                alias = normalizeVersion(data, alias);
                if ( alias ) {
                    version = alias;
                } else {
                    error('Unknown version ' + version + ' of ' + name);
                }
            }
            return [data.name + ' ' + version];
        }
    },

    defaults: {
        regexp: /^defaults$/i,
        select: function () {
            return browserslist(browserslist.defaults);
        }
    }

};

// Get and convert Can I Use data

(function () {
    for ( var name in caniuse ) {
        browserslist.data[name] = {
            name:     name,
            versions: normalize(caniuse[name].versions),
            released: normalize(caniuse[name].versions.slice(0, -3))
        };
        fillUsage(browserslist.usage.global, name, caniuse[name].usage_global);

        browserslist.versionAliases[name] = { };
        for ( var i = 0; i < caniuse[name].versions.length; i++ ) {
            if ( !caniuse[name].versions[i] ) continue;
            var full = caniuse[name].versions[i];

            if ( full.indexOf('-') !== -1 ) {
                var interval = full.split('-');
                for ( var j = 0; j < interval.length; j++ ) {
                    browserslist.versionAliases[name][interval[j]] = full;
                }
            }
        }
    }
}());

module.exports = browserslist;
<commit_msg>Clean up error helper<commit_after>var caniuse = require('caniuse-db/data.json').agents;
var path    = require('path');
var fs      = require('fs');

var FLOAT = /^\d+(\.\d+)?$/;

function uniq(array) {
    var filtered = [];
    for ( var i = 0; i < array.length; i++ ) {
        if ( filtered.indexOf(array[i]) === -1 ) filtered.push(array[i]);
    }
    return filtered;
}

function BrowserslistError(message) {
    this.name = 'BrowserslistError';
    this.message = message || '';
    this.browserslist = true;
    if ( Error.captureStackTrace ) {
        Error.captureStackTrace(this, BrowserslistError);
    }
}
BrowserslistError.prototype = Error.prototype;

// Helpers

function error(name) {
    throw new BrowserslistError(name);
}

function normalize(versions) {
    return versions.filter(function (version) {
        return typeof version === 'string';
    });
}

function fillUsage(result, name, data) {
    for ( var i in data ) {
        result[name + ' ' + i] = data[i];
    }
}

function isFile(file) {
    return fs.existsSync(file) && fs.statSync(file).isFile();
}

// Return array of browsers by selection queries:
//
//   browserslist('IE >= 10, IE 8') //=> ['ie 11', 'ie 10', 'ie 8']
var browserslist = function (selections, opts) {
    if ( typeof opts === 'undefined' ) opts = { };

    if ( typeof selections === 'undefined' || selections === null ) {

        if ( process.env.BROWSERSLIST ) {
            selections = process.env.BROWSERSLIST;
        } else if ( opts.config || process.env.BROWSERSLIST_CONFIG ) {
            var file = opts.config || process.env.BROWSERSLIST_CONFIG;
            if ( fs.existsSync(file) && fs.statSync(file).isFile() ) {
                selections = browserslist.parseConfig( fs.readFileSync(file) );
            } else {
                error('Can\'t read ' + file + ' config');
            }
        } else {
            var config = browserslist.readConfig(opts.path);
            if ( config !== false ) {
                selections = config;
            } else {
                selections = browserslist.defaults;
            }
        }
    }

    if ( typeof selections === 'string' ) {
        selections = selections.split(/,\s*/);
    }

    if ( opts.stats || process.env.BROWSERSLIST_STATS ) {
        browserslist.usage.custom = { };
        var stats = opts.stats || process.env.BROWSERSLIST_STATS;
        if ( typeof stats === 'string' ) {
            try {
                stats = JSON.parse(fs.readFileSync(stats));
            } catch (e) {
                error('Can\'t read ' + stats);
            }
        }
        if ( 'dataByBrowser' in stats ) {
            // Allow to use the data as-is from the caniuse.com website
            stats = stats.dataByBrowser;
        }
        for ( var browser in stats ) {
            fillUsage(browserslist.usage.custom, browser, stats[browser]);
        }
    }

    var result = [];

    var exclude, query, match, array, used;
    selections.forEach(function (selection) {
        if ( selection.trim() === '' ) return;
        exclude = false;
        used    = false;

        if ( selection.indexOf('not ') === 0 ) {
            selection = selection.slice(4);
            exclude   = true;
        }

        for ( var i in browserslist.queries ) {
            query = browserslist.queries[i];
            match = selection.match(query.regexp);
            if ( match ) {
                array = query.select.apply(browserslist, match.slice(1));
                if ( exclude ) {
                    result = result.filter(function (j) {
                        return array.indexOf(j) === -1;
                    });
                } else {
                    result = result.concat(array);
                }
                used   = true;
                break;
            }
        }

        if ( !used ) {
            error('Unknown browser query `' + selection + '`');
        }
    });

    result = uniq(result);

    return result.filter(function (i) {
        var version = i.split(' ')[1];
        if ( version === '0' ) {
            var name = i.split(' ')[0];
            return !result.some(function (j) {
                return j !== i && j.split(' ')[0] === name;
            });
        } else {
            return true;
        }
    }).sort(function (name1, name2) {
        name1 = name1.split(' ');
        name2 = name2.split(' ');
        if ( name1[0] === name2[0] ) {
            if ( FLOAT.test(name1[1]) && FLOAT.test(name2[1]) ) {
                var d = parseFloat(name2[1]) - parseFloat(name1[1]);
                if ( d > 0 ) {
                    return 1;
                } else if ( d < 0 ) {
                    return -1;
                } else {
                    return 0;
                }
            } else {
                return name2[1].localeCompare(name1[1]);
            }
        } else {
            return name1[0].localeCompare(name2[0]);
        }
    });
};

var normalizeVersion = function (data, version) {
    if ( data.versions.indexOf(version) !== -1 ) {
        return version;
    } else {
        return browserslist.versionAliases[data.name][version];
    }
};

var loadCountryStatistics = function (country) {
    if ( !browserslist.usage[country] ) {
        var usage = { };
        var data = require(
            'caniuse-db/region-usage-json/' + country + '.json');
        for ( var i in data.data ) {
            fillUsage(usage, i, data.data[i]);
        }
        browserslist.usage[country] = usage;
    }
};

// Will be filled by Can I Use data below
browserslist.data  = { };
browserslist.usage = {
    global: { },
    custom: null
};

// Default browsers query
browserslist.defaults = [
    '> 1%',
    'last 2 versions',
    'Firefox ESR'
];

// What browsers will be used in `last n version` query
browserslist.major = [
    'safari', 'opera', 'ios_saf', 'ie_mob', 'ie', 'edge', 'firefox', 'chrome'
];

// Browser names aliases
browserslist.aliases = {
    fx:             'firefox',
    ff:             'firefox',
    ios:            'ios_saf',
    explorer:       'ie',
    blackberry:     'bb',
    explorermobile: 'ie_mob',
    operamini:      'op_mini',
    operamobile:    'op_mob',
    chromeandroid:  'and_chr',
    firefoxandroid: 'and_ff',
    ucandroid:      'and_uc'
};

// Aliases to work with joined versions like `ios_saf 7.0-7.1`
browserslist.versionAliases = { };

// Get browser data by alias or case insensitive name
browserslist.byName = function (name) {
    name = name.toLowerCase();
    name = browserslist.aliases[name] || name;
    return browserslist.data[name];
};

// Get browser data by alias or case insensitive name and throw error
// on unknown browser
browserslist.checkName = function (name) {
    var data = browserslist.byName(name);
    if ( !data ) error('Unknown browser ' + name);
    return data;
};

// Find config, read file and parse it
browserslist.readConfig = function (from) {
    if ( from === false )   return false;
    if ( !fs.readFileSync || !fs.existsSync || !fs.statSync ) return false;
    if ( typeof from === 'undefined' ) from = '.';

    var dirs = path.resolve(from).split(path.sep);
    var config, pkgConfig;
    while ( dirs.length ) {
        var configPath = dirs.concat(['browserslist']).join(path.sep);
        var pkgPath = dirs.concat(['package.json']).join(path.sep);

        if ( isFile(configPath) ) {
            config = fs.readFileSync(configPath);
        }

        if ( isFile(pkgPath) ) {
            try {
                pkgConfig = JSON.parse(fs.readFileSync(pkgPath)).browserslist;
            } catch (e) {
                console.warn(
                    '[Browserslist] Could not parse ' + pkgPath + '. ' +
                    'Ignoring it.');
            }
        }

        if ( config && pkgConfig ) {
            error(
                dirs.join(path.sep) + ' contains ' +
                'both browserslist and package.json with browserslist key, ' +
                'this is potentially dangerous');
        } else if ( config ) {
            return browserslist.parseConfig(config);
        } else if ( pkgConfig ) {
            return pkgConfig;
        }

        dirs.pop();
    }

    return false;
};

// Return browsers market coverage
browserslist.coverage = function (browsers, country) {
    if ( country && country !== 'global') {
        country = country.toUpperCase();
        loadCountryStatistics(country);
    } else {
        country = 'global'; // Default value
    }

    return browsers.reduce(function (all, i) {
        var usage = browserslist.usage[country][i];
        if ( usage === undefined ) {
            // Sometimes, Caniuse consolidates country usage data into a single
            // "version 0" entry. This is usually when there is only 1 version.
            usage = browserslist.usage[country][i.replace(/ [\d.]+$/, ' 0')];
        }
        return all + (usage || 0);
    }, 0);
};

// Return array of queries from config content
browserslist.parseConfig = function (string) {
    return string.toString()
            .replace(/#[^\n]*/g, '')
            .split(/\n/)
            .map(function (i) {
                return i.trim();
            }).filter(function (i) {
                return i !== '';
            });
};

browserslist.queries = {

    lastVersions: {
        regexp: /^last\s+(\d+)\s+versions?$/i,
        select: function (versions) {
            var selected = [];
            browserslist.major.forEach(function (name) {
                var data  = browserslist.byName(name);
                if ( !data ) return;
                var array = data.released.slice(-versions);

                array = array.map(function (v) {
                    return data.name + ' ' + v;
                });
                selected = selected.concat(array);
            });
            return selected;
        }
    },

    lastByBrowser: {
        regexp: /^last\s+(\d+)\s+(\w+)\s+versions?$/i,
        select: function (versions, name) {
            var data = browserslist.checkName(name);
            return data.released.slice(-versions).map(function (v) {
                return data.name + ' ' + v;
            });
        }
    },

    globalStatistics: {
        regexp: /^>\s*(\d*\.?\d+)%$/,
        select: function (popularity) {
            popularity = parseFloat(popularity);
            var result = [];

            for ( var version in browserslist.usage.global ) {
                if ( browserslist.usage.global[version] > popularity ) {
                    result.push(version);
                }
            }

            return result;
        }
    },

    customStatistics: {
        regexp: /^>\s*(\d*\.?\d+)%\s+in\s+my\s+stats$/,
        select: function (popularity) {
            popularity = parseFloat(popularity);
            var result = [];

            var usage = browserslist.usage.custom;
            if ( !usage ) {
                error('Custom usage statistics was not provided');
            }

            for ( var version in usage ) {
                if ( usage[version] > popularity ) {
                    result.push(version);
                }
            }

            return result;
        }
    },

    countryStatistics: {
        regexp: /^>\s*(\d*\.?\d+)%\s+in\s+(\w\w)$/,
        select: function (popularity, country) {
            popularity = parseFloat(popularity);
            country    = country.toUpperCase();
            var result = [];

            loadCountryStatistics(country);
            var usage = browserslist.usage[country];

            for ( var version in usage ) {
                if ( usage[version] > popularity ) {
                    result.push(version);
                }
            }

            return result;
        }
    },

    range: {
        regexp: /^(\w+)\s+([\d\.]+)\s*-\s*([\d\.]+)$/i,
        select: function (name, from, to) {
            var data = browserslist.checkName(name);
            from = parseFloat(normalizeVersion(data, from) || from);
            to = parseFloat(normalizeVersion(data, to) || to);

            var filter = function (v) {
                var parsed = parseFloat(v);
                return parsed >= from && parsed <= to;
            };

            return data.released.filter(filter).map(function (v) {
                return data.name + ' ' + v;
            });
        }
    },

    versions: {
        regexp: /^(\w+)\s*(>=?|<=?)\s*([\d\.]+)$/,
        select: function (name, sign, version) {
            var data = browserslist.checkName(name);
            var alias = normalizeVersion(data, version);
            if ( alias ) {
                version = alias;
            }
            version = parseFloat(version);

            var filter;
            if ( sign === '>' ) {
                filter = function (v) {
                    return parseFloat(v) > version;
                };
            } else if ( sign === '>=' ) {
                filter = function (v) {
                    return parseFloat(v) >= version;
                };
            } else if ( sign === '<' ) {
                filter = function (v) {
                    return parseFloat(v) < version;
                };
            } else if ( sign === '<=' ) {
                filter = function (v) {
                    return parseFloat(v) <= version;
                };
            }
            return data.released.filter(filter).map(function (v) {
                return data.name + ' ' + v;
            });
        }
    },

    esr: {
        regexp: /^(firefox|ff|fx)\s+esr$/i,
        select: function () {
            return ['firefox 45'];
        }
    },

    opMini: {
        regexp: /(operamini|op_mini)\s+all/i,
        select: function () {
            return ['op_mini all'];
        }
    },

    direct: {
        regexp: /^(\w+)\s+(tp|[\d\.]+)$/i,
        select: function (name, version) {
            if ( /tp/i.test(version) ) version = 'TP';
            var data  = browserslist.checkName(name);
            var alias = normalizeVersion(data, version);
            if ( alias ) {
                version = alias;
            } else {
                if ( version.indexOf('.') === -1 ) {
                    alias = version + '.0';
                } else if ( /\.0$/.test(version) ) {
                    alias = version.replace(/\.0$/, '');
                }
                alias = normalizeVersion(data, alias);
                if ( alias ) {
                    version = alias;
                } else {
                    error('Unknown version ' + version + ' of ' + name);
                }
            }
            return [data.name + ' ' + version];
        }
    },

    defaults: {
        regexp: /^defaults$/i,
        select: function () {
            return browserslist(browserslist.defaults);
        }
    }

};

// Get and convert Can I Use data

(function () {
    for ( var name in caniuse ) {
        browserslist.data[name] = {
            name:     name,
            versions: normalize(caniuse[name].versions),
            released: normalize(caniuse[name].versions.slice(0, -3))
        };
        fillUsage(browserslist.usage.global, name, caniuse[name].usage_global);

        browserslist.versionAliases[name] = { };
        for ( var i = 0; i < caniuse[name].versions.length; i++ ) {
            if ( !caniuse[name].versions[i] ) continue;
            var full = caniuse[name].versions[i];

            if ( full.indexOf('-') !== -1 ) {
                var interval = full.split('-');
                for ( var j = 0; j < interval.length; j++ ) {
                    browserslist.versionAliases[name][interval[j]] = full;
                }
            }
        }
    }
}());

module.exports = browserslist;
<|endoftext|>


<commit_before>/* eslint-env node */
'use strict';

var Funnel = require('broccoli-funnel');
var mergeTrees = require('broccoli-merge-trees');

module.exports = {
  name: 'ember-cli-bootstrap-4',

  treeForStyles: function treeForStyles(tree) {
    const styleTrees = [];

    if (this.app.project.findAddonByName('ember-cli-sass')) {
      styleTrees.push(new Funnel('node_modules/bootstrap/scss', {
        destDir: 'ember-cli-bootstrap-4'
      }));
    }

    if (tree) {
      styleTrees.push(tree);
    }

    return mergeTrees(styleTrees, { overwrite: true });
  },

  options: {
    nodeAssets: {
      'popper.js': {
        vendor: {
          enabled: !process.env.EMBER_CLI_FASTBOOT,
          srcDir: 'dist/umd',
          import: [
            'popper.js',
            'popper.js.map',
            'popper-utils.js',
            'popper-utils.js.map'
          ],
          included: function(parent) {
            console.log(parent);
            this._super.included.apply(this, arguments);
          }
        }
      },
      bootstrap: {
        enabled: !process.env.EMBER_CLI_FASTBOOT,
        srcDir: 'dist/js',
        import: [
          'bootstrap.js'
        ]
      }
    }
  }
};
<commit_msg>Fixed import of asset popper.js<commit_after>/* eslint-env node */
'use strict';

var Funnel = require('broccoli-funnel');
var mergeTrees = require('broccoli-merge-trees');

module.exports = {
  name: 'ember-cli-bootstrap-4',

  treeForStyles: function treeForStyles(tree) {
    const styleTrees = [];

    if (this.app.project.findAddonByName('ember-cli-sass')) {
      styleTrees.push(new Funnel('node_modules/bootstrap/scss', {
        destDir: 'ember-cli-bootstrap-4'
      }));
    }

    if (tree) {
      styleTrees.push(tree);
    }

    return mergeTrees(styleTrees, { overwrite: true });
  },

  options: {
    nodeAssets: {
      'popper.js': {
        enabled: !process.env.EMBER_CLI_FASTBOOT,
        srcDir: 'dist/umd',
        import: [
          'popper.js',
          'popper-utils.js'
        ]
      },
      bootstrap: {
        enabled: !process.env.EMBER_CLI_FASTBOOT,
        srcDir: 'dist/js',
        import: [
          'bootstrap.js'
        ]
      }
    }
  }
};
<|endoftext|>


<commit_before>'use strict'

var fs = require('fs')
var path = require('path')
var http = require('http')
var qs = require('querystring')
var multiaddr = require('multiaddr')
var File = require('vinyl')
var MultipartDir = require('./multipartdir.js')
var stream = require('stream')

var pkg
try {
  pkg = JSON.parse(fs.readFileSync(__dirname + '/package.json'))
} catch(e) {
  pkg = { name: 'ipfs-api-browserify', version: '?' }
}

var API_PATH = '/api/v0/'

module.exports = function (host_or_multiaddr, port) {
  var host
  try {
    var maddr = multiaddr(host_or_multiaddr).nodeAddress()
    host = maddr.address
    port = maddr.port
  } catch (e) {
    host = host_or_multiaddr
  }

  if (!host) host = 'localhost'
  if (!port) port = 5001

  function send (path, args, opts, files, buffer, cb) {
    var query, stream, contentType
    contentType = 'application/json'

    if (Array.isArray(path)) path = path.join('/')

    opts = opts || {}

    if (args && !Array.isArray(args)) args = [args]
    if (args) opts.arg = args

    opts['stream-channels'] = true
    query = qs.stringify(opts)

    if (files) {
      stream = getFileStream(files)
      contentType = 'multipart/form-data; boundary=' + stream.boundary
    }

    if (typeof buffer === 'function') {
      cb = buffer
      buffer = false
    }

    var req = http.request({
      method: files ? 'POST' : 'GET',
      host: host,
      port: port,
      path: API_PATH + path + '?' + query,
      headers: {
        'User-Agent': '/node-' + pkg.name + '/' + pkg.version + '/',
        'Content-Type': contentType
      },
      withCredentials: false
    }, function (res) {
      var data = ''
      var objects = []
      var stream = !!res.headers['x-stream-output']
      var chunkedObjects = !!res.headers['x-chunked-output']

      if (stream && !buffer) return cb(null, res)
      if (chunkedObjects && buffer) return cb(null, res)

      res.on('data', function (chunk) {
        if (!chunkedObjects) {
          data += chunk
          return data
        }

        try {
          var obj = JSON.parse(chunk.toString())
          objects.push(obj)
        } catch(e) {
          chunkedObjects = false
          data += chunk
        }
      })
      res.on('end', function () {
        var parsed

        if (!chunkedObjects) {
          try {
            parsed = JSON.parse(data)
            data = parsed
          } catch (e) {}
        } else {
          data = objects
        }

        if (res.statusCode >= 400 || !res.statusCode) {
          if (!data) data = new Error()
          return cb(data, null)
        }
        return cb(null, data)
      })
      res.on('error', function (err) {
        return cb(err, null)
      })
    })

    if (stream) {
      stream.pipe(req)
    } else {
      req.end()
    }

    return req
  }

  function getFileStream (files) {
    if (!files) return null
    if (!Array.isArray(files)) files = [files]

    var file

    for (var i = 0; i < files.length; i++) {
      file = files[i]

      if (typeof file === 'string') {
        file = new File({
          cwd: path.dirname(file),
          base: path.dirname(file),
          path: file,
          contents: fs.createReadStream(file)
        })
      } else if (Buffer.isBuffer(file)) {
        file = new File({
          cwd: '/',
          base: '/',
          path: '/',
          contents: file
        })
      } else if (file instanceof stream.Stream) {
        file = new File({
          cwd: '/',
          base: '/',
          path: '/',
          contents: file
        })
      } else if (!file instanceof File) {
        return null
      }

      files[i] = file
    }

    return MultipartDir(files)
  }

  function command (name) {
    return function (cb) {
      return send(name, null, null, null, cb)
    }
  }

  function argCommand (name) {
    return function (arg, cb) {
      return send(name, arg, null, null, cb)
    }
  }

  return {
    send: send,

    add: function (file, cb) {
      return send('add', null, null, file, cb)
    },
    cat: argCommand('cat'),
    ls: argCommand('ls'),

    config: {
      get: argCommand('config'),
      set: function (key, value, cb) {
        return send('config', [key, value], null, null, cb)
      },
      show: function (cb) {
        return send('config/show', null, null, null, true, cb)
      },
      replace: function (file, cb) {
        return send('config/replace', null, null, file, cb)
      }
    },

    update: {
      apply: command('update'),
      check: command('update/check'),
      log: command('update/log')
    },
    version: command('version'),
    commands: command('commands'),

    mount: function (ipfs, ipns, cb) {
      if (typeof ipfs === 'function') {
        cb = ipfs
        ipfs = null
      } else if (typeof ipns === 'function') {
        cb = ipns
        ipns = null
      }
      var opts = {}
      if (ipfs) opts.f = ipfs
      if (ipns) opts.n = ipns
      return send('mount', null, opts, null, cb)
    },

    diag: {
      net: command('diag/net')
    },

    block: {
      get: argCommand('block/get'),
      put: function (file, cb) {
        if (Array.isArray(file)) {
          return cb(null, new Error('block.put() only accepts 1 file'))
        }
        return send('block/put', null, null, file, cb)
      }
    },

    object: {
      get: argCommand('object/get'),
      put: function (file, encoding, cb) {
        if (typeof encoding === 'function') {
          return cb(null, new Error("Must specify an object encoding ('json' or 'protobuf')"))
        }
        return send('object/put', encoding, null, file, cb)
      },
      data: argCommand('object/data'),
      stat: argCommand('object/stat'),
      links: argCommand('object/links')
    },

    swarm: {
      peers: command('swarm/peers'),
      connect: argCommand('swarm/peers')
    },
    ping: function (id, cb) {
      return send('ping', id, { n: 1 }, null, function (err, res) {
        if (err) return cb(err, null)
        cb(null, res[1])
      })
    },

    id: function (id, cb) {
      if (typeof id === 'function') {
        cb = id
        id = null
      }
      return send('id', id, null, null, cb)
    },
    pin: {
      add: function (hash, opts, cb) {
        if (typeof opts === 'function') {
          cb = opts
          opts = null
        }

        send('pin/add', hash, opts, null, cb)
      },
      remove: function (hash, opts, cb) {
        if (typeof opts === 'function') {
          cb = opts
          opts = null
        }

        send('pin/rm', hash, opts, null, cb)
      },
      list: function (type, cb) {
        if (typeof type === 'function') {
          cb = type
          type = null
        }
        var opts = null
        if (type) opts = { type: type }
        return send('pin/ls', null, opts, null, cb)
      }
    },

    gateway: {
      enable: command('gateway/enable'),
      disable: command('gateway/disable')
    },

    log: {
      tail: function (cb) {
        return send('log/tail', null, {enc: 'text'}, null, true, cb)
      }
    },

    name: {
      resolve: argCommand('name/resolve')
    },

    dht: {
      findprovs: argCommand('dht/findprovs'),

      get: function (key, opts, cb) {
        if (typeof (opts) === 'function' && !cb) {
          cb = opts
          opts = null
        }

        return send('dht/get', key, opts, null, function (err, res) {
          if (err || !res) return cb(err)
          if (res[0].Type === 5) {
            cb(null, res[0].Extra)
          } else {
            cb(res)
          }
        })
      },

      put: function (key, value, opts, cb) {
        if (typeof (opts) === 'function' && !cb) {
          cb = opts
          opts = null
        }

        return send('dht/put', [key, value], opts, null, cb)
      }
    }
  }
}
<commit_msg>improved dht.get error reporting<commit_after>'use strict'

var fs = require('fs')
var path = require('path')
var http = require('http')
var qs = require('querystring')
var multiaddr = require('multiaddr')
var File = require('vinyl')
var MultipartDir = require('./multipartdir.js')
var stream = require('stream')

var pkg
try {
  pkg = JSON.parse(fs.readFileSync(__dirname + '/package.json'))
} catch(e) {
  pkg = { name: 'ipfs-api-browserify', version: '?' }
}

var API_PATH = '/api/v0/'

module.exports = function (host_or_multiaddr, port) {
  var host
  try {
    var maddr = multiaddr(host_or_multiaddr).nodeAddress()
    host = maddr.address
    port = maddr.port
  } catch (e) {
    host = host_or_multiaddr
  }

  if (!host) host = 'localhost'
  if (!port) port = 5001

  function send (path, args, opts, files, buffer, cb) {
    var query, stream, contentType
    contentType = 'application/json'

    if (Array.isArray(path)) path = path.join('/')

    opts = opts || {}

    if (args && !Array.isArray(args)) args = [args]
    if (args) opts.arg = args

    opts['stream-channels'] = true
    query = qs.stringify(opts)

    if (files) {
      stream = getFileStream(files)
      contentType = 'multipart/form-data; boundary=' + stream.boundary
    }

    if (typeof buffer === 'function') {
      cb = buffer
      buffer = false
    }

    var req = http.request({
      method: files ? 'POST' : 'GET',
      host: host,
      port: port,
      path: API_PATH + path + '?' + query,
      headers: {
        'User-Agent': '/node-' + pkg.name + '/' + pkg.version + '/',
        'Content-Type': contentType
      },
      withCredentials: false
    }, function (res) {
      var data = ''
      var objects = []
      var stream = !!res.headers['x-stream-output']
      var chunkedObjects = !!res.headers['x-chunked-output']

      if (stream && !buffer) return cb(null, res)
      if (chunkedObjects && buffer) return cb(null, res)

      res.on('data', function (chunk) {
        if (!chunkedObjects) {
          data += chunk
          return data
        }

        try {
          var obj = JSON.parse(chunk.toString())
          objects.push(obj)
        } catch(e) {
          chunkedObjects = false
          data += chunk
        }
      })
      res.on('end', function () {
        var parsed

        if (!chunkedObjects) {
          try {
            parsed = JSON.parse(data)
            data = parsed
          } catch (e) {}
        } else {
          data = objects
        }

        if (res.statusCode >= 400 || !res.statusCode) {
          if (!data) data = new Error()
          return cb(data, null)
        }
        return cb(null, data)
      })
      res.on('error', function (err) {
        return cb(err, null)
      })
    })

    if (stream) {
      stream.pipe(req)
    } else {
      req.end()
    }

    return req
  }

  function getFileStream (files) {
    if (!files) return null
    if (!Array.isArray(files)) files = [files]

    var file

    for (var i = 0; i < files.length; i++) {
      file = files[i]

      if (typeof file === 'string') {
        file = new File({
          cwd: path.dirname(file),
          base: path.dirname(file),
          path: file,
          contents: fs.createReadStream(file)
        })
      } else if (Buffer.isBuffer(file)) {
        file = new File({
          cwd: '/',
          base: '/',
          path: '/',
          contents: file
        })
      } else if (file instanceof stream.Stream) {
        file = new File({
          cwd: '/',
          base: '/',
          path: '/',
          contents: file
        })
      } else if (!file instanceof File) {
        return null
      }

      files[i] = file
    }

    return MultipartDir(files)
  }

  function command (name) {
    return function (cb) {
      return send(name, null, null, null, cb)
    }
  }

  function argCommand (name) {
    return function (arg, cb) {
      return send(name, arg, null, null, cb)
    }
  }

  return {
    send: send,

    add: function (file, cb) {
      return send('add', null, null, file, cb)
    },
    cat: argCommand('cat'),
    ls: argCommand('ls'),

    config: {
      get: argCommand('config'),
      set: function (key, value, cb) {
        return send('config', [key, value], null, null, cb)
      },
      show: function (cb) {
        return send('config/show', null, null, null, true, cb)
      },
      replace: function (file, cb) {
        return send('config/replace', null, null, file, cb)
      }
    },

    update: {
      apply: command('update'),
      check: command('update/check'),
      log: command('update/log')
    },
    version: command('version'),
    commands: command('commands'),

    mount: function (ipfs, ipns, cb) {
      if (typeof ipfs === 'function') {
        cb = ipfs
        ipfs = null
      } else if (typeof ipns === 'function') {
        cb = ipns
        ipns = null
      }
      var opts = {}
      if (ipfs) opts.f = ipfs
      if (ipns) opts.n = ipns
      return send('mount', null, opts, null, cb)
    },

    diag: {
      net: command('diag/net')
    },

    block: {
      get: argCommand('block/get'),
      put: function (file, cb) {
        if (Array.isArray(file)) {
          return cb(null, new Error('block.put() only accepts 1 file'))
        }
        return send('block/put', null, null, file, cb)
      }
    },

    object: {
      get: argCommand('object/get'),
      put: function (file, encoding, cb) {
        if (typeof encoding === 'function') {
          return cb(null, new Error("Must specify an object encoding ('json' or 'protobuf')"))
        }
        return send('object/put', encoding, null, file, cb)
      },
      data: argCommand('object/data'),
      stat: argCommand('object/stat'),
      links: argCommand('object/links')
    },

    swarm: {
      peers: command('swarm/peers'),
      connect: argCommand('swarm/peers')
    },
    ping: function (id, cb) {
      return send('ping', id, { n: 1 }, null, function (err, res) {
        if (err) return cb(err, null)
        cb(null, res[1])
      })
    },

    id: function (id, cb) {
      if (typeof id === 'function') {
        cb = id
        id = null
      }
      return send('id', id, null, null, cb)
    },
    pin: {
      add: function (hash, opts, cb) {
        if (typeof opts === 'function') {
          cb = opts
          opts = null
        }

        send('pin/add', hash, opts, null, cb)
      },
      remove: function (hash, opts, cb) {
        if (typeof opts === 'function') {
          cb = opts
          opts = null
        }

        send('pin/rm', hash, opts, null, cb)
      },
      list: function (type, cb) {
        if (typeof type === 'function') {
          cb = type
          type = null
        }
        var opts = null
        if (type) opts = { type: type }
        return send('pin/ls', null, opts, null, cb)
      }
    },

    gateway: {
      enable: command('gateway/enable'),
      disable: command('gateway/disable')
    },

    log: {
      tail: function (cb) {
        return send('log/tail', null, {enc: 'text'}, null, true, cb)
      }
    },

    name: {
      resolve: argCommand('name/resolve')
    },

    dht: {
      findprovs: argCommand('dht/findprovs'),

      get: function (key, opts, cb) {
        if (typeof (opts) === 'function' && !cb) {
          cb = opts
          opts = null
        }

        return send('dht/get', key, opts, null, function (err, res) {
          if (err) return cb(err)
          if (!res) return cb(new Error('empty response'))
          if (res.length === 0) return cb(new Error('no value returned for key'))

          if (res[0].Type === 5) {
            cb(null, res[0].Extra)
          } else {
            cb(res)
          }
        })
      },

      put: function (key, value, opts, cb) {
        if (typeof (opts) === 'function' && !cb) {
          cb = opts
          opts = null
        }

        return send('dht/put', [key, value], opts, null, cb)
      }
    }
  }
}
<|endoftext|>


<commit_before>var PlayMusic = require('playmusic');
var creds = require(process.env.HOME + '/.googlePlayCreds.json');

var https = require('https');
var http = require('http');

var songCachePath = __dirname + '/songCache';
var fs = require('fs');
if(!fs.existsSync(songCachePath))
    fs.mkdirSync(songCachePath);

var express = require('express');
var bodyParser = require('body-parser');
var app = express();

var probe = require('node-ffprobe');

var gmusicDownload = function(startUrl, songID, callback, errCallback) {
    var doDownload = function(streamUrl) {
        var req = https.request(streamUrl, function(res) {
            console.log('downloading song ' + songID);
            var filePath = songCachePath + '/' + songID;
            var songFd = fs.openSync(filePath, 'w');

            res.on('data', function(chunk) {
                fs.writeSync(songFd, chunk, 0, chunk.length, null);
                //player.stdin.write(chunk);
            });
            res.on('end', function() {
                if(res.statusCode === 302) { // redirect
                    console.log('redirected. retrying with new URL');
                    fs.closeSync(songFd);
                    fs.unlinkSync(songCachePath + '/' + songID);
                    gmusicDownload(res.headers.location, songID, callback, errCallback);
                } else if(res.statusCode === 200) {
                    console.log('download finished ' + songID);
                    fs.closeSync(songFd);
                    if(callback)
                        callback(songID);
                    //player.stdin.end();
                } else {
                    console.log('ERROR: unknown status code ' + res.statusCode);
                    fs.closeSync(songFd);
                    fs.unlinkSync(songCachePath + '/' + songID);
                    if(errCallback)
                        errCallback(songID);
                }
            });
        });
        req.on('error', function(e) {
            console.log('error ' + e + ' while fetching! reconnecting in 5s...');
            setTimeout(function() {
                initPm(function() {
                    console.log('error while fetching! now reconnected to gmusic');
                    pm.getStreamUrl(songID, function(streamUrl) {
                        gmusicDownload(streamUrl, songID, callback, errCallback);
                    });
                });
            }, 5000);
        });
        req.end();
    };

    if(startUrl)
        doDownload(startUrl);
    else
        pm.getStreamUrl(songID, doDownload);
};

var getAudio = function(songID, callback, errCallback) {
    var filePath = songCachePath + '/' + songID;

    if(fs.existsSync(filePath)) {
        // song was found from cache
        //console.log('fetched song from cache ' + songID);
        if(callback)
            callback(songID);
        return;
    } else {
        // song had to be downloaded
        gmusicDownload(null, songID, callback, errCallback);
    }
};

// to be called whenever the queue has been modified
// this function will:
// - play back the first song in the queue if no song is playing
// - precache first and second songs in the queue
var queueCheck = function() {
    if(!queue.length) {
        console.log('end of queue, waiting for more songs');
        return;
    }

    var startedPlayingNext = false;
    if(!nowPlaying) {
        // play song
        nowPlaying = queue.shift();
        cleanupSong(nowPlaying.id);
        startedPlayingNext = true;

        for (var i = 0; i < queue.length; i++) {
            queue[i].oldness++;

            // remove bad songs
            var numDownVotes = Object.keys(queue[i].downVotes).length;
            var numUpVotes = Object.keys(queue[i].upVotes).length;
            if(numDownVotes > numUpVotes) {
                console.log('song ' + queue[i].id + ' removed due to downvotes');
                cleanupSong(queue[i].id);
            }
        }
    }

    getAudio(nowPlaying.id, function(songID) {
        //console.log(songID + ' success cb called');

        if(startedPlayingNext) {
            var filePath = songCachePath + '/' + songID;
            probe(filePath, function(err, probeData) {
                console.log('playing song: ' + nowPlaying.id);
                io.emit('playback', {songID: nowPlaying.id});
                nowPlaying.playbackStart = new Date();

                setTimeout(function() {
                    console.log('DEBUG: playback stopped');
                    nowPlaying = null;
                    queueCheck();
                }, (probeData.format.duration + 1) * 1000);
            });
        }

        // pre-cache next song in queue
        if(queue.length) {
            getAudio(queue[0].id, function(songID) {
                //console.log('successfully pre-cached ' + songID);
            }, function(songID) {
                console.log('error while pre-caching ' + songID);
                cleanupSong(songID);
            });
        } else {
            console.log('nothing to pre-cache');
        }
    }, function(songID) {
        cleanupSong(songID);
        console.log(songID + ' error cb called');
    });
};

var queue = [];
var nowPlaying;

var sortQueue = function() {
    queue.sort(function(a, b) {
        return ((b.oldness + Object.keys(b.upVotes).length - Object.keys(b.downVotes).length) -
               (a.oldness + Object.keys(a.upVotes).length - Object.keys(a.downVotes).length));
    });
};

// find song from queue, if not found then create it
var searchQueue = function(songID) {
    for(var i = 0; i < queue.length; i++) {
        if(queue[i].id === songID)
            return queue[i];
    }

    if(nowPlaying && nowPlaying.id === songID)
        return nowPlaying;

    return null;
};

var cleanupSong = function(songID) {
    for(var i = 0; i < queue.length; i++) {
        if(queue[i].id === songID)
            queue.splice(i, 1);
    }
};

var createSong = function(song) {
    song.upVotes = {};
    song.downVotes = {};
    song.oldness = 0; // favor old songs
    song.playbackStart = null;

    queue.push(song);
    queueCheck();
    return song;
};

var voteSong = function(song, vote, userID) {
    // normalize vote to -1, 0, 1
    vote = parseInt(vote);

    if(vote)
        vote = vote / Math.abs(vote);
    else
        vote = 0;

    if(!vote) {
        delete(song.upVotes[userID]);
        delete(song.downVotes[userID]);
    } else if (vote > 0) {
        delete(song.downVotes[userID]);
        song.upVotes[userID] = true;
    } else if (vote < 0) {
        delete(song.upVotes[userID]);
        song.downVotes[userID] = true;
    }

    sortQueue();
    queueCheck();
};

app.post('/vote/:id', bodyParser.json(), function(req, res) {
    var userID = req.body.userID;
    var vote = req.body.vote;
    var songID = req.params.id;
    if(!userID || vote === undefined || !songID) {
        res.status(404).send('please provide both userID and vote in the body');
    }

    var queuedSong = searchQueue(songID);
    if(!queuedSong) {
        res.status(404).send('song not found');
    }

    voteSong(queuedSong, vote, userID);

    console.log('got vote ' + vote + ' for song: ' + queuedSong.id);

    res.send('success');
});

// get entire queue
app.get('/queue', function(req, res) {
    var response = [];
    if(nowPlaying) {
        response.push({
            artist: nowPlaying.artist,
            title: nowPlaying.title,
            duration: nowPlaying.duration,
            id: nowPlaying.id,
            downVotes: nowPlaying.downVotes,
            upVotes: nowPlaying.upVotes,
            oldness: nowPlaying.oldness
        });
    }
    for(var i = 0; i < queue.length; i++) {
        response.push({
            artist: queue[i].artist,
            title: queue[i].title,
            duration: queue[i].duration,
            id: queue[i].id,
            downVotes: queue[i].downVotes,
            upVotes: queue[i].upVotes,
            oldness: queue[i].oldness
        });
    }
    res.send(JSON.stringify(response));
});

// queue song
app.post('/queue', bodyParser.json(), function(req, res) {
    var song = req.body.song;

    // check that required fields are provided
    if(!song.title || !song.id || !song.duration) {
        res.status(404).send('invalid song object');
    }

    // check that user has an id
    var userID = req.body.userID;
    if(!userID) {
        res.status(404).send('invalid userID');
    }

    // check if the song is already queued
    var queuedSong = searchQueue(song.id);
    if(!queuedSong)
        queuedSong = createSong(song);

    voteSong(queuedSong, +1, userID);

    console.log('added song to queue: ' + queuedSong.id);
    res.send('success');
});

// search for song with given search terms
app.get('/search/:terms', function(req, res) {
    console.log('got search request: ' + req.params.terms);
    pm.search(req.params.terms, 10, function(data) {
        var songs = [];

        if(data.entries) {
            songs = data.entries.sort(function(a, b) {
                return a.score < b.score; // sort by score
            }).filter(function(entry) {
                return entry.type === '1'; // songs only, no albums/artists
            });

            for(var i = 0; i < songs.length; i++) {
                songs[i] = {
                    artist: songs[i].track.artist,
                    title: songs[i].track.title,
                    duration: songs[i].track.durationMillis,
                    id: songs[i].track.nid
                };
            }
        }

        res.send(JSON.stringify(songs));
    }, function(err) {
        console.log(err);
        res.status(404).send(err);
    });
});

app.use('/song', express.static(songCachePath));
app.use(express.static(__dirname + '/public'));

var server = app.listen(process.env.PORT || 8080);
console.log('listening on port ' + (process.env.PORT || 8080));

var io = require('socket.io')(server);
io.on('connection', function(socket) {
    if(nowPlaying) {
        socket.emit('playback', {
            songID: nowPlaying.id,
            position: new Date() - nowPlaying.playbackStart
        });
    }
});

var pm = new PlayMusic();
var initPm = function(callback) {
    pm.init(creds, callback);
};
initPm(function() {
    console.log('google play music initialized');
});
<commit_msg>fix silly bug causing double downloading<commit_after>var PlayMusic = require('playmusic');
var creds = require(process.env.HOME + '/.googlePlayCreds.json');

var https = require('https');
var http = require('http');

var songCachePath = __dirname + '/songCache';
var fs = require('fs');
if(!fs.existsSync(songCachePath))
    fs.mkdirSync(songCachePath);

var express = require('express');
var bodyParser = require('body-parser');
var app = express();

var probe = require('node-ffprobe');

var gmusicDownload = function(startUrl, songID, callback, errCallback) {
    var doDownload = function(streamUrl) {
        console.log('downloading song ' + songID);
        var filePath = songCachePath + '/' + songID;
        var songFd = fs.openSync(filePath, 'w');

        var req = https.request(streamUrl, function(res) {

            res.on('data', function(chunk) {
                fs.writeSync(songFd, chunk, 0, chunk.length, null);
                //player.stdin.write(chunk);
            });
            res.on('end', function() {
                if(res.statusCode === 302) { // redirect
                    console.log('redirected. retrying with new URL');
                    fs.closeSync(songFd);
                    fs.unlinkSync(songCachePath + '/' + songID);
                    gmusicDownload(res.headers.location, songID, callback, errCallback);
                } else if(res.statusCode === 200) {
                    console.log('download finished ' + songID);
                    fs.closeSync(songFd);
                    if(callback)
                        callback(songID);
                    //player.stdin.end();
                } else {
                    console.log('ERROR: unknown status code ' + res.statusCode);
                    fs.closeSync(songFd);
                    fs.unlinkSync(songCachePath + '/' + songID);
                    if(errCallback)
                        errCallback(songID);
                }
            });
        });
        req.on('error', function(e) {
            console.log('error ' + e + ' while fetching! reconnecting in 5s...');
            setTimeout(function() {
                initPm(function() {
                    console.log('error while fetching! now reconnected to gmusic');
                    pm.getStreamUrl(songID, function(streamUrl) {
                        gmusicDownload(streamUrl, songID, callback, errCallback);
                    });
                });
            }, 5000);
        });
        req.end();
    };

    if(startUrl) {
        doDownload(startUrl);
    } else {
        pm.getStreamUrl(songID, function(streamUrl) {
            doDownload(streamUrl);
        });
    }
};

var getAudio = function(songID, callback, errCallback) {
    var filePath = songCachePath + '/' + songID;

    if(fs.existsSync(filePath)) {
        // song was found from cache
        if(callback)
            callback(songID);
        return;
    } else {
        // song had to be downloaded
        gmusicDownload(null, songID, callback, errCallback);
    }
};

// to be called whenever the queue has been modified
// this function will:
// - play back the first song in the queue if no song is playing
// - precache first and second songs in the queue
var queueCheck = function() {
    if(!queue.length) {
        console.log('end of queue, waiting for more songs');
        return;
    }

    var startedPlayingNext = false;
    if(!nowPlaying) {
        // play song
        nowPlaying = queue.shift();
        cleanupSong(nowPlaying.id);
        startedPlayingNext = true;

        for (var i = 0; i < queue.length; i++) {
            queue[i].oldness++;

            // remove bad songs
            var numDownVotes = Object.keys(queue[i].downVotes).length;
            var numUpVotes = Object.keys(queue[i].upVotes).length;
            if(numDownVotes > numUpVotes) {
                console.log('song ' + queue[i].id + ' removed due to downvotes');
                cleanupSong(queue[i].id);
            }
        }
    }

    getAudio(nowPlaying.id, function(songID) {
        if(startedPlayingNext) {
            var filePath = songCachePath + '/' + songID;
            probe(filePath, function(err, probeData) {
                console.log('playing song: ' + nowPlaying.id);
                io.emit('playback', {songID: nowPlaying.id});
                nowPlaying.playbackStart = new Date();

                setTimeout(function() {
                    nowPlaying = null;
                    queueCheck();
                }, (probeData.format.duration + 1) * 1000);
            });
        }

        // pre-cache next song in queue
        if(queue.length) {
            getAudio(queue[0].id, function(songID) {
                console.log('successfully pre-cached ' + songID);
            }, function(songID) {
                console.log('error while pre-caching ' + songID);
                cleanupSong(songID);
            });
        } else {
            console.log('nothing to pre-cache');
        }
    }, function(songID) {
        cleanupSong(songID);
        console.log(songID + ' error cb called');
    });
};

var queue = [];
var nowPlaying;

var sortQueue = function() {
    queue.sort(function(a, b) {
        return ((b.oldness + Object.keys(b.upVotes).length - Object.keys(b.downVotes).length) -
               (a.oldness + Object.keys(a.upVotes).length - Object.keys(a.downVotes).length));
    });
};

// find song from queue, if not found then create it
var searchQueue = function(songID) {
    for(var i = 0; i < queue.length; i++) {
        if(queue[i].id === songID)
            return queue[i];
    }

    if(nowPlaying && nowPlaying.id === songID)
        return nowPlaying;

    return null;
};

var cleanupSong = function(songID) {
    for(var i = 0; i < queue.length; i++) {
        if(queue[i].id === songID)
            queue.splice(i, 1);
    }
};

var createSong = function(song) {
    song.upVotes = {};
    song.downVotes = {};
    song.oldness = 0; // favor old songs
    song.playbackStart = null;

    queue.push(song);
    return song;
};

var voteSong = function(song, vote, userID) {
    // normalize vote to -1, 0, 1
    vote = parseInt(vote);

    if(vote)
        vote = vote / Math.abs(vote);
    else
        vote = 0;

    if(!vote) {
        delete(song.upVotes[userID]);
        delete(song.downVotes[userID]);
    } else if (vote > 0) {
        delete(song.downVotes[userID]);
        song.upVotes[userID] = true;
    } else if (vote < 0) {
        delete(song.upVotes[userID]);
        song.downVotes[userID] = true;
    }

    sortQueue();
};

app.post('/vote/:id', bodyParser.json(), function(req, res) {
    var userID = req.body.userID;
    var vote = req.body.vote;
    var songID = req.params.id;
    if(!userID || vote === undefined || !songID) {
        res.status(404).send('please provide both userID and vote in the body');
    }

    var queuedSong = searchQueue(songID);
    if(!queuedSong) {
        res.status(404).send('song not found');
    }

    voteSong(queuedSong, vote, userID);
    queueCheck();

    console.log('got vote ' + vote + ' for song: ' + queuedSong.id);

    res.send('success');
});

// get entire queue
app.get('/queue', function(req, res) {
    var response = [];
    if(nowPlaying) {
        response.push({
            artist: nowPlaying.artist,
            title: nowPlaying.title,
            duration: nowPlaying.duration,
            id: nowPlaying.id,
            downVotes: nowPlaying.downVotes,
            upVotes: nowPlaying.upVotes,
            oldness: nowPlaying.oldness
        });
    }
    for(var i = 0; i < queue.length; i++) {
        response.push({
            artist: queue[i].artist,
            title: queue[i].title,
            duration: queue[i].duration,
            id: queue[i].id,
            downVotes: queue[i].downVotes,
            upVotes: queue[i].upVotes,
            oldness: queue[i].oldness
        });
    }
    res.send(JSON.stringify(response));
});

// queue song
app.post('/queue', bodyParser.json(), function(req, res) {
    var song = req.body.song;

    // check that required fields are provided
    if(!song.title || !song.id || !song.duration) {
        res.status(404).send('invalid song object');
    }

    // check that user has an id
    var userID = req.body.userID;
    if(!userID) {
        res.status(404).send('invalid userID');
    }

    // check if the song is already queued
    var queuedSong = searchQueue(song.id);
    if(!queuedSong)
        queuedSong = createSong(song);

    voteSong(queuedSong, +1, userID);
    queueCheck();

    console.log('added song to queue: ' + queuedSong.id);
    res.send('success');
});

// search for song with given search terms
app.get('/search/:terms', function(req, res) {
    console.log('got search request: ' + req.params.terms);
    pm.search(req.params.terms, 10, function(data) {
        var songs = [];

        if(data.entries) {
            songs = data.entries.sort(function(a, b) {
                return a.score < b.score; // sort by score
            }).filter(function(entry) {
                return entry.type === '1'; // songs only, no albums/artists
            });

            for(var i = 0; i < songs.length; i++) {
                songs[i] = {
                    artist: songs[i].track.artist,
                    title: songs[i].track.title,
                    duration: songs[i].track.durationMillis,
                    id: songs[i].track.nid
                };
            }
        }

        res.send(JSON.stringify(songs));
    }, function(err) {
        console.log(err);
        res.status(404).send(err);
    });
});

app.use('/song', express.static(songCachePath));
app.use(express.static(__dirname + '/public'));

var server = app.listen(process.env.PORT || 8080);
console.log('listening on port ' + (process.env.PORT || 8080));

var io = require('socket.io')(server);
io.on('connection', function(socket) {
    if(nowPlaying) {
        socket.emit('playback', {
            songID: nowPlaying.id,
            position: new Date() - nowPlaying.playbackStart
        });
    }
});

var pm = new PlayMusic();
var initPm = function(callback) {
    pm.init(creds, callback);
};
initPm(function() {
    console.log('google play music initialized');
});
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import {
  StyleSheet,
  Animated,
  TouchableWithoutFeedback,
  PanResponder,
  View
} from 'react-native'

import _ from 'lodash'

// Default values
const ITEMS_PER_ROW                   = 4
const DRAG_ACTIVATION_TRESHOLD        = 200 // Milliseconds
const BLOCK_TRANSITION_DURATION       = 300 // Milliseconds
const ACTIVE_BLOCK_CENTERING_DURATION = 200 // Milliseconds

class DraggableGrid extends Component {

  constructor() {
    super()

    this.blockTransitionDuration      = BLOCK_TRANSITION_DURATION
    this.activeBlockCenteringDuration = ACTIVE_BLOCK_CENTERING_DURATION
    this.itemsPerRow                  = ITEMS_PER_ROW
    this.dragActivationTreshold       = DRAG_ACTIVATION_TRESHOLD
    this.onDragRelease                = () => {}
    this.onDragStart                  = () => {}

    this.itemOrder         = null
    this.dragPosition      = null
    this.activeBlockOffset = null
    this.rows              = null
    this.ghostBlocks       = []

    this.state = {
      gridLayout: null,
      blockPositions: [],
      startDragWiggle: new Animated.Value(0),
      activeBlock: null,
      blockWidth: null
    }
  }

  componentWillMount = () => this.createTouchHandlers()

  componentDidMount = () => this.handleNewProps(this.props)

  componentWillReceiveProps = (properties) => this.handleNewProps(properties)

  handleNewProps = (properties) => {
    this._assignReceivedPropertiesIntoThis(properties)
    this.itemOrder = _.map(properties.children, ({key, ref}, index) => {
      return { key, ref, order: index }
    })
    this.rows = Math.ceil(properties.children.length / this.itemsPerRow)
  }

  onStartDrag = (evt, gestureState) => {
    if (this.state.activeBlock != null) {
      let activeBlockPosition = this.state.blockPositions[ this.state.activeBlock ].origin
      let x = activeBlockPosition.x - gestureState.x0
      let y = activeBlockPosition.y - gestureState.y0
      this.activeBlockOffset = { x, y }
      this.state.blockPositions[this.state.activeBlock].currentPosition.setOffset({ x, y })
      this.state.blockPositions[this.state.activeBlock].currentPosition.setValue({ x: gestureState.moveX, y: gestureState.moveY })
    }
  }

  onMoveBlock = (evt, {moveX, moveY}) => {
    if (this.state.activeBlock != null) {

      let yChokeAmount = Math.max(0, (this.activeBlockOffset.y + moveY) - (this.state.gridLayout.height - this.blockWidth))
      let xChokeAmount = Math.max(0, (this.activeBlockOffset.x + moveX) - (this.state.gridLayout.width - this.blockWidth))
      let yMinChokeAmount = Math.min(0, this.activeBlockOffset.y + moveY)
      let xMinChokeAmount = Math.min(0, this.activeBlockOffset.x + moveX)

      let dragPosition = { x: moveX - xChokeAmount - xMinChokeAmount, y: moveY - yChokeAmount - yMinChokeAmount }
      this.dragPosition = dragPosition
      let originalPosition = this.state.blockPositions[ this.state.activeBlock ].origin
      let distanceToOrigin = this._getDistanceTo(originalPosition)
      this.state.blockPositions[this.state.activeBlock].currentPosition.setValue(dragPosition)

      let closest = this.state.activeBlock
      let closestDistance = distanceToOrigin
      this.state.blockPositions.forEach( (block, index) => {
        if (index !== this.state.activeBlock) {
          let blockPosition = block.origin
          let distance = this._getDistanceTo(blockPosition)
          if (distance < closestDistance) {
            closest = index
            closestDistance = distance
          }
        }
      })

      this.ghostBlocks.forEach( ghostBlockPosition => {
        let distance = this._getDistanceTo(ghostBlockPosition)
        if (distance < closestDistance) {
          closest = this.state.activeBlock
          closestDistance = distance
        }
      })

      if (closest !== this.state.activeBlock) {
        Animated.timing(
          this.state.blockPositions[closest].currentPosition,
          {
            toValue: this.state.blockPositions[this.state.activeBlock].origin,
            duration: this.blockTransitionDuration
          }
        ).start()
        let blockPositions = this.state.blockPositions
        blockPositions[this.state.activeBlock].origin = blockPositions[closest].origin
        blockPositions[closest].origin = originalPosition
        this.setState({ blockPositions })

        var tempOrderIndex = this.itemOrder[this.state.activeBlock].order
        this.itemOrder[this.state.activeBlock].order = this.itemOrder[closest].order
        this.itemOrder[closest].order = tempOrderIndex
      }
    }
  }

  onReleaseBlock = (evt, gestureState) => {
    if (this.state.activeBlock != null) {
      this.state.blockPositions[this.state.activeBlock].currentPosition.flattenOffset()
      Animated.timing(
        this.state.blockPositions[this.state.activeBlock].currentPosition,
        { toValue: this.state.blockPositions[this.state.activeBlock].origin, duration: this.activeBlockCenteringDuration }
      ).start()
      this.setState({activeBlock: null})
      let itemOrder = _.sortBy(this.itemOrder, item=>item.order)
      this.onDragRelease( {itemOrder} )
    }
  }

  assessGridSize = ({nativeEvent}) => {
    this.blockWidth = nativeEvent.layout.width / this.itemsPerRow
    this.setState({
      gridLayout: nativeEvent.layout,
      blockWidth: this.blockWidth
    })
  }

  saveBlockPositions = (key) => ({nativeEvent}) => {
    if (this.state.blockPositions.length !== this.props.children.length) {
      let blockPositions = this.state.blockPositions;
      let thisPosition = {
        x: nativeEvent.layout.x,
        y: nativeEvent.layout.y
      }
      blockPositions[key] = {
        currentPosition : new Animated.ValueXY( thisPosition ),
        origin          : thisPosition
      }
      this.setState({ blockPositions })

      if (blockPositions.length == this.props.children.length) {
        this.setGhostPositions()
      }
    }
  }

  setGhostPositions = () => {
    let blockWidth = this.state.blockWidth
    let fullGridItemCount = this.rows * this.itemsPerRow
    let ghostBlockCount = fullGridItemCount - this.props.children.length
    let y = blockWidth * (this.rows - 1)
    let initialX =  blockWidth * (this.itemsPerRow - ghostBlockCount)

    for (let i = 0; i < ghostBlockCount; ++i) {
      let x = initialX + blockWidth * i
      this.ghostBlocks.push({x, y})
    }
  }

  activateDrag = (key) => () => {
    this.onDragStart()
    this.setState({activeBlock: key})
    this.state.startDragWiggle.setValue(20)
    Animated.spring(this.state.startDragWiggle, {
      toValue: 0,
      velocity: 2000,
      tension: 2000,
      friction: 5
    }).start()
  }

  render = () => {
    let gridLayout = this.state.gridLayout
    let blockWidth = this.state.blockWidth
    let blockPositionsSet = this.state.blockPositions.length == this.props.children.length

    return (
      <View
        style = {[
          styles.draggableGrid,
          this.props.style,
          { height: blockWidth * this.rows }
        ]}
        onLayout= { this.assessGridSize }
      >
        { gridLayout &&
          this.props.children.map( (item, key) =>
            <Animated.View
              key = {key}
              style = {[
                { width: blockWidth,
                  height: blockWidth },

                blockPositionsSet &&
                { position: 'absolute',
                  top: this.state.blockPositions[key].currentPosition.getLayout().top,
                  left: this.state.blockPositions[key].currentPosition.getLayout().left
                },

                this.state.activeBlock == key && this._blockActivationWiggle(),
                this.state.activeBlock == key && { zIndex: 1 }

                ]}
              onLayout = { this.saveBlockPositions(key) }
              {...this._panResponder.panHandlers}
            >
              <TouchableWithoutFeedback
                style        = {{ flex: 1, zIndex: key }}
                delayPressIn = { this.dragActivationTreshold }
                onPressIn    = { this.activateDrag(key) }>

                { item }

              </TouchableWithoutFeedback>
            </Animated.View>
          )
        }
      </View>
  )}

  // Helpers & other boring stuff

  _getDistanceTo = (point) => {
    let xDistance = this.dragPosition.x + this.activeBlockOffset.x - point.x
    let yDistance = this.dragPosition.y + this.activeBlockOffset.y - point.y
    return Math.sqrt( Math.pow(xDistance, 2) + Math.pow(yDistance, 2) )
  }

  _blockActivationWiggle = () => {
    return { transform: [{ rotate: this.state.startDragWiggle.interpolate({
      inputRange:  [0, 360],
      outputRange: ['0 deg', '360 deg']})}]}
  }

  _assignReceivedPropertiesIntoThis(properties) {
    Object.keys(properties).forEach(property => {
      if (this[property])
        this[property] = properties[property]
    })
  }

  createTouchHandlers = () =>
    this._panResponder = PanResponder.create({
      onPanResponderTerminate:             (evt, gestureState) => {},
      onStartShouldSetPanResponder:        (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponder:         (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture:  (evt, gestureState) => true,
      onShouldBlockNativeResponder:        (evt, gestureState) => false,
      onPanResponderTerminationRequest:    (evt, gestureState) => false,
      onPanResponderGrant:   this.onStartDrag,
      onPanResponderMove:    this.onMoveBlock,
      onPanResponderRelease: this.onReleaseBlock
    })
}

const styles = StyleSheet.create({
draggableGrid: {
  flex: 1,
  flexDirection: 'row',
  flexWrap: 'wrap'
}
})

module.exports = DraggableGrid
<commit_msg>Center the contents of a block<commit_after>import React, { Component } from 'react';
import {
  StyleSheet,
  Animated,
  TouchableWithoutFeedback,
  PanResponder,
  View
} from 'react-native'

import _ from 'lodash'

// Default values
const ITEMS_PER_ROW                   = 4
const DRAG_ACTIVATION_TRESHOLD        = 200 // Milliseconds
const BLOCK_TRANSITION_DURATION       = 300 // Milliseconds
const ACTIVE_BLOCK_CENTERING_DURATION = 200 // Milliseconds

class DraggableGrid extends Component {

  constructor() {
    super()

    this.blockTransitionDuration      = BLOCK_TRANSITION_DURATION
    this.activeBlockCenteringDuration = ACTIVE_BLOCK_CENTERING_DURATION
    this.itemsPerRow                  = ITEMS_PER_ROW
    this.dragActivationTreshold       = DRAG_ACTIVATION_TRESHOLD
    this.onDragRelease                = () => {}
    this.onDragStart                  = () => {}

    this.itemOrder         = null
    this.dragPosition      = null
    this.activeBlockOffset = null
    this.rows              = null
    this.ghostBlocks       = []

    this.state = {
      gridLayout: null,
      blockPositions: [],
      startDragWiggle: new Animated.Value(0),
      activeBlock: null,
      blockWidth: null
    }
  }

  componentWillMount = () => this.createTouchHandlers()

  componentDidMount = () => this.handleNewProps(this.props)

  componentWillReceiveProps = (properties) => this.handleNewProps(properties)

  handleNewProps = (properties) => {
    this._assignReceivedPropertiesIntoThis(properties)
    this.itemOrder = _.map(properties.children, ({key, ref}, index) => {
      return { key, ref, order: index }
    })
    this.rows = Math.ceil(properties.children.length / this.itemsPerRow)
  }

  onStartDrag = (evt, gestureState) => {
    if (this.state.activeBlock != null) {
      let activeBlockPosition = this.state.blockPositions[ this.state.activeBlock ].origin
      let x = activeBlockPosition.x - gestureState.x0
      let y = activeBlockPosition.y - gestureState.y0
      this.activeBlockOffset = { x, y }
      this.state.blockPositions[this.state.activeBlock].currentPosition.setOffset({ x, y })
      this.state.blockPositions[this.state.activeBlock].currentPosition.setValue({ x: gestureState.moveX, y: gestureState.moveY })
    }
  }

  onMoveBlock = (evt, {moveX, moveY}) => {
    if (this.state.activeBlock != null) {

      let yChokeAmount = Math.max(0, (this.activeBlockOffset.y + moveY) - (this.state.gridLayout.height - this.blockWidth))
      let xChokeAmount = Math.max(0, (this.activeBlockOffset.x + moveX) - (this.state.gridLayout.width - this.blockWidth))
      let yMinChokeAmount = Math.min(0, this.activeBlockOffset.y + moveY)
      let xMinChokeAmount = Math.min(0, this.activeBlockOffset.x + moveX)

      let dragPosition = { x: moveX - xChokeAmount - xMinChokeAmount, y: moveY - yChokeAmount - yMinChokeAmount }
      this.dragPosition = dragPosition
      let originalPosition = this.state.blockPositions[ this.state.activeBlock ].origin
      let distanceToOrigin = this._getDistanceTo(originalPosition)
      this.state.blockPositions[this.state.activeBlock].currentPosition.setValue(dragPosition)

      let closest = this.state.activeBlock
      let closestDistance = distanceToOrigin
      this.state.blockPositions.forEach( (block, index) => {
        if (index !== this.state.activeBlock) {
          let blockPosition = block.origin
          let distance = this._getDistanceTo(blockPosition)
          if (distance < closestDistance) {
            closest = index
            closestDistance = distance
          }
        }
      })

      this.ghostBlocks.forEach( ghostBlockPosition => {
        let distance = this._getDistanceTo(ghostBlockPosition)
        if (distance < closestDistance) {
          closest = this.state.activeBlock
          closestDistance = distance
        }
      })

      if (closest !== this.state.activeBlock) {
        Animated.timing(
          this.state.blockPositions[closest].currentPosition,
          {
            toValue: this.state.blockPositions[this.state.activeBlock].origin,
            duration: this.blockTransitionDuration
          }
        ).start()
        let blockPositions = this.state.blockPositions
        blockPositions[this.state.activeBlock].origin = blockPositions[closest].origin
        blockPositions[closest].origin = originalPosition
        this.setState({ blockPositions })

        var tempOrderIndex = this.itemOrder[this.state.activeBlock].order
        this.itemOrder[this.state.activeBlock].order = this.itemOrder[closest].order
        this.itemOrder[closest].order = tempOrderIndex
      }
    }
  }

  onReleaseBlock = (evt, gestureState) => {
    if (this.state.activeBlock != null) {
      this.state.blockPositions[this.state.activeBlock].currentPosition.flattenOffset()
      Animated.timing(
        this.state.blockPositions[this.state.activeBlock].currentPosition,
        { toValue: this.state.blockPositions[this.state.activeBlock].origin, duration: this.activeBlockCenteringDuration }
      ).start()
      this.setState({activeBlock: null})
      let itemOrder = _.sortBy(this.itemOrder, item=>item.order)
      this.onDragRelease( {itemOrder} )
    }
  }

  assessGridSize = ({nativeEvent}) => {
    this.blockWidth = nativeEvent.layout.width / this.itemsPerRow
    this.setState({
      gridLayout: nativeEvent.layout,
      blockWidth: this.blockWidth
    })
  }

  saveBlockPositions = (key) => ({nativeEvent}) => {
    if (this.state.blockPositions.length !== this.props.children.length) {
      let blockPositions = this.state.blockPositions;
      let thisPosition = {
        x: nativeEvent.layout.x,
        y: nativeEvent.layout.y
      }
      blockPositions[key] = {
        currentPosition : new Animated.ValueXY( thisPosition ),
        origin          : thisPosition
      }
      this.setState({ blockPositions })

      if (blockPositions.length == this.props.children.length) {
        this.setGhostPositions()
      }
    }
  }

  setGhostPositions = () => {
    let blockWidth = this.state.blockWidth
    let fullGridItemCount = this.rows * this.itemsPerRow
    let ghostBlockCount = fullGridItemCount - this.props.children.length
    let y = blockWidth * (this.rows - 1)
    let initialX =  blockWidth * (this.itemsPerRow - ghostBlockCount)

    for (let i = 0; i < ghostBlockCount; ++i) {
      let x = initialX + blockWidth * i
      this.ghostBlocks.push({x, y})
    }
  }

  activateDrag = (key) => () => {
    this.onDragStart()
    this.setState({activeBlock: key})
    this.state.startDragWiggle.setValue(20)
    Animated.spring(this.state.startDragWiggle, {
      toValue: 0,
      velocity: 2000,
      tension: 2000,
      friction: 5
    }).start()
  }

  render = () => {
    let gridLayout = this.state.gridLayout
    let blockWidth = this.state.blockWidth
    let blockPositionsSet = this.state.blockPositions.length == this.props.children.length

    return (
      <View
        style = {[
          styles.draggableGrid,
          this.props.style,
          { height: blockWidth * this.rows }
        ]}
        onLayout= { this.assessGridSize }
      >
        { gridLayout &&
          this.props.children.map( (item, key) =>
            <Animated.View
              key = {key}
              style = {[
                { width: blockWidth,
                  height: blockWidth,
                  justifyContent: 'center' },

                blockPositionsSet &&
                { position: 'absolute',
                  top: this.state.blockPositions[key].currentPosition.getLayout().top,
                  left: this.state.blockPositions[key].currentPosition.getLayout().left
                },

                this.state.activeBlock == key && this._blockActivationWiggle(),
                this.state.activeBlock == key && { zIndex: 1 }

                ]}
              onLayout = { this.saveBlockPositions(key) }
              {...this._panResponder.panHandlers}
            >
              <TouchableWithoutFeedback
                style        = {{ flex: 1, zIndex: key }}
                delayPressIn = { this.dragActivationTreshold }
                onPressIn    = { this.activateDrag(key) }>

                { item }

              </TouchableWithoutFeedback>
            </Animated.View>
          )
        }
      </View>
  )}

  // Helpers & other boring stuff

  _getDistanceTo = (point) => {
    let xDistance = this.dragPosition.x + this.activeBlockOffset.x - point.x
    let yDistance = this.dragPosition.y + this.activeBlockOffset.y - point.y
    return Math.sqrt( Math.pow(xDistance, 2) + Math.pow(yDistance, 2) )
  }

  _blockActivationWiggle = () => {
    return { transform: [{ rotate: this.state.startDragWiggle.interpolate({
      inputRange:  [0, 360],
      outputRange: ['0 deg', '360 deg']})}]}
  }

  _assignReceivedPropertiesIntoThis(properties) {
    Object.keys(properties).forEach(property => {
      if (this[property])
        this[property] = properties[property]
    })
  }

  createTouchHandlers = () =>
    this._panResponder = PanResponder.create({
      onPanResponderTerminate:             (evt, gestureState) => {},
      onStartShouldSetPanResponder:        (evt, gestureState) => true,
      onStartShouldSetPanResponderCapture: (evt, gestureState) => false,
      onMoveShouldSetPanResponder:         (evt, gestureState) => true,
      onMoveShouldSetPanResponderCapture:  (evt, gestureState) => true,
      onShouldBlockNativeResponder:        (evt, gestureState) => false,
      onPanResponderTerminationRequest:    (evt, gestureState) => false,
      onPanResponderGrant:   this.onStartDrag,
      onPanResponderMove:    this.onMoveBlock,
      onPanResponderRelease: this.onReleaseBlock
    })
}

const styles = StyleSheet.create({
draggableGrid: {
  flex: 1,
  flexDirection: 'row',
  flexWrap: 'wrap'
}
})

module.exports = DraggableGrid
<|endoftext|>