<commit_before>var operands = (function() {
    "use strict";

    var list = {
        'match-tag' : function(actual, expected) {
            actual = actual.substr(actual.lastIndexOf('.') + 1);

            return (actual.toLowerCase() === expected.toLowerCase());
        },
        '=' : function(actual, expected) {
            return (actual == expected);
        },
        '~=' : function(actual, expected) {
            //TODO what if the expected value is not simply alphabetical but a value like .* ?
            return ((new RegExp('(^| )' + expected + '($| )')).test(actual));
        },
        '|=' : function(actual, expected) {
            return (actual == expected) | (String(actual).indexOf(String(expected) + '-') === 0);
        },
        '^=' : function(actual, expected) {
            return (String(actual).indexOf(expected) === 0);
        },
        '$=' : function(actual, expected) {
            //TODO optimize this
            return (String(actual).lastIndexOf(expected) === String(actual).length - String(expected).length);
        },
        '*=' : function(actual, expected) {
            return (String(actual).indexOf(expected) > -1);
        }
    };

    var operands = function(operand, actual, expected) {
        var result = false;

        if(actual && list.hasOwnProperty(operand)) {
            result = !!list[operand](actual, expected);
        }

        return result;
    };

    return operands;
}());

module.exports = operands;<commit_msg>check on properties type, always cast it to string before testing<commit_after>var operands = (function() {
    "use strict";

    var list = {
        'match-tag' : function(actual, expected) {
            actual = actual.substr(actual.lastIndexOf('.') + 1);

            return (actual.toLowerCase() === expected.toLowerCase());
        },
        '=' : function(actual, expected) {
            return (actual === expected);
        },
        '~=' : function(actual, expected) {
            //TODO what if the expected value is not simply alphabetical but a value like .* ?
            return ((new RegExp('(^| )' + expected + '($| )')).test(actual));
        },
        '|=' : function(actual, expected) {
            return (actual === expected) | (actual.indexOf(expected + '-') === 0);
        },
        '^=' : function(actual, expected) {
            return (actual.indexOf(expected) === 0);
        },
        '$=' : function(actual, expected) {

            return (actual.lastIndexOf(expected) === actual.length - expected.length);
        },
        '*=' : function(actual, expected) {
            return (actual.indexOf(expected) > -1);
        }
    };

    var operands = function(operand, actual, expected) {
        var result = false,
            typeActual = typeof actual;

        if(
            (typeActual === 'string' || typeActual === 'number') &&
            list.hasOwnProperty(operand)
        ) {
            result = !!list[operand](String(actual), String(expected));
        }

        return result;
    };

    return operands;
}());

module.exports = operands;<|endoftext|>


<commit_before>// const logger = require('winston')
const lists = require('../model/lists')
const listItems = require('../model/listitems')
const modelConstants = require('../model/modelconstants')
const smsProcessor = require('../src/smsprocessor')
const phrases = require('../src/phrases')
const logger = require('winston')
const familyMembers = require('../model/familymembers')
const families = require('../model/families')
const jwt = require('jsonwebtoken')
const snakeKeys = require('snakecase-keys-object')

const errorMessages = {
  notFound: { errorCode: 1001, errorMessage: 'not found' },
  alreadyExists: { errorCode: 1002, errorMessage: 'already exists' },
  generalError: { errorCode: 1000, errorMessage: 'error' },
  invalidPhoneNumber: { errorCode: 1003, errorMessage: 'invalid phone number' },
  invalidVerificationNumber: { errorCode: 1004, errorMessage: 'invalid verification number' },
  tokenRequired: { errorCode: 1005, errorMessage: 'token required' },
  tokenVerificationFailure: { errorCode: 1006, errorMessage: 'token verification failed' },
  expiredVerificationNumber: { errorCode: 1007, errorMessage: 'expired verification number' },
  memberRetrivalFailure: { errorCode: 1008, errorMessage: 'could not retrieve member' },
  memberUpdateFailure: { errorCode: 1009, errorMessage: 'could not update member' },
  memberCreateFailure: { errorCode: 1009, errorMessage: 'could not create member' },
  familyUpdateFailure: { errorCode: 1010, errorMessage: 'could not update family' },
}

const issuer = 'https://packhack.us'

const getLists = (request, response) => {
  lists.findAllPromise({familyId: 2})
    .then(result => {
      const listNames = result.lists.map(list => {
        return {name: list.listKey}
      })
      response.json(listNames)
    }, result => {
      response.status(404).send(errorMessages.generalError)
    }
  )
}

const addList = (request, response) => {
  let list = request.body.name
  lists.saveNewPromise({list, familyId: 2})
    .then(result => {
      response.json({name: list})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.duplicateList) {
        response.status(409).send(errorMessages.alreadyExists)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    }
  )
}

const deleteList = (request, response) => {
  let list = request.params.list
  lists.deletePromise({list, familyId: 2})
    .then(result => {
      response.json({name: list})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.listNotFound) {
        response.status(404).send(errorMessages.notFound)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    }
  )
}

const getListItems = (request, response) => {
  listItems.findPromise({list: request.params.list, familyId: 2})
    .then(result => {
      const listItemNames = result.listItems.map(listItem => {
        return {name: listItem.listItemName}
      })
      response.json(listItemNames)
    }, result => {
      response.status(404).send(errorMessages.generalError)
    }
  )
}

const addListItem = (request, response) => {
  let listItemName = request.body.name
  // TODO: deal with duplicates
  listItems.saveNewPromise({list: request.params.list, familyId: 2, listItemName: listItemName})
    .then(result => {
      response.json({name: listItemName})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.duplicateListItem) {
        response.status(409).send(errorMessages.alreadyExists)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    }
  )
}

// Can handle delete and clear (delete all)
const deleteListItem = (request, response) => {
  let listItemName = request.params.item
  listItems.deletePromise({list: request.params.list, familyId: 2}, listItemName)
    .then(result => {
      response.json({name: listItemName})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.listItemNotFound) {
        response.status(404).send(errorMessages.notFound)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    })
}

// This will first check if phone already already exists
// If so, will just update the verification Number.
// If not, will create new member.
const authenticatePhone = (request, response) => {
  logger.log('info', '----authenticatePhone start', request.body)
  const phoneNumber = request.body.phone
  if (!phoneNumber || smsProcessor.validatePhoneNumber(phoneNumber) === false) {
    response.status(404).send(errorMessages.invalidPhoneNumber)
    return
  }
  const newVerificationNumber = Math.floor(Math.random() * 90000) + 10000
  const text = newVerificationNumber + phrases.verification

  // Verification expiry - now + 5 minutes
  let expiryDate = new Date()
  expiryDate.setMinutes(expiryDate.getMinutes() + 5)
  expiryDate = new Date(expiryDate)

  let data = {
    fromPhoneNumber: phoneNumber
  }

  smsProcessor.sendSmsPromise({}, phoneNumber, text)
    .then(result => {
      logger.log('info', '----authenticatePhone SMS success')
      return data
    })
    .then(familyMembers.retrievePersonFromPhoneNumberPromise)
    .catch(data => {
      if (data.errorMessage === modelConstants.errorTypes.personNotFound) {
        return data
      } else {
        throw data
      }
    })
    .then(data => {
      data.verificationNumber = newVerificationNumber
      data.verificationNumberExpiry = expiryDate
      if (data.errorMessage === modelConstants.errorTypes.personNotFound) {
        logger.log('info', '----authenticatePhone save new family member')
        return familyMembers.saveNewFamilyMemberPromise(data)
      } else {
        logger.log('info', '----authenticatePhone update')
        return familyMembers.updateFamilyMemberVerificationNumberPromise(data)
      }
    })
    .then(data => {
      logger.log('info', '----authenticatePhone update success')
      response.json({'phone': phoneNumber})
    })
    .catch(data => {
      logger.log('info', '----authenticatePhone Failure', data)
      response.status(404).send(errorMessages.invalidPhoneNumber)
    })
}

// verify phone
const verifyPhone = (request, response) => {
  const verificationNumber = request.query.verification_number
  logger.log('info', '----verification requested-X ' + request.query.verification_number)
  const phoneNumber = request.query.phone
  let data = {
    fromPhoneNumber: phoneNumber
  }
  logger.log('info', '----verification requested', request.query)
  if (!verificationNumber || verificationNumber.length !== 5) {
    response.status(404).send(errorMessages.invalidVerificationNumber)
    return
  } else if (!phoneNumber || smsProcessor.validatePhoneNumber(phoneNumber) === false) {
    response.status(404).send(errorMessages.invalidPhoneNumber)
    return
  }
  familyMembers.retrievePersonFromPhoneNumberPromise(data)
    .catch(data => {
      data.errorMessage = errorMessages.invalidPhoneNumber
      throw data
    })
    .then(data => {
      if (data.verificationNumber !== parseInt(verificationNumber) && verificationNumber !== '32272') {
        logger.log('info', '----verification no match', data.verificationNumber)
        data.errorMessage = errorMessages.invalidVerificationNumber
        throw data
      }
      if (new Date(data.verificationNumberExpiry) < new Date()) {
        logger.log('info', '----verification expired', data.verificationNumberExpiry)
        data.errorMessage = errorMessages.expiredVerificationNumber
        throw data
      }
      const payload = {phone: phoneNumber}
      const options = {expiresIn: '1y', issuer: issuer}
      const secret = process.env.JWT_SECRET
      const token = jwt.sign(payload, secret, options)
      logger.log('debug', '----verification token', token)
      response.json({'token': token})
    })
    .catch(data => {
      response.status(404).send(data.errorMessage)
    })
}

const validateToken = (request, response, next) => {
  const authorizationHeaader = request.headers.authorization
  let result
  if (authorizationHeaader) {
    const token = request.headers.authorization
    const options = {
      expiresIn: '1y',
      issuer: issuer
    }
    try {
      result = jwt.verify(token, process.env.JWT_SECRET, options)
      request.decoded = result
      // We call next to pass execution to the subsequent middleware
      next()
    } catch (err) {
      logger.log('info', '----verification token invalid', err)
      response.status(401).send(errorMessages.tokenVerificationFailure)
    }
  } else {
    logger.log('info', '----verification token required')
    response.status(401).send(errorMessages.tokenRequired)
  }
}

const getFamilyMemberMe = (request, response) => {
  const phoneNumber = request.decoded.phone
  let data = {
    fromPhoneNumber: phoneNumber
  }
  familyMembers.retrieveForExternalPersonFromPhoneNumberPromise(data)
    .then(data => {
      // If this an orphan, it's not an error
      // If it has a family, retrieve family description.
      if (data.familyId == null) {
        response.json(snakeKeys(data))
      } else {
        return families.retrieveFamilyPromise(data)
          .then(data => {
            response.json(snakeKeys(data))
          })
          .catch(data => {
            logger.log('info', '----getFamilyMemberMe retrieveFamilyPromise Failure', data)
            response.status(404).send(errorMessages.memberRetrivalFailure)
          })
      }
    })
    .catch(data => {
      logger.log('info', '----getFamilyMemberMe Failure', data)
      response.status(404).send(errorMessages.memberRetrivalFailure)
    })
}

const patchFamilyMemberMe = (request, response) => {
  let updateData = {
    name: request.body.name.toLowerCase(),
    description: request.body.name + ' ' + request.body.familyName,
    familyId: request.body.familyId,
    timeZone: request.body.timeZone
  }
  let keyData = {
    fromPhoneNumber: request.decoded.phone
  }
  logger.log('info', '----patchFamilyMemberMe updateData', updateData)
  logger.log('info', '----patchFamilyMemberMe keyData', keyData)
  familyMembers.retrieveForExternalPersonFromPhoneNumberPromise(keyData)
    .then(data => {
      logger.log('info', '----patchFamilyMemberMe userId', data.userId)
      return familyMembers.updateFamilyMemberPromise(data.userId, updateData)
        .then(result => {
          let responseData = updateData
          responseData.phoneNumber = keyData.fromPhoneNumber
          responseData.userId = data.userId
          response.json(snakeKeys(responseData))
        })
        .catch(data => {
          logger.log('info', '----patchFamilyMemberMe patch Failure', data)
          response.status(404).send(errorMessages.memberUpdateFailure)
        })
    })
    .catch(data => {
      logger.log('info', '----patchFamilyMemberMe get Failure', data)
      response.status(404).send(errorMessages.memberRetrivalFailure)
    })
}

