<commit_before>const { send } = require('micro');
const { router, get } = require('microrouter');
const { exportLetterboxd } = require('./export');
const { extract } = require('./extract');
const { createError } = require('./utils');

const help = {
  usage: 'GET /:username/:category/:filter',
  documentation: 'https://github.com/mlcdf/shelob#usage'
};

/**
 *  Validate the request parameters
 * @param {*} params
 */
const validateParams = req => {
  if (
    !['films', 'series', 'bd', 'livres', 'albums', 'morceaux'].includes(
      req.params.category
    )
  ) {
    throw createError(
      400,
      'unexpected_parameter',
      'Invalid category parameter. Should be either `films`, `series`, `bd`, `livres`, `albums` or `morceaux`.'
    );
  }

  if (!['done', 'wish'].includes(req.params.filter)) {
    throw createError(
      400,
      'unexpected_parameter',
      'Invalid filter parameter. Should be either `done` or `filter`.'
    );
  }

  if (req.params.exportWebsite && req.params.exportWebsite !== 'letterboxd') {
    throw createError(
      400,
      'unexpected_parameter',
      'Invalid optional exportWebsite paramater. Should be either empty or `letterboxd`'
    );
  }

  if (
    req.query.exportWebsite === 'letterboxd' &&
    req.params.category !== 'films'
  ) {
    throw createError(
      400,
      'unexpected_parameter',
      'The Letterboxd export is only available for the `films` category. Please remove this query param.'
    );
  }
};

// GET /
const index = (req, res) => {
  send(res, 200, help);
};

// GET /*
const notFound = (req, res) => {
  send(res, 404, {
    message: 'Not found',
    usage: help.usage,
    documentation: help.documentation
  });
};

// GET /:username/:category/:filter/:exportWebsite?
const api = async (req, res) => {
  try {
    validateParams(req);
  } catch (err) {
    send(res, err.statusCode, {
      message: err.message,
      documentation: help.documentation
    });
  }

  await extract(
    req.params.username,
    req.params.category,
    req.params.filter,
    req.query
  )
    .then(data => {
      if (req.query.exportWebsite === 'letterboxd') {
        data = exportLetterboxd(data, req.params.filter);
        res.setHeader(
          'Content-disposition',
          `attachment; filename=${
            req.params.filter === 'done' ? 'watched' : 'wishlist'
          }.csv`
        );
        res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      }
      send(res, 200, data);
    })
    .catch(err => {
      console.log(err);
      createError(500, 'unknown_error', 'Unknown error');
    });
};

const decorateWithHeaders = handler => (req, res) => {
  const referer = req.headers.referer;

  if (!referer || referer === '') {
    res.setHeader('Access-Control-Allow-Origin', '*');
  } else {
    res.setHeader('Access-Control-Allow-Origin', referer);
  }

  return handler(req, res);
};

module.exports = router(
  get('/', decorateWithHeaders(index)),
  get('/:username/:category/:filter', decorateWithHeaders(api)),
  get('/*', decorateWithHeaders(notFound))
);
<commit_msg>feat: improved req validation<commit_after>const { send } = require('micro');
const { router, get } = require('microrouter');
const { exportLetterboxd } = require('./export');
const { extract } = require('./extract');
const { createError } = require('./utils');

const help = {
  usage: 'GET /:username/:category/:filter',
  documentation: 'https://github.com/mlcdf/shelob#usage'
};

class Validator {
  constructor(req) {
    // Request to validate
    this.req = req;
  }

  // Set a default errorId
  get errorId() {
    return 'invalid_parameter';
  }
}

class NoInvalidCategory extends Validator {
  constructor(req) {
    super(req);

    this.allowedCategories = [
      'films',
      'series',
      'bd',
      'livres',
      'albums',
      'morceaux'
    ];
  }

  get errorMessage() {
    return `Expected ${this.allowedCategories.join('|')}, got ${
      this.req.params.category
    }`;
  }

  isValid() {
    console.log(!this.allowedCategories.includes(this.req.params.category));
    if (!this.allowedCategories.includes(this.req.params.category)) {
      return false;
    }
    return true;
  }
}

class NoConflictingExportParameter extends Validator {
  constructor(req) {
    super(req);
  }

  get errorMessage() {
    return "The Letterboxd export is only available for the 'films' category";
  }

  isValid() {
    if (
      this.req.query.exportWebsite === 'letterboxd' &&
      this.req.params.category !== 'films'
    ) {
      return false;
    }

    return true;
  }
}

class NoInvalidExportParameter extends Validator {
  constructor(req) {
    super(req);
  }
  get errorMessage() {
    return 'Invalid optional exportWebsite paramater. Should be either empty or `letterboxd`';
  }

  isValid() {
    if (
      this.req.query.exportWebsite &&
      this.req.query.exportWebsite !== 'letterboxd'
    ) {
      return false;
    }
    return true;
  }
}

class NoInvalidFilterParameter extends Validator {
  constructor(req) {
    super(req);
  }

  get errorMessage() {
    return 'Invalid filter parameter. Should be either `done` or `filter`';
  }

  isValid() {
    if (!['done', 'wish'].includes(this.req.params.filter)) {
      return false;
    }
    return true;
  }
}

/**
 *  Validate the request parameters
 * @param {*} req
 */
const validateReq = req => {
  rules = [
    new NoInvalidCategory(req),
    new NoConflictingExportParameter(req),
    new NoInvalidExportParameter(req),
    new NoInvalidFilterParameter(req)
  ];

  errors = [];

  for (const rule of rules) {
    if (!rule.isValid()) {
      errors.push({
        id: rule.errorId,
        message: rule.errorMessage
      });
    }
  }

  return errors;
};

// GET /
const index = (req, res) => {
  send(res, 200, help);
};

// GET /*
const notFound = (req, res) => {
  send(res, 404, {
    message: 'Not found',
    usage: help.usage,
    documentation: help.documentation
  });
};

// GET /:username/:category/:filter/:exportWebsite?
const api = async (req, res) => {
  req.errors = validateReq(req);

  if (req.errors.length > 0) {
    return send(res, 400, { ok: false, errors });
  }

  await extract(
    req.params.username,
    req.params.category,
    req.params.filter,
    req.query
  )
    .then(data => {
      if (req.query.exportWebsite === 'letterboxd') {
        data = exportLetterboxd(data, req.params.filter);
        res.setHeader(
          'Content-disposition',
          `attachment; filename=${
            req.params.filter === 'done' ? 'watched' : 'wishlist'
          }.csv`
        );
        res.setHeader('Content-Type', 'text/csv; charset=utf-8');
      }
      send(res, 200, data);
    })
    .catch(err => {
      console.log(err);
      createError(500, 'unknown_error', 'Unknown error');
    });
};

const decorateWithHeaders = handler => (req, res) => {
  const referer = req.headers.referer;

  if (!referer || referer === '') {
    res.setHeader('Access-Control-Allow-Origin', '*');
  } else {
    res.setHeader('Access-Control-Allow-Origin', referer);
  }

  return handler(req, res);
};

module.exports = router(
  get('/', decorateWithHeaders(index)),
  get('/:username/:category/:filter', decorateWithHeaders(api)),
  get('/*', decorateWithHeaders(notFound))
);
<|endoftext|>


<commit_before>import React from 'react'
import PropTypes from 'prop-types'
import {
  View,
  Image,
  StyleSheet,
  TouchableOpacity,
  Platform
} from 'react-native'
import ImagePicker from 'react-native-image-picker'
import ImageResizer from 'react-native-image-resizer'
import RNFS from 'react-native-fs'

export default class PhotoUpload extends React.Component {
  static propTypes = {
    containerStyle: PropTypes.object,
    photoPickerTitle: PropTypes.string,
    height: PropTypes.number,
    width: PropTypes.number,
    format: PropTypes.string,
    quality: PropTypes.number,
    onPhotoSelect: PropTypes.func // returns the base64 string of uploaded photo
  }

  state = {
    height: this.props.height || 300,
    width: this.props.width || 300,
    format: this.props.format || 'JPEG',
    quality: this.props.quality || 80
  }

  options = {
    title: this.props.pickerTitle || 'Select Photo',
    storageOptions: {
      skipBackup: true,
      path: 'images'
    }
  }

  openImagePicker = () => {
    // get image from image picker
    ImagePicker.showImagePicker(this.options, async response => {
      console.log('Response = ', response)

      if (response.didCancel) {
        console.log('User cancelled image picker')
        return
      } else if (response.error) {
        console.log('ImagePicker Error: ', response.error)
        return
      } else if (response.customButton) {
        console.log('User tapped custom button: ', response.customButton)
        return
      }

      let { height, width, quality, format } = this.state

      // resize image
      const resizedImageUri = await ImageResizer.createResizedImage(
        `data:image/jpeg;base64,${response.data}`,
        height,
        width,
        format,
        quality
      )
      const filePath = Platform.OS === 'android' && resizedImageUri.uri.replace
        ? resizedImageUri.uri.replace('file:/data', '/data')
        : resizedImageUri.uri

      // convert image back to base64 string
      const photoData = await RNFS.readFile(filePath, 'base64')
      let source = { uri: resizedImageUri.uri }
      this.setState({
        avatarSource: source
      })

      // handle photo in props functions as data string
      if (this.props.onPhotoSelect) {
        this.props.onPhotoSelect(photoData)
      }
    })
  }

  renderChildren = props => {
    return React.Children.map(props.children, child => {
      if (child.type === Image && this.state.avatarSource) {
        return React.cloneElement(child, {
          source: this.state.avatarSource
        })
      } else return child
    })
  }

  render () {
    return (
      <View style={[styles.container, this.props.containerStyle]}>
        <TouchableOpacity onPress={this.openImagePicker}>
          {this.renderChildren(this.props)}
        </TouchableOpacity>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  }
})
<commit_msg>Add multiple life cycle methods. Super useful<commit_after>import React from 'react'
import PropTypes from 'prop-types'
import {
  View,
  Image,
  StyleSheet,
  TouchableOpacity,
  Platform
} from 'react-native'
import ImagePicker from 'react-native-image-picker'
import ImageResizer from 'react-native-image-resizer'
import RNFS from 'react-native-fs'

