<commit_before>import colors from 'colors/safe';
import {errToString} from './util';
import moment from 'moment';
import net from 'net';
import tls from 'tls';
import protodef from 'rethinkdb/proto-def';
import url from 'url';

// A Connection instance wraps an incoming WebSocket (likely from a browser
// using rethinkdb-websocket-client) and an outgoing RethinkDB TCP socket. Once
// start() is called, it will forward traffic in both directions until either
// side disconnects. Each incoming WebSocket spawns a new outgoing TCP socket.
export class Connection {
  constructor(queryValidator, webSocket, loggingMode) {
    this.queryValidator = queryValidator;
    this.webSocket = webSocket;
    this.loggingMode = loggingMode;
    this.remoteAddress = webSocket._socket.remoteAddress;
    this.remotePort = webSocket._socket.remotePort;
  }

  start({sessionCreator, dbHost, dbPort, dbAuthKey, dbSsl}) {
    const urlQueryParams = url.parse(this.webSocket.upgradeReq.url, true).query;
    const req = this.webSocket.upgradeReq;
    this.sessionPromise = sessionCreator(urlQueryParams, req).catch(e => {
      this.cleanupAndLogErr('Error in sessionCreator', e);
    });
    this.dbAuthKey = dbAuthKey;
    this.wsInBuffer = new Buffer(0);
    this.handshakeComplete = false;
    this.isClosed = false;
    let options = {
      host: dbHost,
      port: dbPort
    };
    if (typeof dbSsl === 'boolean' && dbSsl) {
      this.dbSocket = tls.connect(options);
    } else if (typeof dbSsl === 'object') {
      options = {...dbSsl, ...options};
      this.dbSocket = tls.connect(options);
    } else {
      this.dbSocket = net.connect(options);
    }
    this.dbAuthKey = dbAuthKey;
    this.setupDbSocket();
    this.setupWebSocket();
    if (this.loggingMode === 'all') {
      this.log('Connect');
    }
  }

  sendWebSocketMessage(data) {
    if (this.webSocket.protocol === 'base64') {
      const b64EncodedData = (new Buffer(data)).toString('base64');
      this.webSocket.send(b64EncodedData);
    } else {
      this.webSocket.send(data, {binary: true});
    }
  }

  setupDbSocket() {
    this.dbSocket.setNoDelay();
    this.dbSocket.on('data', data => {
      if (!this.isClosed) {
        try {
          this.sendWebSocketMessage(data);
        } catch (e) {
          this.cleanupAndLogErr('Error recv dbSocket data', e);
        }
      }
    });
    this.dbSocket.on('end', () => {
      this.cleanup();
    });
    this.dbSocket.on('close', () => {
      if (this.loggingMode === 'all') {
        this.log('dbSocket closed');
      }
      this.cleanup();
    });
    this.dbSocket.on('error', e => {
      this.cleanupAndLogErr('dbSocket error', e);
    });
  }

  setupWebSocket() {
    this.webSocket.on('message', msg => {
      if (!this.isClosed) {
        try {
          this.handleWsMessage(msg);
        } catch (e) {
          this.cleanupAndLogErr('Error recv webSocket data', e);
        }
      }
    });
    this.webSocket.on('close', () => {
      if (this.loggingMode === 'all') {
        this.log('webSocket closed');
      }
      this.cleanup();
    });
    this.webSocket.on('error', e => {
      this.cleanupAndLogErr('webSocket error', e);
    });
  }

  log(msg, token) {
    const time = colors.blue(moment().format('HH:mm:ss.SSS'));
    const addr = colors.gray(this.remoteAddress + ':' + this.remotePort);
    const tokenStr = token ? colors.yellow(`tkn=${token} `) : '';
    console.log(`${time} ${addr} ${tokenStr}${msg}`);
  }

  cleanup() {
    this.isClosed = true;
    this.dbSocket.destroy();
    this.webSocket.close();
  }

  cleanupAndLogErr(msg, error) {
    this.cleanup();
    const fullMsg = error ? msg + '\n' + errToString(error) : msg;
    this.log(colors.red(fullMsg));
  }

  processClientCommand(cmdBuf) {
    // https://github.com/rethinkdb/rethinkdb/blob/61692c0ed4/drivers/javascript/net.coffee#L269-L273
    const token = cmdBuf.readUInt32LE(0) + 0x100000000 * cmdBuf.readUInt32LE(4);
    const queryCmdBuf = cmdBuf.slice(12, cmdBuf.length);
    const logFn = this.log.bind(this);
    const validatePromise = this.sessionPromise.then(session => (
      this.queryValidator.validateQueryCmd(token, queryCmdBuf, session, logFn)
    ));
    return validatePromise.then(allow => {
      if (allow) {
        this.dbSocket.write(cmdBuf, 'binary');
      }
      // TODO It might be nice to send a response back to the client of type
      // CLIENT_ERROR. However, we'd need to parse every message back from
      // the db in order to avoid injecting our response in the middle of a
      // large response.
      //
      // Right now, the query will be silently ignored in the frontend and
      // logged in the backend. This might lead to some sort of memory leak
      // in the frontend if it's waiting for responses from queries. This is
      // not very important, because properly written frontends should avoid
      // submitting queries that are denied.
      //
      // Alternatively, we could close the connection after a denied query,
      // as an easier to debug fail-fast option.
    });
  }

  validateClientHandshake(buf) {
    const protocolVersion = buf.readUInt32LE(0);
    if (protocolVersion !== protodef.VersionDummy.Version.V0_4) {
      this.cleanupAndLogErr('Invalid protocolVersion ' + protocolVersion);
      return 0;
    }
    const keyLength = buf.readUInt32LE(4);
    if (keyLength !== 0) {
      this.cleanupAndLogErr('Auth key not supported');
      return 0;
    }
    const protocolType = buf.readUInt32LE(8);
    if (protocolType !== protodef.VersionDummy.Protocol.JSON) {
      this.cleanupAndLogErr('Protocol type not supported ' + protocolType);
      return 0;
    }
    return 12;
  }

  processNextMessage(buf) {
    if (!this.handshakeComplete) {
      if (buf.length >= 12) {
        const clientHandshakeLength = this.validateClientHandshake(buf);
        if (clientHandshakeLength > 0) {
          const authKey = this.dbAuthKey || '';
          const outBuf = new Buffer(12 + authKey.length);
          outBuf.writeUInt32LE(protodef.VersionDummy.Version.V0_4, 0);
          outBuf.writeUInt32LE(authKey.length, 4);
          outBuf.write(authKey, 8);
          outBuf.writeUInt32LE(protodef.VersionDummy.Protocol.JSON, 8 + authKey.length);
          this.dbSocket.write(outBuf, 'binary');
          this.handshakeComplete = true;
          return clientHandshakeLength;
        } else {
          return 0;
        }
      }
    } else {
      if (buf.length >= 12) {
        const encodedQueryLength = buf.readUInt32LE(8);
        const queryEndOffset = 12 + encodedQueryLength;
        if (queryEndOffset <= buf.length) {
          const cmdBuf = buf.slice(0, queryEndOffset);
          this.processClientCommand(cmdBuf).catch(e => {
            this.cleanupAndLogErr('Error in processClientCommand', e);
          });
          return queryEndOffset;
        }
      }
    }
    return 0;
  }

  handleWsMessage(msg) {
    const isBase64 = typeof msg === 'string' && this.webSocket.protocol === 'base64';
    const incomingBuffer = isBase64 ? new Buffer(msg, 'base64') : msg;
    this.wsInBuffer = Buffer.concat([this.wsInBuffer, incomingBuffer]);
    let keepGoing = true;
    while (keepGoing) {
      let bytesConsumed = this.processNextMessage(this.wsInBuffer);
      this.wsInBuffer = this.wsInBuffer.slice(bytesConsumed);
      keepGoing = bytesConsumed > 0;
    }
  }
}
<commit_msg>On session reject, send client an error message<commit_after>import colors from 'colors/safe';
import {errToString} from './util';
import moment from 'moment';
import net from 'net';
import tls from 'tls';
import protodef from 'rethinkdb/proto-def';
import url from 'url';

// A Connection instance wraps an incoming WebSocket (likely from a browser
// using rethinkdb-websocket-client) and an outgoing RethinkDB TCP socket. Once
// start() is called, it will forward traffic in both directions until either
// side disconnects. Each incoming WebSocket spawns a new outgoing TCP socket.
export class Connection {
  constructor(queryValidator, webSocket, loggingMode) {
    this.queryValidator = queryValidator;
    this.webSocket = webSocket;
    this.loggingMode = loggingMode;
    this.remoteAddress = webSocket._socket.remoteAddress;
    this.remotePort = webSocket._socket.remotePort;
  }

  start({sessionCreator, dbHost, dbPort, dbAuthKey, dbSsl}) {
    const urlQueryParams = url.parse(this.webSocket.upgradeReq.url, true).query;
    const req = this.webSocket.upgradeReq;
    this.sessionPromise = sessionCreator(urlQueryParams, req).catch(e => {
      this.sendWebSocketMessage('rethinkdb-websocket-server session rejected\0');
      this.cleanupAndLogErr('Error in sessionCreator', e);
    });
    this.dbAuthKey = dbAuthKey;
    this.wsInBuffer = new Buffer(0);
    this.handshakeComplete = false;
    this.isClosed = false;
    let options = {
      host: dbHost,
      port: dbPort
    };
    if (typeof dbSsl === 'boolean' && dbSsl) {
      this.dbSocket = tls.connect(options);
    } else if (typeof dbSsl === 'object') {
      options = {...dbSsl, ...options};
      this.dbSocket = tls.connect(options);
    } else {
      this.dbSocket = net.connect(options);
    }
    this.dbAuthKey = dbAuthKey;
    this.setupDbSocket();
    this.setupWebSocket();
    if (this.loggingMode === 'all') {
      this.log('Connect');
    }
  }

