<commit_before>var assert = require('assert')
var bufferutils = require('./bufferutils')
var ops = require('./opcodes')
var scripts = require('./scripts')

var Address = require('./address')
var ECPair = require('./ecpair')
var ECSignature = require('./ecsignature')
var Script = require('./script')
var Transaction = require('./transaction')

function extractInput (txIn) {
  var redeemScript
  var scriptSig = txIn.script
  var prevOutScript
  var prevOutType = scripts.classifyInput(scriptSig, true)
  var scriptType

  // Re-classify if scriptHash
  if (prevOutType === 'scripthash') {
    redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0])
    prevOutScript = scripts.scriptHashOutput(redeemScript.getHash())

    scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1))
    scriptType = scripts.classifyInput(scriptSig, true)
  } else {
    scriptType = prevOutType
  }

  // Extract hashType, pubKeys and signatures
  var hashType, parsed, pubKeys, signatures

  switch (scriptType) {
    case 'pubkeyhash': {
      parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
      hashType = parsed.hashType
      pubKeys = scriptSig.chunks.slice(1)
      signatures = [parsed.signature]
      prevOutScript = ECPair.fromPublicKeyBuffer(pubKeys[0]).getAddress().toOutputScript()

      break
    }

    case 'pubkey': {
      parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
      hashType = parsed.hashType
      signatures = [parsed.signature]

      if (redeemScript) {
        pubKeys = redeemScript.chunks.slice(0, 1)
      }

      break
    }

    case 'multisig': {
      signatures = scriptSig.chunks.slice(1).map(function (chunk) {
        if (chunk === ops.OP_0) return chunk

        var parsed = ECSignature.parseScriptSignature(chunk)
        hashType = parsed.hashType

        return parsed.signature
      })

      if (redeemScript) {
        pubKeys = redeemScript.chunks.slice(1, -2)
      }

      break
    }
  }

  return {
    hashType: hashType,
    prevOutScript: prevOutScript,
    prevOutType: prevOutType,
    pubKeys: pubKeys,
    redeemScript: redeemScript,
    scriptType: scriptType,
    signatures: signatures
  }
}

function TransactionBuilder () {
  this.prevTxMap = {}
  this.prevOutScripts = {}
  this.prevOutTypes = {}

  this.inputs = []
  this.tx = new Transaction()
}

TransactionBuilder.fromTransaction = function (transaction) {
  var txb = new TransactionBuilder()

  // Copy other transaction fields
  txb.tx.version = transaction.version
  txb.tx.locktime = transaction.locktime

  // Extract/add inputs
  transaction.ins.forEach(function (txIn) {
    txb.addInput(txIn.hash, txIn.index, txIn.sequence)
  })

  // Extract/add outputs
  transaction.outs.forEach(function (txOut) {
    txb.addOutput(txOut.script, txOut.value)
  })

  // Extract/add signatures
  txb.inputs = transaction.ins.map(function (txIn) {
    // TODO: remove me after testcase added
    assert(!Transaction.isCoinbaseHash(txIn.hash), 'coinbase inputs not supported')

    // Ignore empty scripts
    if (txIn.script.buffer.length === 0) return {}

    return extractInput(txIn)
  })

  return txb
}

TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
  // is it a txId?
  if (typeof txHash === 'string') {
    // a txId is big-endian hex, we want a little-endian Buffer
    txHash = new Buffer(txHash, 'hex')
    Array.prototype.reverse.call(txHash)

  // is it a Transaction?
  } else if (txHash instanceof Transaction) {
    prevOutScript = txHash.outs[vout].script
    txHash = txHash.getHash()
  }

  var input = {}
  if (prevOutScript) {
    var prevOutType = scripts.classifyOutput(prevOutScript)

    // if we can, extract pubKey information
    switch (prevOutType) {
      case 'multisig': {
        input.pubKeys = prevOutScript.chunks.slice(1, -2)
        break
      }

      case 'pubkey': {
        input.pubKeys = prevOutScript.chunks.slice(0, 1)
        break
      }
    }

    if (prevOutType !== 'scripthash') {
      input.scriptType = prevOutType
    }

    input.prevOutScript = prevOutScript
    input.prevOutType = prevOutType
  }

  assert(this.inputs.every(function (input2) {
    if (input2.hashType === undefined) return true

    return input2.hashType & Transaction.SIGHASH_ANYONECANPAY
  }), 'No, this would invalidate signatures')

  var prevOut = txHash.toString('hex') + ':' + vout
  assert(!(prevOut in this.prevTxMap), 'Transaction is already an input')

  var vin = this.tx.addInput(txHash, vout, sequence)
  this.inputs[vin] = input
  this.prevTxMap[prevOut] = vin

  return vin
}

TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
  assert(this.inputs.every(function (input) {
    if (input.hashType === undefined) return true

    return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE
  }), 'No, this would invalidate signatures')

  // Attempt to get a valid address if it's a base58 address string
  if (typeof scriptPubKey === 'string') {
    scriptPubKey = Address.fromBase58Check(scriptPubKey)
  }

  // Attempt to get a valid script if it's an Address object
  if (scriptPubKey instanceof Address) {
    scriptPubKey = scriptPubKey.toOutputScript()
  }

  return this.tx.addOutput(scriptPubKey, value)
}

TransactionBuilder.prototype.build = function () {
  return this.__build(false)
}
TransactionBuilder.prototype.buildIncomplete = function () {
  return this.__build(true)
}

var canSignTypes = {
  'pubkeyhash': true,
  'multisig': true,
  'pubkey': true
}

TransactionBuilder.prototype.__build = function (allowIncomplete) {
  if (!allowIncomplete) {
    assert(this.tx.ins.length > 0, 'Transaction has no inputs')
    assert(this.tx.outs.length > 0, 'Transaction has no outputs')
  }

  var tx = this.tx.clone()

  // Create script signatures from signature meta-data
  this.inputs.forEach(function (input, index) {
    var scriptType = input.scriptType
    var scriptSig

    if (!allowIncomplete) {
      assert(!!scriptType, 'Transaction is not complete')
      assert(scriptType in canSignTypes, scriptType + ' not supported')
      assert(input.signatures, 'Transaction is missing signatures')
    }

    if (input.signatures) {
      switch (scriptType) {
        case 'pubkeyhash': {
          var pkhSignature = input.signatures[0].toScriptSignature(input.hashType)
          scriptSig = scripts.pubKeyHashInput(pkhSignature, input.pubKeys[0])
          break
        }

        case 'multisig': {
          // Array.prototype.map is sparse-compatible
          var msSignatures = input.signatures.map(function (signature) {
            return signature && signature.toScriptSignature(input.hashType)
          })

          // fill in blanks with OP_0
          if (allowIncomplete) {
            for (var i = 0; i < msSignatures.length; ++i) {
              if (msSignatures[i]) continue

              msSignatures[i] = ops.OP_0
            }
          } else {
            // Array.prototype.filter returns non-sparse array
            msSignatures = msSignatures.filter(function (x) { return x })
          }

          var redeemScript = allowIncomplete ? undefined : input.redeemScript
          scriptSig = scripts.multisigInput(msSignatures, redeemScript)
          break
        }

        case 'pubkey': {
          var pkSignature = input.signatures[0].toScriptSignature(input.hashType)
          scriptSig = scripts.pubKeyInput(pkSignature)
          break
        }
      }
    }

    // did we build a scriptSig?
    if (scriptSig) {
      // wrap as scriptHash if necessary
      if (input.prevOutType === 'scripthash') {
        scriptSig = scripts.scriptHashInput(scriptSig, input.redeemScript)
      }

      tx.setInputScript(index, scriptSig)
    }
  })

  return tx
}

TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) {
  assert(index in this.inputs, 'No input at index: ' + index)
  hashType = hashType || Transaction.SIGHASH_ALL

  var input = this.inputs[index]
  var canSign = input.hashType &&
    input.prevOutScript &&
    input.prevOutType &&
    input.pubKeys &&
    input.scriptType &&
    input.signatures

  var kpPubKey = keyPair.getPublicKeyBuffer()

  // are we almost ready to sign?
  if (canSign) {
    // if redeemScript was provided, enforce consistency
    if (redeemScript) {
      assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript')
    }

    assert.equal(input.hashType, hashType, 'Inconsistent hashType')

  // no? prepare
  } else {
    // must be pay-to-scriptHash?
    if (redeemScript) {
      // if we have a prevOutScript, enforce scriptHash equality to the redeemScript
      if (input.prevOutScript) {
        assert.equal(input.prevOutType, 'scripthash', 'PrevOutScript must be P2SH')

        var scriptHash = input.prevOutScript.chunks[1]
        assert.deepEqual(scriptHash, redeemScript.getHash(), 'RedeemScript does not match ' + scriptHash.toString('hex'))
      }

      var scriptType = scripts.classifyOutput(redeemScript)
      assert(scriptType in canSignTypes, 'RedeemScript not supported (' + scriptType + ')')

      var pubKeys = []
      switch (scriptType) {
        case 'multisig': {
          pubKeys = redeemScript.chunks.slice(1, -2)
          break
        }

        case 'pubkeyhash': {
          var pkh1 = redeemScript.chunks[2]
          var pkh2 = keyPair.getAddress().hash

          assert.deepEqual(pkh1, pkh2, 'privateKey cannot sign for this input')
          pubKeys = [kpPubKey]
          break
        }

        case 'pubkey': {
          pubKeys = redeemScript.chunks.slice(0, 1)
          break
        }
      }

      if (!input.prevOutScript) {
        input.prevOutScript = scripts.scriptHashOutput(redeemScript.getHash())
        input.prevOutType = 'scripthash'
      }

      input.pubKeys = pubKeys
      input.redeemScript = redeemScript
      input.scriptType = scriptType

    // cannot be pay-to-scriptHash
    } else {
      assert.notEqual(input.prevOutType, 'scripthash', 'PrevOutScript is P2SH, missing redeemScript')

      // can we otherwise sign this?
      if (input.scriptType) {
        assert(input.pubKeys, input.scriptType + ' not supported')

      // we know nothin' Jon Snow, assume pubKeyHash
      } else {
        input.prevOutScript = keyPair.getAddress().toOutputScript()
        input.prevOutType = 'pubkeyhash'
        input.pubKeys = [kpPubKey]
        input.scriptType = input.prevOutType
      }
    }

    input.hashType = hashType
    input.signatures = input.signatures || []
  }

  var signatureScript = input.redeemScript || input.prevOutScript
  var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType)

  // enforce signature order matches public keys
  if (input.scriptType === 'multisig' && input.redeemScript && input.signatures.length !== input.pubKeys.length) {
    // maintain a local copy of unmatched signatures
    var unmatched = input.signatures.slice()

    input.signatures = input.pubKeys.map(function (pubKey) {
      var match
      var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey)

      // check for any matching signatures
      unmatched.some(function (signature, i) {
        if (!keyPair2.verify(signatureHash, signature)) return false
        match = signature

        // remove matched signature from unmatched
        unmatched.splice(i, 1)

        return true
      })

      return match || undefined
    })
  }

  // enforce in order signing of public keys
  assert(input.pubKeys.some(function (pubKey, i) {
    if (kpPubKey.compare(pubKey) !== 0) return false

    assert(!input.signatures[i], 'Signature already exists')

    var signature = keyPair.sign(signatureHash)
    input.signatures[i] = signature

    return true
  }), 'key pair cannot sign for this input')
}

