<commit_before>import { OPEN } from 'ws'
import {
  WebSocketConnectEvent,
  WebSocketDisconnectEvent,
  WebSocketEvent,
} from './lambda-events/index.js'
import debugLog from '../../debugLog.js'
import serverlessLog from '../../serverlessLog.js'
import {
  DEFAULT_WEBSOCKETS_API_ROUTE_SELECTION_EXPRESSION,
  DEFAULT_WEBSOCKETS_ROUTE,
} from '../../config/index.js'
import { jsonPath } from '../../utils/index.js'

const { parse, stringify } = JSON

export default class WebSocketClients {
  #clients = new Map()
  #lambda = null
  #options = null
  #webSocketRoutes = new Map()
  #websocketsApiRouteSelectionExpression = null

  constructor(serverless, options, lambda) {
    this.#lambda = lambda
    this.#options = options
    this.#websocketsApiRouteSelectionExpression =
      serverless.service.provider.websocketsApiRouteSelectionExpression ||
      DEFAULT_WEBSOCKETS_API_ROUTE_SELECTION_EXPRESSION
  }

  _addWebSocketClient(client, connectionId) {
    this.#clients.set(client, connectionId)
    this.#clients.set(connectionId, client)
  }

  _removeWebSocketClient(client) {
    const connectionId = this.#clients.get(client)

    this.#clients.delete(client)
    this.#clients.delete(connectionId)

    return connectionId
  }

  _getWebSocketClient(connectionId) {
    return this.#clients.get(connectionId)
  }

  async _processEvent(websocketClient, connectionId, route, event) {
    let functionKey = this.#webSocketRoutes.get(route)

    if (!functionKey && route !== '$connect' && route !== '$disconnect') {
      functionKey = this.#webSocketRoutes.get('$default')
    }

    if (!functionKey) {
      return
    }

    const sendError = (err) => {
      if (websocketClient.readyState === OPEN) {
        websocketClient.send(
          stringify({
            connectionId,
            message: 'Internal server error',
            requestId: '1234567890',
          }),
        )
      }

      // mimic AWS behaviour (close connection) when the $connect route handler throws
      if (route === '$connect') {
        websocketClient.close()
      }

      debugLog(`Error in route handler '${functionKey}'`, err)
    }

    const lambdaFunction = this.#lambda.get(functionKey)

    lambdaFunction.setEvent(event)

    // let result

    try {
      /* result = */ await lambdaFunction.runHandler()

      // TODO what to do with "result"?
    } catch (err) {
      console.log(err)
      sendError(err)
    }
  }

  _getRoute(value) {
    let json

    try {
      json = parse(value)
    } catch (err) {
      return DEFAULT_WEBSOCKETS_ROUTE
    }

    const routeSelectionExpression = this.#websocketsApiRouteSelectionExpression.replace(
      'request.body',
      '',
    )

    const route = jsonPath(json, routeSelectionExpression)

    if (typeof route !== 'string') {
      return DEFAULT_WEBSOCKETS_ROUTE
    }

    return route || DEFAULT_WEBSOCKETS_ROUTE
  }

  addClient(webSocketClient, request, connectionId) {
    this._addWebSocketClient(webSocketClient, connectionId)

    const connectEvent = new WebSocketConnectEvent(
      connectionId,
      request,
      this.#options,
    ).create()

    this._processEvent(webSocketClient, connectionId, '$connect', connectEvent)

    const hardTimeout = setTimeout(() => {
      debugLog(`timeout:hard:${connectionId}`)
      webSocketClient.close()
    }, this.#options.webSocketHardTimeout * 1000)

    webSocketClient.on('close', () => {
      debugLog(`disconnect:${connectionId}`)

      this._removeWebSocketClient(webSocketClient)

      const disconnectEvent = new WebSocketDisconnectEvent(
        connectionId,
      ).create()

      clearTimeout(hardTimeout)

      this._processEvent(
        webSocketClient,
        connectionId,
        '$disconnect',
        disconnectEvent,
      )
    })

    webSocketClient.on('message', (message) => {
      debugLog(`message:${message}`)

      const route = this._getRoute(message)

      debugLog(`route:${route} on connection=${connectionId}`)

      const event = new WebSocketEvent(connectionId, route, message).create()

      this._processEvent(webSocketClient, connectionId, route, event)
    })
  }

  addRoute(functionKey, route) {
    // set the route name
    this.#webSocketRoutes.set(route, functionKey)

    serverlessLog(`route '${route}'`)
  }

  close(connectionId) {
    const client = this._getWebSocketClient(connectionId)

    if (client) {
      client.close()
      return true
    }

    return false
  }

  send(connectionId, payload) {
    const client = this._getWebSocketClient(connectionId)

    if (client) {
      client.send(payload)
      return true
    }

    return false
  }
}
<commit_msg>feat: support AWS WebSocket idle timeout<commit_after>import { OPEN } from 'ws'
import {
  WebSocketConnectEvent,
  WebSocketDisconnectEvent,
  WebSocketEvent,
} from './lambda-events/index.js'
import debugLog from '../../debugLog.js'
import serverlessLog from '../../serverlessLog.js'
import {
  DEFAULT_WEBSOCKETS_API_ROUTE_SELECTION_EXPRESSION,
  DEFAULT_WEBSOCKETS_ROUTE,
} from '../../config/index.js'
import { jsonPath } from '../../utils/index.js'

const { parse, stringify } = JSON

export default class WebSocketClients {
  #clients = new Map()
  #lambda = null
  #options = null
  #webSocketRoutes = new Map()
  #websocketsApiRouteSelectionExpression = null
  #idleTimeouts = new WeakMap()

  constructor(serverless, options, lambda) {
    this.#lambda = lambda
    this.#options = options
    this.#websocketsApiRouteSelectionExpression =
      serverless.service.provider.websocketsApiRouteSelectionExpression ||
      DEFAULT_WEBSOCKETS_API_ROUTE_SELECTION_EXPRESSION
  }

  _addWebSocketClient(client, connectionId) {
    this.#clients.set(client, connectionId)
    this.#clients.set(connectionId, client)
    this._onWebSocketUsed(connectionId)
  }

  _removeWebSocketClient(client) {
    const connectionId = this.#clients.get(client)

    this.#clients.delete(client)
    this.#clients.delete(connectionId)
    this.#idleTimeouts.delete(connectionId, client)

    return connectionId
  }

  _getWebSocketClient(connectionId) {
    return this.#clients.get(connectionId)
  }

  _onWebSocketUsed(connectionId) {
    const client = this._getWebSocketClient(connectionId)
    this._clearIdleTimeout(client)

    const timeoutId = setTimeout(() => {
      debugLog(`timeout:idle:${connectionId}`)
      client.close(1001, 'Going away')
    }, 10 * 1000)
    this.#idleTimeouts.set(client, timeoutId)
  }

  _clearIdleTimeout(client) {
    const timeoutId = this.#idleTimeouts.get(client)
    clearTimeout(timeoutId)
  }

  async _processEvent(websocketClient, connectionId, route, event) {
    let functionKey = this.#webSocketRoutes.get(route)

    if (!functionKey && route !== '$connect' && route !== '$disconnect') {
      functionKey = this.#webSocketRoutes.get('$default')
    }

    if (!functionKey) {
      return
    }

    const sendError = (err) => {
      if (websocketClient.readyState === OPEN) {
        websocketClient.send(
          stringify({
            connectionId,
            message: 'Internal server error',
            requestId: '1234567890',
          }),
        )
      }

      // mimic AWS behaviour (close connection) when the $connect route handler throws
      if (route === '$connect') {
        websocketClient.close()
      }

      debugLog(`Error in route handler '${functionKey}'`, err)
    }

    const lambdaFunction = this.#lambda.get(functionKey)

    lambdaFunction.setEvent(event)

    // let result

    try {
      /* result = */ await lambdaFunction.runHandler()

      // TODO what to do with "result"?
    } catch (err) {
      console.log(err)
      sendError(err)
    }
  }

  _getRoute(value) {
    let json

    try {
      json = parse(value)
    } catch (err) {
      return DEFAULT_WEBSOCKETS_ROUTE
    }

    const routeSelectionExpression = this.#websocketsApiRouteSelectionExpression.replace(
      'request.body',
      '',
    )

    const route = jsonPath(json, routeSelectionExpression)

    if (typeof route !== 'string') {
      return DEFAULT_WEBSOCKETS_ROUTE
    }

    return route || DEFAULT_WEBSOCKETS_ROUTE
  }

  addClient(webSocketClient, request, connectionId) {
    this._addWebSocketClient(webSocketClient, connectionId)

    const connectEvent = new WebSocketConnectEvent(
      connectionId,
      request,
      this.#options,
    ).create()

    this._processEvent(webSocketClient, connectionId, '$connect', connectEvent)

    const hardTimeout = setTimeout(() => {
      debugLog(`timeout:hard:${connectionId}`)
      webSocketClient.close()
    }, this.#options.webSocketHardTimeout * 1000)

    webSocketClient.on('close', () => {
      debugLog(`disconnect:${connectionId}`)

      this._removeWebSocketClient(webSocketClient)

      const disconnectEvent = new WebSocketDisconnectEvent(
        connectionId,
      ).create()

      clearTimeout(hardTimeout)
      this._clearIdleTimeout(connectionId)

      this._processEvent(
        webSocketClient,
        connectionId,
        '$disconnect',
        disconnectEvent,
      )
    })

    webSocketClient.on('message', (message) => {
      debugLog(`message:${message}`)

      const route = this._getRoute(message)

      debugLog(`route:${route} on connection=${connectionId}`)

      const event = new WebSocketEvent(connectionId, route, message).create()
      this._onWebSocketUsed(connectionId)

      this._processEvent(webSocketClient, connectionId, route, event)
    })
  }

  addRoute(functionKey, route) {
    // set the route name
    this.#webSocketRoutes.set(route, functionKey)

    serverlessLog(`route '${route}'`)
  }

  close(connectionId) {
    const client = this._getWebSocketClient(connectionId)

    if (client) {
      client.close()
      return true
    }

    return false
  }

  send(connectionId, payload) {
    const client = this._getWebSocketClient(connectionId)

    if (client) {
      this._onWebSocketUsed(connectionId)
      client.send(payload)
      return true
    }

    return false
  }
}
<|endoftext|>


