<commit_before>/* eslint-disable comma-style, operator-linebreak, space-unary-ops, no-multi-spaces, key-spacing, indent */
'use strict'
const cardOrder = [ '2', '3', '4', '5', '6', '7', '8', 'T', 'J', 'Q', 'K', 'A' ]
const strategicPositions = require('./strategic-positions')

function round(n) {
  return Math.round(n * 10) / 10
}

function notmetadata(k) {
  return k !== 'metadata'
}

function copyValues(o) {
  function copy(acc, k) {
    acc[k] = o[k]
    return acc
  }
  if (!o) return o
  return Object.keys(o)
    .filter(notmetadata)
    .reduce(copy, {})
}

function normalizeHoleCards(hc) {
  if (!hc) return hc
  const c1 = hc.card1
  const c2 = hc.card2
  if (c1 == null || c2 == null) return hc
  // show large card before smaller card
  return cardOrder.indexOf(c1[0]) < cardOrder.indexOf(c2[0])
    ? { card1: c2, card2: c1 } : { card1: c1, card2: c2 }
}

function getStartingPot(o, playerCount) {
  const totalAnte = (o.ante || 0) * playerCount
  return  (o.sb || 0) + (o.bb || 0) + totalAnte
}

function postFlopOrderFromPreflopOrder(n, playerCount) {
  // headsup just reverses the order
  if (playerCount === 2) return n === 0 ? 1 : 0

  if (n === (playerCount - 1)) return 1 // BB
  if (n === (playerCount - 2)) return 0 // SB
  return n + 2
}
function byPostFlopOrder(p1, p2) {
  return p1.postflopOrder - p2.postflopOrder
}

function sortPlayersByPostFlopOrder(players) {
  function appendPlayer(acc, k) {
    const p = players[k]
    p.name = k
    acc.push(p)
    return acc
  }
  return Object.keys(players)
    .reduce(appendPlayer, [])
    .sort(byPostFlopOrder)
}

function playerInvested(preflop) {
  for (var i = 0; i < preflop.length; i++) {
    const action = preflop[i].type
    if (action === 'bet' || action === 'call' || action === 'raise') return true
  }
  return false
}

function playerSawShowdown(p) {
  if (p.showdown.length) return true
  if (p.river.length && p.river[p.river.length - 1].type !== 'fold') return true
  return false
}

function addActivityInfo(players, info) {
  var anyInvested    = false
  var anySawFlop     = false
  for (var i = 0; i < players.length; i++) {
    const player       = players[i]
    player.invested    = player.sb || player.bb || playerInvested(player.preflop)
    player.sawFlop     = !!player.flop.length
    player.sawShowdown = playerSawShowdown(player)

    if (!anyInvested) anyInvested = player.invested
    if (!anySawFlop) anySawFlop   = player.sawFlop
  }

  info.anyInvested    = anyInvested
  info.anySawFlop     = anySawFlop
}

function updateChips(prev, current, investeds, players, hand) {
  Object.keys(players)
    .forEach(updatePlayerChips, { prev: prev, current: current })

  function updatePlayerChips(k) {
    const p = players[k]
    var chips = p[this.prev] - (investeds[k] || 0)
    if (this.prev === 'chipsPreflop') {
      if (p.bb) chips += hand.info.bb
      if (p.sb) chips += hand.info.sb
    }
    p.chipsAfter = p[this.current] = chips
  }
}

function updateChipsForAction(chips, action, cost, player) {
  action.chips = chips[player]
  chips[player] -= cost
  action.chipsAfter = chips[player]
}

function positionPlayer(player, playerCount, idx) {
  player.preflopOrder = idx
  player.postflopOrder = postFlopOrderFromPreflopOrder(idx, playerCount)
  const positions = strategicPositions(idx, playerCount)
  player.pos = positions.pos
  player.exactPos = positions.exactPos
}

function assignPosition(player, playerCount, idx) {
  if (player.hasOwnProperty('preflopOrder')) return
  positionPlayer(player, playerCount, idx)
}

