<commit_before>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};
<commit_msg>Update ember try config<commit_after>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4',
          'ember-cli-shims': '0.1.3'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8',
          'ember-cli-shims': '0.1.3'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release',
          'ember-cli-shims': '0.1.3'
        },
        resolutions: {
          'ember': 'release'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta',
          'ember-cli-shims': '0.1.3'
        },
        resolutions: {
          'ember': 'beta'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary',
          'ember-cli-shims': '0.1.3'
        },
        resolutions: {
          'ember': 'canary'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-default',
      npm: {
        devDependencies: {}
      }
    }
  ]
};
<|endoftext|>


<commit_before>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'ember-lts-2.4',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-4'
        },
        resolutions: {
          'ember': 'lts-2-4'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-lts-2.8',
      bower: {
        dependencies: {
          'ember': 'components/ember#lts-2-8'
        },
        resolutions: {
          'ember': 'lts-2-8'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      },
      npm: {
        devDependencies: {
          'ember-source': null
        }
      }
    },
    {
      name: 'ember-default',
      npm: {
        devDependencies: {}
      }
    }
  ]
};
<commit_msg>sliyness<commit_after>/*jshint node:true*/
const scenarios = [
  {
    name: 'ember-lts-2.4',
    bower: {
      dependencies: {
        'ember': 'components/ember#lts-2-4'
      },
      resolutions: {
        'ember': 'lts-2-4'
      }
    },
    npm: {
      devDependencies: {
        'ember-source': null
      }
    }
  },
  {
    name: 'ember-lts-2.8',
    bower: {
      dependencies: {
        'ember': 'components/ember#lts-2-8'
      },
      resolutions: {
        'ember': 'lts-2-8'
      }
    },
    npm: {
      devDependencies: {
        'ember-source': null
      }
    }
  },
  {
    name: 'ember-release',
    bower: {
      dependencies: {
        'ember': 'components/ember#release'
      },
      resolutions: {
        'ember': 'release'
      }
    },
    npm: {
      devDependencies: {
        'ember-source': null
      }
    }
  },
  {
    name: 'ember-beta',
    bower: {
      dependencies: {
        'ember': 'components/ember#beta'
      },
      resolutions: {
        'ember': 'beta'
      }
    },
    npm: {
      devDependencies: {
        'ember-source': null
      }
    }
  },
  {
    name: 'ember-canary',
    bower: {
      dependencies: {
        'ember': 'components/ember#canary'
      },
      resolutions: {
        'ember': 'canary'
      }
    },
    npm: {
      devDependencies: {
        'ember-source': null
      }
    }
  },
  {
    name: 'ember-default',
    npm: {
      devDependencies: {}
    }
  }
]

module.exports = {
  scenarios: scenarios
};
<|endoftext|>


<commit_before>import React from "react"
import { Helmet } from "react-helmet"
import { graphql } from "gatsby"
import TagsIcon from "react-icons/lib/ti/tags"

import BlogPostPreviewItem from "../components/blog-post-preview-item"
import Button from "../components/button"
import Container from "../components/container"
import Layout from "../components/layout"
import { space } from "../utils/presets"

// Select first tag with whitespace instead of hyphens for
// readability. But if none present, just use the first tag in the
// collection
const preferSpacedTag = tags => {
  for (const tag of tags) {
    if (!tag.includes(` `)) {
      return tag
    }
  }
  return tags[0]
}

const Tags = ({ pageContext, data, location }) => {
  const { tags } = pageContext
  const { edges, totalCount } = data.allMdx
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? `` : `s`
  } tagged with "${preferSpacedTag(tags)}"`

  return (
    <Layout location={location}>
      <Helmet>
        <title>{`${preferSpacedTag(tags)} Tag`}</title>
        <meta
          name="description"
          content={`Case studies, tutorials, and other posts about Gatsby related to ${preferSpacedTag(
            tags
          )}`}
        />
      </Helmet>
      <Container>

{tagHeader}

        <Button small key="blog-post-view-all-tags-button" to="/blog/tags">
          View all Tags <TagsIcon />
        </Button>
        {edges.map(({ node }) => (
          <BlogPostPreviewItem
            post={node}
            key={node.fields.slug}
            css={{
              marginTop: space[9],
              marginBottom: space[9],
            }}
          />
        ))}
      </Container>
    </Layout>
  )
}

export default Tags

export const pageQuery = graphql`
  query($tags: [String]) {
    allMdx(
      limit: 2000
      sort: { fields: [frontmatter___date, fields___slug], order: DESC }
      filter: {
        frontmatter: { tags: { in: $tags } }
        fileAbsolutePath: { regex: "/docs.blog/" }
        fields: { released: { eq: true } }
      }
    ) {
      totalCount
      edges {
        node {
          ...BlogPostPreview_item
        }
      }
    }
  }
`
<commit_msg>fix(www): Link area overflow problem in tag page (#14335)<commit_after>import React from "react"
import { Helmet } from "react-helmet"
import { graphql } from "gatsby"
import TagsIcon from "react-icons/lib/ti/tags"

import BlogPostPreviewItem from "../components/blog-post-preview-item"
import Button from "../components/button"
import Container from "../components/container"
import Layout from "../components/layout"
import { space } from "../utils/presets"

// Select first tag with whitespace instead of hyphens for
// readability. But if none present, just use the first tag in the
// collection
const preferSpacedTag = tags => {
  for (const tag of tags) {
    if (!tag.includes(` `)) {
      return tag
    }
  }
  return tags[0]
}

const Tags = ({ pageContext, data, location }) => {
  const { tags } = pageContext
  const { edges, totalCount } = data.allMdx
  const tagHeader = `${totalCount} post${
    totalCount === 1 ? `` : `s`
  } tagged with "${preferSpacedTag(tags)}"`

  return (
    <Layout location={location}>
      <Helmet>
        <title>{`${preferSpacedTag(tags)} Tag`}</title>
        <meta
          name="description"
          content={`Case studies, tutorials, and other posts about Gatsby related to ${preferSpacedTag(
            tags
          )}`}
        />
      </Helmet>
      <Container>

{tagHeader}

        <Button small key="blog-post-view-all-tags-button" to="/blog/tags">
          View all Tags <TagsIcon />
        </Button>
        {edges.map(({ node }) => (
          <BlogPostPreviewItem
            post={node}
            key={node.fields.slug}
            css={{
              position: `relative`,
              marginTop: space[9],
              marginBottom: space[9],
            }}
          />
        ))}
      </Container>
    </Layout>
  )
}

export default Tags

export const pageQuery = graphql`
  query($tags: [String]) {
    allMdx(
      limit: 2000
      sort: { fields: [frontmatter___date, fields___slug], order: DESC }
      filter: {
        frontmatter: { tags: { in: $tags } }
        fileAbsolutePath: { regex: "/docs.blog/" }
        fields: { released: { eq: true } }
      }
    ) {
      totalCount
      edges {
        node {
          ...BlogPostPreview_item
        }
      }
    }
  }