  sendWebSocketMessage(data) {
    if (this.webSocket.protocol === 'base64') {
      const b64EncodedData = (new Buffer(data)).toString('base64');
      this.webSocket.send(b64EncodedData);
    } else {
      this.webSocket.send(data, {binary: true});
    }
  }

  setupDbSocket() {
    this.dbSocket.setNoDelay();
    this.dbSocket.on('data', data => {
      if (!this.isClosed) {
        try {
          this.sendWebSocketMessage(data);
        } catch (e) {
          this.cleanupAndLogErr('Error recv dbSocket data', e);
        }
      }
    });
    this.dbSocket.on('end', () => {
      this.cleanup();
    });
    this.dbSocket.on('close', () => {
      if (this.loggingMode === 'all') {
        this.log('dbSocket closed');
      }
      this.cleanup();
    });
    this.dbSocket.on('error', e => {
      this.cleanupAndLogErr('dbSocket error', e);
    });
  }

  setupWebSocket() {
    this.webSocket.on('message', msg => {
      if (!this.isClosed) {
        try {
          this.handleWsMessage(msg);
        } catch (e) {
          this.cleanupAndLogErr('Error recv webSocket data', e);
        }
      }
    });
    this.webSocket.on('close', () => {
      if (this.loggingMode === 'all') {
        this.log('webSocket closed');
      }
      this.cleanup();
    });
    this.webSocket.on('error', e => {
      this.cleanupAndLogErr('webSocket error', e);
    });
  }

  log(msg, token) {
    const time = colors.blue(moment().format('HH:mm:ss.SSS'));
    const addr = colors.gray(this.remoteAddress + ':' + this.remotePort);
    const tokenStr = token ? colors.yellow(`tkn=${token} `) : '';
    console.log(`${time} ${addr} ${tokenStr}${msg}`);
  }

  cleanup() {
    this.isClosed = true;
    this.dbSocket.destroy();
    this.webSocket.close();
  }

  cleanupAndLogErr(msg, error) {
    this.cleanup();
    const fullMsg = error ? msg + '\n' + errToString(error) : msg;
    this.log(colors.red(fullMsg));
  }

  processClientCommand(cmdBuf) {
    // https://github.com/rethinkdb/rethinkdb/blob/61692c0ed4/drivers/javascript/net.coffee#L269-L273
    const token = cmdBuf.readUInt32LE(0) + 0x100000000 * cmdBuf.readUInt32LE(4);
    const queryCmdBuf = cmdBuf.slice(12, cmdBuf.length);
    const logFn = this.log.bind(this);
    const validatePromise = this.sessionPromise.then(session => (
      this.queryValidator.validateQueryCmd(token, queryCmdBuf, session, logFn)
    ));
    return validatePromise.then(allow => {
      if (allow) {
        this.dbSocket.write(cmdBuf, 'binary');
      }
      // TODO It might be nice to send a response back to the client of type
      // CLIENT_ERROR. However, we'd need to parse every message back from
      // the db in order to avoid injecting our response in the middle of a
      // large response.
      //
      // Right now, the query will be silently ignored in the frontend and
      // logged in the backend. This might lead to some sort of memory leak
      // in the frontend if it's waiting for responses from queries. This is
      // not very important, because properly written frontends should avoid
      // submitting queries that are denied.
      //
      // Alternatively, we could close the connection after a denied query,
      // as an easier to debug fail-fast option.
    });
  }

  validateClientHandshake(buf) {
    const protocolVersion = buf.readUInt32LE(0);
    if (protocolVersion !== protodef.VersionDummy.Version.V0_4) {
      this.cleanupAndLogErr('Invalid protocolVersion ' + protocolVersion);
      return 0;
    }
    const keyLength = buf.readUInt32LE(4);
    if (keyLength !== 0) {
      this.cleanupAndLogErr('Auth key not supported');
      return 0;
    }
    const protocolType = buf.readUInt32LE(8);
    if (protocolType !== protodef.VersionDummy.Protocol.JSON) {
      this.cleanupAndLogErr('Protocol type not supported ' + protocolType);
      return 0;
    }
    return 12;
  }

  processNextMessage(buf) {
    if (!this.handshakeComplete) {
      if (buf.length >= 12) {
        const clientHandshakeLength = this.validateClientHandshake(buf);
        if (clientHandshakeLength > 0) {
          const authKey = this.dbAuthKey || '';
          const outBuf = new Buffer(12 + authKey.length);
          outBuf.writeUInt32LE(protodef.VersionDummy.Version.V0_4, 0);
          outBuf.writeUInt32LE(authKey.length, 4);
          outBuf.write(authKey, 8);
          outBuf.writeUInt32LE(protodef.VersionDummy.Protocol.JSON, 8 + authKey.length);
          this.dbSocket.write(outBuf, 'binary');
          this.handshakeComplete = true;
          return clientHandshakeLength;
        } else {
          return 0;
        }
      }
    } else {
      if (buf.length >= 12) {
        const encodedQueryLength = buf.readUInt32LE(8);
        const queryEndOffset = 12 + encodedQueryLength;
        if (queryEndOffset <= buf.length) {
          const cmdBuf = buf.slice(0, queryEndOffset);
          this.processClientCommand(cmdBuf).catch(e => {
            this.cleanupAndLogErr('Error in processClientCommand', e);
          });
          return queryEndOffset;
        }
      }
    }
    return 0;
  }

  handleWsMessage(msg) {
    const isBase64 = typeof msg === 'string' && this.webSocket.protocol === 'base64';
    const incomingBuffer = isBase64 ? new Buffer(msg, 'base64') : msg;
    this.wsInBuffer = Buffer.concat([this.wsInBuffer, incomingBuffer]);
    let keepGoing = true;
    while (keepGoing) {
      let bytesConsumed = this.processNextMessage(this.wsInBuffer);
      this.wsInBuffer = this.wsInBuffer.slice(bytesConsumed);
      keepGoing = bytesConsumed > 0;
    }
  }
}
<|endoftext|>


<commit_before>/**
 * Created by ionagamed on 8/25/16.
 */

import { Card } from '../../logic/Card';
import { Player } from '../../logic/Player';
import { Fight } from '../../logic/Fight';

import packs from '../../logic/packs';

import item from './item';

import _t from './translate';

var playerCache, tableCache, sellingCache;

/**
 *
 * @param {Player} player
 * @param {Table} table
 * @param {[string]} currentlySelling
 */