module.exports = function analyzeHoldem(hand) {
  var pot = 0
  var currentBet = hand.info.bb

  const playerCount = hand.seats.length
  const startingPot = getStartingPot(hand.info, playerCount)

  const players = {}
  const analyzed = {
      info    : copyValues(hand.info)
    , table   : copyValues(hand.table)
    , board   : copyValues(hand.board)
    , hero    : hand.hero
  }
  if (analyzed.info.ante == null) analyzed.info.ante = 0
  analyzed.pots = {
    preflop: startingPot
  }
  analyzed.info.players = playerCount

  for (var i = 0; i < playerCount; i++) {
    const s = hand.seats[i]
    const player = {
        seatno        : s.seatno
      , chips         : s.chips
      , chipsPreflop  : s.chips
      , chipsFlop     : NaN
      , chipsTurn     : NaN
      , chipsRiver    : NaN
      , chipsShowdown : NaN
      , chipsAfter    : NaN
      , m             : Math.round(s.chips / startingPot)
      , preflop       : []
      , flop          : []
      , turn          : []
      , river         : []
      , showdown      : []
    }
    if (hand.table.button === s.seatno) player.button = true
    if (hand.hero === s.player) {
      player.hero = true
      if (hand.holecards) {
        player.cards = normalizeHoleCards(hand.holecards)
      }
    }
    players[s.player] = player
  }
  analyzed.players = players

  for (i = 0; i < hand.posts.length; i++) {
    const p = hand.posts[i]
    const player = players[p.player]
    pot += p.amount
    player.chipsAfter = player.chipsPreflop -= p.amount

    if (p.type === 'sb') player.sb = true
    if (p.type === 'bb') player.bb = true
  }

  function analyzeAction(p, invested, chips) {
    const startingPot = pot
    var cost = 0
    var betDelta = 0
    const action = {
      type: p.type
    }
    if (p.type === 'raise') {
      action.ratio = round(p.raiseTo / currentBet)
      action.allin = !!p.allin
      action.amount = p.raiseTo - invested
      betDelta = 1
      currentBet = p.raiseTo
      pot += currentBet
      cost = action.amount
    } else if (p.type === 'bet') {
      action.ratio = round(p.amount / pot)
      action.allin = !!p.allin
      action.amount = p.amount
      currentBet = p.amount
      pot += currentBet
      cost = action.amount
    } else if (p.type === 'call') {
      action.ratio = round(p.amount / pot)
      action.allin = !!p.allin
      action.amount = p.amount
      pot += p.amount
      cost = action.amount
    } else if (p.type === 'collect') {
      action.ratio = round(p.amount / pot)
      action.allin = false
      action.amount = p.amount
      cost = -p.amount
      pot = 0
    } else if (p.type === 'bet-returned') {
      action.ratio = round(p.amount / pot)
      action.allin = false
      action.amount = p.amount
      cost = -p.amount
      pot = pot - p.amount
    }
    action.pot = startingPot
    action.potAfter = startingPot + cost
    action.chips = chips
    action.chipsAfter = chips - cost
    return { action: action, cost: cost || 0, betDelta: betDelta }
  }

  var investeds = {}
  var chips = {}
  // starting with one bet, first raise is two bet, next three bet and so on
  var bet = 1

  function startPreflopCost(p) {
    if (p.bb) return hand.info.bb
    if (p.sb) return hand.info.sb
    return 0
  }

  function adjustBet(info) {
    bet = bet + info.betDelta
    info.action.bet = bet
  }

  //
  // Preflop
  //
  for (i = 0; i < hand.preflop.length; i++) {
    const p = hand.preflop[i]
    const player = players[p.player]
    const invested = investeds[p.player] || startPreflopCost(player)
    if (typeof chips[p.player] === 'undefined') chips[p.player] = player.chipsPreflop

    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.preflop.push(info.action)
    assignPosition(player, playerCount, i)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsPreflop', 'chipsFlop', investeds, players, hand)

  //
  // Flop
  //
  analyzed.pots.flop = pot
  investeds = {}
  bet = 1
  for (i = 0; i < hand.flop.length; i++) {
    const p = hand.flop[i]
    const player = players[p.player]
    const invested = investeds[p.player] || 0
    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.flop.push(info.action)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsFlop', 'chipsTurn', investeds, players, hand)

  //
  // Turn
  //
  analyzed.pots.turn = pot
  investeds = {}
  bet = 1
  for (i = 0; i < hand.turn.length; i++) {
    const p = hand.turn[i]
    const player = players[p.player]
    const invested = investeds[p.player] || 0
    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.turn.push(info.action)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsTurn', 'chipsRiver', investeds, players, hand)

  //
  // River
  //
  analyzed.pots.river = pot
  investeds = {}
  bet = 1
  for (i = 0; i < hand.river.length; i++) {
    const p = hand.river[i]
    const player = players[p.player]
    const invested = investeds[p.player] || 0
    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.river.push(info.action)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsRiver', 'chipsShowdown', investeds, players, hand)

  //
  // Showdown
  //
  analyzed.pots.showdown = pot
  // first we aggregate all collections and then condense into one action
  var collecteds = {}
  for (i = 0; i < hand.showdown.length; i++) {
    const p = hand.showdown[i]
    const player = players[p.player]
    // in some rare cases we removed a player that had no active involvemement in the hand
    // i.e. if he was allin after posting
    if (player == null) continue

    if ((p.type === 'show' || p.type === 'muck') &&
      player.cards == null && p.card1 != null && p.card2 != null) {
      player.cards = normalizeHoleCards({ card1: p.card1, card2: p.card2 })
    } else if (p.type === 'collect') {
      collecteds[p.player] = (collecteds[p.player] || 0) + p.amount
    }
  }

  Object.keys(collecteds).forEach(processCollecteds)
  function processCollecteds(k) {
    const player = players[k]
    const amount = collecteds[k]
    const ratio = round(amount / pot)
    const action = {
        type       : 'collect'
      , ratio      : ratio
      , winall     : ratio === 1
      , amount     : amount
      , chips      : chips[k]
      , chipsAfter : chips[k] + amount
    }
    player.showdown.push(action)
    player.chipsAfter += amount
  }

  // In some rare cases a player is allin after posting and doesn't act at all
  // In that case we just pretend that player never existed since he's not important
  // to analyze the hand and is problematic to assign a position to at this point
  const playerKeys = Object.keys(players)
  for (i = 0; i < playerKeys.length; i++) {
    const key = playerKeys[i]
    const player = players[key]
    if (player == null || player.preflopOrder == null) delete players[key]
  }

  // Some cards are only exposed in showdown (seen for mucked hands), so let's try to
  // capture the ones we didn't so far.
  if (hand.summary != null) {
    for (i = 0; i < hand.summary.length; i++) {
      const p = hand.summary[i]
      const player = players[p.player]
      if (player == null) continue

      if ((p.type === 'show' || p.type === 'showed' || p.type === 'muck') &&
        player.cards == null && p.card1 != null && p.card2 != null) {
        player.cards = normalizeHoleCards({ card1: p.card1, card2: p.card2 })
      }
    }
  }

  analyzed.players = sortPlayersByPostFlopOrder(players)
  addActivityInfo(analyzed.players, analyzed.info)
  return analyzed
}
<commit_msg>holdem: using null for unknown chips<commit_after>/* eslint-disable comma-style, operator-linebreak, space-unary-ops, no-multi-spaces, key-spacing, indent */
'use strict'
const cardOrder = [ '2', '3', '4', '5', '6', '7', '8', 'T', 'J', 'Q', 'K', 'A' ]
const strategicPositions = require('./strategic-positions')

function round(n) {
  return Math.round(n * 10) / 10
}

function notmetadata(k) {
  return k !== 'metadata'
}

function copyValues(o) {
  function copy(acc, k) {
    acc[k] = o[k]
    return acc
  }
  if (!o) return o
  return Object.keys(o)
    .filter(notmetadata)
    .reduce(copy, {})
}

function normalizeHoleCards(hc) {
  if (!hc) return hc
  const c1 = hc.card1
  const c2 = hc.card2
  if (c1 == null || c2 == null) return hc
  // show large card before smaller card
  return cardOrder.indexOf(c1[0]) < cardOrder.indexOf(c2[0])
    ? { card1: c2, card2: c1 } : { card1: c1, card2: c2 }
}

function getStartingPot(o, playerCount) {
  const totalAnte = (o.ante || 0) * playerCount
  return  (o.sb || 0) + (o.bb || 0) + totalAnte
}

function postFlopOrderFromPreflopOrder(n, playerCount) {
  // headsup just reverses the order
  if (playerCount === 2) return n === 0 ? 1 : 0

  if (n === (playerCount - 1)) return 1 // BB
  if (n === (playerCount - 2)) return 0 // SB
  return n + 2
}
function byPostFlopOrder(p1, p2) {
  return p1.postflopOrder - p2.postflopOrder
}

function sortPlayersByPostFlopOrder(players) {
  function appendPlayer(acc, k) {
    const p = players[k]
    p.name = k
    acc.push(p)
    return acc
  }
  return Object.keys(players)
    .reduce(appendPlayer, [])
    .sort(byPostFlopOrder)
}

function playerInvested(preflop) {
  for (var i = 0; i < preflop.length; i++) {
    const action = preflop[i].type
    if (action === 'bet' || action === 'call' || action === 'raise') return true
  }
  return false
}

function playerSawShowdown(p) {
  if (p.showdown.length) return true
  if (p.river.length && p.river[p.river.length - 1].type !== 'fold') return true
  return false
}

function addActivityInfo(players, info) {
  var anyInvested    = false
  var anySawFlop     = false
  for (var i = 0; i < players.length; i++) {
    const player       = players[i]
    player.invested    = player.sb || player.bb || playerInvested(player.preflop)
    player.sawFlop     = !!player.flop.length
    player.sawShowdown = playerSawShowdown(player)

    if (!anyInvested) anyInvested = player.invested
    if (!anySawFlop) anySawFlop   = player.sawFlop
  }

  info.anyInvested    = anyInvested
  info.anySawFlop     = anySawFlop
}

function updateChips(prev, current, investeds, players, hand) {
  Object.keys(players)
    .forEach(updatePlayerChips, { prev: prev, current: current })

  function updatePlayerChips(k) {
    const p = players[k]
    var chips = p[this.prev] - (investeds[k] || 0)
    if (this.prev === 'chipsPreflop') {
      if (p.bb) chips += hand.info.bb
      if (p.sb) chips += hand.info.sb
    }
    p.chipsAfter = p[this.current] = chips
  }
}

function updateChipsForAction(chips, action, cost, player) {
  action.chips = chips[player]
  chips[player] -= cost
  action.chipsAfter = chips[player]
}

function positionPlayer(player, playerCount, idx) {
  player.preflopOrder = idx
  player.postflopOrder = postFlopOrderFromPreflopOrder(idx, playerCount)
  const positions = strategicPositions(idx, playerCount)
  player.pos = positions.pos
  player.exactPos = positions.exactPos
}

function assignPosition(player, playerCount, idx) {
  if (player.hasOwnProperty('preflopOrder')) return
  positionPlayer(player, playerCount, idx)
}

module.exports = function analyzeHoldem(hand) {
  var pot = 0
  var currentBet = hand.info.bb

  const playerCount = hand.seats.length
  const startingPot = getStartingPot(hand.info, playerCount)

  const players = {}
  const analyzed = {
      info    : copyValues(hand.info)
    , table   : copyValues(hand.table)
    , board   : copyValues(hand.board)
    , hero    : hand.hero
  }
  if (analyzed.info.ante == null) analyzed.info.ante = 0
  analyzed.pots = {
    preflop: startingPot
  }
  analyzed.info.players = playerCount

  for (var i = 0; i < playerCount; i++) {
    const s = hand.seats[i]
    const player = {
        seatno        : s.seatno
      , chips         : s.chips
      , chipsPreflop  : s.chips
      , chipsFlop     : null
      , chipsTurn     : null
      , chipsRiver    : null
      , chipsShowdown : null
      , chipsAfter    : null
      , m             : Math.round(s.chips / startingPot)
      , preflop       : []
      , flop          : []
      , turn          : []
      , river         : []
      , showdown      : []
    }
    if (hand.table.button === s.seatno) player.button = true
    if (hand.hero === s.player) {
      player.hero = true
      if (hand.holecards) {
        player.cards = normalizeHoleCards(hand.holecards)
      }
    }
    players[s.player] = player
  }
  analyzed.players = players

  for (i = 0; i < hand.posts.length; i++) {
    const p = hand.posts[i]
    const player = players[p.player]
    pot += p.amount
    player.chipsAfter = player.chipsPreflop -= p.amount

    if (p.type === 'sb') player.sb = true
    if (p.type === 'bb') player.bb = true
  }

  function analyzeAction(p, invested, chips) {
    const startingPot = pot
    var cost = 0
    var betDelta = 0
    const action = {
      type: p.type
    }
    if (p.type === 'raise') {
      action.ratio = round(p.raiseTo / currentBet)
      action.allin = !!p.allin
      action.amount = p.raiseTo - invested
      betDelta = 1
      currentBet = p.raiseTo
      pot += currentBet
      cost = action.amount
    } else if (p.type === 'bet') {
      action.ratio = round(p.amount / pot)
      action.allin = !!p.allin
      action.amount = p.amount
      currentBet = p.amount
      pot += currentBet
      cost = action.amount
    } else if (p.type === 'call') {
      action.ratio = round(p.amount / pot)
      action.allin = !!p.allin
      action.amount = p.amount
      pot += p.amount
      cost = action.amount
    } else if (p.type === 'collect') {
      action.ratio = round(p.amount / pot)
      action.allin = false
      action.amount = p.amount
      cost = -p.amount
      pot = 0
    } else if (p.type === 'bet-returned') {
      action.ratio = round(p.amount / pot)
      action.allin = false
      action.amount = p.amount
      cost = -p.amount
      pot = pot - p.amount
    }
    action.pot = startingPot
    action.potAfter = startingPot + cost
    action.chips = chips
    action.chipsAfter = chips - cost
    return { action: action, cost: cost || 0, betDelta: betDelta }
  }

  var investeds = {}
  var chips = {}
  // starting with one bet, first raise is two bet, next three bet and so on
  var bet = 1

  function startPreflopCost(p) {
    if (p.bb) return hand.info.bb
    if (p.sb) return hand.info.sb
    return 0
  }

  function adjustBet(info) {
    bet = bet + info.betDelta
    info.action.bet = bet
  }

  //
  // Preflop
  //
  for (i = 0; i < hand.preflop.length; i++) {
    const p = hand.preflop[i]
    const player = players[p.player]
    const invested = investeds[p.player] || startPreflopCost(player)
    if (typeof chips[p.player] === 'undefined') chips[p.player] = player.chipsPreflop

    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.preflop.push(info.action)
    assignPosition(player, playerCount, i)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsPreflop', 'chipsFlop', investeds, players, hand)

  //
  // Flop
  //
  analyzed.pots.flop = pot
  investeds = {}
  bet = 1
  for (i = 0; i < hand.flop.length; i++) {
    const p = hand.flop[i]
    const player = players[p.player]
    const invested = investeds[p.player] || 0
    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.flop.push(info.action)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsFlop', 'chipsTurn', investeds, players, hand)

  //
  // Turn
  //
  analyzed.pots.turn = pot
  investeds = {}
  bet = 1
  for (i = 0; i < hand.turn.length; i++) {
    const p = hand.turn[i]
    const player = players[p.player]
    const invested = investeds[p.player] || 0
    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.turn.push(info.action)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsTurn', 'chipsRiver', investeds, players, hand)

  //
  // River
  //
  analyzed.pots.river = pot
  investeds = {}
  bet = 1
  for (i = 0; i < hand.river.length; i++) {
    const p = hand.river[i]
    const player = players[p.player]
    const invested = investeds[p.player] || 0
    const info = analyzeAction(p, invested)
    adjustBet(info)

    player.river.push(info.action)
    investeds[p.player] = invested + info.cost
    updateChipsForAction(chips, info.action, info.cost, p.player)
  }
  updateChips('chipsRiver', 'chipsShowdown', investeds, players, hand)

  //
  // Showdown
  //
  analyzed.pots.showdown = pot
  // first we aggregate all collections and then condense into one action
  var collecteds = {}
  for (i = 0; i < hand.showdown.length; i++) {
    const p = hand.showdown[i]
    const player = players[p.player]
    // in some rare cases we removed a player that had no active involvemement in the hand
    // i.e. if he was allin after posting
    if (player == null) continue

    if ((p.type === 'show' || p.type === 'muck') &&
      player.cards == null && p.card1 != null && p.card2 != null) {
      player.cards = normalizeHoleCards({ card1: p.card1, card2: p.card2 })
    } else if (p.type === 'collect') {
      collecteds[p.player] = (collecteds[p.player] || 0) + p.amount
    }
  }

  Object.keys(collecteds).forEach(processCollecteds)
  function processCollecteds(k) {
    const player = players[k]
    const amount = collecteds[k]
    const ratio = round(amount / pot)
    const action = {
        type       : 'collect'
      , ratio      : ratio
      , winall     : ratio === 1
      , amount     : amount
      , chips      : chips[k]
      , chipsAfter : chips[k] + amount
    }
    player.showdown.push(action)
    player.chipsAfter += amount
  }

  // In some rare cases a player is allin after posting and doesn't act at all
  // In that case we just pretend that player never existed since he's not important
  // to analyze the hand and is problematic to assign a position to at this point
  const playerKeys = Object.keys(players)
  for (i = 0; i < playerKeys.length; i++) {
    const key = playerKeys[i]
    const player = players[key]
    if (player == null || player.preflopOrder == null) delete players[key]
  }

  // Some cards are only exposed in showdown (seen for mucked hands), so let's try to
  // capture the ones we didn't so far.
  if (hand.summary != null) {
    for (i = 0; i < hand.summary.length; i++) {
      const p = hand.summary[i]
      const player = players[p.player]
      if (player == null) continue

      if ((p.type === 'show' || p.type === 'showed' || p.type === 'muck') &&
        player.cards == null && p.card1 != null && p.card2 != null) {
        player.cards = normalizeHoleCards({ card1: p.card1, card2: p.card2 })
      }
    }
  }

  analyzed.players = sortPlayersByPostFlopOrder(players)
  addActivityInfo(analyzed.players, analyzed.info)
  return analyzed
}
<|endoftext|>


<commit_before>import React from 'react';

export default Base => {
    return class extends React.Component {
        constructor(props, context) {
            super(props, context);

            this.state = {
                isValidating: false,
                isValid: true,
                validationErrorMessage: null
            };
        }

        componentWillReceiveProps(nextProps) {
            if (nextProps.value !== this.props.value) {
                this.setState({
                    isValidating: true
                }, () => {
                    this._validate(nextProps.value);
                });
            }
        }

        _callOnStart() {
            if ('onStart' in this.props) {
                this.props.onStart();
            }
        }

        _callOnEnd() {
            if ('onEnd' in this.props) {
                this.props.onEnd(this.state.isValid, this.state.validationErrorMessage);
            }
        }

        _onResolve(value) {
            if (value === this.props.value) {
                this.setState({
                    isValid: true,
                    isValidating: false,
                    validationErrorMessage: null
                }, this._callOnEnd);
            }
        }

        _onCatch(value, message) {
            if (value === this.props.value) {
                this.setState({
                    isValid: false,
                    isValidating: false,
                    validationErrorMessage: message
                }, this._callOnEnd);
            }
        }

        _validate(value) {
            this._callOnStart();

            Promise
                .all(
                    this.props.validators.map(validator => {
                        return validator.validator(value, validator.params);
                    })
                )
                .then(
                    this._onResolve.bind(this, value),
                    this._onCatch.bind(this, value)
                );
        }

        render() {
            return React.createElement(
                Base,
                {
                    ...this.props,
                    ...this.state
                },
                this.props.children
            );
        }
    }
}
<commit_msg>:space_invader: fix ESLint errors<commit_after>import React from 'react';

export default Base => {
    return class extends React.Component {
        constructor(props, context) {
            super(props, context);

            this.state = {
                isValidating: false,
                isValid: true,
                validationErrorMessage: null
            };
        }

        componentWillReceiveProps(nextProps) {
            if (nextProps.value !== this.props.value) {
                this.setState({
                    isValidating: true
                }, () => {
                    this._validate(nextProps.value);
                });
            }
        }

        _callOnStart() {
            if ('onStart' in this.props) {
                this.props.onStart();
            }
        }

        _callOnEnd() {
            if ('onEnd' in this.props) {
                this.props.onEnd(this.state.isValid, this.state.validationErrorMessage);
            }
        }

        _onResolve(value) {
            if (value === this.props.value) {
                this.setState({
                    isValid: true,
                    isValidating: false,
                    validationErrorMessage: null
                }, this._callOnEnd);
            }
        }

        _onCatch(value, message) {
            if (value === this.props.value) {
                this.setState({
                    isValid: false,
                    isValidating: false,
                    validationErrorMessage: message
                }, this._callOnEnd);
            }
        }

        _validate(value) {
            this._callOnStart();

            Promise
                .all(
                    this.props.validators.map(validator => {
                        return validator.validator(value, validator.params);
                    })
                )
                .then(
                    this._onResolve.bind(this, value),
                    this._onCatch.bind(this, value)
                );
        }

        render() {
            return React.createElement(
                Base,
                {
                    ...this.props,
                    ...this.state
                },
                this.props.children
            );
        }
    };
};
<|endoftext|>


<commit_before>/**
 * multi.js
 * A user-friendly replacement for select boxes with multiple attribute enabled.
 *
 * Author: Fabian Lindfors
 * License: MIT
 */
var multi = (function() {

    // Helper function to trigger an event on an element
    var trigger_event = function( type, el ) {
        var e = document.createEvent( 'HTMLEvents' );
        e.initEvent( type, false, true );
        el.dispatchEvent( e );
    };

    // Refreshes an already constructed multi.js instance
    var refresh_select = function( select, settings ) {

        // Clear columns
        select.wrapper.selected.innerHTML = '';
        select.wrapper.non_selected.innerHTML = '';

        // Get search value
        if ( select.wrapper.search ) {
            var query = select.wrapper.search.value;
        }

        // Loop over select options and add to the non-selected and selected columns
        for ( var i = 0; i < select.options.length; i++ ) {

            // We wrap our code in a immediately invoked function to get a new scope for our variables
            // Without this the event handlers wouldn't work as option would be overriden each iteration
            (function() {

                var option = select.options[i];

                var value = option.value;
                var label = option.textContent || option.innerText;

                var row = document.createElement( 'a' );
                row.tabIndex = 0;
                row.className = 'item';
                row.innerHTML = label;
                row.setAttribute( 'role', 'button' );
                row.setAttribute( 'data-value', value );
                row.setAttribute( 'multi-index', i );

                // Add row to selected column if option selected
                if ( option.selected ) {

                    row.className += ' selected';
                    var clone = row.cloneNode( true );
                    select.wrapper.selected.appendChild( clone );

                }

                // Apply search filtering
                if ( query && query != '' && label.toLowerCase().indexOf( query.toLowerCase() ) === -1 ) {
                    return;
                }

                select.wrapper.non_selected.appendChild( row );

            })();

        }

    };

    // Intializes and constructs an multi.js instance
    var init = function( select, settings ) {

        /**
         * Set up settings (optional parameter) and its default values
         *
         * Default values:
         * enable_search : true
         * search_placeholder : 'Search...'
         */
        settings = typeof settings !== 'undefined' ? settings : {};

        settings['enable_search'] = typeof settings['enable_search'] !== 'undefined' ? settings['enable_search'] : true;
        settings['search_placeholder'] = typeof settings['search_placeholder'] !== 'undefined' ? settings['search_placeholder'] : 'Search...';

        // Check if already initalized
        if ( select.dataset.multijs != null ) {
            console.log('Already init');
            return;
        }

        // Make sure element is select and multiple is enabled
        if ( select.nodeName != 'SELECT' || ! select.multiple ) {
            return;
        }

        // Hide select
        select.style.display = 'none';
        select.setAttribute( 'data-multijs', true );

        // Start constructing selector
        var wrapper = document.createElement( 'div' );
        wrapper.className = 'multi-wrapper';

        // Add search bar
        if ( settings.enable_search ) {
            var search = document.createElement( 'input' );
            search.className = 'search-input';
            search.type = 'text';
            search.setAttribute( 'placeholder', settings.search_placeholder );

            search.addEventListener( 'input', function() {
                refresh_select( select, settings );
            });

            wrapper.appendChild( search );
            wrapper.search = search;
        }

        // Add columns for selected and non-selected
        var non_selected = document.createElement( 'div' );
        non_selected.className = 'non-selected-wrapper';

        var selected = document.createElement( 'div' );
        selected.className = 'selected-wrapper';

        // Add click handler to toggle the selected status
        wrapper.addEventListener( 'click', function ( event ) {

            var target = event.target;
            var option;

            if ( target.getAttribute( 'multi-index' ) ) {
                option = select.options[ target.getAttribute( 'multi-index' ) ];
                option.selected = !option.selected;
                trigger_event( 'change', select );
            }

        } );

        // Add keyboard handler to toggle the selected status
        wrapper.addEventListener( 'keypress', function ( event ) {

            var target = event.target;
            var isActionKey = event.keyCode === 32 || event.keyCode === 13;
            var isOption = target.getAttribute( 'multi-index' );
            var option;

            if ( isOption && isActionKey ) {
                // Prevent the default action to stop scrolling when space is pressed
                event.preventDefault();
                option = select.options[ target.getAttribute( 'multi-index' ) ];
                option.selected = !option.selected;
                trigger_event( 'change', select );
            }

        } );

        wrapper.appendChild( non_selected );
        wrapper.appendChild( selected );

        wrapper.non_selected = non_selected;
        wrapper.selected = selected;

        select.wrapper = wrapper;

        // Add multi.js wrapper after select element
        select.parentNode.insertBefore( wrapper, select.nextSibling );

        // Initialize selector with values from select element
        refresh_select( select, settings );

        // Refresh selector when select values change
        select.addEventListener( 'change', function() {
            refresh_select( select, settings );
        });

    };

    return init;

}());

// Add jQuery wrapper if jQuery is present
if ( typeof jQuery !== 'undefined' ) {
    (function($) {

        $.fn.multi = function( settings ) {

            settings = typeof settings !== 'undefined' ? settings : {};

            return this.each( function() {

                var $select = $(this);

                multi( $select.get(0), settings );

            });

        }

    })(jQuery);
}
<commit_msg>Extract toggle option logic into a function<commit_after>/**
 * multi.js
 * A user-friendly replacement for select boxes with multiple attribute enabled.
 *
 * Author: Fabian Lindfors
 * License: MIT
 */
var multi = (function() {

    // Helper function to trigger an event on an element
    var trigger_event = function( type, el ) {
        var e = document.createEvent( 'HTMLEvents' );
        e.initEvent( type, false, true );
        el.dispatchEvent( e );
    };

    // Toggles the target option on the select
    var toggleOption = function ( select, event ) {
        var option = select.options[ event.target.getAttribute( 'multi-index' ) ];
        option.selected = !option.selected;
        trigger_event( 'change', select );
    };

    // Refreshes an already constructed multi.js instance
    var refresh_select = function( select, settings ) {

        // Clear columns
        select.wrapper.selected.innerHTML = '';
        select.wrapper.non_selected.innerHTML = '';

        // Get search value
        if ( select.wrapper.search ) {
            var query = select.wrapper.search.value;
        }

        // Loop over select options and add to the non-selected and selected columns
        for ( var i = 0; i < select.options.length; i++ ) {

            // We wrap our code in a immediately invoked function to get a new scope for our variables
            // Without this the event handlers wouldn't work as option would be overriden each iteration
            (function() {

                var option = select.options[i];

                var value = option.value;
                var label = option.textContent || option.innerText;

                var row = document.createElement( 'a' );
                row.tabIndex = 0;
                row.className = 'item';
                row.innerHTML = label;
                row.setAttribute( 'role', 'button' );
                row.setAttribute( 'data-value', value );
                row.setAttribute( 'multi-index', i );

                // Add row to selected column if option selected
                if ( option.selected ) {

                    row.className += ' selected';
                    var clone = row.cloneNode( true );
                    select.wrapper.selected.appendChild( clone );

                }

                // Apply search filtering
                if ( query && query != '' && label.toLowerCase().indexOf( query.toLowerCase() ) === -1 ) {
                    return;
                }

                select.wrapper.non_selected.appendChild( row );

            })();

        }

    };

    // Intializes and constructs an multi.js instance
    var init = function( select, settings ) {

        /**
         * Set up settings (optional parameter) and its default values
         *
         * Default values:
         * enable_search : true
         * search_placeholder : 'Search...'
         */
        settings = typeof settings !== 'undefined' ? settings : {};

        settings['enable_search'] = typeof settings['enable_search'] !== 'undefined' ? settings['enable_search'] : true;
        settings['search_placeholder'] = typeof settings['search_placeholder'] !== 'undefined' ? settings['search_placeholder'] : 'Search...';

        // Check if already initalized
        if ( select.dataset.multijs != null ) {
            console.log('Already init');
            return;
        }

        // Make sure element is select and multiple is enabled
        if ( select.nodeName != 'SELECT' || ! select.multiple ) {
            return;
        }

        // Hide select
        select.style.display = 'none';
        select.setAttribute( 'data-multijs', true );

        // Start constructing selector
        var wrapper = document.createElement( 'div' );
        wrapper.className = 'multi-wrapper';

        // Add search bar
        if ( settings.enable_search ) {
            var search = document.createElement( 'input' );
            search.className = 'search-input';
            search.type = 'text';
            search.setAttribute( 'placeholder', settings.search_placeholder );

            search.addEventListener( 'input', function() {
                refresh_select( select, settings );
            });

            wrapper.appendChild( search );
            wrapper.search = search;
        }

        // Add columns for selected and non-selected
        var non_selected = document.createElement( 'div' );
        non_selected.className = 'non-selected-wrapper';

        var selected = document.createElement( 'div' );
        selected.className = 'selected-wrapper';

        // Add click handler to toggle the selected status
        wrapper.addEventListener( 'click', function ( event ) {
            if ( event.target.getAttribute( 'multi-index' ) ) {
                toggleOption( select, event );
            }
        } );

        // Add keyboard handler to toggle the selected status
        wrapper.addEventListener( 'keypress', function ( event ) {

            var isActionKey = event.keyCode === 32 || event.keyCode === 13;
            var isOption = event.target.getAttribute( 'multi-index' );

            if ( isOption && isActionKey ) {
                // Prevent the default action to stop scrolling when space is pressed
                event.preventDefault();
                toggleOption( select, event );
            }

        } );

        wrapper.appendChild( non_selected );
        wrapper.appendChild( selected );

        wrapper.non_selected = non_selected;
        wrapper.selected = selected;

        select.wrapper = wrapper;

        // Add multi.js wrapper after select element
        select.parentNode.insertBefore( wrapper, select.nextSibling );

        // Initialize selector with values from select element
        refresh_select( select, settings );

        // Refresh selector when select values change
        select.addEventListener( 'change', function() {
            refresh_select( select, settings );
        });

    };

    return init;

}());

// Add jQuery wrapper if jQuery is present
if ( typeof jQuery !== 'undefined' ) {
    (function($) {

        $.fn.multi = function( settings ) {

            settings = typeof settings !== 'undefined' ? settings : {};

            return this.each( function() {

                var $select = $(this);

                multi( $select.get(0), settings );

            });

        }

    })(jQuery);
}
<|endoftext|>


<commit_before>(function() {
    'use strict';

    var fs          = require('fs'),
        path        = require('path'),
        zlib        = require('zlib'),
        util        = require('util'),

        findit      = require('findit'),
        assert      = require('assert'),
        Emitter     = require('events').EventEmitter,

        tar         = require('tar-fs'),
        tarStream   = require('tar-stream'),
        pipe        = require('pipe-io');

    util.inherits(Jaguar, Emitter);

    module.exports.pack     = jaguar('pack');
    module.exports.extract  = jaguar('extract');

    function check(from, to, files) {
        assert(typeof from === 'string', 'from should be string!');
        assert(typeof to === 'string', 'to should be string!');

        if (arguments.length > 2)
            assert(Array.isArray(files), 'array should be array!');
    }

    function jaguar(operation) {
        return function(from, to, files) {
            var emitter;

            emitter = new Jaguar(operation, from, to, files);

            return emitter;
        };
    }

    function Jaguar(operation, from, to, files) {
        var self            = this;

        this._i             = 0;
        this._files         = [];

        this._percent       = 0;
        this._percentPrev   = 0;

        this._from          = from;
        this._to            = to;

        switch(operation) {
        case 'pack':
            this._names         = files.slice();
            check(from, to, files);

            this._parallel(from, files, function() {
                self._n         = self._files.length;
                self._pack();
            });
            break;

        case 'extract':
            check(from, to);
            this._parse(from, function(error) {
                if (!error) {
                    self._n = self._files.length;
                    self._unpack();
                } else {
                    self.emit('error', error);
                    self.emit('end');
                }
            });
            break;

        default:
            throw Error('operations: pack or unpack only');
        }
    }

    Jaguar.prototype.abort     = function() {
        this._abort = true;
    };

    Jaguar.prototype._parallel  = function(from, files, callback) {
        var self    = this,
            i       = files.length,
            fn      = function() {
                if (!--i)
                    callback();
            };

        files.forEach(function(name) {
            var full = path.join(from, name);

            self._findFiles(full, fn);
        });
    };

    Jaguar.prototype._findFiles = function(filename, fn) {
        var self        = this,

            finder      = findit(filename),

            onError     = function(error) {
                self._files.push(error.path);
            },

            onFile      = function(name) {
                self._files.push(name);
            };

        finder.on('file', onFile);
        finder.on('error', onError);
        finder.on('directory', onFile);
        finder.on('link', onFile);

        finder.on('end', function() {
            fn();
        });
    };

    Jaguar.prototype._pack = function() {
        var self        = this,
            from        = this._from,
            to          = this._to,

            streamFile  = fs.createWriteStream(to),
            streamZip   = zlib.createGzip(),
            streamTar   = tar.pack(from, {
                entries : this._names,
                map: function(header) {
                    self._progress();
                    self.emit('file', header.name);

                    return header;
                }
            });

            pipe([
                streamTar,
                streamZip,
                streamFile
            ], function(error) {
                if (error)
                    self.emit('error', error);

                if (!self._abort)
                    self.emit('end');
                else
                    fs.unlink(to, function(error) {
                        if (error)
                            self.emit('error', error);

                        self.emit('end');
                    });
            });
    };

    Jaguar.prototype._extract  = function() {
        var self        = this,
            from        = this._from,
            to          = this._to,
            streamFile  = fs.createReadStream(from),
            streamUnzip = zlib.createGunzip(),
            streamUntar = tar.extract(to);

        streamUntar.on('entry', function(header) {
            self._progress();
            self.emit('file', header.name);
        });

        streamUntar.on('finish', function() {
            self.emit('end');
        });

        pipe([
            streamFile,
            streamUnzip,
            streamUntar
        ], function(error) {
            if (error)
                self.emit('error', error);
        });
    };

    Jaguar.prototype._parse     = function(name, callback) {
        var self        = this,
            wasError,
            j           = 0,
            streamFile  = fs.createReadStream(name),
            streamUnzip = zlib.createGunzip(),
            streamParse = tarStream.extract();

        streamParse.on('entry', function(header, stream, callback) {
            stream.on('end', function() {
                ++j;
                self._files.push(header.name);

                callback();
            });

            stream.resume();
        });

        streamParse.on('finish', function() {
            if (!wasError)
                callback();
        });

        pipe([
            streamFile,
            streamUnzip,
            streamParse
        ], function(error) {
            if (error) {
                wasError = true;
                callback(error);
            }
        });
    };

    Jaguar.prototype._progress  = function() {
        var value;

        ++this._i;

        value = Math.round(this._i * 100 / this._n);

        this._percent = value;

        if (value !== this._percentPrev) {
            this._percentPrev = value;
            this.emit('progress', value);
        }
    };

})();
<commit_msg>feature(jaguar) rm this._files<commit_after>(function() {
    'use strict';

    var fs          = require('fs'),
        path        = require('path'),
        zlib        = require('zlib'),
        util        = require('util'),

        findit      = require('findit'),
        assert      = require('assert'),
        Emitter     = require('events').EventEmitter,

        tar         = require('tar-fs'),
        tarStream   = require('tar-stream'),
        pipe        = require('pipe-io');

    util.inherits(Jaguar, Emitter);

    module.exports.pack     = jaguar('pack');
    module.exports.extract  = jaguar('extract');

    function check(from, to, files) {
        assert(typeof from === 'string', 'from should be string!');
        assert(typeof to === 'string', 'to should be string!');

        if (arguments.length > 2)
            assert(Array.isArray(files), 'array should be array!');
    }

    function jaguar(operation) {
        return function(from, to, files) {
            var emitter;

            emitter = new Jaguar(operation, from, to, files);

            return emitter;
        };
    }

    function Jaguar(operation, from, to, files) {
        var self            = this;

        this._i             = 0;
        this._n             = 0;

        this._percent       = 0;
        this._percentPrev   = 0;

        this._from          = from;
        this._to            = to;

        switch(operation) {
        case 'pack':
            this._names     = files.slice();
            check(from, to, files);

            this._parallel(from, files, function() {
                self._pack();
            });
            break;

        case 'extract':
            check(from, to);
            this._parse(from, function(error) {
                if (!error) {
                    self._unpack();
                } else {
                    self.emit('error', error);
                    self.emit('end');
                }
            });
            break;

        default:
            throw Error('operations: pack or unpack only');
        }
    }

    Jaguar.prototype.abort     = function() {
        this._abort = true;
    };

    Jaguar.prototype._parallel  = function(from, files, callback) {
        var self    = this,
            i       = files.length,
            fn      = function() {
                if (!--i)
                    callback();
            };

        files.forEach(function(name) {
            var full = path.join(from, name);

            self._findFiles(full, fn);
        });
    };

    Jaguar.prototype._findFiles = function(filename, fn) {
        var self        = this,

            finder      = findit(filename),

            inc         = function() {
                ++self._n;
            };

        finder.on('file', inc);
        finder.on('error', inc);
        finder.on('directory', inc);
        finder.on('link', inc);

        finder.on('end', function() {
            fn();
        });
    };

    Jaguar.prototype._pack = function() {
        var self        = this,
            from        = this._from,
            to          = this._to,

            streamFile  = fs.createWriteStream(to),
            streamZip   = zlib.createGzip(),
            streamTar   = tar.pack(from, {
                entries : this._names,
                map: function(header) {
                    self._progress();
                    self.emit('file', header.name);

                    return header;
                }
            });

            pipe([
                streamTar,
                streamZip,
                streamFile
            ], function(error) {
                if (error)
                    self.emit('error', error);

                if (!self._abort)
                    self.emit('end');
                else
                    fs.unlink(to, function(error) {
                        if (error)
                            self.emit('error', error);

                        self.emit('end');
                    });
            });
    };

    Jaguar.prototype._extract  = function() {
        var self        = this,
            from        = this._from,
            to          = this._to,
            streamFile  = fs.createReadStream(from),
            streamUnzip = zlib.createGunzip(),
            streamUntar = tar.extract(to);

        streamUntar.on('entry', function(header) {
            self._progress();
            self.emit('file', header.name);
        });

        streamUntar.on('finish', function() {
            self.emit('end');
        });

        pipe([
            streamFile,
            streamUnzip,
            streamUntar
        ], function(error) {
            if (error)
                self.emit('error', error);
        });
    };

    Jaguar.prototype._parse     = function(name, callback) {
        var self        = this,
            wasError,

            streamFile  = fs.createReadStream(name),
            streamUnzip = zlib.createGunzip(),
            streamParse = tarStream.extract();

        streamParse.on('entry', function(header, stream, callback) {
            stream.on('end', function() {
                ++self._n;

                callback();
            });

            stream.resume();
        });

        streamParse.on('finish', function() {
            if (!wasError)
                callback();
        });

        pipe([
            streamFile,
            streamUnzip,
            streamParse
        ], function(error) {
            if (error) {
                wasError = true;
                callback(error);
            }
        });
    };

    Jaguar.prototype._progress  = function() {
        var value;

        ++this._i;

        value = Math.round(this._i * 100 / this._n);

        this._percent = value;

        if (value !== this._percentPrev) {
            this._percentPrev = value;
            this.emit('progress', value);
        }
    };

})();
<|endoftext|>


<commit_before>/**
 * Created by linfengluo@gmail.com on 2017/9/7.
 */

const menu = [
    {
        title: '首页',
        icon: 'home',
        link: '/',
        childern: []
    },
    {
        title: 'Layout 布局',
        icon: 'table',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'header',
                    link: ''
                },
                {
                    title: 'asideMenu',
                    link: ''
                },
                {
                    title: 'content',
                    link: ''
                }
            ]
        }]
    },
    {
        title: 'Elements 元素',
        icon: 'cube',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Button',
                    link: '/elements/button'
                }
            ]
        }]
    },
    {
        title: 'Form 表单',
        icon: 'check-square-o',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Input',
                    link: '/form/input'
                }, {
                    title: 'TextArea',
                    link: '/form/textarea'
                },
                {
                    title: 'Checkbox',
                    link: '/form/checkbox'
                },
                {
                    title: 'Selecter',
                    link: '/form/selecter'
                },
                {
                    title: 'Radio',
                    link: '/form/radio'
                },
                {
                    title: 'Switch',
                    link: '/form/switch'
                },
                {
                    title: 'Datepicker',
                    link: '/form/datepicker'
                },
                {
                    title: 'Editor',
                    link: '/form/editor'
                },
                {
                    title: 'Form',
                    link: '/form/form'
                }
            ]
        }]
    },
    {
        title: 'Data 数据',
        icon: 'database',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Table',
                    link: '/data/table'
                }, {
                    title: 'Badge',
                    link: '/data/badge'
                }, {
                    title: 'Panigation',
                    link: '/data/panigation'
                }
            ]
        }]
    },
    {
        title: 'notice 提醒',
        icon: 'bell-o',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Alert',
                    link: '/notice/alert'
                },
                {
                    title: 'Notify',
                    link: '/notice/notify'
                },
                {
                    title: 'Message',
                    link: '/notice/message'
                }
            ]
        }]
    }
]