<commit_before>const { findLastIndex, flatten } = require('./utils')

/* Processes program and specification to get
 * the list of predicates, prooving which will
 * mean the program's validity.
 * Here `program` is an object tree representation of
 * the program being proven,
 * `spec` is an object, that contains fields
 *  - `precondition` with precondition predicate,
 *  - `postcondition` with postcondition predicate,
 *  - `invariants` with list of loop invariants (may be ommited),
 *  - `boundaryFunctions` with list of boundary functions (may be ommited).
 * `context` field is used by WP to propagate context through recursive calls.
 * You don't need to specify it.
 * WP returns an object of two fields:
 *  - `predicates`, the list of predicates to proove,
 *  - `context`, the that for each predicate of `predicates` provides
 *    a list of context objects, collected while aquiring the predicate.
*/
function wp(program, spec, context) {
  const Q = spec.precondition
  const R = spec.postcondition
  const S = program
  context = context || []

  if (S.length === 0) {     // an empty program acts
    const implication = {   // like a skip statement
      type: 'implies',
      left: Q,
      right: R
    }

    return {
      predicates: [implication],
      context: context
    }
  }

  const lastLoopIndex = findLastIndex(S, st => st.type === 'do')
  if (lastLoopIndex > 0) {
    const DO = S[lastLoopIndex]
    const P = spec.invariants.pop()
    const t = spec.boundaryFunctions.pop()
    const I = S.slice(0, lastLoopIndex)   // commands before the loop
    const J = S.slice(lastLoopIndex + 1)  // commands after the loop

    return doTheorem(Q, R, P, t, I, DO, J, spec, context)
  }

  const firstIfIndex = S.findIndex(st => st.type === 'if')
  if (firstIfIndex > 0) {
    const IF = S[firstIfIndex]
    const I = S.slice(0, firstIfIndex)    // commands before if
    const J = S.slice(firstIfIndex + 1)   // commands after if

    return ifTheorem(Q, R, I, IF, J, spec, context)
  }

  // TODO
  throw new Error('Not implemented')  // wp for elementary command seq
  return
}

/* Uses DO theorem to generate proof for {Q} I; DO; J {R}
 * with invariant P and boundary function t.
 * specBase is used to keep track of context,
 * invariants and boundary functions.
*/
function doTheorem(Q, R, P, t, I, DO, J, innerSpec, context) {
  const BB = conjunction(DO.guards)

  const results = flatten([
    doTheoremPt1(Q, I, P, innerSpec, context),
    doTheoremPt2(P, DO, innerSpec, context),
    doTheoremPt3(P, R, BB, J),
    doTheoremPt5(P, t, DO, innerSpec, context)
  ]).map(wp)

  // pt. 4 returns a predicate, so no wp call needed
  results.push(doTheoremPt4(P, t, BB, DO, innerSpec))

  return combineResults(results)
}

// {Q} I {P}
function doTheoremPt1(Q, I, P, innerSpec, context) {
  const contextObject = createContext({ command: I, type: 'do', step: 1 })

  return Object.assign({}, innerSpec, {
    precondition: Q,
    postcondition: P,
    program: I,
    context: [contextObject, ... context]
  })
}

// {P ^ Bi} Ci {P} for i = 1..n
function doTheoremPt2(P, DO, innerSpec, context) {
  const subSpecs = DO.guards.map((guard, i) => {
    const command = DO.commands[i]
    const contextObject =
      createContext({ command: command, type: 'do', step: 2, branch: i + 1})

    return Object.assign({}, innerSpec, {
      precondition: {
        type: 'and',
        left: P,
        right: guard,
      },
      postcondition: P,
      program: command,
      context: [contextObject, ... context]
    })
  })

  return subSpecs
}

// {P ^ ~BB} J {R}
function doTheoremPt3(P, R, BB, J, innerSpec, context) {
  const contextObject = createContext({ command: J, type: 'do', step: 3})

  return Object.assign({}, innerSpec, {
    precondition: {
      type: 'and',
      left: P,
      right: {
        type: 'not',
        inner: BB
      }
    },
    postcondition: R,
    program: J,
    context: [contextObject, ... context]
  })
}

// P ^ BB => t > 0
function doTheoremPt4(P, t, BB, DO, innerSpec, context) {
  const contextObject = createContext({ command: DO, type: 'do', step: 4 })
  const implication = {
    type: 'implies',
    left: { type: 'and', left: P, right: BB },
    right: {
      type: 'comp',
      comp: '>',
      left: t,
      right: { type: 'const', const: 0 }
    }
  }
  return {
    predicates: [implication],
    context: [contextObject, ... context]
  }
}

// {P ^ Bi} @t := t; Ci {t < @t} for i = i..n
function doTheoremPt5(P, t, DO, innerSpec, context) {
  const specs = DO.guards.map((guard, i) => {
    const tInit = {
      type: 'name',
      name: '@t_init',    // NOTE: here we use @ which might cause problems.
    }
    const fakeAssignment = {            // we reuse first command text range
      textRange: command[0].textRange,  // so it would seem like our command
      type: 'assign',                   // takes no space in source code.
      lvalues: [tInit],
      rvalues: [t]
    }
    const command = [fakeAssignment, ... DO.commands[i]]
    const contextObject = Object.assign({}, getCommandsTextRange(command), { // TODO
      cause: {
        type: 'do',
        step: 5,
        branch: i
      }
    })

    return Object.assign({}, innerSpec, {
      precondition: {
        type: 'and',
        left: P,
        right: guard
      },
      postcondition: {
        type: 'comp',
        comp: '<',
        left: t,
        right: tInit
      },
      program: command,
      context: [contextObject, ... context]
    })
  })

  return specs
}

/* Uses IF theorem to generate proof for {Q} I; IF; J {R}
 * with invariant P and boundary function t.
 * specBase is used to keep track of context,
 * invariants and boundary functions.
*/
function ifTheorem(Q, R, I, IF, J, innerSpec, context) {
  const BB = conjunction(IF.guards)

  const results = flatten([
    ifTheoremPt1(Q, BB, I, innerSpec, context),
    ifTheoremPt2(Q, R, I, IF, J, innerSpec, context)
  ]).map(wp)

  return combineResults(results)
}

function ifTheoremPt1(Q, BB, I, innerSpec, context) {
  const context = createContext({ command: I, type: 'if', step: 1 })

  return Object.assign({}, innerSpec, {
    precondition: Q,
    postcondition: BB,
    program: I,
    context: [contextObject, ... context]
  })
}

function ifTheoremPt2(Q, R, I, IF, J, innerSpec, context) {
  const specs = IF.guards.map((guard, i) => {
    const commands = null // TODO
    const precondition = null // TODO

    const contextObject =
      createContext({ command: commands, type: 'if', step: 2, branch: i})

    return Object.assign({}, innerSpec, {
      precondition: precondition,
      postcondition: guard,
      program: commands,
      context: [contextObject, ... context]
    })
  })

  return specs
}

/* Generates a proof of elementary command sequence.
*/
function elementarySequenceWp(Q, S, R, context) {
  // TODO: reduceRight ??
  const seqWp = S.reverse().reduce((predicate, command) => {
    switch (command.type) {
      case 'abort':
        return { type: 'const', const: false }

      case 'skip':
        return predicate

      case 'assign':
       return substitutePredicate(predicate, command.lvalues, command.rvalues)
    }
  }, R)

  if (seqWp === R) {               // copy R in case there
    seqWp = Object.assign({}, R)   // was nothing but skip's
  }

  const contextObject = createContext({ command: S, type: 'seq' })

  return {
    predicates: [seqWp],
    context: [contextObject, ... context]
  }
}

/* Substitutes variables from `names` by
 * expressions from `exprs` in predicate `pred`
 * and returns the result predicate.
*/
function substitutePredicate(pred, names, exprs) {
  switch (pred.type) {
    case 'const':
      return pred

    case 'not':
    case 'parets':
      return {
        type: pred.type,
        inner: substitutePredicate(pred.inner, names, exprs)
      }

    case 'or':
    case 'implies':
    case 'iff':
    case 'and':
      return {
        type: pred.type,
        left: substitutePredicate(pred.left, names, exprs),
        right: substitutePredicate(pred.right, names, exprs)
      }

    case 'comp':
      return {
        type: pred.type,
        op: pred.op,
        leftIntExpr: substituteIntExpr(pred.leftIntExpr, names, exprs),
        rightIntExpr: substituteIntExpr(pred.rightIntExpr, names, exprs)
      }

    case 'exists':
    case 'forall':
      const filteredPairs = names
        .map((n, i) => { 'var': name, expr: exprs[i] })
        .filter(pair => pred.boundedVars.every(bn => bn.name !== pair.var.name))

      const filteredNames = filteredPairs.map(p => p.var)
      const filteredExprs = filteredPairs.map(p => p.expr)

      return {
        type: pred.type,
        boundedVars: pred.boundedVars,
        condition: substitutePredicate(pred.condition, filteredNames, filteredExprs),
        inner: substitutePredicate(pred.inner, filteredNames, filteredExprs)
      }

    default:
      throw new Error('WP error: unknown predicate type:', pred.type)
  }
}

function substituteIntExpr(intExpr, names, exprs) {
  switch (intExpr.type) {
    case 'const':
      return intExpr

    case 'var':
      return {
        type: 'var',
        var: substituteVariable(intExpr.var, names, exprs)
      }

    case 'negare':
    case 'parets':
      return {
        type: intExpr.type,
        inner: substituteIntExpr(intExpr.inner, names, exprs)
      }

    case 'plus':
    case 'minus':
    case 'mult':
      return {
        type: intExpr.type,
        leftIntExpr: substituteIntExpr(intExpr.leftIntExpr, names. exprs),
        rightIntExpr: substituteIntExpr(intExpr.rightIntExpr, names. exprs)
      }

    default:
     throw new Error('WP error: unknown integer expression type:', intExpr.type)
  }
}