module.exports = TransactionBuilder
<commit_msg>txbuilder: remove unused bufferutils import<commit_after>var assert = require('assert')
var ops = require('./opcodes')
var scripts = require('./scripts')

var Address = require('./address')
var ECPair = require('./ecpair')
var ECSignature = require('./ecsignature')
var Script = require('./script')
var Transaction = require('./transaction')

function extractInput (txIn) {
  var redeemScript
  var scriptSig = txIn.script
  var prevOutScript
  var prevOutType = scripts.classifyInput(scriptSig, true)
  var scriptType

  // Re-classify if scriptHash
  if (prevOutType === 'scripthash') {
    redeemScript = Script.fromBuffer(scriptSig.chunks.slice(-1)[0])
    prevOutScript = scripts.scriptHashOutput(redeemScript.getHash())

    scriptSig = Script.fromChunks(scriptSig.chunks.slice(0, -1))
    scriptType = scripts.classifyInput(scriptSig, true)
  } else {
    scriptType = prevOutType
  }

  // Extract hashType, pubKeys and signatures
  var hashType, parsed, pubKeys, signatures

  switch (scriptType) {
    case 'pubkeyhash': {
      parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
      hashType = parsed.hashType
      pubKeys = scriptSig.chunks.slice(1)
      signatures = [parsed.signature]
      prevOutScript = ECPair.fromPublicKeyBuffer(pubKeys[0]).getAddress().toOutputScript()

      break
    }

    case 'pubkey': {
      parsed = ECSignature.parseScriptSignature(scriptSig.chunks[0])
      hashType = parsed.hashType
      signatures = [parsed.signature]

      if (redeemScript) {
        pubKeys = redeemScript.chunks.slice(0, 1)
      }

      break
    }

    case 'multisig': {
      signatures = scriptSig.chunks.slice(1).map(function (chunk) {
        if (chunk === ops.OP_0) return chunk

        var parsed = ECSignature.parseScriptSignature(chunk)
        hashType = parsed.hashType

        return parsed.signature
      })

      if (redeemScript) {
        pubKeys = redeemScript.chunks.slice(1, -2)
      }

      break
    }
  }

  return {
    hashType: hashType,
    prevOutScript: prevOutScript,
    prevOutType: prevOutType,
    pubKeys: pubKeys,
    redeemScript: redeemScript,
    scriptType: scriptType,
    signatures: signatures
  }
}

function TransactionBuilder () {
  this.prevTxMap = {}
  this.prevOutScripts = {}
  this.prevOutTypes = {}

  this.inputs = []
  this.tx = new Transaction()
}

TransactionBuilder.fromTransaction = function (transaction) {
  var txb = new TransactionBuilder()

  // Copy other transaction fields
  txb.tx.version = transaction.version
  txb.tx.locktime = transaction.locktime

  // Extract/add inputs
  transaction.ins.forEach(function (txIn) {
    txb.addInput(txIn.hash, txIn.index, txIn.sequence)
  })

  // Extract/add outputs
  transaction.outs.forEach(function (txOut) {
    txb.addOutput(txOut.script, txOut.value)
  })

  // Extract/add signatures
  txb.inputs = transaction.ins.map(function (txIn) {
    // TODO: remove me after testcase added
    assert(!Transaction.isCoinbaseHash(txIn.hash), 'coinbase inputs not supported')

    // Ignore empty scripts
    if (txIn.script.buffer.length === 0) return {}

    return extractInput(txIn)
  })

  return txb
}

TransactionBuilder.prototype.addInput = function (txHash, vout, sequence, prevOutScript) {
  // is it a txId?
  if (typeof txHash === 'string') {
    // a txId is big-endian hex, we want a little-endian Buffer
    txHash = new Buffer(txHash, 'hex')
    Array.prototype.reverse.call(txHash)

  // is it a Transaction?
  } else if (txHash instanceof Transaction) {
    prevOutScript = txHash.outs[vout].script
    txHash = txHash.getHash()
  }

  var input = {}
  if (prevOutScript) {
    var prevOutType = scripts.classifyOutput(prevOutScript)

    // if we can, extract pubKey information
    switch (prevOutType) {
      case 'multisig': {
        input.pubKeys = prevOutScript.chunks.slice(1, -2)
        break
      }

      case 'pubkey': {
        input.pubKeys = prevOutScript.chunks.slice(0, 1)
        break
      }
    }

    if (prevOutType !== 'scripthash') {
      input.scriptType = prevOutType
    }

    input.prevOutScript = prevOutScript
    input.prevOutType = prevOutType
  }

  assert(this.inputs.every(function (input2) {
    if (input2.hashType === undefined) return true

    return input2.hashType & Transaction.SIGHASH_ANYONECANPAY
  }), 'No, this would invalidate signatures')

  var prevOut = txHash.toString('hex') + ':' + vout
  assert(!(prevOut in this.prevTxMap), 'Transaction is already an input')

  var vin = this.tx.addInput(txHash, vout, sequence)
  this.inputs[vin] = input
  this.prevTxMap[prevOut] = vin

  return vin
}

TransactionBuilder.prototype.addOutput = function (scriptPubKey, value) {
  assert(this.inputs.every(function (input) {
    if (input.hashType === undefined) return true

    return (input.hashType & 0x1f) === Transaction.SIGHASH_SINGLE
  }), 'No, this would invalidate signatures')

  // Attempt to get a valid address if it's a base58 address string
  if (typeof scriptPubKey === 'string') {
    scriptPubKey = Address.fromBase58Check(scriptPubKey)
  }

  // Attempt to get a valid script if it's an Address object
  if (scriptPubKey instanceof Address) {
    scriptPubKey = scriptPubKey.toOutputScript()
  }

  return this.tx.addOutput(scriptPubKey, value)
}

TransactionBuilder.prototype.build = function () {
  return this.__build(false)
}
TransactionBuilder.prototype.buildIncomplete = function () {
  return this.__build(true)
}

var canSignTypes = {
  'pubkeyhash': true,
  'multisig': true,
  'pubkey': true
}

TransactionBuilder.prototype.__build = function (allowIncomplete) {
  if (!allowIncomplete) {
    assert(this.tx.ins.length > 0, 'Transaction has no inputs')
    assert(this.tx.outs.length > 0, 'Transaction has no outputs')
  }

  var tx = this.tx.clone()

  // Create script signatures from signature meta-data
  this.inputs.forEach(function (input, index) {
    var scriptType = input.scriptType
    var scriptSig

    if (!allowIncomplete) {
      assert(!!scriptType, 'Transaction is not complete')
      assert(scriptType in canSignTypes, scriptType + ' not supported')
      assert(input.signatures, 'Transaction is missing signatures')
    }

    if (input.signatures) {
      switch (scriptType) {
        case 'pubkeyhash': {
          var pkhSignature = input.signatures[0].toScriptSignature(input.hashType)
          scriptSig = scripts.pubKeyHashInput(pkhSignature, input.pubKeys[0])
          break
        }

        case 'multisig': {
          // Array.prototype.map is sparse-compatible
          var msSignatures = input.signatures.map(function (signature) {
            return signature && signature.toScriptSignature(input.hashType)
          })

          // fill in blanks with OP_0
          if (allowIncomplete) {
            for (var i = 0; i < msSignatures.length; ++i) {
              if (msSignatures[i]) continue

              msSignatures[i] = ops.OP_0
            }
          } else {
            // Array.prototype.filter returns non-sparse array
            msSignatures = msSignatures.filter(function (x) { return x })
          }

          var redeemScript = allowIncomplete ? undefined : input.redeemScript
          scriptSig = scripts.multisigInput(msSignatures, redeemScript)
          break
        }

        case 'pubkey': {
          var pkSignature = input.signatures[0].toScriptSignature(input.hashType)
          scriptSig = scripts.pubKeyInput(pkSignature)
          break
        }
      }
    }

    // did we build a scriptSig?
    if (scriptSig) {
      // wrap as scriptHash if necessary
      if (input.prevOutType === 'scripthash') {
        scriptSig = scripts.scriptHashInput(scriptSig, input.redeemScript)
      }

      tx.setInputScript(index, scriptSig)
    }
  })

  return tx
}