const postFamilyMember = (request, response) => {
  //TODO: NEED TO CHECK DECODED PHONE NUMBER IT MATCHES FAMILY ID
  let keyData = {
    fromPhoneNumber: request.decoded.phone
  }
  logger.log('info', '----postFamilyMember keyData', keyData)
  familyMembers.retrieveForExternalPersonFromPhoneNumberPromise(keyData)
  .then(user => {
    let familyKey = {
      id: user.familyId
    }
    logger.log('info', '----postFamilyMember familyID', familyKey)
    return families.retrieveFamilyPromise(familyKey)
    .then(family => {
      logger.log('info', '----postFamilyMember family', family)
      let data = {
        familyId: family.familyId,
        name: request.body.name.toLowerCase(),
        description: request.body.name + ' ' + family.familyName,
        phoneNumber: request.body.phoneNumber,
        timeZone: request.body.timeZone
      }
      return families.saveNewFamilyMemberPromise(data)
      .then(familyMember => {
        logger.log('info', '----postFamilyMember save success', familyMember)
        response.json(snakeKeys(family))
      })
      .catch(data => {
        logger.log('info', '----postFamilyMember Failure', data)
        response.status(404).send(errorMessages.memberCreateFailure)
      })
    })
    .catch(data => {
      logger.log('info', '----postFamilyMember retrieveFamilyPromise Failure', data)
      response.status(404).send(errorMessages.memberRetrivalFailure)
    })
  })
  .catch(data => {
    logger.log('info', '----postFamilyMember Failure', data)
    response.status(404).send(errorMessages.memberRetrivalFailure)
  })

}

const postFamily = (request, response) => {
  let data = {
    name: request.body.description.toLowerCase(),
    description: request.body.description,
    timeZone: request.body.timeZone
  }
  logger.log('info', '----postFamily data', data)
  families.saveNewFamilyPromise(data)
    .then(family => {
      logger.log('info', '----postFamily success', family)
      response.json(snakeKeys(family))
    })
    .catch(data => {
      logger.log('info', '----postFamily Failure', data)
      response.status(404).send(errorMessages.familyUpdateFailure)
    })
}

module.exports = {
  getLists,
  addList,
  deleteList,
  getListItems,
  addListItem,
  deleteListItem,
  authenticatePhone,
  verifyPhone,
  validateToken,
  getFamilyMemberMe,
  patchFamilyMemberMe,
  postFamilyMember,
  postFamily
}
<commit_msg>another typo<commit_after>// const logger = require('winston')
const lists = require('../model/lists')
const listItems = require('../model/listitems')
const modelConstants = require('../model/modelconstants')
const smsProcessor = require('../src/smsprocessor')
const phrases = require('../src/phrases')
const logger = require('winston')
const familyMembers = require('../model/familymembers')
const families = require('../model/families')
const jwt = require('jsonwebtoken')
const snakeKeys = require('snakecase-keys-object')

const errorMessages = {
  notFound: { errorCode: 1001, errorMessage: 'not found' },
  alreadyExists: { errorCode: 1002, errorMessage: 'already exists' },
  generalError: { errorCode: 1000, errorMessage: 'error' },
  invalidPhoneNumber: { errorCode: 1003, errorMessage: 'invalid phone number' },
  invalidVerificationNumber: { errorCode: 1004, errorMessage: 'invalid verification number' },
  tokenRequired: { errorCode: 1005, errorMessage: 'token required' },
  tokenVerificationFailure: { errorCode: 1006, errorMessage: 'token verification failed' },
  expiredVerificationNumber: { errorCode: 1007, errorMessage: 'expired verification number' },
  memberRetrivalFailure: { errorCode: 1008, errorMessage: 'could not retrieve member' },
  memberUpdateFailure: { errorCode: 1009, errorMessage: 'could not update member' },
  memberCreateFailure: { errorCode: 1009, errorMessage: 'could not create member' },
  familyUpdateFailure: { errorCode: 1010, errorMessage: 'could not update family' },
}

const issuer = 'https://packhack.us'

const getLists = (request, response) => {
  lists.findAllPromise({familyId: 2})
    .then(result => {
      const listNames = result.lists.map(list => {
        return {name: list.listKey}
      })
      response.json(listNames)
    }, result => {
      response.status(404).send(errorMessages.generalError)
    }
  )
}

const addList = (request, response) => {
  let list = request.body.name
  lists.saveNewPromise({list, familyId: 2})
    .then(result => {
      response.json({name: list})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.duplicateList) {
        response.status(409).send(errorMessages.alreadyExists)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    }
  )
}

const deleteList = (request, response) => {
  let list = request.params.list
  lists.deletePromise({list, familyId: 2})
    .then(result => {
      response.json({name: list})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.listNotFound) {
        response.status(404).send(errorMessages.notFound)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    }
  )
}

const getListItems = (request, response) => {
  listItems.findPromise({list: request.params.list, familyId: 2})
    .then(result => {
      const listItemNames = result.listItems.map(listItem => {
        return {name: listItem.listItemName}
      })
      response.json(listItemNames)
    }, result => {
      response.status(404).send(errorMessages.generalError)
    }
  )
}

const addListItem = (request, response) => {
  let listItemName = request.body.name
  // TODO: deal with duplicates
  listItems.saveNewPromise({list: request.params.list, familyId: 2, listItemName: listItemName})
    .then(result => {
      response.json({name: listItemName})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.duplicateListItem) {
        response.status(409).send(errorMessages.alreadyExists)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    }
  )
}

// Can handle delete and clear (delete all)
const deleteListItem = (request, response) => {
  let listItemName = request.params.item
  listItems.deletePromise({list: request.params.list, familyId: 2}, listItemName)
    .then(result => {
      response.json({name: listItemName})
    }, result => {
      if (result.errorMessage === modelConstants.errorTypes.listItemNotFound) {
        response.status(404).send(errorMessages.notFound)
      } else {
        response.status(404).send(errorMessages.generalError)
      }
    })
}

// This will first check if phone already already exists
// If so, will just update the verification Number.
// If not, will create new member.
const authenticatePhone = (request, response) => {
  logger.log('info', '----authenticatePhone start', request.body)
  const phoneNumber = request.body.phone
  if (!phoneNumber || smsProcessor.validatePhoneNumber(phoneNumber) === false) {
    response.status(404).send(errorMessages.invalidPhoneNumber)
    return
  }
  const newVerificationNumber = Math.floor(Math.random() * 90000) + 10000
  const text = newVerificationNumber + phrases.verification

  // Verification expiry - now + 5 minutes
  let expiryDate = new Date()
  expiryDate.setMinutes(expiryDate.getMinutes() + 5)
  expiryDate = new Date(expiryDate)

  let data = {
    fromPhoneNumber: phoneNumber
  }

  smsProcessor.sendSmsPromise({}, phoneNumber, text)
    .then(result => {
      logger.log('info', '----authenticatePhone SMS success')
      return data
    })
    .then(familyMembers.retrievePersonFromPhoneNumberPromise)
    .catch(data => {
      if (data.errorMessage === modelConstants.errorTypes.personNotFound) {
        return data
      } else {
        throw data
      }
    })
    .then(data => {
      data.verificationNumber = newVerificationNumber
      data.verificationNumberExpiry = expiryDate
      if (data.errorMessage === modelConstants.errorTypes.personNotFound) {
        logger.log('info', '----authenticatePhone save new family member')
        return familyMembers.saveNewFamilyMemberPromise(data)
      } else {
        logger.log('info', '----authenticatePhone update')
        return familyMembers.updateFamilyMemberVerificationNumberPromise(data)
      }
    })
    .then(data => {
      logger.log('info', '----authenticatePhone update success')
      response.json({'phone': phoneNumber})
    })
    .catch(data => {
      logger.log('info', '----authenticatePhone Failure', data)
      response.status(404).send(errorMessages.invalidPhoneNumber)
    })
}

// verify phone
const verifyPhone = (request, response) => {
  const verificationNumber = request.query.verification_number
  logger.log('info', '----verification requested-X ' + request.query.verification_number)
  const phoneNumber = request.query.phone
  let data = {
    fromPhoneNumber: phoneNumber
  }
  logger.log('info', '----verification requested', request.query)
  if (!verificationNumber || verificationNumber.length !== 5) {
    response.status(404).send(errorMessages.invalidVerificationNumber)
    return
  } else if (!phoneNumber || smsProcessor.validatePhoneNumber(phoneNumber) === false) {
    response.status(404).send(errorMessages.invalidPhoneNumber)
    return
  }
  familyMembers.retrievePersonFromPhoneNumberPromise(data)
    .catch(data => {
      data.errorMessage = errorMessages.invalidPhoneNumber
      throw data
    })
    .then(data => {
      if (data.verificationNumber !== parseInt(verificationNumber) && verificationNumber !== '32272') {
        logger.log('info', '----verification no match', data.verificationNumber)
        data.errorMessage = errorMessages.invalidVerificationNumber
        throw data
      }
      if (new Date(data.verificationNumberExpiry) < new Date()) {
        logger.log('info', '----verification expired', data.verificationNumberExpiry)
        data.errorMessage = errorMessages.expiredVerificationNumber
        throw data
      }
      const payload = {phone: phoneNumber}
      const options = {expiresIn: '1y', issuer: issuer}
      const secret = process.env.JWT_SECRET
      const token = jwt.sign(payload, secret, options)
      logger.log('debug', '----verification token', token)
      response.json({'token': token})
    })
    .catch(data => {
      response.status(404).send(data.errorMessage)
    })
}

const validateToken = (request, response, next) => {
  const authorizationHeaader = request.headers.authorization
  let result
  if (authorizationHeaader) {
    const token = request.headers.authorization
    const options = {
      expiresIn: '1y',
      issuer: issuer
    }
    try {
      result = jwt.verify(token, process.env.JWT_SECRET, options)
      request.decoded = result
      // We call next to pass execution to the subsequent middleware
      next()
    } catch (err) {
      logger.log('info', '----verification token invalid', err)
      response.status(401).send(errorMessages.tokenVerificationFailure)
    }
  } else {
    logger.log('info', '----verification token required')
    response.status(401).send(errorMessages.tokenRequired)
  }
}

const getFamilyMemberMe = (request, response) => {
  const phoneNumber = request.decoded.phone
  let data = {
    fromPhoneNumber: phoneNumber
  }
  familyMembers.retrieveForExternalPersonFromPhoneNumberPromise(data)
    .then(data => {
      // If this an orphan, it's not an error
      // If it has a family, retrieve family description.
      if (data.familyId == null) {
        response.json(snakeKeys(data))
      } else {
        return families.retrieveFamilyPromise(data)
          .then(data => {
            response.json(snakeKeys(data))
          })
          .catch(data => {
            logger.log('info', '----getFamilyMemberMe retrieveFamilyPromise Failure', data)
            response.status(404).send(errorMessages.memberRetrivalFailure)
          })
      }
    })
    .catch(data => {
      logger.log('info', '----getFamilyMemberMe Failure', data)
      response.status(404).send(errorMessages.memberRetrivalFailure)
    })
}

const patchFamilyMemberMe = (request, response) => {
  let updateData = {
    name: request.body.name.toLowerCase(),
    description: request.body.name + ' ' + request.body.familyName,
    familyId: request.body.familyId,
    timeZone: request.body.timeZone
  }
  let keyData = {
    fromPhoneNumber: request.decoded.phone
  }
  logger.log('info', '----patchFamilyMemberMe updateData', updateData)
  logger.log('info', '----patchFamilyMemberMe keyData', keyData)
  familyMembers.retrieveForExternalPersonFromPhoneNumberPromise(keyData)
    .then(data => {
      logger.log('info', '----patchFamilyMemberMe userId', data.userId)
      return familyMembers.updateFamilyMemberPromise(data.userId, updateData)
        .then(result => {
          let responseData = updateData
          responseData.phoneNumber = keyData.fromPhoneNumber
          responseData.userId = data.userId
          response.json(snakeKeys(responseData))
        })
        .catch(data => {
          logger.log('info', '----patchFamilyMemberMe patch Failure', data)
          response.status(404).send(errorMessages.memberUpdateFailure)
        })
    })
    .catch(data => {
      logger.log('info', '----patchFamilyMemberMe get Failure', data)
      response.status(404).send(errorMessages.memberRetrivalFailure)
    })
}