function substituteVariable(variable, names, exprs) {
  switch (variable.type) {
    case 'name':
      const nameIndex = names.findIndex(n => n.name === variable.name)
      return nameIndex < 0 ? variable : exprs[nameIndex]

    case 'select':
      return {
        type: 'select',
        base: substituteVariable(variable.base, names, exprs),
        selector: substituteIntExpr(variable.selector, names, exprs)
      }

    case 'store':
      return {
        type: 'store',
        base: substituteVariable(variable.base, names, exprs),         // TODO: do we need
        selector: substituteIntExpr(variable.selector, names, exprs),  // substitutions
        value: substituteIntExpr(variable.value, names, exprs)         // in all of these??
      }

    default:
      throw new Error('WP error: unknown variable type:', variable.type)
  }
}

function combineResults(results) {
  // TOOD: is there a better way? (maybe concat without creating new array?)
  return results.reduce((acc, res) => {
    predicates: acc.predicates.concat(res.predicates),
    context: acc.context.concat(res.context)
  })
}

function conjunction(predicates) {
  return predicates.reduce((conj, pred) => ({
    type: 'and',
    left: conj,
    right: pred
  }))
}

// You can omit step and branch
function createContext({ command, type, step, branch }) {
  // TODO: either change docs or return to what we had before
  let context = { proofType: type, proofStep: step, proofBranch branch }

  if (Array.isArray(command)) {
    // TODO: what if command is empty (this *can* happen)
    context.start = command[0].textRange.start
    context.end = command[commands.length - 1].textRange.end
  } else {
    context.start = command.textRange.start
    context.end = command.textRange.end
  }

  return context
}

module.exports = wp
<commit_msg>#11 Solved half of the TODOs<commit_after>const { findLastIndex, flatten } = require('./utils')

/* Processes program and specification to get
 * the list of predicates, prooving which will
 * mean the program's validity.
 * Here `program` is an object tree representation of
 * the program being proven,
 * `spec` is an object, that contains fields
 *  - `precondition` with precondition predicate,
 *  - `postcondition` with postcondition predicate,
 *  - `invariants` with list of loop invariants (may be ommited),
 *  - `boundaryFunctions` with list of boundary functions (may be ommited).
 * `context` field is used by WP to propagate context through recursive calls.
 * You don't need to specify it.
 * WP returns an object of two fields:
 *  - `predicates`, the list of predicates to proove,
 *  - `context`, the that for each predicate of `predicates` provides
 *    a list of context objects, collected while aquiring the predicate.
*/
function wp(program, spec, context) {
  const Q = spec.precondition
  const R = spec.postcondition
  const S = program
  context = context || []

  if (S.length === 0) {     // an empty program acts
    const implication = {   // like a skip statement
      type: 'implies',
      left: Q,
      right: R
    }

    return {
      predicates: [implication],
      context: context
    }
  }

  const lastLoopIndex = findLastIndex(S, st => st.type === 'do')
  if (lastLoopIndex > 0) {
    const DO = S[lastLoopIndex]
    const P = spec.invariants.pop()
    const t = spec.boundaryFunctions.pop()
    const I = S.slice(0, lastLoopIndex)   // commands before the loop
    const J = S.slice(lastLoopIndex + 1)  // commands after the loop

    return doTheorem(Q, R, P, t, I, DO, J, spec, context)
  }

  const firstIfIndex = S.findIndex(st => st.type === 'if')
  if (firstIfIndex > 0) {
    const IF = S[firstIfIndex]
    const I = S.slice(0, firstIfIndex)    // commands before if
    const J = S.slice(firstIfIndex + 1)   // commands after if

    return ifTheorem(Q, R, I, IF, J, spec, context)
  }

  return elementarySequenceWp(Q, S, R, context)
}

/* Uses DO theorem to generate proof for {Q} I; DO; J {R}
 * with invariant P and boundary function t.
 * specBase is used to keep track of context,
 * invariants and boundary functions.
*/
function doTheorem(Q, R, P, t, I, DO, J, innerSpec, context) {
  const BB = conjunction(DO.guards)

  const results = flatten([
    doTheoremPt1(Q, I, P, innerSpec, context),
    doTheoremPt2(P, DO, innerSpec, context),
    doTheoremPt3(P, R, BB, J),
    doTheoremPt5(P, t, DO, innerSpec, context)
  ]).map(wp)

  // pt. 4 returns a predicate, so no wp call needed
  results.push(doTheoremPt4(P, t, BB, DO, innerSpec))

  return combineResults(results)
}

// {Q} I {P}
function doTheoremPt1(Q, I, P, innerSpec, context) {
  const contextObject = createContext({ command: I, type: 'do', step: 1 })

  return Object.assign({}, innerSpec, {
    precondition: Q,
    postcondition: P,
    program: I,
    context: [contextObject, ... context]
  })
}

// {P ^ Bi} Ci {P} for i = 1..n
function doTheoremPt2(P, DO, innerSpec, context) {
  const subSpecs = DO.guards.map((guard, i) => {
    const command = DO.commands[i]
    const contextObject =
      createContext({ command: command, type: 'do', step: 2, branch: i + 1})

    return Object.assign({}, innerSpec, {
      precondition: {
        type: 'and',
        left: P,
        right: guard,
      },
      postcondition: P,
      program: command,
      context: [contextObject, ... context]
    })
  })

  return subSpecs
}

// {P ^ ~BB} J {R}
function doTheoremPt3(P, R, BB, J, innerSpec, context) {
  const contextObject = createContext({ command: J, type: 'do', step: 3})

  return Object.assign({}, innerSpec, {
    precondition: {
      type: 'and',
      left: P,
      right: {
        type: 'not',
        inner: BB
      }
    },
    postcondition: R,
    program: J,
    context: [contextObject, ... context]
  })
}

// P ^ BB => t > 0
function doTheoremPt4(P, t, BB, DO, innerSpec, context) {
  const contextObject = createContext({ command: DO, type: 'do', step: 4 })
  const implication = {
    type: 'implies',
    left: { type: 'and', left: P, right: BB },
    right: {
      type: 'comp',
      comp: '>',
      left: t,
      right: { type: 'const', const: 0 }
    }
  }
  return {
    predicates: [implication],
    context: [contextObject, ... context]
  }
}

// {P ^ Bi} @t := t; Ci {t < @t} for i = i..n
function doTheoremPt5(P, t, DO, innerSpec, context) {
  const specs = DO.guards.map((guard, i) => {
    const tInit = {
      type: 'name',
      name: '@t_init',    // NOTE: here we use @ which might cause problems.
    }
    const fakeAssignment = {            // we reuse first command text range
      textRange: command[0].textRange,  // so it would seem like our command
      type: 'assign',                   // takes no space in source code.
      lvalues: [tInit],
      rvalues: [t]
    }
    const command = [fakeAssignment, ... DO.commands[i]]
    const contextObject =
      createContext({ command: command, type: 'do', step: 5, branch: i + 1 })

    return Object.assign({}, innerSpec, {
      precondition: {
        type: 'and',
        left: P,
        right: guard
      },
      postcondition: {
        type: 'comp',
        comp: '<',
        left: t,
        right: tInit
      },
      program: command,
      context: [contextObject, ... context]
    })
  })

  return specs
}

/* Uses IF theorem to generate proof for {Q} I; IF; J {R}
 * with invariant P and boundary function t.
 * specBase is used to keep track of context,
 * invariants and boundary functions.
*/
function ifTheorem(Q, R, I, IF, J, innerSpec, context) {
  const BB = conjunction(IF.guards)

  const results = flatten([
    ifTheoremPt1(Q, BB, I, innerSpec, context),
    ifTheoremPt2(Q, R, BB, I, IF, J, innerSpec, context)
  ]).map(wp)

  return combineResults(results)
}

function ifTheoremPt1(Q, BB, I, innerSpec, context) {
  const context = createContext({ command: I, type: 'if', step: 1 })

  return Object.assign({}, innerSpec, {
    precondition: Q,
    postcondition: BB,
    program: I,
    context: [contextObject, ... context]
  })
}

function ifTheoremPt2(Q, R, BB, I, IF, J, innerSpec, context) {
  const specs = IF.guards.map((guard, i) => {
    const commands = I.concat(IF.commands[i]).concat(J)
    const precondition = {
      type: 'and',
      left: Q,
      // we get Q => WP(I, BB) and take the right part
      right: elementarySequenceWp(Q, I, BB).predicates[0].right
    }

    const contextObject =
      createContext({ command: commands, type: 'if', step: 2, branch: i + 1})

    return Object.assign({}, innerSpec, {
      precondition: precondition,
      postcondition: guard,
      program: commands,
      context: [contextObject, ... context]
    })
  })

  return specs
}

/* Generates a proof of elementary command sequence.
*/
function elementarySequenceWp(Q, S, R, context) {
  const seqWp = S.reduceRight((predicate, command) => {
    switch (command.type) {
      case 'abort':
        return { type: 'const', const: false }

      case 'skip':
        return predicate

      case 'assign':
       return substitutePredicate(predicate, command.lvalues, command.rvalues)
    }
  }, R)

  if (seqWp === R) {               // copy R in case there
    seqWp = Object.assign({}, R)   // was nothing but skip's
  }

  const contextObject = createContext({ command: S, type: 'seq' })

  return {
    predicates: [seqWp],
    context: [contextObject, ... context]
  }
}