export default class PhotoUpload extends React.Component {
  static propTypes = {
    containerStyle: PropTypes.object,
    photoPickerTitle: PropTypes.string,
    height: PropTypes.number,
    width: PropTypes.number,
    format: PropTypes.string,
    quality: PropTypes.number,
    onPhotoSelect: PropTypes.func, // returns the base64 string of uploaded photo
    onError: PropTypes.func, // if any error occur with response
    onTapCustomButton: PropTypes.func, // on tap custom button
    onStart: PropTypes.func, // when user starts (useful for loading, etc)
    onCancel: PropTypes.func, // when user cancel
    onResponse: PropTypes.func, // on response exists!
    onRender: PropTypes.func, // after render
    onResizedImageUri: PropTypes.func, // when image resized is ready
  }

  state = {
    height: this.props.height || 300,
    width: this.props.width || 300,
    format: this.props.format || 'JPEG',
    quality: this.props.quality || 80
  }

  options = {
    title: this.props.pickerTitle || 'Select Photo',
    storageOptions: {
      skipBackup: true,
      path: 'images'
    }
  }

  openImagePicker = () => {
    if (this.props.onStart) this.props.onStart()

    // get image from image picker
    ImagePicker.showImagePicker(this.options, async response => {
      console.log('Response = ', response)

      if (this.props.onResponse) this.props.onResponse(response)

      if (response.didCancel) {
        console.log('User cancelled image picker')
        this.props.onCancel('User cancelled image picker')
        return
      } else if (response.error) {
        console.log('ImagePicker Error: ', response.error)
        this.props.onError(response.error)
        return
      } else if (response.customButton) {
        console.log('User tapped custom button: ', response.customButton)
        this.props.onTapCustomButton(response.customButton)
        return
      }

      let { height, width, quality, format } = this.state

      // resize image
      const resizedImageUri = await ImageResizer.createResizedImage(
        `data:image/jpeg;base64,${response.data}`,
        height,
        width,
        format,
        quality
      )

      if (this.props.onResizedImageUri) this.props.onResizedImageUri(resizedImageUri)

      const filePath = Platform.OS === 'android' && resizedImageUri.uri.replace
        ? resizedImageUri.uri.replace('file:/data', '/data')
        : resizedImageUri.uri

      // convert image back to base64 string
      const photoData = await RNFS.readFile(filePath, 'base64')
      let source = { uri: resizedImageUri.uri }
      this.setState({
        avatarSource: source
      })

      // handle photo in props functions as data string
      if (this.props.onPhotoSelect) this.props.onPhotoSelect(photoData)
    })
  }

  renderChildren = props => {
    return React.Children.map(props.children, child => {
      if (child.type === Image && this.state.avatarSource) {
        return React.cloneElement(child, {
          source: this.state.avatarSource
        })
      } else return child
    })
  }

  componentDidUpdate() {
    if (this.props.onAfterRender) this.props.onAfterRender(this.state)
  }

  render() {
    return (
      <View style={[styles.container, this.props.containerStyle]}>
        <TouchableOpacity onPress={this.openImagePicker}>
          {this.renderChildren(this.props)}
        </TouchableOpacity>
      </View>
    )
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center'
  }
})
<|endoftext|>


<commit_before>exports = module.exports = function vault(id) {
  var map = {
    'client': './xom/client'
  };

  var mid = map[id];
  if (mid) {
    return require(mid);
  }
};

exports.used = function(container) {
  container.add('client');
};
<commit_msg>Simplify spec manifest.<commit_after>exports = module.exports = {
  'client': require('./xom/client')
};
<|endoftext|>


<commit_before>let fs = require("fs");
let path = require("path");
let http = require("http");
let url = require("url");
let mime = require("mime");

//Server options
let options = {folder: process.cwd(), port: 5000, cors: false, index: "index.html"};

//Get the default error file path
options.error = path.resolve(__dirname, "./assets/error.html");

//Change a default option value
module.exports.set = function (key, value) {
    if (typeof options[key] === "undefined") {
        throw new Error("Unknown key '" + key + "'");
    }
    if (typeof options[key] !== typeof value) {
        throw new Error("Invalid key type '" + key + "'");
    }
    options[key] = value;
};

//Get a value
module.exports.get = function (key) {
    return options[key];
};

//Create the server
module.exports.listen = function (port, cb) {
    //Parse the arguments
    cb = (typeof port === "function") ? port : cb;
    port = (typeof port === "number") ? parseInt(port) : options.port;

    //Get the static files folder path
    options.folder = path.resolve(process.cwd(), options.folder);

    //Get the error file path
    options.error = path.resolve(process.cwd(), options.error);

    //Initialize the server
    let server = http.createServer(function (req, res) {
        let timeStart = Date.now();

        //Check the cors option
        if (options.cors === true) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
        }
        let pathname = url.parse(req.url).pathname;
        pathname = path.normalize(pathname); //Fix path traversal
        let localPath = path.join(options.folder, pathname);
        if (path.extname(localPath) === "") {
            //Add the index file to the local path
            localPath = path.join(localPath, "./" + path.basename(options.index));
        }

        //Reponse finish event
        res.on("finish", function () {
            console.log("" + res.statusCode + "  " + pathname + "  " + (Date.now() - timeStart) + " ms");
        });

        //Write the header with the content type
        res.writeHead(200, {"Content-Type": mime.getType(localPath)});

        //Initialize the reader stream
        //let reader = fs.createReadStream(local_path, { encoding: "utf8" });
        //Remove encoding -> fixed bug reading images (jpg, png, etc...)
        let reader = fs.createReadStream(localPath);
        reader.on("data", function (data) {
            //Write the data to the response
            res.write(data);
        });
        reader.on("end", function () {
            res.end("");
        });
        reader.on("error", function (error) {
            //Check for file not found
            if(error.code === "ENOENT") {
                return errorPage(res, 404, "File not found on the server.");
            }
            return errorPage(res, 500, "Something went wrong...");
        });
    });

    //Start server
    server.listen(port, function () {
        if (typeof cb === "function") {
            cb.call(null);
        }
        else {
            //Show the console log success
            console.log("");
            console.log("Static server listening on: " + "http://localhost:" + options.port + "");
            console.log("Reading files from: " + options.folder + "");
            console.log("");
        }
    });
};