const postFamilyMember = (request, response) => {
  //TODO: NEED TO CHECK DECODED PHONE NUMBER IT MATCHES FAMILY ID
  let keyData = {
    fromPhoneNumber: request.decoded.phone
  }
  logger.log('info', '----postFamilyMember keyData', keyData)
  familyMembers.retrieveForExternalPersonFromPhoneNumberPromise(keyData)
  .then(user => {
    let familyKey = {
      familyId: user.familyId
    }
    logger.log('info', '----postFamilyMember familyID', familyKey)
    return families.retrieveFamilyPromise(familyKey)
    .then(family => {
      logger.log('info', '----postFamilyMember family', family)
      let data = {
        familyId: family.familyId,
        name: request.body.name.toLowerCase(),
        description: request.body.name + ' ' + family.familyName,
        phoneNumber: request.body.phoneNumber,
        timeZone: request.body.timeZone
      }
      return families.saveNewFamilyMemberPromise(data)
      .then(familyMember => {
        logger.log('info', '----postFamilyMember save success', familyMember)
        response.json(snakeKeys(family))
      })
      .catch(data => {
        logger.log('info', '----postFamilyMember Failure', data)
        response.status(404).send(errorMessages.memberCreateFailure)
      })
    })
    .catch(data => {
      logger.log('info', '----postFamilyMember retrieveFamilyPromise Failure', data)
      response.status(404).send(errorMessages.memberRetrivalFailure)
    })
  })
  .catch(data => {
    logger.log('info', '----postFamilyMember Failure', data)
    response.status(404).send(errorMessages.memberRetrivalFailure)
  })

}

const postFamily = (request, response) => {
  let data = {
    name: request.body.description.toLowerCase(),
    description: request.body.description,
    timeZone: request.body.timeZone
  }
  logger.log('info', '----postFamily data', data)
  families.saveNewFamilyPromise(data)
    .then(family => {
      logger.log('info', '----postFamily success', family)
      response.json(snakeKeys(family))
    })
    .catch(data => {
      logger.log('info', '----postFamily Failure', data)
      response.status(404).send(errorMessages.familyUpdateFailure)
    })
}

module.exports = {
  getLists,
  addList,
  deleteList,
  getListItems,
  addListItem,
  deleteListItem,
  authenticatePhone,
  verifyPhone,
  validateToken,
  getFamilyMemberMe,
  patchFamilyMemberMe,
  postFamilyMember,
  postFamily
}
<|endoftext|>


<commit_before>var test = require("tape")

var node-env = require("../index")

test("node-env is a function", function (assert) {
    assert.equal(typeof node-env, "function")
    assert.end()
})
<commit_msg>fix test since node-env returns a string, not a function<commit_after>var test = require("tape")

var nodeEnv = require("../index")

test("node-env is a string", function (assert) {
    assert.equal(typeof nodeEnv, "string")
    assert.end()
})
<|endoftext|>


<commit_before>(function() {
var boxes = window.Plotly.Boxes = {};

boxes.calc = function(gd,gdc) {
    // box plots make no sense if you don't have y
    if(!('y' in gdc) || gdc.visible===false) { return; }

    // outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
    var xa = Plotly.Axes.getFromId(gd,gdc.xaxis||'x'),
        ya = Plotly.Axes.getFromId(gd,gdc.yaxis||'y'),
        x,
        y = ya.makeCalcdata(gdc,'y');
    if('x' in gdc) { x = xa.makeCalcdata(gdc,'x'); }
    // if no x data, use x0, or name, or text - so if you want one box
    // per trace, set x0 to the x value or category for this trace
    // (or set x to a constant array matching y)
    else {
        var x0;
        if('x0' in gdc) { x0 = gdc.x0; }
        else if('name' in gdc && (
                xa.type=='category' ||
                ($.isNumeric(gdc.name) && ['linear','log'].indexOf(xa.type)!=-1) ||
                (Plotly.Lib.isDateTime(gdc.name) && xa.type=='date')
            )) { x0 = gdc.name; }
        else { x0 = gd.numboxes; }
        x0 = xa.d2c(x0);
        x = y.map(function(){ return x0; });
    }
    // find x values
    var dv = Plotly.Lib.distinctVals(x),
        xvals = dv.vals,
        dx = dv.minDiff/2,
        cd = xvals.map(function(v){ return {x:v}; }),
        pts = xvals.map(function(){ return []; }),
        bins = xvals.map(function(v){ return v-dx; }),
        l = xvals.length;
    bins.push(xvals[l-1]+dx);

    // y autorange based on all source points
    // x happens afterward when we know all the x values
    Plotly.Axes.expand(ya,y,{padded:true});

    // bin the points
    y.forEach(function(v,i){
        if(!$.isNumeric(v)){ return; }
        var n = Plotly.Lib.findBin(x[i],bins);
        if(n>=0 && n<l) { pts[n].push(v); }
    });
    // sort the bins and calculate the stats
    pts.forEach(function(v,i){
        v.sort(function(a,b){ return a-b; });
        var last = v.length-1, p = cd[i];
        p.y = v; // put all points into calcdata
        p.min = v[0];
        p.max = v[last];
        p.mean = Plotly.Lib.mean(v,last+1);
        p.sd = Plotly.Lib.stdev(v,last+1,p.mean);
        p.q1 = interp(v,(last/4)); // first quartile
        p.med = interp(v,(last/2)); // median
        p.q3 = interp(v,(0.75*last)); // third quartile
        // lower and upper fences - last point inside
        // 1.5 interquartile ranges from quartiles
        p.lf = Math.min(p.q1,v[Math.min(Plotly.Lib.findBin(2.5*p.q1-1.5*p.q3,v,true)+1,last)]);
        p.uf = Math.max(p.q3,v[Math.max(Plotly.Lib.findBin(2.5*p.q3-1.5*p.q1,v),0)]);
        // lower and upper outliers - 3 IQR out (don't clip to max/min,
        // this is only for discriminating suggested & far outliers)
        p.lo = 4*p.q1-3*p.q3;
        p.uo = 4*p.q3-3*p.q1;
    });
    cd[0].t = {boxnum: gd.numboxes, dx: dx};
    gd.numboxes++;
    return cd;
};

boxes.setPositions = function(gd,plotinfo) {
    var xa = plotinfo.x,
        ya = plotinfo.y,
        boxlist = [],
        minPad = 0,
        maxPad = 0;
    gd.calcdata.forEach(function(cd,i) {
        var t=cd[0].t;
        if(t.visible!==false && t.type=='box' &&
          (t.xaxis||'x')==xa._id && (t.yaxis||'y')==ya._id) {
            boxlist.push(i);
            if(t.boxpts!==false) {
                // console.log(t.jitter,t.ptpos,gd.layout.boxgap,gd.numboxes,t.jitter-t.ptpos+gd.layout.boxgap-1);
                minPad = Math.max(minPad,t.jitter-t.ptpos-1);
                maxPad = Math.max(maxPad,t.jitter+t.ptpos-1);
            }
        }
    });

    // box plots - update dx based on multiple traces, and then use for x autorange
    var boxx = [];
    boxlist.forEach(function(i){ gd.calcdata[i].forEach(function(v){ boxx.push(v.x); }); });
    if(boxx.length) {
        var boxdv = Plotly.Lib.distinctVals(boxx),
            dx = boxdv.minDiff/2;

        // if there's no duplication of x points, disable 'group' mode by setting numboxes=1
        if(boxx.length==boxdv.vals.length) { gd.numboxes = 1; }

        // check for forced minimum dtick
        Plotly.Axes.minDtick(xa,boxdv.minDiff,boxdv.vals[0],true);

        // set the width of all boxes
        boxlist.forEach(function(i){ gd.calcdata[i][0].t.dx = dx; });

        // autoscale the x axis - including space for points if they're off the side
        // TODO: this will overdo it if the outermost boxes don't have their points as far out as the other boxes
        var padfactor = (1-gd.layout.boxgap)*(1-gd.layout.boxgroupgap)*dx/gd.numboxes;
        Plotly.Axes.expand(xa, boxdv.vals, {vpadminus:dx+minPad*padfactor, vpadplus:dx+maxPad*padfactor});
    }
};

boxes.plot = function(gd,plotinfo,cdbox) {
    var gl = gd.layout,
        xa = plotinfo.x,
        ya = plotinfo.y;
    var boxtraces = plotinfo.plot.selectAll('g.trace.boxes') // <-- select trace group
        .data(cdbox) // <-- bind calcdata to traces
      .enter().append('g') // <-- add a trace for each calcdata
        .attr('class','trace boxes');

    boxtraces.each(function(d){
        var t = d[0].t,
            group = (gl.boxmode=='group' && gd.numboxes>1), // like grouped bars
            // box half width
            bdx = t.dx*(1-gl.boxgap)*(1-gl.boxgroupgap)/(group ? gd.numboxes : 1),
            // box center offset
            bx = group ? 2*t.dx*(-0.5+(t.boxnum+0.5)/gd.numboxes)*(1-gl.boxgap) : 0,
            wdx = bdx*t.ww; // whisker width
        if(t.visible===false) { d3.select(this).remove(); return; }
        // save the box size and box position for use by hover
        t.bx = bx;
        t.bdx = bdx;
        // boxes and whiskers
        d3.select(this).selectAll('path.box')
            .data(Plotly.Lib.identity)
            .enter().append('path')
            .attr('class','box')
            .each(function(d){
                // draw the bars and whiskers
                var xc = xa.c2p(d.x+bx,true),
                    x0 = xa.c2p(d.x+bx-bdx,true),
                    x1 = xa.c2p(d.x+bx+bdx,true),
                    xw0 = xa.c2p(d.x+bx-wdx,true),
                    xw1 = xa.c2p(d.x+bx+wdx,true),
                    ym = ya.c2p(d.med,true),
                    yq1 = ya.c2p(d.q1,true),
                    yq3 = ya.c2p(d.q3,true),
                    ylf = ya.c2p(t.boxpts===false ? d.min : d.lf, true),
                    yuf = ya.c2p(t.boxpts===false ? d.max : d.uf, true);
                d3.select(this).attr('d',
                    'M'+x0+','+ym+'H'+x1+ // median line
                    'M'+x0+','+yq1+'H'+x1+'V'+yq3+'H'+x0+'Z'+ // box
                    'M'+xc+','+yq1+'V'+ylf+'M'+xc+','+yq3+'V'+yuf+ // whiskers
                    ((t.ww===0) ? '' : // whisker caps
                        'M'+xw0+','+ylf+'H'+xw1+'M'+xw0+','+yuf+'H'+xw1));
            });
        // draw points, if desired
        if(t.boxpts!==false) {
            d3.select(this).selectAll('g.points')
                // since box plot points get an extra level of nesting, each
                // box needs the trace styling info
                .data(function(d){ d.forEach(function(v){v.t=t;}); return d; })
                .enter().append('g')
                .attr('class','points')
              .selectAll('path')
                .data(function(d){
                    var pts = (t.boxpts=='all') ? d.y :
                        d.y.filter(function(v){ return (v<d.lf || v>d.uf); });
                    return pts.map(function(v){
                        var xo = (t.jitter ? t.jitter*(Math.random()-0.5)*2 : 0)+t.ptpos,
                            p = {x:d.x+xo*bdx+bx,y:v,t:t};
                        // tag suggested outliers
                        if(t.boxpts!='all' && v<d.uo && v>d.lo) { p.so=true; }
                        return p;
                    });
                })
                .enter().append('path')
                .call(Plotly.Drawing.translatePoints,xa,ya);
        }
        // draw mean (and stdev diamond) if desired
        if(t.mean) {
            d3.select(this).selectAll('path.mean')
                .data(Plotly.Lib.identity)
                .enter().append('path')
                .attr('class','mean')
                .style('fill','none')
                .each(function(d){
                    var xc = xa.c2p(d.x+bx,true),
                        x0 = xa.c2p(d.x+bx-bdx,true),
                        x1 = xa.c2p(d.x+bx+bdx,true),
                        ym = ya.c2p(d.mean,true),
                        ysl = ya.c2p(d.mean-d.sd,true),
                        ysh = ya.c2p(d.mean+d.sd,true);
                    d3.select(this).attr('d','M'+x0+','+ym+'H'+x1+
                        ((t.mean!='sd') ? '' :
                        'm0,0L'+xc+','+ysl+'L'+x0+','+ym+'L'+xc+','+ysh+'Z'));
                });
        }
    });
};

boxes.style = function(s) {
    s.style('opacity',function(d){ return d[0].t.op; })
        .each(function(d){
            var t = d[0].t;
            d3.select(this).selectAll('path.box')
                .style('stroke-width',t.lw+'px')
                .call(Plotly.Drawing.strokeColor,t.lc)
                .call(Plotly.Drawing.fillColor,t.fc);
            d3.select(this).selectAll('path.mean')
                .style({'stroke-width':t.lw, 'stroke-dasharray':(2*t.lw)+'px,'+(t.lw)+'px'})
                .call(Plotly.Drawing.strokeColor,t.lc);
        })
        .selectAll('g.points')
            .each(function(d){
                var t = d.t||d[0].t;
                d3.select(this).selectAll('path')
                    .call(Plotly.Drawing.pointStyle,t);
                d3.select(this).selectAll('text')
                    .call(Plotly.Drawing.textPointStyle,t);
            });
};

// interpolate an array given a (possibly non-integer) index n
// clip the ends to the extreme values in the array
function interp(arr,n) {
    if(n<0) { return arr[0]; }
    if(n>arr.length-1) { return arr[arr.length-1]; }
    var frac = n%1;
    return frac*arr[Math.ceil(n)]+(1-frac)*arr[Math.floor(n)];
}

}()); // end Boxes object definition<commit_msg>fix for empty boxes in box plots<commit_after>(function() {
var boxes = window.Plotly.Boxes = {};