`
<|endoftext|>


<commit_before>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'default',
      bower: {
        dependencies: { }
      }
    },
    {
      name: 'ember-1.13',
      bower: {
        dependencies: {
          'ember': '~1.13.0'
        },
        resolutions: {
          'ember': '~1.13.0'
        }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};
<commit_msg>[DEPENDENCY] remove building of 1.X series with ember-try<commit_after>/*jshint node:true*/
module.exports = {
  scenarios: [
    {
      name: 'default',
      bower: {
        dependencies: { }
      }
    },
    {
      name: 'ember-release',
      bower: {
        dependencies: {
          'ember': 'components/ember#release'
        },
        resolutions: {
          'ember': 'release'
        }
      }
    },
    {
      name: 'ember-beta',
      bower: {
        dependencies: {
          'ember': 'components/ember#beta'
        },
        resolutions: {
          'ember': 'beta'
        }
      }
    },
    {
      name: 'ember-canary',
      bower: {
        dependencies: {
          'ember': 'components/ember#canary'
        },
        resolutions: {
          'ember': 'canary'
        }
      }
    }
  ]
};
<|endoftext|>


<commit_before>module.exports = {
  // Component specific configuration
  auth: {
    // Azure table for the client table
    clientTableName:                'LoadTestClients',

    // Publish references and schemas
    publishMetaData:                'false',

    // Name of component in statistics
    statsComponent:                 'auth-load-tests',
  },

  // Server configuration
  server: {
    env:                            'production',
    forceSSL:                       true,
    // We trust the proxy on heroku, as the SSL end-point provided by heroku
    // is a proxy, so we have to trust it.
    trustProxy:                     true
  },

  // AWS SDK configuration for publication of schemas and references
  aws: {
    // Default AWS region, this is where the S3 bucket lives
    region:                         'us-west-2',

    // Lock API version to use the latest API from 2013, this is fuzzy locking,
    // but it does the trick...
    apiVersion:                     '2014-01-01'
  }
};
<commit_msg>Add fix load-test<commit_after>module.exports = {
  // Component specific configuration
  auth: {
    // Azure table for the client table
    clientTableName:                'LoadTestClients',

    // Publish references and schemas
    publishMetaData:                'false',

    // Name of component in statistics
    statsComponent:                 'auth-load-tests',
  },

  // Server configuration
  server: {
    publicUrl:                      'https://tc-auth-load-test.herokuapp.com',
    env:                            'production',
    forceSSL:                       true,
    // We trust the proxy on heroku, as the SSL end-point provided by heroku
    // is a proxy, so we have to trust it.
    trustProxy:                     true
  },

  // AWS SDK configuration for publication of schemas and references
  aws: {
    // Default AWS region, this is where the S3 bucket lives
    region:                         'us-west-2',

    // Lock API version to use the latest API from 2013, this is fuzzy locking,
    // but it does the trick...
    apiVersion:                     '2014-01-01'
  }
};
<|endoftext|>


<commit_before>import add from '../support/task';
import getValue from '../system/getValue';
import moreToDo from '../common/moreToDo';
import {lastActivityRE, nowSecs} from '../support/constants';

var dotList;
var dotCount;
var redDot =
  '';
var greenDiamond =
  '';
var yellowDiamond =
  '';
var orangeDiamond =
  '';
var offlineDot =
  '';
var sevenDayDot =
  '';

var getMins = [
  function(obj, min) {
    if (obj.day) {return min + parseInt(obj.day, 10) * 1440;}
    return min;
  },
  function(obj, min) {
    if (obj.hour) {return min + parseInt(obj.hour, 10) * 60;}
    return min;
  },
  function(obj, min) {
    if (obj.min) {return min + parseInt(obj.min, 10);}
    return min;
  },
  function(obj, min) {
    if (obj.last_login) {
      return Math.floor((nowSecs - obj.last_login) / 60);
    }
    return min;
  },
  function(obj, min) {
    // last_login is 'false' over 30 days
    if ('last_login' in obj && !obj.last_login) {return 99999;}
    return min;
  }
];

var getDot = [
  {condition: 2, result: greenDiamond},
  {condition: 5, result: yellowDiamond},
  {condition: 30, result: orangeDiamond},
  {condition: 10080, result: offlineDot},
  {condition: 44640, result: sevenDayDot}
];

export function onlineDot(obj) {
  var min = getMins.reduce(function(prev, curr) {
    return curr(obj, prev);
  }, 0);
  for (var i = 0; i < getDot.length; i += 1) {
    var el = getDot[i];
    if (min < el.condition) {return el.result;}
  }
  return redDot;
}

function changeOnlineDot(contactLink) {
  var lastActivity = lastActivityRE
    .exec(contactLink.dataset.tipped);
  contactLink.parentNode.previousSibling.innerHTML =
    onlineDot({
      min: lastActivity[3],
      hour: lastActivity[2],
      day: lastActivity[1]
    });
}

function batchDots() {
  var limit = performance.now() + 5;
  while (moreToDo(limit, dotCount, dotList)) {
    changeOnlineDot(dotList[dotCount]);
    dotCount += 1;
  }
  if (dotCount < dotList.length) {
    add(3, batchDots);
  }
}

export function colouredDots() {
  if (!getValue('enhanceOnlineDots')) {return;}
  dotList = document.querySelectorAll(
    '#pCC a[data-tipped*="Last Activity"]');
  dotCount = 0;
  add(3, batchDots);
}
<commit_msg>fixup import path<commit_after>import add from '../support/task';
import getValue from '../system/getValue';
import moreToDo from './moreToDo';
import {lastActivityRE, nowSecs} from '../support/constants';

var dotList;
var dotCount;
var redDot =
  '';
var greenDiamond =
  '';
var yellowDiamond =
  '';
var orangeDiamond =
  '';
var offlineDot =
  '';
var sevenDayDot =
  '';

var getMins = [
  function(obj, min) {
    if (obj.day) {return min + parseInt(obj.day, 10) * 1440;}
    return min;
  },
  function(obj, min) {
    if (obj.hour) {return min + parseInt(obj.hour, 10) * 60;}
    return min;
  },
  function(obj, min) {
    if (obj.min) {return min + parseInt(obj.min, 10);}
    return min;
  },
  function(obj, min) {
    if (obj.last_login) {
      return Math.floor((nowSecs - obj.last_login) / 60);
    }
    return min;
  },
  function(obj, min) {
    // last_login is 'false' over 30 days
    if ('last_login' in obj && !obj.last_login) {return 99999;}
    return min;
  }
];

var getDot = [
  {condition: 2, result: greenDiamond},
  {condition: 5, result: yellowDiamond},
  {condition: 30, result: orangeDiamond},
  {condition: 10080, result: offlineDot},
  {condition: 44640, result: sevenDayDot}
];

export function onlineDot(obj) {
  var min = getMins.reduce(function(prev, curr) {
    return curr(obj, prev);
  }, 0);
  for (var i = 0; i < getDot.length; i += 1) {
    var el = getDot[i];
    if (min < el.condition) {return el.result;}
  }
  return redDot;
}

function changeOnlineDot(contactLink) {
  var lastActivity = lastActivityRE
    .exec(contactLink.dataset.tipped);
  contactLink.parentNode.previousSibling.innerHTML =
    onlineDot({
      min: lastActivity[3],
      hour: lastActivity[2],
      day: lastActivity[1]
    });
}

function batchDots() {
  var limit = performance.now() + 5;
  while (moreToDo(limit, dotCount, dotList)) {
    changeOnlineDot(dotList[dotCount]);
    dotCount += 1;
  }
  if (dotCount < dotList.length) {
    add(3, batchDots);
  }
}

export function colouredDots() {
  if (!getValue('enhanceOnlineDots')) {return;}
  dotList = document.querySelectorAll(
    '#pCC a[data-tipped*="Last Activity"]');
  dotCount = 0;
  add(3, batchDots);
}
<|endoftext|>


<commit_before>'use strict'

const hashring = require('swim-hashring')
const EE = require('events').EventEmitter
const inherits = require('util').inherits
const net = require('net')
const tentacoli = require('tentacoli')
const pump = require('pump')
const dezalgo = require('fastzalgo')
const networkAddress = require('network-address')
const bloomrun = require('bloomrun')
const pino = require('pino')
const tinysonic = require('tinysonic')
const tracker = require('./lib/tracker')
const replicator = require('./lib/replicator')
const serializers = require('./lib/serializers')
const monitoring = require('./lib/monitoring')

function UpRing (opts) {
  if (!(this instanceof UpRing)) {
    return new UpRing(opts)
  }

  opts = opts || {}
  opts.port = opts.port || 0
  opts.host = opts.host || networkAddress()

  const hashringOpts = opts.hashring || {}
  hashringOpts.base = hashringOpts.base || opts.base
  hashringOpts.name = hashringOpts.name || opts.name
  hashringOpts.client = hashringOpts.client || opts.client
  hashringOpts.host = opts.host

  this._inbound = new Set()
  this.logger = opts.logger ? opts.logger.child({ serializers }) : pino({ serializers })
  this.info = {}
  this.ready = false

  if (!opts.logger) {
    this.logger.level = opts.logLevel || 'info'
  }

  this._dispatch = (req, reply) => {
    if (!this.ready) {
      this.on('up', this._dispatch.bind(this, req, reply))
      return
    }

    var func
    this.emit('prerequest', req)
    if (this._router) {
      func = this._router.lookup(req)
      if (func) {
        var result = func(req, reply)
        if (result && typeof result.then === 'function') {
          result
            .then(res => process.nextTick(reply, null, res))
            .catch(err => process.nextTick(reply, err, null))
        }
      } else {
        reply(new Error('message does not match any pattern'))
      }
    } else {
      this.emit('request', req, reply)
    }
  }

  this._server = net.createServer((stream) => {
    if (this.closed) {
      stream.on('error', noop)
      stream.destroy()
      return
    }

    this.logger.debug({ address: stream.address() }, 'incoming connection')

    const instance = tentacoli()
    this._inbound.add(instance)
    pump(stream, instance, stream, () => {
      this.logger.debug({ address: stream.address() }, 'closed connection')
      this._inbound.delete(instance)
    })
    instance.on('request', this._dispatch)
  })
  this._server.listen(opts.port, opts.host, () => {
    this.logger.debug({ address: this._server.address() }, 'listening')
    const local = hashringOpts.local = hashringOpts.local || {}
    const meta = local.meta = local.meta || {}
    meta.upring = {
      address: opts.host,
      port: this._server.address().port
    }
    this._hashring = hashring(hashringOpts)
    this._tracker = tracker(this._hashring)
    this._replicator = replicator(this._hashring)
    this.track = this._tracker.track
    this.replica = this._replicator.replica

    // needed because of request retrials
    this._hashring.setMaxListeners(0)
    this._hashring.on('up', () => {
      this.ready = true
      this.logger = this.logger.child({ id: this.whoami() })
      this.logger.info({ address: this._server.address() }, 'node up')
      this.emit('up')
    })

    this._hashring.on('move', this._tracker.check)
    this._hashring.on('move', (info) => {
      this.logger.trace(info, 'move')
      this.emit('move', info)
    })
    this._hashring.on('steal', this._replicator.check)
    this._hashring.on('steal', (info) => {
      this.logger.trace(info, 'steal')
      this.emit('steal', info)
    })
    this._hashring.on('error', this.emit.bind(this, 'error'))
    this._hashring.on('peerUp', this.emit.bind(this, 'peerUp'))
    this._hashring.on('peerDown', this.emit.bind(this, 'peerDown'))
  })

  this._server.on('error', this.emit.bind(this, 'error'))

  this._peers = {}
}

inherits(UpRing, EE)

UpRing.prototype.whoami = function () {
  return this._hashring.whoami()
}

UpRing.prototype.join = function (peers, cb) {
  if (!Array.isArray(peers)) {
    peers = [peers]
  }
  return this._hashring.swim.join(peers, cb)
}

UpRing.prototype.allocatedToMe = function (key) {
  return this._hashring.allocatedToMe(key)
}

UpRing.prototype.peerConn = function (peer) {
  let conn = this._peers[peer.id]

  if (!conn) {
    this.logger.debug({ peer: peer }, 'connecting to peer')
    const upring = peer.meta.upring
    const stream = net.connect(upring.port, upring.address)
    conn = setupConn(this, peer, stream)
  }

  return conn
}

function setupConn (that, peer, stream, retry) {
  const conn = tentacoli()

  pump(stream, conn, stream, function () {
    that.logger.debug({ peer: peer }, 'peer disconnected')
    var nustream = null
    that._hashring.on('peerDown', onPeerDown)

    if (!retry) {
      that.logger.debug({ peer: peer }, 'reconnecting to peer')
      nustream = net.connect(peer.meta.upring.port, peer.meta.upring.address)
      nustream.on('connect', onConnect)
      nustream.on('error', onError)
    }

    function onPeerDown (peerDown) {
      if (peerDown.id === peer.id) {
        that.logger.debug({ peer: peer }, 'peer down')
        delete that._peers[peer.id]
        that._hashring.removeListener('peerDown', onPeerDown)
        if (nustream) {
          nustream.destroy()
        }
      }
    }

    function onConnect () {
      that.logger.debug({ peer: peer }, 'reconnected')
      setupConn(that, peer, nustream, true)
      that._hashring.removeListener('peerDown', onPeerDown)
    }

    function onError () {
      // do nothing, let's wait for peerDown
      // TODO that might never come, how to signal the hashring?
    }
  })

  setTimeout(function () {
    retry = true
  }, 10 * 1000).unref() // 10 seconds

  that._peers[peer.id] = conn
  return conn
}

UpRing.prototype.peers = function (myself) {
  return this._hashring.peers(myself)
}

UpRing.prototype.mymeta = function () {
  return this._hashring.mymeta()
}

UpRing.prototype.request = function (obj, callback, _count) {
  if (this._hashring.allocatedToMe(obj.key)) {
    this.logger.trace({ msg: obj }, 'local call')
    this._dispatch(obj, dezalgo(callback))
  } else {
    let peer = this._hashring.lookup(obj.key)
    this.logger.trace({ msg: obj, peer }, 'remote call')

    let upring = peer.meta.upring
    if (!upring || !upring.address || !upring.port) {
      callback(new Error('peer has invalid upring metadata'))
      return
    }

    // TODO simplify all this logic
    // and avoid allocating a closure
    if (typeof _count !== 'number') {
      _count = 0
    } else if (_count === 3) {
      callback(new Error('retried three times'))
      return
    } else {
      _count++
    }

    const conn = this.peerConn(peer)

    if (conn.destroyed) {
      // TODO make this dependent on the gossip interval
      setTimeout(retry, 500, this, obj, callback, _count)
      return
    }

    conn.request(obj, (err, result) => {
      if (err) {
        // the peer has changed
        if (this._hashring.lookup(obj.key).id !== peer.id || conn.destroyed) {
          // TODO make this dependent on the gossip interval
          setTimeout(retry, 500, this, obj, callback, _count)
          return
        }
      }
      callback(err, result)
    })
  }

  return this
}

function retry (that, obj, callback, _count) {
  that.request(obj, callback, _count)
}

UpRing.prototype.add = function (pattern, func) {
  if (!this._router) {
    this._router = bloomrun()
    monitoring(this)
  }

  if (typeof pattern === 'string') {
    let sonic = tinysonic(pattern)

    if (sonic) {
      pattern = sonic
    } else {
      pattern = { cmd: pattern }
    }
  }

  this._router.add(pattern, func)
}

UpRing.prototype.close = function (cb) {
  cb = cb || noop

  if (this.closed) {
    return cb()
  }

  if (this._tracker) {
    this._tracker.clear()
  }

  this.closed = true

  Object.keys(this._peers).forEach((id) => {
    this._peers[id].destroy()
  })

  this._inbound.forEach((s) => {
    s.destroy()
  })
  this._hashring.close()
  this._server.close((err) => {
    this.logger.info('closed')
    this.emit('close')
    cb(err)
  })

  return this
}

function noop () {}

module.exports = UpRing
<commit_msg>Promisified request<commit_after>'use strict'

const hashring = require('swim-hashring')
const EE = require('events').EventEmitter
const inherits = require('util').inherits
const net = require('net')
const tentacoli = require('tentacoli')
const pump = require('pump')
const dezalgo = require('fastzalgo')
const networkAddress = require('network-address')
const bloomrun = require('bloomrun')
const pino = require('pino')
const tinysonic = require('tinysonic')
const promisify = require('util.promisify')
const tracker = require('./lib/tracker')
const replicator = require('./lib/replicator')
const serializers = require('./lib/serializers')
const monitoring = require('./lib/monitoring')

function UpRing (opts) {
  if (!(this instanceof UpRing)) {
    return new UpRing(opts)
  }

  opts = opts || {}
  opts.port = opts.port || 0
  opts.host = opts.host || networkAddress()

  const hashringOpts = opts.hashring || {}
  hashringOpts.base = hashringOpts.base || opts.base
  hashringOpts.name = hashringOpts.name || opts.name
  hashringOpts.client = hashringOpts.client || opts.client
  hashringOpts.host = opts.host

  this._inbound = new Set()
  this.logger = opts.logger ? opts.logger.child({ serializers }) : pino({ serializers })
  this.info = {}
  this.ready = false

  if (!opts.logger) {
    this.logger.level = opts.logLevel || 'info'
  }

  this._dispatch = (req, reply) => {
    if (!this.ready) {
      this.on('up', this._dispatch.bind(this, req, reply))
      return
    }

    var func
    this.emit('prerequest', req)
    if (this._router) {
      func = this._router.lookup(req)
      if (func) {
        var result = func(req, reply)
        if (result && typeof result.then === 'function') {
          result
            .then(res => process.nextTick(reply, null, res))
            .catch(err => process.nextTick(reply, err, null))
        }
      } else {
        reply(new Error('message does not match any pattern'))
      }
    } else {
      this.emit('request', req, reply)
    }
  }

  this._server = net.createServer((stream) => {
    if (this.closed) {
      stream.on('error', noop)
      stream.destroy()
      return
    }

    this.logger.debug({ address: stream.address() }, 'incoming connection')

    const instance = tentacoli()
    this._inbound.add(instance)
    pump(stream, instance, stream, () => {
      this.logger.debug({ address: stream.address() }, 'closed connection')
      this._inbound.delete(instance)
    })
    instance.on('request', this._dispatch)
  })
  this._server.listen(opts.port, opts.host, () => {
    this.logger.debug({ address: this._server.address() }, 'listening')
    const local = hashringOpts.local = hashringOpts.local || {}
    const meta = local.meta = local.meta || {}
    meta.upring = {
      address: opts.host,
      port: this._server.address().port
    }
    this._hashring = hashring(hashringOpts)
    this._tracker = tracker(this._hashring)
    this._replicator = replicator(this._hashring)
    this.track = this._tracker.track
    this.replica = this._replicator.replica

    // needed because of request retrials
    this._hashring.setMaxListeners(0)
    this._hashring.on('up', () => {
      this.ready = true
      this.logger = this.logger.child({ id: this.whoami() })
      this.logger.info({ address: this._server.address() }, 'node up')
      this.emit('up')
    })

    this._hashring.on('move', this._tracker.check)
    this._hashring.on('move', (info) => {
      this.logger.trace(info, 'move')
      this.emit('move', info)
    })
    this._hashring.on('steal', this._replicator.check)
    this._hashring.on('steal', (info) => {
      this.logger.trace(info, 'steal')
      this.emit('steal', info)
    })
    this._hashring.on('error', this.emit.bind(this, 'error'))
    this._hashring.on('peerUp', this.emit.bind(this, 'peerUp'))
    this._hashring.on('peerDown', this.emit.bind(this, 'peerDown'))
  })

  this._server.on('error', this.emit.bind(this, 'error'))

  this._peers = {}
}

inherits(UpRing, EE)

UpRing.prototype.whoami = function () {
  return this._hashring.whoami()
}

UpRing.prototype.join = function (peers, cb) {
  if (!Array.isArray(peers)) {
    peers = [peers]
  }
  return this._hashring.swim.join(peers, cb)
}

UpRing.prototype.allocatedToMe = function (key) {
  return this._hashring.allocatedToMe(key)
}

UpRing.prototype.peerConn = function (peer) {
  let conn = this._peers[peer.id]

  if (!conn) {
    this.logger.debug({ peer: peer }, 'connecting to peer')
    const upring = peer.meta.upring
    const stream = net.connect(upring.port, upring.address)
    conn = setupConn(this, peer, stream)
  }

  return conn
}

function setupConn (that, peer, stream, retry) {
  const conn = tentacoli()

  pump(stream, conn, stream, function () {
    that.logger.debug({ peer: peer }, 'peer disconnected')
    var nustream = null
    that._hashring.on('peerDown', onPeerDown)

    if (!retry) {
      that.logger.debug({ peer: peer }, 'reconnecting to peer')
      nustream = net.connect(peer.meta.upring.port, peer.meta.upring.address)
      nustream.on('connect', onConnect)
      nustream.on('error', onError)
    }

    function onPeerDown (peerDown) {
      if (peerDown.id === peer.id) {
        that.logger.debug({ peer: peer }, 'peer down')
        delete that._peers[peer.id]
        that._hashring.removeListener('peerDown', onPeerDown)
        if (nustream) {
          nustream.destroy()
        }
      }
    }

    function onConnect () {
      that.logger.debug({ peer: peer }, 'reconnected')
      setupConn(that, peer, nustream, true)
      that._hashring.removeListener('peerDown', onPeerDown)
    }

    function onError () {
      // do nothing, let's wait for peerDown
      // TODO that might never come, how to signal the hashring?
    }
  })

  setTimeout(function () {
    retry = true
  }, 10 * 1000).unref() // 10 seconds

  that._peers[peer.id] = conn
  return conn
}

UpRing.prototype.peers = function (myself) {
  return this._hashring.peers(myself)
}

UpRing.prototype.mymeta = function () {
  return this._hashring.mymeta()
}

UpRing.prototype.request = promisify(function (obj, callback, _count) {
  if (this._hashring.allocatedToMe(obj.key)) {
    this.logger.trace({ msg: obj }, 'local call')
    this._dispatch(obj, dezalgo(callback))
  } else {
    let peer = this._hashring.lookup(obj.key)
    this.logger.trace({ msg: obj, peer }, 'remote call')

    let upring = peer.meta.upring
    if (!upring || !upring.address || !upring.port) {
      callback(new Error('peer has invalid upring metadata'))
      return
    }

    // TODO simplify all this logic
    // and avoid allocating a closure
    if (typeof _count !== 'number') {
      _count = 0
    } else if (_count === 3) {
      callback(new Error('retried three times'))
      return
    } else {
      _count++
    }

    const conn = this.peerConn(peer)

    if (conn.destroyed) {
      // TODO make this dependent on the gossip interval
      setTimeout(retry, 500, this, obj, callback, _count)
      return
    }

    conn.request(obj, (err, result) => {
      if (err) {
        // the peer has changed
        if (this._hashring.lookup(obj.key).id !== peer.id || conn.destroyed) {
          // TODO make this dependent on the gossip interval
          setTimeout(retry, 500, this, obj, callback, _count)
          return
        }
      }
      callback(err, result)
    })
  }

  return this
})

function retry (that, obj, callback, _count) {
  that.request(obj, callback, _count)
}

UpRing.prototype.add = function (pattern, func) {
  if (!this._router) {
    this._router = bloomrun()
    monitoring(this)
  }

  if (typeof pattern === 'string') {
    let sonic = tinysonic(pattern)

    if (sonic) {
      pattern = sonic
    } else {
      pattern = { cmd: pattern }
    }
  }

  this._router.add(pattern, func)
}

UpRing.prototype.close = function (cb) {
  cb = cb || noop

  if (this.closed) {
    return cb()
  }

  if (this._tracker) {
    this._tracker.clear()
  }

  this.closed = true

  Object.keys(this._peers).forEach((id) => {
    this._peers[id].destroy()
  })

  this._inbound.forEach((s) => {
    s.destroy()
  })
  this._hashring.close()
  this._server.close((err) => {
    this.logger.info('closed')
    this.emit('close')
    cb(err)
  })

  return this
}

function noop () {}

module.exports = UpRing
<|endoftext|>


<commit_before>angular.module('anol.draw')
/**
 * @ngdoc directive
 * @name anol.draw.anolDraw
 *
 * @requires $compile
 * @requires $rootScope
 * @requires $translate
 * @requires anol.map.MapService
 * @requires anol.map.ControlsSerivce
 * @requries anol.map.DrawService
 *
 * @param {string} pointTooltipPlacement Position of point tooltip
 * @param {string} lineTooltipPlacement Position of line tooltip
 * @param {string} polygonTooltipPlacement Position of polygon tooltip
 * @param {number} tooltipDelay Time in milisecounds to wait before display tooltip
 * @param {boolean} tooltipEnable Enable tooltips. Default true for non-touch screens, default false for touchscreens
 * @param {string} templateUrl Url to template to use instead of default one
 *
 * @description
 * Provides controls to draw points, lines and polygons, modify and remove them
 */
.directive('anolDraw', ['$compile', '$rootScope', '$translate', 'ControlsService', 'MapService', 'DrawService',
    function($compile, $rootScope, $translate, ControlsService, MapService, DrawService) {
    return {
        restrict: 'A',
        require: ['?^anolFeaturePropertiesEditor', '?^anolFeatureStyleEditor'],
        scope: {
            tooltipDelay: '@',
            tooltipEnable: '@',
            pointTooltipPlacement: '@',
            lineTooltipPlacement: '@',
            polygonTooltipPlacement: '@',

        },
        templateUrl: function(tElement, tAttrs) {
            var defaultUrl = 'src/modules/draw/templates/draw.html';
            return tAttrs.templateUrl || defaultUrl;
        },
        link: function(scope, element, attrs, controllers) {
            var AnolFeaturePropertiesEditor = controllers[0];
            var AnolFeatureStyleEditor = controllers[1];

            // attribute defaults
            scope.tooltipEnable = angular.isDefined(scope.tooltipEnable) ?
                scope.tooltipEnable : !ol.has.TOUCH;
            scope.tooltipDelay = angular.isDefined(scope.tooltipDelay) ?
                scope.tooltipDelay : 500;
            scope.pointTooltipPlacement = angular.isDefined(scope.pointTooltipPlacement) ?
                scope.pointTooltipPlacement : 'right';
            scope.lineTooltipPlacement = angular.isDefined(scope.lineTooltipPlacement) ?
                scope.lineTooltipPlacement : 'right';
            scope.polygonTooltipPlacement = angular.isDefined(scope.polygonTooltipPlacement) ?
                scope.polygonTooltipPlacement : 'right';

            var activeLayer;
            var selectedFeature;
            var drawPointControl, drawLineControl, drawPolygonControl, modifyControl;

            // disabled by default. Will be enabled, when feature selected
            var removeButtonElement = element.find('.draw-remove');
            removeButtonElement.addClass('disabled');

            var createDrawInteractions = function(drawType, source) {
                // create draw interaction
                var draw = new ol.interaction.Draw({
                    source: source,
                    type: drawType
                });

                // TODO how to use both?
                if(angular.isObject(AnolFeaturePropertiesEditor)) {
                    draw.on('drawend', function(evt) {
                        var feature = evt.feature;
                        AnolFeaturePropertiesEditor.editFeature(feature);
                    });
                } else if (angular.isObject(AnolFeatureStyleEditor)) {
                    draw.on('drawend', function(evt) {
                        var feature = evt.feature;
                        AnolFeatureStyleEditor.editFeature(feature);
                    });
                }
                return [draw];
            };

            var createModifyInteractions = function(layer) {
                var selectInteraction = new ol.interaction.Select({
                    toggleCondition: ol.events.condition.never,
                    layers: [layer]
                });
                selectInteraction.on('select', function(evt) {
                    if(evt.selected.length === 0) {
                        selectedFeature = undefined;
                        removeButtonElement.addClass('disabled');
                    } else {
                        selectedFeature = evt.selected[0];
                        removeButtonElement.removeClass('disabled');
                    }
                });
                var modifyInteraction = new ol.interaction.Modify({
                    features: selectInteraction.getFeatures()
                });
                var snapInteraction = new ol.interaction.Snap({
                    source: layer.getSource()
                });
                return [selectInteraction, modifyInteraction, snapInteraction];
            };

            var createDrawControl = function(controlElement, controlTarget) {
                var drawControl = new anol.control.Control({
                    element: controlElement,
                    target: controlTarget,
                    exclusive: true,
                    disabled: true
                });
                drawControl.onDeactivate(deactivate, scope);
                drawControl.onActivate(activate, scope);
                return drawControl;
            };

            var createModifyControl = function(controlElement, controlTarget) {
                var _modifyControl = new anol.control.Control({
                    element: controlElement,
                    target: controlTarget,
                    exclusive: true,
                    disabled: true
                });
                _modifyControl.onDeactivate(deactivate);
                _modifyControl.onActivate(activate);
                _modifyControl.onDeactivate(function() {
                    selectedFeature = undefined;
                });
                return _modifyControl;
            };

            var deactivate = function(targetControl) {
                angular.forEach(targetControl.interactions, function(interaction) {
                    interaction.setActive(false);
                });
            };

            var activate = function(targetControl) {
                angular.forEach(targetControl.interactions, function(interaction) {
                    interaction.setActive(true);
                });
            };

            // Button binds
            scope.drawPoint = function() {
                if(drawPointControl.disabled === true) {
                    return;
                }
                if(drawPointControl.active) {
                    drawPointControl.deactivate();
                } else {
                    drawPointControl.activate();
                }
            };

            scope.drawLine = function() {
                if(drawLineControl.disabled === true) {
                    return;
                }
                if(drawLineControl.active) {
                    drawLineControl.deactivate();
                } else {
                    drawLineControl.activate();
                }
            };

            scope.drawPolygon = function() {
                if(drawPolygonControl.disabled === true) {
                    return;
                }
                if(drawPolygonControl.active) {
                    drawPolygonControl.deactivate();
                } else {
                    drawPolygonControl.activate();
                }
            };

            scope.modify = function() {
                if(modifyControl.disabled === true) {
                    return;
                }
                if(modifyControl.active) {
                    modifyControl.deactivate();
                } else {
                    modifyControl.activate();
                }
            };

            scope.remove = function() {
                if(selectedFeature !== undefined) {
                    activeLayer.olLayer.getSource().removeFeature(selectedFeature);
                    modifyControl.interactions[0].getFeatures().clear();
                    selectedFeature = undefined;
                    removeButtonElement.addClass('disabled');
                }
            };

            scope.map = MapService.getMap();

            element.addClass('ol-control');
            element.addClass('anol-draw');

            var drawControl = new anol.control.Control({
                element: element
            });

            drawPointControl = createDrawControl(
                element.find('.draw-point'),
                element
            );

            drawLineControl = createDrawControl(
                element.find('.draw-line'),
                element
            );

            drawPolygonControl = createDrawControl(
                element.find('.draw-polygon'),
                element
            );

            modifyControl = createModifyControl(
                element.find('.draw-modify'),
                element
            );

            ControlsService.addControls([
                drawControl, drawPointControl, drawLineControl,
                drawPolygonControl, modifyControl
            ]);

            var allInteractions = function() {
                return drawPointControl.interactions
                    .concat(drawLineControl.interactions)
                    .concat(drawPolygonControl.interactions)
                    .concat(modifyControl.interactions);
            };

            var bindActiveLayer = function(layer) {
                drawPointControl.interactions = createDrawInteractions('Point', layer.olLayer.getSource());
                drawPointControl.enable();
                drawLineControl.interactions = createDrawInteractions('LineString', layer.olLayer.getSource());
                drawLineControl.enable();
                drawPolygonControl.interactions = createDrawInteractions('Polygon', layer.olLayer.getSource());
                drawPolygonControl.enable();
                modifyControl.interactions = createModifyInteractions(layer.olLayer);
                modifyControl.enable();

                angular.forEach(allInteractions(), function(interaction) {
                    interaction.setActive(false);
                    scope.map.addInteraction(interaction);
                });

                activeLayer = layer;
            };

            var unbindActiveLayer = function() {
                angular.forEach(allInteractions(), function(interaction) {
                    interaction.setActive(false);
                    scope.map.removeInteraction(interaction);
                });

                drawPointControl.interactions = [];
                drawPointControl.disable();
                drawLineControl.interactions = [];
                drawLineControl.disable();
                drawPolygonControl.interactions = [];
                drawPolygonControl.disable();
                modifyControl.interactions = [];
                modifyControl.disable();

                activeLayer = undefined;
            };

            scope.$watch(function() {
                return DrawService.activeLayer;
            }, function(newActiveLayer, oldActiveLayer) {
                if(newActiveLayer === activeLayer) {
                    return;
                }
                if(oldActiveLayer !== undefined) {
                    unbindActiveLayer();
                }
                if(newActiveLayer !== undefined) {
                    bindActiveLayer(newActiveLayer);
                }
            });
        }
    };
}]);
<commit_msg>Deactivate draw controls after feature drawn by default<commit_after>angular.module('anol.draw')
/**
 * @ngdoc directive
 * @name anol.draw.anolDraw
 *
 * @requires $compile
 * @requires $rootScope
 * @requires $translate
 * @requires anol.map.MapService
 * @requires anol.map.ControlsSerivce
 * @requries anol.map.DrawService
 *
 * @param {boolean} continueDrawing Don't deactivate drawing after feature is added
 * @param {string} pointTooltipPlacement Position of point tooltip
 * @param {string} lineTooltipPlacement Position of line tooltip
 * @param {string} polygonTooltipPlacement Position of polygon tooltip
 * @param {number} tooltipDelay Time in milisecounds to wait before display tooltip
 * @param {boolean} tooltipEnable Enable tooltips. Default true for non-touch screens, default false for touchscreens
 * @param {string} templateUrl Url to template to use instead of default one
 *
 * @description
 * Provides controls to draw points, lines and polygons, modify and remove them
 */
.directive('anolDraw', ['$compile', '$rootScope', '$translate', '$timeout', 'ControlsService', 'MapService', 'DrawService',
    function($compile, $rootScope, $translate, $timeout, ControlsService, MapService, DrawService) {
    return {
        restrict: 'A',
        require: ['?^anolFeaturePropertiesEditor', '?^anolFeatureStyleEditor'],
        scope: {
            continueDrawing: '@',
            tooltipDelay: '@',
            tooltipEnable: '@',
            pointTooltipPlacement: '@',
            lineTooltipPlacement: '@',
            polygonTooltipPlacement: '@'
        },
        templateUrl: function(tElement, tAttrs) {
            var defaultUrl = 'src/modules/draw/templates/draw.html';
            return tAttrs.templateUrl || defaultUrl;
        },
        link: function(scope, element, attrs, controllers) {
            var AnolFeaturePropertiesEditor = controllers[0];
            var AnolFeatureStyleEditor = controllers[1];

            // attribute defaults
            scope.continueDrawing = angular.isDefined(scope.continueDrawing) ?
                scope.continueDrawing : false;
            scope.tooltipEnable = angular.isDefined(scope.tooltipEnable) ?
                scope.tooltipEnable : !ol.has.TOUCH;
            scope.tooltipDelay = angular.isDefined(scope.tooltipDelay) ?
                scope.tooltipDelay : 500;
            scope.pointTooltipPlacement = angular.isDefined(scope.pointTooltipPlacement) ?
                scope.pointTooltipPlacement : 'right';
            scope.lineTooltipPlacement = angular.isDefined(scope.lineTooltipPlacement) ?
                scope.lineTooltipPlacement : 'right';
            scope.polygonTooltipPlacement = angular.isDefined(scope.polygonTooltipPlacement) ?
                scope.polygonTooltipPlacement : 'right';

            var activeLayer;
            var selectedFeature;
            var drawPointControl, drawLineControl, drawPolygonControl, modifyControl;

            // disabled by default. Will be enabled, when feature selected
            var removeButtonElement = element.find('.draw-remove');
            removeButtonElement.addClass('disabled');

            var createDrawInteractions = function(drawType, source, control) {
                // create draw interaction
                var draw = new ol.interaction.Draw({
                    source: source,
                    type: drawType
                });

                if(scope.continueDrawing === false) {
                    draw.on('drawend', function() {
                        // TODO remove when https://github.com/openlayers/ol3/issues/3610/ resolved
                        $timeout(function() { control.deactivate(); }, 275);
                    });
                }

                // TODO how to use both?
                // TODO check if we can remove this!
                if(angular.isObject(AnolFeaturePropertiesEditor)) {
                    draw.on('drawend', function(evt) {
                        var feature = evt.feature;
                        AnolFeaturePropertiesEditor.editFeature(feature);
                    });
                } else if (angular.isObject(AnolFeatureStyleEditor)) {
                    draw.on('drawend', function(evt) {
                        var feature = evt.feature;
                        AnolFeatureStyleEditor.editFeature(feature);
                    });
                }
                return [draw];
            };

            var createModifyInteractions = function(layer) {
                var selectInteraction = new ol.interaction.Select({
                    toggleCondition: ol.events.condition.never,
                    layers: [layer]
                });
                selectInteraction.on('select', function(evt) {
                    if(evt.selected.length === 0) {
                        selectedFeature = undefined;
                        removeButtonElement.addClass('disabled');
                    } else {
                        selectedFeature = evt.selected[0];
                        removeButtonElement.removeClass('disabled');
                    }
                });
                var modifyInteraction = new ol.interaction.Modify({
                    features: selectInteraction.getFeatures()
                });
                var snapInteraction = new ol.interaction.Snap({
                    source: layer.getSource()
                });
                return [selectInteraction, modifyInteraction, snapInteraction];
            };

            var createDrawControl = function(controlElement, controlTarget) {
                var drawControl = new anol.control.Control({
                    element: controlElement,
                    target: controlTarget,
                    exclusive: true,
                    disabled: true
                });
                drawControl.onDeactivate(deactivate, scope);
                drawControl.onActivate(activate, scope);
                return drawControl;
            };

            var createModifyControl = function(controlElement, controlTarget) {
                var _modifyControl = new anol.control.Control({
                    element: controlElement,
                    target: controlTarget,
                    exclusive: true,
                    disabled: true
                });
                _modifyControl.onDeactivate(deactivate);
                _modifyControl.onActivate(activate);
                _modifyControl.onDeactivate(function() {
                    selectedFeature = undefined;
                });
                return _modifyControl;
            };

            var deactivate = function(targetControl) {
                angular.forEach(targetControl.interactions, function(interaction) {
                    interaction.setActive(false);
                });
            };

            var activate = function(targetControl) {
                angular.forEach(targetControl.interactions, function(interaction) {
                    interaction.setActive(true);
                });
            };

            // Button binds
            scope.drawPoint = function() {
                if(drawPointControl.disabled === true) {
                    return;
                }
                if(drawPointControl.active) {
                    drawPointControl.deactivate();
                } else {
                    drawPointControl.activate();
                }
            };

            scope.drawLine = function() {
                if(drawLineControl.disabled === true) {
                    return;
                }
                if(drawLineControl.active) {
                    drawLineControl.deactivate();
                } else {
                    drawLineControl.activate();
                }
            };

            scope.drawPolygon = function() {
                if(drawPolygonControl.disabled === true) {
                    return;
                }
                if(drawPolygonControl.active) {
                    drawPolygonControl.deactivate();
                } else {
                    drawPolygonControl.activate();
                }
            };

            scope.modify = function() {
                if(modifyControl.disabled === true) {
                    return;
                }
                if(modifyControl.active) {
                    modifyControl.deactivate();
                } else {
                    modifyControl.activate();
                }
            };

            scope.remove = function() {
                if(selectedFeature !== undefined) {
                    activeLayer.olLayer.getSource().removeFeature(selectedFeature);
                    modifyControl.interactions[0].getFeatures().clear();
                    selectedFeature = undefined;
                    removeButtonElement.addClass('disabled');
                }
            };

            scope.map = MapService.getMap();

            element.addClass('ol-control');
            element.addClass('anol-draw');

            var drawControl = new anol.control.Control({
                element: element
            });

            drawPointControl = createDrawControl(
                element.find('.draw-point'),
                element
            );

            drawLineControl = createDrawControl(
                element.find('.draw-line'),
                element
            );

            drawPolygonControl = createDrawControl(
                element.find('.draw-polygon'),
                element
            );

            modifyControl = createModifyControl(
                element.find('.draw-modify'),
                element
            );

            ControlsService.addControls([
                drawControl, drawPointControl, drawLineControl,
                drawPolygonControl, modifyControl
            ]);

            var allInteractions = function() {
                return drawPointControl.interactions
                    .concat(drawLineControl.interactions)
                    .concat(drawPolygonControl.interactions)
                    .concat(modifyControl.interactions);
            };

            var bindActiveLayer = function(layer) {
                drawPointControl.interactions = createDrawInteractions('Point', layer.olLayer.getSource(), drawPointControl);
                drawPointControl.enable();
                drawLineControl.interactions = createDrawInteractions('LineString', layer.olLayer.getSource(), drawLineControl);
                drawLineControl.enable();
                drawPolygonControl.interactions = createDrawInteractions('Polygon', layer.olLayer.getSource(), drawPolygonControl);
                drawPolygonControl.enable();
                modifyControl.interactions = createModifyInteractions(layer.olLayer);
                modifyControl.enable();

                angular.forEach(allInteractions(), function(interaction) {
                    interaction.setActive(false);
                    scope.map.addInteraction(interaction);
                });

                activeLayer = layer;
            };

            var unbindActiveLayer = function() {
                angular.forEach(allInteractions(), function(interaction) {
                    interaction.setActive(false);
                    scope.map.removeInteraction(interaction);
                });

                drawPointControl.interactions = [];
                drawPointControl.disable();
                drawLineControl.interactions = [];
                drawLineControl.disable();
                drawPolygonControl.interactions = [];
                drawPolygonControl.disable();
                modifyControl.interactions = [];
                modifyControl.disable();

                activeLayer = undefined;
            };

            scope.$watch(function() {
                return DrawService.activeLayer;
            }, function(newActiveLayer, oldActiveLayer) {
                if(newActiveLayer === activeLayer) {
                    return;
                }
                if(oldActiveLayer !== undefined) {
                    unbindActiveLayer();
                }
                if(newActiveLayer !== undefined) {
                    bindActiveLayer(newActiveLayer);
                }
            });
        }
    };
}]);
<|endoftext|>


<commit_before>'use strict';

// Load the module dependencies
var config = require('./config'),
	path = require('path'),
	http = require('http'),
	socketio = require('socket.io');

// Define the Socket.io configuration method
module.exports = function (app, db) {
	var server = http.createServer(app);

	var io = socketio(config.socketPort, { transports: ['websocket', 'polling'] });
	var redis = require('socket.io-redis');
	io.adapter(redis( process.env.REDIS_URL || { host: process.env.REDIS_DB_PORT_6379_TCP_ADDR || '127.0.0.1' , port: process.env.REDIS_DB_PORT_6379_TCP_PORT || 6379 }));

	// Add an event listener to the 'connection' event
	io.on('connection', function (socket) {
		config.getGlobbedFiles('./app/sockets/**.js').forEach(function (socketConfiguration) {
			require(path.resolve(socketConfiguration))(io, socket);
		});
	});

	return server;
};
<commit_msg>Disabled redis if it is a heroku installation<commit_after>'use strict';

// Load the module dependencies
var config = require('./config'),
	path = require('path'),
	http = require('http'),
	socketio = require('socket.io');

// Define the Socket.io configuration method
module.exports = function (app, db) {
	var server = http.createServer(app);

	var io = socketio(config.socketPort, { transports: ['websocket', 'polling'] });
	if(process.env.IS_HEROKU_INSTALLATION == "TRUE"){
		var redis = require('socket.io-redis');
		io.adapter(redis( process.env.REDIS_URL || { host: process.env.REDIS_DB_PORT_6379_TCP_ADDR || '127.0.0.1' , port: process.env.REDIS_DB_PORT_6379_TCP_PORT || 6379 }));
	}
	// Add an event listener to the 'connection' event
	io.on('connection', function (socket) {
		config.getGlobbedFiles('./app/sockets/**.js').forEach(function (socketConfiguration) {
			require(path.resolve(socketConfiguration))(io, socket);
		});
	});

	return server;
};
<|endoftext|>


<commit_before>import React from 'react'
import Results from './results.js'
import { SORT_RELEVANCE, SORT_OLDEST, SORT_NEWEST } from './searchpage.js'

class Search extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      searching: false,
      error: null,
      results: null,
      query: null
    }
  }

  buildQuery(q) {
    return {
      query_string: {
        query: q,
        default_operator: 'AND',
      },
    }
  }

  buildSortQuery(order) {
    var sort = ['_score']
    switch (order) {
      case SORT_NEWEST:
        sort = [{"date": {"order": "desc"}}, ... sort]
        break
      case SORT_OLDEST:
        sort = [{"date": {"order": "asc"}}, ... sort]
        break
    }
    return sort
  }

  search(query, success, error) {
    $.ajax({
      url: '/search',
      method: 'POST',
      contentType: 'application/json',
      data: JSON.stringify({
        from: (query.page - 1) * query.size,
        size: query.size,
        query: this.buildQuery(query.q),
        sort: this.buildSortQuery(query.order),
        aggs: {
          count_by_filetype: {terms: {field: 'filetype'}},
        },
        collections: query.collections,
        fields: ['path', 'url', 'mime_type', 'attachments', 'filename', 'word-count'],
        highlight: {
          fields: {
            '*': {
              fragment_size: 150,
              number_of_fragments: 3,
              require_field_match: false,
            }
          }
        },
      }),
      success: success,
      error: error,
    })
  }

  onResults(resp) {
    this.setState({
      searching: false
    })

    var url = function (p) {
      var u = "?q=" + encodeURIComponent(this.state.query.q)
      if (p > 1) u += "&p=" + p
      if (this.state.query.size > 10) {
        u += "&size=" + encodeURIComponent(this.state.query.size)
      }
      if (this.state.query.order != SORT_RELEVANCE) {
        u += "&order=" + encodeURIComponent(this.state.query.order)
      }
      return u
    }.bind(this)

    var page_count = Math.ceil(resp.hits.total / this.state.query.size)
    var page = this.state.query.page
    var prev_url = page > 1 ? url(page - 1) : null
    var next_url = page < page_count ? url(page + 1) : null

    var results = {
      resp: resp,
      hits: resp.hits.hits,
      total: resp.hits.total,
      counts: resp.count_by_index,
      page: page,
      page_count: page_count,
      prev_url: prev_url,
      next_url: next_url,
    }

    this.setState({
      results: results,
      error: null
    })
  }

  onError(err) {
    console.error(err.responseText)
    this.setState({
      searching: false,
      results: null,
      error: "Server error while searching"
    })
  }

  performSearch(query) {
    if(!query) {
      return
    }
    if (this.state.query === query) {
      return
    }

    this.setState({
      searching: true,
      query: query
    })

    this.search(
      query,
      this.onResults.bind(this),
      this.onError.bind(this))
  }

  componentDidMount() {
    this.performSearch(this.props.query)
  }

  componentWillReceiveProps(props) {
    this.performSearch(props.query)
  }

  render() {
    var rv = null
    var results = this.state.results
    if (results) {
      rv = <Results
        resp={results.resp}
        hits={results.hits}
        total={results.total}
        counts={results.counts}
        page={results.page}
        pagesize={this.state.query.size}
        page_count={results.page_count}
        prev_url={results.prev_url}
        next_url={results.next_url}
        collections={this.props.collections}
        onSelect={this.props.onSelect}
      ></Results>
    }
    return (

        {rv}
        {this.state.searching ?
searching ...
 : null }
        {this.state.error ?
{ this.state.error }
 : null }

    )
  }
}

export default Search
<commit_msg>add collections selection to next/prev links<commit_after>import React from 'react'
import Results from './results.js'
import { SORT_RELEVANCE, SORT_OLDEST, SORT_NEWEST } from './searchpage.js'

class Search extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      searching: false,
      error: null,
      results: null,
      query: null
    }
  }

  buildQuery(q) {
    return {
      query_string: {
        query: q,
        default_operator: 'AND',
      },
    }
  }

  buildSortQuery(order) {
    var sort = ['_score']
    switch (order) {
      case SORT_NEWEST:
        sort = [{"date": {"order": "desc"}}, ... sort]
        break
      case SORT_OLDEST:
        sort = [{"date": {"order": "asc"}}, ... sort]
        break
    }
    return sort
  }

  search(query, success, error) {
    $.ajax({
      url: '/search',
      method: 'POST',
      contentType: 'application/json',
      data: JSON.stringify({
        from: (query.page - 1) * query.size,
        size: query.size,
        query: this.buildQuery(query.q),
        sort: this.buildSortQuery(query.order),
        aggs: {
          count_by_filetype: {terms: {field: 'filetype'}},
        },
        collections: query.collections,
        fields: ['path', 'url', 'mime_type', 'attachments', 'filename', 'word-count'],
        highlight: {
          fields: {
            '*': {
              fragment_size: 150,
              number_of_fragments: 3,
              require_field_match: false,
            }
          }
        },
      }),
      success: success,
      error: error,
    })
  }

  onResults(resp) {
    this.setState({
      searching: false
    })

    var url = function (p) {
      var u = "?q=" + encodeURIComponent(this.state.query.q)
      if (p > 1) u += "&p=" + p
      if (this.state.query.size > 10) {
        u += "&size=" + encodeURIComponent(this.state.query.size)
      }
      if (this.state.query.order != SORT_RELEVANCE) {
        u += "&order=" + encodeURIComponent(this.state.query.order)
      }
      if (this.props.collections) {
        u += "&collections=" + this.props.collections.map(encodeURIComponent).join('+');
      }
      return u
    }.bind(this)

    var page_count = Math.ceil(resp.hits.total / this.state.query.size)
    var page = this.state.query.page
    var prev_url = page > 1 ? url(page - 1) : null
    var next_url = page < page_count ? url(page + 1) : null

    var results = {
      resp: resp,
      hits: resp.hits.hits,
      total: resp.hits.total,
      counts: resp.count_by_index,
      page: page,
      page_count: page_count,
      prev_url: prev_url,
      next_url: next_url,
    }

    this.setState({
      results: results,
      error: null
    })
  }

  onError(err) {
    console.error(err.responseText)
    this.setState({
      searching: false,
      results: null,
      error: "Server error while searching"
    })
  }

  performSearch(query) {
    if(!query) {
      return
    }
    if (this.state.query === query) {
      return
    }

    this.setState({
      searching: true,
      query: query
    })

    this.search(
      query,
      this.onResults.bind(this),
      this.onError.bind(this))
  }

  componentDidMount() {
    this.performSearch(this.props.query)
  }

  componentWillReceiveProps(props) {
    this.performSearch(props.query)
  }

  render() {
    var rv = null
    var results = this.state.results
    if (results) {
      rv = <Results
        resp={results.resp}
        hits={results.hits}
        total={results.total}
        counts={results.counts}
        page={results.page}
        pagesize={this.state.query.size}
        page_count={results.page_count}
        prev_url={results.prev_url}
        next_url={results.next_url}
        collections={this.props.collections}
        onSelect={this.props.onSelect}
      ></Results>
    }
    return (

        {rv}
        {this.state.searching ?
searching ...
 : null }
        {this.state.error ?
{ this.state.error }
 : null }

    )
  }
}

export default Search
<|endoftext|>


<commit_before>var _ = require('lodash');

var productionConfig = require('../production/production');
var testConfig = {
  port: 8080,
  url: 'http://127.0.0.1:8080',
  logging: {
    console: false,
    file: false
  }
};

module.exports = _.merge({}, productionConfig, testConfig);
<commit_msg>Fixed test config file location to be in /tmp to fix travis<commit_after>var _ = require('lodash');

var productionConfig = require('../production/production');
var testConfig = {
  port: 8080,
  url: 'http://127.0.0.1:8080',
  temp: '/tmp/npmzor/temp',
  db: '/tmp/npmzor/db/' + (process.env.ENV || 'dev'),
  internal: {
    tgz: '/tmp/npmzor/internal'
  },
  cache: {
    // Timeout is in seconds
    tgz: '/tmp/npmzor/cache'
  },
  logging: {
    console: false,
    file: false
  }
};

module.exports = _.merge({}, productionConfig, testConfig);
<|endoftext|>


<commit_before>import LocationConflation from '@ideditor/location-conflation';
import whichPolygon from 'which-polygon';
import calcArea from '@mapbox/geojson-area';
import { utilArrayChunk } from '../util';

let _mainLocations = coreLocations(); // singleton
export { _mainLocations as locationManager };

//
// `coreLocations` maintains an internal index of all the boundaries/geofences used by iD.
// It's used by presets, community index, background imagery, to know where in the world these things are valid.
// These geofences should be defined by `locationSet` objects:
//
// let locationSet = {
//   include: [ Array of locations ],
//   exclude: [ Array of locations ]
// };
//
// For more info see the location-conflation and country-coder projects, see:
// https://github.com/ideditor/location-conflation
// https://github.com/ideditor/country-coder
//
export function coreLocations() {
  let _this = {};
  let _resolvedFeatures = {};              // cache of *resolved* locationSet features
  let _loco = new LocationConflation();    // instance of a location-conflation resolver
  let _wp;                                 // instance of a which-polygon index

  // pre-resolve the worldwide locationSet
  const world = { locationSet: { include: ['Q2'] } };
  resolveLocationSet(world);
  rebuildIndex();

  let _queue = [];
  let _deferred = new Set();
  let _inProcess;

  // Returns a Promise to process the queue
  function processQueue() {
    if (!_queue.length) return Promise.resolve();

    // console.log(`queue length ${_queue.length}`);
    const chunk = _queue.pop();
    return new Promise(resolvePromise => {
        const handle = window.requestIdleCallback(() => {
          _deferred.delete(handle);
          // const t0 = performance.now();
          chunk.forEach(resolveLocationSet);
          // const t1 = performance.now();
          // console.log('chunk processed in ' + (t1 - t0) + ' ms');
          resolvePromise();
        });
        _deferred.add(handle);
      })
      .then(() => processQueue());
  }

  // Pass an Object with a `locationSet` property,
  // Performs the locationSet resolution, caches the result, and sets a `locationSetID` property on the object.
  function resolveLocationSet(obj) {
    if (obj.locationSetID) return;  // work was done already

    try {
      const locationSet = obj.locationSet;
      if (!locationSet) {
        throw new Error('object missing locationSet property');
      }
      const resolved = _loco.resolveLocationSet(locationSet);
      const locationSetID = resolved.id;
      obj.locationSetID = locationSetID;

      if (!resolved.feature.geometry.coordinates.length || !resolved.feature.properties.area) {
        throw new Error(`locationSet ${locationSetID} resolves to an empty feature.`);
      }
      if (!_resolvedFeatures[locationSetID]) {  // First time seeing this locationSet feature
        let feature = JSON.parse(JSON.stringify(resolved.feature));   // deep clone
        feature.id = locationSetID;      // Important: always use the locationSet `id` (`+[Q30]`), not the feature `id` (`Q30`)
        feature.properties.id = locationSetID;
        _resolvedFeatures[locationSetID] = feature;  // insert into cache
      }
    } catch (err) {
      obj.locationSet = { include: ['Q2'] };  // default worldwide
      obj.locationSetID = '+[Q2]';
    }
  }

  // Rebuilds the whichPolygon index with whatever features have been resolved.
  function rebuildIndex() {
    _wp = whichPolygon({ features: Object.values(_resolvedFeatures) });
  }

  //
  // `mergeCustomGeoJSON`
  //  Accepts an FeatureCollection-like object containing custom locations
  //  Each feature must have a filename-like `id`, for example: `something.geojson`
  //
  //  {
  //    "type": "FeatureCollection"
  //    "features": [
  //      {
  //        "type": "Feature",
  //        "id": "philly_metro.geojson",
  //        "properties": { … },
  //        "geometry": { … }
  //      }
  //    ]
  //  }
  //
  _this.mergeCustomGeoJSON = (fc) => {
    if (fc && fc.type === 'FeatureCollection' && Array.isArray(fc.features)) {
      fc.features.forEach(feature => {
        feature.properties = feature.properties || {};
        let props = feature.properties;

        // Get `id` from either `id` or `properties`
        let id = feature.id || props.id;
        if (!id || !/^\S+\.geojson$/i.test(id)) return;

        // Ensure `id` exists and is lowercase
        id = id.toLowerCase();
        feature.id = id;
        props.id = id;

        // Ensure `area` property exists
        if (!props.area) {
          const area = calcArea.geometry(feature.geometry) / 1e6;  // m² to km²
          props.area = Number(area.toFixed(2));
        }

        _loco._cache[id] = feature;
      });
    }
  };

  //
  // `mergeLocationSets`
  //  Accepts an Array of Objects containing `locationSet` properties.
  //  The locationSets will be resolved and indexed in the background.
  //  [
  //   { id: 'preset1', locationSet: {…} },
  //   { id: 'preset2', locationSet: {…} },
  //   { id: 'preset3', locationSet: {…} },
  //   …
  //  ]
  //  After resolving and indexing, the Objects will be decorated with a
  //  `locationSetID` property.
  //  [
  //   { id: 'preset1', locationSet: {…}, locationSetID: '+[Q2]' },
  //   { id: 'preset2', locationSet: {…}, locationSetID: '+[Q30]' },
  //   { id: 'preset3', locationSet: {…}, locationSetID: '+[Q2]' },
  //   …
  //  ]
  //
  //  Returns a Promise fulfilled when the resolving/indexing has been completed
  //  This will take some seconds but happen in the background during browser idle time.
  //
  _this.mergeLocationSets = (objects) => {
    if (!Array.isArray(objects)) return Promise.reject('nothing to do');

    // Resolve all locationSets -> geojson, processing data in chunks
    //
    // Because this will happen during idle callbacks, we want to choose a chunk size
    // that won't make the browser stutter too badly.  LocationSets that are a simple
    // country coder include will resolve instantly, but ones that involve complex
    // include/exclude operations will take some milliseconds longer.
    //
    // Some discussion and performance results on these tickets:
    // https://github.com/ideditor/location-conflation/issues/26
    // https://github.com/osmlab/name-suggestion-index/issues/4784#issuecomment-742003434
    _queue = _queue.concat(utilArrayChunk(objects, 200));

    if (!_inProcess) {
      _inProcess = processQueue()
        .then(() => {
          rebuildIndex();
          _inProcess = null;
          return objects;
        });
    }
    return _inProcess;
  };

  //
  // `locationSetID`
  // Returns a locationSetID for a given locationSet (fallback to `+[Q2]`, world)
  // (The locationset doesn't necessarily need to be resolved to compute its `id`)
  //
  // Arguments
  //   `locationSet`: A locationSet, e.g. `{ include: ['us'] }`
  // Returns
  //   The locationSetID, e.g. `+[Q30]`
  //
  _this.locationSetID = (locationSet) => {
    let locationSetID;
    try {
      locationSetID = _loco.validateLocationSet(locationSet).id;
    } catch (err) {
      locationSetID = '+[Q2]';  // the world
    }
    return locationSetID;
  };

  //
  // `feature`
  // Returns the resolved GeoJSON feature for a given locationSetID (fallback to 'world')
  //
  // Arguments
  //   `locationSetID`: id of the form like `+[Q30]`  (United States)
  // Returns
  //   A GeoJSON feature:
  //   {
  //     type: 'Feature',
  //     id: '+[Q30]',
  //     properties: { id: '+[Q30]', area: 21817019.17, … },
  //     geometry: { … }
  //   }
  _this.feature = (locationSetID) => _resolvedFeatures[locationSetID] || _resolvedFeatures['+[Q2]'];

  //
  // `locationsAt`
  // Find all the resolved locationSets valid at the given location.
  // Results include the area (in km²) to facilitate sorting.
  //
  // Arguments
  //   `loc`: the [lon,lat] location to query, e.g. `[-74.4813, 40.7967]`
  // Returns
  //   Object of locationSetIDs to areas (in km²)
  //   {
  //     "+[Q2]": 511207893.3958111,
  //     "+[Q30]": 21817019.17,
  //     "+[new_jersey.geojson]": 22390.77,
  //     …
  //   }
  //
  _this.locationsAt = (loc) => {
    let result = {};
    _wp(loc, true).forEach(prop => result[prop.id] = prop.area);
    return result;
  };

  //
  // `query`
  // Execute a query directly against which-polygon
  // https://github.com/mapbox/which-polygon
  //
  // Arguments
  //   `loc`: the [lon,lat] location to query,
  //   `multi`: `true` to return all results, `false` to return first result
  // Returns
  //   Array of GeoJSON *properties* for the locationSet features that exist at `loc`
  //
  _this.query = (loc, multi) => _wp(loc, multi);

  // Direct access to the location-conflation resolver
  _this.loco = () => _loco;

  // Direct access to the which-polygon index
  _this.wp = () => _wp;

  return _this;
}
<commit_msg>Add guard code in `locationsAt`, for testing entities with invalid loc<commit_after>import LocationConflation from '@ideditor/location-conflation';
import whichPolygon from 'which-polygon';
import calcArea from '@mapbox/geojson-area';
import { utilArrayChunk } from '../util';

let _mainLocations = coreLocations(); // singleton
export { _mainLocations as locationManager };

//
// `coreLocations` maintains an internal index of all the boundaries/geofences used by iD.
// It's used by presets, community index, background imagery, to know where in the world these things are valid.
// These geofences should be defined by `locationSet` objects:
//
// let locationSet = {
//   include: [ Array of locations ],
//   exclude: [ Array of locations ]
// };
//
// For more info see the location-conflation and country-coder projects, see:
// https://github.com/ideditor/location-conflation
// https://github.com/ideditor/country-coder
//
export function coreLocations() {
  let _this = {};
  let _resolvedFeatures = {};              // cache of *resolved* locationSet features
  let _loco = new LocationConflation();    // instance of a location-conflation resolver
  let _wp;                                 // instance of a which-polygon index

  // pre-resolve the worldwide locationSet
  const world = { locationSet: { include: ['Q2'] } };
  resolveLocationSet(world);
  rebuildIndex();

  let _queue = [];
  let _deferred = new Set();
  let _inProcess;

  // Returns a Promise to process the queue
  function processQueue() {
    if (!_queue.length) return Promise.resolve();

    // console.log(`queue length ${_queue.length}`);
    const chunk = _queue.pop();
    return new Promise(resolvePromise => {
        const handle = window.requestIdleCallback(() => {
          _deferred.delete(handle);
          // const t0 = performance.now();
          chunk.forEach(resolveLocationSet);
          // const t1 = performance.now();
          // console.log('chunk processed in ' + (t1 - t0) + ' ms');
          resolvePromise();
        });
        _deferred.add(handle);
      })
      .then(() => processQueue());
  }

  // Pass an Object with a `locationSet` property,
  // Performs the locationSet resolution, caches the result, and sets a `locationSetID` property on the object.
  function resolveLocationSet(obj) {
    if (obj.locationSetID) return;  // work was done already

    try {
      const locationSet = obj.locationSet;
      if (!locationSet) {
        throw new Error('object missing locationSet property');
      }
      const resolved = _loco.resolveLocationSet(locationSet);
      const locationSetID = resolved.id;
      obj.locationSetID = locationSetID;

      if (!resolved.feature.geometry.coordinates.length || !resolved.feature.properties.area) {
        throw new Error(`locationSet ${locationSetID} resolves to an empty feature.`);
      }
      if (!_resolvedFeatures[locationSetID]) {  // First time seeing this locationSet feature
        let feature = JSON.parse(JSON.stringify(resolved.feature));   // deep clone
        feature.id = locationSetID;      // Important: always use the locationSet `id` (`+[Q30]`), not the feature `id` (`Q30`)
        feature.properties.id = locationSetID;
        _resolvedFeatures[locationSetID] = feature;  // insert into cache
      }
    } catch (err) {
      obj.locationSet = { include: ['Q2'] };  // default worldwide
      obj.locationSetID = '+[Q2]';
    }
  }

  // Rebuilds the whichPolygon index with whatever features have been resolved.
  function rebuildIndex() {
    _wp = whichPolygon({ features: Object.values(_resolvedFeatures) });
  }

  //
  // `mergeCustomGeoJSON`
  //  Accepts an FeatureCollection-like object containing custom locations
  //  Each feature must have a filename-like `id`, for example: `something.geojson`
  //
  //  {
  //    "type": "FeatureCollection"
  //    "features": [
  //      {
  //        "type": "Feature",
  //        "id": "philly_metro.geojson",
  //        "properties": { … },
  //        "geometry": { … }
  //      }
  //    ]
  //  }
  //
  _this.mergeCustomGeoJSON = (fc) => {
    if (fc && fc.type === 'FeatureCollection' && Array.isArray(fc.features)) {
      fc.features.forEach(feature => {
        feature.properties = feature.properties || {};
        let props = feature.properties;

        // Get `id` from either `id` or `properties`
        let id = feature.id || props.id;
        if (!id || !/^\S+\.geojson$/i.test(id)) return;

        // Ensure `id` exists and is lowercase
        id = id.toLowerCase();
        feature.id = id;
        props.id = id;

        // Ensure `area` property exists
        if (!props.area) {
          const area = calcArea.geometry(feature.geometry) / 1e6;  // m² to km²
          props.area = Number(area.toFixed(2));
        }

        _loco._cache[id] = feature;
      });
    }
  };

  //
  // `mergeLocationSets`
  //  Accepts an Array of Objects containing `locationSet` properties.
  //  The locationSets will be resolved and indexed in the background.
  //  [
  //   { id: 'preset1', locationSet: {…} },
  //   { id: 'preset2', locationSet: {…} },
  //   { id: 'preset3', locationSet: {…} },
  //   …
  //  ]
  //  After resolving and indexing, the Objects will be decorated with a
  //  `locationSetID` property.
  //  [
  //   { id: 'preset1', locationSet: {…}, locationSetID: '+[Q2]' },
  //   { id: 'preset2', locationSet: {…}, locationSetID: '+[Q30]' },
  //   { id: 'preset3', locationSet: {…}, locationSetID: '+[Q2]' },
  //   …
  //  ]
  //
  //  Returns a Promise fulfilled when the resolving/indexing has been completed
  //  This will take some seconds but happen in the background during browser idle time.
  //
  _this.mergeLocationSets = (objects) => {
    if (!Array.isArray(objects)) return Promise.reject('nothing to do');

    // Resolve all locationSets -> geojson, processing data in chunks
    //
    // Because this will happen during idle callbacks, we want to choose a chunk size
    // that won't make the browser stutter too badly.  LocationSets that are a simple
    // country coder include will resolve instantly, but ones that involve complex
    // include/exclude operations will take some milliseconds longer.
    //
    // Some discussion and performance results on these tickets:
    // https://github.com/ideditor/location-conflation/issues/26
    // https://github.com/osmlab/name-suggestion-index/issues/4784#issuecomment-742003434
    _queue = _queue.concat(utilArrayChunk(objects, 200));

    if (!_inProcess) {
      _inProcess = processQueue()
        .then(() => {
          rebuildIndex();
          _inProcess = null;
          return objects;
        });
    }
    return _inProcess;
  };

  //
  // `locationSetID`
  // Returns a locationSetID for a given locationSet (fallback to `+[Q2]`, world)
  // (The locationset doesn't necessarily need to be resolved to compute its `id`)
  //
  // Arguments
  //   `locationSet`: A locationSet, e.g. `{ include: ['us'] }`
  // Returns
  //   The locationSetID, e.g. `+[Q30]`
  //
  _this.locationSetID = (locationSet) => {
    let locationSetID;
    try {
      locationSetID = _loco.validateLocationSet(locationSet).id;
    } catch (err) {
      locationSetID = '+[Q2]';  // the world
    }
    return locationSetID;
  };

  //
  // `feature`
  // Returns the resolved GeoJSON feature for a given locationSetID (fallback to 'world')
  //
  // Arguments
  //   `locationSetID`: id of the form like `+[Q30]`  (United States)
  // Returns
  //   A GeoJSON feature:
  //   {
  //     type: 'Feature',
  //     id: '+[Q30]',
  //     properties: { id: '+[Q30]', area: 21817019.17, … },
  //     geometry: { … }
  //   }
  _this.feature = (locationSetID) => _resolvedFeatures[locationSetID] || _resolvedFeatures['+[Q2]'];

  //
  // `locationsAt`
  // Find all the resolved locationSets valid at the given location.
  // Results include the area (in km²) to facilitate sorting.
  //
  // Arguments
  //   `loc`: the [lon,lat] location to query, e.g. `[-74.4813, 40.7967]`
  // Returns
  //   Object of locationSetIDs to areas (in km²)
  //   {
  //     "+[Q2]": 511207893.3958111,
  //     "+[Q30]": 21817019.17,
  //     "+[new_jersey.geojson]": 22390.77,
  //     …
  //   }
  //
  _this.locationsAt = (loc) => {
    let result = {};
    (_wp(loc, true) || []).forEach(prop => result[prop.id] = prop.area);
    return result;
  };

  //
  // `query`
  // Execute a query directly against which-polygon
  // https://github.com/mapbox/which-polygon
  //
  // Arguments
  //   `loc`: the [lon,lat] location to query,
  //   `multi`: `true` to return all results, `false` to return first result
  // Returns
  //   Array of GeoJSON *properties* for the locationSet features that exist at `loc`
  //
  _this.query = (loc, multi) => _wp(loc, multi);

  // Direct access to the location-conflation resolver
  _this.loco = () => _loco;

  // Direct access to the which-polygon index
  _this.wp = () => _wp;

  return _this;
}
<|endoftext|>