//Display an error page
let errorPage = function (res, errorCode, errorMessage) {
    res.writeHead(errorCode, {"Content-Type": "text/html"});
    //Read the local error file
    return fs.readFile(options.error, "utf8", function (error, content) {
        //Check the error
        if (error) {
            return res.end("<" + "h1>Error

" + errorMessage + "
");
        }
        let data = {code: errorCode, message: errorMessage};
        content = content.replace(/{{([^{}]+)}}/g, function(match, found)
        {
            found = found.trim();
            if(typeof data[found] !== "undefined") {
                return data[found].toString();
            } else {
                return match;
            }
        });
        return res.end(content);
    });
};
<commit_msg>Fixed bug when file is not found and headers has been already sent<commit_after>let fs = require("fs");
let path = require("path");
let http = require("http");
let url = require("url");
let mime = require("mime");

//Server options
let options = {folder: process.cwd(), port: 5000, cors: false, index: "index.html"};

//Get the default error file path
options.error = path.resolve(__dirname, "./assets/error.html");

//Change a default option value
module.exports.set = function (key, value) {
    if (typeof options[key] === "undefined") {
        throw new Error("Unknown key '" + key + "'");
    }
    if (typeof options[key] !== typeof value) {
        throw new Error("Invalid key type '" + key + "'");
    }
    options[key] = value;
};

//Get a value
module.exports.get = function (key) {
    return options[key];
};

//Create the server
module.exports.listen = function (port, cb) {
    //Parse the arguments
    cb = (typeof port === "function") ? port : cb;
    port = (typeof port === "number") ? parseInt(port) : options.port;

    //Get the static files folder path
    options.folder = path.resolve(process.cwd(), options.folder);

    //Get the error file path
    options.error = path.resolve(process.cwd(), options.error);

    //Initialize the server
    let server = http.createServer(function (req, res) {
        let timeStart = Date.now();

        //Check the cors option
        if (options.cors === true) {
            res.setHeader("Access-Control-Allow-Origin", "*");
            res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS, PUT, PATCH, DELETE");
            res.setHeader("Access-Control-Allow-Headers", "X-Requested-With,content-type");
        }
        let pathname = url.parse(req.url).pathname;
        pathname = path.normalize(pathname); //Fix path traversal
        let localPath = path.join(options.folder, pathname);
        if (path.extname(localPath) === "") {
            //Add the index file to the local path
            localPath = path.join(localPath, "./" + path.basename(options.index));
        }

        //Reponse finish event
        res.on("finish", function () {
            console.log("" + res.statusCode + "  " + pathname + "  " + (Date.now() - timeStart) + " ms");
        });

        //Check if file exists
        return fs.stat(localPath, function (error, stat) {
            if (error) {
                if (error.code === "ENOENT") {
                    return errorPage(res, 404, "File not found");
                }
                return errorPage(res, 500, "Internal server error");
            }
            if (stat.isFile() === false) {
                return errorPage(res, 404, "File not found");
            }

            //Write the header with the content type
            res.writeHead(200, {"Content-Type": mime.getType(localPath)});

            //Initialize the reader stream
            //let reader = fs.createReadStream(local_path, { encoding: "utf8" });
            //Remove encoding -> fixed bug reading images (jpg, png, etc...)
            let reader = fs.createReadStream(localPath);
            reader.on("data", function (data) {
                //Write the data to the response
                res.write(data);
            });
            reader.on("end", function () {
                res.end("");
            });
            reader.on("error", function (error) {
                //Unknown error...
                return errorPage(res, 500, "Something went wrong...");
            });
        });
    });

    //Start server
    server.listen(port, function () {
        if (typeof cb === "function") {
            cb.call(null);
        }
        else {
            //Show the console log success
            console.log("");
            console.log("Static server listening on: " + "http://localhost:" + options.port + "");
            console.log("Reading files from: " + options.folder + "");
            console.log("");
        }
    });
};

//Display an error page
let errorPage = function (res, errorCode, errorMessage) {
    res.writeHead(errorCode, {"Content-Type": "text/html"});
    //Read the local error file
    return fs.readFile(options.error, "utf8", function (error, content) {
        //Check the error
        if (error) {
            return res.end("<" + "h1>Error

" + errorMessage + "
");
        }
        let data = {code: errorCode, message: errorMessage};
        content = content.replace(/{{([^{}]+)}}/g, function(match, found)
            {
                found = found.trim();
                if(typeof data[found] !== "undefined") {
                    return data[found].toString();
                } else {
                    return match;
                }
            });
        return res.end(content);
    });
};
<|endoftext|>


<commit_before>"use strict";

// Returns a random integer between min (included) and max (excluded)
// Using Math.round() will give you a non-uniform distribution!
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

var slogans = [
    "– Come in and find out",
    "– The beautiness company",
    "macht Kinder froh",
    "– Don't Be evil",
    "– wenn's um Geld geht",
    "– Ganz schön unanständig",
    "– seriös anders",
    "– From laboratory to life.",
    "– Nie ohne.",
    "– For generations to come.",
    "– Stets zu Diensten!",
    "– Von Natur aus das Beste.",
    "– Great reputations are built on details.",
    "– The hidden ingredient.",
    "– Das Magazin für werdende Väter.",
    "– Wir wachsen mit Licht.",
    "– Das Ingeneurbüro im Sauerland.",
    "– Shaping the future.",
    "– Turn sweat into energy.",
    "– Macht aus Schweiß Energie.",
    "– There's something in the water.",
    "– With added super heroes.",
    "– Everything you need to look and feel like a star.",
    "– The toughest hoarding panels known to man.",
    "– Denken. Wo andere programmieren.",
    "– Unleash all your horses.",
    "– High-Tech im Rolltreppenbau.",
    "– Il buon caffè italiano.",
    "– Norddeutschlands bester Stau-Service.",
    "– Sleep well. Move better.",
    "– Gönn dir was!",
    "– The 24 carat Scotch.",
    "– Everything should taste like bacon.",
    "– Now we know better!",
    "– Wir verkaufen Lösungen, keine Produkte!",
    "– Der sichere Weg zu Windows 7.",
    "– Glamour! Stars! Hollywood!",
    "– Ist doch ganz einfach.",
    "– Let somebody else do the job for you!",
    "– Schön zu sehen - gut zu gehen.",
    "– Strahlend in Eleganz und Qualität.",
    "– Dröpje voor Dröpje Qualität. ",
    "– Mehr Konzept. Weniger Erfolg.",
    "– More Blubble, less Code",
    "– Tische & Softeis",
    "– all kinds of everything",
    "- Arbeitswut tut selten gut",
    "– Ganz schön und anständig",
    "— Das Auto",
    "— Qualitysoftware since 1784"
];

module.exports = function giveMeSillySlogan(companyName) {
    return companyName + " " + slogans[getRandomInt(0, slogans.length - 1)];
};

<commit_msg>Update index.js<commit_after>"use strict";

// Returns a random integer between min (included) and max (excluded)
// Using Math.round() will give you a non-uniform distribution!
// taken from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
function getRandomInt(min, max) {
    return Math.floor(Math.random() * (max - min)) + min;
}

var slogans = [
    "– Come in and find out",
    "– The beautiness company",
    "macht Kinder froh",
    "– Don't Be evil",
    "– wenn's um Geld geht",
    "– Ganz schön unanständig",
    "– seriös anders",
    "– From laboratory to life.",
    "– Nie ohne.",
    "– For generations to come.",
    "– Stets zu Diensten!",
    "– Von Natur aus das Beste.",
    "– Great reputations are built on details.",
    "– The hidden ingredient.",
    "– Das Magazin für werdende Väter.",
    "– Wir wachsen mit Licht.",
    "– Das Ingenieursbüro im Sauerland.",
    "– Shaping the future.",
    "– Turn sweat into energy.",
    "– Macht aus Schweiß Energie.",
    "– There's something in the water.",
    "– With added super heroes.",
    "– Everything you need to look and feel like a star.",
    "– The toughest hoarding panels known to man.",
    "– Denken. Wo andere programmieren.",
    "– Unleash all your horses.",
    "– High-Tech im Rolltreppenbau.",
    "– Il buon caffè italiano.",
    "– Norddeutschlands bester Stau-Service.",
    "– Sleep well. Move better.",
    "– Gönn dir was!",
    "– The 24 carat Scotch.",
    "– Everything should taste like bacon.",
    "– Now we know better!",
    "– Wir verkaufen Lösungen, keine Produkte!",
    "– Der sichere Weg zu Windows 7.",
    "– Glamour! Stars! Hollywood!",
    "– Ist doch ganz einfach.",
    "– Let somebody else do the job for you!",
    "– Schön zu sehen - gut zu gehen.",
    "– Strahlend in Eleganz und Qualität.",
    "– Dröpje voor Dröpje Qualität. ",
    "– Mehr Konzept. Weniger Erfolg.",
    "– More Blubble, less Code",
    "– Tische & Softeis",
    "– all kinds of everything",
    "- Arbeitswut tut selten gut",
    "– Ganz schön und anständig",
    "— Das Auto",
    "— Qualitysoftware since 1784",
    "- flieg und sieg!"
];

module.exports = function giveMeSillySlogan(companyName) {
    return companyName + " " + slogans[getRandomInt(0, slogans.length - 1)];
};

<|endoftext|>


<commit_before>var steamServerStatus = require('steam-server-status');
var dgram = require('dgram');
var net = require('net');
var BufferBuilder = require('buffer-builder');
var _ = require('lodash');
var Q = require('Q');
var restler = require('restler');
var fs = require('fs');
var MAXPAGES = 50;

CONFIG_JSON = {};
try {
	CONFIG_JSON = JSON.parse(fs.readFileSync('config.json'));
} catch (e) {
	console.error("Couldn't parse config.json: ", e);
}

var sendSingleQuery = function(socket, prevIP, prevPort, queryStr){
	var buf = new BufferBuilder();
	var queryPageServer = prevIP + ":" + prevPort;
	console.log("QueryPageServer: " + queryPageServer)
	buf.appendUInt8(0x31);
	buf.appendUInt8(0xFF);
	buf.appendString(queryPageServer);
	buf.appendUInt8(0);
	buf.appendString(queryStr);
	buf.appendUInt8(0);

	var cb = function(err){
		if (err != null) {
			console.error("Some error occurred: ", err);
		}
	}

	socket.send(buf.get(), 0, buf.length, 27011, "hl2master.steampowered.com", cb);
}

var sendQuery = function(queryStr, callback) {
	var i = 0;
	var socket;
	socket = dgram.createSocket('udp4');
	socket.bind(51202);

	var servers = [];

	var done = function(receivedServers) {
		console.log("Done fetching server list! Receiving server details...");
		// Filter 0.0.0.0:0 out of received server list first:
		receivedServers = _.filter(receivedServers, function(receivedServer){
			return receivedServer.ip != "0.0.0.0";
		})
		// Setup array of promises
		var promises = _.map(receivedServers, function(receivedServer) {
			var deferred = Q.defer();
			console.log("Getting details for server " + receivedServer.ip + ":" + receivedServer.port);
			steamServerStatus.getServerStatus(receivedServer.ip, receivedServer.port, function(serverInfo) {
		        if (serverInfo.error) {
		        	deferred.reject(serverInfo.error);
		        } else {
		        	deferred.resolve(serverInfo);
		        }
			});
			return deferred.promise;
		});
		// Query all servers and wait until they're all settled...
		Q.allSettled(promises).then(function(results) {
			var fulfilledPromises = _.filter(results, function(result) {
				return result.state === "fulfilled";
			});
			var fulfilledResults = _.pluck(fulfilledPromises, 'value');
			var numberOfPlayers = _.reduce(fulfilledResults, function(memo, result) {
				return memo + result.numberOfPlayers;
			}, 0);
			if (callback !== undefined) {
				callback(fulfilledResults, numberOfPlayers);
			}
		});
	}

	socket.on('message', function(msg, rinfo) {
		i++;
		var receivedServers = parseResponse(msg);
		var lastServer = receivedServers[receivedServers.length-1];
		if (receivedServers != null && lastServer.ip != "0.0.0.0" && lastServer.port != 0) {
			if (i < MAXPAGES) {
				sendSingleQuery(socket, lastServer.ip, lastServer.port, queryStr);
			} else {
				console.warn("too many pages received from Valve masterserver; stopping!")
			}
		} else {
			done(receivedServers);
		}
	});
	sendSingleQuery(socket, "0.0.0.0", "0", queryStr);
}

var parseResponse = function(buf) {
  // todo: validate first 6 bytes:  FF FF FF FF 66 0A
  var getIPPort = function(buf, offset) {
  	var o1 = buf.readUInt8(offset+0);
  	var o2 = buf.readUInt8(offset+1);
  	var o3 = buf.readUInt8(offset+2);
  	var o4 = buf.readUInt8(offset+3);
  	var port = buf.readUInt16BE(offset+4);
  	return {
  		"ip": o1 + "." + o2 + "." + o3 + "." + o4,
  		"port": port
  	};
  }
  var lastoffset = buf.length;
  var retval = [];
  for (var offset = 6; offset < lastoffset; offset+=6) {
  	var ipport = getIPPort(buf, offset);
	retval.push(ipport);
  }
  return retval;
}

sendQuery("\\appid\\244630", function(results, numberOfPlayers) {
	console.log("Number of Neotokyo players: " + numberOfPlayers);
	if (CONFIG_JSON.hasOwnProperty("phant-post")) {
		console.log("Submitting to Phant...");
		restler.post(CONFIG_JSON["phant-post"]["post-url"], {
			"data": {numplayers: "" + numberOfPlayers},
			"headers": {'Phant-Private-Key': CONFIG_JSON["phant-post"]["private-key"]}
		}).on('complete', function(data, response){
			console.log("Request sent. Response code: " + response.statusCode);
		});
	}
});
<commit_msg>Proper indentation<commit_after>var steamServerStatus = require('steam-server-status');
var dgram = require('dgram');
var net = require('net');
var BufferBuilder = require('buffer-builder');
var _ = require('lodash');
var Q = require('Q');
var restler = require('restler');
var fs = require('fs');
var MAXPAGES = 50;

CONFIG_JSON = {};
try {
  CONFIG_JSON = JSON.parse(fs.readFileSync('config.json'));
} catch (e) {
  console.error("Couldn't parse config.json: ", e);
}

var sendSingleQuery = function(socket, prevIP, prevPort, queryStr){
  var buf = new BufferBuilder();
  var queryPageServer = prevIP + ":" + prevPort;
  console.log("QueryPageServer: " + queryPageServer)
  buf.appendUInt8(0x31);
  buf.appendUInt8(0xFF);
  buf.appendString(queryPageServer);
  buf.appendUInt8(0);
  buf.appendString(queryStr);
  buf.appendUInt8(0);

  var cb = function(err){
    if (err != null) {
      console.error("Some error occurred: ", err);
    }
  }

  socket.send(buf.get(), 0, buf.length, 27011, "hl2master.steampowered.com", cb);
}

var sendQuery = function(queryStr, callback) {
  var i = 0;
  var socket;
  socket = dgram.createSocket('udp4');
  socket.bind(51202);

  var servers = [];

  var done = function(receivedServers) {
    console.log("Done fetching server list! Receiving server details...");
    // Filter 0.0.0.0:0 out of received server list first:
    receivedServers = _.filter(receivedServers, function(receivedServer){
      return receivedServer.ip != "0.0.0.0";
    })
    // Setup array of promises
    var promises = _.map(receivedServers, function(receivedServer) {
      var deferred = Q.defer();
      console.log("Getting details for server " + receivedServer.ip + ":" + receivedServer.port);
      steamServerStatus.getServerStatus(receivedServer.ip, receivedServer.port, function(serverInfo) {
        if (serverInfo.error) {
          deferred.reject(serverInfo.error);
        } else {
          deferred.resolve(serverInfo);
        }
      });
      return deferred.promise;
    });
    // Query all servers and wait until they're all settled...
    Q.allSettled(promises).then(function(results) {
      var fulfilledPromises = _.filter(results, function(result) {
        return result.state === "fulfilled";
      });
      var fulfilledResults = _.pluck(fulfilledPromises, 'value');
      var numberOfPlayers = _.reduce(fulfilledResults, function(memo, result) {
        return memo + result.numberOfPlayers;
      }, 0);
      if (callback !== undefined) {
        callback(fulfilledResults, numberOfPlayers);
      }
    });
  }

  socket.on('message', function(msg, rinfo) {
    i++;
    var receivedServers = parseResponse(msg);
    var lastServer = receivedServers[receivedServers.length-1];
    if (receivedServers != null && lastServer.ip != "0.0.0.0" && lastServer.port != 0) {
      if (i < MAXPAGES) {
        sendSingleQuery(socket, lastServer.ip, lastServer.port, queryStr);
      } else {
        console.warn("too many pages received from Valve masterserver; stopping!")
      }
    } else {
      done(receivedServers);
    }
  });
  sendSingleQuery(socket, "0.0.0.0", "0", queryStr);
}

var parseResponse = function(buf) {
  // todo: validate first 6 bytes:  FF FF FF FF 66 0A
  var getIPPort = function(buf, offset) {
    var o1 = buf.readUInt8(offset+0);
    var o2 = buf.readUInt8(offset+1);
    var o3 = buf.readUInt8(offset+2);
    var o4 = buf.readUInt8(offset+3);
    var port = buf.readUInt16BE(offset+4);
    return {
      "ip": o1 + "." + o2 + "." + o3 + "." + o4,
      "port": port
    };
  }
  var lastoffset = buf.length;
  var retval = [];
  for (var offset = 6; offset < lastoffset; offset+=6) {
    var ipport = getIPPort(buf, offset);
  retval.push(ipport);
  }
  return retval;
}

sendQuery("\\appid\\244630", function(results, numberOfPlayers) {
  console.log("Number of Neotokyo players: " + numberOfPlayers);
  if (CONFIG_JSON.hasOwnProperty("phant-post")) {
    console.log("Submitting to Phant...");
    restler.post(CONFIG_JSON["phant-post"]["post-url"], {
      "data": {numplayers: "" + numberOfPlayers},
      "headers": {'Phant-Private-Key': CONFIG_JSON["phant-post"]["private-key"]}
    }).on('complete', function(data, response){
      console.log("Request sent. Response code: " + response.statusCode);
    });
  }
});
<|endoftext|>


<commit_before>/**
 * Module dependencies
 */

var Onus = require('onus');
var React = require('react');

var componentCache = {};

module.exports = function(conf) {
  var render = conf.render;
  return Onus({
    render: function(DOM, $get, props, state, _yield, params, query, forms, t) {
      var out = render(format, $get, props, state, _yield, params, query, forms, t);
      var style = Array.isArray(out) ? out.join('') : out;
      return DOM('style', null, '\n' + style);
    }
  });
}

function format(selectors, props) {
  return selectors.join('\n') + ' {\n' + formatProps(props) + '\n}\n';
}

function formatProps(props) {
  var parts = [];
  for (var k in props) {
    if (k === 'key') continue;
    parts.push('  ' + k + ':' + props[k] + ';');
  }
  return parts.join('\n');
}
<commit_msg>join selectors with a comma<commit_after>/**
 * Module dependencies
 */

var Onus = require('onus');
var React = require('react');

var componentCache = {};

module.exports = function(conf) {
  var render = conf.render;
  return Onus({
    render: function(DOM, $get, props, state, _yield, params, query, forms, t) {
      var out = render(format, $get, props, state, _yield, params, query, forms, t);
      var style = Array.isArray(out) ? out.join('') : out;
      return DOM('style', null, '\n' + style);
    }
  });
}

function format(selectors, props) {
  return selectors.join(',\n') + ' {\n' + formatProps(props) + '\n}\n';
}

function formatProps(props) {
  var parts = [];
  for (var k in props) {
    if (k === 'key') continue;
    parts.push('  ' + k + ':' + props[k] + ';');
  }
  return parts.join('\n');
}
<|endoftext|>


<commit_before>var express = require('express');
var cors = require('cors');
var got = require('got');
var videoman = require('./videoman');
var app = express();

app.set('port', (process.env.PORT || 3000));
app.use(cors());

app.get('/fetch_song/:id', function (req, res) {
  var videoId = req.params.id;
  var pushItem = videoman.pushItem(videoId);
  var options = {
    headers: {
      "Accept-Location": "*",
      "Referer": "http://www.youtube-mp3.org/es"
    }
  };

  got(pushItem, options, function(err, data, r) {
    var itemInfo = videoman.itemInfo(videoId);

    res.send({
      status_url: "/song_status/" + videoId,
      data: data
    });
  });
});

app.get('/song_status/:id', function (req, res) {
  var videoId = req.params.id;
  var itemInfo = videoman.itemInfo(videoId);

  console.log('itemInfo', itemInfo);
  got(itemInfo, function(err, data, r) {
    //TODO: Add safety checks...
    //Sanitize response
    data = data.replace('info = ', '')
    data = data.substring(0, data.length - 1);
    data = JSON.parse(data);

    response = {
      title: data.title,
      minutes: data.length,
      status: data.status,
      progress: data.progress,
      speed: data.progress_speed,
      metadata: data,
      itemInfo: itemInfo
    };

    if (data.status === 'serving') {
      response.song_src = videoman.songSrc(data, videoId);
    }

    res.send(response);
  });
});

var server = app.listen(app.get('port'), function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('App running on port', app.get('port'));
});<commit_msg>Add options to all requests<commit_after>var express = require('express');
var cors = require('cors');
var got = require('got');
var videoman = require('./videoman');
var app = express();
var requestOptions = {
  headers: {
    "Accept-Location": "*",
    "Referer": "http://www.youtube-mp3.org/es"
  }
};

app.set('port', (process.env.PORT || 3000));
app.use(cors());

app.get('/fetch_song/:id', function (req, res) {
  var videoId = req.params.id;
  var pushItem = videoman.pushItem(videoId);

  got(pushItem, requestOptions, function(err, data, r) {
    var itemInfo = videoman.itemInfo(videoId);

    res.send({
      status_url: "/song_status/" + videoId,
      data: data
    });
  });
});

app.get('/song_status/:id', function (req, res) {
  var videoId = req.params.id;
  var itemInfo = videoman.itemInfo(videoId);

  console.log('itemInfo', itemInfo);
  got(itemInfo, requestOptions, function(err, data, r) {
    //TODO: Add safety checks...
    //Sanitize response
    data = data.replace('info = ', '')
    data = data.substring(0, data.length - 1);
    data = JSON.parse(data);

    response = {
      title: data.title,
      minutes: data.length,
      status: data.status,
      progress: data.progress,
      speed: data.progress_speed,
      metadata: data,
      itemInfo: itemInfo
    };

    if (data.status === 'serving') {
      response.song_src = videoman.songSrc(data, videoId);
    }

    res.send(response);
  });
});

var server = app.listen(app.get('port'), function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('App running on port', app.get('port'));
});<|endoftext|>


