<commit_before>/**
# List Bindings — Lists, Grids, and Tables

`b8r` list bindings are a deceptively simple feature.

Tne simplest case is straighforward:

```
<div data-list="_component_.list" data-bind="text=."></div>
<script>
  set('list', ['this', 'that', 'the other'])
</script>
```

If you look at the DOM, there will be one copy of the
list template for each element in the list, and it will have
a `data-list-instance` based on its corresponding array index.

e.g.

    <div
      data-bind="text=c#fiddle-xxxx.list[1]"
      data-orig-display=""
      data-list-instance="c#fiddle-xxxx.list[1]"
    >
      that
    </div>

Note that in a list instance, binding to `.` gets you the thing itself,
which is useful when binding to arrays of strings or numbers. In general, `b8r` list
bindings are designed with lists of *objects* in mind (which is generally what
the lists you get from service calls tend to look like).

## Best Practices — id-paths

Ideally, you'll be binding a list of objects each of which has a unique
identifier. You can provide the *path* to that identifier in the list
binding which allows `b8r` to perform more efficient list updates.

```
<div data-list="_component_.jedi:id">
  <span data-bind="text=.name"></span>
</div>
<script>
  set('jedi', [
    {id: 1, name: "Obi Wan Kenobi"},
    {id: 2, name: "Luke Skywalker"},
    {id: 3, name: "Yoda"},
    {id: 4, name: "Mace Windu"},
    {id: 5, name: "Qui-Gon Jinn"},
  ])
</script>
```

**Note** `data-list="_component_.jedi:id"` — the ":id" is referred
to as the `id-path` of the list binding. This curresponds to `b8r`'s
concept of an `id-path` that specifies an array element in a binding.

In this case `jedi[id=2]` points to:

    {id: 2, name: "Luke Skywalker"}

Now the corresponding list instance looks like this:

    <div
      data-orig-display=""
      data-list-instance="c#fiddle-xxxx.jedi[id=2]"
    >
      <span
        data-bind="text=c#fiddle-xxxx.jedi[id=2].name"
      >
        Luke Skywalker
      </span>
    </div>

### `_auto_`

If you have a list of *objects* without a unique id, you can simply use use
`_auto_` as your list id-path and `b8r` will automatically generate a unique
id for each array element. E.g.

```
<div data-list="_component_.jedi:_auto_">
  <span data-bind="text=.name"></span>
</div>
<pre data-bind="json=_component_.jedi"></pre>
<script>
  set('jedi', [
    {name: "Obi Wan Kenobi"},
    {name: "Luke Skywalker"},
    {name: "Yoda"},
    {name: "Mace Windu"},
    {name: "Qui-Gon Jinn"},
  ])
</script>
```

If you look at the content of the array after it's bound, you'll see that
each item in the array now has a *unique* `_auto_` property.

## Computed Lists

`b8r` provides seamless support for computed lists, and it looks like this:

    data-list="path.to.method(path.to.sourceList,to.this,to.that):id"

**Note**: for computed lists to work, you need to provide an `id-path` and the output
list must be a *filtered subset* of the source list. This makes implementing
**sorted** and/or **filtered** lists easy and efficient, but does *not* allow you
to fabricate lists of new objects on-the-fly without problems. (The solution is
to build joined lists and register them separately, or to compute additional properties
on-the-fly.)

### Filtered List

```
<input placeholder="filter names" data-bind="value=_component_.nameFilter">
<div data-list="_component_.filterByName(_component_.jedi,_component_.nameFilter):id">
  <span data-bind="text=.name"></span>
</div>
<script>
  set({
    nameFilter: '',
    filterByName: (list, nameFilter) => {
      nameFilter = nameFilter.toLocaleLowerCase()
      return nameFilter
        ? list.filter(item => item.name.toLocaleLowerCase().includes(nameFilter))
        : list
    },
    jedi: [
      {id: 1, name: "Obi Wan Kenobi"},
      {id: 2, name: "Luke Skywalker"},
      {id: 3, name: "Yoda"},
      {id: 4, name: "Mace Windu"},
      {id: 5, name: "Qui-Gon Jinn"},
    ]
  })
</script>
```

If you need to bind a list computed from scratch (e.g. the equivalent of an
OUTER JOIN), then simply `bind-list` to a path and then stick the computed
list in that path.

### Sorted (and Filtered) List

Sorted lists work just like any other computered list unless you change
the list items directly, in which case you need to let `b8r` know that the
list has changed (b.g. by "touching" the list via `b8r.touch('path.to.list')`).

In the following example you can edit the names of the Jedi in the list and
it is resorted on a `change` event (e.g. when you leave the field). It could
sort on `input` events as well, but that seems like a bad user experience!

The call to `touchList` also updates the *filtering* so, for example, you
could filter for Jedi named "luke" then rename "Luke Skywalker" as "Bill" and
when you tab out of the field, that record disappears.

```
<input placeholder="filter names" data-bind="value=_component_.nameFilter">
<div data-list="_component_.filterAndSort(_component_.jedi,_component_.nameFilter):id">
  <input data-bind="value=.name" data-event="change:_component_.forceRender">
</div>
<script>
  set({
    nameFilter: '',
    forceRender() {
      touch('jedi')
    },
    filterAndSort: (list, nameFilter) => {
      nameFilter = nameFilter.toLocaleLowerCase()
      filtered = nameFilter
        ? list.filter(item => item.name.toLocaleLowerCase().includes(nameFilter))
        : list
      return filtered.sort((a, b) => b8r.sortAscending(a.name, b.name))
    },
    jedi: [
      {id: 1, name: "Obi Wan Kenobi"},
      {id: 2, name: "Luke Skywalker"},
      {id: 3, name: "Yoda"},
      {id: 4, name: "Mace Windu"},
      {id: 5, name: "Qui-Gon Jinn"},
    ]
  })
</script>
```

### Using bindAll or touchElement for more efficient updates

If, as in the preceding example, you `touch` a list's path to force recomputing its
sorting / filtering then you will also force the rerendering the list elsewhere.
Usually this isn't a problem, but to avoid it you can instead use `touchElement` or
`bindAll` on the list *template*  (the element with the `data-list` binding) to simply
rerender that specific list.

You can also see an example of this in the [To Do](?source=todo.component.html) example
where `touchElement` is used to avoid triggering an unnecessary history entry.

### Virtual Lists & Grids

A common problem in applications is dealing with *very large* lists of objects. The usual
solution to this is to render only the things the user can see, and track the rest of the
objects elsewhere. This is often referred to as a virtual list (or grid)

#### Paging

The simplest way to implement a virtual list is via paging.

```
<input placeholder="filter names" data-bind="value=_component_.nameFilter">
<div data-list="_component_.filterByName(_component_.jedi,_component_.nameFilter,_component_.page):id">
  <span data-bind="text=.name"></span>
</div>
<div style="text-align: center;">
  <button
    data-bind="enabled_if=_component_.enablePrevious"
    data-event="click:_component_.previousPage"
  >
    <
  </button>
  <input
    style="width: 60px; text-align: right"
    type="number"
    min="1"
    data-bind="
      value=_component_.page;
      attr(max)=_component_.pageCount
    "
  >
  /
  <span data-bind="text=_component_.pageCount"></span>
  <button
    data-bind="enabled_if=_component_.enableNext"
    data-event="click:_component_.nextPage"
  >
    >
  </button>
</div>
<script>
  const {NameGenerator} = await import('../lib/name-generator.js')
  const examples = await b8r.json('./test/jedi-names.json')
  const jediNames = new NameGenerator(examples)

  set({
    nameFilter: '',
    filterByName: (list, nameFilter, page) => {
      nameFilter = nameFilter.toLocaleLowerCase()
      const filtered = nameFilter
        ? list.filter(item => item.name.toLocaleLowerCase().includes(nameFilter))
        : list
      if (get('nameFilter') !== nameFilter) {
        set({
          nameFilter,
          page: 1
        })
      }
      const {pageSize} = get()
      const pageCount = Math.ceil(filtered.length / pageSize)
      const start = (page - 1) * pageSize
      set({
        pageCount,
        enablePrevious: page > 1,
        enableNext: page < pageCount
      })
      return filtered.sort((a, b) => b8r.sortAscending(a.name, b.name)).slice(start, start + pageSize)
    },
    nextPage() {
      set('page', parseInt(get('page')) + 1)
    },
    previousPage() {
      set('page', parseInt(get('page')) - 1)
    },
    enablePrevious: false,
    enableNext: true,
    pageSize: 20,
    page: 1,
    pageCount: 1,
    jedi: (() => {
      const list = []
      for(let id = 0; id < 1000; id++) {
        list.push({id, name: jediNames.generate().replace(/(^\w| \w)/g, s => s.toLocaleUpperCase()) })
      }
      return list
    })()
  })
</script>
```

## Other Examples

- [Data Table](?source=components/data-table.component.js) is a sortable, customizable virtual data-table (powered by Big Grid)
- [Big Grid](?source=biggrid-test.component.html) is an example of a large virtual grid with fixed-sized elements
- [Big List](?source=big-list.component.html) is an example of a large virtual table with rows of varying height

### More to come…

- master/detail views (with a "single source of truth")
- editing lists (adding and removing elements, optimising service calls, etc.)
- client-side inner joins (compute them from the one of the source lists)
- client-side outer joins (pick a path, compute the list, stick it in the path)

*/
<commit_msg>Update lists.js<commit_after>/**
# List Bindings — Lists, Grids, and Tables

`b8r` list bindings are a deceptively simple feature.

Tne simplest case is straighforward:

```
<div data-list="_component_.list" data-bind="text=."></div>
<script>
  set('list', ['this', 'that', 'the other'])
</script>
```

If you look at the DOM, there will be one copy of the
list template (the element with the `data-list` attribute)
for each element in the list, and it will have
a `data-list-instance` based on its corresponding array index.

e.g.

    <div
      data-bind="text=c#fiddle-xxxx.list[1]"
      data-orig-display=""
      data-list-instance="c#fiddle-xxxx.list[1]"
    >
      that
    </div>

Note that in a list instance, binding to `.` gets you the thing itself,
which is useful when binding to arrays of strings or numbers. In general, `b8r` list
bindings are designed with lists of *objects* in mind (which is generally what
the lists you get from service calls tend to look like).

## Best Practices — id-paths

Ideally, you'll be binding a list of objects each of which has a unique
identifier. You can provide the *path* to that identifier in the list
binding which allows `b8r` to perform more efficient list updates.

```
<div data-list="_component_.jedi:id">
  <span data-bind="text=.name"></span>
</div>
<script>
  set('jedi', [
    {id: 1, name: "Obi Wan Kenobi"},
    {id: 2, name: "Luke Skywalker"},
    {id: 3, name: "Yoda"},
    {id: 4, name: "Mace Windu"},
    {id: 5, name: "Qui-Gon Jinn"},
  ])
</script>
```

**Note** `data-list="_component_.jedi:id"` — the ":id" is referred
to as the `id-path` of the list binding. This curresponds to `b8r`'s
concept of an `id-path` that specifies an array element in a binding.

In this case `jedi[id=2]` points to:

    {id: 2, name: "Luke Skywalker"}

Now the corresponding list instance looks like this:

    <div
      data-orig-display=""
      data-list-instance="c#fiddle-xxxx.jedi[id=2]"
    >
      <span
        data-bind="text=c#fiddle-xxxx.jedi[id=2].name"
      >
        Luke Skywalker
      </span>
    </div>

### `_auto_`

If you have a list of *objects* without a unique id, you can simply use use
`_auto_` as your list id-path and `b8r` will automatically generate a unique
id for each array element. E.g.

```
<div data-list="_component_.jedi:_auto_">
  <span data-bind="text=.name"></span>
</div>
<pre data-bind="json=_component_.jedi"></pre>
<script>
  set('jedi', [
    {name: "Obi Wan Kenobi"},
    {name: "Luke Skywalker"},
    {name: "Yoda"},
    {name: "Mace Windu"},
    {name: "Qui-Gon Jinn"},
  ])
</script>
```

If you look at the content of the array after it's bound, you'll see that
each item in the array now has a *unique* `_auto_` property.

## Computed Lists

`b8r` provides seamless support for computed lists, and it looks like this:

    data-list="path.to.method(path.to.sourceList,to.this,to.that):id"

**Note**: for computed lists to work, you need to provide an `id-path` and the output
list must be a *filtered subset* of the source list. This makes implementing
**sorted** and/or **filtered** lists easy and efficient, but does *not* allow you
to fabricate lists of new objects on-the-fly without problems. (The solution is
to build joined lists and register them separately, or to compute additional properties
on-the-fly.)

### Filtered List

```
<input placeholder="filter names" data-bind="value=_component_.nameFilter">
<div data-list="_component_.filterByName(_component_.jedi,_component_.nameFilter):id">
  <span data-bind="text=.name"></span>
</div>
<script>
  set({
    nameFilter: '',
    filterByName: (list, nameFilter) => {
      nameFilter = nameFilter.toLocaleLowerCase()
      return nameFilter
        ? list.filter(item => item.name.toLocaleLowerCase().includes(nameFilter))
        : list
    },
    jedi: [
      {id: 1, name: "Obi Wan Kenobi"},
      {id: 2, name: "Luke Skywalker"},
      {id: 3, name: "Yoda"},
      {id: 4, name: "Mace Windu"},
      {id: 5, name: "Qui-Gon Jinn"},
    ]
  })
</script>
```

If you need to bind a list computed from scratch (e.g. the equivalent of an
OUTER JOIN), then simply `bind-list` to a path and then stick the computed
list in that path.

### Sorted (and Filtered) List

Sorted lists work just like any other computered list unless you change
the list items directly, in which case you need to let `b8r` know that the
list has changed (b.g. by "touching" the list via `b8r.touch('path.to.list')`).

In the following example you can edit the names of the Jedi in the list and
it is resorted on a `change` event (e.g. when you leave the field). It could
sort on `input` events as well, but that seems like a bad user experience!

The call to `touchList` also updates the *filtering* so, for example, you
could filter for Jedi named "luke" then rename "Luke Skywalker" as "Bill" and
when you tab out of the field, that record disappears.

```
<input placeholder="filter names" data-bind="value=_component_.nameFilter">
<div data-list="_component_.filterAndSort(_component_.jedi,_component_.nameFilter):id">
  <input data-bind="value=.name" data-event="change:_component_.forceRender">
</div>
<script>
  set({
    nameFilter: '',
    forceRender() {
      touch('jedi')
    },
    filterAndSort: (list, nameFilter) => {
      nameFilter = nameFilter.toLocaleLowerCase()
      filtered = nameFilter
        ? list.filter(item => item.name.toLocaleLowerCase().includes(nameFilter))
        : list
      return filtered.sort((a, b) => b8r.sortAscending(a.name, b.name))
    },
    jedi: [
      {id: 1, name: "Obi Wan Kenobi"},
      {id: 2, name: "Luke Skywalker"},
      {id: 3, name: "Yoda"},
      {id: 4, name: "Mace Windu"},
      {id: 5, name: "Qui-Gon Jinn"},
    ]
  })
</script>
```

### Using bindAll or touchElement for more efficient updates

If, as in the preceding example, you `touch` a list's path to force recomputing its
sorting / filtering then you will also force the rerendering the list elsewhere.
Usually this isn't a problem, but to avoid it you can instead use `touchElement` or
`bindAll` on the list *template*  (the element with the `data-list` binding) to simply
rerender that specific list.

You can also see an example of this in the [To Do](?source=todo.component.html) example
where `touchElement` is used to avoid triggering an unnecessary history entry.

### Virtual Lists & Grids

A common problem in applications is dealing with *very large* lists of objects. The usual
solution to this is to render only the things the user can see, and track the rest of the
objects elsewhere. This is often referred to as a virtual list (or grid)

#### Paging

The simplest way to implement a virtual list is via paging.

```
<input placeholder="filter names" data-bind="value=_component_.nameFilter">
<div data-list="_component_.filterByName(_component_.jedi,_component_.nameFilter,_component_.page):id">
  <span data-bind="text=.name"></span>
</div>
<div style="text-align: center;">
  <button
    data-bind="enabled_if=_component_.enablePrevious"
    data-event="click:_component_.previousPage"
  >
    <
  </button>
  <input
    style="width: 60px; text-align: right"
    type="number"
    min="1"
    data-bind="
      value=_component_.page;
      attr(max)=_component_.pageCount
    "
  >
  /
  <span data-bind="text=_component_.pageCount"></span>
  <button
    data-bind="enabled_if=_component_.enableNext"
    data-event="click:_component_.nextPage"
  >
    >
  </button>
</div>
<script>
  const {NameGenerator} = await import('../lib/name-generator.js')
  const examples = await b8r.json('./test/jedi-names.json')
  const jediNames = new NameGenerator(examples)

  set({
    nameFilter: '',
    filterByName: (list, nameFilter, page) => {
      nameFilter = nameFilter.toLocaleLowerCase()
      const filtered = nameFilter
        ? list.filter(item => item.name.toLocaleLowerCase().includes(nameFilter))
        : list
      if (get('nameFilter') !== nameFilter) {
        set({
          nameFilter,
          page: 1
        })
      }
      const {pageSize} = get()
      const pageCount = Math.ceil(filtered.length / pageSize)
      const start = (page - 1) * pageSize
      set({
        pageCount,
        enablePrevious: page > 1,
        enableNext: page < pageCount
      })
      return filtered.sort((a, b) => b8r.sortAscending(a.name, b.name)).slice(start, start + pageSize)
    },
    nextPage() {
      set('page', parseInt(get('page')) + 1)
    },
    previousPage() {
      set('page', parseInt(get('page')) - 1)
    },
    enablePrevious: false,
    enableNext: true,
    pageSize: 20,
    page: 1,
    pageCount: 1,
    jedi: (() => {
      const list = []
      for(let id = 0; id < 1000; id++) {
        list.push({id, name: jediNames.generate().replace(/(^\w| \w)/g, s => s.toLocaleUpperCase()) })
      }
      return list
    })()
  })
</script>
```

## Other Examples

- [Data Table](?source=components/data-table.component.js) is a sortable, customizable virtual data-table (powered by Big Grid)
- [Big Grid](?source=biggrid-test.component.html) is an example of a large virtual grid with fixed-sized elements
- [Big List](?source=big-list.component.html) is an example of a large virtual table with rows of varying height

### More to come…

- master/detail views (with a "single source of truth")
- editing lists (adding and removing elements, optimising service calls, etc.)
- client-side inner joins (compute them from the one of the source lists)
- client-side outer joins (pick a path, compute the list, stick it in the path)

*/
<|endoftext|>