export default menu
<commit_msg>update route<commit_after>/**
 * Created by linfengluo@gmail.com on 2017/9/7.
 */

const menu = [
    {
        title: '首页',
        icon: 'home',
        link: '/',
        childern: []
    },
    {
        title: 'Elements 元素',
        icon: 'cube',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Button',
                    link: '/elements/button'
                }
            ]
        }]
    },
    {
        title: 'Form 表单',
        icon: 'check-square-o',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Input',
                    link: '/form/input'
                }, {
                    title: 'TextArea',
                    link: '/form/textarea'
                },
                {
                    title: 'Checkbox',
                    link: '/form/checkbox'
                },
                {
                    title: 'Selecter',
                    link: '/form/selecter'
                },
                {
                    title: 'Radio',
                    link: '/form/radio'
                },
                {
                    title: 'Switch',
                    link: '/form/switch'
                },
                {
                    title: 'Datepicker',
                    link: '/form/datepicker'
                },
                {
                    title: 'Editor',
                    link: '/form/editor'
                },
                {
                    title: 'Form',
                    link: '/form/form'
                }
            ]
        }]
    },
    {
        title: 'Data 数据',
        icon: 'database',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Table',
                    link: '/data/table'
                }, {
                    title: 'Badge',
                    link: '/data/badge'
                }, {
                    title: 'Panigation',
                    link: '/data/panigation'
                }
            ]
        }]
    },
    {
        title: 'notice 提醒',
        icon: 'bell-o',
        link: '',
        childern: [{
            menu: [
                {
                    title: 'Alert',
                    link: '/notice/alert'
                },
                {
                    title: 'Notify',
                    link: '/notice/notify'
                },
                {
                    title: 'Message',
                    link: '/notice/message'
                }
            ]
        }]
    }
]