<commit_before>const Discord = require("discord.js");
const fs = require("fs");
const date = require("date");
const bot = new Discord.Client();
const prefix = "!";
const botToken = require("./bottoken");

bot.on("ready", () => {
    console.log("Klar");
    bot.user.setGame("Skriv !help för hjälp.");
});

var dabRespond = [
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Vad är det du inte förstår?",
    "Jävla människor ska alltid komma och tjata",
    "Okej då, ***DAB***"
];

Date.prototype.getWeek = function() {
    var onejan = new Date(this.getFullYear(), 0, 1);
    return Math.ceil((((this - onejan) / 86400000) + onejan.getDay() + 1) / 7);
}

var weekNumber = (new Date()).getWeek();
var widthSchema = "600"
var heightSchema = "600"

var schemaLink = "http://www.novasoftware.se/ImgGen/schedulegenerator.aspx?format=png&schoolid=80220/sv-se&type=1&id={EA17E85E-CBFC-4836-935C-04780337F6D5}&period=&week="+weekNumber+"&mode=0&printer=0&colors=32&head=0&clock=0&foot=0&day=0&width="+widthSchema+"&height="+heightSchema+"&maxwidth=1883&maxheight=847"

bot.on("message", (message) => {

    if (!message.content.startsWith(prefix)) return;
    if (message.author.bot) return;

    //console.log(test);

    var split = message.content.substring(prefix.length).split(' ');

    switch (split[0].toLowerCase()) {

        case "help":
            message.channel.send({
                embed: new Discord.RichEmbed()
                    .setAuthor("Kommandon:", bot.user.avatarURL)
                    .addField("Allmäna kommandon:", "!help - visar denna meny\n!github - skickar länken till botens github repo\n!ping - visar botens internal ping (för felsökning)\n!poll <fråga> - Starta en ja eller nej fråga")
                    .addField("Skolrelaterade kommandon:", "!schema - visar veckans schema\n!hex - ger dig en slumpmässig färg")
                    .setColor("0x111111")
            });
            break;
        case "schema":
            message.channel.send({
                embed: new Discord.RichEmbed()
                    .setAuthor("Schema v."+weekNumber+":")
                    .setImage(schemaLink)
                    .setColor("0x"+("000000"+Math.random().toString(16).slice(2, 8).toUpperCase()).slice(-6))
            });
            break;
        case "github":
            message.author.send("https://github.com/ChilladeChillin/ITGDiscordBot");
            break;
        case "dab":
            message.channel.send(dabRespond[Math.floor(Math.random() * dabRespond.length)]);
            break;
        case "say":
            var textFromSender = message.content;
            message.delete(0);
            if (message.author.id==="164283691802165250") {
                message.channel.send(message.content.substring(5));
                break;
            }
            else {
                break;
            }
        case "ping":
            message.channel.send(bot.ping+" ms");
            break;
        case "hex":
            var randomhex = ("000000"+Math.random().toString(16).slice(2, 8).toUpperCase()).slice(-6);
            message.channel.send({
                embed: new Discord.RichEmbed()
                    .setAuthor("Hex:")
                    .setDescription("#"+randomhex)
                    .setColor("0x"+randomhex)
            });
            break;
        case "poll":
            var question = message.content.substring(6)
            message.delete(0);
            message.channel.send(question+"\n\n`👍=JA 👎=NEJ`")
                .then(function (message) {
                    message.react("👍")
                    message.react("👎")
                }).catch(function() {
                    console.log("Reaktionen gick inte hela vägen fram")
                   });
            break;
        default:
            message.channel.send("```Detta kommando existerar inte (än)\nSkriv !help för att se de kommandon som faktiskt existerar```");
        //case "bordejag"
            //[Math.floor(Math.random() * bordejag.length)]
    }
});