<commit_before>// # Choices component

/*
Render customized (non-native) dropdown choices.
*/

'use strict';

var _keyCodeToDirection;

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

var React = require('react');
var R = React.DOM;
var ReactDOM = require('react-dom');
var _ = require('../../undash');
var ScrollLock = require('react-scroll-lock');

var _require = require('../../utils');

var keyCodes = _require.keyCodes;
var scrollIntoContainerView = _require.scrollIntoContainerView;

var magicChoiceRe = /^\/\/\/[^\/]+\/\/\/$/;

var keyCodeToDirection = (_keyCodeToDirection = {}, _defineProperty(_keyCodeToDirection, keyCodes.UP, -1), _defineProperty(_keyCodeToDirection, keyCodes.DOWN, 1), _keyCodeToDirection);

var requestAnimationFrameThrottled = function requestAnimationFrameThrottled(frameCount, cb) {
  if (frameCount === undefined) frameCount = 1;

  if (frameCount < 1) {
    frameCount = 1;
  }
  var frameIndex = 0;
  var listenToFrame = function listenToFrame() {
    requestAnimationFrame(function () {
      frameIndex++;
      if (frameIndex === frameCount) {
        cb.apply(undefined, arguments);
      } else {
        listenToFrame();
      }
    });
  };
  listenToFrame();
};