export default menu
<|endoftext|>


<commit_before>'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import url from 'url';
import { Button } from 'react-bootstrap';
import { content_views } from './../globals';
import { ajax, console, object, Filters, JWT, DateUtility, layout } from './../util';
import { ItemPageTitle, ItemDetailList } from './components';
import ReactTooltip from 'react-tooltip';
import * as vizUtil from './../viz/utilities';
import * as d3 from 'd3';
import _ from 'underscore';
import JSONTree from 'react-json-tree';

/**
 * Fallback content_view for pages which are not specifically 'Items.
 * Renders out JSON.
 *
 * @export
 * @class Item
 * @extends {React.Component}
 */
export class HealthView extends React.Component {

    static propTypes = {
        'href' : PropTypes.string
    }

    constructor(props){
        super(props);
        this.render = this.render.bind(this);
        this.componentDidMount = this.componentDidMount.bind(this);
        this.getCounts = _.throttle(this.getCounts.bind(this), 1000);
        this.state = {
            'db_es_total' : "loading...",
            'db_es_compare' : "loading...",
            'mounted' : false
        };
    }

    componentDidMount(){
        this.setState({ 'mounted' : true });
        this.getCounts();
    }

    getCounts(){

        ajax.load('/counts?format=json', (resp)=>{
            this.setState({
                'db_es_total' : resp.db_es_total,
                'db_es_compare': resp.db_es_compare,
            });
        }, 'GET', (resp)=>{
            this.setState({
                'error' : resp.error || resp.message,
                'db_es_total' : null,
                'db_es_compare': null
            });
        });
    }