bot.login(botToken);<commit_msg>förbättrade !say<commit_after>const Discord = require("discord.js");
const fs = require("fs");
const date = require("date");
const bot = new Discord.Client();
const prefix = "!";
const botToken = require("./bottoken");

bot.on("ready", () => {
    console.log("Klar");
    bot.user.setGame("Skriv !help för hjälp.");
});

var dabRespond = [
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Du kan inte tvinga mig",
    "Vad är det du inte förstår?",
    "Jävla människor ska alltid komma och tjata",
    "Okej då, ***DAB***"
];

Date.prototype.getWeek = function() {
    var onejan = new Date(this.getFullYear(), 0, 1);
    return Math.ceil((((this - onejan) / 86400000) + onejan.getDay() + 1) / 7);
}

var weekNumber = (new Date()).getWeek();
var widthSchema = "600"
var heightSchema = "600"

var schemaLink = "http://www.novasoftware.se/ImgGen/schedulegenerator.aspx?format=png&schoolid=80220/sv-se&type=1&id={EA17E85E-CBFC-4836-935C-04780337F6D5}&period=&week="+weekNumber+"&mode=0&printer=0&colors=32&head=0&clock=0&foot=0&day=0&width="+widthSchema+"&height="+heightSchema+"&maxwidth=1883&maxheight=847"

bot.on("message", (message) => {

    if (!message.content.startsWith(prefix)) return;
    if (message.author.bot) return;

    //console.log(test);

    var split = message.content.substring(prefix.length).split(' ');

    switch (split[0].toLowerCase()) {

        case "help":
            message.channel.send({
                embed: new Discord.RichEmbed()
                    .setAuthor("Kommandon:", bot.user.avatarURL)
                    .addField("Allmäna kommandon:", "!help - visar denna meny\n!github - skickar länken till botens github repo\n!ping - visar botens internal ping (för felsökning)\n!poll <fråga> - Starta en ja eller nej fråga")
                    .addField("Skolrelaterade kommandon:", "!schema - visar veckans schema\n!hex - ger dig en slumpmässig färg")
                    .setColor("0x111111")
            });
            break;
        case "schema":
            message.channel.send({
                embed: new Discord.RichEmbed()
                    .setAuthor("Schema v."+weekNumber+":")
                    .setImage(schemaLink)
                    .setColor("0x"+("000000"+Math.random().toString(16).slice(2, 8).toUpperCase()).slice(-6))
            });
            break;
        case "github":
            message.author.send("https://github.com/ChilladeChillin/ITGDiscordBot");
            break;
        case "dab":
            message.channel.send(dabRespond[Math.floor(Math.random() * dabRespond.length)]);
            break;
        case "say":
            var textFromSender = message.content;
            message.delete(0);
            if (message.author.id==="164283691802165250") {
                message.channel.send(message.content.substring(5));
                break;
            }
            else {
                message.author.send("Du har inte tillåtelse att använda detta kommando");
                break;
            }
        case "ping":
            message.channel.send(bot.ping+" ms");
            break;
        case "hex":
            var randomhex = ("000000"+Math.random().toString(16).slice(2, 8).toUpperCase()).slice(-6);
            message.channel.send({
                embed: new Discord.RichEmbed()
                    .setAuthor("Hex:")
                    .setDescription("#"+randomhex)
                    .setColor("0x"+randomhex)
            });
            break;
        case "poll":
            var question = message.content.substring(6)
            message.delete(0);
            message.channel.send(question+"\n\n`👍=JA 👎=NEJ`")
                .then(function (message) {
                    message.react("👍")
                    message.react("👎")
                }).catch(function() {
                    console.log("Reaktionen gick inte hela vägen fram")
                   });
            break;
        case ""
        default:
            message.channel.send("```Detta kommando existerar inte (än)\nSkriv !help för att se de kommandon som faktiskt existerar```");
        //case "bordejag"
            //[Math.floor(Math.random() * bordejag.length)]
    }
});

bot.login(botToken);<|endoftext|>


<commit_before>var h = require('hyperscript')

/*
element classes are set with BEM convention.
https://css-tricks.com/bem-101/

B = block. module name, on the top level of the component.
E = element. a specific part of the component
M = modifier. something that changes an element (or block)
*/

var Menu = require('./menu')

module.exports = function (onSelect) {

  var names = []
  var tabs = h('div.row.hypertabs__tabs')
  var content = h('div.column.hypertabs__content')

  var d = h('div.hypertabs.column', Menu(content), content)

  d.add = function (name, tab, change) {
    tab.style.display = 'none'
    content.appendChild(tab)
    if(change !== false) d.select(name)
  }

  d.has = function (name) {
    return !!~names.indexOf(name)
  }

  d.select = function (name) {
    var prev = d.selectedTab
    d.selected = selected = name
    return
    var i = names.indexOf(name)
    if(!~i) return console.log('unknown tab:'+name + ' expected:' + JSON.stringify(names) + i)
    ;[].forEach.call(tabs.children, function (el) {
      el.classList.remove('hypertabs--selected')
    })
    tabs.children[i].classList.add('hypertabs--selected')
    ;[].forEach.call(content.children, function (tab) {
      tab.style.display = 'none'
    })
    var el = d.selectedTab = content.children[i]
    if(prev && prev != el)
      prev.dispatchEvent(new CustomEvent('blur', {target: el}))

    el.style.display = 'flex'
    el.dispatchEvent(new CustomEvent('focus', {target: el}))
    onSelect && onSelect(name, i)
  }

  d.selectRelative = function (n) {
    d.select(names[(names.indexOf(selected) + n + names.length) % names.length])
  }

  d.remove = function (name) {
    var i = names.indexOf(name)
    if(!~i) return
    names.splice(i, 1)
    tabs.removeChild(tabs.children[i])
    content.removeChild(content.children[i])
    if(selected === name)
      d.select(names[i] || names[0])
  }

  d.tabs = names

  return d
}

<commit_msg>select tab by index or string, wrap in flex column (magic to make columns work correctly)<commit_after>var h = require('hyperscript')

/*
element classes are set with BEM convention.
https://css-tricks.com/bem-101/

B = block. module name, on the top level of the component.
E = element. a specific part of the component
M = modifier. something that changes an element (or block)
*/

var Menu = require('./menu')

module.exports = function (onSelect) {

  var names = []
  var tabs = h('div.row.hypertabs__tabs')
  var content = h('div.row.hypertabs__content')

  var d = h('div.hypertabs.column', Menu(content), h('div.column', content))

  d.add = function (tab, change) {
    tab.style.display = 'none'
    var index = content.children.length
    content.appendChild(tab)
    if(change !== false) d.select(index)
  }

  function find(name) {
    if(Number.isInteger(name)) return content.children[name]

    for(var i = 0; i < content.children.length; i++)
      if(content.children[i].id == name) return i

    return -1
  }

  d.has = function (name) {
    return ~find(name)
  }

  d.select = function (index) {
    if('string' === typeof index) index = find(index)

    var prev = d.selectedTab
    d.selected = selected = index
    ;[].forEach.call(content.children, function (tab, i) {
      tab.style.display = i == index ? 'flex' : 'none'
    })
    onSelect && onSelect(index)
  }

  d.selectRelative = function (n) {
    d.select(selected + n)
  }

  d.remove = function (i) {
    if(i < 0 || i > content.children.length) return
    content.removeChild(content.children[i])
    if(selected === i)
      d.select(i < content.children.length ? i : 0)
  }

  return d
}

<|endoftext|>


<commit_before>const RopeSequence = require("rope-sequence")
const {Transform, Mapping} = require("../transform")

// FIXME stop relying on timing or take timestamps as input?

// ProseMirror's history isn't simply a way to roll back to a previous
// state, because ProseMirror supports applying changes without adding
// them to the history (for example during collaboration).
//
// To this end, each 'Branch' (one for the undo history and one for
// the redo history) keeps an array of 'Items', which can optionally
// hold a step (an actual undoable change), and always hold a position
// map (which is needed to move changes below them to apply to the
// current document).
//
// An item that has both a step and a selection token field is the
// start of an 'event' -- a group of changes that will be undone or
// redone at once. (It stores only a token, since that way we don't
// have to provide a document until the selection is actually applied,
// which is useful when compressing.)

// Used to schedule history compression
const max_empty_items = 500

class Branch {
  constructor(items, eventCount) {
    this.items = items
    this.eventCount = eventCount
  }

  // : (Node, bool, ?Item) → ?{transform: Transform, selection: SelectionToken, ids: [number]}
  // Pop the latest event off the branch's history and apply it
  // to a document transform, returning the transform and the step IDs.
  popEvent(doc, preserveItems) {
    if (this.eventCount == 0) return null

    let end = this.items.length
    for (;; end--) {
      let next = this.items.get(end - 1)
      if (next.selection) { --end; break }
    }

    let remap, mapFrom
    if (preserveItems) {
      remap = this.remapping(end, this.items.length)
      mapFrom = remap.maps.length
    }
    let transform = new Transform(doc)
    let selection, remaining
    let addAfter = [], addBefore = []

    this.items.forEach((item, i) => {
      if (!item.step) {
        if (!remap) {
          remap = this.remapping(end, i + 1)
          mapFrom = remap.maps.length
        }
        mapFrom--
        addBefore.push(item)
        return
      }

      if (remap) {
        addBefore.push(new Item(item.map))
        let step = item.step.map(remap.slice(mapFrom)), map

        if (step && transform.maybeStep(step).doc) {
          map = transform.mapping.maps[transform.mapping.maps.length - 1]
          addAfter.push(new Item(map, null, null, addAfter.length + addBefore.length))
        }
        mapFrom--
        if (map) remap.appendMap(map, mapFrom)
      } else {
        transform.maybeStep(item.step)
      }

      if (item.selection) {
        selection = remap ? item.selection.type.mapToken(item.selection, remap.slice(mapFrom)) : item.selection
        remaining = new Branch(this.items.slice(0, end).append(addBefore.reverse().concat(addAfter)), this.eventCount - 1)
        return false
      }
    }, this.items.length, 0)

    return {remaining, transform, selection}
  }