module.exports = React.createClass({

  displayName: 'Choices',

  mixins: [require('../../mixins/helper'),
  //plugin.require('mixin.resize'),
  //plugin.require('mixin.scroll'),
  require('../../mixins/click-outside'), ScrollLock],

  getInitialState: function getInitialState() {
    return {
      maxHeight: null,
      open: this.props.open,
      searchString: '',
      hoverValue: null
    };
  },

  getIgnoreCloseNodes: function getIgnoreCloseNodes() {
    if (!this.props.ignoreCloseNodes) {
      return [];
    }
    var nodes = this.props.ignoreCloseNodes();
    if (!_.isArray(nodes)) {
      nodes = [nodes];
    }
    return nodes;
  },

  componentDidMount: function componentDidMount() {
    this.setOnClickOutside('container', (function (event) {

      // Make sure we don't find any nodes to ignore.
      if (!_.find(this.getIgnoreCloseNodes(), (function (node) {
        return this.isNodeInside(event.target, node);
      }).bind(this))) {
        this.onClose();
      }
    }).bind(this));

    if (this.refs.search) {
      this.refs.search.focus();
    }

    this.adjustSize();
    this.updateListeningToWindow();
  },

  componentWillUnmount: function componentWillUnmount() {
    this.stopListeningToWindow();
  },

  onSelect: function onSelect(choice, event) {
    this.setState({
      openSection: null,
      searchString: ''
    });
    this.props.onSelect(choice.value, event);
  },

  onChoiceAction: function onChoiceAction(choice) {
    this.setState({
      openSection: null,
      searchString: ''
    });
    this.props.onChoiceAction(choice);
  },

  onClose: function onClose() {
    this.setState({
      openSection: null,
      searchString: ''
    });
    this.props.onClose();
  },

  // Doing something a little crazy... measuring on every frame. Makes this smoother than using an interval.
  // Can't using scrolling events, because we might be scrolling inside a container instead of the body.
  // Shouldn't be any more costly than animations though, I think. And only one of these is open at a time.
  updateListeningToWindow: function updateListeningToWindow() {
    var _this = this;

    if (this.refs.choices) {
      if (!this.isListening) {
        (function () {
          var listenToFrame = function listenToFrame() {
            requestAnimationFrameThrottled(3, function () {
              if (_this.isListening) {
                // Make sure we don't adjust again before rendering.
                _this.adjustSize(function () {
                  if (_this.isListening) {
                    listenToFrame();
                  }
                });
              }
            });
          };
          _this.isListening = true;
          listenToFrame();
        })();
      }
    } else {
      if (this.isListening) {
        this.stopListeningToWindow();
      }
    }
  },

  stopListeningToWindow: function stopListeningToWindow() {
    if (this.isListening) {
      this.isListening = false;
    }
  },

  adjustSize: function adjustSize(cb) {
    var didSetState = false;
    if (this.refs.choices) {
      var node = this.refs.container;
      var rect = node.getBoundingClientRect();
      var top = rect.top;
      var windowHeight = window.innerHeight;
      var height = windowHeight - top;
      if (height !== this.state.maxHeight) {
        didSetState = true;
        this.setState({
          maxHeight: height
        }, cb);
      }
    }
    if (!didSetState) {
      if (cb) {
        cb();
      }
    }
  },

  componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
    var _this2 = this;

    var isOpening = !this.props.open && nextProps.open;

    var nextState = {
      open: nextProps.open
    };

    // For now, erase hover value when opening. Maybe get smarter about this later.
    if (isOpening) {
      nextState.hoverValue = null;
    }

    var isSearchOpening = this.isSearchOpening(nextProps);

    this.setState(nextState, function () {
      if (isOpening || isSearchOpening) {
        if (_this2.refs.search) {
          _this2.refs.search.focus();
        }
      }
      _this2.adjustSize();
      _this2.updateListeningToWindow();
    });
  },

  onHeaderClick: function onHeaderClick(choice) {
    if (this.state.openSection === choice.sectionKey) {
      this.setState({ openSection: null });
    } else {
      this.setState({ openSection: choice.sectionKey }, this.adjustSize);
    }
  },

  hasOneSection: function hasOneSection() {
    var sectionHeaders = this.props.choices.filter(function (c) {
      return c.sectionKey;
    });
    return sectionHeaders.length === 1;
  },

  visibleChoices: function visibleChoices() {
    var _this3 = this;

    var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];

    var choices = props.choices;
    var config = props.config;

    if (choices && choices.length === 0) {
      return [{ value: '///empty///' }];
    }

    if (this.state.searchString) {
      choices = choices.filter(function (choice) {
        if (choice.sectionKey) {
          return true;
        }
        return config.isSearchStringInChoice(_this3.state.searchString, choice);
      });
    }

    if (!props.isAccordion) {
      return choices;
    }

    var openSection = this.state.openSection;
    var alwaysExanded = this.hasOneSection() || this.state.searchString;
    var visibleChoices = [];
    var inSection;

    choices.forEach(function (choice) {
      if (choice.value && choice.value.match(magicChoiceRe)) {
        visibleChoices.push(choice);
      }
      if (choice.sectionKey) {
        inSection = choice.sectionKey === openSection;
      }
      if (alwaysExanded || choice.sectionKey || inSection) {
        visibleChoices.push(choice);
      }
    });

    return visibleChoices;
  },

  hasSearch: function hasSearch() {
    var visibleChoices = arguments.length <= 0 || arguments[0] === undefined ? this.visibleChoices() : arguments[0];
    var props = arguments.length <= 1 || arguments[1] === undefined ? this.props : arguments[1];

    var hasSearch = !!this.state.searchString;

    if (!hasSearch) {
      if (props.choices.length > 2) {
        if (_.find(visibleChoices, function (choice) {
          return !choice.action && choice.value !== '///loading///';
        })) {
          hasSearch = true;
        }
      }
    }

    return hasSearch;
  },

  isSearchOpening: function isSearchOpening(nextProps) {
    if (this.props.choices.length < nextProps.choices.length) {
      var prevHasSearch = this.hasSearch(this.visibleChoices());
      var nextHasSearch = this.hasSearch(this.visibleChoices(nextProps), nextProps);
      if (nextHasSearch && !prevHasSearch) {
        return true;
      }
    }
    return false;
  },

  render: function render() {
    return this.renderWithConfig();
  },

  onClick: function onClick(event) {
    // swallow clicks
    event.stopPropagation();
  },

  onChangeSearch: function onChangeSearch(event) {
    this.setState({
      searchString: event.target.value
    });
  },

  choiceValue: function choiceValue(choice) {
    if (choice.value === '///loading///') {
      return 'loading';
    } else if (choice.value === '///empty///') {
      return 'empty';
    } else if (choice.action) {
      return 'action:' + choice.action;
    } else if (choice.sectionKey) {
      return 'section:' + choice.sectionKey;
    } else {
      return 'value:' + choice.value;
    }
  },

  // Receive keydown events from parent. Really, this component should be
  // ripped apart into a stateless component, but much refactoring to be done
  // for that.
  onKeyDown: function onKeyDown(event) {
    var _this4 = this;

    var direction = event.keyCode in keyCodeToDirection ? keyCodeToDirection[event.keyCode] : 0;

    if (direction !== 0 || event.keyCode === keyCodes.ENTER) {
      (function () {

        var visibleChoices = _this4.visibleChoices();
        var hoverValue = _this4.state.hoverValue;

        event.preventDefault();
        event.stopPropagation();

        if (direction !== 0) {
          if (visibleChoices) {
            var hoverIndex = -1;
            if (hoverValue === 'search') {
              hoverIndex = -1;
            } else {
              _.find(visibleChoices, function (choice, i) {
                if (hoverValue === _this4.choiceValue(choice)) {
                  hoverIndex = i;
                  return true;
                }
              });
            }
            var nextHoverIndex = hoverIndex + direction;
            if (nextHoverIndex < 0) {
              nextHoverIndex = 0;
              if (_this4.refs.container) {
                var containerNode = ReactDOM.findDOMNode(_this4.refs.container);
                containerNode.scrollTop = 0;
              }
            } else if (nextHoverIndex + 1 > visibleChoices.length) {
              nextHoverIndex = visibleChoices.length - 1;
            }
            var nextHoverComponent = _this4.refs['choice-' + nextHoverIndex];
            if (nextHoverComponent && _this4.refs.container) {
              var node = ReactDOM.findDOMNode(nextHoverComponent);
              var containerNode = ReactDOM.findDOMNode(_this4.refs.container);
              scrollIntoContainerView(node, containerNode);
            }
            var nextHoverValue = nextHoverIndex > -1 ? _this4.choiceValue(visibleChoices[nextHoverIndex]) : 'search';
            if (nextHoverValue === 'search') {
              if (_this4.refs.search) {
                _this4.refs.search.focus();
              }
            }
            _this4.setState({
              hoverValue: nextHoverValue
            });
          }
        }

        if (event.keyCode === keyCodes.ENTER) {
          var selectedChoice = _.find(visibleChoices, function (choice) {
            return _this4.choiceValue(choice) === hoverValue;
          });
          if (selectedChoice) {
            if (hoverValue.indexOf('value:') === 0) {
              _this4.onSelect(selectedChoice, event);
            } else if (selectedChoice.action) {
              _this4.onChoiceAction(selectedChoice);
            } else if (selectedChoice.sectionKey) {
              _this4.onHeaderClick(selectedChoice);
            } else {
              _this4.onClose();
            }
          }
        }
      })();
    }
  },

  renderDefault: function renderDefault() {

    if (!this.props.open) {
      return null;
    }

    var config = this.props.config;

    var choices = this.visibleChoices();

    var search = null;

    var hasSearch = this.hasSearch(choices);

    if (hasSearch) {
      search = config.createElement('choices-search', { ref: 'search', key: 'choices-search', field: this.props.field, onChange: this.onChangeSearch });
    }

    return R.div({
      ref: 'container', onClick: this.onClick, className: 'choices-container', style: {
        userSelect: 'none', WebkitUserSelect: 'none', position: 'absolute',
        maxHeight: this.state.maxHeight ? this.state.maxHeight : null
      }
    }, config.cssTransitionWrapper(search, R.ul({ key: 'choices', ref: 'choices', className: 'choices' }, choices.map((function (choice, i) {

      var choiceElement = null;
      var choiceValue = null;

      if (choice.value === '///loading///') {
        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onClose }, R.span({ className: 'choice-label' }, config.createElement('loading-choice', { field: this.props.field })));
        choiceValue = 'loading';
      } else if (choice.value === '///empty///') {
        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onClose }, R.span({ className: 'choice-label' }, 'No choices available.'));
        choiceValue = 'empty';
      } else if (choice.action) {
        var labelClasses = 'choice-label ' + choice.action;

        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onChoiceAction.bind(this, choice) }, R.span({ className: labelClasses }, choice.label || this.props.config.actionChoiceLabel(choice.action)), this.props.config.createElement('choice-action-sample', { action: choice.action, choice: choice }));
        choiceValue = 'action:' + choice.action;
      } else if (choice.sectionKey) {
        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onHeaderClick.bind(this, choice) }, config.createElement('choice-section-header', { choice: choice }));
        choiceValue = 'section:' + choice.sectionKey;
      } else {
        choiceElement = config.createElement('choice', {
          onSelect: this.onSelect, choice: choice, field: this.props.field
        });
        choiceValue = 'value:' + choice.value;
      }

      return config.createElement('choices-item', {
        ref: 'choice-' + i, key: i, isHovering: this.state.hoverValue && this.state.hoverValue === choiceValue
      }, choiceElement);
    }).bind(this)))));
  }
});<commit_msg>rebuild<commit_after>// # Choices component

/*
Render customized (non-native) dropdown choices.
*/

'use strict';

var _keyCodeToDirection;

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

var React = require('react');
var R = React.DOM;
var ReactDOM = require('react-dom');
var _ = require('../../undash');
var ScrollLock = require('react-scroll-lock');

var _require = require('../../utils');

var keyCodes = _require.keyCodes;
var scrollIntoContainerView = _require.scrollIntoContainerView;

var magicChoiceRe = /^\/\/\/[^\/]+\/\/\/$/;

var keyCodeToDirection = (_keyCodeToDirection = {}, _defineProperty(_keyCodeToDirection, keyCodes.UP, -1), _defineProperty(_keyCodeToDirection, keyCodes.DOWN, 1), _keyCodeToDirection);

var requestAnimationFrameThrottled = function requestAnimationFrameThrottled(frameCount, cb) {
  if (frameCount === undefined) frameCount = 1;

  if (frameCount < 1) {
    frameCount = 1;
  }
  var frameIndex = 0;
  var listenToFrame = function listenToFrame() {
    requestAnimationFrame(function () {
      frameIndex++;
      if (frameIndex === frameCount) {
        cb.apply(undefined, arguments);
      } else {
        listenToFrame();
      }
    });
  };
  listenToFrame();
};