    render() {
        var context = this.props.context;
        var title = typeof context.title == "string" ? context.title : url.parse(this.props.href).path;

        var width = null;
        if (this.state.mounted){
            width = layout.gridContainerWidth();
        }

        return (

Configuration

                {typeof context.description == "string" ?
{context.description}
 : null}
                <ItemDetailList excludedKeys={ItemDetailList.Detail.defaultProps.excludedKeys.concat(['content'])} hideButtons context={context} schemas={this.props.schemas} keyTitleDescriptionMap={{
                    'blob_bucket' : {
                        title : "Blob Bucket",
                        description : "Name of S3 bucket used for blob data."
                    },
                    'content' : {
                        title : "Extra Information"
                    },
                    'database' : {
                        title : "Database Location",
                        description : "URI used to connect to the back-end PgSQL DB."
                    },
                    'elasticsearch' : {
                        title : "ElasticSearch Location",
                        description : "URI used to connect to the back-end ES instance."
                    },
                    'db_es_total' : {
                        title : "DB and ES Counts",
                        description : "Total counts of items in database and elasticsearch."
                    },
                    'db_es_compare' : {
                        title : "DB and ES Counts by Type",
                        description : "Counts of items in database and elasticsearch for each doc_type index."
                    },
                    'file_upload_bucket' : {
                        title : "File Upload Bucket",
                        description : "Where uploaded files are stored."
                    },
                    'load_data' : {
                        title : "Data Loaded",
                        description : "Data which was loaded into database on initialization or boot."
                    },
                    'ontology_updated' : {
                        title : 'Last Ontology Update',
                        description : "Last time ontologies were updated."
                    },
                    'system_bucket' : {
                        title : 'System Bucket',
                        description : "Name of S3 Bucket used for system data."
                    },
                    'aggregations' : {
                        title : 'Aggregations',
                        description : "Aggregations of ES-indexed data."
                    }
                }} />

                <Button className="refresh-counts-button pull-right mt-2" onClick={this.getCounts}>  Refresh Counts</Button>

Database Counts

                <ItemDetailList excludedKeys={ItemDetailList.Detail.defaultProps.excludedKeys.concat(['content'])} hideButtons context={_.pick(this.state, 'db_es_total', 'db_es_compare')} schemas={this.props.schemas} keyTitleDescriptionMap={{
                    'db_es_total' : {
                        title : "DB and ES Counts",
                        description : "Total counts of items in database and elasticsearch."
                    },
                    'db_es_compare' : {
                        title : "DB and ES Counts by Type",
                        description : "Counts of items in database and elasticsearch for each doc_type index."
                    }
                }} />

ES Contents

                <HealthChart mounted={this.state.mounted} width={width} height={400} />

        );
    }
}