  // : (Transform, Selection, Object)
  // Create a new branch with the given transform added.
  addTransform(transform, selection, histOptions) {
    let newItems = [], eventCount = this.eventCount + (selection ? 1 : 0)
    for (let i = 0; i < transform.steps.length; i++) {
      let step = transform.steps[i].invert(transform.docs[i])
      newItems.push(new Item(transform.mapping.maps[i], step, selection))
      selection = null
    }
    let overflow = this.eventCount - histOptions.depth
    let items = overflow > DEPTH_OVERFLOW ? cutOffEvents(this.items, overflow) : this.items
    return new Branch(items.append(newItems), eventCount)
  }

  remapping(from, to) {
    let maps = [], mirrors = []
    this.items.forEach((item, i) => {
      if (item.mirrorOffset != null) {
        let mirrorPos = i - item.mirrorOffset
        if (mirrorPos >= from) mirrors.push(maps.length - item.mirrorOffset, maps.length)
      }
      maps.push(item.map)
    }, from, to)
    return new Mapping(maps, mirrors)
  }

  addMaps(array) {
    if (this.eventCount == 0) return this
    return new Branch(this.items.append(array.map(map => new Item(map))), this.eventCount)
  }

  // : ([PosMap], Transform, [number])
  // When the collab module receives remote changes, the history has
  // to know about those, so that it can adjust the steps that were
  // rebased on top of the remote changes, and include the position
  // maps for the remote changes in its array of items.
  rebased(rebasedTransform, rebasedCount) {
    if (!this.eventCount) return this

    let rebasedItems = [], start = this.items.length - rebasedCount, startPos = 0
    if (start < 0) {
      startPos = -start
      start = 0
    }

    let mapping = rebasedTransform.mapping
    let newUntil = rebasedTransform.steps.length

    let iRebased = startPos
    this.items.forEach(item => {
      let pos = mapping.getMirror(iRebased++)
      if (pos == null) return
      newUntil = Math.min(newUntil, pos)
      let map = mapping.maps[pos]
      if (item.step) {
        let step = rebasedTransform.steps[pos].invert(rebasedTransform.docs[pos])
        let selection = item.selection &&
            item.selection.type.mapToken(item.selection, mapping.slice(iRebased - 1, pos))
        rebasedItems.push(new Item(map, step, selection))
      } else {
        rebasedItems.push(new Item(map))
      }
    }, start)

    let newMaps = []
    for (let i = rebasedCount; i < newUntil; i++)
      newMaps.push(new Item(mapping.maps[i]))
    let items = this.items.slice(0, start).append(newMaps).append(rebasedItems)
    let branch = new Branch(items, this.eventCount) // FIXME might update event count
    if (branch.emptyItemCount() > max_empty_items)
      branch = branch.compress(this.items.length - rebasedItems.length)
    return branch
  }

  emptyItemCount() {
    let count = 0
    this.items.forEach(item => { if (!item.step) count++ })
    return count
  }

  // Compressing a branch means rewriting it to push the air (map-only
  // items) out. During collaboration, these naturally accumulate
  // because each remote change adds one. The `upto` argument is used
  // to ensure that only the items below a given level are compressed,
  // because `rebased` relies on a clean, untouched set of items in
  // order to associate old ids to rebased steps.
  compress(upto = this.items.length) {
    let remap = this.remapping(0, upto), mapFrom = remap.maps.length
    let items = [], events = 0
    this.items.forEach((item, i) => {
      if (i >= upto) {
        items.push(item)
      } else if (item.step) {
        let step = item.step.map(remap.slice(mapFrom)), map = step && step.posMap()
        mapFrom--
        if (map) remap.appendMap(map, mapFrom)
        if (step) {
          let selection = item.selection && item.selection.type.mapToken(item.selection, remap.slice(mapFrom))
          items.push(new Item(map.invert(), step, selection))
          if (selection) events++
        }
      } else if (item.map) {
        mapFrom--
      } else {
        items.push(item)
      }
    }, this.items.length, 0)
    return new Branch(RopeSequence.from(items.reverse()), events)
  }
}

Branch.empty = new Branch(RopeSequence.empty, 0)

function cutOffEvents(items, n) {
  let cutPoint
  items.forEach((item, i) => {
    if (item.selection && (--n == 0)) {
      cutPoint = i
      return false
    }
  })
  return items.slice(cutPoint)
}

// History items all have ids, but the meaning of these is somewhat
// complicated.
//
// - For StepItems, the ids are kept ordered (inside a given branch),
//   and are kept associated with a given change (if you undo and then
//   redo it, the resulting item gets the old id)
//
// - For MapItems, the ids are just opaque identifiers, not
//   necessarily ordered.
//
// - The placeholder item at the base of a branch's list
class Item {
  constructor(map, step, selection, mirrorOffset) {
    this.map = map
    this.step = step
    this.selection = selection
    this.mirrorOffset = mirrorOffset
  }
}

// ;; An undo/redo history manager for an editor instance.
class HistoryState {
  constructor(done, undone, lastAddedAt) {
    this.done = done
    this.undone = undone
    this.lastAddedAt = lastAddedAt
  }

  // :: number
  // The amount of undoable events available.
  get undoDepth() { return this.done.eventCount }

  // :: number
  // The amount of redoable events available.
  get redoDepth() { return this.undone.eventCount }

  // :: () → History
  // Makes sure that the next change made will start a new history
  // event, not be added to the last event.
  cut() {
    return new HistoryState(this.done, this.undone, 0)
  }

  // Used by the test suite to force compression
  forceCompress() {
    return new HistoryState(this.done.compress(), this.undone, this.lastAddedAt)
  }
}
exports.HistoryState = HistoryState

const defaults = {
  depth: 100,
  eventDelay: 500,
  preserveItems: false
}
const DEPTH_OVERFLOW = 20

// : (EditorState, Transform, Selection, Object)
// Record a transformation in undo history.
function applyTransform(state, transform, options, histOptions) {
  let cur = state.history
  if (options.historyState) {
    return options.historyState
  } else if (options.addToHistory == false) {
    if (options.rebased) {
      // Used by the collab module to tell the history that some of its
      // content has been rebased.
      return new HistoryState(cur.done.rebased(transform, options.rebased),
                              cur.undone.rebased(transform, options.rebased),
                              cur.lastAddedAt)
    } else {
      return new HistoryState(cur.done.addMaps(transform.mapping.maps),
                              cur.undone.addMaps(transform.mapping.maps),
                              cur.lastAddedAt)
    }
  } else {
    let now = Date.now()
    // Group transforms that occur in quick succession into one event.
    let newGroup = now > cur.lastAddedAt + histOptions.eventDelay
    return new HistoryState(cur.done.addTransform(transform, newGroup ? state.selection.token : null, histOptions),
                            Branch.empty,
                            now)
  }
}

// : (EditorState, bool) → EditorState
// Apply the latest event from one branch to the document and optionally
// shift the event onto the other branch. Returns true when an event could
// be shifted.
function shift(state, redo, histOptions) {
  let cur = state.history
  let pop = (redo ? cur.undone : cur.done).popEvent(state.doc, histOptions.preserveItems)
  if (!pop) return state

  let selectionBeforeTransform = state.selection
  let selection = pop.selection.type.fromToken(pop.selection, pop.transform.doc)
  let added = (redo ? cur.done : cur.undone).addTransform(pop.transform, selectionBeforeTransform.token, histOptions)

  let newHist = new HistoryState(redo ? added : pop.remaining, redo ? pop.remaining : added, 0)
  let newState = state.applyTransform(pop.transform, {selection, filter: false, historyState: newHist})
  if (!pop.transform.steps.length && pop.remaining.eventCount) return shift(newState, redo, histOptions)
  return newState
}

// :: (Object) → Plugin
// A plugin that enables the undo history for an editor. Has the
// effect of setting the editor's `history` property to an instance of
// `History`. Takes the following options:
//
// **`depth`**`: number`
//   : The amount of history events that are collected before the
//     oldest events are discarded. Defaults to 100.
//
// **`eventDelay`**`: number`
//   : The amount of milliseconds that must pass between changes to
//     start a new history event. Defaults to 500.
//
// **`preserveItems`**`: bool`
//   : Whether to throw away undone items. Needs to be true to use the
//     history together with the collaborative editing plugin.
exports.history = function(config) {
  let histOptions = {}
  for (let prop in defaults) histOptions[prop] = config && config.hasOwnProperty(prop) ? config[prop] : defaults[prop]

  return {
    stateFields: {
      history: {
        init() {
          return new HistoryState(Branch.empty, Branch.empty, 0)
        },
        applyTransform(state, transform, options) {
          return applyTransform(state, transform, options, histOptions)
        }
      }
    },

    stateMethods: {
      undo() { return shift(this, false, histOptions) },
      redo() { return shift(this, true, histOptions) }
    }
  }
}
<commit_msg>Remove the concept of plugins, extend state class differently<commit_after>const RopeSequence = require("rope-sequence")
const {Transform, Mapping} = require("../transform")

// FIXME stop relying on timing or take timestamps as input?

// ProseMirror's history isn't simply a way to roll back to a previous
// state, because ProseMirror supports applying changes without adding
// them to the history (for example during collaboration).
//
// To this end, each 'Branch' (one for the undo history and one for
// the redo history) keeps an array of 'Items', which can optionally
// hold a step (an actual undoable change), and always hold a position
// map (which is needed to move changes below them to apply to the
// current document).
//
// An item that has both a step and a selection token field is the
// start of an 'event' -- a group of changes that will be undone or
// redone at once. (It stores only a token, since that way we don't
// have to provide a document until the selection is actually applied,
// which is useful when compressing.)

// Used to schedule history compression
const max_empty_items = 500