module.exports = React.createClass({

  displayName: 'Choices',

  mixins: [require('../../mixins/helper'),
  //plugin.require('mixin.resize'),
  //plugin.require('mixin.scroll'),
  require('../../mixins/click-outside'), ScrollLock],

  getInitialState: function getInitialState() {
    return {
      maxHeight: null,
      open: this.props.open,
      searchString: '',
      hoverValue: null
    };
  },

  getIgnoreCloseNodes: function getIgnoreCloseNodes() {
    if (!this.props.ignoreCloseNodes) {
      return [];
    }
    var nodes = this.props.ignoreCloseNodes();
    if (!_.isArray(nodes)) {
      nodes = [nodes];
    }
    return nodes;
  },

  componentDidMount: function componentDidMount() {
    this.setOnClickOutside('container', (function (event) {

      // Make sure we don't find any nodes to ignore.
      if (!_.find(this.getIgnoreCloseNodes(), (function (node) {
        return this.isNodeInside(event.target, node);
      }).bind(this))) {
        this.onClose();
      }
    }).bind(this));

    if (this.refs.search) {
      this.refs.search.focus();
    }

    this.adjustSize();
    this.updateListeningToWindow();
  },

  componentWillUnmount: function componentWillUnmount() {
    this.stopListeningToWindow();
  },

  onSelect: function onSelect(choice, event) {
    this.setState({
      openSection: null,
      searchString: ''
    });
    this.props.onSelect(choice.value, event);
  },

  onChoiceAction: function onChoiceAction(choice) {
    this.setState({
      openSection: null,
      searchString: ''
    });
    this.props.onChoiceAction(choice);
  },

  onClose: function onClose() {
    this.setState({
      openSection: null,
      searchString: ''
    });
    this.props.onClose();
  },

  // Doing something a little crazy... measuring on every frame. Makes this smoother than using an interval.
  // Can't using scrolling events, because we might be scrolling inside a container instead of the body.
  // Shouldn't be any more costly than animations though, I think. And only one of these is open at a time.
  updateListeningToWindow: function updateListeningToWindow() {
    var _this = this;

    if (this.refs.choices) {
      if (!this.isListening) {
        (function () {
          var listenToFrame = function listenToFrame() {
            requestAnimationFrameThrottled(3, function () {
              if (_this.isListening) {
                // Make sure we don't adjust again before rendering.
                _this.adjustSize(function () {
                  if (_this.isListening) {
                    listenToFrame();
                  }
                });
              }
            });
          };
          _this.isListening = true;
          listenToFrame();
        })();
      }
    } else {
      if (this.isListening) {
        this.stopListeningToWindow();
      }
    }
  },

  stopListeningToWindow: function stopListeningToWindow() {
    if (this.isListening) {
      this.isListening = false;
    }
  },

  adjustSize: function adjustSize(cb) {
    var didSetState = false;
    if (this.refs.choices) {
      var node = this.refs.container;
      var rect = node.getBoundingClientRect();
      var top = rect.top;
      var windowHeight = window.innerHeight;
      var height = windowHeight - top;
      if (height !== this.state.maxHeight) {
        didSetState = true;
        this.setState({
          maxHeight: height
        }, cb);
      }
    }
    if (!didSetState) {
      if (cb) {
        cb();
      }
    }
  },

  componentWillReceiveProps: function componentWillReceiveProps(nextProps) {
    var _this2 = this;

    var isOpening = !this.props.open && nextProps.open;

    var nextState = {
      open: nextProps.open
    };

    // For now, erase hover value when opening. Maybe get smarter about this later.
    if (isOpening) {
      nextState.hoverValue = null;
    }

    var isSearchOpening = this.isSearchOpening(nextProps);

    this.setState(nextState, function () {
      if (isOpening || isSearchOpening) {
        if (_this2.refs.search) {
          _this2.refs.search.focus();
        }
      }
      _this2.adjustSize();
      _this2.updateListeningToWindow();
    });
  },

  onHeaderClick: function onHeaderClick(choice) {
    if (this.state.openSection === choice.sectionKey) {
      this.setState({ openSection: null });
    } else {
      this.setState({ openSection: choice.sectionKey }, this.adjustSize);
    }
  },

  hasOneSection: function hasOneSection() {
    var sectionHeaders = this.props.choices.filter(function (c) {
      return c.sectionKey;
    });
    return sectionHeaders.length === 1;
  },

  visibleChoices: function visibleChoices() {
    var _this3 = this;

    var props = arguments.length <= 0 || arguments[0] === undefined ? this.props : arguments[0];

    var choices = props.choices;
    var config = props.config;

    if (choices && choices.length === 0) {
      return [{ value: '///empty///' }];
    }

    if (this.state.searchString) {
      choices = choices.filter(function (choice) {
        if (choice.sectionKey) {
          return true;
        }
        return config.isSearchStringInChoice(_this3.state.searchString, choice);
      });
    }

    if (!props.isAccordion) {
      return choices;
    }

    var openSection = this.state.openSection;
    var alwaysExanded = this.hasOneSection() || this.state.searchString;
    var visibleChoices = [];
    var inSection;

    choices.forEach(function (choice) {
      if (choice.value && choice.value.match(magicChoiceRe)) {
        visibleChoices.push(choice);
      }
      if (choice.sectionKey) {
        inSection = choice.sectionKey === openSection;
      }
      if (alwaysExanded || choice.sectionKey || inSection) {
        visibleChoices.push(choice);
      }
    });

    return visibleChoices;
  },

  hasSearch: function hasSearch() {
    var visibleChoices = arguments.length <= 0 || arguments[0] === undefined ? this.visibleChoices() : arguments[0];
    var props = arguments.length <= 1 || arguments[1] === undefined ? this.props : arguments[1];

    var hasSearch = !!this.state.searchString;

    if (!hasSearch) {
      if (props.choices.length > 2) {
        if (_.find(visibleChoices, function (choice) {
          return !choice.action && choice.value !== '///loading///';
        })) {
          hasSearch = true;
        }
      }
    }

    return hasSearch;
  },

  isSearchOpening: function isSearchOpening(nextProps) {
    if (this.props.choices.length < nextProps.choices.length) {
      var prevHasSearch = this.hasSearch(this.visibleChoices());
      var nextHasSearch = this.hasSearch(this.visibleChoices(nextProps), nextProps);
      if (nextHasSearch && !prevHasSearch) {
        return true;
      }
    }
    return false;
  },

  render: function render() {
    return this.renderWithConfig();
  },

  onClick: function onClick(event) {
    // swallow clicks
    event.stopPropagation();
  },

  onChangeSearch: function onChangeSearch(event) {
    this.setState({
      searchString: event.target.value
    });
  },

  choiceValue: function choiceValue(choice) {
    if (choice.value === '///loading///') {
      return 'loading';
    } else if (choice.value === '///empty///') {
      return 'empty';
    } else if (choice.action) {
      return 'action:' + choice.action;
    } else if (choice.sectionKey) {
      return 'section:' + choice.sectionKey;
    } else {
      return 'value:' + choice.value;
    }
  },

  // Receive keydown events from parent. Really, this component should be
  // ripped apart into a stateless component, but much refactoring to be done
  // for that.
  onKeyDown: function onKeyDown(event) {
    var _this4 = this;

    var direction = event.keyCode in keyCodeToDirection ? keyCodeToDirection[event.keyCode] : 0;

    if (direction !== 0 || event.keyCode === keyCodes.ENTER) {
      (function () {

        var visibleChoices = _this4.visibleChoices();
        var hoverValue = _this4.state.hoverValue;

        event.preventDefault();
        event.stopPropagation();

        if (direction !== 0) {
          if (visibleChoices) {
            var hoverIndex = -1;
            if (hoverValue === 'search') {
              hoverIndex = -1;
            } else {
              _.find(visibleChoices, function (choice, i) {
                if (hoverValue === _this4.choiceValue(choice)) {
                  hoverIndex = i;
                  return true;
                }
              });
            }
            var nextHoverIndex = hoverIndex + direction;
            if (nextHoverIndex < 0) {
              nextHoverIndex = 0;
              if (_this4.refs.container) {
                var containerNode = ReactDOM.findDOMNode(_this4.refs.container);
                containerNode.scrollTop = 0;
              }
            } else if (nextHoverIndex + 1 > visibleChoices.length) {
              nextHoverIndex = visibleChoices.length - 1;
            }
            var nextHoverComponent = _this4.refs['choice-' + nextHoverIndex];
            if (nextHoverComponent && _this4.refs.container) {
              var node = ReactDOM.findDOMNode(nextHoverComponent);
              var containerNode = ReactDOM.findDOMNode(_this4.refs.container);
              scrollIntoContainerView(node, containerNode);
            }
            var nextHoverValue = nextHoverIndex > -1 ? _this4.choiceValue(visibleChoices[nextHoverIndex]) : 'search';
            if (nextHoverValue === 'search') {
              if (_this4.refs.search) {
                _this4.refs.search.focus();
              }
            }
            _this4.setState({
              hoverValue: nextHoverValue
            });
          }
        }

        if (event.keyCode === keyCodes.ENTER) {
          var selectedChoice = _.find(visibleChoices, function (choice) {
            return _this4.choiceValue(choice) === hoverValue;
          });
          if (selectedChoice) {
            if (_this4.props.onFocusSelect) {
              _this4.props.onFocusSelect();
            }
            if (hoverValue.indexOf('value:') === 0) {
              _this4.onSelect(selectedChoice, event);
            } else if (selectedChoice.action) {
              _this4.onChoiceAction(selectedChoice);
            } else if (selectedChoice.sectionKey) {
              _this4.onHeaderClick(selectedChoice);
            } else {
              _this4.onClose();
            }
          }
        }
      })();
    }
  },

  renderDefault: function renderDefault() {

    if (!this.props.open) {
      return null;
    }

    var config = this.props.config;

    var choices = this.visibleChoices();

    var search = null;

    var hasSearch = this.hasSearch(choices);

    if (hasSearch) {
      search = config.createElement('choices-search', { ref: 'search', key: 'choices-search', field: this.props.field, onChange: this.onChangeSearch });
    }

    return R.div({
      ref: 'container', onClick: this.onClick, className: 'choices-container', style: {
        userSelect: 'none', WebkitUserSelect: 'none', position: 'absolute',
        maxHeight: this.state.maxHeight ? this.state.maxHeight : null
      }
    }, config.cssTransitionWrapper(search, R.ul({ key: 'choices', ref: 'choices', className: 'choices' }, choices.map((function (choice, i) {

      var choiceElement = null;
      var choiceValue = null;

      if (choice.value === '///loading///') {
        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onClose }, R.span({ className: 'choice-label' }, config.createElement('loading-choice', { field: this.props.field })));
        choiceValue = 'loading';
      } else if (choice.value === '///empty///') {
        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onClose }, R.span({ className: 'choice-label' }, 'No choices available.'));
        choiceValue = 'empty';
      } else if (choice.action) {
        var labelClasses = 'choice-label ' + choice.action;

        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onChoiceAction.bind(this, choice) }, R.span({ className: labelClasses }, choice.label || this.props.config.actionChoiceLabel(choice.action)), this.props.config.createElement('choice-action-sample', { action: choice.action, choice: choice }));
        choiceValue = 'action:' + choice.action;
      } else if (choice.sectionKey) {
        choiceElement = R.a({ href: 'JavaScript' + ':', onClick: this.onHeaderClick.bind(this, choice) }, config.createElement('choice-section-header', { choice: choice }));
        choiceValue = 'section:' + choice.sectionKey;
      } else {
        choiceElement = config.createElement('choice', {
          onSelect: this.onSelect, choice: choice, field: this.props.field
        });
        choiceValue = 'value:' + choice.value;
      }

      return config.createElement('choices-item', {
        ref: 'choice-' + i, key: i, isHovering: this.state.hoverValue && this.state.hoverValue === choiceValue
      }, choiceElement);
    }).bind(this)))));
  }
});<|endoftext|>


<commit_before>declare module "next" {
  declare export type RequestHandler = (req: http$IncomingMessage, res: http$ServerResponse, parsedUrl: any) => Promise<void>;
  declare export type NextApp = {
    prepare(): Promise<void>,
    getRequestHandler(): RequestHandler,
    render(req: http$IncomingMessage, res: http$ServerResponse, pathname: string, query: any): any,
    renderToHTML(req: http$IncomingMessage, res: http$ServerResponse, pathname: string, query: string): string,
    renderError(err: Error, req: http$IncomingMessage, res: http$ServerResponse, pathname: any, query: any): any,
    renderErrorToHTML(err: Error, req: http$IncomingMessage, res: http$ServerResponse, pathname: string, query: any): string,
  };
  declare export type Options = {
    dev?: boolean,
    dir?: string,
    quiet?: boolean,
    staticMarkup?: boolean,
  };
  declare module.exports: (opts: Options) => NextApp;
}