content_views.register(HealthView, 'Health');

class HealthChart extends React.Component {

    static defaultProps = {
        'mounted' : false,
    }

    constructor(props){
        super(props);
        this.state = {
            'loaded' : false,
            'data' : null
        };
    }

    componentDidMount(){
        this.load();
    }

    componentDidUpdate(){
        if (this.state.loaded) this.drawTreeMap();
    }

    load(){
        ajax.load('/bar_plot_aggregations/type=Item&field=@type/?field=@type', (r)=>{
            this.setState({
                data : r,
                loaded : true
            });
        });
    }

    drawTreeMap(){
        var { width, height, mounted } = this.props;

        var svg = this.refs && this.refs.svg && d3.select(this.refs.svg);

        var fader = function(color) { return d3.interpolateRgb(color, "#fff")(0.2); };
        var color = d3.scaleOrdinal(d3.schemeCategory20.map(fader));

        var d3Data = vizUtil.transformBarPlotAggregationsToD3CompatibleHierarchy(this.state.data);

        var treemap = d3.treemap()
            .tile(d3.treemapResquarify)
            .size([width, height])
            .round(true)
            .paddingInner(1);

        var root = d3.hierarchy(d3Data)
            .eachBefore(function(d) {d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })
            .sum(function(d){ return d.size; })
            .sort(function(a, b) { return b.height - a.height || b.value - a.value; });

        treemap(root);

        var cell = svg.selectAll("g")
            .data(root.leaves())
            .enter().append("g")
            .attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; })
            .attr("data-tip", function(d){ return d.data.name + " - \n" + d.data.size + ' Items'; })
            .attr("data-effect", function(d){ return 'float'; });

        cell.append("rect")
            .attr("id", function(d) { return d.data.id; })
            .attr("width", function(d) { return d.x1 - d.x0; })
            .attr("height", function(d) { return d.y1 - d.y0; })
            .attr("fill", function(d) { return color(d.data.id); });

        cell.append("clipPath")
            .attr("id", function(d) { return "clip-" + d.data.id; })
            .append("use")
            .attr("xlink:href", function(d) { return "#" + d.data.id; });

        cell.append("text")
            .attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; })
            .selectAll("tspan")
            .data(function(d) { return d.data.name.split(/(?=[A-Z][^A-Z])/g); })
            .enter().append("tspan")
            .attr("x", 4)
            .attr("y", function(d, i) { return 13 + i * 10; })
            .text(function(d) { return d; });

        ReactTooltip.rebuild();
    }

    render(){
        var { width, height, mounted } = this.props;

        if (!mounted || !this.state.loaded || !this.state.data) return null;

        return (
            <svg width={width} height={height} ref="svg"/>
        );

    }

}
<commit_msg>Exclude OntologyTerm<commit_after>'use strict';

import React from 'react';
import PropTypes from 'prop-types';
import url from 'url';
import { Button } from 'react-bootstrap';
import { content_views } from './../globals';
import { ajax, console, object, Filters, JWT, DateUtility, layout } from './../util';
import { ItemPageTitle, ItemDetailList } from './components';
import ReactTooltip from 'react-tooltip';
import * as vizUtil from './../viz/utilities';
import * as d3 from 'd3';
import _ from 'underscore';
import JSONTree from 'react-json-tree';

/**
 * Fallback content_view for pages which are not specifically 'Items.
 * Renders out JSON.
 *
 * @export
 * @class Item
 * @extends {React.Component}
 */
export class HealthView extends React.Component {

    static propTypes = {
        'href' : PropTypes.string
    }

    constructor(props){
        super(props);
        this.render = this.render.bind(this);
        this.componentDidMount = this.componentDidMount.bind(this);
        this.getCounts = _.throttle(this.getCounts.bind(this), 1000);
        this.state = {
            'db_es_total' : "loading...",
            'db_es_compare' : "loading...",
            'mounted' : false
        };
    }

    componentDidMount(){
        this.setState({ 'mounted' : true });
        this.getCounts();
    }

    getCounts(){

        ajax.load('/counts?format=json', (resp)=>{
            this.setState({
                'db_es_total' : resp.db_es_total,
                'db_es_compare': resp.db_es_compare,
            });
        }, 'GET', (resp)=>{
            this.setState({
                'error' : resp.error || resp.message,
                'db_es_total' : null,
                'db_es_compare': null
            });
        });
    }

