import _ from 'lodash' export const UPDATE_FILTER = 'VIEW_UPDATE_FILTER' export const UPDATE_SORT = 'VIEW_UPDATE_SORT' export const ADD = 'VIEW_ADD' export const UPDATE = 'VIEW_UPDATE' export const REMOVE = 'VIEW_REMOVE' export const RECEIVE = 'VIEW_RECEIVE' const defaultState = { data: [], indexOf: {}, } export default function reduce(state = defaultState, action) { switch (action.type) { case UPDATE_FILTER: { const data = action.list.filter(action.filter).sort(action.sort) return { ...state, data, indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), } } case UPDATE_SORT: { const data = [...state.data].sort(action.sort) return { ...state, data, indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), } } case ADD: if (state.indexOf[action.item.id] != null || !action.filter(action.item)) { return state } return { ...state, ...sortedInsert(state, action.item, action.sort), } case REMOVE: if (state.indexOf[action.id] == null) { return state } return { ...state, ...sortedRemove(state, action.id), } case UPDATE: let hasOldItem = state.indexOf[action.item.id] !== null && state.indexOf[action.item.id] !== undefined let hasNewItem = action.filter(action.item) if (!hasNewItem && !hasOldItem) { return state } if (hasNewItem && !hasOldItem) { return { ...state, ...sortedInsert(state, action.item, action.sort) } } if (!hasNewItem && hasOldItem) { return { ...state, ...sortedRemove(state, action.item.id) } } if (hasNewItem && hasOldItem) { return { ...state, ...sortedUpdate(state, action.item, action.sort), } } case RECEIVE: { const data = action.list.filter(action.filter).sort(action.sort) return { ...state, data, indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), } } default: return state } } export function updateFilter(list, filter = defaultFilter, sort = defaultSort) { return { type: UPDATE_FILTER, list, filter, sort } } export function updateSort(sort = defaultSort) { return { type: UPDATE_SORT, sort } } export function add(item, filter = defaultFilter, sort = defaultSort) { return { type: ADD, item, filter, sort } } export function update(item, filter = defaultFilter, sort = defaultSort) { return { type: UPDATE, item, filter, sort } } export function remove(id) { return { type: REMOVE, id } } export function receive(list, filter = defaultFilter, sort = defaultSort) { return { type: RECEIVE, list, filter, sort } } function sortedInsert(state, item, sort) { const index = sortedIndex(state.data, item, sort) const data = [ ...state.data ] const indexOf = { ...state.indexOf } data.splice(index, 0, item) for (let i = data.length - 1; i >= index; i--) { indexOf[data[i].id] = i } return { data, indexOf } } function sortedRemove(state, id) { const index = state.indexOf[id] const data = [...state.data] const indexOf = { ...state.indexOf, [id]: null } data.splice(index, 1) for (let i = data.length - 1; i >= index; i--) { indexOf[data[i].id] = i } return { data, indexOf } } function sortedUpdate(state, item, sort) { let data = [ ...state.data ] let indexOf = { ...state.indexOf } let index = indexOf[item.id] data[index] = item while (index + 1 < data.length && sort(data[index], data[index + 1]) > 0) { data[index] = data[index + 1] data[index + 1] = item indexOf[item.id] = index + 1 indexOf[data[index].id] = index ++index } while (index > 0 && sort(data[index], data[index - 1]) < 0) { data[index] = data[index - 1] data[index - 1] = item indexOf[item.id] = index - 1 indexOf[data[index].id] = index --index } return { data, indexOf } } function sortedIndex(list, item, sort) { let low = 0 let high = list.length while (low < high) { const middle = (low + high) >>> 1 if (sort(item, list[middle]) >= 0) { low = middle + 1 } else { high = middle } } return low } function defaultFilter() { return true } function defaultSort(a, b) { return 0 }