diff options
Diffstat (limited to 'web/src/js/ducks/utils')
-rw-r--r-- | web/src/js/ducks/utils/list.js | 209 | ||||
-rwxr-xr-x[-rw-r--r--] | web/src/js/ducks/utils/view.js | 243 |
2 files changed, 200 insertions, 252 deletions
diff --git a/web/src/js/ducks/utils/list.js b/web/src/js/ducks/utils/list.js index a830fe99..e66a8549 100644 --- a/web/src/js/ducks/utils/list.js +++ b/web/src/js/ducks/utils/list.js @@ -1,166 +1,91 @@ -import {fetchApi} from "../../utils" - -export const ADD = "ADD" -export const UPDATE = "UPDATE" -export const REMOVE = "REMOVE" -export const REQUEST_LIST = "REQUEST_LIST" -export const RECEIVE_LIST = "RECEIVE_LIST" +import _ from 'lodash' +export const SET = 'LIST_SET' +export const CLEAR = 'LIST_CLEAR' +export const REQUEST = 'LIST_REQUEST' +export const RECEIVE = 'LIST_RECEIVE' const defaultState = { - list: [], - isFetching: false, - actionsDuringFetch: [], - byId: {}, - indexOf: {}, + data: {}, + pendingActions: null, } -export default function makeList(actionType, fetchURL) { - function reduceList(state = defaultState, action = {}) { - - if (action.type !== actionType) { - return state - } +export default function reduce(state = defaultState, action) { + switch (action.type) { - // Handle cases where we finished fetching or are still fetching. - if (action.cmd === RECEIVE_LIST) { - let s = { - isFetching: false, - actionsDuringFetch: [], - list: action.list, - byId: {}, - indexOf: {} - } - for (let i = 0; i < action.list.length; i++) { - let item = action.list[i] - s.byId[item.id] = item - s.indexOf[item.id] = i - } - for (action of state.actionsDuringFetch) { - s = reduceList(s, action) + case SET: + if (state.pendingActions) { + return { + ...state, + pendingActions: [...state.pendingActions, action] + } } - return s - } else if (state.isFetching) { return { ...state, - actionsDuringFetch: [...state.actionsDuringFetch, action] + data: { ...state.data, [action.id]: null, [action.item.id]: action.item } } - } - - let list, itemIndex - switch (action.cmd) { - case ADD: - return { - list: [...state.list, action.item], - byId: {...state.byId, [action.item.id]: action.item}, - indexOf: {...state.indexOf, [action.item.id]: state.list.length}, - } - - case UPDATE: - list = [...state.list] - itemIndex = state.indexOf[action.item.id] - list[itemIndex] = action.item + case CLEAR: + if (state.pendingActions) { return { ...state, - list, - byId: {...state.byId, [action.item.id]: action.item}, + pendingActions: [...state.pendingActions, action] } + } + return { + ...state, + data: { ...state.data, [action.id]: null } + } - case REMOVE: - list = [...state.list] - itemIndex = state.indexOf[action.item.id] - list.splice(itemIndex, 1) - return { - ...state, - list, - byId: {...state.byId, [action.item.id]: undefined}, - indexOf: {...state.indexOf, [action.item.id]: undefined}, - } - - case REQUEST_LIST: - return { - ...state, - isFetching: true - } - - default: - console.debug("unknown action", action) - return state - } - } - - function addItem(item) { - return { - type: actionType, - cmd: ADD, - item - } - } - - function updateItem(item) { - return { - type: actionType, - cmd: UPDATE, - item - } - } - - function removeItem(item) { - return { - type: actionType, - cmd: REMOVE, - item - } - } - - - function updateList(event) { - /* This action creater takes all WebSocket events */ - return dispatch => { - switch (event.cmd) { - case "add": - return dispatch(addItem(event.data)) - case "update": - return dispatch(updateItem(event.data)) - case "remove": - return dispatch(removeItem(event.data)) - case "reset": - return dispatch(fetchList()) - default: - console.error("unknown list update", event) + case REQUEST: + return { + ...state, + pendingActions: [] } - } - } - function requestList() { - return { - type: actionType, - cmd: REQUEST_LIST, - } - } + case RECEIVE: + return state.pendingActions.reduce(reduce, { + ...state, + pendingActions: null, + data: _.fromPairs(action.list.map(item => [item.id, item])), + }) - function receiveList(list) { - return { - type: actionType, - cmd: RECEIVE_LIST, - list - } + default: + return state } +} - function fetchList() { - return dispatch => { +/** + * @public + */ +export function add(item) { + return { type: SET, id: item.id, item } +} - dispatch(requestList()) +/** + * @public + */ +export function update(id, item) { + return { type: SET, id, item } +} - return fetchApi(fetchURL).then(response => { - return response.json().then(json => { - dispatch(receiveList(json.data)) - }) - }) - } - } +/** + * @public + */ +export function remove(id) { + return { type: CLEAR, id } +} +/** + * @public + */ +export function request() { + return { type: REQUEST } +} - return {reduceList, updateList, fetchList, addItem, updateItem, removeItem,} -}
\ No newline at end of file +/** + * @public + */ +export function receive(list) { + return { type: RECEIVE, list } +} diff --git a/web/src/js/ducks/utils/view.js b/web/src/js/ducks/utils/view.js index 01d57b17..3b552378 100644..100755 --- a/web/src/js/ducks/utils/view.js +++ b/web/src/js/ducks/utils/view.js @@ -1,134 +1,157 @@ -import {ADD, UPDATE, REMOVE, REQUEST_LIST, RECEIVE_LIST} from "./list" - -const defaultFilterFn = x => true -const defaultSortFn = false - -const makeCompareFn = sortFn => { - let compareFn = (a, b) => { - let akey = sortFn(a), - bkey = sortFn(b) - if (akey < bkey) { - return -1 - } else if (akey > bkey) { - return 1 - } else { - return 0 - } - } - // need to adjust sortedIndexOf as well - // if (sortFn.reverse) - // return (a, b) => compareFn(b, a) - return compareFn -} +import _ from 'lodash' -const sortedInsert = (list, sortFn, item) => { - let l = [...list, item] - l.indexOf = x => sortedIndexOf(l, x, sortFn) - let compareFn = makeCompareFn(sortFn) - - // only sort if sorting order is not correct yet - if (sortFn && compareFn(list[list.length - 1], item) > 0) { - // TODO: This is untested - console.debug("sorting view...") - l.sort(compareFn) - } - return l -} +export const UPDATE_FILTER = 'VIEW_UPDATE_FILTER' +export const UPDATE_SORTER = 'VIEW_UPDATE_SORTER' +export const ADD = 'VIEW_ADD' +export const UPDATE = 'VIEW_UPDATE' +export const REMOVE = 'VIEW_REMOVE' +export const RECEIVE = 'VIEW_RECEIVE' -const sortedRemove = (list, sortFn, item) => { - let itemId = item.id - let l = list.filter(x => x.id !== itemId) - l.indexOf = x => sortedIndexOf(l, x, sortFn) - return l +const defaultState = { + data: [], + indexOf: {}, } -export function sortedIndexOf(list, value, sortFn) { - if (!sortFn) { - sortFn = x => 0 // This triggers the linear search for flows that have the same sort value. - } +export default function reduce(state = defaultState, action) { + switch (action.type) { - let low = 0, - high = list.length, - val = sortFn(value), - mid; - while (low < high) { - mid = (low + high) >>> 1; - if (sortFn(list[mid]) < val) { - low = mid + 1 - } else { - high = mid + case UPDATE_FILTER: { + const data = _.values(action.list.data).filter(action.filter).sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), + } } - } - // Two flows may have the same sort value. - // we previously determined the leftmost flow with the same sort value, - // so no we need to scan linearly - while (list[low].id !== value.id && sortFn(list[low + 1]) === val) { - low++ - } - return low; -} + case UPDATE_SORTER: { + const data = [...state.data].sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])) + } + } -// for when the list changes -export function updateViewList(currentView, currentList, nextList, action, filterFn = defaultFilterFn, sortFn = defaultSortFn) { - switch (action.cmd) { - case REQUEST_LIST: - return currentView - case RECEIVE_LIST: - return updateViewFilter(nextList, filterFn, sortFn) case ADD: - if (filterFn(action.item)) { - return sortedInsert(currentView, sortFn, action.item) + if (state.indexOf[action.item.id] != null || !action.filter(action.item)) { + return state } - return currentView - case UPDATE: - // let's determine if it's in the view currently and if it should be in the view. - let currentItemState = currentList.byId[action.item.id], - nextItemState = action.item, - isInView = filterFn(currentItemState), - shouldBeInView = filterFn(nextItemState) - - if (!isInView && shouldBeInView) - return sortedInsert(currentView, sortFn, action.item) - if (isInView && !shouldBeInView) - return sortedRemove(currentView, sortFn, action.item) - if (isInView && shouldBeInView) { - let s = [...currentView] - s.indexOf = x => sortedIndexOf(s, x, sortFn) - s[s.indexOf(currentItemState)] = nextItemState - if (sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) - s.sort(makeCompareFn(sortFn)) - return s + return { + ...state, + ...sortedInsert(state, action.item, action.sorter), } - return currentView + case REMOVE: - let isInView_ = filterFn(currentList.byId[action.item.id]) - if (isInView_) { - return sortedRemove(currentView, sortFn, action.item) + if (state.indexOf[action.item.id] == null) { + return state + } + return { + ...state, + ...sortedRemove(state, action.id), + } + + case UPDATE: { + if (state.indexOf[action.item.id] == null) { + return + } + const nextState = { + ...state, + ...sortedRemove(state, action.id), } - return currentView + if (!action.filter(action.item)) { + return nextState + } + return { + ...nextState, + ...sortedInsert(nextState, action.item, action.sorter) + } + } + + case RECEIVE: { + const data = _.values(action.list.data).filter(action.filter).sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), + } + } + default: - console.error("Unknown list action: ", action) - return currentView + return state } } -export function updateViewFilter(list, filterFn = defaultFilterFn, sortFn = defaultSortFn) { - let filtered = list.list.filter(filterFn) - if (sortFn){ - filtered.sort(makeCompareFn(sortFn)) +export function updateFilter(list, filter = defaultFilter, sorter = defaultSorter) { + return { type: UPDATE_FILTER, list, filter, sorter } +} + +export function updateSorter(sorter = defaultSorter) { + return { type: UPDATE_SORTER, sorter } +} + +export function add(item, filter = defaultFilter, sorter = defaultSorter) { + return { type: ADD, item, filter, sorter } +} + +export function update(id, item, filter = defaultFilter, sorter = defaultSorter) { + return { type: UPDATE, id, item, filter, sorter } +} + +export function remove(id) { + return { type: REMOVE, id } +} + +export function receive(list, filter = defaultFilter, sorter = defaultSorter) { + return { type: RECEIVE, list, filter, sorter } +} + +function sortedInsert(state, item, sorter) { + const index = sortedIndex(state.data, item, sorter) + 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 } - filtered.indexOf = x => sortedIndexOf(filtered, x, sortFn) - return filtered + return { data, indexOf } } -export function updateViewSort(list, sortFn = defaultSortFn) { - let sorted = [...list] - if (sortFn) { - sorted.sort(makeCompareFn(sortFn)) +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 } - sorted.indexOf = x => sortedIndexOf(sorted, x, sortFn) - return sorted + return { data, indexOf } +} + +function sortedIndex(list, item, sorter) { + let low = 0 + let high = list.length + + while (low < high) { + const middle = (low + high) >>> 1 + if (sorter(item, list[middle]) > 0) { + low = middle + 1 + } else { + high = middle + } + } + + return low +} + +function defaultFilter() { + return true +} + +function defaultSorter(a, b) { + return 0 } |