    render() {
        var context = this.props.context;
        var title = typeof context.title == "string" ? context.title : url.parse(this.props.href).path;

        var width = null;
        if (this.state.mounted){
            width = layout.gridContainerWidth();
        }

        return (

Configuration

                {typeof context.description == "string" ?
{context.description}
 : null}
                <ItemDetailList excludedKeys={ItemDetailList.Detail.defaultProps.excludedKeys.concat(['content'])} hideButtons context={context} schemas={this.props.schemas} keyTitleDescriptionMap={{
                    'blob_bucket' : {
                        title : "Blob Bucket",
                        description : "Name of S3 bucket used for blob data."
                    },
                    'content' : {
                        title : "Extra Information"
                    },
                    'database' : {
                        title : "Database Location",
                        description : "URI used to connect to the back-end PgSQL DB."
                    },
                    'elasticsearch' : {
                        title : "ElasticSearch Location",
                        description : "URI used to connect to the back-end ES instance."
                    },
                    'db_es_total' : {
                        title : "DB and ES Counts",
                        description : "Total counts of items in database and elasticsearch."
                    },
                    'db_es_compare' : {
                        title : "DB and ES Counts by Type",
                        description : "Counts of items in database and elasticsearch for each doc_type index."
                    },
                    'file_upload_bucket' : {
                        title : "File Upload Bucket",
                        description : "Where uploaded files are stored."
                    },
                    'load_data' : {
                        title : "Data Loaded",
                        description : "Data which was loaded into database on initialization or boot."
                    },
                    'ontology_updated' : {
                        title : 'Last Ontology Update',
                        description : "Last time ontologies were updated."
                    },
                    'system_bucket' : {
                        title : 'System Bucket',
                        description : "Name of S3 Bucket used for system data."
                    },
                    'aggregations' : {
                        title : 'Aggregations',
                        description : "Aggregations of ES-indexed data."
                    }
                }} />

                <Button className="refresh-counts-button pull-right mt-2" onClick={this.getCounts}>  Refresh Counts</Button>

Database Counts

                <ItemDetailList excludedKeys={ItemDetailList.Detail.defaultProps.excludedKeys.concat(['content'])} hideButtons context={_.pick(this.state, 'db_es_total', 'db_es_compare')} schemas={this.props.schemas} keyTitleDescriptionMap={{
                    'db_es_total' : {
                        title : "DB and ES Counts",
                        description : "Total counts of items in database and elasticsearch."
                    },
                    'db_es_compare' : {
                        title : "DB and ES Counts by Type",
                        description : "Counts of items in database and elasticsearch for each doc_type index."
                    }
                }} />

                <HealthChart mounted={this.state.mounted} width={width} height={400} />

        );
    }
}

content_views.register(HealthView, 'Health');

class HealthChart extends React.Component {

    static defaultProps = {
        'mounted' : false,
    }

    constructor(props){
        super(props);
        this.state = {
            'loaded' : false,
            'data' : null
        };
    }

    componentDidMount(){
        this.load();
    }

    componentDidUpdate(){
        if (this.state.loaded) this.drawTreeMap();
    }

    load(){
        ajax.load('/bar_plot_aggregations/type=Item&field=@type/?field=@type', (r)=>{
            this.setState({
                data : r,
                loaded : true
            });
        });
    }

    drawTreeMap(){
        var { width, height, mounted } = this.props;

        var svg = this.refs && this.refs.svg && d3.select(this.refs.svg);

        var fader = function(color) { return d3.interpolateRgb(color, "#fff")(0.2); };
        var color = d3.scaleOrdinal(d3.schemeCategory20.map(fader));

        var d3Data = vizUtil.transformBarPlotAggregationsToD3CompatibleHierarchy(this.state.data);

        // Exclude ontology terms -
        d3Data.children = _.filter(d3Data.children, function(typeAggregation){ return typeAggregation.name !== 'OntologyTerm'; });

        var treemap = d3.treemap()
            .tile(d3.treemapResquarify)
            .size([width, height])
            .round(true)
            .paddingInner(1);

        var root = d3.hierarchy(d3Data)
            .eachBefore(function(d) {d.data.id = (d.parent ? d.parent.data.id + "." : "") + d.data.name; })
            .sum(function(d){ return d.size; })
            .sort(function(a, b) { return b.height - a.height || b.value - a.value; });

        treemap(root);

        var cell = svg.selectAll("g")
            .data(root.leaves())
            .enter().append("g")
            .attr("transform", function(d) { return "translate(" + d.x0 + "," + d.y0 + ")"; })
            .attr("data-tip", function(d){ return d.data.name + " - \n" + d.data.size + ' Items'; })
            .attr("data-effect", function(d){ return 'float'; });

        cell.append("rect")
            .attr("id", function(d) { return d.data.id; })
            .attr("width", function(d) { return d.x1 - d.x0; })
            .attr("height", function(d) { return d.y1 - d.y0; })
            .attr("fill", function(d) { return color(d.data.id); });

        cell.append("clipPath")
            .attr("id", function(d) { return "clip-" + d.data.id; })
            .append("use")
            .attr("xlink:href", function(d) { return "#" + d.data.id; });

        cell.append("text")
            .attr("clip-path", function(d) { return "url(#clip-" + d.data.id + ")"; })
            .selectAll("tspan")
            .data(function(d) { return d.data.name.split(/(?=[A-Z][^A-Z])/g); })
            .enter().append("tspan")
            .attr("x", 4)
            .attr("y", function(d, i) { return 13 + i * 10; })
            .text(function(d) { return d; });

        ReactTooltip.rebuild();
    }

    render(){
        var { width, height, mounted } = this.props;

        if (!mounted || !this.state.loaded || !this.state.data) return null;

        return (

Excluding OntologyTerm

ES Types by Count

                <svg width={width} height={height} ref="svg"/>

        );

    }

}
<|endoftext|>


<commit_before>/*
Script: ART.Widget.js

License:
	MIT-style license.
*/

// Base widget class. Based on » http://gist.github.com/85837
(function(){

var counting, focused;

ART.Widget = new Class({

	Implements: [Options, Events],

	ns: 'art',
	name: 'widget',

	options: {
		// onShow: $empty,
		// onHide: $empty,
		// onFocus: $empty,
		// onBlur: $empty,
		// onEnable: $empty,
		// onDisable: $empty,
		// onDestroy: $empty,
		// onAdoption: $empty(parentWidget),
		// onOrphan: $empty(previousParent),
		// id: null,
		// style: null,
		renderWhileHidden: false,
		className: '',
		keyboardOptions: {
			active: true
		},
		parentWidget: null
	},

	initialize: function(options){
		this.latchEvents('adopt');
		this.pseudos = [];
		this.childWidgets = [];
		this.keyboard = new Keyboard();
		this.keyboard.widget = this;
		var parent;
		if (options) {
			if (options.parentWidget) {
				parent = options.parentWidget;
				delete options.parentWidget;
			}
		}
		this.setOptions(options);
		this.keyboard.setOptions(this.options.keyboardOptions).setup();
		this.prefix = this.ns + '-' + this.name;
		this.element = new Element('div', {
			id: this.options.id || this.prefix+new Date().getTime(),
			'class': this.options.className
		}).store(this.prefix, this).store('widget', this);
		this.element.addClass(this.ns).addClass(this.prefix);
		this.classes = this.options.classes;
		this.classes = (this.options.className) ? this.options.className.split(' ') : [];
		if (parent) this.setParent(parent);
	},

	getSelector: function(){
		var selector = (this.parentWidget) ? this.parentWidget.getSelector() + ' ' : '';
		selector += this.name;
		if (this.classes.length) selector += '.' + this.classes.join('.');
		if (this.pseudos.length) selector += ':' + this.pseudos.join(':');
		return selector;
	},

	addPseudo: function(pseudo){
		this.pseudos.include(pseudo);
	},

	removePseudo: function(pseudo){
		this.pseudos.erase(pseudo);
	},

	setParent: function(widget, initing){
		if (!initing) this.removeParent();
		this.parentWidget = widget;
		widget.childWidgets.include(this);
		this.parentWidget.keyboard.manage(this.keyboard);
		if (this.parentWidget.disabled) {
			this.disabledByParent = true;
			this.disable();
		} else {
			this.disabled = true;
			this.enable();
		}
		this.render();
		this.fireEvent('adoption', widget);
		return this;
	},

	removeParent: function(){
		this.prevParent = this.parentWidget || this.prevParent;
		if (!this.prevParent) return;
		if (this.parentWidget) {
			this.parentWidget.childWidgets.erase(this);
			Keyboard.manager.manage(this.keyboard);
			this.parentWidget = null;
		}
		this.fireEvent('orphan', this.prevParent);
	},

	// render

	requireToRender: function(){
		this.requiredToRender = this.requiredToRender || [];
		$A(arguments).each(function(requirement) {
			this.requiredToRender.push(requirement);
		}, this);
	},

	readyToRender: function() {
		if (!this.requiredToRender || !this.requiredToRender.length) return;
		$A(arguments).each(function(requirement) {
			this.requiredToRender.erase(requirement);
		}, this);
	},

	redraw: function(){
		if (counting) this._counters();
		this.childWidgets.each(function(child) {
			child.render();
		});
		return this;
	},

	isReadyToRender: function(){
		var isReady = (!this.requiredToRender || !this.requiredToRender.length) && !this.destroyed && (this.options.renderWhileHidden || !this.hidden);
		if (isReady && this.parentWidget) isReady = this.parentWidget.isReadyToRender();
		return isReady;
	},

	render: function(override){
		if (this.isReadyToRender()) this.redraw(override);
		return this;
	},

	// special states

	hide: function(){
		if (!this.hidden){
			this.hidden = true;
			this.fireEvent('hide');
			this.element.addClass(this.prefix + '-hidden');
			this.addPseudo('hidden');
			this.render();
		}
		return this;
	},

	activate: function(){
		if (!this.active){
			this.active = true;
			this.fireEvent('activate');
			this.element.addClass(this.prefix + '-active');
			this.addPseudo('active');
			this.render();
		}
		return this;
	},

	focus: function(){
		if (!this.focused){
			this.focused = true;
			this.fireEvent('focus');
			this.element.addClass(this.prefix + '-focused');
			this.addPseudo('focus');
			if (focused) focused.blur();
			this.keyboard.activate();
			focused = this;
			this.render();
		}
		return this;
	},

	disable: function(byParent){
		this.disabledByParent = false;
		if (!this.disabled){
			if (byParent) this.disabledByParent = true;
			this.blur();
			this.disabled = true;
			this.fireEvent('disable');
			this.keyboard.deactivate();
			this.childWidgets.each(function(child){
				child.disable(true);
			});
			this.element.addClass(this.prefix + '-disabled');
			this.addPseudo('disabled');
			this.render();
		}
		return this;
	},

	// normal states

	show: function(){
		if (this.hidden){
			this.hidden = false;
			this.fireEvent('show');
			this.element.removeClass(this.prefix + '-hidden');
			this.removePseudo('hidden');
			this.render();
		}
		return this;
	},

	deactivate: function(){
		if (this.active){
			this.active = false;
			this.fireEvent('deactivate');
			this.element.removeClass(this.prefix + '-active');
			this.removePseudo('active');
			this.render();
		}
		return this;
	},

	blur: function(){
		if (this.focused){
			this.focused = false;
			this.fireEvent('blur');
			this.element.removeClass(this.prefix + '-focused');
			this.removePseudo('focus');
			this.render();
		}
		return this;
	},

	enable: function(){
		if (this.parentWidget && this.parentWidget.disabled) {
			this.disabledByParent = true;
			return;
		};
		if (this.disabled){
			this.disabled = false;
			this.keyboard.activate();
			this.childWidgets.each(function(child){
				if (child.disabledByParent) {
					child.enable();
					delete child.disabledByParent;
				}
			});
			this.fireEvent('enable');
			this.element.removeClass(this.prefix + '-disabled');
			this.removePseudo('disabled');
			this.render();
		}
		return this;
	},

	attachKeys: function(events){
		this.keyboard.addEvents(events);
	},

	detachKeys: function(events) {
		this.keyboard.removeEvents(events);
	},

	destroy: function(){
		this.removeParent();
		this.childWidgets.each(function(child) {
			child.destroy();
		});
		this.element.destroy();
		this.destroyed = true;
		return this.fireEvent('destroy');
	},

	// toElement

	toElement: function(){
		return this.element;
	},

	_redrawCount: 0,
	_counters: function(){
		this._redrawCount++;
		$clear(this._countTimer);
		this._countTimer = (function(){
			this._redrawCount = 0;
		}).delay(1000, this);
		dbug.log('redraw %s: ', this.name, this._redrawCount, this.element);
	}

});

ART._counts = function(){
	counting = !counting;
	if (counting) dbug.log('counting widget renders');
	else dbug.log('disabling widget render counts');
};

})();<commit_msg>optimizing rendering a bit more. making sure child elements render properly.<commit_after>/*
Script: ART.Widget.js

License:
	MIT-style license.
*/