export function updateView(player, table, currentlySelling) {
    let updatePlayer = (_player) => {
        let content = `<div class="list-group-item" data-player="${_player.name}">`;
        content += _player.name;
        content += ` | пол: ${_t(_player.sex)}`;
        content += ` | уровень: ${_player.level} | сила: <b>${_player.getAttack()}</b>`;
        content += '

';
        content += '
рука:
';
        _player.hand.map(x => {
            content += item(x, [
                'name',
                'attack',
                'big',
                'price',
                'offer',
                'type',
                'wield',
                'toBelt',
                'use',
                'cast',
                'discard'
            ], player, _player);
        });
        content += '

пояс:
';
        _player.belt.map(x => {
            content += item(x, [
                'name',
                'attack',
                'big',
                'price',
                'offer',
                'type',
                'wield',
                'use',
                'cast',
                'discard'
            ], player, _player);
        });
        content += '

стол:
';
        _player.wielded.map(x => {
            content += item(x, [
                'name',
                'attack',
                'big',
                'price',
                'offer',
                'type',
                'unwield',
                'use',
                'discard'
            ], player, _player);
        });
        content += '

';
        content += `<div class='btn-group'>`;
        if (table.players[table.turn].name == player.name && player.name == _player.name) {
            if (table.phase == 'begin') {
                content += `<button class='btn btn-primary kickDoor'>Пнуть дверь</button>`;
            }
            if (table.phase == 'open' && table.fight == null) {
                content += `<button class='btn btn-primary closeDoor'>Почистить нычки</button>`
            }
            if (table.phase != 'begin') {
                if (table.fight == null) {
                    if ((player.hasClassAdvantages('dwarf') && player.hand.length > 6) || player.hand.length > 5) {
                        content += `<button class='btn btn-primary disabled'>Нельзя закончить ход</button>`
                    } else {
                        content += `<button class='btn btn-primary endTurn'>Закончить ход</button>`;
                    }
                } else {
                    content += `<button class='btn btn-primary disabled'>Нельзя закончить ход</button>`
                }
            }
        }
        content += '

';
        $('#players-list').append(content);
    };
    let updatePlayers = () => {
        $('#players').html('

');
        let idx = -1;
        table.players.map((x, i) => {
            if (x.name == player.name) {
                idx = i;
            }
        });
        if (idx < 0) throw new Exception();
        let passed = 0;
        for (let i = idx; passed
 {
        const fightEl = $('.fight');
        if (!table.fight) {
            fightEl.html('на данный момент все спокойно');
        } else {
            let isFighting = false;
            table.fight.players.map(x => {
                if (x.player.name == player.name) isFighting = true;
            });

            let content = '';
            if (table.fight.players[0].state == 'fighting' && isFighting && table.fight.players[0].player.name == player.name) {
                if (table.fight.getWinningSide() == 'players') {
                    if (+(new Date()) - table.fight.beganAt < 5) {
                        content += ` | осталось ${+(new Date()) - table.fight.beganAt} секунд`;
                    } else {
                        content += ` | <a href='#' class='winFight'>выиграть</a>`;
                    }
                } else {
                    content += ` | <a href='#' class='escape'>смывка</a>`;
                }
                if (table.fight.players[0].player.name == player.name && table.currentlyHelping.length > 0) {
                    content += ` | помощь от: `;
                    let t = [];
                    table.currentlyHelping.map(x => {
                        t.push(`<a class='acceptHelp' data-from='${x}'>${x}</a>`);
                    });
                    content += t.join(', ');
                }
            }
            if (table.fight.players[0].state == 'fighting' && !isFighting) {
                if (table.currentlyHelping.indexOf(player.name) < 0) {
                    content += ` | <a href='#' class='help'>помочь</a>`;
                } else {
                    content += ` | предложена помощь`;
                }
            }
            content += `<ul><li>монстры: | атака: <b>${table.fight.getMonstersAttack(table)}</b>`;
            content += `<ul>`;
            table.fight.monsters.map((x, i) => {
                content += `<li><a class='itemId' data-id='${x.monster}'>${_t(x.monster)}</a> | атака: <b>${Card.byId(x.monster).getAttackAgainst(table.fight.players.map(y => y.player))}</b>`;
                if (table.fight.players[0].state == 'escaping' && isFighting) {
                    content += ` | <button class='m_escape' data-pos='${i}'>смыться</button>`;
                }
                content += `<ul>`;
                x.modifiers.map(y => {
                    content += `<li><a class='itemId' data-id='${y}'>${_t(y)}</a></li>`;
                });
                content += `</ul></li>`;
            });
            content += `</ul></li><li>игроки: | атака: <b>${table.fight.getPlayersAttack()}</b><ul>`;
            table.fight.players.map(x => {
                content += `<li>${x.player.name} | атака: <b>${x.player.getAttack()}</b><ul>`;
                x.modifiers.map(y => {
                    content += `<li><a class='itemId' data-id='${y}'>${_t(y)}</a></li>`;
                });
                content += `</ul></li>`;
            });
            content += '

';
            fightEl.html(content);
        }
    };
    let updateKickedDoor = () => {
        if (table.recentDoor) {
            $('.recentDoor').html(`<a class='itemId' data-id='${table.recentDoor}'>${_t(table.recentDoor)}</a>`);
        }
    };
    let updateDiscard = () => {
        if (table.discardedDoors.length > 0) {
            const id = table.discardedDoors[table.discardedDoors.length - 1];
            $('.discardedDoors').html(`<a class='itemId' data-id='${id}'>${_t(id)}</a>, ...`)
        }
        if (table.discardedTreasure.length > 0) {
            const id = table.discardedTreasure[table.discardedTreasure.length - 1];
            $('.discardedTreasure').html(`<a class='itemId' data-id='${id}'>${_t(id)}</a>, ...`)
        }
    };
    let updateDice = () => {
        const el = $('.dice');
        el.html('');
        for (let i of [].concat(table.diceRolls).reverse().splice(0, 10)) {
            el.append(`<img width='25' height='25' src='/img/dice-${i}.png'>`);
        }
    };
    let updateTable = () => {
        updateDiscard();
        updateKickedDoor();
        updateFight();
        updateDice();
    };

    let updateCastList = () => {
        const wrapper = $('.cast-list-wrapper');
        const list = $('.cast-list');
        list.html('');
        if (wrapper.hasClass('hidden')) return;
        const card = Card.byId(wrapper.data('card'));
        table.players.map(x => {
            if (card.canBeCast(player, x, table)) {
                list.append(`<li><a class='doCast' data-type='player' data-target='${x.name}'>${x.name}</a></li>`);
            }
        });
        if (table.fight) {
            table.fight.monsters.map(x => {
                if (card.canBeCast(player, x.monster, table)) {
                    list.append(`<li><a class='doCast' data-type='monster' data-target='${x.monster}'>${_t(x.monster)}</a></li>`);
                }
            });
        }
    };

    let updateSelling = () => {
        let sum = 0;
        const list = $('.sell-list');
        list.html('');
        currentlySelling.map(x => {
            sum += Card.byId(x).price;
            list.append(`<li><a class='itemId' data-id='${x}'>${_t(x)}</a></li>`);
        });
        $('.price').html(sum);
    };

    let updateOffers = () => {
        const list = $('.offer-list');
        const wrapper = $('.offers-wrapper');
        list.html('');
        if (table.offers) {
            wrapper.removeClass('hidden');
            table.offers.map(x => {
                let content = `${x.from} отдает <a class='itemId' data-id='${x.item}'>${_t(x.item)}</a> в руки ${x.to}`;
                if (x.to == player.name) {
                    content += ` | <a class='acceptOffer'>согласиться</a> | <a class='declineOffer'>отказаться</a>`;
                }
                if (x.from == player.name) {
                    content += ` | <a class='declineOffer'>отменить</a>`;
                }
                list.append(`<li data-from='${x.from}' data-item='${x.item}' data-to='${x.to}'>${content}</li>`);
            });
        } else {
            wrapper.addClass('hidden');
        }
    };
    let updateOffer = () => {
        const list = $('.offer-selector');
        const wrapper = $('.offer-selector-wrapper');
        const id = $('.offer-item .itemId').data('id');
        list.html('');
        if (!wrapper.hasClass('hidden')) {
            table.players.map(x => {
                if (x.name != player.name) {
                    list.append(`<li data-to='${x.name}' data-id='${id}'><a class='makeOfferFinal'>${x.name}</a></li>`);
                }
            });
        }
    };

    const playerString = JSON.stringify(player);
    const tableString = JSON.stringify(table);
    const sellingString = JSON.stringify(currentlySelling);

    if (playerString != playerCache) {
        playerCache = playerString;
        updatePlayers();
    }
    if (tableString != tableCache) {
        tableCache = tableString;
        updateTable();
        updateOffers();
        updateOffer();
        if (playerString == playerCache) {
            updatePlayers();
        }
    }
    updateCastList();
    if (sellingString != sellingCache) {
        sellingCache = sellingString;
        updateSelling();
    }

    $('.current-turn').html(table.players[table.turn].name);
    if (table.players[table.turn].name == player.name && player.dead) {
        Server.resurrect();
    }
}<commit_msg>fix offers<commit_after>/**
 * Created by ionagamed on 8/25/16.
 */

import { Card } from '../../logic/Card';
import { Player } from '../../logic/Player';
import { Fight } from '../../logic/Fight';

import packs from '../../logic/packs';

import item from './item';

import _t from './translate';

var playerCache, tableCache, sellingCache;

/**
 *
 * @param {Player} player
 * @param {Table} table
 * @param {[string]} currentlySelling
 */
export function updateView(player, table, currentlySelling) {
    let updatePlayer = (_player) => {
        let content = `<div class="list-group-item" data-player="${_player.name}">`;
        content += _player.name;
        content += ` | пол: ${_t(_player.sex)}`;
        content += ` | уровень: ${_player.level} | сила: <b>${_player.getAttack()}</b>`;
        content += '

';
        content += '
рука:
';
        _player.hand.map(x => {
            content += item(x, [
                'name',
                'attack',
                'big',
                'price',
                'offer',
                'type',
                'wield',
                'toBelt',
                'use',
                'cast',
                'discard'
            ], player, _player);
        });
        content += '

пояс:
';
        _player.belt.map(x => {
            content += item(x, [
                'name',
                'attack',
                'big',
                'price',
                'offer',
                'type',
                'wield',
                'use',
                'cast',
                'discard'
            ], player, _player);
        });
        content += '

стол:
';
        _player.wielded.map(x => {
            content += item(x, [
                'name',
                'attack',
                'big',
                'price',
                'offer',
                'type',
                'unwield',
                'use',
                'discard'
            ], player, _player);
        });
        content += '

';
        content += `<div class='btn-group'>`;
        if (table.players[table.turn].name == player.name && player.name == _player.name) {
            if (table.phase == 'begin') {
                content += `<button class='btn btn-primary kickDoor'>Пнуть дверь</button>`;
            }
            if (table.phase == 'open' && table.fight == null) {
                content += `<button class='btn btn-primary closeDoor'>Почистить нычки</button>`
            }
            if (table.phase != 'begin') {
                if (table.fight == null) {
                    if ((player.hasClassAdvantages('dwarf') && player.hand.length > 6) || player.hand.length > 5) {
                        content += `<button class='btn btn-primary disabled'>Нельзя закончить ход</button>`
                    } else {
                        content += `<button class='btn btn-primary endTurn'>Закончить ход</button>`;
                    }
                } else {
                    content += `<button class='btn btn-primary disabled'>Нельзя закончить ход</button>`
                }
            }
        }
        content += '

';
        $('#players-list').append(content);
    };
    let updatePlayers = () => {
        $('#players').html('

');
        let idx = -1;
        table.players.map((x, i) => {
            if (x.name == player.name) {
                idx = i;
            }
        });
        if (idx < 0) throw new Exception();
        let passed = 0;
        for (let i = idx; passed
 {
        const fightEl = $('.fight');
        if (!table.fight) {
            fightEl.html('на данный момент все спокойно');
        } else {
            let isFighting = false;
            table.fight.players.map(x => {
                if (x.player.name == player.name) isFighting = true;
            });

            let content = '';
            if (table.fight.players[0].state == 'fighting' && isFighting && table.fight.players[0].player.name == player.name) {
                if (table.fight.getWinningSide() == 'players') {
                    if (+(new Date()) - table.fight.beganAt < 5) {
                        content += ` | осталось ${+(new Date()) - table.fight.beganAt} секунд`;
                    } else {
                        content += ` | <a href='#' class='winFight'>выиграть</a>`;
                    }
                } else {
                    content += ` | <a href='#' class='escape'>смывка</a>`;
                }
                if (table.fight.players[0].player.name == player.name && table.currentlyHelping.length > 0) {
                    content += ` | помощь от: `;
                    let t = [];
                    table.currentlyHelping.map(x => {
                        t.push(`<a class='acceptHelp' data-from='${x}'>${x}</a>`);
                    });
                    content += t.join(', ');
                }
            }
            if (table.fight.players[0].state == 'fighting' && !isFighting) {
                if (table.currentlyHelping.indexOf(player.name) < 0) {
                    content += ` | <a href='#' class='help'>помочь</a>`;
                } else {
                    content += ` | предложена помощь`;
                }
            }
            content += `<ul><li>монстры: | атака: <b>${table.fight.getMonstersAttack(table)}</b>`;
            content += `<ul>`;
            table.fight.monsters.map((x, i) => {
                content += `<li><a class='itemId' data-id='${x.monster}'>${_t(x.monster)}</a> | атака: <b>${Card.byId(x.monster).getAttackAgainst(table.fight.players.map(y => y.player))}</b>`;
                if (table.fight.players[0].state == 'escaping' && isFighting) {
                    content += ` | <button class='m_escape' data-pos='${i}'>смыться</button>`;
                }
                content += `<ul>`;
                x.modifiers.map(y => {
                    content += `<li><a class='itemId' data-id='${y}'>${_t(y)}</a></li>`;
                });
                content += `</ul></li>`;
            });
            content += `</ul></li><li>игроки: | атака: <b>${table.fight.getPlayersAttack()}</b><ul>`;
            table.fight.players.map(x => {
                content += `<li>${x.player.name} | атака: <b>${x.player.getAttack()}</b><ul>`;
                x.modifiers.map(y => {
                    content += `<li><a class='itemId' data-id='${y}'>${_t(y)}</a></li>`;
                });
                content += `</ul></li>`;
            });
            content += '

';
            fightEl.html(content);
        }
    };
    let updateKickedDoor = () => {
        if (table.recentDoor) {
            $('.recentDoor').html(`<a class='itemId' data-id='${table.recentDoor}'>${_t(table.recentDoor)}</a>`);
        }
    };
    let updateDiscard = () => {
        if (table.discardedDoors.length > 0) {
            const id = table.discardedDoors[table.discardedDoors.length - 1];
            $('.discardedDoors').html(`<a class='itemId' data-id='${id}'>${_t(id)}</a>, ...`)
        }
        if (table.discardedTreasure.length > 0) {
            const id = table.discardedTreasure[table.discardedTreasure.length - 1];
            $('.discardedTreasure').html(`<a class='itemId' data-id='${id}'>${_t(id)}</a>, ...`)
        }
    };
    let updateDice = () => {
        const el = $('.dice');
        el.html('');
        for (let i of [].concat(table.diceRolls).reverse().splice(0, 10)) {
            el.append(`<img width='25' height='25' src='/img/dice-${i}.png'>`);
        }
    };
    let updateTable = () => {
        updateDiscard();
        updateKickedDoor();
        updateFight();
        updateDice();
    };

    let updateCastList = () => {
        const wrapper = $('.cast-list-wrapper');
        const list = $('.cast-list');
        list.html('');
        if (wrapper.hasClass('hidden')) return;
        const card = Card.byId(wrapper.data('card'));
        table.players.map(x => {
            if (card.canBeCast(player, x, table)) {
                list.append(`<li><a class='doCast' data-type='player' data-target='${x.name}'>${x.name}</a></li>`);
            }
        });
        if (table.fight) {
            table.fight.monsters.map(x => {
                if (card.canBeCast(player, x.monster, table)) {
                    list.append(`<li><a class='doCast' data-type='monster' data-target='${x.monster}'>${_t(x.monster)}</a></li>`);
                }
            });
        }
    };

    let updateSelling = () => {
        let sum = 0;
        const list = $('.sell-list');
        list.html('');
        currentlySelling.map(x => {
            sum += Card.byId(x).price;
            list.append(`<li><a class='itemId' data-id='${x}'>${_t(x)}</a></li>`);
        });
        $('.price').html(sum);
    };

    let updateOffers = () => {
        const list = $('.offer-list');
        const wrapper = $('.offers-wrapper');
        list.html('');
        if (table.offers) {
            wrapper.removeClass('hidden');
            table.offers.map(x => {
                let content = `${x.from} отдает <a class='itemId' data-id='${x.item}'>${_t(x.item)}</a> в руки ${x.to}`;
                if (x.to == player.name) {
                    content += ` | <a class='acceptOffer'>согласиться</a> | <a class='declineOffer'>отказаться</a>`;
                }
                if (x.from == player.name) {
                    content += ` | <a class='declineOffer'>отменить</a>`;
                }
                list.append(`<li data-from='${x.from}' data-item='${x.item}' data-to='${x.to}'>${content}</li>`);
            });
        } else {
            wrapper.addClass('hidden');
        }
    };
    let updateOffer = () => {
        const list = $('.offer-selector');
        const wrapper = $('.offer-selector-wrapper');
        const id = $('.offer-item .itemId').data('id');
        list.html('');
        if (!wrapper.hasClass('hidden')) {
            table.players.map(x => {
                if (x.name != player.name) {
                    list.append(`<li data-to='${x.name}' data-id='${id}'><a class='makeOfferFinal'>${x.name}</a></li>`);
                }
            });
        }
    };

    const playerString = JSON.stringify(player);
    const tableString = JSON.stringify(table);
    const sellingString = JSON.stringify(currentlySelling);

    if (playerString != playerCache) {
        playerCache = playerString;
        updatePlayers();
    }
    if (tableString != tableCache) {
        tableCache = tableString;
        updateTable();
        if (playerString == playerCache) {
            updatePlayers();
        }
    }
    updateCastList();
    updateOffers();
    updateOffer();
    if (sellingString != sellingCache) {
        sellingCache = sellingString;
        updateSelling();
    }

    $('.current-turn').html(table.players[table.turn].name);
    if (table.players[table.turn].name == player.name && player.dead) {
        Server.resurrect();
    }
}<|endoftext|>


<commit_before>const {join} = require('path');
const fs = require('fs-extra');
const {existsSync, readFileSync} = require('fs-extra');
const proc = require('./proc');

class CordovaCli {

  constructor(cwd) {
    this._cwd = cwd;
    this._resolveCordovaPath();
  }

  _resolveCordovaPath() {
    let binPath = proc.exec('npm', ['bin'], {cwd: __dirname, stdio: 'pipe'});
    let dir = binPath.stdout.toString().trim();
    this._cordova = join(dir, 'cordova');
  }

  platformAddSafe(platform, spec, {options = []} = {}) {
    let opts = options.filter(truthy).map(option => '--' + option);
    let args = ['platform', 'add', spec, ...opts].filter(truthy);
    if (this._platformDeclared(platform)) {
      return;
    }
    if (this._platformDirectoryExists(platform)) {
      fs.removeSync(join(this._cwd, 'platforms', platform));
    }
    proc.exec(this._cordova, args, {cwd: `${this._cwd}`});
    return this;
  }

  platformCommand(command, platform, {options = [], platformOpts = []} = {}) {
    let opts = options.filter(truthy).map(option => '--' + option);
    let parameters = platformOpts.length && ['--', ...platformOpts] || [];
    proc.exec(this._cordova, [command, platform, ...opts, ...parameters].filter(truthy), {cwd: this._cwd});
    return this;
  }

  _platformDirectoryExists(platform) {
    let platformDirectory = join(this._cwd, 'platforms', platform);
    return existsSync(platformDirectory);
  }

  _platformDeclared(name) {
    let platformsJsonPath = join(this._cwd, 'platforms', 'platforms.json');
    if (!existsSync(platformsJsonPath)) {
      return false;
    }
    let platforms = JSON.parse(readFileSync(platformsJsonPath, 'utf8'));
    return !!platforms[name];
  }

}

function truthy(value) {
  return !!value;
}

module.exports = CordovaCli;
<commit_msg>Tidy-up option/platform args handling of Cordova CLI<commit_after>const {join} = require('path');
const fs = require('fs-extra');
const {existsSync, readFileSync} = require('fs-extra');
const proc = require('./proc');

class CordovaCli {

  constructor(cwd) {
    this._cwd = cwd;
    this._resolveCordovaPath();
  }

  _resolveCordovaPath() {
    let binPath = proc.exec('npm', ['bin'], {cwd: __dirname, stdio: 'pipe'});
    let dir = binPath.stdout.toString().trim();
    this._cordovaPath = join(dir, 'cordova');
  }

  platformAddSafe(platform, spec, {options = []} = {}) {
    if (this._platformDeclared(platform)) {
      return;
    }
    if (this._platformDirectoryExists(platform)) {
      fs.removeSync(join(this._cwd, 'platforms', platform));
    }
    this._execCordova(['platform', 'add', spec], options);
    return this;
  }

  platformCommand(command, platform, {options = [], platformOpts = []} = {}) {
    this._execCordova([command, platform], options, platformOpts);
    return this;
  }

  _platformDirectoryExists(platform) {
    let platformDirectory = join(this._cwd, 'platforms', platform);
    return existsSync(platformDirectory);
  }

  _platformDeclared(name) {
    let platformsJsonPath = join(this._cwd, 'platforms', 'platforms.json');
    if (!existsSync(platformsJsonPath)) {
      return false;
    }
    let platforms = JSON.parse(readFileSync(platformsJsonPath, 'utf8'));
    return !!platforms[name];
  }

  _execCordova(args, opts = [], platformOpts = []) {
    let nonOptionArgs = platformOpts.length ? ['--', ...platformOpts] : [];
    let options = opts.filter(truthy).map(opt => `--${opt}`);
    proc.exec(this._cordovaPath, [...args, ...options, ...nonOptionArgs], {cwd: this._cwd});
  }

}

function truthy(value) {
  return !!value;
}

module.exports = CordovaCli;
<|endoftext|>


<commit_before>var moment =		require('moment');
var bluebird =		require('bluebird');
var escapes =		require('ansi-escapes');
var lPad =			require('left-pad');
var chalk =			require('chalk');

module.exports = {

	digits: [
		{
			unit:	'day',
			method:	'date',
			length:	2,
			offset:	0
		}, {
			unit:	'month',
			method:	'month',
			length:	2,
			offset:	1   // because January is 0
		}, {
			unit: 'year',
			method: 'year',
			length: 4,
			offset:	0
		}
	],

	_m:		null,   // moment.js object
	_i:		null,   // stdin
	_o:		null,   // stdout

	_df:	null,   // deferred
	date:	null,   // promise

	_d:		null,   // current index of `this.digits`
	_p:		null,   // temporary typed value
	_k:		null,   // key handler
	_l:		null,   // when a number key was pressed the last time

	init: function (question, options) {
		var self = this;
		options = options || {};

		if (!question || 'string' != typeof question)
			throw new Error('`question` must be a string.');

		this._q = question;
		this._m = options.date ? moment(options.date) : moment();
		this._i = options.stdin || process.stdin;
		this._o = options.stdout || process.stdout;

		// todo: let `DatePrompt` directly inherit from bluebird
		this._df = bluebird.defer();
		this.date = this._df.promise;

		this._d = 0;
		this._p = '';

		var _k = function (key) {
			key = key.toString('utf8');

			if(/\x1b\[+[A-Z]/.test(key)) {
				key = key.charAt(key.length - 1);
				if		(key === 'A')	return self.up();
				else if	(key === 'B')	return self.down();
				else if	(key === 'C')	return self.right();
				else if	(key === 'D')	return self.left();
				else if	(key === 'F')	return self.first();
				else if	(key === 'H')	return self.last();
			}

			else if	(key === '\u0003')	return self.abort();   // ctrl + C
			else if	(key === '\x1b')	return self.abort();   // escape
			else if	(key === '\r')		return self.submit();   // carriage return
			else if	(key === '\n')		return self.submit();   // newline
			else if	(key === '\t')		return self.right();   // tab

			else if	(/[0-9]/.test(key))	return self.number(parseInt(key));   // number key
			else if (key.length > 1)
				for (var i = 0; i < key.length; i++) _k(key[i]);
		};
		this._k = _k;
		this._i.on('data', _k);

		this._i.setRawMode(true);
		this._i.resume();
		this._o.write(escapes.cursorHide);

		this.render();
		return this;
	},

	abort: function () {
		this._df.reject();

		this._i.removeListener('data', this._k);
		this._i.setRawMode(false);
		this._i.pause();

		this._o.write(escapes.eraseLine + '\r' + escapes.cursorShow);
	},

	submit: function () {
		this._df.resolve(this._m);

		this._i.removeListener('data', this._k);
		this._i.setRawMode(false);
		this._i.pause();

		this.render(true);
		this._o.write('\r\n' + escapes.cursorShow);
	},

	// number key handling

	number: function (n) {
		var now = new Date();
		if ((now - this._l) > 1000)   // a lot of time elapsed
			this._p = '' + n;   // reset typed value
		else {
			this._p += n;
			if (this._p.length >= this.digits[this._d].length) {
				var v = parseInt(this._p);
				v -= this.digits[this._d].offset || 0;
				this._m.set(this.digits[this._d].method, v);
				this._p = '';
				if (this._d < this.digits.length - 1) this._d++;
			}
		}
		this._l = now;
		this.render();
	},

	// arrow key handling

	first: function () {
		if (this._d != 0) this._p = '';
		this._d = 0;
		this.render();
	},
	right: function () {
		if (this._d != this.digits.length - 1) this._p = '';
		this._d = this.digits.length - 1;
		this.render();
	},

	left: function () {
		if (this._d > 0) {
			this._d--;
			this._p = '';
		} else this._o.write(escapes.beep);
		this.render();
	},
	right: function () {
		if (this._d < this.digits.length - 1) {
			this._d++;
			this._p = '';
		} else this._o.write(escapes.beep);
		this.render();
	},

	up: function () {
		this._m.add(1, this.digits[this._d].unit);
		this._p = '';
		this.render();
	},
	down: function () {
		this._m.subtract(1, this.digits[this._d].unit);
		this._p = '';
		this.render();
	},

	render: function (formatted) {
		this._o.write(escapes.eraseLine + '\r');
		if (formatted) {

			this._o.write([
				chalk.green('✔'),
				chalk.bold(this._q),
				chalk.cyan(this._m.format('dd DD MMM YYYY'))
			].join(' '));

		} else {

			this._o.write([
				chalk.green('?'),
				chalk.bold(this._q),
				this._m.format('dd')
			].join(' '));
			var digits = [];
			digits.push(lPad(this._m.format('D'), 2, '0'));
			digits.push(this._m.format('MMM'));
			digits.push(this._m.format('YYYY'));
			digits[this._d] = chalk.underline.yellow(digits[this._d]);
			this._o.write(' ' + digits.join(' '));

		}
	},

};
<commit_msg>`src/DatePrompt`: rewritten `render`<commit_after>var moment =		require('moment');
var bluebird =		require('bluebird');
var escapes =		require('ansi-escapes');
var lPad =			require('left-pad');
var chalk =			require('chalk');

module.exports = {

	digits: [
		{
			unit:		'day',
			method:		'day',
			length:		2,
			offset:		1,   // because Sunday is 0
			format:		'ddd'
		}, {
			unit:		'day',
			method:		'date',
			length:		2,
			format:		'DD'
		}, {
			unit:		'month',
			method:		'month',
			length:		2,
			offset:		1,   // because January is 0
			format:		'MMM'
		}, {
			unit:		'year',
			method:		'year',
			length:		4,
			format:		'YYYY'
			separator:	''
		}
	],

	_m:		null,   // moment.js object
	_i:		null,   // stdin
	_o:		null,   // stdout

	_df:	null,   // deferred
	date:	null,   // promise

	_d:		null,   // current index of `this.digits`
	_p:		null,   // temporary typed value
	_k:		null,   // key handler
	_l:		null,   // when a number key was pressed the last time

	init: function (question, options) {
		var self = this;
		options = options || {};

		if (!question || 'string' != typeof question)
			throw new Error('`question` must be a string.');

		this._q = question;
		this._m = options.date ? moment(options.date) : moment();
		this._i = options.stdin || process.stdin;
		this._o = options.stdout || process.stdout;

		// todo: let `DatePrompt` directly inherit from bluebird
		this._df = bluebird.defer();
		this.date = this._df.promise;

		this._d = 0;
		this._p = '';

		var _k = function (key) {
			key = key.toString('utf8');

			if(/\x1b\[+[A-Z]/.test(key)) {
				key = key.charAt(key.length - 1);
				if		(key === 'A')	return self.up();
				else if	(key === 'B')	return self.down();
				else if	(key === 'C')	return self.right();
				else if	(key === 'D')	return self.left();
				else if	(key === 'F')	return self.first();
				else if	(key === 'H')	return self.last();
			}

			else if	(key === '\u0003')	return self.abort();   // ctrl + C
			else if	(key === '\x1b')	return self.abort();   // escape
			else if	(key === '\r')		return self.submit();   // carriage return
			else if	(key === '\n')		return self.submit();   // newline
			else if	(key === '\t')		return self.right();   // tab

			else if	(/[0-9]/.test(key))	return self.number(parseInt(key));   // number key
			else if (key.length > 1)
				for (var i = 0; i < key.length; i++) _k(key[i]);
		};
		this._k = _k;
		this._i.on('data', _k);

		this._i.setRawMode(true);
		this._i.resume();
		this._o.write(escapes.cursorHide);

		this.render();
		return this;
	},

	abort: function () {
		this._df.reject();

		this._i.removeListener('data', this._k);
		this._i.setRawMode(false);
		this._i.pause();

		this._o.write(escapes.eraseLine + '\r' + escapes.cursorShow);
	},

	submit: function () {
		this._df.resolve(this._m);

		this._i.removeListener('data', this._k);
		this._i.setRawMode(false);
		this._i.pause();

		this.render(true);
		this._o.write('\r\n' + escapes.cursorShow);
	},

	// number key handling

	number: function (n) {
		var now = new Date();
		if ((now - this._l) > 1000)   // a lot of time elapsed
			this._p = '' + n;   // reset temporary value
		else {
			this._p += n;
			if (this._p.length >= this.digits[this._d].length) {   // got all digits
				var v = parseInt(this._p);
				v -= this.digits[this._d].offset || 0;
				this._m[this.digits[this._d].method](v);
				this._p = '';
				if (this._d < this.digits.length - 1) this._d++;
			}
		}
		this._l = now;
		this.render();
	},

	// arrow key handling

	first: function () {
		if (this._d != 0) this._p = '';
		this._d = 0;
		this.render();
	},
	right: function () {
		if (this._d != this.digits.length - 1) this._p = '';
		this._d = this.digits.length - 1;
		this.render();
	},

	left: function () {
		if (this._d > 0) {
			this._d--;
			this._p = '';
		} else this._o.write(escapes.beep);
		this.render();
	},
	right: function () {
		if (this._d < this.digits.length - 1) {
			this._d++;
			this._p = '';
		} else this._o.write(escapes.beep);
		this.render();
	},

	up: function () {
		this._m.add(1, this.digits[this._d].unit);
		this._p = '';
		this.render();
	},
	down: function () {
		this._m.subtract(1, this.digits[this._d].unit);
		this._p = '';
		this.render();
	},

	render: function (formatted) {
		this._o.write(escapes.eraseLine + '\r' + chalk.green(formatted ? '✔' : '?') + ' ');

		var i, digit;
		for (i = 0; i < this.digits.length; i++) {
			digit = this.digits[i];

			if (!formatted && i === this._d)
				this._o.write(chalk.cyan.underline(this._m.format(digit.format)));
			else this._o.write(this._m.format(digit.format));

			if ('string' === typeof digit.separator) this._o.write(digit.separator);
			else this._o.write(' ');
		}
	},

};
<|endoftext|>


<commit_before>import React from 'react'
import { findDOMNode } from 'react-dom'
import Component from 'react-class'

import assign from 'object-assign'
import { Flex, Item } from 'react-flex'
import moment from 'moment'

import times from './utils/times'
import toMoment from './toMoment'
import join from './join'
import bemFactory from './bemFactory'

const bem = bemFactory('react-date-picker__decade-view')

const ARROWS = {
  prev: <svg height="24" viewBox="0 0 24 24" width="24">
    <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
    <path d="M0 0h24v24H0z" fill="none" />
  </svg>,

  next: <svg height="24" viewBox="0 0 24 24" width="24">
    <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
    <path d="M0 0h24v24H0z" fill="none" />
  </svg>
}

import ON_KEY_DOWN from './MonthView/onKeyDown'

const getDecadeStartYear = (mom) => {
  const year = mom.get('year')

  return year - year % 10
}

const getDecadeEndYear = (mom) => {
  return getDecadeStartYear(mom) + 9
}

const NAV_KEYS = {
  ArrowUp(mom) {
    return mom.add(-5, 'year')
  },
  ArrowDown(mom) {
    return mom.add(5, 'year')
  },
  ArrowLeft(mom) {
    return mom.add(-1, 'year')
  },
  ArrowRight(mom) {
    return mom.add(1, 'year')
  },
  Home(mom) {
    return mom.set('year', getDecadeStartYear(mom))
  },
  End(mom) {
    return mom.set('year', getDecadeEndYear(mom))
  },
  PageUp(mom) {
    return mom.add(-10, 'year')
  },
  PageDown(mom) {
    return mom.add(10, 'year')
  }
}

const isDateInMinMax = (timestamp, props) => {
  if (props.minDate && timestamp < props.minDate) {
    return false
  }

  if (props.maxDate && timestamp > props.maxDate) {
    return false
  }

  return true
}

const isValidActiveDate = (timestamp, props) => {
  if (!props) {
    throw new Error('props is mandatory in isValidActiveDate')
  }

  return isDateInMinMax(timestamp, props)
}

const select = function ({ dateMoment, timestamp }, event) {
  if (this.props.select) {
    return this.props.select({ dateMoment, timestamp }, event)
  }

  if (!timestamp) {
    timestamp = +dateMoment
  }

  this.gotoViewDate({ dateMoment, timestamp })
  this.onChange({ dateMoment, timestamp }, event)

  return undefined
}

const confirm = function (date, event) {
  event.preventDefault()

  if (this.props.confirm) {
    return this.props.confirm(date, event)
  }

  const dateMoment = this.toMoment(date)
  const timestamp = +dateMoment

  this.select({ dateMoment, timestamp }, event)

  if (this.props.onConfirm) {
    this.props.onConfirm({ dateMoment, timestamp })
  }

  return undefined
}

const onActiveDateChange = function ({ dateMoment, timestamp }) {
  if (!isValidActiveDate(timestamp, this.p)) {
    return
  }

  if (this.props.activeDate === undefined) {
    this.setState({
      activeDate: timestamp
    })
  }

  if (this.props.onActiveDateChange) {
    const dateString = this.format(dateMoment)
    this.props.onActiveDateChange(dateString, { dateMoment, timestamp, dateString })
  }
}

const onViewDateChange = function ({ dateMoment, timestamp }) {
  if (dateMoment && timestamp === undefined) {
    timestamp = +dateMoment
  }

  if (this.props.constrainViewDate && !isDateInMinMax(timestamp, this.p)) {
    return
  }

  if (this.props.viewDate === undefined) {
    this.setState({
      viewDate: timestamp
    })
  }

  if (this.props.onViewDateChange) {
    const dateString = this.format(dateMoment)
    this.props.onViewDateChange(dateString, { dateMoment, dateString, timestamp })
  }
}

const onChange = function ({ dateMoment, timestamp }, event) {
  if (this.props.date === undefined) {
    this.setState({
      date: timestamp
    })
  }

  if (this.props.onChange) {
    const dateString = this.format(dateMoment)
    this.props.onChange(dateString, { dateMoment, timestamp, dateString }, event)
  }
}

const navigate = function (direction, event) {
  const props = this.p

  const getNavigationDate = (dir, date, dateFormat) => {
    const mom = moment.isMoment(date) ? date : this.toMoment(date, dateFormat)

    if (typeof dir == 'function') {
      return dir(mom)
    }

    return mom
  }

  if (props.navigate) {
    return props.navigate(direction, event, getNavigationDate)
  }

  event.preventDefault()

  if (props.activeDate) {
    const nextMoment = getNavigationDate(direction, props.activeDate)

    this.gotoViewDate({ dateMoment: nextMoment })
  }

  return undefined
}

const gotoViewDate = function ({ dateMoment, timestamp }) {
  if (!timestamp) {
    timestamp = dateMoment == null ? null : +dateMoment
  }

  this.onViewDateChange({ dateMoment, timestamp })
  this.onActiveDateChange({ dateMoment, timestamp })
}

const prepareDate = function (props, state) {
  return props.date === undefined ?
          state.date :
          props.date
}

const prepareViewDate = function (props, state) {
  const viewDate = props.viewDate === undefined ?
        state.viewDate :
        props.viewDate

  if (!viewDate && props.date) {
    return props.date
  }

  return viewDate
}

const prepareActiveDate = function (props, state) {
  const activeDate = props.activeDate === undefined ?
    state.activeDate || prepareDate(props, state) :
    props.activeDate

  return activeDate
}

const prepareMinMax = function (props) {
  const { minDate, maxDate } = props

  const result = {}

  if (minDate != null) {
    result.minDateMoment = toMoment(
      props.minDate,
      props
    ).startOf(props.adjustMinDateStartOf)

    result.minDate = +result.minDateMoment
  }

  if (maxDate != null) {
    result.maxDateMoment = toMoment(
      props.maxDate,
      props
    ).endOf(props.adjustMaxDateStartOf)

    result.maxDate = +result.maxDateMoment
  }

  return result
}

const prepareDateProps = function (props, state) {
  const result = {}

  assign(result, prepareMinMax(props))

  result.date = prepareDate(props, state)
  result.viewDate = prepareViewDate(props, state)

  const activeDate = prepareActiveDate(props, state)

  if (result.date != null) {
    result.moment = toMoment(result.date, props)
    if (props.adjustDateStartOf) {
      result.moment.startOf(props.adjustDateStartOf)
    }
    result.timestamp = +result.moment
  }

  if (activeDate) {
    result.activeMoment = toMoment(activeDate, props)
    if (props.adjustDateStartOf) {
      result.activeMoment.startOf(props.adjustDateStartOf)
    }
    result.activeDate = +result.activeMoment
  }

  let viewMoment = toMoment(result.viewDate, props)

  if (props.constrainViewDate &&
    result.minDate != null &&
    viewMoment.isBefore(result.minDate)
  ) {
    result.minConstrained = true
    viewMoment = toMoment(result.minDate, props)
  }

  if (props.constrainViewDate &&
    result.maxDate != null &&
    viewMoment.isAfter(result.maxDate)
  ) {
    result.maxConstrained = true
    viewMoment = toMoment(result.maxDate, props)
  }

  if (props.adjustDateStartOf) {
    viewMoment.startOf(props.adjustDateStartOf)
  }

  result.viewMoment = viewMoment

  return result
}

const getInitialState = (props) => {
  return {
    date: props.defaultDate,
    activeDate: props.defaultActiveDate,
    viewDate: props.defaultViewDate
  }
}

export default class DecadeView extends Component {

  constructor(props) {
    super(props)

    this.state = getInitialState(props)
  }

  getYearsInDecade(value) {
    const year = getDecadeStartYear(this.toMoment(value))

    const start = this.toMoment(`${year}`, 'YYYY').startOf('year')

    return times(10).map(i => {
      return this.toMoment(start).add(i, 'year')
    })
  }

  toMoment(date, format) {
    return toMoment(date, format, this.props)
  }

  render() {
    const props = this.p = assign({}, this.props)

    if (props.onlyCompareYear) {
      // props.adjustDateStartOf = null
    }

    const dateProps = prepareDateProps(props, this.state)

    assign(props, dateProps)

    const yearsInView = this.getYearsInDecade(props.viewMoment)

    const className = join(
      props.className,
      bem(),
      props.theme && bem(null, `theme-${props.theme}`)
    )

    let children = this.renderYears(props, yearsInView)
    let align = 'stretch'
    let column = true

    if (props.navigation) {
      column = false
      align = 'center'

      children = [
        this.renderNav(-1),
        <Flex inline flex column alignItems="stretch" children={children} />,
        this.renderNav(1)
      ]
    }

    const flexProps = assign({}, this.props)

    delete flexProps.activeDate
    delete flexProps.adjustDateStartOf
    delete flexProps.adjustMaxDateStartOf
    delete flexProps.adjustMinDateStartOf
    delete flexProps.arrows
    delete flexProps.cleanup
    delete flexProps.constrainViewDate
    delete flexProps.date
    delete flexProps.dateFormat
    delete flexProps.isDecadeView
    delete flexProps.maxDate
    delete flexProps.minDate
    delete flexProps.navigation
    delete flexProps.navKeys
    delete flexProps.onActiveDateChange
    delete flexProps.onConfirm
    delete flexProps.onlyCompareYear
    delete flexProps.onViewDateChange
    delete flexProps.perRow
    delete flexProps.theme
    delete flexProps.viewDate
    delete flexProps.yearFormat

    if (typeof props.cleanup == 'function') {
      props.cleanup(flexProps)
    }

    return <Flex
      inline
      column={column}
      alignItems={align}
      tabIndex={0}
      {...flexProps}
      onKeyDown={this.onKeyDown}
      className={className}
      children={children}
    />
  }

  renderNav(dir) {
    const props = this.p

    const name = dir == -1 ? 'prev' : 'next'
    const navMoment = this.toMoment(props.viewMoment).add(dir * 10, 'year')

    const disabled = dir == -1 ?
      props.minDateMoment && getDecadeEndYear(navMoment) < getDecadeEndYear(props.minDateMoment) :
      props.maxDateMoment && getDecadeEndYear(navMoment) > getDecadeEndYear(props.maxDateMoment)

    const className = join(
      bem('arrow'),
      bem(`arrow--${name}`),
      disabled && bem('arrow--disabled')
    )

    const arrow = props.arrows[name] || ARROWS[name]

    const arrowProps = {
      className,
      onClick: !disabled ? () => this.onViewDateChange({ dateMoment: navMoment }) : null,
      children: arrow,
      disabled
    }

    if (props.renderNavigation) {
      return props.renderNavigation(arrowProps, props)
    }

    return <div {...arrowProps} />
  }

  renderYears(props, years) {
    const nodes = years.map(this.renderYear)

    const perRow = props.perRow
    const buckets = times(Math.ceil(nodes.length / perRow)).map(i => {
      return nodes.slice(i * perRow, (i + 1) * perRow)
    })

    return buckets.map((bucket, i) => <Flex
      alignItems="center"
      flex
      row
      inline
      key={`row_${i}`}
      className="dp-row"
    >
      {bucket}
    </Flex>)
  }

  renderYear(dateMoment) {
    const props = this.p
    const yearText = this.format(dateMoment)

    const timestamp = +dateMoment

    const isActiveDate = props.onlyCompareYear && props.activeMoment ?
      dateMoment.get('year') == props.activeMoment.get('year') :
      timestamp === props.activeDate

    const isValue = props.onlyCompareYear && props.moment ?
      dateMoment.get('year') == props.moment.get('year') :
      timestamp === props.timestamp

    const className = join(
      bem('year'),
      isActiveDate && bem('year', 'active'),
      isValue && bem('year', 'value'),
      props.minDate != null && timestamp < props.minDate && bem('year', 'disabled'),
      props.maxDate != null && timestamp > props.maxDate && bem('year', 'disabled')
    )

    const onClick = this.handleClick.bind(this, {
      dateMoment,
      timestamp
    })

    return <Item
      key={yearText}
      className={className}
      onClick={onClick}
    >
      {yearText}
    </Item>
  }

  format(mom, format) {
    format = format || this.props.yearFormat

    return mom.format(format)
  }

  handleClick({ timestamp, dateMoment }, event) {
    event.target.value = timestamp

    const props = this.p
    if (props.minDate && timestamp < props.minDate) {
      return
    }

    if (props.maxDate && timestamp > props.maxDate) {
      return
    }

    this.select({ dateMoment, timestamp }, event)
  }

  onKeyDown(event) {
    return ON_KEY_DOWN.call(this, event)
  }

  confirm(date, event) {
    return confirm.call(this, date, event)
  }

  navigate(direction, event) {
    return navigate.call(this, direction, event)
  }

  select({ dateMoment, timestamp }, event) {
    return select.call(this, { dateMoment, timestamp }, event)
  }

  onViewDateChange({ dateMoment, timestamp }) {
    return onViewDateChange.call(this, { dateMoment, timestamp })
  }

  gotoViewDate({ dateMoment, timestamp }) {
    return gotoViewDate.call(this, { dateMoment, timestamp })
  }

  onActiveDateChange({ dateMoment, timestamp }) {
    return onActiveDateChange.call(this, { dateMoment, timestamp })
  }

  onChange({ dateMoment, timestamp }, event) {
    return onChange.call(this, { dateMoment, timestamp }, event)
  }

  focus() {
    findDOMNode(this).focus()
  }
}

DecadeView.defaultProps = {
  isDecadeView: true,
  arrows: {},
  navigation: true,
  constrainViewDate: true,
  navKeys: NAV_KEYS,
  theme: 'default',
  yearFormat: 'YYYY',
  dateFormat: 'YYYY-MM-DD',
  perRow: 5,

  onlyCompareYear: true,

  adjustDateStartOf: 'year',
  adjustMinDateStartOf: 'year',
  adjustMaxDateStartOf: 'year'
}

export {
  onChange,
  onViewDateChange,
  onActiveDateChange,
  select,
  confirm,
  gotoViewDate,
  navigate,

  ON_KEY_DOWN as onKeyDown,

  prepareActiveDate,
  prepareViewDate,
  prepareMinMax,
  prepareDateProps,
  prepareDate,

  isDateInMinMax,
  isValidActiveDate,

  getInitialState
}
<commit_msg>[hardcode] output of three years<commit_after>import React from 'react'
import { findDOMNode } from 'react-dom'
import Component from 'react-class'

import assign from 'object-assign'
import { Flex, Item } from 'react-flex'
import moment from 'moment'

import times from './utils/times'
import toMoment from './toMoment'
import join from './join'
import bemFactory from './bemFactory'

const bem = bemFactory('react-date-picker__decade-view')

const ARROWS = {
  prev: <svg height="24" viewBox="0 0 24 24" width="24">
    <path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z" />
    <path d="M0 0h24v24H0z" fill="none" />
  </svg>,

  next: <svg height="24" viewBox="0 0 24 24" width="24">
    <path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z" />
    <path d="M0 0h24v24H0z" fill="none" />
  </svg>
}

import ON_KEY_DOWN from './MonthView/onKeyDown'

const getDecadeStartYear = (mom) => {
  const year = mom.get('year')

  return year - year % 10
}

const getDecadeEndYear = (mom) => {
  return getDecadeStartYear(mom) + 9
}

const NAV_KEYS = {
  ArrowUp(mom) {
    return mom.add(-5, 'year')
  },
  ArrowDown(mom) {
    return mom.add(5, 'year')
  },
  ArrowLeft(mom) {
    return mom.add(-1, 'year')
  },
  ArrowRight(mom) {
    return mom.add(1, 'year')
  },
  Home(mom) {
    return mom.set('year', getDecadeStartYear(mom))
  },
  End(mom) {
    return mom.set('year', getDecadeEndYear(mom))
  },
  PageUp(mom) {
    return mom.add(-10, 'year')
  },
  PageDown(mom) {
    return mom.add(10, 'year')
  }
}

const isDateInMinMax = (timestamp, props) => {
  if (props.minDate && timestamp < props.minDate) {
    return false
  }

  if (props.maxDate && timestamp > props.maxDate) {
    return false
  }

  return true
}

const isValidActiveDate = (timestamp, props) => {
  if (!props) {
    throw new Error('props is mandatory in isValidActiveDate')
  }

  return isDateInMinMax(timestamp, props)
}

const select = function ({ dateMoment, timestamp }, event) {
  if (this.props.select) {
    return this.props.select({ dateMoment, timestamp }, event)
  }

  if (!timestamp) {
    timestamp = +dateMoment
  }

  this.gotoViewDate({ dateMoment, timestamp })
  this.onChange({ dateMoment, timestamp }, event)

  return undefined
}

const confirm = function (date, event) {
  event.preventDefault()

  if (this.props.confirm) {
    return this.props.confirm(date, event)
  }

  const dateMoment = this.toMoment(date)
  const timestamp = +dateMoment

  this.select({ dateMoment, timestamp }, event)

  if (this.props.onConfirm) {
    this.props.onConfirm({ dateMoment, timestamp })
  }

  return undefined
}

const onActiveDateChange = function ({ dateMoment, timestamp }) {
  if (!isValidActiveDate(timestamp, this.p)) {
    return
  }

  if (this.props.activeDate === undefined) {
    this.setState({
      activeDate: timestamp
    })
  }

  if (this.props.onActiveDateChange) {
    const dateString = this.format(dateMoment)
    this.props.onActiveDateChange(dateString, { dateMoment, timestamp, dateString })
  }
}

const onViewDateChange = function ({ dateMoment, timestamp }) {
  if (dateMoment && timestamp === undefined) {
    timestamp = +dateMoment
  }

  if (this.props.constrainViewDate && !isDateInMinMax(timestamp, this.p)) {
    return
  }

  if (this.props.viewDate === undefined) {
    this.setState({
      viewDate: timestamp
    })
  }

  if (this.props.onViewDateChange) {
    const dateString = this.format(dateMoment)
    this.props.onViewDateChange(dateString, { dateMoment, dateString, timestamp })
  }
}

const onChange = function ({ dateMoment, timestamp }, event) {
  if (this.props.date === undefined) {
    this.setState({
      date: timestamp
    })
  }

  if (this.props.onChange) {
    const dateString = this.format(dateMoment)
    this.props.onChange(dateString, { dateMoment, timestamp, dateString }, event)
  }
}

const navigate = function (direction, event) {
  const props = this.p

  const getNavigationDate = (dir, date, dateFormat) => {
    const mom = moment.isMoment(date) ? date : this.toMoment(date, dateFormat)

    if (typeof dir == 'function') {
      return dir(mom)
    }

    return mom
  }

  if (props.navigate) {
    return props.navigate(direction, event, getNavigationDate)
  }

  event.preventDefault()

  if (props.activeDate) {
    const nextMoment = getNavigationDate(direction, props.activeDate)

    this.gotoViewDate({ dateMoment: nextMoment })
  }

  return undefined
}

const gotoViewDate = function ({ dateMoment, timestamp }) {
  if (!timestamp) {
    timestamp = dateMoment == null ? null : +dateMoment
  }

  this.onViewDateChange({ dateMoment, timestamp })
  this.onActiveDateChange({ dateMoment, timestamp })
}

const prepareDate = function (props, state) {
  return props.date === undefined ?
          state.date :
          props.date
}

const prepareViewDate = function (props, state) {
  const viewDate = props.viewDate === undefined ?
        state.viewDate :
        props.viewDate

  if (!viewDate && props.date) {
    return props.date
  }

  return viewDate
}

const prepareActiveDate = function (props, state) {
  const activeDate = props.activeDate === undefined ?
    state.activeDate || prepareDate(props, state) :
    props.activeDate

  return activeDate
}

const prepareMinMax = function (props) {
  const { minDate, maxDate } = props

  const result = {}

  if (minDate != null) {
    result.minDateMoment = toMoment(
      props.minDate,
      props
    ).startOf(props.adjustMinDateStartOf)

    result.minDate = +result.minDateMoment
  }

  if (maxDate != null) {
    result.maxDateMoment = toMoment(
      props.maxDate,
      props
    ).endOf(props.adjustMaxDateStartOf)

    result.maxDate = +result.maxDateMoment
  }

  return result
}

const prepareDateProps = function (props, state) {
  const result = {}

  assign(result, prepareMinMax(props))

  result.date = prepareDate(props, state)
  result.viewDate = prepareViewDate(props, state)

  const activeDate = prepareActiveDate(props, state)

  if (result.date != null) {
    result.moment = toMoment(result.date, props)
    if (props.adjustDateStartOf) {
      result.moment.startOf(props.adjustDateStartOf)
    }
    result.timestamp = +result.moment
  }

  if (activeDate) {
    result.activeMoment = toMoment(activeDate, props)
    if (props.adjustDateStartOf) {
      result.activeMoment.startOf(props.adjustDateStartOf)
    }
    result.activeDate = +result.activeMoment
  }

  let viewMoment = toMoment(result.viewDate, props)

  if (props.constrainViewDate &&
    result.minDate != null &&
    viewMoment.isBefore(result.minDate)
  ) {
    result.minConstrained = true
    viewMoment = toMoment(result.minDate, props)
  }

  if (props.constrainViewDate &&
    result.maxDate != null &&
    viewMoment.isAfter(result.maxDate)
  ) {
    result.maxConstrained = true
    viewMoment = toMoment(result.maxDate, props)
  }

  if (props.adjustDateStartOf) {
    viewMoment.startOf(props.adjustDateStartOf)
  }

  result.viewMoment = viewMoment

  return result
}

const getInitialState = (props) => {
  return {
    date: props.defaultDate,
    activeDate: props.defaultActiveDate,
    viewDate: props.defaultViewDate
  }
}

export default class DecadeView extends Component {

  constructor(props) {
    super(props)

    this.state = getInitialState(props)
  }

  getYearsInDecade(value) {
    return [this.toMoment().startOf('year'), this.toMoment().add(1, 'years').startOf('year'), this.toMoment().add(2, 'years').startOf('year')]
  }

  toMoment(date, format) {
    return toMoment(date, format, this.props)
  }

  render() {
    const props = this.p = assign({}, this.props)

    if (props.onlyCompareYear) {
      // props.adjustDateStartOf = null
    }

    const dateProps = prepareDateProps(props, this.state)

    assign(props, dateProps)

    const yearsInView = this.getYearsInDecade(props.viewMoment)

    const className = join(
      props.className,
      bem(),
      props.theme && bem(null, `theme-${props.theme}`)
    )

    let children = this.renderYears(props, yearsInView)
    let align = 'stretch'
    let column = true

    if (props.navigation) {
      column = false
      align = 'center'

      children = [
        this.renderNav(-1),
        <Flex inline flex column alignItems="stretch" children={children} />,
        this.renderNav(1)
      ]
    }

    const flexProps = assign({}, this.props)

    delete flexProps.activeDate
    delete flexProps.adjustDateStartOf
    delete flexProps.adjustMaxDateStartOf
    delete flexProps.adjustMinDateStartOf
    delete flexProps.arrows
    delete flexProps.cleanup
    delete flexProps.constrainViewDate
    delete flexProps.date
    delete flexProps.dateFormat
    delete flexProps.isDecadeView
    delete flexProps.maxDate
    delete flexProps.minDate
    delete flexProps.navigation
    delete flexProps.navKeys
    delete flexProps.onActiveDateChange
    delete flexProps.onConfirm
    delete flexProps.onlyCompareYear
    delete flexProps.onViewDateChange
    delete flexProps.perRow
    delete flexProps.theme
    delete flexProps.viewDate
    delete flexProps.yearFormat

    if (typeof props.cleanup == 'function') {
      props.cleanup(flexProps)
    }

    return <Flex
      inline
      column={column}
      alignItems={align}
      tabIndex={0}
      {...flexProps}
      onKeyDown={this.onKeyDown}
      className={className}
      children={children}
    />
  }

  renderNav(dir) {
    const props = this.p

    const name = dir == -1 ? 'prev' : 'next'
    const navMoment = this.toMoment(props.viewMoment).add(dir * 10, 'year')

    const disabled = dir == -1 ?
      props.minDateMoment && getDecadeEndYear(navMoment) < getDecadeEndYear(props.minDateMoment) :
      props.maxDateMoment && getDecadeEndYear(navMoment) > getDecadeEndYear(props.maxDateMoment)

    const className = join(
      bem('arrow'),
      bem(`arrow--${name}`),
      disabled && bem('arrow--disabled')
    )

    const arrow = props.arrows[name] || ARROWS[name]

    const arrowProps = {
      className,
      onClick: !disabled ? () => this.onViewDateChange({ dateMoment: navMoment }) : null,
      children: arrow,
      disabled
    }

    if (props.renderNavigation) {
      return props.renderNavigation(arrowProps, props)
    }

    return <div {...arrowProps} />
  }

  renderYears(props, years) {
    const nodes = years.map(this.renderYear)

    const perRow = props.perRow
    const buckets = times(Math.ceil(nodes.length / perRow)).map(i => {
      return nodes.slice(i * perRow, (i + 1) * perRow)
    })

    return buckets.map((bucket, i) => <Flex
      alignItems="center"
      flex
      row
      inline
      key={`row_${i}`}
      className="dp-row"
    >
      {bucket}
    </Flex>)
  }

  renderYear(dateMoment) {
    const props = this.p
    const yearText = this.format(dateMoment)

    const timestamp = +dateMoment

    const isActiveDate = props.onlyCompareYear && props.activeMoment ?
      dateMoment.get('year') == props.activeMoment.get('year') :
      timestamp === props.activeDate

    const isValue = props.onlyCompareYear && props.moment ?
      dateMoment.get('year') == props.moment.get('year') :
      timestamp === props.timestamp

    const className = join(
      bem('year'),
      isActiveDate && bem('year', 'active'),
      isValue && bem('year', 'value'),
      props.minDate != null && timestamp < props.minDate && bem('year', 'disabled'),
      props.maxDate != null && timestamp > props.maxDate && bem('year', 'disabled')
    )

    const onClick = this.handleClick.bind(this, {
      dateMoment,
      timestamp
    })

    return <Item
      key={yearText}
      className={className}
      onClick={onClick}
    >
      {yearText}
    </Item>
  }

  format(mom, format) {
    format = format || this.props.yearFormat

    return mom.format(format)
  }

  handleClick({ timestamp, dateMoment }, event) {
    event.target.value = timestamp

    const props = this.p
    if (props.minDate && timestamp < props.minDate) {
      return
    }

    if (props.maxDate && timestamp > props.maxDate) {
      return
    }

    this.select({ dateMoment, timestamp }, event)
  }

  onKeyDown(event) {
    return ON_KEY_DOWN.call(this, event)
  }

  confirm(date, event) {
    return confirm.call(this, date, event)
  }

  navigate(direction, event) {
    return navigate.call(this, direction, event)
  }

  select({ dateMoment, timestamp }, event) {
    return select.call(this, { dateMoment, timestamp }, event)
  }

  onViewDateChange({ dateMoment, timestamp }) {
    return onViewDateChange.call(this, { dateMoment, timestamp })
  }

  gotoViewDate({ dateMoment, timestamp }) {
    return gotoViewDate.call(this, { dateMoment, timestamp })
  }

  onActiveDateChange({ dateMoment, timestamp }) {
    return onActiveDateChange.call(this, { dateMoment, timestamp })
  }

  onChange({ dateMoment, timestamp }, event) {
    return onChange.call(this, { dateMoment, timestamp }, event)
  }

  focus() {
    findDOMNode(this).focus()
  }
}

DecadeView.defaultProps = {
  isDecadeView: true,
  arrows: {},
  navigation: true,
  constrainViewDate: true,
  navKeys: NAV_KEYS,
  theme: 'default',
  yearFormat: 'YYYY',
  dateFormat: 'YYYY-MM-DD',
  perRow: 5,

  onlyCompareYear: true,

  adjustDateStartOf: 'year',
  adjustMinDateStartOf: 'year',
  adjustMaxDateStartOf: 'year'
}

export {
  onChange,
  onViewDateChange,
  onActiveDateChange,
  select,
  confirm,
  gotoViewDate,
  navigate,

  ON_KEY_DOWN as onKeyDown,

  prepareActiveDate,
  prepareViewDate,
  prepareMinMax,
  prepareDateProps,
  prepareDate,

  isDateInMinMax,
  isValidActiveDate,

  getInitialState
}
<|endoftext|>