/* Substitutes variables from `names` by
 * expressions from `exprs` in predicate `pred`
 * and returns the result predicate.
*/
function substitutePredicate(pred, names, exprs) {
  switch (pred.type) {
    case 'const':
      return pred

    case 'not':
    case 'parets':
      return {
        type: pred.type,
        inner: substitutePredicate(pred.inner, names, exprs)
      }

    case 'or':
    case 'implies':
    case 'iff':
    case 'and':
      return {
        type: pred.type,
        left: substitutePredicate(pred.left, names, exprs),
        right: substitutePredicate(pred.right, names, exprs)
      }

    case 'comp':
      return {
        type: pred.type,
        op: pred.op,
        leftIntExpr: substituteIntExpr(pred.leftIntExpr, names, exprs),
        rightIntExpr: substituteIntExpr(pred.rightIntExpr, names, exprs)
      }

    case 'exists':
    case 'forall':
      const filteredPairs = names
        .map((n, i) => { 'var': name, expr: exprs[i] })
        .filter(pair => pred.boundedVars.every(bn => bn.name !== pair.var.name))

      const filteredNames = filteredPairs.map(p => p.var)
      const filteredExprs = filteredPairs.map(p => p.expr)

      return {
        type: pred.type,
        boundedVars: pred.boundedVars,
        condition: substitutePredicate(pred.condition, filteredNames, filteredExprs),
        inner: substitutePredicate(pred.inner, filteredNames, filteredExprs)
      }

    default:
      throw new Error('WP error: unknown predicate type:', pred.type)
  }
}

function substituteIntExpr(intExpr, names, exprs) {
  switch (intExpr.type) {
    case 'const':
      return intExpr

    case 'var':
      return {
        type: 'var',
        var: substituteVariable(intExpr.var, names, exprs)
      }

    case 'negare':
    case 'parets':
      return {
        type: intExpr.type,
        inner: substituteIntExpr(intExpr.inner, names, exprs)
      }

    case 'plus':
    case 'minus':
    case 'mult':
      return {
        type: intExpr.type,
        leftIntExpr: substituteIntExpr(intExpr.leftIntExpr, names. exprs),
        rightIntExpr: substituteIntExpr(intExpr.rightIntExpr, names. exprs)
      }

    default:
     throw new Error('WP error: unknown integer expression type:', intExpr.type)
  }
}

function substituteVariable(variable, names, exprs) {
  switch (variable.type) {
    case 'name':
      const nameIndex = names.findIndex(n => n.name === variable.name)
      return nameIndex < 0 ? variable : exprs[nameIndex]

    case 'select':
      return {
        type: 'select',
        base: substituteVariable(variable.base, names, exprs),
        selector: substituteIntExpr(variable.selector, names, exprs)
      }

    case 'store':
      return {
        type: 'store',
        base: substituteVariable(variable.base, names, exprs),         // TODO: do we need
        selector: substituteIntExpr(variable.selector, names, exprs),  // substitutions
        value: substituteIntExpr(variable.value, names, exprs)         // in all of these??
      }

    default:
      throw new Error('WP error: unknown variable type:', variable.type)
  }
}

function combineResults(results) {
  // TOOD: is there a better way? (maybe concat without creating new array?)
  return results.reduce((acc, res) => {
    predicates: acc.predicates.concat(res.predicates),
    context: acc.context.concat(res.context)
  })
}

function conjunction(predicates) {
  return predicates.reduce((conj, pred) => ({
    type: 'and',
    left: conj,
    right: pred
  }))
}

// You can omit step and branch
function createContext({ command, type, step, branch }) {
  // TODO: either change docs or return to what we had before
  let context = { proofType: type, proofStep: step, proofBranch branch }

  if (Array.isArray(command)) {
    // TODO: what if command is empty (this *can* happen)
    // (past me did not bother to left a note on *when* it can happen)
    context.start = command[0].textRange.start
    context.end = command[commands.length - 1].textRange.end
  } else {
    context.start = command.textRange.start
    context.end = command.textRange.end
  }

  return context
}

module.exports = wp
<|endoftext|>


<commit_before>/*global chrome*/

/**
 * Long lived background script running in the browser, works in tandem with the
 * client-script to coordinate messaging between EmberDebug, EmberInspector and the
 * ClientApp.  The background-script serves as a proxy between the EmberInspector
 * and the content-script.
 *
 * It is also responsible for showing the Ember icon and tooltip in the url bar.
 *
 * See:
 *     https://developer.chrome.com/extensions/background_pages
 *     https://developer.chrome.com/extensions/messaging
 */