TransactionBuilder.prototype.sign = function (index, keyPair, redeemScript, hashType) {
  assert(index in this.inputs, 'No input at index: ' + index)
  hashType = hashType || Transaction.SIGHASH_ALL

  var input = this.inputs[index]
  var canSign = input.hashType &&
    input.prevOutScript &&
    input.prevOutType &&
    input.pubKeys &&
    input.scriptType &&
    input.signatures

  var kpPubKey = keyPair.getPublicKeyBuffer()

  // are we almost ready to sign?
  if (canSign) {
    // if redeemScript was provided, enforce consistency
    if (redeemScript) {
      assert.deepEqual(input.redeemScript, redeemScript, 'Inconsistent redeemScript')
    }

    assert.equal(input.hashType, hashType, 'Inconsistent hashType')

  // no? prepare
  } else {
    // must be pay-to-scriptHash?
    if (redeemScript) {
      // if we have a prevOutScript, enforce scriptHash equality to the redeemScript
      if (input.prevOutScript) {
        assert.equal(input.prevOutType, 'scripthash', 'PrevOutScript must be P2SH')

        var scriptHash = input.prevOutScript.chunks[1]
        assert.deepEqual(scriptHash, redeemScript.getHash(), 'RedeemScript does not match ' + scriptHash.toString('hex'))
      }

      var scriptType = scripts.classifyOutput(redeemScript)
      assert(scriptType in canSignTypes, 'RedeemScript not supported (' + scriptType + ')')

      var pubKeys = []
      switch (scriptType) {
        case 'multisig': {
          pubKeys = redeemScript.chunks.slice(1, -2)
          break
        }

        case 'pubkeyhash': {
          var pkh1 = redeemScript.chunks[2]
          var pkh2 = keyPair.getAddress().hash

          assert.deepEqual(pkh1, pkh2, 'privateKey cannot sign for this input')
          pubKeys = [kpPubKey]
          break
        }

        case 'pubkey': {
          pubKeys = redeemScript.chunks.slice(0, 1)
          break
        }
      }

      if (!input.prevOutScript) {
        input.prevOutScript = scripts.scriptHashOutput(redeemScript.getHash())
        input.prevOutType = 'scripthash'
      }

      input.pubKeys = pubKeys
      input.redeemScript = redeemScript
      input.scriptType = scriptType

    // cannot be pay-to-scriptHash
    } else {
      assert.notEqual(input.prevOutType, 'scripthash', 'PrevOutScript is P2SH, missing redeemScript')

      // can we otherwise sign this?
      if (input.scriptType) {
        assert(input.pubKeys, input.scriptType + ' not supported')

      // we know nothin' Jon Snow, assume pubKeyHash
      } else {
        input.prevOutScript = keyPair.getAddress().toOutputScript()
        input.prevOutType = 'pubkeyhash'
        input.pubKeys = [kpPubKey]
        input.scriptType = input.prevOutType
      }
    }

    input.hashType = hashType
    input.signatures = input.signatures || []
  }

  var signatureScript = input.redeemScript || input.prevOutScript
  var signatureHash = this.tx.hashForSignature(index, signatureScript, hashType)

  // enforce signature order matches public keys
  if (input.scriptType === 'multisig' && input.redeemScript && input.signatures.length !== input.pubKeys.length) {
    // maintain a local copy of unmatched signatures
    var unmatched = input.signatures.slice()

    input.signatures = input.pubKeys.map(function (pubKey) {
      var match
      var keyPair2 = ECPair.fromPublicKeyBuffer(pubKey)

      // check for any matching signatures
      unmatched.some(function (signature, i) {
        if (!keyPair2.verify(signatureHash, signature)) return false
        match = signature

        // remove matched signature from unmatched
        unmatched.splice(i, 1)

        return true
      })

      return match || undefined
    })
  }

  // enforce in order signing of public keys
  assert(input.pubKeys.some(function (pubKey, i) {
    if (kpPubKey.compare(pubKey) !== 0) return false

    assert(!input.signatures[i], 'Signature already exists')

    var signature = keyPair.sign(signatureHash)
    input.signatures[i] = signature

    return true
  }), 'key pair cannot sign for this input')
}

module.exports = TransactionBuilder
<|endoftext|>


<commit_before>import uiModules from 'ui/modules';
import 'angular-ui-bootstrap';

const app = uiModules.get('api/sentinl', ['ui.bootstrap']);
export { app };
<commit_msg>Kibana now uses a new api route.<commit_after>import uiModules from 'ui/modules';
import 'angular-ui-bootstrap';

const app = uiModules.get('apps/sentinl', ['ui.bootstrap']);
export { app };
<|endoftext|>


<commit_before>angular.module('ui.bootstrap.typeahead', [])

/**
 * A helper service that can parse typeahead's syntax (string provided by users)
 * Extracted to a separate service for ease of unit testing
 */
  .factory('typeaheadParser', ['$parse', function ($parse) {

  //                      00000111000000000000022200000000000000003333333333333330000000000044000
  var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;

  return {
    parse:function (input) {

      var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
      if (!match) {
        throw new Error(
          "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
            " but got '" + input + "'.");
      }

      return {
        itemName:match[3],
        source:$parse(match[4]),
        viewMapper:$parse(match[2] || match[1]),
        modelMapper:$parse(match[1])
      };
    }
  };
}])

  //options - min length
  .directive('typeahead', ['$compile', '$q', '$document', 'typeaheadParser', function ($compile, $q, $document, typeaheadParser) {

  var HOT_KEYS = [9, 13, 27, 38, 40];

  return {
    require:'ngModel',
    link:function (originalScope, element, attrs, modelCtrl) {

      var selected = modelCtrl.$modelValue;

      //minimal no of characters that needs to be entered before typeahead kicks-in
      var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;

      //expressions used by typeahead
      var parserResult = typeaheadParser.parse(attrs.typeahead);

      //create a child scope for the typeahead directive so we are not polluting original scope
      //with typeahead-specific data (matches, query etc.)
      var scope = originalScope.$new();
      originalScope.$on('$destroy', function(){
        scope.$destroy();
      });

      var resetMatches = function() {
        scope.matches = [];
        scope.activeIdx = -1;
      };

      var getMatchesAsync = function(inputValue) {

        var locals = {$viewValue: inputValue};
        $q.when(parserResult.source(scope, locals)).then(function(matches) {

          //it might happen that several async queries were in progress if a user were typing fast
          //but we are interested only in responses that correspond to the current view value
          if (inputValue === modelCtrl.$viewValue) {
            if (matches.length > 0) {

              scope.activeIdx = 0;
              scope.matches.length = 0;

              //transform labels
              for(var i=0; i<matches.length; i++) {
                locals[parserResult.itemName] = matches[i];
                scope.matches.push({
                  label: parserResult.viewMapper(scope, locals),
                  model: matches[i]
                });
              }

              scope.query = inputValue;

            } else {
              resetMatches();
            }
          }
        }, resetMatches);
      };

      resetMatches();

      //we need to propagate user's query so we can higlight matches
      scope.query = undefined;

      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
      //$parsers kick-in on all the changes coming from the vview as well as manually triggered by $setViewValue
      modelCtrl.$parsers.push(function (inputValue) {

        resetMatches();
        if (selected) {
          return inputValue;
        } else {
          if (inputValue && inputValue.length >= minSearch) {
            getMatchesAsync(inputValue);
          }
        }

        return undefined;
      });

      modelCtrl.$render = function () {
        var locals = {};
        locals[parserResult.itemName] = selected;
        element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue);
        selected = undefined;
      };

      scope.select = function (activeIdx) {
        //called from within the $digest() cycle
        var locals = {};
        locals[parserResult.itemName] = selected = scope.matches[activeIdx].model;

        modelCtrl.$setViewValue(parserResult.modelMapper(scope, locals));
        modelCtrl.$render();
      };

      //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(9)
      element.bind('keydown', function (evt) {

        //typeahead is open and an "interesting" key was pressed
        if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
          return;
        }

        evt.preventDefault();

        if (evt.which === 40) {
          scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
          scope.$digest();

        } else if (evt.which === 38) {
          scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
          scope.$digest();

        } else if (evt.which === 13 || evt.which === 9) {
          scope.$apply(function () {
            scope.select(scope.activeIdx);
          });

        } else if (evt.which === 27) {
          scope.matches = [];
          scope.$digest();
        }
      });

      $document.find('body').bind('click', function(){
        scope.matches = [];
        scope.$digest();
      });

      var tplElCompiled = $compile("<typeahead-popup matches='matches' active='activeIdx' select='select(activeIdx)' "+
        "query='query'></typeahead-popup>")(scope);
      element.after(tplElCompiled);
    }
  };

}])

  .directive('typeaheadPopup', function () {
    return {
      restrict:'E',
      scope:{
        matches:'=',
        query:'=',
        active:'=',
        select:'&'
      },
      replace:true,
      templateUrl:'template/typeahead/typeahead.html',
      link:function (scope, element, attrs) {

        scope.isOpen = function () {
          return scope.matches.length > 0;
        };

        scope.isActive = function (matchIdx) {
          return scope.active == matchIdx;
        };

        scope.selectActive = function (matchIdx) {
          scope.active = matchIdx;
        };

        scope.selectMatch = function (activeIdx) {
          scope.select({activeIdx:activeIdx});
        };
      }
    };
  })

  .filter('typeaheadHighlight', function() {
    return function(matchItem, query) {
      return (query) ? matchItem.replace(new RegExp(query, 'gi'), '$&') : query;
    };
  });<commit_msg>fix(typeahead): stop keydown event propagation when ESC pressed to discard matches<commit_after>angular.module('ui.bootstrap.typeahead', [])

