[web] finish utils/list

This commit is contained in:
Jason 2016-06-23 00:17:35 +08:00
parent 42f433e395
commit a42512a1cc
3 changed files with 160 additions and 160 deletions

View File

@ -1,15 +1,17 @@
import { fetchApi as fetch } from '../utils'
import { CMD_RESET as WS_CMD_RESET } from './websocket'
import reduceList, * as listActions from './utils/list'
export const TOGGLE_FILTER = 'EVENTLOG_TOGGLE_FILTER'
export const TOGGLE_VISIBILITY = 'EVENTLOG_TOGGLE_VISIBILITY'
export const TOGGLE_FILTER = 'EVENTLOG_TOGGLE_FILTER'
export const ADD = 'EVENTLOG_ADD'
export const UPDATE = 'EVENTLOG_UPDATE'
export const WS_MSG = 'EVENTLOG_WS_MSG'
export const REQUEST = 'EVENTLOG_REQUEST'
export const RECEIVE = 'EVENTLOG_RECEIVE'
export const ERROR = 'EVENTLOG_ERROR'
const defaultState = {
logId: 0,
visible: false,
filters: { debug: false, info: true, web: true },
list: reduceList(undefined, { type: Symbol('EVENTLOG_INIT_LIST') })
@ -32,19 +34,36 @@ export default function reduce(state = defaultState, action) {
case ADD:
return {
...state,
list: reduceList(state.list, listActions.add({ message: action.message, level: action.level }))
logId: state.logId + 1,
list: reduceList(state.list, listActions.add({
id: `log-${state.logId}`,
message: action.message,
level: action.level,
}))
}
case UPDATE:
case WS_MSG:
return {
...state,
list: reduceList(state.list, listActions.update(action))
list: reduceList(state.list, listActions.handleWsMsg(action.msg))
}
case REQUEST:
return {
...state,
list: reduceList(state.list, listActions.request())
}
case RECEIVE:
return {
...state,
list: reduceList(state.list, listActions.reset(action.list))
list: reduceList(state.list, listActions.receive(action.list))
}
case FETCH_ERROR:
return {
...state,
list: reduceList(state.list, listActions.fetchError(action.error))
}
default:
@ -84,14 +103,7 @@ export function handleWsMsg(msg) {
if (msg.cmd === WS_CMD_RESET) {
return fetch()
}
return update(msg.cmd, msg.data)
}
/**
* @private
*/
export function update(cmd, data) {
return { type: UPDATE, cmd, data }
return { type: WS_MSG, msg }
}
/**

View File

@ -1,166 +1,150 @@
import {fetchApi} from "../../utils"
import * as websocketActions from './websocket'
export const ADD = "ADD"
export const UPDATE = "UPDATE"
export const REMOVE = "REMOVE"
export const REQUEST_LIST = "REQUEST_LIST"
export const RECEIVE_LIST = "RECEIVE_LIST"
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 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 = {
list: [],
isFetching: false,
actionsDuringFetch: [],
raw: [],
data: [],
byId: {},
indexOf: {},
isFetching: false,
[SYM_FILTER]: () => true,
[SYM_SORTER]: () => 0,
[SYM_PENDING]: [],
}
export default function makeList(actionType, fetchURL) {
function reduceList(state = defaultState, action = {}) {
if (action.type !== actionType) {
return state
export default function reduce(state = defaultState, action) {
if (state.isFetching && action.type !== RECEIVE) {
return {
...state,
[SYM_PENDING]: [...state[SYM_PENDING], action]
}
}
// 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)
}
return s
} else if (state.isFetching) {
switch (action.type) {
case UPDATE_FILTER:
return {
...state,
actionsDuringFetch: [...state.actionsDuringFetch, action]
[SYM_FILTER]: action.filter,
data: state.raw.filter(action.filter).sort(state[SYM_SORTER]),
}
}
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
return {
...state,
list,
byId: {...state.byId, [action.item.id]: action.item},
}
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 UPDATE_SORTER:
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 },
}
case REQUEST:
return {
...state,
isFetching: true,
}
case RECEIVE:
return {
...state,
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])),
}
default:
return state
}
}
function requestList() {
return {
type: actionType,
cmd: REQUEST_LIST,
}
export function updateFilter(filter) {
return { type: UPDATE_FILTER, filter }
}
export function updateSorter(sorter) {
return { type: UPDATE_SORTER, sorter }
}
export function add(item) {
return { type: ADD, item }
}
export function update(id, item) {
return { type: UPDATE, id, item }
}
export function remove(id) {
return { type: REMOVE, id }
}
export function handleWsMsg(msg) {
switch (msg.cmd) {
case websocketActions.CMD_ADD:
return add(msg.data)
case websocketActions.CMD_UPDATE:
return update(msg.data.id, msg.data)
case websocketActions.CMD_REMOVE:
return remove(msg.data.id)
default:
return { type: UNKNOWN_CMD, msg }
}
}
function receiveList(list) {
return {
type: actionType,
cmd: RECEIVE_LIST,
list
}
}
export function request() {
return { type: REQUEST }
}
function fetchList() {
return dispatch => {
export function receive(list) {
return { type: RECEIVE, list }
}
dispatch(requestList())
return fetchApi(fetchURL).then(response => {
return response.json().then(json => {
dispatch(receiveList(json.data))
})
})
}
}
return {reduceList, updateList, fetchList, addItem, updateItem, removeItem,}
}
export function fetchError(error) {
return { type: FETCH_ERROR, error }
}

View File

@ -1,6 +1,10 @@
const CONNECTED = 'WEBSOCKET_CONNECTED'
const DISCONNECTED = 'WEBSOCKET_DISCONNECTED'
export const CMD_ADD = 'add'
export const CMD_UPDATE = 'update'
export const CMD_REMOVE = 'remove'
export const CMD_RESET = 'reset'
const defaultState = {
connected: false,