From 5adb7a54fd689f69d5734d64fea0d3de43f4ce6d Mon Sep 17 00:00:00 2001 From: Jason Date: Thu, 23 Jun 2016 23:20:41 +0800 Subject: [web] separate views and list --- web/src/js/ducks/eventLog.js | 5 +- web/src/js/ducks/flows.js | 112 +++++++-------------------- web/src/js/ducks/utils/list.js | 129 +++++++++++--------------------- web/src/js/ducks/utils/view.js | 139 ++++++++++++++++++++++++++++++++++ web/src/js/ducks/views.js | 35 +++++++++ web/src/js/ducks/views/main.js | 166 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 413 insertions(+), 173 deletions(-) create mode 100755 web/src/js/ducks/utils/view.js create mode 100755 web/src/js/ducks/views.js create mode 100755 web/src/js/ducks/views/main.js (limited to 'web/src/js/ducks') diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js index 4eba8b54..61b5882f 100644 --- a/web/src/js/ducks/eventLog.js +++ b/web/src/js/ducks/eventLog.js @@ -16,7 +16,8 @@ const defaultState = { logId: 0, visible: false, filters: { debug: false, info: true, web: true }, - list: reduceList(undefined, { type: Symbol('EVENTLOG_INIT_LIST') }) + list: reduceList(undefined, { type: Symbol('EVENTLOG_INIT_LIST') }), + view: reduceView(undefined, viewActions.init([])) } export default function reduce(state = defaultState, action) { @@ -30,7 +31,7 @@ export default function reduce(state = defaultState, action) { return { ...state, filters, - list: reduceList(state.list, listActions.updateFilter(e => filters[e.level])) + view: reduceView(state.list, listActions.updateFilter(e => filters[e.level], state.list)) } case ADD: diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js index 5825efa1..4111c8bc 100644 --- a/web/src/js/ducks/flows.js +++ b/web/src/js/ducks/flows.js @@ -1,124 +1,66 @@ -import { fetchApi as fetch } from '../utils' -import { CMD_RESET as WS_CMD_RESET } from './websocket' import reduceList, * as listActions from './utils/list' +import reduceViews, * as viewsActions from './views' -export const WS_MSG_TYPE ='UPDATE_FLOWS' +export const WS_MSG_TYPE = 'UPDATE_FLOWS' -export const UPDATE_FILTER = 'FLOWS_UPDATE_FLOW_FILTER' -export const UPDATE_HIGHLIGHT = 'FLOWS_UPDATE_FLOW_HIGHLIGHT' -export const UPDATE_SORT = 'FLOWS_UPDATE_FLOW_SORT' -export const WS_MSG = 'FLOWS_WS_MSG' -export const SELECT_FLOW = 'FLOWS_SELECT_FLOW' -export const REQUEST_ACTION = 'FLOWS_REQUEST_ACTION' +export const ADD = 'FLOWS_ADD' +export const UPDATE = 'FLOWS_UPDATE' +export const REMOVE = 'FLOWS_REMOVE' export const REQUEST = 'FLOWS_REQUEST' export const RECEIVE = 'FLOWS_RECEIVE' +export const WS_MSG = 'FLOWS_WS_MSG' +export const REQUEST_ACTION = 'FLOWS_REQUEST_ACTION' export const FETCH_ERROR = 'FLOWS_FETCH_ERROR' const defaultState = { - selected: [], - sorter: {}, - filter: null, - highlight: null, - list: reduceList(undefined, { type: Symbol('FLOWS_INIT_LIST') }), + list: null, + views: null, } export default function reduce(state = defaultState, action) { switch (action.type) { - case UPDATE_FILTER: + case ADD: return { ...state, - filter: action.filter, - list: reduceList(state.list, listActions.updateFilter(makeFilterFun(action.filter))), + list: reduceList(state.list, listActions.add(action.item)), + views: reduceViews(state.views, viewsActions.add(action.item)), } - case UPDATE_HIGHLIGHT: + case UPDATE: return { ...state, - highlight: action.highlight, + list: reduceList(state.list, listActions.update(action.item.id, action.item)), + views: reduceViews(state.views, viewsActions.update(action.item.id, action.item)), } - case UPDATE_SORTER: + case REMOVE: return { ...state, - sorter: { column: action.column, desc: action.desc }, - list: reduceList(state.list, listActions.updateSorter(makeSortFun(action.sortKeyFun, action.desc))), - } - - case SELECT_FLOW: - return { - ...state, - selected: [action.id], - } - - case WS_MSG: - return { - ...state, - list: reduceList(state.list, listActions.handleWsMsg(action.msg)), + list: reduceList(state.list, listActions.remove(action.item.id)), + views: reduceViews(state.views, viewsActions.remove(action.item.id)), } case REQUEST: return { ...state, - list: reduceList(state.list, listActions.request()) + list: reduceList(state.list, listActions.request()), } case RECEIVE: + const list = reduceList(state.list, listActions.receive(action.list)) return { ...state, - list: reduceList(state.list, listActions.receive(action.list)) + list, + views: reduceViews(state.views, viewsActions.receive(list)), } default: - return state - } -} - -function makeFilterFun(filter) { - return filter ? Filt.parse(filter) : () => true -} - -function makeSortFun(sortKeyFun, desc) { - return (a, b) => { - const ka = sortKeyFun(a) - const kb = sortKeyFun(b) - if (ka > kb) { - return desc ? -1 : 1 - } - if (ka < kb) { - return desc ? 1 : -1 - } - return 0 - } -} - -/** - * @public - */ -export function updateFilter(filter) { - return { type: UPDATE_FILTER, filter } -} - -/** - * @public - */ -export function updateHighlight(highlight) { - return { type: UPDATE_HIGHLIGHT, highlight } -} - -/** - * @public - */ -export function updateSorter(column, desc, sortKeyFun) { - return { type: UPDATE_SORTER, column, desc, sortKeyFun } -} - -/** - * @public - */ -export function select(id) { - return (dispatch, getState) => { - dispatch({ type: SELECT_FLOW, currentSelection: getState().flows.selected[0], id }) + return { + ...state, + list: reduceList(state.list, action), + views: reduceViews(state.views, action), + } } } diff --git a/web/src/js/ducks/utils/list.js b/web/src/js/ducks/utils/list.js index fd8ac106..b95a4527 100644 --- a/web/src/js/ducks/utils/list.js +++ b/web/src/js/ducks/utils/list.js @@ -1,99 +1,50 @@ +import _ from 'lodash' import * as websocketActions from '../websocket' -export const UPDATE_FILTER = 'LIST_UPDATE_FILTER' -export const UPDATE_SORTER = 'LIST_UPDATE_SORTER' -export const ADD = 'LIST_ADD' -export const UPDATE = 'LIST_UPDATE' -export const REMOVE = 'LIST_REMOVE' +export const SET = 'LIST_SET' +export const CLEAR = 'LIST_CLEAR' export const UNKNOWN_CMD = 'LIST_UNKNOWN_CMD' export const REQUEST = 'LIST_REQUEST' export const RECEIVE = 'LIST_RECEIVE' -export const FETCH_ERROR = 'LIST_FETCH_ERROR' -export const SYM_FILTER = Symbol('LIST_SYM_FILTER') -export const SYM_SORTER = Symbol('LIST_SYM_SORTER') -export const SYM_PENDING = Symbol('LIST_SYM_PENDING') - -// @todo add indexOf map if necessary const defaultState = { - raw: [], - data: [], - byId: {}, - isFetching: false, - [SYM_FILTER]: () => true, - [SYM_SORTER]: () => 0, - [SYM_PENDING]: [], + data: {}, + pendingActions: null, } export default function reduce(state = defaultState, action) { - if (state.isFetching && action.type !== RECEIVE) { + if (state.pendingActions && action.type !== RECEIVE) { return { ...state, - [SYM_PENDING]: [...state[SYM_PENDING], action] + pendingActions: [...state.pendingActions, action] } } switch (action.type) { - case UPDATE_FILTER: + case SET: return { ...state, - [SYM_FILTER]: action.filter, - data: state.raw.filter(action.filter).sort(state[SYM_SORTER]), + data: { ...state.data, [action.id]: null, [action.item.id]: action.item } } - case UPDATE_SORTER: + case CLEAR: return { ...state, - [SYM_SORTER]: action.sorter, - data: state.data.slice().sort(state[SYM_SORTER]), - } - - case ADD: - let data = state.data - if (state[SYM_FILTER](action.item)) { - data = [...state.data, action.item].sort(state[SYM_SORTER]) - } - return { - ...state, - data, - raw: [...state.raw, action.item], - byId: { ...state.byId, [action.item.id]: action.item }, - } - - case UPDATE: - // @todo optimize if necessary - const raw = state.raw.map(item => item.id === action.id ? action.item : item) - return { - ...state, - raw, - data: raw.filter(state[SYM_FILTER]).sort(state[SYM_SORTER]), - byId: { ...state.byId, [action.id]: null, [action.item.id]: action.item }, - } - - case REMOVE: - // @todo optimize if necessary - return { - ...state, - raw: state.raw.filter(item => item.id !== action.id), - data: state.data.filter(item => item.id !== action.id), - byId: { ...state.byId, [action.id]: null }, + data: { ...state.data, [action.id]: null } } case REQUEST: return { ...state, - isFetching: true, + pendingActions: [] } case RECEIVE: - return state[SYM_PENDING].reduce(reduce, { + return state.pendingActions.reduce(reduce, { ...state, - [SYM_PENDING]: [], - isFetching: false, - raw: action.list, - data: action.list.filter(state[SYM_FILTER]).sort(state[SYM_SORTER]), - byId: _.fromPairs(action.list.map(item => [item.id, item])), + pendingActions: null, + data: _.fromPairs(action.list.map(item => [item.id, item])), }) default: @@ -101,26 +52,44 @@ export default function reduce(state = defaultState, action) { } } -export function updateFilter(filter) { - return { type: UPDATE_FILTER, filter } +/** + * @public + */ +export function add(item) { + return { type: SET, id: item.id, item } } -export function updateSorter(sorter) { - return { type: UPDATE_SORTER, sorter } +/** + * @public + */ +export function update(id, item) { + return { type: SET, id, item } } -export function add(item) { - return { type: ADD, item } +/** + * @public + */ +export function remove(id) { + return { type: CLEAR, id } } -export function update(id, item) { - return { type: UPDATE, id, item } +/** + * @public + */ +export function request() { + return { type: REQUEST } } -export function remove(id) { - return { type: REMOVE, id } +/** + * @public + */ +export function receive(list) { + return { type: RECEIVE, list } } +/** + * @public websocket + */ export function handleWsMsg(msg) { switch (msg.cmd) { @@ -137,15 +106,3 @@ export function handleWsMsg(msg) { return { type: UNKNOWN_CMD, msg } } } - -export function request() { - return { type: REQUEST } -} - -export function receive(list) { - return { type: RECEIVE, list } -} - -export function fetchError(error) { - return { type: FETCH_ERROR, error } -} diff --git a/web/src/js/ducks/utils/view.js b/web/src/js/ducks/utils/view.js new file mode 100755 index 00000000..adf7fc6a --- /dev/null +++ b/web/src/js/ducks/utils/view.js @@ -0,0 +1,139 @@ +import _ from 'lodash' + +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 defaultState = { + data: [], + indexOf: {}, +} + +export default function reduce(state = defaultState, action) { + switch (action.type) { + + case UPDATE_FILTER: + const data = action.list.data.filter(action.filter).sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), + } + + case UPDATE_SORTER: + const data = state.data.slice().sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])) + } + + case ADD: + if (!action.filter(action.item)) { + return state + } + return { + ...state, + ...sortedInsert(state, action.item, action.sorter), + } + + case REMOVE: + return { + ...state, + ...sortedRemove(state, action.id), + } + + case UPDATE: + const nextState = { + ...state, + ...sortedRemove(state, action.id), + } + if (!action.filter(action.item)) { + return nextState + } + return { + ...nextState, + ...sortedInsert(nextState, action.item, action.sorter) + } + + case RECEIVE: + const data = action.list.data.filter(action.filter).sort(action.sorter) + return { + ...state, + data, + indexOf: _.fromPairs(data.map((item, index) => [item.id, index])), + } + + default: + return state + } +} + +export function updateFilter(list, filter, sorter) { + return { type: UPDATE_FILTER, list, filter, sorter } +} + +export function updateSorter(sorter) { + return { type: UPDATE_SORTER, sorter } +} + +export function add(item, filter, sorter) { + return { type: ADD, item, filter, sorter } +} + +export function update(id, item, filter, sorter) { + return { type: UPDATE, id, item, filter, sorter } +} + +export function remove(id) { + return { type: REMOVE, id } +} + +export function receive(list, filter, sorter) { + 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 + } + + 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 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 +} diff --git a/web/src/js/ducks/views.js b/web/src/js/ducks/views.js new file mode 100755 index 00000000..88b4d355 --- /dev/null +++ b/web/src/js/ducks/views.js @@ -0,0 +1,35 @@ +import { combineReducers } from 'redux' +import * as viewActions from './utils/view' +import main from './views/main.js' + +export default combineReducers({ + main, +}) + +/** + * @public + */ +export function add(item) { + return { type: ADD, item } +} + +/** + * @public + */ +export function update(id, item) { + return { type: UPDATE, id, item } +} + +/** + * @public + */ +export function remove(id) { + return { type: REMOVE, id } +} + +/** + * @public + */ +export function receive(list) { + return { type: RECEIVE, list } +} diff --git a/web/src/js/ducks/views/main.js b/web/src/js/ducks/views/main.js new file mode 100755 index 00000000..a9700584 --- /dev/null +++ b/web/src/js/ducks/views/main.js @@ -0,0 +1,166 @@ +import reduceView, * as viewActions from '../utils/list' +import * as viewsActions from '../views' + +export const UPDATE_FILTER = 'MAIN_VIEW_UPDATE_FILTER' +export const UPDATE_SORTER = 'MAIN_VIEW_UPDATE_SORTER' +export const UPDATE_HIGHLIGHT = 'MAIN_VIEW_UPDATE_HIGHLIGHT' +export const SELECT = 'MAIN_VIEW_SELECT' + +const defaultState = { + filter: null, + sorter: null, + highlight: null, + selected: [], + view: null, +} + +export default function reduce(state = defaultState, action) { + switch (action.type) { + + case UPDATE_HIGHLIGHT: + return { + ...state, + highlight: action.highlight, + } + + case SELECT: + return { + ...state, + selected: [action.id] + } + + case UPDATE_FILTER: + return { + ...state, + filter: action.filter, + view: reduceView( + state.view, + viewActions.updateFilter( + action.list, + makeFilter(action.filter), + makeSorter(state.sorter) + ) + ), + } + + case UPDATE_SORTER: + const sorter = { column: action.column, desc: action.desc } + return { + ...state, + sorter, + view: reduceView( + state.view, + viewActions.updateSorter( + makeSorter(sorter) + ) + ), + } + + case viewsActions.ADD: + return { + ...state, + view: reduceView( + state.view, + viewActions.add( + action.item, + makeFilter(state.filter), + makeSorter(state.sorter) + ) + ), + } + + case viewsActions.UPDATE: + return { + ...state, + view: reduceView( + state.view, + viewActions.update( + action.id, + action.item, + makeFilter(state.filter), + makeSorter(state.sorter) + ) + ), + } + + case viewsActions.REMOVE: + return { + ...state, + view: reduceView(state.view, viewActions.remove(action.id)), + } + + case viewsActions.RECEIVE: + return { + ...state, + view: reduceView( + state.view, + viewActions.receive( + action.list, + makeFilter(state.filter), + makeSorter(state.sorter) + ) + ), + } + + default: + return { + ...state, + view: reduceView(state.view, action) + } + } +} + +/** + * @public + */ +export function updateFilter(filter) { + return (dispatch, getState) => { + return { type: UPDATE_FILTER, filter, list: getState().flows.list } + } +} + +/** + * @public + */ +export function updateHighlight(highlight) { + return { type: UPDATE_HIGHLIGHT, highlight } +} + +/** + * @public + */ +export function updateSorter(column, desc) { + return { type: UPDATE_SORTER, column, desc } +} + +/** + * @public + */ +export function select(id) { + return { type: SELECT, currentSelection: getState().flows.views.main.selected[0], id } +} + +/** + * @private + */ +function makeFilter(filter) { + return filter ? Filt.parse(filter) : () => true +} + +/** + * @private + */ +function makeSorter(column, desc) { + const sortKeyFun = sortKeyFuns[column] + return (a, b) => { + const ka = sortKeyFun(a) + const kb = sortKeyFun(b) + if (ka > kb) { + return desc ? -1 : 1 + } + if (ka < kb) { + return desc ? 1 : -1 + } + return 0 + } +} -- cgit v1.2.3