/**
 * A helper service that can parse typeahead's syntax (string provided by users)
 * Extracted to a separate service for ease of unit testing
 */
  .factory('typeaheadParser', ['$parse', function ($parse) {

  //                      00000111000000000000022200000000000000003333333333333330000000000044000
  var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/;

  return {
    parse:function (input) {

      var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source;
      if (!match) {
        throw new Error(
          "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" +
            " but got '" + input + "'.");
      }

      return {
        itemName:match[3],
        source:$parse(match[4]),
        viewMapper:$parse(match[2] || match[1]),
        modelMapper:$parse(match[1])
      };
    }
  };
}])

  //options - min length
  .directive('typeahead', ['$compile', '$q', '$document', 'typeaheadParser', function ($compile, $q, $document, typeaheadParser) {

  var HOT_KEYS = [9, 13, 27, 38, 40];

  return {
    require:'ngModel',
    link:function (originalScope, element, attrs, modelCtrl) {

      var selected = modelCtrl.$modelValue;

      //minimal no of characters that needs to be entered before typeahead kicks-in
      var minSearch = originalScope.$eval(attrs.typeaheadMinLength) || 1;

      //expressions used by typeahead
      var parserResult = typeaheadParser.parse(attrs.typeahead);

      //create a child scope for the typeahead directive so we are not polluting original scope
      //with typeahead-specific data (matches, query etc.)
      var scope = originalScope.$new();
      originalScope.$on('$destroy', function(){
        scope.$destroy();
      });

      var resetMatches = function() {
        scope.matches = [];
        scope.activeIdx = -1;
      };

      var getMatchesAsync = function(inputValue) {

        var locals = {$viewValue: inputValue};
        $q.when(parserResult.source(scope, locals)).then(function(matches) {

          //it might happen that several async queries were in progress if a user were typing fast
          //but we are interested only in responses that correspond to the current view value
          if (inputValue === modelCtrl.$viewValue) {
            if (matches.length > 0) {

              scope.activeIdx = 0;
              scope.matches.length = 0;

              //transform labels
              for(var i=0; i<matches.length; i++) {
                locals[parserResult.itemName] = matches[i];
                scope.matches.push({
                  label: parserResult.viewMapper(scope, locals),
                  model: matches[i]
                });
              }

              scope.query = inputValue;

            } else {
              resetMatches();
            }
          }
        }, resetMatches);
      };

      resetMatches();

      //we need to propagate user's query so we can higlight matches
      scope.query = undefined;

      //plug into $parsers pipeline to open a typeahead on view changes initiated from DOM
      //$parsers kick-in on all the changes coming from the vview as well as manually triggered by $setViewValue
      modelCtrl.$parsers.push(function (inputValue) {

        resetMatches();
        if (selected) {
          return inputValue;
        } else {
          if (inputValue && inputValue.length >= minSearch) {
            getMatchesAsync(inputValue);
          }
        }

        return undefined;
      });

      modelCtrl.$render = function () {
        var locals = {};
        locals[parserResult.itemName] = selected;
        element.val(parserResult.viewMapper(scope, locals) || modelCtrl.$viewValue);
        selected = undefined;
      };

      scope.select = function (activeIdx) {
        //called from within the $digest() cycle
        var locals = {};
        locals[parserResult.itemName] = selected = scope.matches[activeIdx].model;

        modelCtrl.$setViewValue(parserResult.modelMapper(scope, locals));
        modelCtrl.$render();
      };

      //bind keyboard events: arrows up(38) / down(40), enter(13) and tab(9), esc(27)
      element.bind('keydown', function (evt) {

        //typeahead is open and an "interesting" key was pressed
        if (scope.matches.length === 0 || HOT_KEYS.indexOf(evt.which) === -1) {
          return;
        }

        evt.preventDefault();

        if (evt.which === 40) {
          scope.activeIdx = (scope.activeIdx + 1) % scope.matches.length;
          scope.$digest();

        } else if (evt.which === 38) {
          scope.activeIdx = (scope.activeIdx ? scope.activeIdx : scope.matches.length) - 1;
          scope.$digest();

        } else if (evt.which === 13 || evt.which === 9) {
          scope.$apply(function () {
            scope.select(scope.activeIdx);
          });

        } else if (evt.which === 27) {
          evt.stopPropagation();
          scope.matches = [];
          scope.$digest();
        }
      });

      $document.find('body').bind('click', function(){
        scope.matches = [];
        scope.$digest();
      });

      var tplElCompiled = $compile("<typeahead-popup matches='matches' active='activeIdx' select='select(activeIdx)' "+
        "query='query'></typeahead-popup>")(scope);
      element.after(tplElCompiled);
    }
  };

}])

  .directive('typeaheadPopup', function () {
    return {
      restrict:'E',
      scope:{
        matches:'=',
        query:'=',
        active:'=',
        select:'&'
      },
      replace:true,
      templateUrl:'template/typeahead/typeahead.html',
      link:function (scope, element, attrs) {

        scope.isOpen = function () {
          return scope.matches.length > 0;
        };

        scope.isActive = function (matchIdx) {
          return scope.active == matchIdx;
        };

        scope.selectActive = function (matchIdx) {
          scope.active = matchIdx;
        };

        scope.selectMatch = function (activeIdx) {
          scope.select({activeIdx:activeIdx});
        };
      }
    };
  })

  .filter('typeaheadHighlight', function() {
    return function(matchItem, query) {
      return (query) ? matchItem.replace(new RegExp(query, 'gi'), '$&') : query;
    };
  });<|endoftext|>


<commit_before>function(doc) {
	if (doc.type != "Vrac") {
		return;
	}

	var date = doc.valide.date_saisie;
	var statut = doc.valide.statut;
	var vendeur = {identifiant: doc.vendeur_identifiant, intitule: doc.vendeur.intitule, raison_sociale: doc.vendeur.raison_sociale, date_validation: doc.valide.date_validation_vendeur};
	var acheteur = {identifiant: doc.acheteur_identifiant, intitule: doc.acheteur.intitule, raison_sociale: doc.acheteur.raison_sociale, date_validation: doc.valide.date_validation_acheteur};
	var mandataire = {identifiant: doc.mandataire_identifiant, intitule: doc.mandataire.intitule, raison_sociale: doc.mandataire.raison_sociale, date_validation: doc.valide.date_validation_mandataire};
	var numero = doc.numero_contrat;
	var numero_visa = doc.numero_visa;
	var etape = doc.etape;
	var type_contrat = doc.type_contrat;

	if (doc.vendeur_identifiant) {
		var is_proprietaire = (doc.vendeur_identifiant == doc.createur_identifiant)? 1 : 0;
		emit([doc.vendeur_identifiant, type_contrat, doc.campagne, doc.valide.statut], {soussignes: {vendeur: vendeur, acheteur: acheteur, mandataire: mandataire}, is_proprietaire: is_proprietaire, date: date, statut: statut, numero: numero, numero_visa: numero_visa, etape: etape, type_contrat: type_contrat});
	}
	if (doc.acheteur_identifiant) {
		var is_proprietaire = (doc.acheteur_identifiant == doc.createur_identifiant)? 1 : 0;
		emit([doc.acheteur_identifiant, type_contrat, doc.campagne, doc.valide.statut], {soussignes: {vendeur: vendeur, acheteur: acheteur, mandataire: mandataire}, is_proprietaire: is_proprietaire, date: date, statut: statut, numero: numero, numero_visa: numero_visa, etape: etape, type_contrat: type_contrat});
	}
	if (doc.mandataire_identifiant) {
		var is_proprietaire = (doc.mandataire_identifiant == doc.createur_identifiant)? 1 : 0;
		emit([doc.mandataire_identifiant, type_contrat, doc.campagne, doc.valide.statut], {soussignes: {vendeur: vendeur, acheteur: acheteur, mandataire: mandataire}, is_proprietaire: is_proprietaire, date: date, statut: statut, numero: numero, numero_visa: numero_visa, etape: etape, type_contrat: type_contrat});
	}
}<commit_msg>Mise à jour de la vue avec les roles<commit_after>function(doc) {
	if (doc.type != "Vrac") {
		return;
	}

	var date = doc.valide.date_saisie;
	var statut = doc.valide.statut;
	var vendeur = {identifiant: doc.vendeur_identifiant, intitule: doc.vendeur.intitule, raison_sociale: doc.vendeur.raison_sociale, date_validation: doc.valide.date_validation_vendeur};
	var acheteur = {identifiant: doc.acheteur_identifiant, intitule: doc.acheteur.intitule, raison_sociale: doc.acheteur.raison_sociale, date_validation: doc.valide.date_validation_acheteur};
	var mandataire = {identifiant: doc.mandataire_identifiant, intitule: doc.mandataire.intitule, raison_sociale: doc.mandataire.raison_sociale, date_validation: doc.valide.date_validation_mandataire};
	var numero = doc.numero_contrat;
	var numero_visa = doc.numero_visa;
	var etape = doc.etape;
	var type_contrat = doc.type_contrat;

	if (doc.vendeur_identifiant) {
		var is_proprietaire = (doc.vendeur_identifiant == doc.createur_identifiant)? 1 : 0;
		emit([doc.vendeur_identifiant, type_contrat, doc.campagne, doc.valide.statut], {role: "vendeur", soussignes: {vendeur: vendeur, acheteur: acheteur, mandataire: mandataire}, is_proprietaire: is_proprietaire, date: date, statut: statut, numero: numero, numero_visa: numero_visa, etape: etape, type_contrat: type_contrat});
	}
	if (doc.acheteur_identifiant) {
		var is_proprietaire = (doc.acheteur_identifiant == doc.createur_identifiant)? 1 : 0;
		emit([doc.acheteur_identifiant, type_contrat, doc.campagne, doc.valide.statut], {role: "acheteur", soussignes: {vendeur: vendeur, acheteur: acheteur, mandataire: mandataire}, is_proprietaire: is_proprietaire, date: date, statut: statut, numero: numero, numero_visa: numero_visa, etape: etape, type_contrat: type_contrat});
	}
	if (doc.mandataire_identifiant) {
		var is_proprietaire = (doc.mandataire_identifiant == doc.createur_identifiant)? 1 : 0;
		emit([doc.mandataire_identifiant, type_contrat, doc.campagne, doc.valide.statut], {role: "mandataire", soussignes: {vendeur: vendeur, acheteur: acheteur, mandataire: mandataire}, is_proprietaire: is_proprietaire, date: date, statut: statut, numero: numero, numero_visa: numero_visa, etape: etape, type_contrat: type_contrat});
	}
}<|endoftext|>