declare module "next/head" {
  import type {Component} from 'react';

  declare export default Class<Component<void, *, *>>;
}

declare module "next/link" {
  import type {Component} from 'react';

  declare export type State = { href: string };
  declare export default Class<Component<void, State, *>>;
}

declare module "next/prefetch" {
  import type {Component} from 'react';

  declare export type State = {
    href: string,
    prefetch?: boolean,
  };
  declare export var reloadIfPrefetched: any;
  declare export function prefetch(url: string): any;
  declare export default Class<Component<void, State, *>>;
}

declare module "next/router" {
  declare type RouteError = Error & { cancelled: boolean };
  declare type RouteCallback = (url: string) => void;
  declare type RouteErrorCallback = (err: RouteError, url: string) => void;

  declare export default {
    route: string;
    pathname: string;
    query: Object;
    onRouteChangeStart: ?RouteCallback;
    onRouteChangeComplete: ?RouteCallback;
    onRouteChangeError: ?RouteErrorCallback;
    push(url: string, as: ?string): Promise<boolean>;
    replace(url: string, as: ?string): Promise<boolean>;
  };
}

declare module "next/document" {
  import type {Component} from 'react';

  declare export type Context = {
    pathname: string,
    query: any,
    req?: any,
    res?: any,
    xhr?: any,
    err?: any,
  };
  declare export var Head: Class<Component<void, *, *>>;
  declare export var Main: Class<Component<void, *, *>>;
  declare export var NextScript: Class<Component<void, *, *>>;
  declare export default Class<Component<void, *, *>> & {
    getInitialProps: (ctx: Context) => Promise<*>;
    renderPage(cb: Function): void;
  };
}
<commit_msg>next links can take a URLObject (#1046)<commit_after>declare module "next" {
  declare export type RequestHandler = (req: http$IncomingMessage, res: http$ServerResponse, parsedUrl: any) => Promise<void>;
  declare export type NextApp = {
    prepare(): Promise<void>,
    getRequestHandler(): RequestHandler,
    render(req: http$IncomingMessage, res: http$ServerResponse, pathname: string, query: any): any,
    renderToHTML(req: http$IncomingMessage, res: http$ServerResponse, pathname: string, query: string): string,
    renderError(err: Error, req: http$IncomingMessage, res: http$ServerResponse, pathname: any, query: any): any,
    renderErrorToHTML(err: Error, req: http$IncomingMessage, res: http$ServerResponse, pathname: string, query: any): string,
  };
  declare export type Options = {
    dev?: boolean,
    dir?: string,
    quiet?: boolean,
    staticMarkup?: boolean,
  };
  declare module.exports: (opts: Options) => NextApp;
}

declare module "next/head" {
  import type {Component} from 'react';

  declare export default Class<Component<void, *, *>>;
}

declare module "next/link" {
  import type { Component } from "react";

  declare export type URLObject = {
    +href?: string,
    +protocol?: string,
    +slashes?: boolean,
    +auth?: string,
    +hostname?: string,
    +port?: string | number,
    +host?: string,
    +pathname?: string,
    +search?: string,
    +query?: Object,
    +hash?: string
  };

  declare export type State = {
    href: string | URLObject,
    as?: string | URLObject
  };
  declare export default Class<Component<void, State, *>>
}

declare module "next/prefetch" {
  import type {Component} from 'react';

  declare export type State = {
    href: string,
    prefetch?: boolean,
  };
  declare export var reloadIfPrefetched: any;
  declare export function prefetch(url: string): any;
  declare export default Class<Component<void, State, *>>;
}

declare module "next/router" {
  declare type RouteError = Error & { cancelled: boolean };
  declare type RouteCallback = (url: string) => void;
  declare type RouteErrorCallback = (err: RouteError, url: string) => void;

  declare export default {
    route: string;
    pathname: string;
    query: Object;
    onRouteChangeStart: ?RouteCallback;
    onRouteChangeComplete: ?RouteCallback;
    onRouteChangeError: ?RouteErrorCallback;
    push(url: string, as: ?string): Promise<boolean>;
    replace(url: string, as: ?string): Promise<boolean>;
  };
}

declare module "next/document" {
  import type {Component} from 'react';

  declare export type Context = {
    pathname: string,
    query: any,
    req?: any,
    res?: any,
    xhr?: any,
    err?: any,
  };
  declare export var Head: Class<Component<void, *, *>>;
  declare export var Main: Class<Component<void, *, *>>;
  declare export var NextScript: Class<Component<void, *, *>>;
  declare export default Class<Component<void, *, *>> & {
    getInitialProps: (ctx: Context) => Promise<*>;
    renderPage(cb: Function): void;
  };
}
<|endoftext|>


<commit_before>#!/usr/bin/env node
/*
 *     $ curl -O http://rgolubtsov.github.io/docs/misc/find_equil_index.js && \
 *       chmod 700 find_equil_index.js                                     && \
 *               ./find_equil_index.js
 *
 * This is a demo task.
 *
 * A zero-indexed array A consisting of N integers is given. An equilibrium
 * index of this array is any integer P such that 0 <= P < N and the sum
 * of elements of lower indices is equal to the sum of elements of higher
 * indices, i.e.
 *
 *     A[0] + A[1] + ... + A[P-1] = A[P+1] + ... + A[N-2] + A[N-1].
 *
 * Sum of zero elements is assumed to be equal to 0. This can happen if P = 0
 * or if P = N-1.
 *
 * For example, consider the following array A consisting of N = 8 elements:
 *
 *     A[0] = -1
 *     A[1] =  3
 *     A[2] = -4
 *     A[3] =  5
 *     A[4] =  1
 *     A[5] = -6
 *     A[6] =  2
 *     A[7] =  1
 *
 * P = 1 is an equilibrium index of this array, because:
 *
 *     A[0] = -1 = A[2] + A[3] + A[4] + A[5] + A[6] + A[7]
 *
 * P = 3 is an equilibrium index of this array, because:
 *
 *     A[0] + A[1] + A[2] = -2 = A[4] + A[5] + A[6] + A[7]
 *
 * P = 7 is also an equilibrium index, because:
 *
 *     A[0] + A[1] + A[2] + A[3] + A[4] + A[5] + A[6] = 0
 *
 * and there are no elements with indices greater than 7.
 *
 * P = 8 is not an equilibrium index, because it does not fulfill
 * the condition 0 <= P < N.
 *
 * Write a function:
 *
 *     function solution(A);
 *
 * that, given a zero-indexed array A consisting of N integers, returns any
 * of its equilibrium indices. The function should return -1 if no equilibrium
 * index exists.
 *
 * For example, given array A shown above, the function may return 1, 3 or 7,
 * as explained above.
 *
 * Assume that:
 *
 *     N is an integer within the range [0..100,000];
 *     each element of array A is an integer within the range
 *     [-2,147,483,648..2,147,483,647].
 *
 * Complexity:
 *
 *     expected worst-case time complexity is O(N);
 *     expected worst-case space complexity is O(N), beyond input storage
 *     (not counting the storage required for input arguments).
 *
 * Elements of input arrays can be modified.
 */

"use strict";

var NEW_LINE        = "\n";
var EQUIL_INDEX_MSG = "==> The equilibrium index of A is ";

function solution(A) {
    var num_of_A = A.length;

    var sum_of_A   = 0; // <== The complete sum of elements of A.
    var sum_of_A_1 = 0; // <== The sum of elements of lower  indices of A.
    var sum_of_A_2 = 0; // <== The sum of elements of higher indices of A.

    // Calculating the complete sum of elements of A.
    for (var i = 0; i < num_of_A; i++) {
        sum_of_A += A[i];
    }

    // Searching for the equilibrium index of A.
    for (var i = 0; i < num_of_A; i++) {
        sum_of_A_2  = sum_of_A - sum_of_A_1;
        sum_of_A_2 -= A[i];

        if (sum_of_A_1 === sum_of_A_2) {
            return i; // Okay, the equilibrium index found.
        }

        sum_of_A_1 += A[i];
    }

    return -1; // Returning -1 if there's no such an equilibrium index exists.
}

var A = [-1, 3, -4, 5, 1, -6, 2, 1];

var i = solution(A);

console.log(NEW_LINE + EQUIL_INDEX_MSG + i + NEW_LINE);

// vim:set nu:et:ts=4:sw=4:
<commit_msg>(A mug of): Demo algo snippet: Add a copyright notice and a reference to the license.<commit_after>#!/usr/bin/env node
/*
 * content/doc/misc/find_equil_index.js
 * ============================================================================
 *     $ curl -O http://rgolubtsov.github.io/docs/misc/find_equil_index.js && \
 *       chmod 700 find_equil_index.js                                     && \
 *               ./find_equil_index.js
 * ============================================================================
 * This is a demo task.
 *
 * A zero-indexed array A consisting of N integers is given. An equilibrium
 * index of this array is any integer P such that 0 <= P < N and the sum
 * of elements of lower indices is equal to the sum of elements of higher
 * indices, i.e.
 *
 *     A[0] + A[1] + ... + A[P-1] = A[P+1] + ... + A[N-2] + A[N-1].
 *
 * Sum of zero elements is assumed to be equal to 0. This can happen if P = 0
 * or if P = N-1.
 *
 * For example, consider the following array A consisting of N = 8 elements:
 *
 *     A[0] = -1
 *     A[1] =  3
 *     A[2] = -4
 *     A[3] =  5
 *     A[4] =  1
 *     A[5] = -6
 *     A[6] =  2
 *     A[7] =  1
 *
 * P = 1 is an equilibrium index of this array, because:
 *
 *     A[0] = -1 = A[2] + A[3] + A[4] + A[5] + A[6] + A[7]
 *
 * P = 3 is an equilibrium index of this array, because:
 *
 *     A[0] + A[1] + A[2] = -2 = A[4] + A[5] + A[6] + A[7]
 *
 * P = 7 is also an equilibrium index, because:
 *
 *     A[0] + A[1] + A[2] + A[3] + A[4] + A[5] + A[6] = 0
 *
 * and there are no elements with indices greater than 7.
 *
 * P = 8 is not an equilibrium index, because it does not fulfill
 * the condition 0 <= P < N.
 *
 * Write a function:
 *
 *     function solution(A);
 *
 * that, given a zero-indexed array A consisting of N integers, returns any
 * of its equilibrium indices. The function should return -1 if no equilibrium
 * index exists.
 *
 * For example, given array A shown above, the function may return 1, 3 or 7,
 * as explained above.
 *
 * Assume that:
 *
 *     N is an integer within the range [0..100,000];
 *     each element of array A is an integer within the range
 *     [-2,147,483,648..2,147,483,647].
 *
 * Complexity:
 *
 *     expected worst-case time complexity is O(N);
 *     expected worst-case space complexity is O(N), beyond input storage
 *     (not counting the storage required for input arguments).
 *
 * Elements of input arrays can be modified.
 * ============================================================================
 * Copyright (C) 2017 Radislav (Radicchio) Golubtsov
 *
 * (See the LICENSE file at the top of the source tree.)
 */

"use strict";

var NEW_LINE        = "\n";
var EQUIL_INDEX_MSG = "==> The equilibrium index of A is ";