(function() {
  "use strict";

  var activeTabs = {},
      activeTabId,
      contextMenuAdded = false,
      emberInspectorChromePorts = {};

  /**
   * Creates the tooltip string to show the version of libraries used in the ClientApp
   * @param  {Array} versions - array of library objects
   * @return {String} - string of library names and versions
   */
  function generateVersionsTooltip(versions) {
    return versions.map(function(lib) {
      return lib.name + " " + lib.version;
    }).join("\n");
  }

  /**
   * Creates the title for the pageAction for the current ClientApp
   * @param {Number} tabId - the current tab
   */
  function setActionTitle(tabId) {
    chrome.pageAction.setTitle({
      tabId: tabId,
      title: generateVersionsTooltip(activeTabs[tabId])
    });
  }

  /**
   * Update the tab's pageAction: https://developer.chrome.com/extensions/pageAction
   * If the user has choosen to display it, the icon is shown and the title
   * is updated to display the ClientApp's information in the tooltip.
   * @param {Number} tabId - the current tab
   */
  function updateTabAction(tabId) {
    chrome.storage.sync.get("options", function(data) {
      if (!data.options || !data.options.showTomster) { return; }
      chrome.pageAction.show(tabId);
      setActionTitle(tabId);
    });
  }

  /**
   * Remove the curent tab's Ember icon.
   * Typically used to clearout the icon after reload.
   * @param {Number} tabId - the current tab
   */
  function hideAction(tabId) {
    if (!activeTabs[tabId]) {
      return;
    }

    chrome.pageAction.hide(tabId);
  }

  /**
   * Update the tab's contextMenu: https://developer.chrome.com/extensions/contextMenus
   * Add a menu item called "Inspect Ember Component" that shows info
   * about the component in the inspector.
   * @param {Boolean} force don't use the activeTabs array to check for an existing context menu
   */
  function updateContextMenu(force) {
    // The Chromium that Electron runs does not have a chrome.contextMenus,
    // so make sure this doesn't throw an error in Electron
    if (!chrome.contextMenus) {
      return;
    }

    // Only add context menu item when an Ember app has been detected
    var isEmberApp = !!activeTabs[activeTabId] || force;
    if (!isEmberApp && contextMenuAdded) {
      chrome.contextMenus.remove('inspect-ember-component');
      contextMenuAdded = false;
    }

    if (isEmberApp && !contextMenuAdded) {
      chrome.contextMenus.create({
        id: 'inspect-ember-component',
        title: 'Inspect Ember Component',
        contexts: ['all'],
        onclick: function() {
          chrome.tabs.sendMessage(activeTabId, {
            from: 'devtools',
            type: 'view:inspectComponent'
          });
        }
      });
      contextMenuAdded = true;
    }
  }

  /**
   * Listen for a connection request from the EmberInspector.
   * When the EmberInspector connects to the extension a messageListener
   * is added for the specific EmberInspector instance, and saved into
   * an array, keyed by appId.
   *
   * @param {Port} emberInspectorChromePort
   */
  chrome.runtime.onConnect.addListener(function(emberInspectorChromePort) {
    var appId;

    /**
     * Listen for messages from the EmberInspector.
     * The first message is used to save the port, all others are forwarded
     * to the content-script.
     * @param {Message} message
     */
    emberInspectorChromePort.onMessage.addListener(function(message) {
      // if the message contains the appId, this is the first
      // message and the appId is used to map the port for this app.
      if (message.appId) {
        appId = message.appId;

        emberInspectorChromePorts[appId] = emberInspectorChromePort;

        emberInspectorChromePort.onDisconnect.addListener(function() {
          delete emberInspectorChromePorts[appId];
        });
      } else if (message.from === 'devtools') {
        // all other messages from EmberInspector are forwarded to the content-script
        // https://developer.chrome.com/extensions/tabs#method-sendMessage
        chrome.tabs.sendMessage(appId, message);
      }
    });
  });

  /**
   * Listen for messages from the content-script.
   * A few events trigger specfic behavior, all others are forwarded to EmberInspector.
   * @param {Object} request
   * @param {MessageSender} sender
   */
  chrome.runtime.onMessage.addListener(function(request, sender) {
    // only listen to messages from the content-script
    if (!sender.tab) {
      // noop
    } else if (request && request.type === 'emberVersion') {
      // set the version info and update title
      activeTabs[sender.tab.id] = request.versions;

      updateTabAction(sender.tab.id);
      updateContextMenu();
    } else if (request && request.type === 'resetEmberIcon') {
      // hide the Ember icon
      hideAction(sender.tab.id);
    } else if (request && request.type === 'inspectorLoaded') {
      updateContextMenu(true);
    } else {
      // forward the message to EmberInspector
      var emberInspectorChromePort = emberInspectorChromePorts[sender.tab.id];
      if (emberInspectorChromePort) { emberInspectorChromePort.postMessage(request); }
    }
  });

  /**
   * Keep track of which browser tab is active and update the context menu.
   */
  chrome.tabs.onActivated.addListener(({ tabId }) => {
    activeTabId = tabId;
    if (activeTabs[tabId]) {
      updateTabAction(tabId);
    }
    updateContextMenu();
  });

  /**
   * Only keep track of active tabs
   */
  chrome.tabs.onRemoved.addListener(({ tabId }) => {
    delete activeTabs[tabId];
  });

}());
<commit_msg>bring back the inspect component context menu (#2043)<commit_after>/*global chrome*/

/**
 * Long lived background script running in the browser, works in tandem with the
 * client-script to coordinate messaging between EmberDebug, EmberInspector and the
 * ClientApp.  The background-script serves as a proxy between the EmberInspector
 * and the content-script.
 *
 * It is also responsible for showing the Ember icon and tooltip in the url bar.
 *
 * See:
 *     https://developer.chrome.com/extensions/background_pages
 *     https://developer.chrome.com/extensions/messaging
 */
(function() {
  "use strict";

  var activeTabs = {},
      activeTabId,
      contextMenuAdded = false,
      emberInspectorChromePorts = {};

  /**
   * Creates the tooltip string to show the version of libraries used in the ClientApp
   * @param  {Array} versions - array of library objects
   * @return {String} - string of library names and versions
   */
  function generateVersionsTooltip(versions) {
    return versions.map(function(lib) {
      return lib.name + " " + lib.version;
    }).join("\n");
  }

  /**
   * Creates the title for the pageAction for the current ClientApp
   * @param {Number} tabId - the current tab
   */
  function setActionTitle(tabId) {
    chrome.pageAction.setTitle({
      tabId: tabId,
      title: generateVersionsTooltip(activeTabs[tabId])
    });
  }

  /**
   * Update the tab's pageAction: https://developer.chrome.com/extensions/pageAction
   * If the user has choosen to display it, the icon is shown and the title
   * is updated to display the ClientApp's information in the tooltip.
   * @param {Number} tabId - the current tab
   */
  function updateTabAction(tabId) {
    chrome.storage.sync.get("options", function(data) {
      if (!data.options || !data.options.showTomster) { return; }
      chrome.pageAction.show(tabId);
      setActionTitle(tabId);
    });
  }

  /**
   * Remove the curent tab's Ember icon.
   * Typically used to clearout the icon after reload.
   * @param {Number} tabId - the current tab
   */
  function hideAction(tabId) {
    if (!activeTabs[tabId]) {
      return;
    }

    chrome.pageAction.hide(tabId);
  }

  /**
   * Update the tab's contextMenu: https://developer.chrome.com/extensions/contextMenus
   * Add a menu item called "Inspect Ember Component" that shows info
   * about the component in the inspector.
   * @param {Boolean} force don't use the activeTabs array to check for an existing context menu
   */
  function updateContextMenu(force) {
    // The Chromium that Electron runs does not have a chrome.contextMenus,
    // so make sure this doesn't throw an error in Electron
    if (!chrome.contextMenus) {
      return;
    }

    // Only add context menu item when an Ember app has been detected
    var isEmberApp = !!activeTabs[activeTabId] || force;
    if (!isEmberApp && contextMenuAdded) {
      chrome.contextMenus.remove('inspect-ember-component');
      contextMenuAdded = false;
    }

    if (isEmberApp && !contextMenuAdded) {
      chrome.contextMenus.create({
        id: 'inspect-ember-component',
        title: 'Inspect Ember Component',
        contexts: ['all'],
        onclick: function() {
          chrome.tabs.sendMessage(activeTabId, {
            from: 'devtools',
            type: 'view:contextMenu'
          });
        }
      });
      contextMenuAdded = true;
    }
  }

  /**
   * Listen for a connection request from the EmberInspector.
   * When the EmberInspector connects to the extension a messageListener
   * is added for the specific EmberInspector instance, and saved into
   * an array, keyed by appId.
   *
   * @param {Port} emberInspectorChromePort
   */
  chrome.runtime.onConnect.addListener(function(emberInspectorChromePort) {
    var appId;

    /**
     * Listen for messages from the EmberInspector.
     * The first message is used to save the port, all others are forwarded
     * to the content-script.
     * @param {Message} message
     */
    emberInspectorChromePort.onMessage.addListener(function(message) {
      // if the message contains the appId, this is the first
      // message and the appId is used to map the port for this app.
      if (message.appId) {
        appId = message.appId;

        emberInspectorChromePorts[appId] = emberInspectorChromePort;

        emberInspectorChromePort.onDisconnect.addListener(function() {
          delete emberInspectorChromePorts[appId];
        });
      } else if (message.from === 'devtools') {
        // all other messages from EmberInspector are forwarded to the content-script
        // https://developer.chrome.com/extensions/tabs#method-sendMessage
        chrome.tabs.sendMessage(appId, message);
      }
    });
  });

  /**
   * Listen for messages from the content-script.
   * A few events trigger specfic behavior, all others are forwarded to EmberInspector.
   * @param {Object} request
   * @param {MessageSender} sender
   */
  chrome.runtime.onMessage.addListener(function(request, sender) {
    // only listen to messages from the content-script
    if (!sender.tab) {
      // noop
    } else if (request && request.type === 'emberVersion') {
      // set the version info and update title
      activeTabs[sender.tab.id] = request.versions;

      updateTabAction(sender.tab.id);
      updateContextMenu();
    } else if (request && request.type === 'resetEmberIcon') {
      // hide the Ember icon
      hideAction(sender.tab.id);
    } else if (request && request.type === 'inspectorLoaded') {
      updateContextMenu(true);
    } else {
      // forward the message to EmberInspector
      var emberInspectorChromePort = emberInspectorChromePorts[sender.tab.id];
      if (emberInspectorChromePort) { emberInspectorChromePort.postMessage(request); }
    }
  });

  /**
   * Keep track of which browser tab is active and update the context menu.
   */
  chrome.tabs.onActivated.addListener(({ tabId }) => {
    activeTabId = tabId;
    if (activeTabs[tabId]) {
      updateTabAction(tabId);
    }
    updateContextMenu();
  });

  /**
   * Only keep track of active tabs
   */
  chrome.tabs.onRemoved.addListener(({ tabId }) => {
    delete activeTabs[tabId];
  });

}());
<|endoftext|>


<commit_before>/**
 * Carga los grupos iconicos y su respectivo conteo
 * @param properties
 */
var cargaGrupos = function()
{
    $.ajax({
        url: '/explora-por-region/conteo-por-grupo',
        data: parametros()
    }).done(function(resp) {
        if (resp.estatus)
        {
            $('#contenedor_grupos').empty();
            $('#contenedor_especies').empty();
            var grupos_orden = [resp.resultados[5],resp.resultados[1],resp.resultados[9],resp.resultados[0],
                resp.resultados[6],resp.resultados[4], resp.resultados[7],resp.resultados[3],resp.resultados[2],resp.resultados[8]];
            $.each(grupos_orden, function(index, prop){
                $('#contenedor_grupos').append('<input type="radio" name="id">' +
                    ' ' + prop.total +  '');
            });
        } else
            console.log('Falló el servicio de conteo del SNIB');

    }).fail(function() {
        console.log('Falló el servicio de conteo del SNIB');
    });
};

/**
 * Devuelve los parametros de acuerdo a los filtros, grupo, region y paginado
 * @param prop, parametros adicionales
 * @returns {string}
 */
var parametros = function(prop)
{
    var params_generales = { tipo_region: opciones.tipo_region_seleccionado, grupo: opciones.grupo_seleccionado, estado_id: opciones.estado_seleccionado,
        municipio_id: opciones.municipio_seleccionado, pagina: opciones.pagina_especies, nombre: $('#nombre').val() };

    if (prop != undefined)
        params_generales = Object.assign({},params_generales, prop);

    return $('#b_avanzada').serialize() + '&' + $.param(params_generales);
};

/**
 * Pregunta por los datos correspondientes a estas especies en nuestra base, todos deberian coincidir en teoria
 * ya que son ids de catalogos, a excepcion de los nuevos, ya que aún no se actualiza a la base centralizada
 */
var cargaEspecies = function()
{
    $.ajax({
        url: '/explora-por-region/especies-por-grupo',
        method: 'GET',
        data: parametros()
    }).done(function(resp) {
        if (resp.estatus)  // Para asignar los resultados con o sin filtros
        {
            if (opciones.pagina_especies == 1) $('#contenedor_especies').empty();
            $('#grupos').find("[grupo_id_badge='" + opciones.grupo_seleccionado + "']").text(resp.totales);

            $.each(resp.resultados, function(index, taxon){
                var url = dameUrlServicioSnibPorRegion({catalogo_id: taxon.catalogo_id, estado_id: opciones.estado_seleccionado,
                    municipio_id: opciones.municipio_seleccionado, snib_url: opciones.snib_url, reino: opciones.reino_seleccionado});
                if (url == undefined) return;

                // Las que no tiene imagen se le pega la fuente
                if (taxon.foto == null)
                    var recurso = '';
                else
                    var recurso = '';

                // Las que no tienen nombre común se le pondra vacío
                if (taxon.nombre_comun == null) taxon.nombre_comun = '';

                $('#contenedor_especies').append('
' +
                '' + recurso + '' + taxon.nregistros + '' +
                '
' +
                '' + taxon.nombre_comun + '
' +
                '' + taxon.nombre_cientifico + '' +
                '
' +
                '
');
            });
        } else
            console.log(resp.msg);

    }).fail(function() {
        console.log('Falló la carga de especies de enciclovida');
    });
};

/**
 * Pone los municipios correspondientes, selecciona el valor y carga la region
 * @param valor
 */
var seleccionaEstado = function(region_id)
{
    $('#svg-division-municipal').remove();

    if (region_id == '')
    {
        $('#region_municipio').empty().append('<option value>- - - - - - - -</option>').prop('disabled', true);
        opciones.estado_seleccionado = null;
        opciones.tipo_region_seleccionado = null;
    } else {
        var region_id = parseInt(region_id);
        $('#region_estado').val(region_id);
        $('#region_municipio').empty().append('<option value>- - - Escoge un municipio - - -</option>');
        $('#region_municipio').prop('disabled', false).attr('parent_id', region_id);

        opciones.estado_seleccionado = region_id;
        opciones.municipio_seleccionado = null;
        opciones.tipo_region_seleccionado = 'estado';
        cargaRegion(opciones.datos[region_id].properties);
    }
};

/**
 * Pone los municipios correspondientes, selecciona el valor y carga la region
 * @param valor
 */
var seleccionaMunicipio = function(region_id)
{
    if (region_id == '')
    {
        opciones.municipio_seleccionado = null;
        opciones.tipo_region_seleccionado = 'estado';

    } else {
        var region_id = parseInt(region_id);
        $('#region_municipio').val(region_id);
        opciones.municipio_seleccionado = region_id;
        opciones.tipo_region_seleccionado = 'municipio';
        cargaRegion(opciones.datos[opciones.estado_seleccionado].municipios[region_id].properties);
    }
};

/**
 * El nombre de la region cuanso se pasa el mouse por encima
 * @param prop
 */
var nombreRegion = function(prop)
{
    $('#contenedor-subtitulo-busqueda-region').html(prop.nombre_region);
};

/**
 * Rellena las opciones de estado y municipio
 * @param prop
 */
var completaSelect = function(prop)
{
    $('#region_' + prop.tipo_region).append("<option value='" + parseInt(prop.region_id) + "'>" + prop.nombre_region + '</option>');
};

/**
 * Devuelve la URL de las especies por region
 * @param prop
 * @returns {string}
 */
var dameUrlServicioSnibPorRegion = function(prop)
{
    prop.estado_id = opciones.correspondencia[prop.estado_id];

    var snib_url = prop.snib_url + '/' + prop.reino + '/' + prop.catalogo_id + '/' + prop.estado_id;

    if (prop.municipio_id != null && prop.municipio_id != '')
    {
        prop.municipio_id = ('00'+prop.municipio_id).slice(-3);
        snib_url+= '/' + prop.municipio_id;
    }

    snib_url+= '?apiKey=enciclovida';
    return snib_url;
};

$(document).ready(function(){

    /**
     * Cuando selecciona un grupo de los grupos icónicos
     */
    $('#contenedor_grupos').on('click', '.grupo_id', function(){
        opciones.grupo_seleccionado = $(this).attr('grupo');
        opciones.pagina_especies = 1;
        opciones.reino_seleccionado = $(this).attr('reino');
        cargaEspecies();
    });

    /**
     * Cuando selecciona una especie
     */
    $('#contenedor_especies').on('click', '.especie_id', function(){
        cargaEjemplaresSnib($(this).attr('snib_url'));
        opciones.taxon = $(this).attr('especie_id');

        $.ajax({
            url: '/especies/' + opciones.taxon + '/dame-nombre-con-formato',
            type: 'GET',
        }).done(function(nombre) {
            opciones.nombre = nombre;

        }).fail(function(){
            opciones.nombre = '';
        });
    });

    /**
     *  Cuando selecciona un estado de la lista
     */
    $('#regiones').on('change', '#region_estado', function(){
        seleccionaEstado($(this).val());
    });

    /**
     * Cuando selecciona un municipio de la lista
     */
    $('#regiones').on('change', '#region_municipio', function(){
        seleccionaMunicipio($(this).val());
    });

    /**
     * Para los filtros default: distribucion y riesgo
     */
    $('#b_avanzada').on('change', ".checkbox input", function()
    {
        opciones.pagina_especies = 1;
        cargaEspecies();
    });

    /**
     * Para enviar la descarga o que se envie correo
     */
    $(document).on('keyup', '#correo', function(){
        if( !correoValido($(this).val()) )
        {
            $(this).parent().addClass("has-error");
            $(this).parent().removeClass("has-success");

            $(this).siblings("span:first").addClass("glyphicon-remove");
            $(this).siblings("span:first").removeClass("glyphicon-ok");
            $('#boton_enviar_descarga').attr('disabled', 'disabled')
        } else {
            $(this).parent().removeClass("has-error");
            $(this).parent().addClass("has-success");
            $(this).siblings("span:first").addClass("glyphicon-ok");
            $(this).siblings("span:first").removeClass("glyphicon-remove");
            $('#boton_enviar_descarga').removeAttr('disabled')
        }
    });

    /**
     * Para validar una ultima vez cuando paso la validacion del boton
     */
    $(document).on('click', '#boton_enviar_descarga', function(){
        var correo = $('#correo').val();

        if(correoValido(correo))
        {
            $.ajax({
                url: '/explora-por-region/descarga-taxa',
                type: 'GET',
                dataType: "json",
                data: parametros({correo: correo})
            }).done(function(resp) {
                if (resp.estatus == 1)
                {
                    $('#estatus_descargar_taxa').empty().html('!La petición se envió correctamente!. Se te enviará un correo con los resultados que seleccionaste.');
                } else
                    $('#estatus_descargar_taxa').empty().html(resp.msg);

            }).fail(function(){
                $('#estatus_descargar_taxa').empty().html('Lo sentimos no se pudo procesar tu petición, asegurate de haber anotado correctamente tu correo e inténtalo de nuevo.');
            });

        } else
            $('#estatus_descargar_taxa').empty().html('El correo no parece válido, por favor verifica.');
    });

    /**
     * Esta funcion se sustituirá por el scrolling
     */
    $('#carga_mas_especies').on('click', function(){
        opciones.pagina_especies++;
        cargaEspecies();
        return false;
    });

    /**
     * Cuando autocompleta por nombre cientifico o comun
     */
    $('#especies').on('keyup', '#nombre', function(){
        opciones.pagina_especies = 1;
        cargaEspecies();
    });

    /**
     * Para que aparezca la barra del scroll en las especies
     */
    $(window).load(function()
    {
        $("html,body").animate({scrollTop: 122}, 1000);
    });
});<commit_msg>fix para q en la seleccion de grupos iconicos, tome en cuenta toda el area seleccionable para el evento<commit_after>/**
 * Carga los grupos iconicos y su respectivo conteo
 * @param properties
 */
var cargaGrupos = function()
{
    $.ajax({
        url: '/explora-por-region/conteo-por-grupo',
        data: parametros()
    }).done(function(resp) {
        if (resp.estatus)
        {
            $('#contenedor_grupos').empty();
            $('#contenedor_especies').empty();
            var grupos_orden = [resp.resultados[5],resp.resultados[1],resp.resultados[9],resp.resultados[0],
                resp.resultados[6],resp.resultados[4], resp.resultados[7],resp.resultados[3],resp.resultados[2],resp.resultados[8]];
            $.each(grupos_orden, function(index, prop){
                $('#contenedor_grupos').append('<input type="radio" name="id">' + ' ' + prop.total +  '');
            });
        } else
            console.log('Falló el servicio de conteo del SNIB');

    }).fail(function() {
        console.log('Falló el servicio de conteo del SNIB');
    });
};

/**
 * Devuelve los parametros de acuerdo a los filtros, grupo, region y paginado
 * @param prop, parametros adicionales
 * @returns {string}
 */
var parametros = function(prop)
{
    var params_generales = { tipo_region: opciones.tipo_region_seleccionado, grupo: opciones.grupo_seleccionado, estado_id: opciones.estado_seleccionado,
        municipio_id: opciones.municipio_seleccionado, pagina: opciones.pagina_especies, nombre: $('#nombre').val() };

    if (prop != undefined)
        params_generales = Object.assign({},params_generales, prop);

    return $('#b_avanzada').serialize() + '&' + $.param(params_generales);
};

/**
 * Pregunta por los datos correspondientes a estas especies en nuestra base, todos deberian coincidir en teoria
 * ya que son ids de catalogos, a excepcion de los nuevos, ya que aún no se actualiza a la base centralizada
 */
var cargaEspecies = function()
{
    $.ajax({
        url: '/explora-por-region/especies-por-grupo',
        method: 'GET',
        data: parametros()
    }).done(function(resp) {
        if (resp.estatus)  // Para asignar los resultados con o sin filtros
        {
            if (opciones.pagina_especies == 1) $('#contenedor_especies').empty();
            $('#grupos').find("[grupo_id_badge='" + opciones.grupo_seleccionado + "']").text(resp.totales);

            $.each(resp.resultados, function(index, taxon){
                var url = dameUrlServicioSnibPorRegion({catalogo_id: taxon.catalogo_id, estado_id: opciones.estado_seleccionado,
                    municipio_id: opciones.municipio_seleccionado, snib_url: opciones.snib_url, reino: opciones.reino_seleccionado});
                if (url == undefined) return;

                // Las que no tiene imagen se le pega la fuente
                if (taxon.foto == null)
                    var recurso = '';
                else
                    var recurso = '';

                // Las que no tienen nombre común se le pondra vacío
                if (taxon.nombre_comun == null) taxon.nombre_comun = '';

                $('#contenedor_especies').append('
' +
                '' + recurso + '' + taxon.nregistros + '' +
                '
' +
                '' + taxon.nombre_comun + '
' +
                '' + taxon.nombre_cientifico + '' +
                '
' +
                '
');
            });
        } else
            console.log(resp.msg);

    }).fail(function() {
        console.log('Falló la carga de especies de enciclovida');
    });
};

/**
 * Pone los municipios correspondientes, selecciona el valor y carga la region
 * @param valor
 */
var seleccionaEstado = function(region_id)
{
    $('#svg-division-municipal').remove();

    if (region_id == '')
    {
        $('#region_municipio').empty().append('<option value>- - - - - - - -</option>').prop('disabled', true);
        opciones.estado_seleccionado = null;
        opciones.tipo_region_seleccionado = null;
    } else {
        var region_id = parseInt(region_id);
        $('#region_estado').val(region_id);
        $('#region_municipio').empty().append('<option value>- - - Escoge un municipio - - -</option>');
        $('#region_municipio').prop('disabled', false).attr('parent_id', region_id);

        opciones.estado_seleccionado = region_id;
        opciones.municipio_seleccionado = null;
        opciones.tipo_region_seleccionado = 'estado';
        cargaRegion(opciones.datos[region_id].properties);
    }
};

/**
 * Pone los municipios correspondientes, selecciona el valor y carga la region
 * @param valor
 */
var seleccionaMunicipio = function(region_id)
{
    if (region_id == '')
    {
        opciones.municipio_seleccionado = null;
        opciones.tipo_region_seleccionado = 'estado';

    } else {
        var region_id = parseInt(region_id);
        $('#region_municipio').val(region_id);
        opciones.municipio_seleccionado = region_id;
        opciones.tipo_region_seleccionado = 'municipio';
        cargaRegion(opciones.datos[opciones.estado_seleccionado].municipios[region_id].properties);
    }
};

/**
 * El nombre de la region cuanso se pasa el mouse por encima
 * @param prop
 */
var nombreRegion = function(prop)
{
    $('#contenedor-subtitulo-busqueda-region').html(prop.nombre_region);
};

/**
 * Rellena las opciones de estado y municipio
 * @param prop
 */
var completaSelect = function(prop)
{
    $('#region_' + prop.tipo_region).append("<option value='" + parseInt(prop.region_id) + "'>" + prop.nombre_region + '</option>');
};

/**
 * Devuelve la URL de las especies por region
 * @param prop
 * @returns {string}
 */
var dameUrlServicioSnibPorRegion = function(prop)
{
    prop.estado_id = opciones.correspondencia[prop.estado_id];

    var snib_url = prop.snib_url + '/' + prop.reino + '/' + prop.catalogo_id + '/' + prop.estado_id;

    if (prop.municipio_id != null && prop.municipio_id != '')
    {
        prop.municipio_id = ('00'+prop.municipio_id).slice(-3);
        snib_url+= '/' + prop.municipio_id;
    }

    snib_url+= '?apiKey=enciclovida';
    return snib_url;
};

$(document).ready(function(){

    /**
     * Cuando selecciona un grupo de los grupos icónicos
     */
    $('#contenedor_grupos').on('click', '.grupo_id', function(){
        opciones.grupo_seleccionado = $(this).attr('grupo');
        opciones.pagina_especies = 1;
        opciones.reino_seleccionado = $(this).attr('reino');
        cargaEspecies();
    });

    /**
     * Cuando selecciona una especie
     */
    $('#contenedor_especies').on('click', '.especie_id', function(){
        cargaEjemplaresSnib($(this).attr('snib_url'));
        opciones.taxon = $(this).attr('especie_id');

        $.ajax({
            url: '/especies/' + opciones.taxon + '/dame-nombre-con-formato',
            type: 'GET',
        }).done(function(nombre) {
            opciones.nombre = nombre;

        }).fail(function(){
            opciones.nombre = '';
        });
    });

    /**
     *  Cuando selecciona un estado de la lista
     */
    $('#regiones').on('change', '#region_estado', function(){
        seleccionaEstado($(this).val());
    });

    /**
     * Cuando selecciona un municipio de la lista
     */
    $('#regiones').on('change', '#region_municipio', function(){
        seleccionaMunicipio($(this).val());
    });

    /**
     * Para los filtros default: distribucion y riesgo
     */
    $('#b_avanzada').on('change', ".checkbox input", function()
    {
        opciones.pagina_especies = 1;
        cargaEspecies();
    });

    /**
     * Para enviar la descarga o que se envie correo
     */
    $(document).on('keyup', '#correo', function(){
        if( !correoValido($(this).val()) )
        {
            $(this).parent().addClass("has-error");
            $(this).parent().removeClass("has-success");

            $(this).siblings("span:first").addClass("glyphicon-remove");
            $(this).siblings("span:first").removeClass("glyphicon-ok");
            $('#boton_enviar_descarga').attr('disabled', 'disabled')
        } else {
            $(this).parent().removeClass("has-error");
            $(this).parent().addClass("has-success");
            $(this).siblings("span:first").addClass("glyphicon-ok");
            $(this).siblings("span:first").removeClass("glyphicon-remove");
            $('#boton_enviar_descarga').removeAttr('disabled')
        }
    });

    /**
     * Para validar una ultima vez cuando paso la validacion del boton
     */
    $(document).on('click', '#boton_enviar_descarga', function(){
        var correo = $('#correo').val();

        if(correoValido(correo))
        {
            $.ajax({
                url: '/explora-por-region/descarga-taxa',
                type: 'GET',
                dataType: "json",
                data: parametros({correo: correo})
            }).done(function(resp) {
                if (resp.estatus == 1)
                {
                    $('#estatus_descargar_taxa').empty().html('!La petición se envió correctamente!. Se te enviará un correo con los resultados que seleccionaste.');
                } else
                    $('#estatus_descargar_taxa').empty().html(resp.msg);

            }).fail(function(){
                $('#estatus_descargar_taxa').empty().html('Lo sentimos no se pudo procesar tu petición, asegurate de haber anotado correctamente tu correo e inténtalo de nuevo.');
            });

        } else
            $('#estatus_descargar_taxa').empty().html('El correo no parece válido, por favor verifica.');
    });

    /**
     * Esta funcion se sustituirá por el scrolling
     */
    $('#carga_mas_especies').on('click', function(){
        opciones.pagina_especies++;
        cargaEspecies();
        return false;
    });

    /**
     * Cuando autocompleta por nombre cientifico o comun
     */
    $('#especies').on('keyup', '#nombre', function(){
        opciones.pagina_especies = 1;
        cargaEspecies();
    });

    /**
     * Para que aparezca la barra del scroll en las especies
     */
    $(window).load(function()
    {
        $("html,body").animate({scrollTop: 122}, 1000);
    });
});<|endoftext|>


<commit_before>// Tell jslint that certain variables are global
/* global afqb, FileReader, d3, d3_queue, THREE */

afqb.global.formatKeyName = function(bundle) {
    // Standardize bundle names by making them lower case and
    // replacing all dots and spaces with dashes
    return bundle.toLowerCase().replace(/\s+/g, "-").replace(/\./g, "-");
};

afqb.global.updateQueryString = function(queryObj) {
    "use strict";

    var urlSettings = Qs.parse(location.search.slice(1));
    var updatedSettings = $.extend(true, {}, urlSettings, queryObj);

    var settingsStr = "?" + Qs.stringify(updatedSettings, {encode: false});

    window.history.pushState({search: settingsStr}, '', settingsStr);
};

afqb.global.initSettings = function (callback) {
    "use strict";
    if (afqb.global.settings.loaded) {
        if (callback) { callback(null); }
	} else {
        // Load default settings from settings.json
        d3.json("settings.json", function(settings) {
            // Update with values from query string
            "use strict";
            var qsSettings = Qs.parse(location.search.slice(1));
            var updatedSettings = $.extend(true, {}, settings, qsSettings);

            afqb.three.settings = $.extend(true, {}, afqb.three.settings, updatedSettings.three);
            afqb.plots.settings = $.extend(true, {}, afqb.plots.settings, updatedSettings.plots);
            afqb.table.settings = $.extend(true, {}, afqb.table.settings, updatedSettings.table);
            afqb.global.settings = $.extend(true, {}, afqb.global.settings, updatedSettings.global);

            // Restore spaces and capitalized words in splitMethod
            afqb.table.settings.splitMethod = afqb.table.settings.splitMethod
                .split("-").map(function (word) {
                    return word.charAt(0).toUpperCase() + word.slice(1);
                }).join(" ");

            // Parse all the checkbox strings as booleans
            Object.keys(afqb.plots.settings.checkboxes).forEach(function (bundle) {
                afqb.plots.settings.checkboxes[bundle] = (
                	afqb.plots.settings.checkboxes[bundle].toLowerCase() === 'true'
				);
            });

            // Parse the brushTract checkbox as boolean
            if (typeof afqb.plots.settings.brushTract !== 'boolean') {
                afqb.plots.settings.brushTract = (afqb.plots.settings.brushTract.toLowerCase() === 'true');
            }

            // Parse the brushes
            if (afqb.plots.settings.hasOwnProperty("brushes")) {
                Object.keys(afqb.plots.settings.brushes).forEach(function (bundle) {
                    if (afqb.plots.settings.brushes[bundle].hasOwnProperty("brushOn")
                        && typeof afqb.plots.settings.brushes[bundle].brushOn !== "boolean") {
                        afqb.plots.settings.brushes[bundle].brushOn = (
                            afqb.plots.settings.brushes[bundle].brushOn.toLowerCase() === "true"
                        );
                    }
                    if (afqb.plots.settings.brushes[bundle].hasOwnProperty("brushExtent")) {
                        afqb.plots.settings.brushes[bundle].brushExtent = afqb.plots.settings.brushes[bundle].brushExtent.map(parseFloat);
                    }
                });
            }

            // Parse the zoom params as floats
            if (afqb.plots.settings.hasOwnProperty("zoom")) {
                Object.keys(afqb.plots.settings.zoom).forEach(function (key) {
                    if (afqb.plots.settings.zoom[key].hasOwnProperty("scale")) {
                        afqb.plots.settings.zoom[key].scale = parseFloat(afqb.plots.settings.zoom[key].scale);
                    }
                    if (afqb.plots.settings.zoom[key].hasOwnProperty("translate")) {
                        afqb.plots.settings.zoom[key].translate = afqb.plots
							.settings.zoom[key].translate.map(parseFloat);
                    }
                });
            }

			// Parse lineOpacity as float
			afqb.plots.settings.lineOpacity = parseFloat(afqb.plots.settings.lineOpacity);

			// Parse table sorting counts as ints
			afqb.table.settings.sort.count = parseInt(afqb.table.settings.sort.count);
            afqb.table.settings.prevSort.count = parseInt(afqb.table.settings.prevSort.count);

            if (afqb.table.settings.selectedRows) {
                Object.keys(afqb.table.settings.selectedRows).forEach(function (subject) {
                    if (typeof afqb.table.settings.selectedRows[subject] !== "boolean") {
                        afqb.table.settings.selectedRows[subject] = (
                            afqb.table.settings.selectedRows[subject].toLowerCase() === "true"
                        );
                    }
                });
            }

            // Parse three.js opacities as floats
			afqb.three.settings.rHOpacity = parseFloat(afqb.three.settings.rHOpacity);
            afqb.three.settings.lHOpacity = parseFloat(afqb.three.settings.lHOpacity);
            afqb.three.settings.fiberOpacity = parseFloat(afqb.three.settings.fiberOpacity);

            // Parse mouseoverHighlight as boolean
			if (afqb.three.settings.hasOwnProperty("mouseoverHighlight")) {
                if (typeof afqb.three.settings.mouseoverHighlight !== 'boolean') {
                    afqb.three.settings.mouseoverHighlight = (
                        afqb.three.settings.mouseoverHighlight.toLowerCase() === 'true'
                    );
                }
            }

            // Parse camera position as floats
			if (afqb.three.settings.hasOwnProperty("cameraPosition")) {
                Object.keys(afqb.three.settings.cameraPosition).forEach(function (coord) {
                    afqb.three.settings.cameraPosition[coord] = parseFloat(
                        afqb.three.settings.cameraPosition[coord]
                    )
                });
            }

            // Parse fiber representation
            afqb.three.settings.fiberRepresentation = afqb.three.settings.fiberRepresentation
                .split("-").join(" ");

            afqb.global.settings.loaded = true;

            if (callback) { callback(null); }
        });
	}
};

afqb.plots.restoreBrush = function () {
    "use strict";
    Object.keys(afqb.plots.settings.brushes).forEach(function (tract) {
        if (afqb.plots.settings.brushes[tract].brushOn) {
            var targetBrush = afqb.plots.brushes.filter(function (b) {
                return b.name === tract;
            })[0].brush;

            d3.selectAll("#tract-" + tract)
                .selectAll(".brush")
                .call(targetBrush.extent(
                    afqb.plots.settings.brushes[tract].brushExtent
                ));

            var formatter = d3.format(".0f");
            var ext = targetBrush.extent();
            d3.select("#brush-ext-" + tract).text("(" + formatter(ext[0]) + ", " + formatter(ext[1]) + ")");
        }
    });
};

afqb.table.restoreRowSelection = function () {
    "use strict";
    Object.keys(afqb.table.settings.selectedRows).forEach(function (rowID) {
        if (afqb.table.settings.selectedRows[rowID]) {
            d3.selectAll('#' + rowID)
                .selectAll('g')
                .style("opacity", 1);

            d3.selectAll('#' + rowID)
                .selectAll('path')
                .style("opacity", 1)
                .style("stroke-width", "2.1px");
        }
    });
};

$('#launch-binder').click(function (e) {
    e.preventDefault();
    // Parse the URL
    var uri = new URI(location.href);
    var user = uri.hostname();
    var repo = uri.directory();
    var binderUrl = '//mybinder.org/v2/gh/' + user + '/' + repo + '/gh-pages?filepath=index.ipynb';
    console.log(binderUrl);
    // window.open(binderUrl);
    return false;
})
<commit_msg>Set user to drop `github.io` part of the hostname.<commit_after>// Tell jslint that certain variables are global
/* global afqb, FileReader, d3, d3_queue, THREE */

afqb.global.formatKeyName = function(bundle) {
    // Standardize bundle names by making them lower case and
    // replacing all dots and spaces with dashes
    return bundle.toLowerCase().replace(/\s+/g, "-").replace(/\./g, "-");
};

afqb.global.updateQueryString = function(queryObj) {
    "use strict";

    var urlSettings = Qs.parse(location.search.slice(1));
    var updatedSettings = $.extend(true, {}, urlSettings, queryObj);

    var settingsStr = "?" + Qs.stringify(updatedSettings, {encode: false});

    window.history.pushState({search: settingsStr}, '', settingsStr);
};

afqb.global.initSettings = function (callback) {
    "use strict";
    if (afqb.global.settings.loaded) {
        if (callback) { callback(null); }
	} else {
        // Load default settings from settings.json
        d3.json("settings.json", function(settings) {
            // Update with values from query string
            "use strict";
            var qsSettings = Qs.parse(location.search.slice(1));
            var updatedSettings = $.extend(true, {}, settings, qsSettings);

            afqb.three.settings = $.extend(true, {}, afqb.three.settings, updatedSettings.three);
            afqb.plots.settings = $.extend(true, {}, afqb.plots.settings, updatedSettings.plots);
            afqb.table.settings = $.extend(true, {}, afqb.table.settings, updatedSettings.table);
            afqb.global.settings = $.extend(true, {}, afqb.global.settings, updatedSettings.global);

            // Restore spaces and capitalized words in splitMethod
            afqb.table.settings.splitMethod = afqb.table.settings.splitMethod
                .split("-").map(function (word) {
                    return word.charAt(0).toUpperCase() + word.slice(1);
                }).join(" ");

            // Parse all the checkbox strings as booleans
            Object.keys(afqb.plots.settings.checkboxes).forEach(function (bundle) {
                afqb.plots.settings.checkboxes[bundle] = (
                	afqb.plots.settings.checkboxes[bundle].toLowerCase() === 'true'
				);
            });

            // Parse the brushTract checkbox as boolean
            if (typeof afqb.plots.settings.brushTract !== 'boolean') {
                afqb.plots.settings.brushTract = (afqb.plots.settings.brushTract.toLowerCase() === 'true');
            }

            // Parse the brushes
            if (afqb.plots.settings.hasOwnProperty("brushes")) {
                Object.keys(afqb.plots.settings.brushes).forEach(function (bundle) {
                    if (afqb.plots.settings.brushes[bundle].hasOwnProperty("brushOn")
                        && typeof afqb.plots.settings.brushes[bundle].brushOn !== "boolean") {
                        afqb.plots.settings.brushes[bundle].brushOn = (
                            afqb.plots.settings.brushes[bundle].brushOn.toLowerCase() === "true"
                        );
                    }
                    if (afqb.plots.settings.brushes[bundle].hasOwnProperty("brushExtent")) {
                        afqb.plots.settings.brushes[bundle].brushExtent = afqb.plots.settings.brushes[bundle].brushExtent.map(parseFloat);
                    }
                });
            }

            // Parse the zoom params as floats
            if (afqb.plots.settings.hasOwnProperty("zoom")) {
                Object.keys(afqb.plots.settings.zoom).forEach(function (key) {
                    if (afqb.plots.settings.zoom[key].hasOwnProperty("scale")) {
                        afqb.plots.settings.zoom[key].scale = parseFloat(afqb.plots.settings.zoom[key].scale);
                    }
                    if (afqb.plots.settings.zoom[key].hasOwnProperty("translate")) {
                        afqb.plots.settings.zoom[key].translate = afqb.plots
							.settings.zoom[key].translate.map(parseFloat);
                    }
                });
            }

			// Parse lineOpacity as float
			afqb.plots.settings.lineOpacity = parseFloat(afqb.plots.settings.lineOpacity);

			// Parse table sorting counts as ints
			afqb.table.settings.sort.count = parseInt(afqb.table.settings.sort.count);
            afqb.table.settings.prevSort.count = parseInt(afqb.table.settings.prevSort.count);

            if (afqb.table.settings.selectedRows) {
                Object.keys(afqb.table.settings.selectedRows).forEach(function (subject) {
                    if (typeof afqb.table.settings.selectedRows[subject] !== "boolean") {
                        afqb.table.settings.selectedRows[subject] = (
                            afqb.table.settings.selectedRows[subject].toLowerCase() === "true"
                        );
                    }
                });
            }

            // Parse three.js opacities as floats
			afqb.three.settings.rHOpacity = parseFloat(afqb.three.settings.rHOpacity);
            afqb.three.settings.lHOpacity = parseFloat(afqb.three.settings.lHOpacity);
            afqb.three.settings.fiberOpacity = parseFloat(afqb.three.settings.fiberOpacity);

            // Parse mouseoverHighlight as boolean
			if (afqb.three.settings.hasOwnProperty("mouseoverHighlight")) {
                if (typeof afqb.three.settings.mouseoverHighlight !== 'boolean') {
                    afqb.three.settings.mouseoverHighlight = (
                        afqb.three.settings.mouseoverHighlight.toLowerCase() === 'true'
                    );
                }
            }

            // Parse camera position as floats
			if (afqb.three.settings.hasOwnProperty("cameraPosition")) {
                Object.keys(afqb.three.settings.cameraPosition).forEach(function (coord) {
                    afqb.three.settings.cameraPosition[coord] = parseFloat(
                        afqb.three.settings.cameraPosition[coord]
                    )
                });
            }

            // Parse fiber representation
            afqb.three.settings.fiberRepresentation = afqb.three.settings.fiberRepresentation
                .split("-").join(" ");

            afqb.global.settings.loaded = true;

            if (callback) { callback(null); }
        });
	}
};

afqb.plots.restoreBrush = function () {
    "use strict";
    Object.keys(afqb.plots.settings.brushes).forEach(function (tract) {
        if (afqb.plots.settings.brushes[tract].brushOn) {
            var targetBrush = afqb.plots.brushes.filter(function (b) {
                return b.name === tract;
            })[0].brush;

            d3.selectAll("#tract-" + tract)
                .selectAll(".brush")
                .call(targetBrush.extent(
                    afqb.plots.settings.brushes[tract].brushExtent
                ));

            var formatter = d3.format(".0f");
            var ext = targetBrush.extent();
            d3.select("#brush-ext-" + tract).text("(" + formatter(ext[0]) + ", " + formatter(ext[1]) + ")");
        }
    });
};

afqb.table.restoreRowSelection = function () {
    "use strict";
    Object.keys(afqb.table.settings.selectedRows).forEach(function (rowID) {
        if (afqb.table.settings.selectedRows[rowID]) {
            d3.selectAll('#' + rowID)
                .selectAll('g')
                .style("opacity", 1);

            d3.selectAll('#' + rowID)
                .selectAll('path')
                .style("opacity", 1)
                .style("stroke-width", "2.1px");
        }
    });
};

$('#launch-binder').click(function (e) {
    e.preventDefault();
    // Parse the URL
    var uri = new URI(location.href);
    var user = user.hostname().replace('.github.io/', '');
    var repo = uri.directory();
    var binderUrl = '//mybinder.org/v2/gh/' + user + '/' + repo + '/gh-pages?filepath=index.ipynb';
    console.log(binderUrl);
    // window.open(binderUrl);
    return false;
})
<|endoftext|>