<commit_before>import React from 'react'
import RadioGroup from 'react-radio-group';
import Component from './component';

import AppActions from '../action/app';

export default class CreateProject extends Component {
	/**
	 *
	 */
	constructor () {
		super();

		this._bind(
			'onNameChange',
			'onScopeChange',
			'onValueChange',
			'onCreateProject'
		);

		this.state = {
			name: '',
			scope: '',
			value: ''
		};
	}

	/**
	 *
	 */
	render () {
		return (

							{'Name:'}

								<input
									name='projectName'
									type='text'
									placeholder='Project Name'
									onChange={this.onNameChange} />

							{'Scope:'}

								<RadioGroup name='scope' onChange={this.onScopeChange} selectedValue={this.state.scope}>
									{Radio => (

											<Radio value='solo' />{'Solo'}
											<Radio value='group' />{'Group'}

									)}
								</RadioGroup>

							{'Value:'}

								<input
									ref='projectValue'
									name='projectValue'
									type='text'
									placeholder='Project Value'
									onChange={this.onValueChange} />

				<button onClick={this.onCreateProject}>{'Create'}</button>

		);
	}

	/**
	 *
	 */
	onNameChange (e) {
		this.setState({
			name: e.target.value
		});
	}

	/**
	 *
	 */
	onScopeChange (scope) {
		this.setState({
			scope
		});
	}

	/**
	 *
	 */
	onValueChange (e) {
		this.setState({
			value: e.target.value
		});
	}

	/**
	 *
	 */
	onCreateProject () {
		if (!this.state.scope) {
			// complain
			return;
		}

		AppActions.createProject(this.state);
	}
}
<commit_msg>Moving from `<input>` to `<textarea>`<commit_after>import React from 'react'
import RadioGroup from 'react-radio-group';
import Component from './component';

import AppActions from '../action/app';

export default class CreateProject extends Component {
	/**
	 *
	 */
	constructor () {
		super();

		this._bind(
			'onNameChange',
			'onScopeChange',
			'onMarkdownChange',
			'onCreateProject'
		);

		this.state = {
			name: '',
			scope: '',
			markdown: ''
		};
	}

	/**
	 *
	 */
	render () {
		return (

							{'Name:'}

								<input
									name='projectName'
									type='text'
									placeholder='Project Name'
									onChange={this.onNameChange} />

							{'Scope:'}

								<RadioGroup name='scope' onChange={this.onScopeChange} selectedValue={this.state.scope}>
									{Radio => (

											<Radio value='solo' />{'Solo'}
											<Radio value='group' />{'Group'}

									)}
								</RadioGroup>

							{'Markdown:'}

								<textarea
									ref='projectMarkdown'
									name='projectMarkdown'
									placeholder='Project Markdown'
									onChange={this.onMarkdownChange} />

								<button onClick={this.onCreateProject}>{'Create'}</button>

		);
	}

	/**
	 *
	 */
	onNameChange (e) {
		this.setState({
			name: e.target.value
		});
	}

	/**
	 *
	 */
	onScopeChange (scope) {
		this.setState({
			scope
		});
	}

	/**
	 *
	 */
	onMarkdownChange (e) {
		this.setState({
			markdown: e.target.value
		});
	}

	/**
	 *
	 */
	onCreateProject () {
		if (!this.state.scope) {
			// complain
			return;
		}

		AppActions.createProject(this.state);
	}
}
<|endoftext|>