function solution(A) {
    var num_of_A = A.length;

    var sum_of_A   = 0; // <== The complete sum of elements of A.
    var sum_of_A_1 = 0; // <== The sum of elements of lower  indices of A.
    var sum_of_A_2 = 0; // <== The sum of elements of higher indices of A.

    // Calculating the complete sum of elements of A.
    for (var i = 0; i < num_of_A; i++) {
        sum_of_A += A[i];
    }

    // Searching for the equilibrium index of A.
    for (var i = 0; i < num_of_A; i++) {
        sum_of_A_2  = sum_of_A - sum_of_A_1;
        sum_of_A_2 -= A[i];

        if (sum_of_A_1 === sum_of_A_2) {
            return i; // Okay, the equilibrium index found.
        }

        sum_of_A_1 += A[i];
    }

    return -1; // Returning -1 if there's no such an equilibrium index exists.
}

var A = [-1, 3, -4, 5, 1, -6, 2, 1];

var i = solution(A);

console.log(NEW_LINE + EQUIL_INDEX_MSG + i + NEW_LINE);

// vim:set nu:et:ts=4:sw=4:
<|endoftext|>


<commit_before>{
	data: (function() {
		var ro = {};
		ro.partname = 'end of the world 2014';
		//ro.partlength = (2 * 60 + 20) * 1000 * 100;
        ro.partlength = 0;
		ro.cameras = {
			'bottlecam': new THREE.PerspectiveCamera(45, global_engine.getAspectRatio(), 0.1, 10000),
			'logocam': new THREE.PerspectiveCamera(45, global_engine.getAspectRatio(), 0.1, 10000),
			'scrollercam': new THREE.PerspectiveCamera(45, global_engine.getAspectRatio(), 0.1, 10000),
			'ortho': new THREE.OrthographicCamera(global_engine.width / -2, global_engine.width / 2, global_engine.height / 2, global_engine.height / -2, 1, 1000)
		};

		ro.scenes = {};
		ro.lights = {};
		ro.objects = {};

		ro.scenes['bottles'] = (function(obj) {
			var scene = new THREE.Scene();

			obj.cameras['bottlecam'].position.z = 0;
			scene.add(obj.cameras['bottlecam']);

			obj.objects['bottles'] = [];

			var colortable = [0xff0c00, 0xff6d00, 0xffda00, 0xfcff01, 0xb0f909, 0x47cd17, 0x00af2f, 0x00ba60, 0x00e5b8, 0x00f7ff, 0x00f7ff, 0x0030a6, 0x00008b, 0x350091, 0xc100b4, 0xff11ce];
			var materials = [];

			for (var i=0; i<colortable.length; i++) {
				var material = new THREE.MeshLambertMaterial({ transparent: false, color: colortable[i], shininess: 1000, ambient: 0x000000});
				materials.push(material);
			}

			for (var i=0; i<5000; i++) {
				var mesh = new THREE.Mesh(object_pullo.children[1].geometry, materials[Math.floor(Math.random()*materials.length)]);
				mesh.position.y = Math.random() * 1000 - 500;
				mesh.position.x = Math.random() * 1000 - 500;
				mesh.position.z = Math.random() * 1000 - 500;
				mesh.scale.x = 3;
				mesh.scale.y = 3;
				mesh.scale.z = 3;

				mesh.rotation.x = Math.random() * Math.PI * 2;
				mesh.rotation.y = Math.random() * Math.PI * 2;
				mesh.rotation.z = Math.random() * Math.PI * 2;
				scene.add(mesh);
				obj.objects['bottles'][i] = mesh;
			}

			var directionallight = new THREE.DirectionalLight( 0xffffff, 0.5 );
			directionallight.position.set( 0, 1, 0 );
			scene.add( directionallight );
			obj.lights['directionallight'] = directionallight;

			var light = new THREE.SpotLight(0xFFFFFF);
			light.castShadow = false;
			light.position.set(200,200,400);
			scene.add(light);
			obj.lights['star-spot'] = light;

			scene.add(obj.cameras['bottlecam']);

			return scene;
		}(ro));

		ro.scenes['logo'] = (function(obj) {
			var scene = new THREE.Scene();

			var geometry = new THREE.PlaneGeometry(1024, 199, 1, 1);
			var material = new THREE.MeshLambertMaterial( { map: THREE.ImageUtils.loadTexture( image_damones.src ), transparent: true } );
			var mesh = new THREE.Mesh(geometry, material);
			mesh.position.x = 0;
			mesh.position.y = 0;
			mesh.position.z = 500;
			scene.add(mesh);
			obj.objects['logo'] = mesh;

			geometry = new THREE.PlaneGeometry(global_engine.getWidth(), global_engine.getHeight(), 1, 1);
			material = new THREE.MeshBasicMaterial({ map: global_engine.rendertargets['secondary'].target });
			mesh = new THREE.Mesh(geometry, material);
			mesh.position.x = 0;
			mesh.position.y = 0;
			mesh.position.z = 0;
			scene.add(mesh);
			obj.objects['bottleplane'] = mesh;

			var light = new THREE.SpotLight(0xFFFFFF);
			light.position.set(200, 200, 1500);
			scene.add(light);
			obj.lights['logospot1'] = light;

			light = new THREE.SpotLight(0xFFFFFF);
			light.position.set(-200, -200, 1500);
			scene.add(light);
			obj.lights['logospot2'] = light;

			light = new THREE.SpotLight(0xFFFFFF);
			light.intensity = 1.2;
			light.position.set(10, 10, 1500);
			scene.add(light);
			obj.lights['logospot3'] = light;

			var directionallight = new THREE.DirectionalLight( 0xffffff, 0.5 );
			directionallight.position.set( 0, 1, 0 );
			scene.add( directionallight );
			obj.lights['logodirectional'] = directionallight;

			scene.add(obj.cameras['logocam']);
			obj.cameras['logocam'].position.z = 1000;

			return scene;
		}(ro));

		ro.scenes['scroller'] = (function(obj) {
			var scene = new THREE.Scene();

			obj.objects['charmap'] = [];

			for (var i=0; i<256; i++) {
				var chardata = [];

				for (var j=0; j<16; j++) {
					var charline = raw_TopazPlus[i * 16 + j];
					var map = '';

					for (var k=0; k<8; k++) {
						if (charline & 0x80) {
							chardata.push(1);
							map += '1';
						} else {
							chardata.push(0);
							map += '0';
						}
						charline = charline << 1;
					}
				}

				obj.objects['charmap'].push(chardata);
			}
			console.log(obj.objects['charmap'][65]);

			obj.objects['scrolltext'] = '                        The drunken hobos are back! This time we would like to send the best birthday wishes to all the heroes at The End of The World 2014!        Zados always says that the only thing a demo needs is a scroller! aBHO says fuck you to all you nerds and drunks out there. We grab titties\'n\'ass if we want to, just like big boss Zados does. Need more beer? Kakka will helicopter you to the secret stash. Oh wait, there\'s no stash, we drank it all. Duncan passed out already - that wanky elevetor lamer. Where are some lamers we can puke on?!?            Credits for this eye cancer goes                Code: Kakka           Music: Abaddon & Tempest            Font: dMG...            Greetings fly to Extend, Scoopex, Komplex, Pyrotech, Dekadence, Fairlight, TRSi, HiRMU, Accession, Kewlers, Primitive, Darklite, CNCD, Byterapers, Orange, flo, MFX and all the rest!                    This message will now repeat.....................................                ';
			obj.objects['scrollpointer'] = 0;

			var geometry = new THREE.CubeGeometry(8, 8, 8);
			var colortable = [0x2f8cce, 0x479bd6, 0x67aee3, 0x8ac4f0, 0xaed9fd, 0xcfecff, 0xf2ffff, 0xfafefd, 0x9b751c, 0xae7000, 0xe1a603, 0xe6b529, 0xedc75e, 0xf4db97, 0xf9eecd, 0xfefcf8];
			var materials = [];

			for (var i=0; i<colortable.length; i++) {
				var material = new THREE.MeshBasicMaterial({ color: colortable[i] });
				materials.push(material);
			}

			obj.objects['scrollcubes'] = [];

			for (var i=0; i<30; i++) {
				for (var j=0; j<16; j++) {
					for (var k=0; k<8; k++) {
						var mesh = new THREE.Mesh(geometry, materials[j]);
						mesh.position.x = i * 10 * 8 + k * 10;
						mesh.position.y = -j * 10;
						mesh.position.z = 0;
						scene.add(mesh);
						obj.objects['scrollcubes'].push(mesh);
					}
				}
			}

			geometry = new THREE.PlaneGeometry(global_engine.getWidth(), global_engine.getHeight(), 1, 1);
			material = new THREE.MeshBasicMaterial({ map: global_engine.rendertargets['tertiary'].target });
			mesh = new THREE.Mesh(geometry, material);
			mesh.position.x = 0;
			mesh.position.y = 0;
			mesh.position.z = 0;
			scene.add(mesh);
			obj.objects['bottlelogoplane'] = mesh;

			var light = new THREE.SpotLight(0xFFFFFF);
			light.position.set(200, 200, 1500);
			scene.add(light);
			obj.lights['cubespot1'] = light;

			light = new THREE.SpotLight(0xFFFFFF);
			light.position.set(-200, -200, 1500);
			scene.add(light);
			obj.lights['cubespot2'] = light;

			light = new THREE.SpotLight(0xFFFFFF);
			light.intensity = 1.2;
			light.position.set(10, 10, 1500);
			scene.add(light);
			obj.lights['cubespot3'] = light;

			var directionallight = new THREE.DirectionalLight( 0xffffff, 0.5 );
			directionallight.position.set( 0, 1, 0 );
			scene.add( directionallight );
			obj.lights['cubedirectional'] = directionallight;

			scene.add(obj.cameras['scrollercam']);
			obj.cameras['scrollercam'].position.z = 1000;

			return scene;
		}(ro));

		ro.player = function(partdata, parttick, tick) {
			this.cameras['bottlecam'].position.x = Math.sin(tick/1000) * 70;
			this.cameras['bottlecam'].position.y = Math.sin(tick/700) * 50;
			this.cameras['bottlecam'].position.z = Math.sin(tick/800) * 40;

			this.cameras['bottlecam'].rotation.x = Math.sin(tick/1200);
			this.cameras['bottlecam'].rotation.y = Math.cos(tick/1000);
			this.cameras['bottlecam'].rotation.z = Math.sin(tick/809);

			this.objects['logo'].position.z = Math.sin(tick/200) * 400 + 500 + 1;
			this.objects['logo'].rotation.z = Math.sin(tick/12300) * Math.PI * 2 * Math.cos(tick/9000) * Math.PI * 2;

			var scrollpos = parttick;

			var scrollpos_x_sin = (Math.sin(parttick/500) * Math.cos(parttick/700) * 400) + parttick*2;

			var textpos = Math.floor(scrollpos_x_sin/200);
			var scrollspeed_remainder = textpos - (scrollpos_x_sin/200);

			var line_ypos = Math.sin(parttick/100) * 100 - 100 + 16 * 10;
			var line_zpos = Math.cos(parttick/1234) * 100 + 110;

			var cuberot_z = Math.sin(parttick/123) * Math.PI * 2;
			var cuberot_x = Math.cos(parttick/154) * Math.PI * 4;

			for (var i=0; i<30; i++) {
				var c = this.objects['scrolltext'].charAt((i + textpos) % this.objects['scrolltext'].length);
				var charmapdata = this.objects['charmap'][c.charCodeAt(0)];

				var charz = Math.sin((parttick)/200 + i/10) * 300 + 300;

				for (var j=0; j<16; j++) {
					for (var k=0; k<8; k++) {
						if (charmapdata[j*8 + k]) {
							this.objects['scrollcubes'][k + j*8 + i*16*8].visible = true;
						} else {
							this.objects['scrollcubes'][k + j*8 + i*16*8].visible = false;
						}

						var posx = i * 10 * 8 + k * 10 - (15*10*8);
						var spos = -scrollspeed_remainder * 80;

						this.objects['scrollcubes'][k + j*8 + i*16*8].position.x = posx - spos;
						this.objects['scrollcubes'][k + j*8 + i*16*8].position.y = -j * 10 + line_ypos;;
						this.objects['scrollcubes'][k + j*8 + i*16*8].position.z = line_zpos + charz;

						this.objects['scrollcubes'][k + j*8 + i*16*8].rotation.x = cuberot_x;
						this.objects['scrollcubes'][k + j*8 + i*16*8].rotation.z = cuberot_z;

//						this.objects['scrollcubes'][k + j*8 + i*16*8].position.y = -j * 10 * (Math.sin(parttick/1000 + k/10) * 2 - 1);
					}
				}
			}

			global_engine.renderers['main'].render(this.scenes['bottles'], this.cameras['bottlecam'], global_engine.rendertargets['secondary'].target, false);

			this.objects['bottleplane'].material.needsUpdate = true;

			global_engine.renderers['main'].render(this.scenes['logo'], this.cameras['logocam'], global_engine.rendertargets['tertiary'].target, false);

			this.objects['bottlelogoplane'].material.needsUpdate = true;

//			global_engine.renderers['main'].render(this.scenes['logo'], this.cameras['logocam']);
			global_engine.renderers['main'].render(this.scenes['scroller'], this.cameras['scrollercam']);
		}

		return ro;
	}())
}