class Branch {
  constructor(items, eventCount) {
    this.items = items
    this.eventCount = eventCount
  }

  // : (Node, bool, ?Item) → ?{transform: Transform, selection: SelectionToken, ids: [number]}
  // Pop the latest event off the branch's history and apply it
  // to a document transform, returning the transform and the step IDs.
  popEvent(doc, preserveItems) {
    if (this.eventCount == 0) return null

    let end = this.items.length
    for (;; end--) {
      let next = this.items.get(end - 1)
      if (next.selection) { --end; break }
    }

    let remap, mapFrom
    if (preserveItems) {
      remap = this.remapping(end, this.items.length)
      mapFrom = remap.maps.length
    }
    let transform = new Transform(doc)
    let selection, remaining
    let addAfter = [], addBefore = []

    this.items.forEach((item, i) => {
      if (!item.step) {
        if (!remap) {
          remap = this.remapping(end, i + 1)
          mapFrom = remap.maps.length
        }
        mapFrom--
        addBefore.push(item)
        return
      }

      if (remap) {
        addBefore.push(new Item(item.map))
        let step = item.step.map(remap.slice(mapFrom)), map

        if (step && transform.maybeStep(step).doc) {
          map = transform.mapping.maps[transform.mapping.maps.length - 1]
          addAfter.push(new Item(map, null, null, addAfter.length + addBefore.length))
        }
        mapFrom--
        if (map) remap.appendMap(map, mapFrom)
      } else {
        transform.maybeStep(item.step)
      }

      if (item.selection) {
        selection = remap ? item.selection.type.mapToken(item.selection, remap.slice(mapFrom)) : item.selection
        remaining = new Branch(this.items.slice(0, end).append(addBefore.reverse().concat(addAfter)), this.eventCount - 1)
        return false
      }
    }, this.items.length, 0)

    return {remaining, transform, selection}
  }

  // : (Transform, Selection, Object)
  // Create a new branch with the given transform added.
  addTransform(transform, selection, histOptions) {
    let newItems = [], eventCount = this.eventCount + (selection ? 1 : 0)
    for (let i = 0; i < transform.steps.length; i++) {
      let step = transform.steps[i].invert(transform.docs[i])
      newItems.push(new Item(transform.mapping.maps[i], step, selection))
      selection = null
    }
    let overflow = this.eventCount - histOptions.depth
    let items = overflow > DEPTH_OVERFLOW ? cutOffEvents(this.items, overflow) : this.items
    return new Branch(items.append(newItems), eventCount)
  }

  remapping(from, to) {
    let maps = [], mirrors = []
    this.items.forEach((item, i) => {
      if (item.mirrorOffset != null) {
        let mirrorPos = i - item.mirrorOffset
        if (mirrorPos >= from) mirrors.push(maps.length - item.mirrorOffset, maps.length)
      }
      maps.push(item.map)
    }, from, to)
    return new Mapping(maps, mirrors)
  }

  addMaps(array) {
    if (this.eventCount == 0) return this
    return new Branch(this.items.append(array.map(map => new Item(map))), this.eventCount)
  }

  // : ([PosMap], Transform, [number])
  // When the collab module receives remote changes, the history has
  // to know about those, so that it can adjust the steps that were
  // rebased on top of the remote changes, and include the position
  // maps for the remote changes in its array of items.
  rebased(rebasedTransform, rebasedCount) {
    if (!this.eventCount) return this

    let rebasedItems = [], start = this.items.length - rebasedCount, startPos = 0
    if (start < 0) {
      startPos = -start
      start = 0
    }

    let mapping = rebasedTransform.mapping
    let newUntil = rebasedTransform.steps.length

    let iRebased = startPos
    this.items.forEach(item => {
      let pos = mapping.getMirror(iRebased++)
      if (pos == null) return
      newUntil = Math.min(newUntil, pos)
      let map = mapping.maps[pos]
      if (item.step) {
        let step = rebasedTransform.steps[pos].invert(rebasedTransform.docs[pos])
        let selection = item.selection &&
            item.selection.type.mapToken(item.selection, mapping.slice(iRebased - 1, pos))
        rebasedItems.push(new Item(map, step, selection))
      } else {
        rebasedItems.push(new Item(map))
      }
    }, start)

    let newMaps = []
    for (let i = rebasedCount; i < newUntil; i++)
      newMaps.push(new Item(mapping.maps[i]))
    let items = this.items.slice(0, start).append(newMaps).append(rebasedItems)
    let branch = new Branch(items, this.eventCount) // FIXME might update event count
    if (branch.emptyItemCount() > max_empty_items)
      branch = branch.compress(this.items.length - rebasedItems.length)
    return branch
  }

  emptyItemCount() {
    let count = 0
    this.items.forEach(item => { if (!item.step) count++ })
    return count
  }

  // Compressing a branch means rewriting it to push the air (map-only
  // items) out. During collaboration, these naturally accumulate
  // because each remote change adds one. The `upto` argument is used
  // to ensure that only the items below a given level are compressed,
  // because `rebased` relies on a clean, untouched set of items in
  // order to associate old ids to rebased steps.
  compress(upto = this.items.length) {
    let remap = this.remapping(0, upto), mapFrom = remap.maps.length
    let items = [], events = 0
    this.items.forEach((item, i) => {
      if (i >= upto) {
        items.push(item)
      } else if (item.step) {
        let step = item.step.map(remap.slice(mapFrom)), map = step && step.posMap()
        mapFrom--
        if (map) remap.appendMap(map, mapFrom)
        if (step) {
          let selection = item.selection && item.selection.type.mapToken(item.selection, remap.slice(mapFrom))
          items.push(new Item(map.invert(), step, selection))
          if (selection) events++
        }
      } else if (item.map) {
        mapFrom--
      } else {
        items.push(item)
      }
    }, this.items.length, 0)
    return new Branch(RopeSequence.from(items.reverse()), events)
  }
}

Branch.empty = new Branch(RopeSequence.empty, 0)

function cutOffEvents(items, n) {
  let cutPoint
  items.forEach((item, i) => {
    if (item.selection && (--n == 0)) {
      cutPoint = i
      return false
    }
  })
  return items.slice(cutPoint)
}

// History items all have ids, but the meaning of these is somewhat
// complicated.
//
// - For StepItems, the ids are kept ordered (inside a given branch),
//   and are kept associated with a given change (if you undo and then
//   redo it, the resulting item gets the old id)
//
// - For MapItems, the ids are just opaque identifiers, not
//   necessarily ordered.
//
// - The placeholder item at the base of a branch's list
class Item {
  constructor(map, step, selection, mirrorOffset) {
    this.map = map
    this.step = step
    this.selection = selection
    this.mirrorOffset = mirrorOffset
  }
}

// ;; An undo/redo history manager for an editor instance.
class HistoryState {
  constructor(done, undone, lastAddedAt) {
    this.done = done
    this.undone = undone
    this.lastAddedAt = lastAddedAt
  }

  // :: number
  // The amount of undoable events available.
  get undoDepth() { return this.done.eventCount }

  // :: number
  // The amount of redoable events available.
  get redoDepth() { return this.undone.eventCount }

  // :: () → History
  // Makes sure that the next change made will start a new history
  // event, not be added to the last event.
  cut() {
    return new HistoryState(this.done, this.undone, 0)
  }

  // Used by the test suite to force compression
  forceCompress() {
    return new HistoryState(this.done.compress(), this.undone, this.lastAddedAt)
  }
}
exports.HistoryState = HistoryState

const defaults = {
  depth: 100,
  eventDelay: 500,
  preserveItems: false
}
const DEPTH_OVERFLOW = 20

// : (EditorState, Transform, Selection, Object)
// Record a transformation in undo history.
function applyTransform(state, transform, options, histOptions) {
  let cur = state.history
  if (options.historyState) {
    return options.historyState
  } else if (options.addToHistory == false) {
    if (options.rebased) {
      // Used by the collab module to tell the history that some of its
      // content has been rebased.
      return new HistoryState(cur.done.rebased(transform, options.rebased),
                              cur.undone.rebased(transform, options.rebased),
                              cur.lastAddedAt)
    } else {
      return new HistoryState(cur.done.addMaps(transform.mapping.maps),
                              cur.undone.addMaps(transform.mapping.maps),
                              cur.lastAddedAt)
    }
  } else {
    let now = Date.now()
    // Group transforms that occur in quick succession into one event.
    let newGroup = now > cur.lastAddedAt + histOptions.eventDelay
    return new HistoryState(cur.done.addTransform(transform, newGroup ? state.selection.token : null, histOptions),
                            Branch.empty,
                            now)
  }
}

// : (EditorState, bool) → EditorState
// Apply the latest event from one branch to the document and optionally
// shift the event onto the other branch. Returns true when an event could
// be shifted.
function shift(state, redo, histOptions) {
  let cur = state.history
  let pop = (redo ? cur.undone : cur.done).popEvent(state.doc, histOptions.preserveItems)
  if (!pop) return state

  let selectionBeforeTransform = state.selection
  let selection = pop.selection.type.fromToken(pop.selection, pop.transform.doc)
  let added = (redo ? cur.done : cur.undone).addTransform(pop.transform, selectionBeforeTransform.token, histOptions)

  let newHist = new HistoryState(redo ? added : pop.remaining, redo ? pop.remaining : added, 0)
  let newState = state.applyTransform(pop.transform, {selection, filter: false, historyState: newHist})
  if (!pop.transform.steps.length && pop.remaining.eventCount) return shift(newState, redo, histOptions)
  return newState
}

// :: (Object) → Plugin
// A plugin that enables the undo history for an editor. Has the
// effect of setting the editor's `history` property to an instance of
// `History`. Takes the following options:
//
// **`depth`**`: number`
//   : The amount of history events that are collected before the
//     oldest events are discarded. Defaults to 100.
//
// **`eventDelay`**`: number`
//   : The amount of milliseconds that must pass between changes to
//     start a new history event. Defaults to 500.
//
// **`preserveItems`**`: bool`
//   : Whether to throw away undone items. Needs to be true to use the
//     history together with the collaborative editing plugin.
exports.history = function(config) {
  let histOptions = {}
  for (let prop in defaults) histOptions[prop] = config && config.hasOwnProperty(prop) ? config[prop] : defaults[prop]

  return {
    fields: {
      history: {
        init() {
          return new HistoryState(Branch.empty, Branch.empty, 0)
        },
        applyTransform(state, transform, options) {
          return applyTransform(state, transform, options, histOptions)
        }
      }
    },

    methods: {
      undo() { return shift(this, false, histOptions) },
      redo() { return shift(this, true, histOptions) }
    }
  }
}
<|endoftext|>