<commit_before>/*
Copyright (c) 2011, Chris Umbel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var PorterStemmer = require('../stemmers/porter_stemmer'),
util = require('util'),
events = require('events');

var Classifier = function(classifier, stemmer) {
    this.classifier = classifier;
    this.docs = [];
    this.features = {};
    this.stemmer = stemmer || PorterStemmer;
    this.lastAdded = 0;
    this.events = new events.EventEmitter();
};

function addDocument(text, classification) {

    // Ignore further processing if classification is undefined
    if(typeof classification === 'undefined') return;

    // If classification is type of string then make sure it's dosen't have blank space at both end
    if(typeof classification === 'sytring'){
      classification = classification.trim();
    }

    if(typeof text === 'string')
	text = this.stemmer.tokenizeAndStem(text);

    if(text.length === 0) {
        // ignore empty documents
        return;
    }

    this.docs.push({
	label: classification,
	text: text
    });

    for (var i = 0; i < text.length; i++) {
        var token = text[i];
        this.features[token] = (this.features[token] || 0) + 1;
    }
}

function removeDocument(text, classification) {
  var docs = this.docs
    , doc
    , pos;

  if (typeof text === 'string') {
    text = this.stemmer.tokenizeAndStem(text);
  }

  for (var i = 0, ii = docs.length; i < ii; i++) {
    doc = docs[i];
    if (doc.text.join(' ') == text.join(' ') &&
        doc.label == classification) {
      pos = i;
    }
  }

  // Remove if there's a match
  if (!isNaN(pos)) {
    this.docs.splice(pos, 1);

    for (var i = 0, ii = text.length; i < ii; i++) {
      delete this.features[text[i]];
    }
  }
}

function textToFeatures(observation) {
    var features = [];

    if(typeof observation === 'string')
	observation = this.stemmer.tokenizeAndStem(observation);

    for(var feature in this.features) {
        if(observation.indexOf(feature) > -1)
            features.push(1);
        else
            features.push(0);
    }

    return features;
}

function train() {
    var totalDocs = this.docs.length;
    for(var i = this.lastAdded; i < totalDocs; i++) {
        var features = this.textToFeatures(this.docs[i].text);
        this.classifier.addExample(features, this.docs[i].label);
        this.events.emit('trainedWithDocument', {index: i, total: totalDocs, doc: this.docs[i]});
        this.lastAdded++;
    }
    this.events.emit('doneTraining', true);
    this.classifier.train();
}

function retrain() {
  this.classifier = new (this.classifier.constructor)();
  this.lastAdded = 0;
  this.train();
}

function getClassifications(observation) {
    return this.classifier.getClassifications(this.textToFeatures(observation));
}

function classify(observation) {
    return this.classifier.classify(this.textToFeatures(observation));
}

function restore(classifier, stemmer) {
    classifier.stemmer = stemmer || PorterStemmer;
    classifier.events = new events.EventEmitter();
    return classifier;
}

function save(filename, callback) {
    var data = JSON.stringify(this);
    var fs = require('fs');
    var classifier = this;
    fs.writeFile(filename, data, 'utf8', function(err) {
        if(callback) {
            callback(err, err ? null : classifier);
        }
    });
}

function load(filename, callback) {
    var fs = require('fs');

    fs.readFile(filename, 'utf8', function(err, data) {
        var classifier;

        if(!err) {
            classifier = JSON.parse(data);
        }

        if(callback)
            callback(err, classifier);
    });
}

Classifier.prototype.addDocument = addDocument;
Classifier.prototype.removeDocument = removeDocument;
Classifier.prototype.train = train;
Classifier.prototype.retrain = retrain;
Classifier.prototype.classify = classify;
Classifier.prototype.textToFeatures = textToFeatures;
Classifier.prototype.save = save;
Classifier.prototype.getClassifications = getClassifications;
Classifier.restore = restore;
Classifier.load = load;

module.exports = Classifier;
<commit_msg>Fixed typo<commit_after>/*
Copyright (c) 2011, Chris Umbel

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

var PorterStemmer = require('../stemmers/porter_stemmer'),
util = require('util'),
events = require('events');

var Classifier = function(classifier, stemmer) {
    this.classifier = classifier;
    this.docs = [];
    this.features = {};
    this.stemmer = stemmer || PorterStemmer;
    this.lastAdded = 0;
    this.events = new events.EventEmitter();
};

function addDocument(text, classification) {

    // Ignore further processing if classification is undefined
    if(typeof classification === 'undefined') return;

    // If classification is type of string then make sure it's dosen't have blank space at both end
    if(typeof classification === 'string'){
      classification = classification.trim();
    }

    if(typeof text === 'string')
	text = this.stemmer.tokenizeAndStem(text);

    if(text.length === 0) {
        // ignore empty documents
        return;
    }

    this.docs.push({
	label: classification,
	text: text
    });

    for (var i = 0; i < text.length; i++) {
        var token = text[i];
        this.features[token] = (this.features[token] || 0) + 1;
    }
}

function removeDocument(text, classification) {
  var docs = this.docs
    , doc
    , pos;

  if (typeof text === 'string') {
    text = this.stemmer.tokenizeAndStem(text);
  }

  for (var i = 0, ii = docs.length; i < ii; i++) {
    doc = docs[i];
    if (doc.text.join(' ') == text.join(' ') &&
        doc.label == classification) {
      pos = i;
    }
  }

  // Remove if there's a match
  if (!isNaN(pos)) {
    this.docs.splice(pos, 1);

    for (var i = 0, ii = text.length; i < ii; i++) {
      delete this.features[text[i]];
    }
  }
}

function textToFeatures(observation) {
    var features = [];

    if(typeof observation === 'string')
	observation = this.stemmer.tokenizeAndStem(observation);

    for(var feature in this.features) {
        if(observation.indexOf(feature) > -1)
            features.push(1);
        else
            features.push(0);
    }

    return features;
}

function train() {
    var totalDocs = this.docs.length;
    for(var i = this.lastAdded; i < totalDocs; i++) {
        var features = this.textToFeatures(this.docs[i].text);
        this.classifier.addExample(features, this.docs[i].label);
        this.events.emit('trainedWithDocument', {index: i, total: totalDocs, doc: this.docs[i]});
        this.lastAdded++;
    }
    this.events.emit('doneTraining', true);
    this.classifier.train();
}

function retrain() {
  this.classifier = new (this.classifier.constructor)();
  this.lastAdded = 0;
  this.train();
}

function getClassifications(observation) {
    return this.classifier.getClassifications(this.textToFeatures(observation));
}

function classify(observation) {
    return this.classifier.classify(this.textToFeatures(observation));
}

function restore(classifier, stemmer) {
    classifier.stemmer = stemmer || PorterStemmer;
    classifier.events = new events.EventEmitter();
    return classifier;
}

function save(filename, callback) {
    var data = JSON.stringify(this);
    var fs = require('fs');
    var classifier = this;
    fs.writeFile(filename, data, 'utf8', function(err) {
        if(callback) {
            callback(err, err ? null : classifier);
        }
    });
}

function load(filename, callback) {
    var fs = require('fs');

    fs.readFile(filename, 'utf8', function(err, data) {
        var classifier;

        if(!err) {
            classifier = JSON.parse(data);
        }

        if(callback)
            callback(err, classifier);
    });
}

Classifier.prototype.addDocument = addDocument;
Classifier.prototype.removeDocument = removeDocument;
Classifier.prototype.train = train;
Classifier.prototype.retrain = retrain;
Classifier.prototype.classify = classify;
Classifier.prototype.textToFeatures = textToFeatures;
Classifier.prototype.save = save;
Classifier.prototype.getClassifications = getClassifications;
Classifier.restore = restore;
Classifier.load = load;

module.exports = Classifier;
<|endoftext|>


<commit_before>import React from 'react';

class license extends React.Component {
    render() {
        return (

Creative Commons Licenses and SlideWiki

SlideWiki is an open platform allowing you to publish educational content under a Creative Commons license. By publishing content under a Creative Commons license, SlideWiki intends to empower educational communities to author, share and reuse educational content in order to improve the availability of educational materials. This page will explain how SlideWiki uses Creative Common licenses.

Creative Commons Attribution ShareAlike 4.0 CC BY-SA

Decks and slides in SlideWiki are published under the Creative Common Attribution ShareAlike licence. This license lets others reuse, alter and build on slides and decks as long as the user credit the orginial author and license their new creations under identical terms. Any external works that is is based on SlideWiki content must carry the same license. This means that any derivatives will also allow for commercial use, but still need to attribute the orginal author and publish under the same license.

Find out more about the CC BY-SA and access the full license text by viewing the Human readable summary of the CC-BY-SA license from Creative Commons.

Why do we use the CC BY-SA license for SlideWiki decks and slides?

The CC BY-SA license is used by Wikipedia and similar resources, which means you can include material from Wikipedia in your decks as long as you include the original source. It also ensures that material created by SlideWiki users is always attributed to the orginal author, which we believe enhances the quality and probity of educational resources.

By using this license, everyone will be free to copy and redistribute the materials from any SlideWiki deck in any medium or format as long as they are published under the same license. SlideWiki material can also be adapted, including remixing, transforming and building on the material for any purpose, including commercial activities.

Can I add any materials to my slides and decks under the CC BY-SA licence?

When adding content to your slides, it is your responsibility to ensure that it can be shared under a Creative Commons Attribution ShareAlike license. This means that the content you create must also have a similar license so that it can be adapted and used by anyone (including for commercial activities). Materials that are in the public domain (sometimes referred to as CC0) or licensed under Creative Commons Attribution (CC BY) can be included in your slides as well as those already publised under Creative Commons Attribution ShareAlike license (CC BY-SA).

Where can I find material that I can use under this license in my decks and slides?

As all decks and slides are published under a license that allows content to be adapted and reused, you can reuse content from any decks in your own slides. The Append Deck and Append Slides function allows you to find and attach  decks and slide as you are creating your own work.

Creative Commons Search lists sources of materials published under creative commons licenses. Some media services such as Flickr, YouTube and Vimeo publish some content under creative commons licenses. Content marked “All rights reserved” cannot be included in SlideWiki.

If you add content to your slides that is published under a Creative Common Attribution ShareAlike licence then you must ensure you add the attribution information into the Sources for the slide. [You can find out more about adding Sources to slides in our Guide]. Images published under a Creative Commons Attribution ShareAlike licence can be uploaded and incorporated into SlideWiki slides. However, it is your responsibility to ensure that you provide credit or attribution to the creator within the image upload dialog.

 If a video or content has an Embed function provided by the video’s owner, then it is possible to add the code to embed the video into your slides. However, caution should be taken with videos that could have been posted without the copyright owner’s permission. [You can find out more about embedding videos in slides in our Guide.]

If you would like to find out more about copyright issues then you may find this article helpful: “The Educator’s Guide To Copyright, Fair Use, And Creative Commons”.

Creative Commons Attribution 4.0 CC BY

This license allows materials to be copied and redistributed in any medium or format as long as they are attributed to the original creator and copyright notices. Images published under a Creative Commons Attribution licence can be uploaded and incorporated into SlideWiki slides. However, it is your responsibility to ensure that you provide credit or attribution to the creator within the image upload dialog.

Find out more about the CC BY and access the full license text by viewing the Human readable summary of the CC BY license from Creative Commons.

Work in the Public Domain, free of copyright or CC0

Work in the Public domain is free of copyright restrictions. This is usually for works that are free from copyright restrictions. This includes works where the copyright has expired or the creator has assigned their work to the public domain. While it is not always necessary to attribute the source of public domain work. However, it is good practice to attribute works to the creator or source to add provienance to your decks and slides. CCO is the Creative Commons license that enables authors and creators to waive their rights to their work and place it in the public domain.

Notices

The SlideWiki website and its content are provided "as is". We offer no warranties, explicit or implied regarding any content, the webiste or the accuracy of any information.  These license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. We reserve the right to remove materials and content that we believe to infringe copyright and license requirements.

        );
    }
}

export default license;
<commit_msg>Translated Licence.js.<commit_after>import React from 'react';
import {FormattedMessage, defineMessages} from 'react-intl';

class license extends React.Component {
    render() {
        return (

<FormattedMessage id="licence.header" defaultMessage="Creative Commons Licenses and SlideWiki"/>

<FormattedMessage id="licence.p1" defaultMessage="SlideWiki is an open platform allowing you to publish educational content under a Creative Commons license. By publishing content under a Creative Commons license, SlideWiki intends to empower educational communities to author, share and reuse educational content in order to improve the availability of educational materials. This page will explain how SlideWiki uses Creative Common licenses."/>

<FormattedMessage id="licence.1.header" defaultMessage="Creative Commons Attribution ShareAlike 4.0 CC BY-SA"/>

<FormattedMessage id="licence.1.p1" defaultMessage="Decks and slides in SlideWiki are published under the Creative Common Attribution ShareAlike licence. This license lets others reuse, alter and build on slides and decks as long as the user credit the orginial author and license their new creations under identical terms. Any external works that is is based on SlideWiki content must carry the same license. This means that any derivatives will also allow for commercial use, but still need to attribute the orginal author and publish under the same license."/>

                  <FormattedMessage id="licence.1.p2"
                    values={{
                        link_1:
                            <FormattedMessage id="licence.summary" defaultMessage="Human readable summary of the CC-BY-SA license from Creative Commons"/>

                    }}
                    defaultMessage="Find out more about the CC BY-SA and access the full license text by viewing the {link_1}."
                  />

<FormattedMessage id="licence.1.1.header" defaultMessage="Why do we use the CC BY-SA license for SlideWiki decks and slides?"/>

<FormattedMessage id="licence.1.1.p1" defaultMessage="The CC BY-SA license is used by Wikipedia and similar resources, which means you can include material from Wikipedia in your decks as long as you include the original source. It also ensures that material created by SlideWiki users is always attributed to the orginal author, which we believe enhances the quality and probity of educational resources."/>

<FormattedMessage id="licence.1.1.p2" defaultMessage="By using this license, everyone will be free to copy and redistribute the materials from any SlideWiki deck in any medium or format as long as they are published under the same license. SlideWiki material can also be adapted, including remixing, transforming and building on the material for any purpose, including commercial activities."/>

<FormattedMessage id="licence.1.2.header" defaultMessage="Can I add any materials to my slides and decks under the CC BY-SA licence?"/>

<FormattedMessage id="licence.1.2.p1" defaultMessage="When adding content to your slides, it is your responsibility to ensure that it can be shared under a Creative Commons Attribution ShareAlike license. This means that the content you create must also have a similar license so that it can be adapted and used by anyone (including for commercial activities). Materials that are in the public domain (sometimes referred to as CC0) or licensed under Creative Commons Attribution (CC BY) can be included in your slides as well as those already publised under Creative Commons Attribution ShareAlike license (CC BY-SA)."/>

<FormattedMessage id="license.1.3.header" defaultMessage="Where can I find material that I can use under this license in my decks and slides?"/>

<FormattedMessage id="licence.1.3.p1" defaultMessage="As all decks and slides are published under a license that allows content to be adapted and reused, you can reuse content from any decks in your own slides. The Append Deck and Append Slides function allows you to find and attach  decks and slide as you are creating your own work."/>

                  <FormattedMessage id="licence.1.3.p2"
                    values={{
                        link_1: <FormattedMessage id="license.ccSearch" defaultMessage="Creative Commons Search"/>
                    }}
                    defaultMessage="{link_1} lists sources of materials published under creative commons licenses. Some media services such as Flickr, YouTube and Vimeo publish some content under creative commons licenses. Content marked “All rights reserved” cannot be included in SlideWiki."
                  />

<FormattedMessage id="licence.1.3.p3" defaultMessage="If you add content to your slides that is published under a Creative Common Attribution ShareAlike licence then you must ensure you add the attribution information into the Sources for the slide. [You can find out more about adding Sources to slides in our Guide]. Images published under a Creative Commons Attribution ShareAlike licence can be uploaded and incorporated into SlideWiki slides. However, it is your responsibility to ensure that you provide credit or attribution to the creator within the image upload dialog."/>

<FormattedMessage id="licence.1.3.p4" defaultMessage="If a video or content has an Embed function provided by the video’s owner, then it is possible to add the code to embed the video into your slides. However, caution should be taken with videos that could have been posted without the copyright owner’s permission. [You can find out more about embedding videos in slides in our Guide.]"/>

                  <FormattedMessage id="licence.1.3.p5"
                    values={{
                        link_1:
                            <FormattedMessage id="licence.copyrightGuide" defaultMessage="“The Educator’s Guide To Copyright, Fair Use, And Creative Commons”"/>

                    }}
                    defaultMessage="If you would like to find out more about copyright issues then you may find this article helpful: {link_1}."
                  />

<FormattedMessage id="licence.2.header" defaultMessage="Creative Commons Attribution 4.0 CC BY"/>

<FormattedMessage id="licence.2.p1" defaultMessage="This license allows materials to be copied and redistributed in any medium or format as long as they are attributed to the original creator and copyright notices. Images published under a Creative Commons Attribution licence can be uploaded and incorporated into SlideWiki slides. However, it is your responsibility to ensure that you provide credit or attribution to the creator within the image upload dialog."/>

                  <FormattedMessage id="licence.2.p2"
                    values={{
                        link_1:
                            <FormattedMessage id="licence.ccSummary" defaultMessage="Human readable summary of the CC BY license from Creative Commons"/>

                    }}
                    defaultMessage="Find out more about the CC BY and access the full license text by viewing the {link_1}."
                  />

<FormattedMessage id="licence.3.header" defaultMessage="Work in the Public Domain, free of copyright or CC0"/>

<FormattedMessage id="licence.3.p1" defaultMessage="Work in the Public domain is free of copyright restrictions. This is usually for works that are free from copyright restrictions. This includes works where the copyright has expired or the creator has assigned their work to the public domain. While it is not always necessary to attribute the source of public domain work. However, it is good practice to attribute works to the creator or source to add provienance to your decks and slides. CCO is the Creative Commons license that enables authors and creators to waive their rights to their work and place it in the public domain."/>

<FormattedMessage id="licence.4.header" defaultMessage="Notices"/>

<FormattedMessage id="licence.4.p1" defaultMessage="The SlideWiki website and its content are provided "as is". We offer no warranties, explicit or implied regarding any content, the webiste or the accuracy of any information.  These license may not give you all of the permissions necessary for your intended use. For example, other rights such as publicity, privacy, or moral rights may limit how you use the material. We reserve the right to remove materials and content that we believe to infringe copyright and license requirements."/>

        );
    }
}

export default license;
<|endoftext|>


<commit_before>/** @jsx React.DOM */
var React = require('react');
var _ = require("lodash");
var d3 = require("d3");
var moment = require("moment");

