aboutsummaryrefslogtreecommitdiffstats
path: root/web/src/js/ducks/utils/store.js
diff options
context:
space:
mode:
Diffstat (limited to 'web/src/js/ducks/utils/store.js')
-rw-r--r--web/src/js/ducks/utils/store.js194
1 files changed, 194 insertions, 0 deletions
diff --git a/web/src/js/ducks/utils/store.js b/web/src/js/ducks/utils/store.js
new file mode 100644
index 00000000..3252d697
--- /dev/null
+++ b/web/src/js/ducks/utils/store.js
@@ -0,0 +1,194 @@
+export const SET_FILTER = 'LIST_SET_FILTER'
+export const SET_SORT = 'LIST_SET_SORT'
+export const ADD = 'LIST_ADD'
+export const UPDATE = 'LIST_UPDATE'
+export const REMOVE = 'LIST_REMOVE'
+export const RECEIVE = 'LIST_RECEIVE'
+
+const defaultState = {
+ byId: {},
+ list: [],
+ listIndex: {},
+ view: [],
+ viewIndex: {},
+}
+
+export default function reduce(state = defaultState, action) {
+
+ let { byId, list, listIndex, view, viewIndex } = state
+
+ switch (action.type) {
+ case SET_FILTER:
+ view = list.filter(action.filter).sort(action.sort)
+ viewIndex = {}
+ view.forEach((item, index) => {
+ viewIndex[item.id] = index
+ })
+ break
+
+ case SET_SORT:
+ view = [...view].sort(action.sort)
+ viewIndex = {}
+ view.forEach((item, index) => {
+ viewIndex[item.id] = index
+ })
+ break
+
+ case ADD:
+ if (action.item.id in byId) {
+ // we already had that.
+ break
+ }
+ byId = { ...byId, [action.item.id]: action.item }
+ listIndex = { ...listIndex, [action.item.id]: list.length }
+ list = [...list, action.item]
+ if (action.filter(action.item)) {
+ ({ view, viewIndex } = sortedInsert(state, action.item, action.sort))
+ }
+ break
+
+ case UPDATE:
+ byId = { ...byId, [action.item.id]: action.item }
+ list = [...list]
+ list[listIndex[action.item.id]] = action.item
+
+ let hasOldItem = action.item.id in viewIndex
+ let hasNewItem = action.filter(action.item)
+ if (hasNewItem && !hasOldItem) {
+ ({view, viewIndex} = sortedInsert(state, action.item, action.sort))
+ }
+ else if (!hasNewItem && hasOldItem) {
+ ({data: view, dataIndex: viewIndex} = removeData(view, viewIndex, action.item.id))
+ }
+ else if (hasNewItem && hasOldItem) {
+ ({view, viewIndex} = sortedUpdate(state, action.item, action.sort))
+ }
+ break
+
+ case REMOVE:
+ if (!(action.id in byId)) {
+ break
+ }
+ delete byId[action.id];
+ ({data: list, dataIndex: listIndex} = removeData(list, listIndex, action.id))
+
+ if (action.id in viewIndex) {
+ ({data: view, dataIndex: viewIndex} = removeData(view, viewIndex, action.id))
+ }
+ break
+
+ case RECEIVE:
+ list = action.list
+ listIndex = {}
+ byId = {}
+ list.forEach((item, i) => {
+ byId[item.id] = item
+ listIndex[item.id] = i
+ })
+ view = list.filter(action.filter).sort(action.sort)
+ viewIndex = {}
+ view.forEach((item, index) => {
+ viewIndex[item.id] = index
+ })
+ break
+ }
+ return { byId, list, listIndex, view, viewIndex }
+}
+
+
+export function setFilter(filter = defaultFilter, sort = defaultSort) {
+ return { type: SET_FILTER, filter, sort }
+}
+
+export function setSort(sort = defaultSort) {
+ return { type: SET_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.view, item, sort)
+ const view = [...state.view]
+ const viewIndex = { ...state.viewIndex }
+
+ view.splice(index, 0, item)
+ for (let i = view.length - 1; i >= index; i--) {
+ viewIndex[view[i].id] = i
+ }
+
+ return { view, viewIndex }
+}
+
+function removeData(currentData, currentDataIndex, id) {
+ const index = currentDataIndex[id]
+ const data = [...currentData]
+ const dataIndex = { ...currentDataIndex }
+ delete dataIndex[id];
+
+ data.splice(index, 1)
+ for (let i = data.length - 1; i >= index; i--) {
+ dataIndex[data[i].id] = i
+ }
+
+ return { data, dataIndex }
+}
+
+function sortedUpdate(state, item, sort) {
+ let view = [...state.view]
+ let viewIndex = { ...state.viewIndex }
+ let index = viewIndex[item.id]
+ view[index] = item
+ while (index + 1 < view.length && sort(view[index], view[index + 1]) > 0) {
+ view[index] = view[index + 1]
+ view[index + 1] = item
+ viewIndex[item.id] = index + 1
+ viewIndex[view[index].id] = index
+ ++index
+ }
+ while (index > 0 && sort(view[index], view[index - 1]) < 0) {
+ view[index] = view[index - 1]
+ view[index - 1] = item
+ viewIndex[item.id] = index - 1
+ viewIndex[view[index].id] = index
+ --index
+ }
+ return { view, viewIndex }
+}
+
+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
+}