boxes.calc = function(gd,gdc) {
    // box plots make no sense if you don't have y
    if(!('y' in gdc) || gdc.visible===false) { return; }

    // outlier definition based on http://www.physics.csbsju.edu/stats/box2.html
    var xa = Plotly.Axes.getFromId(gd,gdc.xaxis||'x'),
        ya = Plotly.Axes.getFromId(gd,gdc.yaxis||'y'),
        x,
        y = ya.makeCalcdata(gdc,'y');
    if('x' in gdc) { x = xa.makeCalcdata(gdc,'x'); }
    // if no x data, use x0, or name, or text - so if you want one box
    // per trace, set x0 to the x value or category for this trace
    // (or set x to a constant array matching y)
    else {
        var x0;
        if('x0' in gdc) { x0 = gdc.x0; }
        else if('name' in gdc && (
                xa.type=='category' ||
                ($.isNumeric(gdc.name) && ['linear','log'].indexOf(xa.type)!=-1) ||
                (Plotly.Lib.isDateTime(gdc.name) && xa.type=='date')
            )) { x0 = gdc.name; }
        else { x0 = gd.numboxes; }
        x0 = xa.d2c(x0);
        x = y.map(function(){ return x0; });
    }
    // find x values
    var dv = Plotly.Lib.distinctVals(x),
        xvals = dv.vals,
        dx = dv.minDiff/2,
        cd = xvals.map(function(v){ return {x:v}; }),
        pts = xvals.map(function(){ return []; }),
        bins = xvals.map(function(v){ return v-dx; }),
        l = xvals.length;
    bins.push(xvals[l-1]+dx);

    // y autorange based on all source points
    // x happens afterward when we know all the x values
    Plotly.Axes.expand(ya,y,{padded:true});

    // bin the points
    y.forEach(function(v,i){
        if(!$.isNumeric(v)){ return; }
        var n = Plotly.Lib.findBin(x[i],bins);
        if(n>=0 && n<l) { pts[n].push(v); }
    });
    // sort the bins and calculate the stats
    pts.forEach(function(v,i){
        v.sort(function(a,b){ return a-b; });
        var last = v.length-1, p = cd[i];
        p.y = v; // put all points into calcdata
        p.min = v[0];
        p.max = v[last];
        p.mean = Plotly.Lib.mean(v,last+1);
        p.sd = Plotly.Lib.stdev(v,last+1,p.mean);
        p.q1 = interp(v,(last/4)); // first quartile
        p.med = interp(v,(last/2)); // median
        p.q3 = interp(v,(0.75*last)); // third quartile
        // lower and upper fences - last point inside
        // 1.5 interquartile ranges from quartiles
        p.lf = Math.min(p.q1,v[Math.min(Plotly.Lib.findBin(2.5*p.q1-1.5*p.q3,v,true)+1,last)]);
        p.uf = Math.max(p.q3,v[Math.max(Plotly.Lib.findBin(2.5*p.q3-1.5*p.q1,v),0)]);
        // lower and upper outliers - 3 IQR out (don't clip to max/min,
        // this is only for discriminating suggested & far outliers)
        p.lo = 4*p.q1-3*p.q3;
        p.uo = 4*p.q3-3*p.q1;
    });

    // remove empty bins
    cd = cd.filter(function(p){ return p.y && p.y.length; });
    if(!cd.length) { return [{t:{emptybox:true}}]; }

    cd[0].t = {boxnum: gd.numboxes, dx: dx};
    gd.numboxes++;
    return cd;
};

boxes.setPositions = function(gd,plotinfo) {
    var xa = plotinfo.x,
        ya = plotinfo.y,
        boxlist = [],
        minPad = 0,
        maxPad = 0;
    gd.calcdata.forEach(function(cd,i) {
        var t=cd[0].t;
        if(t.visible!==false && !t.emptybox && t.type=='box' &&
          (t.xaxis||'x')==xa._id && (t.yaxis||'y')==ya._id) {
            boxlist.push(i);
            if(t.boxpts!==false) {
                // console.log(t.jitter,t.ptpos,gd.layout.boxgap,gd.numboxes,t.jitter-t.ptpos+gd.layout.boxgap-1);
                minPad = Math.max(minPad,t.jitter-t.ptpos-1);
                maxPad = Math.max(maxPad,t.jitter+t.ptpos-1);
            }
        }
    });

    // box plots - update dx based on multiple traces, and then use for x autorange
    var boxx = [];
    boxlist.forEach(function(i){ gd.calcdata[i].forEach(function(v){ boxx.push(v.x); }); });
    if(boxx.length) {
        var boxdv = Plotly.Lib.distinctVals(boxx),
            dx = boxdv.minDiff/2;

        // if there's no duplication of x points, disable 'group' mode by setting numboxes=1
        if(boxx.length==boxdv.vals.length) { gd.numboxes = 1; }

        // check for forced minimum dtick
        Plotly.Axes.minDtick(xa,boxdv.minDiff,boxdv.vals[0],true);

        // set the width of all boxes
        boxlist.forEach(function(i){ gd.calcdata[i][0].t.dx = dx; });

        // autoscale the x axis - including space for points if they're off the side
        // TODO: this will overdo it if the outermost boxes don't have their points as far out as the other boxes
        var padfactor = (1-gd.layout.boxgap)*(1-gd.layout.boxgroupgap)*dx/gd.numboxes;
        Plotly.Axes.expand(xa, boxdv.vals, {vpadminus:dx+minPad*padfactor, vpadplus:dx+maxPad*padfactor});
    }
};

boxes.plot = function(gd,plotinfo,cdbox) {
    var gl = gd.layout,
        xa = plotinfo.x,
        ya = plotinfo.y;
    var boxtraces = plotinfo.plot.selectAll('g.trace.boxes') // <-- select trace group
        .data(cdbox) // <-- bind calcdata to traces
      .enter().append('g') // <-- add a trace for each calcdata
        .attr('class','trace boxes');

    boxtraces.each(function(d){
        var t = d[0].t,
            group = (gl.boxmode=='group' && gd.numboxes>1), // like grouped bars
            // box half width
            bdx = t.dx*(1-gl.boxgap)*(1-gl.boxgroupgap)/(group ? gd.numboxes : 1),
            // box center offset
            bx = group ? 2*t.dx*(-0.5+(t.boxnum+0.5)/gd.numboxes)*(1-gl.boxgap) : 0,
            wdx = bdx*t.ww; // whisker width
        if(t.visible===false || t.emptybox) { d3.select(this).remove(); return; }
        // save the box size and box position for use by hover
        t.bx = bx;
        t.bdx = bdx;
        // boxes and whiskers
        d3.select(this).selectAll('path.box')
            .data(Plotly.Lib.identity)
            .enter().append('path')
            .attr('class','box')
            .each(function(d){
                // draw the bars and whiskers
                var xc = xa.c2p(d.x+bx,true),
                    x0 = xa.c2p(d.x+bx-bdx,true),
                    x1 = xa.c2p(d.x+bx+bdx,true),
                    xw0 = xa.c2p(d.x+bx-wdx,true),
                    xw1 = xa.c2p(d.x+bx+wdx,true),
                    ym = ya.c2p(d.med,true),
                    yq1 = ya.c2p(d.q1,true),
                    yq3 = ya.c2p(d.q3,true),
                    ylf = ya.c2p(t.boxpts===false ? d.min : d.lf, true),
                    yuf = ya.c2p(t.boxpts===false ? d.max : d.uf, true);
                d3.select(this).attr('d',
                    'M'+x0+','+ym+'H'+x1+ // median line
                    'M'+x0+','+yq1+'H'+x1+'V'+yq3+'H'+x0+'Z'+ // box
                    'M'+xc+','+yq1+'V'+ylf+'M'+xc+','+yq3+'V'+yuf+ // whiskers
                    ((t.ww===0) ? '' : // whisker caps
                        'M'+xw0+','+ylf+'H'+xw1+'M'+xw0+','+yuf+'H'+xw1));
            });
        // draw points, if desired
        if(t.boxpts!==false) {
            d3.select(this).selectAll('g.points')
                // since box plot points get an extra level of nesting, each
                // box needs the trace styling info
                .data(function(d){ d.forEach(function(v){v.t=t;}); return d; })
                .enter().append('g')
                .attr('class','points')
              .selectAll('path')
                .data(function(d){
                    var pts = (t.boxpts=='all') ? d.y :
                        d.y.filter(function(v){ return (v<d.lf || v>d.uf); });
                    return pts.map(function(v){
                        var xo = (t.jitter ? t.jitter*(Math.random()-0.5)*2 : 0)+t.ptpos,
                            p = {x:d.x+xo*bdx+bx,y:v,t:t};
                        // tag suggested outliers
                        if(t.boxpts!='all' && v<d.uo && v>d.lo) { p.so=true; }
                        return p;
                    });
                })
                .enter().append('path')
                .call(Plotly.Drawing.translatePoints,xa,ya);
        }
        // draw mean (and stdev diamond) if desired
        if(t.mean) {
            d3.select(this).selectAll('path.mean')
                .data(Plotly.Lib.identity)
                .enter().append('path')
                .attr('class','mean')
                .style('fill','none')
                .each(function(d){
                    var xc = xa.c2p(d.x+bx,true),
                        x0 = xa.c2p(d.x+bx-bdx,true),
                        x1 = xa.c2p(d.x+bx+bdx,true),
                        ym = ya.c2p(d.mean,true),
                        ysl = ya.c2p(d.mean-d.sd,true),
                        ysh = ya.c2p(d.mean+d.sd,true);
                    d3.select(this).attr('d','M'+x0+','+ym+'H'+x1+
                        ((t.mean!='sd') ? '' :
                        'm0,0L'+xc+','+ysl+'L'+x0+','+ym+'L'+xc+','+ysh+'Z'));
                });
        }
    });
};