var WeatherSvgPath = require("./WeatherSvgPath");
var GraphCursor = require("./GraphCursor");

function cmp(a, b) {
    return b.time.getTime() - a.time.getTime();
}

// TODO: this is a sorted array. We could binary search it...
function findClosest(value, arr, start) {
    return arr.reduce(function(best, current) {
        if (Math.abs(cmp(value, current)) < Math.abs(cmp(value, best))) {
            return current;
        } else {
            return best;
        }
    }, start || arr[0]);
}

var WeatherGraph = React.createClass({

    style: {
        backgroundColor: "red"
    },

    componentWillReceiveProps: function(props) {
        this.computeData(props);
    },

    getWidth: function() {
        return window.innerWidth - this.props.margin;
    },

    getHeight: function() {
        // return parseInt(screen.height / 2);
        return Math.max(parseInt(window.innerHeight - 250), 300);
    },

    updateDimensions: function() {
        this.setState({
            width: this.getWidth(),
            height: this.getHeight()
        });
    },

    getInitialState: function() {
        return {
            width: this.getWidth(),
            height: this.getHeight(),

            maxValue: 13,
            minValue: 0,

            startTime: new Date(),
            endTime: new Date(),

            _cursorPosition: null
        };
    },

    getDefaultProps: function() {
        return {
            padding: 30,
            paddingTop: 30,
            margin: 20
        };
    },

    computeData: function(props) {
        props = props || this.props;
        var newState = this.getInitialState();
        console.log("Computing DATA!");

        props.lines.forEach(function(d) {
            _.forEach(d, function(values) {

                var minValue = d3.min(values, function(d) { return d.value; });
                var maxValue = d3.max(values, function(d) { return d.value; });

                var startTime = d3.min(values, function(d) { return d.time; });
                var endTime = d3.max(values, function(d) { return d.time; });

                newState.maxValue = d3.max([maxValue, newState.maxValue]);
                newState.minValue = d3.min([minValue, newState.minValue]);

                newState.startTime = d3.min([startTime, newState.startTime]);
                newState.endTime = d3.max([endTime, newState.endTime]);

            });
        });

        // Ensure that the current is always visible in the graph
        newState.endTime = new Date(
             Math.max(newState.endTime.getTime(), Date.now())
         );
        newState.startTime = new Date(
             Math.min(newState.startTime.getTime(), Date.now())
         );

        this.setState(newState);
    },

    updateScales: function() {
        var state = this.state;

        var xScale = d3.scale.linear()
            .domain([state.startTime, state.endTime])
            .range([this.props.padding, state.width - this.props.padding]);

        var yScale = d3.scale.linear()
            .domain([state.minValue, state.maxValue])
            .range([state.height - this.props.padding, this.props.paddingTop])
            ;

        // educated guess
        var tickLabelWidth = 50;

        var xAxis = d3.svg.axis()
            .scale(xScale)
            .orient("bottom")
            .ticks( parseInt(state.width / tickLabelWidth) )
            .tickFormat(function(d) {
                return d3.time.format("%H:%M")(new Date(d));
            })
            ;

        var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient("left")
            ;

        var yAxisRight = d3.svg.axis()
            .scale(yScale)
            .orient("right")
            ;

        _.extend(this, {
            xScale: xScale,
            yScale: yScale,
            xAxis: xAxis,
            yAxis: yAxis,
            yAxisRight: yAxisRight
        });

    },

    componentWillMount: function() {
        this.updateDimensions();
    },

    renderAxes: function() {
        if (!this.xAxis) return;
        if (!this.yAxis) return;
        if (!this.yAxisRight) return;

        this.xAxis(d3.select(this.refs.xAxis.getDOMNode()));
        this.yAxis(d3.select(this.refs.yAxis.getDOMNode()));
        this.yAxisRight(d3.select(this.refs.yAxisRight.getDOMNode()));
    },

    componentDidMount: function() {
        this.computeData();
        window.addEventListener("resize", this.updateDimensions);
    },

    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    },

    componentDidUpdate: function() {
        this.renderAxes();
    },

    handleTouchMove: function(e) {
        e.preventDefault();
        this.handleMove(e.targetTouches[0].clientX);
    },

    handleMouseMove: function(e) {
        this.handleMove(e.clientX);
    },

    handleMove: function(clientX) {
        if (clientX === null || clientX === undefined) return;
        clientX -= this.refs.svg.getDOMNode().offsetLeft;
        this.setState({ _cursorPosition: clientX });
    },

    getCursorPosition: function() {
        if (this.state._cursorPosition === null) {
            return this.xScale(new Date());
        }
        return this.state._cursorPosition;
    },

    renderLimit: function(desc, color, limit) {
        var bottom = this.state.height;
        bottom -= this.yScale(this.state.maxValue - limit);
        bottom -= this.props.padding;

        var textHeight = 8;
        var textWidth = 100;
        return (
            <g>
                <rect
                    x={this.props.padding}
                    y={this.props.padding}
                    width={this.state.width - this.props.padding*2}
                    height={bottom}
                    fill={color} />

                <text
                    className="limit-text"
                    x={this.state.width - textWidth}
                    y={bottom + this.props.padding - textHeight}
                    fill="black">{desc}</text>

            </g>

        );
    },

    getPointsCloseToCursor: function() {
        var self = this;

        var pos = {
            time: new Date(this.xScale.invert(this.getCursorPosition()))
        };

        return _.compact(this.props.lines.map(function(line) {
            var points = line.forecasts;
            if (pos.time.getTime() < Date.now()) {
                points = line.observations;
            }
            var point = findClosest(pos, points);
            if (!point) return;
            return {
                title: line.title,
                time: point.time,
                value: point.value,
                x: self.xScale(point.time.getTime()),
                y: self.yScale(point.value)
            };
        }));
    },

    render: function() {
        var self = this;
        this.updateScales();

        var circles;
        circles = this.getPointsCloseToCursor().map(function(point) {
            return (
                <circle
                    cx={point.x}
                    cy={point.y}
                    r="5"
                    fill="none"
                    stroke="red"
                />
            );
        });

        return (

                    {this.getPointsCloseToCursor().map(function(point) {
                        return (

                                {point.title}
                                {point.value} m/s
                                {moment(point.time).fromNow()}

                        );
                    })}

                <svg
                    ref="svg"
                    onMouseMove={this.handleMouseMove}
                    onTouchStart={this.handleTouchMove}
                    onTouchMove={this.handleTouchMove}
                    width={this.state.width}
                    height={this.state.height} >

                    {this.renderLimit("Student limit", "lightgray", 8)}
                    {this.renderLimit("License limit", "gray", 11)}

                    <GraphCursor
                        height={self.state.height}
                        cursorPosition={self.getCursorPosition()}
                        padding={self.props.padding}
                        paddingTop={self.props.paddingTop}
                    />

                    {circles}

                    {self.props.lines.map(function(d) {
                        return (
                            <WeatherSvgPath
                                key={d.title}
                                xScale={self.xScale}
                                yScale={self.yScale}
                                observations={d.observations}
                                forecasts={d.forecasts}
                            />
                        );
                    })}

                    <g
                        ref="xAxis"
                        className="axis"
                        transform={ "translate(0, " + (self.state.height - self.props.padding) + ")" }
                    />

                    <g
                        ref="yAxis"
                        className="axis"
                        transform={ "translate(" + self.props.padding + ",0)" }
                    />

                    <g
                        ref="yAxisRight"
                        className="axis"
                        transform={ "translate(" +  (self.state.width - self.props.padding) + ",0)" }
                    />

                </svg>

        );
    }
});