<commit_before>import stableStringify from 'json-stable-stringify'
import {autocomplete as mapzenAutocomplete, reverse as reverseSearch} from 'isomorphic-mapzen-search'
import isEqual from 'lodash/isEqual'
import throttle from 'lodash/throttle'
import PropTypes from 'prop-types'
import React, {PureComponent} from 'react'
import Select from 'react-select'

const GEOLOCATE_VALUE = 'geolocate'

class Geocoder extends PureComponent {
  static propTypes = {
    apiKey: PropTypes.string.isRequired,
    boundary: PropTypes.object,
    featureToLabel: PropTypes.func,
    featureToValue: PropTypes.func,
    findingLocationText: PropTypes.string,
    focusPoint: PropTypes.any,
    geolocate: PropTypes.bool,
    onChange: PropTypes.func,
    placeholder: PropTypes.string,
    rateLimit: PropTypes.number,
    reverseSearch: PropTypes.func,
    search: PropTypes.func,
    useLocationText: PropTypes.string,
    value: PropTypes.object
  }

  static defaultProps = {
    featureToLabel: (feature) => feature.properties.label,
    featureToValue: (feature) => `${feature.properties.label}-${feature.geometry.coordinates.join(',')}`,
    findingLocationText: 'Locating you...',
    reverseSearch,
    search: mapzenAutocomplete,
    useLocationText: 'Use Current Location'
  }

  autocompleteCache = {}

  options = {}

  state = {
    options: this.defaultOptions(),
    value: this.props.value || null
  }

  cacheOptions (options) {
    options.forEach((o) => {
      this.options[o.value] = o.feature
    })
  }

  componentWillMount () {
    this._throttledLoadOptions = throttle(this.loadOptions, this.props.rateLimit || 500)
  }

  componentWillReceiveProps (nextProps) {
    if (!isEqual(nextProps.value, this.props.value)) {
      this.setState({value: nextProps.value})
    }

    if (this.props.rateLimit !== nextProps.rateLimit) {
      this._throttledLoadOptions = throttle(this.loadOptions, this.props.rateLimit || 500)
    }
  }

  defaultOptions () {
    return this.props.geolocate && 'geolocation' in navigator
      ? [{label: this.props.useLocationText, value: GEOLOCATE_VALUE}]
      : []
  }

  featureToOption = (feature) => {
    const {featureToLabel, featureToValue} = this.props
    return {
      feature,
      label: featureToLabel(feature),
      value: featureToValue(feature)
    }
  }

  focus () {
    this.select.focus()
  }

  loadOptions = (input, callback) => {
    const {apiKey, boundary, focusPoint, geolocate, search} = this.props
    if (!input) {
      if (geolocate && 'geolocation' in navigator) {
        callback(null, {
          options: this.defaultOptions()
        })
      }
    } else {
      const autocompleteQuery = {
        apiKey,
        boundary,
        focusPoint,
        text: input
      }
      const autocompleteQueryKey = stableStringify(autocompleteQuery)

      // check if autocomplete query has been made before
      const cacheValue = this.autocompleteCache[autocompleteQueryKey]
      if (cacheValue) {
        return callback(null, cacheValue)
      }

      search(autocompleteQuery).then((geojson) => {
        const options = geojson && geojson.features
          ? geojson.features.map(this.featureToOption)
          : []
        this.cacheOptions(options)
        this.autocompleteCache[autocompleteQueryKey] = {options}
        callback(null, {options})
      }).catch((error) => {
        callback(error)
      })
    }
  }

  _onChange = (value) => {
    const {apiKey, findingLocationText, onChange, reverseSearch} = this.props
    if (value && value.value === GEOLOCATE_VALUE) {
      this.setState({
        ...this.state,
        value: {
          label: findingLocationText
        }
      })
      window.navigator.geolocation.getCurrentPosition((position) => {
        reverseSearch({
          apiKey,
          point: position.coords
        }).then((geojson) => {
          const value = this.featureToOption(geojson.features[0])
          this.setState({
            ...this.state,
            value
          })
          onChange && onChange(value)
        }).catch((err) => {
          console.error('Error during reverse lookup of', position)
          console.error(err)
          const value = this.featureToOption({
            type: 'Feature',
            properties: {
              label: `${position.longitude}, ${position.latitude}`
            },
            geometry: {
              type: 'Point',
              coordinates: [
                position.longitude,
                position.latitude
              ]
            }
          })
          this.setState({
            ...this.state,
            value
          })
          onChange && onChange(value)
        })
      })
    } else {
      if (!value) {
        this.setState({
          options: this.defaultOptions(),
          value
        })
      } else {
        this.setState({value})
      }
      this.props.onChange && this.props.onChange(value && this.options[value.value])
    }
  }

  _saveRef = (select) => {
    this.select = select
  }

  render () {
    return (
      <Select.Async
        autoBlur
        autoload={false}
        cache={false}
        filterOptions={false}
        ignoreAccents={false}
        ignoreCase={false}
        loadOptions={this._throttledLoadOptions}
        minimumInput={3}
        options={this.state.options}
        {...this.props}
        onChange={this._onChange}
        ref={this._saveRef}
        value={this.state.value}
        />
    )
  }
}

module.exports = Geocoder
<commit_msg>revert(component): Just use Component since we implement shouldComponentUpdate manually<commit_after>import stableStringify from 'json-stable-stringify'
import {autocomplete as mapzenAutocomplete, reverse as reverseSearch} from 'isomorphic-mapzen-search'
import isEqual from 'lodash/isEqual'
import throttle from 'lodash/throttle'
import PropTypes from 'prop-types'
import React, {Component} from 'react'
import Select from 'react-select'

const GEOLOCATE_VALUE = 'geolocate'

class Geocoder extends Component {
  static propTypes = {
    apiKey: PropTypes.string.isRequired,
    boundary: PropTypes.object,
    featureToLabel: PropTypes.func,
    featureToValue: PropTypes.func,
    findingLocationText: PropTypes.string,
    focusPoint: PropTypes.any,
    geolocate: PropTypes.bool,
    onChange: PropTypes.func,
    placeholder: PropTypes.string,
    rateLimit: PropTypes.number,
    reverseSearch: PropTypes.func,
    search: PropTypes.func,
    useLocationText: PropTypes.string,
    value: PropTypes.object
  }

  static defaultProps = {
    featureToLabel: (feature) => feature.properties.label,
    featureToValue: (feature) => `${feature.properties.label}-${feature.geometry.coordinates.join(',')}`,
    findingLocationText: 'Locating you...',
    reverseSearch,
    search: mapzenAutocomplete,
    useLocationText: 'Use Current Location'
  }

  autocompleteCache = {}

  options = {}

  state = {
    options: this.defaultOptions(),
    value: this.props.value || null
  }

  cacheOptions (options) {
    options.forEach((o) => {
      this.options[o.value] = o.feature
    })
  }

  componentWillMount () {
    this._throttledLoadOptions = throttle(this.loadOptions, this.props.rateLimit || 500)
  }

  componentWillReceiveProps (nextProps) {
    if (!isEqual(nextProps.value, this.props.value)) {
      this.setState({value: nextProps.value})
    }

    if (this.props.rateLimit !== nextProps.rateLimit) {
      this._throttledLoadOptions = throttle(this.loadOptions, this.props.rateLimit || 500)
    }
  }

  defaultOptions () {
    return this.props.geolocate && 'geolocation' in navigator
      ? [{label: this.props.useLocationText, value: GEOLOCATE_VALUE}]
      : []
  }

  featureToOption = (feature) => {
    const {featureToLabel, featureToValue} = this.props
    return {
      feature,
      label: featureToLabel(feature),
      value: featureToValue(feature)
    }
  }

  focus () {
    this.select.focus()
  }

  loadOptions = (input, callback) => {
    const {apiKey, boundary, focusPoint, geolocate, search} = this.props
    if (!input) {
      if (geolocate && 'geolocation' in navigator) {
        callback(null, {
          options: this.defaultOptions()
        })
      }
    } else {
      const autocompleteQuery = {
        apiKey,
        boundary,
        focusPoint,
        text: input
      }
      const autocompleteQueryKey = stableStringify(autocompleteQuery)

      // check if autocomplete query has been made before
      const cacheValue = this.autocompleteCache[autocompleteQueryKey]
      if (cacheValue) {
        return callback(null, cacheValue)
      }

      search(autocompleteQuery).then((geojson) => {
        const options = geojson && geojson.features
          ? geojson.features.map(this.featureToOption)
          : []
        this.cacheOptions(options)
        this.autocompleteCache[autocompleteQueryKey] = {options}
        callback(null, {options})
      }).catch((error) => {
        callback(error)
      })
    }
  }

  _onChange = (value) => {
    const {apiKey, findingLocationText, onChange, reverseSearch} = this.props
    if (value && value.value === GEOLOCATE_VALUE) {
      this.setState({
        ...this.state,
        value: {
          label: findingLocationText
        }
      })
      window.navigator.geolocation.getCurrentPosition((position) => {
        reverseSearch({
          apiKey,
          point: position.coords
        }).then((geojson) => {
          const value = this.featureToOption(geojson.features[0])
          this.setState({
            ...this.state,
            value
          })
          onChange && onChange(value)
        }).catch((err) => {
          console.error('Error during reverse lookup of', position)
          console.error(err)
          const value = this.featureToOption({
            type: 'Feature',
            properties: {
              label: `${position.longitude}, ${position.latitude}`
            },
            geometry: {
              type: 'Point',
              coordinates: [
                position.longitude,
                position.latitude
              ]
            }
          })
          this.setState({
            ...this.state,
            value
          })
          onChange && onChange(value)
        })
      })
    } else {
      if (!value) {
        this.setState({
          options: this.defaultOptions(),
          value
        })
      } else {
        this.setState({value})
      }
      this.props.onChange && this.props.onChange(value && this.options[value.value])
    }
  }

  _saveRef = (select) => {
    this.select = select
  }

  render () {
    return (
      <Select.Async
        autoBlur
        autoload={false}
        cache={false}
        filterOptions={false}
        ignoreAccents={false}
        ignoreCase={false}
        loadOptions={this._throttledLoadOptions}
        minimumInput={3}
        options={this.state.options}
        {...this.props}
        onChange={this._onChange}
        ref={this._saveRef}
        value={this.state.value}
        />
    )
  }
}

module.exports = Geocoder
<|endoftext|>