<commit_msg>removed unused part<commit_after><|endoftext|>


<commit_before>// Generated by Creer at 10:56PM on April 20, 2016 UTC, git hash: '087b1901032ab5bed5806b24830233eac5c2de55'

var Class = require(__basedir + "/utilities/class");
var serializer = require(__basedir + "/gameplay/serializer");
var log = require(__basedir + "/gameplay/log");
var TwoPlayerGame = require(__basedir + "/gameplay/shared/twoPlayerGame");
var TurnBasedGame = require(__basedir + "/gameplay/shared/turnBasedGame");

//<<-- Creer-Merge: requires -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

// any additional requires you want can be required here safely between Creer re-runs

//<<-- /Creer-Merge: requires -->>

// @class Game: There's an infestation of enemy spiders challenging your queen broodmother spider! Protect her and attack the other broodmother in this turn based, node based, game.
var Game = Class(TwoPlayerGame, TurnBasedGame, {
    /**
     * Initializes Games.
     *
     * @param {Object} data - a simple mapping passsed in to the constructor with whatever you sent with it. GameSettings are in here by key/value as well.
     */
    init: function(data) {
        TurnBasedGame.init.apply(this, arguments);
        TwoPlayerGame.init.apply(this, arguments);

        //<<-- Creer-Merge: init -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

        this.maxTurns = 300;
        this.movementSpeed = 10;
        this.cutSpeed = 2;
        this.spitSpeed = 24;
        this.weaveSpeed = 16;
        this.initialWebStrength = 5;
        this.weavePower = 1;
        this.eggsScalar = 0.10;

        // used for map generation
        this._mapWidth = 400;
        this._mapHeight = 200;
        this._deadzone = 25;
        this._maxNests = 48; // per side, as are the folling
        this._minNests = 8;
        this._maxWebs = 20;
        this._minWebs = 0;
        this._minCrossWebs = 0;
        this._maxCrossWebs = 4;

        //<<-- /Creer-Merge: init -->>
    },

    name: "Spiders",

    aliases: [
        //<<-- Creer-Merge: aliases -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.
        "MegaMinerAI-17-Spiders",
        //<<-- /Creer-Merge: aliases -->>
    ],

    /**
     * This is called when the game begins, once players are connected and ready to play, and game objects have been initialized. Anything in init() may not have the appropriate game objects created yet..
     */
    begin: function() {
        TurnBasedGame.begin.apply(this, arguments);
        TwoPlayerGame.begin.apply(this, arguments);

        //<<-- Creer-Merge: begin -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

        // genreate Nests on the left
        var numNests = Math.randomInt(this._maxNests, this._minNests);
        var retries = 1000; // try to place nests this many times before giving up because the map is probably too congested
        for(var i = 0; i < numNests; i++) {
            while(--retries > 0) {
                var point = {
                    x: Math.randomInt(this._mapWidth/2 - this._deadzone/2),
                    y: Math.randomInt(this._mapHeight),
                };

                for(var i = 0; i < this.nests.length; i++) {
                    if(Math.euclideanDistance(this.nests[i], point) <= this._deadzone) {
                        point = null;
                        break;
                    }
                }

                if(point) {
                    this.create("Nest", point);
                    break; // out of while(true), as the point was valid
                }
            }
        }

        numNests = this.nests.length;

        // geneate Webs on the left
        var numWebs = Math.randomInt(this._maxWebs, this._minWebs);
        for(var i = 0; i < numWebs; i++) {
            var nestA = this.nests.randomElement();
            var nestB = nestA;
            while(nestB === nestA) {
                nestB = this.nests.randomElement();
            }

            this.create("Web", {
                nestA: nestA,
                nestB: nestB,
            });
        }

        // create the BroodMother
        this.players[0].broodMother = this.create("BroodMother", {
            owner: this.players[0],
            nest: this.nests.randomElement(),
        });

        // now mirror it

        // mirror the Nests
        for(var i = 0; i < numNests; i++) {
            var mirroring = this.nests[i];
            var mirrored = this.create("Nest", {
                x: this._mapWidth - mirroring.x,
                y: mirroring.y,
            });

            // these are not exposed to competitors
            mirroring.mirrorNest = mirrored;
            mirrored.mirrorNest = mirroring;
        }

        // mirror the Webs
        for(var i = 0; i < numWebs; i++) {
            var mirroring = this.webs[i];

            this.create("Web", {
                nestA: mirroring.nestA.mirrorNest,
                nestB: mirroring.nestB.mirrorNest,
            });
        }

        // webs that cross the middle of the game
        var numCrossWebs = Math.randomInt(this._minCrossWebs, this._maxCrossWebs);
        for(var i = 0; i < numCrossWebs; i++) {
            var nestA = this.nests[Math.randomInt(0, numNests-1)]; // the first half the the array has the nests on player 0's side
            var nestB = this.nests[Math.randomInt(numNests, numNests*2 - 1)]; // and the other half has playe 1's
            this.create("Web", {
                nestA: nestA,
                nestB: nestB,
            });

            if(nestA.mirrorNest !== nestB) {
                // this is the mirror of the web created above, so long as the nests don't mirror each other already
                this.create("Web", {
                    nestA: nestA.mirrorNest,
                    nestB: nestB.mirrorNest,
                });
            }
        }

        // mirror the BroodMother
        this.players[1].broodMother = this.create("BroodMother", {
            owner: this.players[1],
            nest:this.players[0].broodMother.nest.mirrorNest,
        });

        //<<-- /Creer-Merge: begin -->>
    },

    /**
     * This is called when the game has started, after all the begin()s. This is a good spot to send orders.
     */
    _started: function() {
        TurnBasedGame._started.apply(this, arguments);
        TwoPlayerGame._started.apply(this, arguments);

        //<<-- Creer-Merge: _started -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.
        // any logic for _started can be put here
        //<<-- /Creer-Merge: _started -->>
    },

    //<<-- Creer-Merge: added-functions -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

    _playerStartingTime: 9e10, // 90 seconds in nanoseconds

    /**
     * Calculates, and gives eggs to all the BroodMothers.
     *
     * @param {Player} player - the player to give their broodmother eggs
     */
    _giveEggs: function(player) {
        player.broodMother.eggs = Math.ceil((player.maxSpiderlings - player.spiders.length - 1) * this.eggsScalar); // -1 for the BroodMother in player.spiders that is not a Spiderling
    },

    /**
     * @override
     */
    nextTurn: function() {
        var returned = TurnBasedGame.nextTurn.apply(this, arguments);

        // before we go to the next player's turn, have all the spiders that are busy do stuff.
        // This ensures spiders finish work on the OPPONENT'S turn, so opponents have at least 1 turn to react.
        var movers = [];
        for(var j = 0; j < this.currentPlayer.spiders.length; j++) {
            var spider = this.currentPlayer.spiders[j];

            if(spider.workRemaining > 0) {
                spider.workRemaining -= Math.sqrt(spider.coworkers.length + 1); // + 1 for the spiderling itself

                if(spider.workRemaining <= 0) { // then they are done
                    if(spider.busy === "Moving") {
                        movers.push(spider); // they will finish moving AFTER other actions (e.g. cut)
                    }
                    else { // they finish now
                        for(var i = 0; i < spider.coworkers.length; i++) { // all the co-workers are done too
                            spider.coworkers[i].finish(true); // force finish them
                        }

                        spider.finish();
                    }
                }
            }
        }

        for(var i = 0; i < movers.length; i++) {
            var mover = movers[i];
            if(!mover.isDead) { // they may have died because of an action above (e.g. cut)
                movers[i].finish(); // now the spiderling moving can finish, because his Web may have been snapped above
            }
        }

        this._giveEggs(this.currentPlayer);

        if(this._checkPrimaryWin()) {
            return returned; // just incase we add code after this check, as if the primary win condition happens we need to exit the game immediately
        }

        return returned;
    },

    /**
     * Checks if the game is over because the primary win condition was reached (broodmother died), and delcares winners/losers as such
     *
     * @param {boolean} True if the game is over, false otherwise
     */
    _checkPrimaryWin: function() {
        var losers = [];
        var stillPlaying = [];
        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            if(player.broodMother.isDead) {
                losers.push(player);
            }
            else {
                stillPlaying.push(player);
            }
        }

        if(losers.length > 0) { // someone lost
            if(losers.length === this.players.length) {
                this._secondaryWin("All BroodMothers died on same turn");
            }
            else {
                for(var i = 0; i < losers.length; i++) {
                    var loser = losers[i];
                    if(!loser.lost) { // then they JUST lost
                        this.declareLoser(loser, "BroodMother died.");
                    }
                }

                if(stillPlaying.length === 1) { // they won!
                    this.declareWinner(stillPlaying[0], "Eliminated enemy BroodMother!");
                    return true;
                }
            }
        }

        return false;
    },

    /**
     * declares winners and losers based on win/loses
     *
     * @param {boolean} True if the game is over, false otherwise
     */
    _secondaryWin: function(secondaryReason) {
        var players = this.players.clone();
        players.sort(function(a, b) {
            return b.broodMother.health - a.broodMother.health;
        });

        // check if one player has more health in his broodmother than the rest
        if(players[0].broodMother.health !== players[1].broodMother.health) {
            var winner = players.shift();
            this.declareWinner(winner, "{} - BroodMother has the most remaining health ({}).".format(secondaryReason, winner.broodMother.health));
            this.declareLosers(players, "{} - BroodMother has less health remaining than winner.".format(secondaryReason));
            return;
        }

        // else check if one player has more spiders than the other
        players.sort(function(a, b) {
            return b.spiders.length - a.spiders.length;
        });

        if(players[0].spiders.length !== players[1].spiders.length) {
            var winner = players.shift();
            this.declareWinner(winner, "{} - Player has the most Spiders ({}).".format(secondaryReason, winner.spiders.length));
            this.declareLosers(players, "{} - Player has less Spiders alive than winner.".format(secondaryReason));
            return;
        }

        this._endGameViaCoinFlip();
    },

    /**
     * @override
     */
   _maxTurnsReached: function(){TurnBasedGame._maxTurnsReached.apply(this, arguments);
        var returned = TurnBasedGame._maxTurnsReached.apply(this, arguments);

        this._secondaryWin("Max turns reached (" + this.maxTurns + ")");

        return returned;
   },

    //<<-- /Creer-Merge: added-functions -->>

});