boxes.style = function(s) {
    s.style('opacity',function(d){ return d[0].t.op; })
        .each(function(d){
            var t = d[0].t;
            d3.select(this).selectAll('path.box')
                .style('stroke-width',t.lw+'px')
                .call(Plotly.Drawing.strokeColor,t.lc)
                .call(Plotly.Drawing.fillColor,t.fc);
            d3.select(this).selectAll('path.mean')
                .style({'stroke-width':t.lw, 'stroke-dasharray':(2*t.lw)+'px,'+(t.lw)+'px'})
                .call(Plotly.Drawing.strokeColor,t.lc);
        })
        .selectAll('g.points')
            .each(function(d){
                var t = d.t||d[0].t;
                d3.select(this).selectAll('path')
                    .call(Plotly.Drawing.pointStyle,t);
                d3.select(this).selectAll('text')
                    .call(Plotly.Drawing.textPointStyle,t);
            });
};

// interpolate an array given a (possibly non-integer) index n
// clip the ends to the extreme values in the array
function interp(arr,n) {
    if(n<0) { return arr[0]; }
    if(n>arr.length-1) { return arr[arr.length-1]; }
    var frac = n%1;
    return frac*arr[Math.ceil(n)]+(1-frac)*arr[Math.floor(n)];
}

}()); // end Boxes object definition<|endoftext|>


<commit_before>'use strict';

const _ = require('lodash');

function Scroller(consoleElement) {
  this.lines = [];
  this.lineOffset = 0;
  this.visibleCount = 50;
  this.startPosition = 0;
  this.endPosition = 0;
  this.animateRequest = null;
  this.sticky = true;
  this.jumpToBottom = true;
  this.dirty = false;
  this.console = consoleElement;
  this.expandDistance = 200;

  //pre-bind functions
  this.refresh = this._renderVisible.bind(this);
  this.scroll = this._onScroll.bind(this);
  this.expandTop = this._expandTop.bind(this);
  this.expandBottom = this._expandBottom.bind(this);
}

Scroller.prototype._generateContent = function(){
  return _(this.lines)
    .slice(this.startPosition - this.lineOffset, this.endPosition - this.lineOffset)
    .map(function(line){
      if(line.length === 0){
        // insert a blank space to prevent pre omitting a trailing newline,
        // even though pre/pre-nowrap/pre-line are specified.
        return '\u2009';
      }
      return line;
    })
    .join('\n');
};

Scroller.prototype.setLines = function(newLines, offset) {
  this.lines = newLines;
  this.lineOffset = offset;
  if(this.sticky){
    this.endPosition = this.lineCount();
    this.startPosition = Math.max(this.lineOffset, this.endPosition - this.visibleCount);
    if(this.endPosition <= this.visibleCount){
      // follow text during initial 50 lines
      this.jumpToBottom = true;
    }
  }else if(newLines.length === 1 && newLines[0].length === 0){
    // ^^ `lines` is reset to an array with one empty line. ugh.

    // handle the reset case when lines is replaced with an empty array
    // we don't have a direct event that can call this
    this.reset();
  }else if(this.lineOffset > this.startPosition){
    // when buffer trims and we are now below the trimmed area, move up by difference
    const lineDiff = this.lineOffset - this.startPosition;
    this.startPosition += lineDiff;
    this.endPosition += lineDiff;
  }
  this.dirty = true;
};

Scroller.prototype.lineCount = function(){
  return this.lines.length + this.lineOffset;
};

Scroller.prototype.reset = function(){
  this.endPosition = Math.max(0, this.lineCount());
  this.startPosition = Math.max(0, this.endPosition - this.visibleCount);
  this.lineOffset = 0;
  this.jumpToBottom = true;
  this.sticky = true;
  this.dirty = true;
};

Scroller.prototype.requestRefresh = function(){
  if(this.console){
    this.animateRequest = requestAnimationFrame(this.refresh);
  }
};

Scroller.prototype._renderVisible = function(){
  this.animateRequest = null;
  if(this.dirty && this.console){
    if(this.sticky){
      this.endPosition = this.lineCount();
      this.startPosition = Math.max(this.lineOffset, this.endPosition - this.visibleCount);
    }
    this.console.innerHTML = this._generateContent();
    if(this.jumpToBottom){
      this.console.scrollTop = 4000;
      this.jumpToBottom = false;
    }
    this.dirty = false;
  }
};

Scroller.prototype._expandTop = function(){
  this.startPosition = Math.max(this.lineOffset, this.startPosition - this.visibleCount);
  if(this.console){
    this.sticky = false;
    const scrollHeight = this.console.scrollHeight;
    const scrollTop = this.console.scrollTop;

    // do an inline scroll to avoid potential scroll interleaving
    this.console.innerHTML = this._generateContent();
    const newScrollHeight = this.console.scrollHeight;
    this.console.scrollTop = scrollTop + newScrollHeight - scrollHeight;

    const oldEndPos = this.endPosition;
    this.endPosition = Math.min(this.endPosition, this.startPosition + (this.visibleCount * 2));

    this.dirty = oldEndPos !== this.endPosition;
    if(this.dirty && !this.animateRequest){
      this.animateRequest = requestAnimationFrame(this.refresh);
    }
  }
};

Scroller.prototype._expandBottom = function(){
  this.endPosition = Math.min(this.lineCount(), this.endPosition + this.visibleCount);
  if(this.console){
    // add the new content to the bottom, then get scroll position to remove content
    this.console.innerHTML = this._generateContent();
    const scrollHeight = this.console.scrollHeight;
    const scrollTop = this.console.scrollTop;

    // update start position and render
    this.startPosition = Math.max(this.lineOffset, Math.min(this.lineCount() - (this.visibleCount * 2), this.endPosition - (this.visibleCount * 2)));
    this.console.innerHTML = this._generateContent();

    // use difference to scroll offset
    const newScrollHeight = this.console.scrollHeight;
    this.console.scrollTop = scrollTop - (scrollHeight - newScrollHeight);

    this.dirty = false;
  }
};

Scroller.prototype._onScroll = function(){
  if(this.jumpToBottom){
    // do nothing, prepare to jump
    return;
  }
  const height = this.console.offsetHeight;
  const scrollHeight = this.console.scrollHeight;
  const scrollTop = this.console.scrollTop;
  const nearTop = scrollTop < this.expandDistance;
  const nearBottom = scrollTop + height > scrollHeight - this.expandDistance;
  const nearSticky = scrollTop + height > scrollHeight - 10;

  if(this.sticky){
    if(!nearSticky){
      this.sticky = false;
    }
  }else{
    if(nearTop && this.startPosition > this.lineOffset){
      this.expandTop();
    }else if(nearBottom){
      if(this.endPosition < this.lineCount() - 2){
        this.expandBottom();
      }else if(nearSticky){
        this.jumpToBottom = true;
        this.sticky = true;
        this.dirty = true;
      }
    }
  }

  if(this.dirty && !this.animateRequest){
    this.animateRequest = requestAnimationFrame(this.refresh);
  }
};

module.exports = Scroller;
<commit_msg>Tweak initial jump-to-bottom values<commit_after>'use strict';

const _ = require('lodash');

function Scroller(consoleElement) {
  this.lines = [];
  this.lineOffset = 0;
  this.visibleCount = 50;
  this.startPosition = 0;
  this.endPosition = 0;
  this.animateRequest = null;
  this.sticky = true;
  this.jumpToBottom = true;
  this.dirty = false;
  this.console = consoleElement;
  this.expandDistance = 200;

  //pre-bind functions
  this.refresh = this._renderVisible.bind(this);
  this.scroll = this._onScroll.bind(this);
  this.expandTop = this._expandTop.bind(this);
  this.expandBottom = this._expandBottom.bind(this);
}

Scroller.prototype._generateContent = function(){
  return _(this.lines)
    .slice(this.startPosition - this.lineOffset, this.endPosition - this.lineOffset)
    .map(function(line){
      if(line.length === 0){
        // insert a blank space to prevent pre omitting a trailing newline,
        // even though pre/pre-nowrap/pre-line are specified.
        return '\u2009';
      }
      return line;
    })
    .join('\n');
};

Scroller.prototype.setLines = function(newLines, offset) {
  this.lines = newLines;
  this.lineOffset = offset;
  if(this.sticky){
    this.endPosition = this.lineCount();
    this.startPosition = Math.max(this.lineOffset, this.endPosition - this.visibleCount);
    if(this.endPosition <= this.visibleCount * 2){
      // follow text during initial lines (console can show up to twice the visibleCount when expanding)
      this.jumpToBottom = true;
    }
  }else if(newLines.length === 1 && newLines[0].length === 0){
    // ^^ `lines` is reset to an array with one empty line. ugh.

    // handle the reset case when lines is replaced with an empty array
    // we don't have a direct event that can call this
    this.reset();
  }else if(this.lineOffset > this.startPosition){
    // when buffer trims and we are now below the trimmed area, move up by difference
    const lineDiff = this.lineOffset - this.startPosition;
    this.startPosition += lineDiff;
    this.endPosition += lineDiff;
  }
  this.dirty = true;
};

Scroller.prototype.lineCount = function(){
  return this.lines.length + this.lineOffset;
};

Scroller.prototype.reset = function(){
  this.endPosition = Math.max(0, this.lineCount());
  this.startPosition = Math.max(0, this.endPosition - this.visibleCount);
  this.lineOffset = 0;
  this.jumpToBottom = true;
  this.sticky = true;
  this.dirty = true;
};

Scroller.prototype.requestRefresh = function(){
  if(this.console){
    this.animateRequest = requestAnimationFrame(this.refresh);
  }
};

Scroller.prototype._renderVisible = function(){
  this.animateRequest = null;
  if(this.dirty && this.console){
    if(this.sticky){
      this.endPosition = this.lineCount();
      this.startPosition = Math.max(this.lineOffset, this.endPosition - this.visibleCount);
    }
    this.console.innerHTML = this._generateContent();
    if(this.jumpToBottom){
      this.console.scrollTop = 4000;
      this.jumpToBottom = false;
    }
    this.dirty = false;
  }
};

Scroller.prototype._expandTop = function(){
  this.startPosition = Math.max(this.lineOffset, this.startPosition - this.visibleCount);
  if(this.console){
    this.sticky = false;
    const scrollHeight = this.console.scrollHeight;
    const scrollTop = this.console.scrollTop;

    // do an inline scroll to avoid potential scroll interleaving
    this.console.innerHTML = this._generateContent();
    const newScrollHeight = this.console.scrollHeight;
    this.console.scrollTop = scrollTop + newScrollHeight - scrollHeight;

    const oldEndPos = this.endPosition;
    this.endPosition = Math.min(this.endPosition, this.startPosition + (this.visibleCount * 2));

    this.dirty = oldEndPos !== this.endPosition;
    if(this.dirty && !this.animateRequest){
      this.animateRequest = requestAnimationFrame(this.refresh);
    }
  }
};

Scroller.prototype._expandBottom = function(){
  this.endPosition = Math.min(this.lineCount(), this.endPosition + this.visibleCount);
  if(this.console){
    // add the new content to the bottom, then get scroll position to remove content
    this.console.innerHTML = this._generateContent();
    const scrollHeight = this.console.scrollHeight;
    const scrollTop = this.console.scrollTop;

    // update start position and render
    this.startPosition = Math.max(this.lineOffset, Math.min(this.lineCount() - (this.visibleCount * 2), this.endPosition - (this.visibleCount * 2)));
    this.console.innerHTML = this._generateContent();

    // use difference to scroll offset
    const newScrollHeight = this.console.scrollHeight;
    this.console.scrollTop = scrollTop - (scrollHeight - newScrollHeight);

    this.dirty = false;
  }
};