module.exports = WeatherGraph;
<commit_msg>avoid extra iterations<commit_after>/** @jsx React.DOM */
var React = require('react');
var _ = require("lodash");
var d3 = require("d3");
var moment = require("moment");

var WeatherSvgPath = require("./WeatherSvgPath");
var GraphCursor = require("./GraphCursor");

function cmp(a, b) {
    return b.time.getTime() - a.time.getTime();
}

// TODO: this is a sorted array. We could binary search it...
function findClosest(value, arr, start) {
    return arr.reduce(function(best, current) {
        if (Math.abs(cmp(value, current)) < Math.abs(cmp(value, best))) {
            return current;
        } else {
            return best;
        }
    }, start || arr[0]);
}

var WeatherGraph = React.createClass({

    style: {
        backgroundColor: "red"
    },

    componentWillReceiveProps: function(props) {
        this.computeData(props);
    },

    getWidth: function() {
        return window.innerWidth - this.props.margin;
    },

    getHeight: function() {
        // return parseInt(screen.height / 2);
        return Math.max(parseInt(window.innerHeight - 250), 300);
    },

    updateDimensions: function() {
        this.setState({
            width: this.getWidth(),
            height: this.getHeight()
        });
    },

    getInitialState: function() {
        return {
            width: this.getWidth(),
            height: this.getHeight(),

            maxValue: 13,
            minValue: 0,

            startTime: new Date(),
            endTime: new Date(),

            _cursorPosition: null
        };
    },

    getDefaultProps: function() {
        return {
            padding: 30,
            paddingTop: 30,
            margin: 20
        };
    },

    computeData: function(props) {
        props = props || this.props;
        var newState = this.getInitialState();
        console.log("Computing DATA!");

        props.lines.forEach(function(d) {
            _.forEach(d, function(values) {

                var minValue = d3.min(values, function(d) { return d.value; });
                var maxValue = d3.max(values, function(d) { return d.value; });

                var startTime = d3.min(values, function(d) { return d.time; });
                var endTime = d3.max(values, function(d) { return d.time; });

                newState.maxValue = d3.max([maxValue, newState.maxValue]);
                newState.minValue = d3.min([minValue, newState.minValue]);

                newState.startTime = d3.min([startTime, newState.startTime]);
                newState.endTime = d3.max([endTime, newState.endTime]);

            });
        });

        // Ensure that the current is always visible in the graph
        newState.endTime = new Date(
             Math.max(newState.endTime.getTime(), Date.now())
         );
        newState.startTime = new Date(
             Math.min(newState.startTime.getTime(), Date.now())
         );

        this.setState(newState);
    },

    updateScales: function() {
        var state = this.state;

        var xScale = d3.scale.linear()
            .domain([state.startTime, state.endTime])
            .range([this.props.padding, state.width - this.props.padding]);

        var yScale = d3.scale.linear()
            .domain([state.minValue, state.maxValue])
            .range([state.height - this.props.padding, this.props.paddingTop])
            ;

        // educated guess
        var tickLabelWidth = 50;

        var xAxis = d3.svg.axis()
            .scale(xScale)
            .orient("bottom")
            .ticks( parseInt(state.width / tickLabelWidth) )
            .tickFormat(function(d) {
                return d3.time.format("%H:%M")(new Date(d));
            })
            ;

        var yAxis = d3.svg.axis()
            .scale(yScale)
            .orient("left")
            ;

        var yAxisRight = d3.svg.axis()
            .scale(yScale)
            .orient("right")
            ;

        _.extend(this, {
            xScale: xScale,
            yScale: yScale,
            xAxis: xAxis,
            yAxis: yAxis,
            yAxisRight: yAxisRight
        });

    },

    componentWillMount: function() {
        this.updateDimensions();
    },

    renderAxes: function() {
        if (!this.xAxis) return;
        if (!this.yAxis) return;
        if (!this.yAxisRight) return;

        this.xAxis(d3.select(this.refs.xAxis.getDOMNode()));
        this.yAxis(d3.select(this.refs.yAxis.getDOMNode()));
        this.yAxisRight(d3.select(this.refs.yAxisRight.getDOMNode()));
    },

    componentDidMount: function() {
        this.computeData();
        window.addEventListener("resize", this.updateDimensions);
    },

    componentWillUnmount: function() {
        window.removeEventListener("resize", this.updateDimensions);
    },

    componentDidUpdate: function() {
        this.renderAxes();
    },

    handleTouchMove: function(e) {
        e.preventDefault();
        this.handleMove(e.targetTouches[0].clientX);
    },

    handleMouseMove: function(e) {
        this.handleMove(e.clientX);
    },

    handleMove: function(clientX) {
        if (clientX === null || clientX === undefined) return;
        clientX -= this.refs.svg.getDOMNode().offsetLeft;
        this.setState({ _cursorPosition: clientX });
    },

    getCursorPosition: function() {
        if (this.state._cursorPosition === null) {
            return this.xScale(new Date());
        }
        return this.state._cursorPosition;
    },

    renderLimit: function(desc, color, limit) {
        var bottom = this.state.height;
        bottom -= this.yScale(this.state.maxValue - limit);
        bottom -= this.props.padding;

        var textHeight = 8;
        var textWidth = 100;
        return (
            <g>
                <rect
                    x={this.props.padding}
                    y={this.props.padding}
                    width={this.state.width - this.props.padding*2}
                    height={bottom}
                    fill={color} />

                <text
                    className="limit-text"
                    x={this.state.width - textWidth}
                    y={bottom + this.props.padding - textHeight}
                    fill="black">{desc}</text>

            </g>

        );
    },

    getPointsCloseToCursor: function() {
        var self = this;

        var pos = {
            time: new Date(this.xScale.invert(this.getCursorPosition()))
        };

        return _.compact(this.props.lines.map(function(line) {
            var points = line.forecasts;
            if (pos.time.getTime() < Date.now()) {
                points = line.observations;
            }
            var point = findClosest(pos, points);
            if (!point) return;
            return {
                title: line.title,
                time: point.time,
                value: point.value,
                x: self.xScale(point.time.getTime()),
                y: self.yScale(point.value)
            };
        }));
    },

    render: function() {
        var self = this;
        this.updateScales();

        var points = this.getPointsCloseToCursor();
        var circles = points.map(function(point) {
            return (
                <circle
                    cx={point.x}
                    cy={point.y}
                    r="5"
                    fill="none"
                    stroke="red"
                />
            );
        });

        return (

                    {points.map(function(point) {
                        return (

                                {point.title}
                                {point.value} m/s
                                {moment(point.time).fromNow()}

                        );
                    })}

                <svg
                    ref="svg"
                    onMouseMove={this.handleMouseMove}
                    onTouchStart={this.handleTouchMove}
                    onTouchMove={this.handleTouchMove}
                    width={this.state.width}
                    height={this.state.height} >

                    {this.renderLimit("Student limit", "lightgray", 8)}
                    {this.renderLimit("License limit", "gray", 11)}

                    <GraphCursor
                        height={self.state.height}
                        cursorPosition={self.getCursorPosition()}
                        padding={self.props.padding}
                        paddingTop={self.props.paddingTop}
                    />

                    {circles}

                    {self.props.lines.map(function(d) {
                        return (
                            <WeatherSvgPath
                                key={d.title}
                                xScale={self.xScale}
                                yScale={self.yScale}
                                observations={d.observations}
                                forecasts={d.forecasts}
                            />
                        );
                    })}

                    <g
                        ref="xAxis"
                        className="axis"
                        transform={ "translate(0, " + (self.state.height - self.props.padding) + ")" }
                    />

                    <g
                        ref="yAxis"
                        className="axis"
                        transform={ "translate(" + self.props.padding + ",0)" }
                    />

                    <g
                        ref="yAxisRight"
                        className="axis"
                        transform={ "translate(" +  (self.state.width - self.props.padding) + ",0)" }
                    />

                </svg>

        );
    }
});

module.exports = WeatherGraph;
<|endoftext|>