module.exports = Game;
<commit_msg>Give starting player eggs<commit_after>// Generated by Creer at 10:56PM on April 20, 2016 UTC, git hash: '087b1901032ab5bed5806b24830233eac5c2de55'

var Class = require(__basedir + "/utilities/class");
var serializer = require(__basedir + "/gameplay/serializer");
var log = require(__basedir + "/gameplay/log");
var TwoPlayerGame = require(__basedir + "/gameplay/shared/twoPlayerGame");
var TurnBasedGame = require(__basedir + "/gameplay/shared/turnBasedGame");

//<<-- Creer-Merge: requires -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

// any additional requires you want can be required here safely between Creer re-runs

//<<-- /Creer-Merge: requires -->>

// @class Game: There's an infestation of enemy spiders challenging your queen broodmother spider! Protect her and attack the other broodmother in this turn based, node based, game.
var Game = Class(TwoPlayerGame, TurnBasedGame, {
    /**
     * Initializes Games.
     *
     * @param {Object} data - a simple mapping passsed in to the constructor with whatever you sent with it. GameSettings are in here by key/value as well.
     */
    init: function(data) {
        TurnBasedGame.init.apply(this, arguments);
        TwoPlayerGame.init.apply(this, arguments);

        //<<-- Creer-Merge: init -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

        this.maxTurns = 300;
        this.movementSpeed = 10;
        this.cutSpeed = 2;
        this.spitSpeed = 24;
        this.weaveSpeed = 16;
        this.initialWebStrength = 5;
        this.weavePower = 1;
        this.eggsScalar = 0.10;

        // used for map generation
        this._mapWidth = 400;
        this._mapHeight = 200;
        this._deadzone = 25;
        this._maxNests = 48; // per side, as are the folling
        this._minNests = 8;
        this._maxWebs = 20;
        this._minWebs = 0;
        this._minCrossWebs = 0;
        this._maxCrossWebs = 4;

        //<<-- /Creer-Merge: init -->>
    },

    name: "Spiders",

    aliases: [
        //<<-- Creer-Merge: aliases -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.
        "MegaMinerAI-17-Spiders",
        //<<-- /Creer-Merge: aliases -->>
    ],

    /**
     * This is called when the game begins, once players are connected and ready to play, and game objects have been initialized. Anything in init() may not have the appropriate game objects created yet..
     */
    begin: function() {
        TurnBasedGame.begin.apply(this, arguments);
        TwoPlayerGame.begin.apply(this, arguments);

        //<<-- Creer-Merge: begin -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

        // genreate Nests on the left
        var numNests = Math.randomInt(this._maxNests, this._minNests);
        var retries = 1000; // try to place nests this many times before giving up because the map is probably too congested
        for(var i = 0; i < numNests; i++) {
            while(--retries > 0) {
                var point = {
                    x: Math.randomInt(this._mapWidth/2 - this._deadzone/2),
                    y: Math.randomInt(this._mapHeight),
                };

                for(var i = 0; i < this.nests.length; i++) {
                    if(Math.euclideanDistance(this.nests[i], point) <= this._deadzone) {
                        point = null;
                        break;
                    }
                }

                if(point) {
                    this.create("Nest", point);
                    break; // out of while(true), as the point was valid
                }
            }
        }

        numNests = this.nests.length;

        // geneate Webs on the left
        var numWebs = Math.randomInt(this._maxWebs, this._minWebs);
        for(var i = 0; i < numWebs; i++) {
            var nestA = this.nests.randomElement();
            var nestB = nestA;
            while(nestB === nestA) {
                nestB = this.nests.randomElement();
            }

            this.create("Web", {
                nestA: nestA,
                nestB: nestB,
            });
        }

        // create the BroodMother
        this.players[0].broodMother = this.create("BroodMother", {
            owner: this.players[0],
            nest: this.nests.randomElement(),
        });

        // now mirror it

        // mirror the Nests
        for(var i = 0; i < numNests; i++) {
            var mirroring = this.nests[i];
            var mirrored = this.create("Nest", {
                x: this._mapWidth - mirroring.x,
                y: mirroring.y,
            });

            // these are not exposed to competitors
            mirroring.mirrorNest = mirrored;
            mirrored.mirrorNest = mirroring;
        }

        // mirror the Webs
        for(var i = 0; i < numWebs; i++) {
            var mirroring = this.webs[i];

            this.create("Web", {
                nestA: mirroring.nestA.mirrorNest,
                nestB: mirroring.nestB.mirrorNest,
            });
        }

        // webs that cross the middle of the game
        var numCrossWebs = Math.randomInt(this._minCrossWebs, this._maxCrossWebs);
        for(var i = 0; i < numCrossWebs; i++) {
            var nestA = this.nests[Math.randomInt(0, numNests-1)]; // the first half the the array has the nests on player 0's side
            var nestB = this.nests[Math.randomInt(numNests, numNests*2 - 1)]; // and the other half has playe 1's
            this.create("Web", {
                nestA: nestA,
                nestB: nestB,
            });

            if(nestA.mirrorNest !== nestB) {
                // this is the mirror of the web created above, so long as the nests don't mirror each other already
                this.create("Web", {
                    nestA: nestA.mirrorNest,
                    nestB: nestB.mirrorNest,
                });
            }
        }

        // mirror the BroodMother
        this.players[1].broodMother = this.create("BroodMother", {
            owner: this.players[1],
            nest:this.players[0].broodMother.nest.mirrorNest,
        });

        this._giveEggs(this.players[0]);

        //<<-- /Creer-Merge: begin -->>
    },

    /**
     * This is called when the game has started, after all the begin()s. This is a good spot to send orders.
     */
    _started: function() {
        TurnBasedGame._started.apply(this, arguments);
        TwoPlayerGame._started.apply(this, arguments);

        //<<-- Creer-Merge: _started -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.
        // any logic for _started can be put here
        //<<-- /Creer-Merge: _started -->>
    },

    //<<-- Creer-Merge: added-functions -->> - Code you add between this comment and the end comment will be preserved between Creer re-runs.

    _playerStartingTime: 9e10, // 90 seconds in nanoseconds

    /**
     * Calculates, and gives eggs to all the BroodMothers.
     *
     * @param {Player} player - the player to give their broodmother eggs
     */
    _giveEggs: function(player) {
        player.broodMother.eggs = Math.ceil((player.maxSpiderlings - player.spiders.length - 1) * this.eggsScalar); // -1 for the BroodMother in player.spiders that is not a Spiderling
    },

    /**
     * @override
     */
    nextTurn: function() {
        var returned = TurnBasedGame.nextTurn.apply(this, arguments);

        // before we go to the next player's turn, have all the spiders that are busy do stuff.
        // This ensures spiders finish work on the OPPONENT'S turn, so opponents have at least 1 turn to react.
        var movers = [];
        for(var j = 0; j < this.currentPlayer.spiders.length; j++) {
            var spider = this.currentPlayer.spiders[j];

            if(spider.workRemaining > 0) {
                spider.workRemaining -= Math.sqrt(spider.coworkers.length + 1); // + 1 for the spiderling itself

                if(spider.workRemaining <= 0) { // then they are done
                    if(spider.busy === "Moving") {
                        movers.push(spider); // they will finish moving AFTER other actions (e.g. cut)
                    }
                    else { // they finish now
                        for(var i = 0; i < spider.coworkers.length; i++) { // all the co-workers are done too
                            spider.coworkers[i].finish(true); // force finish them
                        }

                        spider.finish();
                    }
                }
            }
        }

        for(var i = 0; i < movers.length; i++) {
            var mover = movers[i];
            if(!mover.isDead) { // they may have died because of an action above (e.g. cut)
                movers[i].finish(); // now the spiderling moving can finish, because his Web may have been snapped above
            }
        }

        this._giveEggs(this.currentPlayer);

        if(this._checkPrimaryWin()) {
            return returned; // just incase we add code after this check, as if the primary win condition happens we need to exit the game immediately
        }

        return returned;
    },

    /**
     * Checks if the game is over because the primary win condition was reached (broodmother died), and delcares winners/losers as such
     *
     * @param {boolean} True if the game is over, false otherwise
     */
    _checkPrimaryWin: function() {
        var losers = [];
        var stillPlaying = [];
        for(var i = 0; i < this.players.length; i++) {
            var player = this.players[i];

            if(player.broodMother.isDead) {
                losers.push(player);
            }
            else {
                stillPlaying.push(player);
            }
        }

        if(losers.length > 0) { // someone lost
            if(losers.length === this.players.length) {
                this._secondaryWin("All BroodMothers died on same turn");
            }
            else {
                for(var i = 0; i < losers.length; i++) {
                    var loser = losers[i];
                    if(!loser.lost) { // then they JUST lost
                        this.declareLoser(loser, "BroodMother died.");
                    }
                }

                if(stillPlaying.length === 1) { // they won!
                    this.declareWinner(stillPlaying[0], "Eliminated enemy BroodMother!");
                    return true;
                }
            }
        }

        return false;
    },

    /**
     * declares winners and losers based on win/loses
     *
     * @param {boolean} True if the game is over, false otherwise
     */
    _secondaryWin: function(secondaryReason) {
        var players = this.players.clone();
        players.sort(function(a, b) {
            return b.broodMother.health - a.broodMother.health;
        });

        // check if one player has more health in his broodmother than the rest
        if(players[0].broodMother.health !== players[1].broodMother.health) {
            var winner = players.shift();
            this.declareWinner(winner, "{} - BroodMother has the most remaining health ({}).".format(secondaryReason, winner.broodMother.health));
            this.declareLosers(players, "{} - BroodMother has less health remaining than winner.".format(secondaryReason));
            return;
        }

        // else check if one player has more spiders than the other
        players.sort(function(a, b) {
            return b.spiders.length - a.spiders.length;
        });

        if(players[0].spiders.length !== players[1].spiders.length) {
            var winner = players.shift();
            this.declareWinner(winner, "{} - Player has the most Spiders ({}).".format(secondaryReason, winner.spiders.length));
            this.declareLosers(players, "{} - Player has less Spiders alive than winner.".format(secondaryReason));
            return;
        }

        this._endGameViaCoinFlip();
    },

    /**
     * @override
     */
   _maxTurnsReached: function(){TurnBasedGame._maxTurnsReached.apply(this, arguments);
        var returned = TurnBasedGame._maxTurnsReached.apply(this, arguments);

        this._secondaryWin("Max turns reached (" + this.maxTurns + ")");

        return returned;
   },

    //<<-- /Creer-Merge: added-functions -->>

});

module.exports = Game;
<|endoftext|>