// Base widget class. Based on » http://gist.github.com/85837
(function(){

var counting, focused;

ART.Widget = new Class({

	Implements: [Options, Events],

	ns: 'art',
	name: 'widget',

	options: {
		// onShow: $empty,
		// onHide: $empty,
		// onFocus: $empty,
		// onBlur: $empty,
		// onEnable: $empty,
		// onDisable: $empty,
		// onDestroy: $empty,
		// onAdoption: $empty(parentWidget),
		// onOrphan: $empty(previousParent),
		// id: null,
		// style: null,
		renderWhileHidden: false,
		className: '',
		keyboardOptions: {
			active: true
		},
		parentWidget: null
	},

	initialize: function(options){
		this.latchEvents('adopt');
		this.pseudos = [];
		this.childWidgets = [];
		this.keyboard = new Keyboard();
		this.keyboard.widget = this;
		var parent;
		if (options) {
			if (options.parentWidget) {
				parent = options.parentWidget;
				delete options.parentWidget;
			}
		}
		this.setOptions(options);
		this.keyboard.setOptions(this.options.keyboardOptions).setup();
		this.prefix = this.ns + '-' + this.name;
		this.element = new Element('div', {
			id: this.options.id || this.prefix+new Date().getTime(),
			'class': this.options.className
		}).store(this.prefix, this).store('widget', this);
		this.element.addClass(this.ns).addClass(this.prefix);
		this.classes = this.options.classes;
		this.classes = (this.options.className) ? this.options.className.split(' ') : [];
		if (parent) this.setParent(parent);
	},

	getSelector: function(){
		var selector = (this.parentWidget) ? this.parentWidget.getSelector() + ' ' : '';
		selector += this.name;
		if (this.classes.length) selector += '.' + this.classes.join('.');
		if (this.pseudos.length) selector += ':' + this.pseudos.join(':');
		return selector;
	},

	addPseudo: function(pseudo){
		this.pseudos.include(pseudo);
	},

	removePseudo: function(pseudo){
		this.pseudos.erase(pseudo);
	},

	setParent: function(widget, initing){
		if (!initing) this.removeParent();
		this.parentWidget = widget;
		widget.childWidgets.include(this);
		this.parentWidget.keyboard.manage(this.keyboard);
		this.disabled = true;
		this.enable();
		this.fireEvent('adoption', widget);
		return this;
	},

	removeParent: function(){
		this.prevParent = this.parentWidget || this.prevParent;
		if (!this.prevParent) return;
		if (this.parentWidget) {
			this.parentWidget.childWidgets.erase(this);
			Keyboard.manager.manage(this.keyboard);
			this.parentWidget = null;
		}
		this.fireEvent('orphan', this.prevParent);
	},

	// render

	requireToRender: function(){
		this.requiredToRender = this.requiredToRender || [];
		$A(arguments).each(function(requirement) {
			this.requiredToRender.push(requirement);
		}, this);
	},

	readyToRender: function() {
		if (!this.requiredToRender || !this.requiredToRender.length) return;
		$A(arguments).each(function(requirement) {
			this.requiredToRender.erase(requirement);
		}, this);
	},

	redraw: function(){
		if (counting) this._counters();
		this.childWidgets.each(function(child) {
			child.render();
		});
		return this;
	},

	isReadyToRender: function(){
		var isReady = (!this.requiredToRender || !this.requiredToRender.length) && !this.destroyed && (this.options.renderWhileHidden || !this.hidden);
		if (isReady && this.parentWidget) isReady = this.parentWidget.isReadyToRender();
		return isReady;
	},

	render: function(override){
		if (this.isReadyToRender()) this.redraw(override);
		return this;
	},

	// special states

	hide: function(){
		if (!this.hidden){
			this.hidden = true;
			this.fireEvent('hide');
			this.element.addClass(this.prefix + '-hidden');
			this.addPseudo('hidden');
			this.render();
		}
		return this;
	},

	activate: function(){
		if (!this.active){
			this.active = true;
			this.fireEvent('activate');
			this.element.addClass(this.prefix + '-active');
			this.addPseudo('active');
			this.render();
		}
		return this;
	},

	focus: function(){
		if (!this.focused){
			this.focused = true;
			this.fireEvent('focus');
			this.element.addClass(this.prefix + '-focused');
			this.addPseudo('focus');
			if (focused) focused.blur();
			this.keyboard.activate();
			focused = this;
			this.render();
		}
		return this;
	},

	disable: function(byParent){
		this.disabledByParent = false;
		if (!this.disabled){
			if (byParent) this.disabledByParent = true;
			this.blur();
			this.disabled = true;
			this.fireEvent('disable');
			this.keyboard.deactivate();
			this.childWidgets.each(function(child){
				child.disable(true);
			});
			this.element.addClass(this.prefix + '-disabled');
			this.addPseudo('disabled');
			this.render();
		}
		return this;
	},

	// normal states

	show: function(){
		if (this.hidden){
			this.hidden = false;
			this.fireEvent('show');
			this.element.removeClass(this.prefix + '-hidden');
			this.removePseudo('hidden');
			this.render();
		}
		return this;
	},

	deactivate: function(){
		if (this.active){
			this.active = false;
			this.fireEvent('deactivate');
			this.element.removeClass(this.prefix + '-active');
			this.removePseudo('active');
			this.render();
		}
		return this;
	},

	blur: function(){
		if (this.focused){
			this.focused = false;
			this.fireEvent('blur');
			this.element.removeClass(this.prefix + '-focused');
			this.removePseudo('focus');
			this.render();
		}
		return this;
	},

	enable: function(){
		if (this.parentWidget && this.parentWidget.disabled) {
			this.disabledByParent = true;
			return this;
		};
		delete this.disabledByParent;
		if (this.disabled){
			this.disabled = false;
			this.keyboard.activate();
			this.fireEvent('enable');
			this.element.removeClass(this.prefix + '-disabled');
			this.removePseudo('disabled');
			this.render();
		}
		this.childWidgets.each(function(child){
			if (child.disabledByParent) {
				child.enable();
				delete child.disabledByParent;
			}
		});
		return this;
	},

	attachKeys: function(events){
		this.keyboard.addEvents(events);
	},

	detachKeys: function(events) {
		this.keyboard.removeEvents(events);
	},

	destroy: function(){
		this.removeParent();
		this.childWidgets.each(function(child) {
			child.destroy();
		});
		this.element.destroy();
		this.destroyed = true;
		return this.fireEvent('destroy');
	},

	// toElement

	toElement: function(){
		return this.element;
	},

	_redrawCount: 0,
	_counters: function(){
		this._redrawCount++;
		$clear(this._countTimer);
		this._countTimer = (function(){
			this._redrawCount = 0;
		}).delay(1000, this);
		console.log('redraw %s: ', this.name, this._redrawCount, this.element);
	}

});

ART._counts = function(){
	counting = !counting;
	if (counting) console.log('counting widget renders');
	else console.log('disabling widget render counts');
};

})();<|endoftext|>