metamaps/metamaps

View on GitHub
realtime/reducer.js

Summary

Maintainability
A
2 hrs
Test Coverage
const { find, omit, mapValues, values } = require('lodash')
const {
  JOIN_MAP,
  LEAVE_MAP,
  JOIN_CALL,
  LEAVE_CALL
} = require('../frontend/src/Metamaps/Realtime/events')

const NOT_IN_CONVERSATION = 0
const IN_CONVERSATION = 1

const addMapperToMap = (map, userId) => Object.assign({}, map, { [userId]: NOT_IN_CONVERSATION })
const userStillPresent = (userId, liveMaps) => {
  if (!userId) return false
  let stillPresent = false
  const userIdString = userId.toString()
  values(liveMaps).forEach(presentUsers => {
    if (find(Object.keys(presentUsers), id => id === userIdString)) stillPresent = true
  })
  return stillPresent
}

const reducer = (state = { connectedPeople: {}, liveMaps: {} }, action) => {
  const { type, payload } = action
  const { connectedPeople, liveMaps } = state
  const map = payload && liveMaps[payload.mapid]
  const mapWillEmpty = map && Object.keys(map).length === 1
  const callWillFinish = map && (type === LEAVE_CALL || type === 'DISCONNECT') && Object.keys(map).length === 2

  switch (type) {
    case JOIN_MAP:
      return Object.assign({}, state, {
        connectedPeople: Object.assign({}, connectedPeople, {
          [payload.userid]: {
            id: payload.userid,
            username: payload.username,
            avatar: payload.avatar
          }
        }),
        liveMaps: Object.assign({}, liveMaps, {
          [payload.mapid]: addMapperToMap(map || {}, payload.userid)
        })
      })
    case LEAVE_MAP:
    // if the map will empty, remove it from liveMaps, if the map will not empty, just remove the mapper
      const newLiveMaps = mapWillEmpty
      ? omit(liveMaps, payload.mapid)
      : Object.assign({}, liveMaps, { [payload.mapid]: omit(map, payload.userid) })
      delete newLiveMaps[undefined]
      delete newLiveMaps[null]
      const updateConnectedPeople = userStillPresent(payload.userid, newLiveMaps) ? connectedPeople : omit(connectedPeople, payload.userid)

      return {
        connectedPeople: updateConnectedPeople,
        liveMaps: newLiveMaps
      }
    case JOIN_CALL:
    // update the user (payload.id is user id) in the given map to be marked in the conversation
      return Object.assign({}, state, {
        liveMaps: Object.assign({}, liveMaps, {
          [payload.mapid]: Object.assign({}, map, {
            [payload.id]: IN_CONVERSATION
          })
        })
      })
    case LEAVE_CALL:
      const newMap = callWillFinish
      ? mapValues(map, () => NOT_IN_CONVERSATION)
      : Object.assign({}, map, { [payload.userid]: NOT_IN_CONVERSATION })

      return Object.assign({}, state, {
        liveMaps: Object.assign({}, liveMaps, { [payload.mapid]: newMap })
      })
    case 'DISCONNECT':
      const mapWithoutUser = omit(map, payload.userid)
      const newMapWithoutUser = callWillFinish ? mapValues(mapWithoutUser, () => NOT_IN_CONVERSATION) : mapWithoutUser
      const newLiveMapsWithoutUser = mapWillEmpty ? omit(liveMaps, payload.mapid) : Object.assign({}, liveMaps, { [payload.mapid]: newMapWithoutUser })
      delete newLiveMapsWithoutUser[undefined]
      delete newLiveMapsWithoutUser[null]
      const newConnectedPeople = userStillPresent(payload.userid, newLiveMapsWithoutUser) ? connectedPeople : omit(connectedPeople, payload.userid)
      return {
        connectedPeople: newConnectedPeople,
        liveMaps: newLiveMapsWithoutUser
      }
    default:
      return state
  }
}

module.exports = reducer