Scroller.prototype._onScroll = function(){
  if(this.jumpToBottom){
    // do nothing, prepare to jump
    return;
  }
  const height = this.console.offsetHeight;
  const scrollHeight = this.console.scrollHeight;
  const scrollTop = this.console.scrollTop;
  const nearTop = scrollTop < this.expandDistance;
  const nearBottom = scrollTop + height > scrollHeight - this.expandDistance;
  const nearSticky = scrollTop + height > scrollHeight - 10;

  if(this.sticky){
    if(!nearSticky){
      this.sticky = false;
    }
  }else{
    if(nearTop && this.startPosition > this.lineOffset){
      this.expandTop();
    }else if(nearBottom){
      if(this.endPosition < this.lineCount() - 2){
        this.expandBottom();
      }else if(nearSticky){
        this.jumpToBottom = true;
        this.sticky = true;
        this.dirty = true;
      }
    }
  }

  if(this.dirty && !this.animateRequest){
    this.animateRequest = requestAnimationFrame(this.refresh);
  }
};

module.exports = Scroller;
<|endoftext|>


<commit_before>import { createStore } from "../src"
import test from "tape"

function nextState(store) {
  return store.dispatch(() => store.getState() + 1)
}

test("createStore", t => {
  const initialState = {}
  const store = createStore(initialState)

  t.ok(store.getState() === initialState,
    "creates a store with an initial state")

  t.throws(() => createStore(),
    /missing initial state/i,
    "the initial state is mandatory")

  t.end()
})

test("subscribe", t => {
  const store = createStore(1)
  t.plan(3)

  const unsubscribe = store.subscribe(() => {
    t.pass("adds a subscriber to be called upon dispatch")
  })
  nextState(store)

  unsubscribe()
  nextState(store)
  t.pass("returns a function that can be used to stop subscribing")

  const subscriber = () => t.pass("ignores duplicate subscribers")
  store.subscribe(subscriber)
  store.subscribe(subscriber)
  nextState(store)
})

test("dispatch", t => {
  const store = createStore(1)

  nextState(store)
  t.equal(2, store.getState(),
    "updates the state with the result of the dispatched change")

  nextState(store)
  t.equal(3, store.getState(),
    "passes the current state to the dispatched change")

  store.dispatch((s, i) => s + i, 1)
  t.equal(4, store.getState(),
    "passes extra arguments to the dispatched change")

  store.subscribe((newState, oldState) => {
    t.pass("notifies subscribers if the state changes")
    t.equal(5, newState, "passes the new to the subscriber")
    t.equal(4, oldState, "passes the old state to the subscriber")
  })
  nextState(store)

  store.subscribe(() => {
    t.fail("does not notifies subscribers if the state does not change")
  })
  store.dispatch(s => s)

  t.end()
})

test("middleware", t => {
  t.plan(4)

  const setStateTo2 = () => 2
  const changeArg = "some argument"
  const middleware = (next, state, change, ...args) => {
    t.pass("is called when a change is dispatched")
    t.equal(state, store.getState(),
      "is passed the store state")
    t.equal(change, setStateTo2,
      "is passed the dispatched change")
    t.equal(args[0], changeArg,
      "is passed the dispatched change arguments")

    return next(state, change, ...args)
  }

  const store = createStore(1, [ middleware ])
  store.dispatch(setStateTo2, changeArg)
})
<commit_msg>Update tests<commit_after>import { createStore } from "../src"
import test from "tape"

function nextState(store) {
  return store.dispatch(() => store.getState() + 1)
}

test("createStore", t => {
  const initialState = {}
  const store = createStore(initialState)

  t.ok(store.getState() === initialState,
    "creates a store with an initial state")

  t.throws(() => createStore(),
    /missing initial state/i,
    "the initial state is mandatory")

  t.end()
})

test("subscribe", t => {
  const store = createStore(1)
  t.plan(3)

  const unsubscribe = store.subscribe(() => {
    t.pass("adds a subscriber to be called upon dispatch")
  })
  nextState(store)

  unsubscribe()
  nextState(store)
  t.pass("returns a function that can be used to stop subscribing")

  const subscriber = () => t.pass("ignores duplicate subscribers")
  store.subscribe(subscriber)
  store.subscribe(subscriber)
  nextState(store)
})

test("dispatch", t => {
  const store = createStore(1)

  nextState(store)
  t.equal(2, store.getState(),
    "updates the state with the result of the dispatched change")

  store.dispatch(s => s - 1)
  t.equal(1, store.getState(),
    "passes the current state to the dispatched change")

  store.dispatch((s, i) => s + i, 1)
  t.equal(2, store.getState(),
    "passes extra arguments to the dispatched change")

  store.subscribe((newState, oldState) => {
    t.pass("notifies subscribers if the state changes")
    t.equal(3, newState, "passes the new to the subscriber")
    t.equal(2, oldState, "passes the old state to the subscriber")
  })
  nextState(store)

  store.subscribe(() => {
    t.fail("does not notifies subscribers if the state does not change")
  })
  store.dispatch(s => s)

  t.end()
})

test("middleware", t => {
  t.plan(4)

  const setStateTo2 = () => 2
  const changeArg = "some argument"
  const middleware = (next, state, change, ...args) => {
    t.pass("is called when a change is dispatched")
    t.equal(state, store.getState(),
      "is passed the store state")
    t.equal(change, setStateTo2,
      "is passed the dispatched change")
    t.equal(args[0], changeArg,
      "is passed the dispatched change arguments")

    return next(state, change, ...args)
  }

  const store = createStore(1, [ middleware ])
  store.dispatch(setStateTo2, changeArg)
})
<|endoftext|>


<commit_before>"use strict";

const RemoteCallable = require("./RemoteCallable.js"),
	RemoteNode = require("./RemoteNode.js"),
	ID = require("./ID.js");

class RemoteCallModule extends RemoteCallable {
	constructor(chord){
		super(chord, "ChordRPC");
		chord.registerModule(this);
	}

	delegate (handler, message) {
		if(super.delegate(handler, message))
			return;

		switch(handler){
			case "getSuccessor":
				this.chord.node.getSuccessor()
					.then(
						result => this.answer(message, (result) ? result.id.idString : result)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "setSuccessor":
				this.chord.node.setSuccessor(this.chord.obtainRemoteNode(message.data.params[0]))
					.then(
						result => this.answer(message, null)
					);
				break;
			case "getPredecessor":
				this.chord.node.getPredecessor()
					.then(
						result => this.answer(message, (result) ? result.id.idString : result)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "setPredecessor":
				this.chord.node.setPredecessor(this.chord.obtainRemoteNode(message.data.params[0]))
					.then(
						result => this.answer(message, null)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "updateFingerTable":
				this.chord.node.updateFingerTable(this.chord.obtainRemoteNode(message.data.params[0]), message.data.params[1])
					.then(
						result => this.answer(message, null)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "findSuccessor":
				this.chord.node.findSuccessor(new ID(message.data.params[0]))
					.then(
						result => this.answer(message, result.id.idString)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "findPredecessor":
				this.chord.node.findPredecessor(new ID(message.data.params[0]))
					.then(
						result => this.answer(message, result.id.idString)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "closestPrecedingFinger":
				this.chord.node.closestPrecedingFinger(new ID(message.data.params[0]))
					.then(
						result => this.answer(message, result.id.idString)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "notify":
				this.chord.node.notify(this.chord.obtainRemoteNode(message.data.params[0]))
					.then(
						result => this.answer(message, null)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "unlinkClient":
				this.answer(message, this.chord.node.unlinkClient(message.data.params[0]));
				break;
			default:
				//ILLEGAL CALL
				break;
		}
	}
}

module.exports = RemoteCallModule;<commit_msg>Changes to RCM's delegate call.<commit_after>"use strict";

const RemoteCallable = require("./RemoteCallable.js"),
	RemoteNode = require("./RemoteNode.js"),
	ID = require("./ID.js");

class RemoteCallModule extends RemoteCallable {
	constructor(chord){
		super(chord, "ChordRPC");
		chord.registerModule(this);
	}

	delegate (message) {
		if(super.delegate(message))
			return;

		switch(handler){
			case "getSuccessor":
				this.chord.node.getSuccessor()
					.then(
						result => this.answer(message, (result) ? result.id.idString : result)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "setSuccessor":
				this.chord.node.setSuccessor(this.chord.obtainRemoteNode(message.data.params[0]))
					.then(
						result => this.answer(message, null)
					);
				break;
			case "getPredecessor":
				this.chord.node.getPredecessor()
					.then(
						result => this.answer(message, (result) ? result.id.idString : result)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "setPredecessor":
				this.chord.node.setPredecessor(this.chord.obtainRemoteNode(message.data.params[0]))
					.then(
						result => this.answer(message, null)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "updateFingerTable":
				this.chord.node.updateFingerTable(this.chord.obtainRemoteNode(message.data.params[0]), message.data.params[1])
					.then(
						result => this.answer(message, null)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "findSuccessor":
				this.chord.node.findSuccessor(new ID(message.data.params[0]))
					.then(
						result => this.answer(message, result.id.idString)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "findPredecessor":
				this.chord.node.findPredecessor(new ID(message.data.params[0]))
					.then(
						result => this.answer(message, result.id.idString)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "closestPrecedingFinger":
				this.chord.node.closestPrecedingFinger(new ID(message.data.params[0]))
					.then(
						result => this.answer(message, result.id.idString)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "notify":
				this.chord.node.notify(this.chord.obtainRemoteNode(message.data.params[0]))
					.then(
						result => this.answer(message, null)
					)
					.catch(
						reason => this.error(message, reason)
					);
				break;
			case "unlinkClient":
				this.answer(message, this.chord.node.unlinkClient(message.data.params[0]));
				break;
			default:
				//ILLEGAL CALL
				break;
		}
	}
}

module.exports = RemoteCallModule;<|endoftext|>


<commit_before>import { Store }            from 'airflux';
import Firebase             from 'firebase';

import AuthStore            from './AuthStore';
import * as Actions         from './BoardActions';

class BoardStore extends Store {

    state = {
        widgets : [],
        authStoreState : '',
        zoom : 1
    };

    constructor() {
        super();

        this.listenTo( AuthStore, this._onAuthSuccess.bind( this ) );

        Actions.setSize.listen( this._setSize.bind( this ) );
        Actions.addWidget.listen( this._addWidget.bind( this ) );
        Actions.removeWidget.listen( this._removeWidget.bind( this ) );
        Actions.clearBoard.listen( this._clearBoard.bind( this ) );
        Actions.setZoom.listen( this._setZoom.bind(this ) );
    }

    get size() { return this.state.size; }

    get zoom() { return this.state.zoom || 1; }

    destroy() {
        this.boardSizeRef.off();
        this.widgetsRef.off();
        this.latestIndexRef.off();
    }

    _setZoom( zoom ) {
        this.state.zoom = zoom;
    }

    // FIXME : use promise
    getLatestIndex( callback ) {
        this.latestIndexRef.transaction( latestIndex => ( latestIndex || 0 ) + 1, callback );
    }

    _onAuthSuccess( authStoreState ) {
        const { firebaseUrl , boardKey } = authStoreState.appConfig;
        this.authStoreState = authStoreState;
        //this.boardSizeRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/size` );
        //this.widgetsRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/widget` );
        //this.latestIndexRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/latestWidgetIndex` );

        this.boardSizeRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/size` );
        this.widgetsRef = new Firebase( `${firebaseUrl}/widgets/${boardKey}` );
        this.latestIndexRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/latestWidgetIndex` );

        this.widgetsRef.on( 'child_added', this._onAddWidget.bind( this ) );
        this.widgetsRef.on( 'child_removed', this._onRemoveWidget.bind( this ) );
        this.boardSizeRef.on( 'value', this._onNewSize.bind( this ) );
    }

    _onAddWidget( dataSnapshot ) {
        let { widgets } = this.state;
        widgets.push( { key : dataSnapshot.key(), val : dataSnapshot.val() } );
        this.state.widgets = widgets;
        this.publishState();
    }

    _onRemoveWidget( oldDataSnapshot ) {
        const widgetKey = oldDataSnapshot.key();
        let { widgets } = this.state;
        _.remove( widgets, w => { return w.key === widgetKey; } );
        this.state.widgets = widgets;
        this.publishState();
    }

    _onNewSize( dataSnapshot ) {
       const size = dataSnapshot.val();
       if ( size ) {
           this.state.size = size;
           this.publishState();
       }
   }

   _addWidget( widget ) {
       this.getLatestIndex( ( error, committed, snapshot ) => {
           if ( !error && committed ) {
               widget.props.index = snapshot.val();
               widget.props.isEditingBy = AuthStore.currentUser;
               this.widgetsRef.push( widget, (error) => {
                    error ? console.log(error) : null;
               });
           } else {
               // TODO : handle error ?
               console.log(error);
           }
       });
   }

    _removeWidget( widgetKey ) {
        const { firebaseUrl , boardKey } = this.authStoreState.appConfig;
        let widgetBase = new Firebase( `${firebaseUrl}/widgets/${boardKey}/${widgetKey}` );
        widgetBase.remove();
        widgetBase.off();
    }

    _clearBoard() {
        this.widgetsRef.remove();
        this.latestIndexRef.remove();
    }

    _setSize( size ) {
        this.boardSizeRef.set( size );
    }
}

export default new BoardStore();
<commit_msg>initialize list of empty widget<commit_after>import { Store }            from 'airflux';
import Firebase             from 'firebase';

import AuthStore            from './AuthStore';
import * as Actions         from './BoardActions';

class BoardStore extends Store {

    state = {
        widgets : [],
        authStoreState : '',
        zoom : 1
    };

    constructor() {
        super();

        this.listenTo( AuthStore, this._onAuthSuccess.bind( this ) );

        Actions.setSize.listen( this._setSize.bind( this ) );
        Actions.addWidget.listen( this._addWidget.bind( this ) );
        Actions.removeWidget.listen( this._removeWidget.bind( this ) );
        Actions.clearBoard.listen( this._clearBoard.bind( this ) );
        Actions.setZoom.listen( this._setZoom.bind(this ) );
    }

    get size() { return this.state.size; }

    get zoom() { return this.state.zoom || 1; }

    destroy() {
        this.boardSizeRef.off();
        this.widgetsRef.off();
        this.latestIndexRef.off();
    }

    _setZoom( zoom ) {
        this.state.zoom = zoom;
    }

    // FIXME : use promise
    getLatestIndex( callback ) {
        this.latestIndexRef.transaction( latestIndex => ( latestIndex || 0 ) + 1, callback );
    }

    _onAuthSuccess( authStoreState ) {
        const { firebaseUrl , boardKey } = authStoreState.appConfig;
        this.authStoreState = authStoreState;

        this.boardSizeRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/size` );
        this.widgetsRef = new Firebase( `${firebaseUrl}/widgets/${boardKey}` );
        this.latestIndexRef = new Firebase( `${firebaseUrl}/boards/${boardKey}/latestWidgetIndex` );

        this.widgetsRef.off();
        this.boardSizeRef.off();
        this.state.widgets = [];

        this.widgetsRef.on( 'child_added', this._onAddWidget.bind( this ) );
        this.widgetsRef.on( 'child_removed', this._onRemoveWidget.bind( this ) );
        this.boardSizeRef.on( 'value', this._onNewSize.bind( this ) );
    }

    _onAddWidget( dataSnapshot ) {
        let { widgets } = this.state;
        widgets.push( { key : dataSnapshot.key(), val : dataSnapshot.val() } );
        this.state.widgets = widgets;
        this.publishState();
    }

    _onRemoveWidget( oldDataSnapshot ) {
        const widgetKey = oldDataSnapshot.key();
        let { widgets } = this.state;
        _.remove( widgets, w => { return w.key === widgetKey; } );
        this.state.widgets = widgets;
        this.publishState();
    }

    _onNewSize( dataSnapshot ) {
       const size = dataSnapshot.val();
       if ( size ) {
           this.state.size = size;
           this.publishState();
       }
   }

   _addWidget( widget ) {
       this.getLatestIndex( ( error, committed, snapshot ) => {
           if ( !error && committed ) {
               widget.props.index = snapshot.val();
               widget.props.isEditingBy = AuthStore.currentUser;
               this.widgetsRef.push( widget, (error) => {
                    error ? console.log(error) : null;
               });
           } else {
               // TODO : handle error ?
               console.log(error);
           }
       });
   }

    _removeWidget( widgetKey ) {
        const { firebaseUrl , boardKey } = this.authStoreState.appConfig;
        let widgetBase = new Firebase( `${firebaseUrl}/widgets/${boardKey}/${widgetKey}` );
        widgetBase.remove();
        widgetBase.off();
    }

    _clearBoard() {
        this.widgetsRef.remove();
        this.latestIndexRef.remove();
    }

    _setSize( size ) {
        this.boardSizeRef.set( size );
    }
}

export default new BoardStore();
<|endoftext|>


<commit_before>/* eslint consistent-return: 0, no-else-return: 0*/
import {API} from "../../.env";
import {polyfill} from "es6-promise";
import axios from "axios";

import {dataFold as fold} from "d3plus-viz";
import {DICTIONARY} from "../helpers/dictionary";
import {FORMATTERS, VARIABLES} from "../helpers/formatters";

polyfill();

export function fetchStats(store) {

  const prefix = `${API}api/join/?geo=${store.id}`;

  const crop = axios.get(`${prefix}&show=crop&sumlevel=lowest&required=harvested_area&order=harvested_area&sort=desc`)
    .then(res => {

      const d = fold(res.data)[0];

      return {
        attr: "crop",
        key: "harvested_area",
        label: `Most Harvested Crop in ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: d.crop
      };

    });

  const rainfall = axios.get(`${prefix}&show=geo&required=rainfall_awa_mm,year,start_year`)
    .then(res => {

      const d = fold(res.data)[0];

      return {
        key: "rainfall_awa_mm",
        label: `${DICTIONARY.rainfall_awa_mm} from ${d.start_year} to ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: VARIABLES.rainfall_awa_mm(d.rainfall_awa_mm)
      };

    });

  const condition = axios.get(`${prefix}&show=geo&required=condition,proportion_of_children&severity=severe&order=year&sort=desc`)
    .then(res => {

      const data = fold(res.data);
      const d = data
        .filter(d => d.year === data[0].year)
        .sort((a, b) => b.proportion_of_children - a.proportion_of_children)[0];

      return {
        key: "proportion_of_children,condition",
        label: `Most Severe Condition in ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: `${FORMATTERS.shareWhole(d.proportion_of_children)} ${DICTIONARY[d.condition]}`
      };

    });

  const poverty = axios.get(`${prefix}&show=geo&required=hc&poverty_level=ppp1&order=year&sort=desc`)
    .then(res => {

      const d = fold(res.data)[0];

      return {
        key: "hc",
        label: `Poverty (PPP1) in ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: FORMATTERS.shareWhole(d.hc)
      };

    });

  return {
    type: "GET_STATS",
    promise: Promise.all([crop, poverty, condition, rainfall])
  };

}

function formatVars(res) {

  const data = fold(res.data);
  const url = res.config.url;
  const name = (/show=([a-z_,]+)/g).exec(url)[1];
  const key = url.includes("order=") ? (/order=([a-z_,]+)/g).exec(url)[1] : (/required=([a-z_,]+)/g).exec(url)[1];

  return {
    name,
    data,
    key
  };

}

export function fetchVars(store) {

  const prefix = `${API}api/join/?geo=${store.id}`;

  const harvest = axios.get(`${prefix}&show=crop&required=harvested_area,value_of_production&limit=5&order=harvested_area&sort=desc`)
    .then(formatVars);

  const production = axios.get(`${prefix}&show=crop&required=value_of_production,harvested_area&limit=5&order=value_of_production&sort=desc`)
    .then(formatVars);

  return {
    type: "GET_VARS",
    promise: Promise.all([harvest, production])
  };

}
<commit_msg>adding improved label for ppp in splash. closes #4<commit_after>/* eslint consistent-return: 0, no-else-return: 0*/
import {API} from "../../.env";
import {polyfill} from "es6-promise";
import axios from "axios";

import {dataFold as fold} from "d3plus-viz";
import {DICTIONARY} from "../helpers/dictionary";
import {FORMATTERS, VARIABLES} from "../helpers/formatters";

polyfill();

export function fetchStats(store) {

  const prefix = `${API}api/join/?geo=${store.id}`;

  const crop = axios.get(`${prefix}&show=crop&sumlevel=lowest&required=harvested_area&order=harvested_area&sort=desc`)
    .then(res => {

      const d = fold(res.data)[0];

      return {
        attr: "crop",
        key: "harvested_area",
        label: `Most Harvested Crop in ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: d.crop
      };

    });

  const rainfall = axios.get(`${prefix}&show=geo&required=rainfall_awa_mm,year,start_year`)
    .then(res => {

      const d = fold(res.data)[0];

      return {
        key: "rainfall_awa_mm",
        label: `${DICTIONARY.rainfall_awa_mm} from ${d.start_year} to ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: VARIABLES.rainfall_awa_mm(d.rainfall_awa_mm)
      };

    });

  const condition = axios.get(`${prefix}&show=geo&required=condition,proportion_of_children&severity=severe&order=year&sort=desc`)
    .then(res => {

      const data = fold(res.data);
      const d = data
        .filter(d => d.year === data[0].year)
        .sort((a, b) => b.proportion_of_children - a.proportion_of_children)[0];

      return {
        key: "proportion_of_children,condition",
        label: `Most Severe Condition in ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: `${FORMATTERS.shareWhole(d.proportion_of_children)} ${DICTIONARY[d.condition]}`
      };

    });

  const poverty = axios.get(`${prefix}&show=geo&required=hc&poverty_level=ppp1&order=year&sort=desc`)
    .then(res => {

      const d = fold(res.data)[0];

      return {
        key: "hc",
        label: `Poverty Headcount Ratio ${DICTIONARY.ppp1} in ${d.year}`,
        source: res.data.source[0],
        subs: res.data.subs,
        url: res.config.url,
        value: FORMATTERS.shareWhole(d.hc)
      };

    });

  return {
    type: "GET_STATS",
    promise: Promise.all([crop, poverty, condition, rainfall])
  };

}

function formatVars(res) {

  const data = fold(res.data);
  const url = res.config.url;
  const name = (/show=([a-z_,]+)/g).exec(url)[1];
  const key = url.includes("order=") ? (/order=([a-z_,]+)/g).exec(url)[1] : (/required=([a-z_,]+)/g).exec(url)[1];

  return {
    name,
    data,
    key
  };

}

export function fetchVars(store) {

  const prefix = `${API}api/join/?geo=${store.id}`;

  const harvest = axios.get(`${prefix}&show=crop&required=harvested_area,value_of_production&limit=5&order=harvested_area&sort=desc`)
    .then(formatVars);

  const production = axios.get(`${prefix}&show=crop&required=value_of_production,harvested_area&limit=5&order=value_of_production&sort=desc`)
    .then(formatVars);

  return {
    type: "GET_VARS",
    promise: Promise.all([harvest, production])
  };

}
<|endoftext|>


<commit_before>import { introJs } from 'intro.js'
import isMobile from 'ismobilejs'
import { shouldEnableTracking, getTracker } from 'cozy-ui/react/helpers/tracker'
require('../../node_modules/intro.js/minified/introjs.min.css')

export function isTutorial () {
  return window.location.hash.endsWith('/?intro')
}

export function display (t) {
  const isSmall = document.querySelectorAll('.coz-nav')[0].offsetParent === null
  const isLandscape = window.innerWidth > window.innerHeight
  const cozyBarMenuClass = isSmall ? '.coz-bar-burger' : '[data-icon=icon-cube]'
  const cozyBarMenuButton = document.querySelectorAll(cozyBarMenuClass)[0]
  const tooltipClass = 'tooltip' + (isSmall ? 'Small' : '') + (isSmall && isLandscape ? 'Right' : 'Bottom')
  const shouldTrackTutorial = shouldEnableTracking()
  const trackerInstance = getTracker()
  const pageURLsForTracking = [
    'tutorial/automate',
    'tutorial/apps'
  ]
  const tutorial = introJs()
  tutorial.setOptions({
    overlayOpacity: 0.7,
    showBullets: false,
    hidePrev: true,
    hideNext: true,
    exitOnEsc: false,
    exitOnOverlayClick: false,
    disableInteraction: true,
    doneLabel: `${t('tutorial.menu_apps.button')}`,
    nextLabel: `${t('tutorial.cozy_collect.button')}`,
    steps: [
      {
        element: document.querySelectorAll('.item-wrapper')[0],
        intro: `<h1>${t('tutorial.cozy_collect.title')}</h1><div>${t('tutorial.cozy_collect.text')}</div>`,
        tooltipClass: tooltipClass,
        position: isSmall && isLandscape ? 'right' : 'bottom'
      },
      {
        element: cozyBarMenuButton,
        intro: `<h1>${t('tutorial.menu_apps.title')}</h1><div>${t('tutorial.menu_apps.text')}</div>`,
        tooltipClass: isSmall ? 'tooltipCenter' : 'tooltipLeft',
        position: isSmall ? 'right' : 'bottom'
      }
    ]
  })
  .onafterchange((targetElement) => {
    if (shouldTrackTutorial && trackerInstance) {
      let stepIndex
      let steps = tutorial._options.steps
      for (let i = 0, l = steps.length; i < l; ++i) {
        if (steps[i].element === targetElement) {
          stepIndex = i
          break
        }
      }

      const trackingURL = pageURLsForTracking[stepIndex]
      trackerInstance.push(['setCustomUrl', trackingURL])
      trackerInstance.push(['trackPageView'])
    }

    // The intro.js button for the last step is hidden, so we need to show it when we arrive on it
    const doneButton = document.querySelectorAll('.introjs-donebutton')[0]
    if (!doneButton) return // step 1, no done button yet

    if (targetElement.className === cozyBarMenuButton.className) {
      doneButton.classList.remove('introjs-skipbutton')
    } else {
      doneButton.classList.add('introjs-skipbutton')
    }
  })
  .oncomplete(() => {
    cozyBarMenuButton.click()
    window.location.hash = '#/discovery'
  })
  .start()
  if (isMobile.phone) {
    const clickZone = '.introjs-disableInteraction, .introjs-overlay, .introjs-tooltiptext, .introjs-tooltipbuttons'
    const clickAction = (e) => {
      if (e.srcElement.tagName !== 'A') {
        e.stopPropagation()
        tutorial.nextStep()
      }
    }
    for (const elem of document.querySelectorAll(clickZone)) {
      elem.onclick = clickAction
    }
  }
}
<commit_msg>feat: add a final step<commit_after>import { introJs } from 'intro.js'
import isMobile from 'ismobilejs'
import { shouldEnableTracking, getTracker } from 'cozy-ui/react/helpers/tracker'
require('../../node_modules/intro.js/minified/introjs.min.css')

export function isTutorial () {
  return window.location.hash.endsWith('/?intro')
}

export function display (t) {
  const isSmall = document.querySelectorAll('.coz-nav')[0].offsetParent === null
  const isLandscape = window.innerWidth > window.innerHeight
  const cozyBarMenuClass = isSmall ? '.coz-bar-burger' : '[data-icon=icon-cube]'
  const cozyBarMenuButton = document.querySelectorAll(cozyBarMenuClass)[0]
  const tooltipClass = 'tooltip' + (isSmall ? 'Small' : '') + (isSmall && isLandscape ? 'Right' : 'Bottom')
  const shouldTrackTutorial = shouldEnableTracking()
  const trackerInstance = getTracker()
  const pageURLsForTracking = [
    'tutorial/automate',
    'tutorial/apps',
    'tutorial/complete'
  ]
  const tutorial = introJs()
  tutorial.setOptions({
    overlayOpacity: 0.7,
    showBullets: false,
    hidePrev: true,
    hideNext: true,
    exitOnEsc: false,
    exitOnOverlayClick: false,
    disableInteraction: true,
    doneLabel: `${t('tutorial.menu_apps.button')}`,
    nextLabel: `${t('tutorial.cozy_collect.button')}`,
    steps: [
      {
        element: document.querySelectorAll('.item-wrapper')[0],
        intro: `<h1>${t('tutorial.cozy_collect.title')}</h1><div>${t('tutorial.cozy_collect.text')}</div>`,
        tooltipClass: tooltipClass,
        position: isSmall && isLandscape ? 'right' : 'bottom'
      },
      {
        element: cozyBarMenuButton,
        intro: `<h1>${t('tutorial.menu_apps.title')}</h1><div>${t('tutorial.menu_apps.text')}</div>`,
        tooltipClass: isSmall ? 'tooltipCenter' : 'tooltipLeft',
        position: isSmall ? 'right' : 'bottom'
      }
    ]
  })
  .onafterchange((targetElement) => {
    if (shouldTrackTutorial && trackerInstance) {
      let stepIndex
      let steps = tutorial._options.steps
      for (let i = 0, l = steps.length; i < l; ++i) {
        if (steps[i].element === targetElement) {
          stepIndex = i
          break
        }
      }

      const trackingURL = pageURLsForTracking[stepIndex]
      trackerInstance.push(['setCustomUrl', trackingURL])
      trackerInstance.push(['trackPageView'])
    }

    // The intro.js button for the last step is hidden, so we need to show it when we arrive on it
    const doneButton = document.querySelectorAll('.introjs-donebutton')[0]
    if (!doneButton) return // step 1, no done button yet

    if (targetElement.className === cozyBarMenuButton.className) {
      doneButton.classList.remove('introjs-skipbutton')
    } else {
      doneButton.classList.add('introjs-skipbutton')
    }
  })
  .oncomplete(() => {
    if (shouldTrackTutorial && trackerInstance) {
      trackerInstance.push(['setCustomUrl', pageURLsForTracking[pageURLsForTracking.length - 1]])
      trackerInstance.push(['trackPageView'])
    }

    cozyBarMenuButton.click()
    window.location.hash = '#/discovery'
  })
  .start()
  if (isMobile.phone) {
    const clickZone = '.introjs-disableInteraction, .introjs-overlay, .introjs-tooltiptext, .introjs-tooltipbuttons'
    const clickAction = (e) => {
      if (e.srcElement.tagName !== 'A') {
        e.stopPropagation()
        tutorial.nextStep()
      }
    }
    for (const elem of document.querySelectorAll(clickZone)) {
      elem.onclick = clickAction
    }
  }
}
<|endoftext|>


<commit_before>var Gaffa = require('gaffa'),
    doc = require('doc-js'),
    crel = require('crel');

function createNextStep(action, stepIndex){
    stepIndex = stepIndex || 0;

    var callback = function(event){
        if(event.getValue()){
            action.gaffa.gedi.debind(callback);
            if(++stepIndex<action.steps.length){
                createNextStep(stepIndex);
            }else{
                action.triggerActions('complete');
                action.debind();
            }
        }
    };
    action.gediCallbacks.push(callback);
    action.gaffa.gedi.bind(action.steps[stepIndex], callback);
}

function Flow(actionDefinition){}
Flow = Gaffa.createSpec(Flow, Gaffa.Action);
Flow.prototype.type = 'flow';
Flow.prototype.cancel = new Gaffa.Property();
Flow.prototype.trigger = function(){
    this.__super__.trigger.apply(this, arguments);

    var action = this;

    action.gediCallbacks = action.gediCallbacks || [];

    if(!this.steps || !this.steps.length){
        return;
    }

    createNextStep(this);

    var cancelCallback = function(event){
        if(event.getValue()){
            action.triggerActions('cancel');
            action.debind();
        }
    };
    action.gediCallbacks.push(cancelCallback);
    action.gaffa.model.bind(action.cancel.binding, cancelCallback);
};

module.exports = Flow;<commit_msg>fixed other bind call<commit_after>var Gaffa = require('gaffa'),
    doc = require('doc-js'),
    crel = require('crel');

function createNextStep(action, stepIndex){
    stepIndex = stepIndex || 0;

    var callback = function(event){
        if(event.getValue()){
            action.gaffa.gedi.debind(callback);
            if(++stepIndex<action.steps.length){
                createNextStep(stepIndex);
            }else{
                action.triggerActions('complete');
                action.debind();
            }
        }
    };
    action.gediCallbacks.push(callback);
    action.gaffa.gedi.bind(action.steps[stepIndex], callback);
}

function Flow(actionDefinition){}
Flow = Gaffa.createSpec(Flow, Gaffa.Action);
Flow.prototype.type = 'flow';
Flow.prototype.cancel = new Gaffa.Property();
Flow.prototype.trigger = function(){
    this.__super__.trigger.apply(this, arguments);

    var action = this;

    action.gediCallbacks = action.gediCallbacks || [];

    if(!this.steps || !this.steps.length){
        return;
    }

    createNextStep(this);

    var cancelCallback = function(event){
        if(event.getValue()){
            action.triggerActions('cancel');
            action.debind();
        }
    };
    action.gediCallbacks.push(cancelCallback);
    action.gaffa.gedi.bind(action.cancel.binding, cancelCallback);
};

module.exports = Flow;<|endoftext|>


<commit_before>import { Adapter } from 'ember-pouch';
import Ember from 'ember';

import PouchAdapterUtils from 'hospitalrun/mixins/pouch-adapter-utils';

export default Adapter.extend(PouchAdapterUtils, {
    databaseName: 'config',
    database: Ember.inject.service(),
    db:  Ember.computed.alias('database.configDB'),

    _mapQuery: function(doc, emit) {
        if (doc._id) {
            emit(doc._id, null);
        }
    },

    findQuery: function(store, type, query, options) {
        if (!query.exactKeys) {
            return this._super(store, type, query, options);
        } else {
            var self = this,
            queryParams = {
                reduce: false,
                include_docs: true
            };

            queryParams.keys = query.exactKeys.map(function(key) {
                return this.get('database').getPouchId(key, 'config');
            }.bind(this));
            return new Ember.RSVP.Promise(function(resolve, reject){
                self._getDb().then(function(db){
                    try {
                        db.query(self._mapQuery, queryParams, function(err, response) {
                            if (err) {
                                self._pouchError(reject)(err);
                            } else {
                                if (response.rows) {
                                    var data = Ember.A(response.rows).mapBy('doc');
                                    Ember.run(null, resolve, data);
                                }
                            }
                        });
                    } catch (err){
                        self._pouchError(reject)(err);
                    }
                }, self._pouchError(reject));
            }, "findQuery in config-pouchdb-adapter");
        }
    }
});
<commit_msg>Removed reference to database in config adapter<commit_after>import { Adapter } from 'ember-pouch';
import Ember from 'ember';

import PouchAdapterUtils from 'hospitalrun/mixins/pouch-adapter-utils';

export default Adapter.extend(PouchAdapterUtils, {
    databaseName: 'config',

    _mapQuery: function(doc, emit) {
        if (doc._id) {
            emit(doc._id, null);
        }
    },

    findQuery: function(store, type, query, options) {
        if (!query.exactKeys) {
            return this._super(store, type, query, options);
        } else {
            var self = this,
            queryParams = {
                reduce: false,
                include_docs: true
            };

            queryParams.keys = query.exactKeys.map(function(key) {
                return this.get('database').getPouchId(key, 'config');
            }.bind(this));
            return new Ember.RSVP.Promise(function(resolve, reject){
                self._getDb().then(function(db){
                    try {
                        db.query(self._mapQuery, queryParams, function(err, response) {
                            if (err) {
                                self._pouchError(reject)(err);
                            } else {
                                if (response.rows) {
                                    var data = Ember.A(response.rows).mapBy('doc');
                                    Ember.run(null, resolve, data);
                                }
                            }
                        });
                    } catch (err){
                        self._pouchError(reject)(err);
                    }
                }, self._pouchError(reject));
            }, "findQuery in config-pouchdb-adapter");
        }
    }
});
<|endoftext|>