diff --git a/mitmproxy/tools/web/app.py b/mitmproxy/tools/web/app.py
index 7788497e1..e5f74af3b 100644
--- a/mitmproxy/tools/web/app.py
+++ b/mitmproxy/tools/web/app.py
@@ -251,7 +251,7 @@ class FlowHandler(RequestHandler):
elif k == "port":
request.port = int(v)
elif k == "headers":
- request.headers.set_state(v)
+ request.headers.set_state((a.encode(), b.encode()) for a, b in v)
elif k == "content":
request.text = v
else:
diff --git a/mitmproxy/tools/web/master.py b/mitmproxy/tools/web/master.py
index 605cdf18b..e35815ad4 100644
--- a/mitmproxy/tools/web/master.py
+++ b/mitmproxy/tools/web/master.py
@@ -21,7 +21,8 @@ class Stop(Exception):
class _WebState():
def add_log(self, e, level):
- self._last_event_id += 1
+ # server-side log ids are odd
+ self._last_event_id += 2
entry = {
"id": self._last_event_id,
"message": e,
diff --git a/web/src/js/backends/websocket.js b/web/src/js/backends/websocket.js
index aa890bb74..40dde0d74 100644
--- a/web/src/js/backends/websocket.js
+++ b/web/src/js/backends/websocket.js
@@ -48,9 +48,9 @@ export default class WebsocketBackend {
}
}
- receive(resource, msg) {
+ receive(resource, data) {
let type = `${resource}_RECEIVE`.toUpperCase()
- this.store.dispatch({ type, [resource]: msg })
+ this.store.dispatch({ type, cmd: "receive", resource, data })
let queue = this.activeFetches[resource]
delete this.activeFetches[resource]
queue.forEach(msg => this.onMessage(msg))
diff --git a/web/src/js/components/FlowTable/FlowTableHead.jsx b/web/src/js/components/FlowTable/FlowTableHead.jsx
index 50242737c..b201285f2 100644
--- a/web/src/js/components/FlowTable/FlowTableHead.jsx
+++ b/web/src/js/components/FlowTable/FlowTableHead.jsx
@@ -3,15 +3,15 @@ import { connect } from 'react-redux'
import classnames from 'classnames'
import columns from './FlowColumns'
-import { updateSort } from '../../ducks/flowView'
+import { setSort } from '../../ducks/flows'
FlowTableHead.propTypes = {
- updateSort: PropTypes.func.isRequired,
+ setSort: PropTypes.func.isRequired,
sortDesc: React.PropTypes.bool.isRequired,
sortColumn: React.PropTypes.string,
}
-function FlowTableHead({ sortColumn, sortDesc, updateSort }) {
+function FlowTableHead({ sortColumn, sortDesc, setSort }) {
const sortType = sortDesc ? 'sort-desc' : 'sort-asc'
return (
@@ -19,7 +19,7 @@ function FlowTableHead({ sortColumn, sortDesc, updateSort }) {
{columns.map(Column => (
updateSort(Column.name, Column.name !== sortColumn ? false : !sortDesc)}>
+ onClick={() => setSort(Column.name, Column.name !== sortColumn ? false : !sortDesc)}>
{Column.headerName}
|
))}
@@ -29,10 +29,10 @@ function FlowTableHead({ sortColumn, sortDesc, updateSort }) {
export default connect(
state => ({
- sortDesc: state.flowView.sort.desc,
- sortColumn: state.flowView.sort.column,
+ sortDesc: state.flows.sort.desc,
+ sortColumn: state.flows.sort.column,
}),
{
- updateSort
+ setSort
}
)(FlowTableHead)
diff --git a/web/src/js/components/Header/MainMenu.jsx b/web/src/js/components/Header/MainMenu.jsx
index 7236d31f7..5ab3fa9d9 100644
--- a/web/src/js/components/Header/MainMenu.jsx
+++ b/web/src/js/components/Header/MainMenu.jsx
@@ -2,7 +2,7 @@ import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import FilterInput from './FilterInput'
import { update as updateSettings } from '../../ducks/settings'
-import { updateFilter, updateHighlight } from '../../ducks/flowView'
+import { setFilter, setHighlight } from '../../ducks/flows'
MainMenu.title = "Start"
@@ -31,20 +31,20 @@ const InterceptInput = connect(
const FlowFilterInput = connect(
state => ({
- value: state.flowView.filter || '',
+ value: state.flows.filter || '',
placeholder: 'Search',
type: 'search',
color: 'black'
}),
- { onChange: updateFilter }
+ { onChange: setFilter }
)(FilterInput);
const HighlightInput = connect(
state => ({
- value: state.flowView.highlight || '',
+ value: state.flows.highlight || '',
placeholder: 'Highlight',
type: 'tag',
color: 'hsl(48, 100%, 50%)'
}),
- { onChange: updateHighlight }
+ { onChange: setHighlight }
)(FilterInput);
diff --git a/web/src/js/components/MainView.jsx b/web/src/js/components/MainView.jsx
index 8be6f21cf..5c9a2d30f 100644
--- a/web/src/js/components/MainView.jsx
+++ b/web/src/js/components/MainView.jsx
@@ -4,7 +4,6 @@ import Splitter from './common/Splitter'
import FlowTable from './FlowTable'
import FlowView from './FlowView'
import * as flowsActions from '../ducks/flows'
-import { updateFilter, updateHighlight } from '../ducks/flowView'
class MainView extends Component {
@@ -41,16 +40,14 @@ class MainView extends Component {
export default connect(
state => ({
- flows: state.flowView.data,
- filter: state.flowView.filter,
- highlight: state.flowView.highlight,
+ flows: state.flows.view,
+ filter: state.flows.filter,
+ highlight: state.flows.highlight,
selectedFlow: state.flows.byId[state.flows.selected[0]],
tab: state.ui.flow.tab,
}),
{
selectFlow: flowsActions.select,
- updateFilter,
- updateHighlight,
updateFlow: flowsActions.update,
}
)(MainView)
diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js
index 76572a5d3..776e4b08f 100644
--- a/web/src/js/ducks/eventLog.js
+++ b/web/src/js/ducks/eventLog.js
@@ -1,19 +1,15 @@
-import reduceList, * as listActions from './utils/list'
-import reduceView, * as viewActions from './utils/view'
+import reduceStore from "./utils/store"
+import * as storeActions from "./utils/store"
export const ADD = 'EVENTS_ADD'
export const RECEIVE = 'EVENTS_RECEIVE'
export const TOGGLE_VISIBILITY = 'EVENTS_TOGGLE_VISIBILITY'
export const TOGGLE_FILTER = 'EVENTS_TOGGLE_FILTER'
-export const UNKNOWN_CMD = 'EVENTS_UNKNOWN_CMD'
-export const FETCH_ERROR = 'EVENTS_FETCH_ERROR'
const defaultState = {
- logId: 0,
visible: false,
filters: { debug: false, info: true, web: true },
- list: reduceList(undefined, {}),
- view: reduceView(undefined, {}),
+ ...reduceStore(undefined, {}),
}
export default function reduce(state = defaultState, action) {
@@ -30,27 +26,14 @@ export default function reduce(state = defaultState, action) {
return {
...state,
filters,
- view: reduceView(state.view, viewActions.updateFilter(state.list.data, log => filters[log.level])),
+ ...reduceStore(state, storeActions.setFilter(log => filters[log.level]))
}
case ADD:
- const item = {
- id: state.logId,
- message: action.message,
- level: action.level,
- }
- return {
- ...state,
- logId: state.logId + 1,
- list: reduceList(state.list, listActions.add(item)),
- view: reduceView(state.view, viewActions.add(item, log => state.filters[log.level])),
- }
-
case RECEIVE:
return {
...state,
- list: reduceList(state.list, listActions.receive(action.events)),
- view: reduceView(state.view, viewActions.receive(action.events, log => state.filters[log.level])),
+ ...reduceStore(state, storeActions[action.cmd](action.data, log => state.filters[log.level]))
}
default:
@@ -66,6 +49,17 @@ export function toggleVisibility() {
return { type: TOGGLE_VISIBILITY }
}
+let logId = 1 // client-side log ids are odd
export function add(message, level = 'web') {
- return { type: ADD, message, level }
+ let data = {
+ id: logId,
+ message,
+ level,
+ }
+ logId += 2
+ return {
+ type: ADD,
+ cmd: "add",
+ data
+ }
}
diff --git a/web/src/js/ducks/flowView.js b/web/src/js/ducks/flowView.js
deleted file mode 100644
index 1af96573e..000000000
--- a/web/src/js/ducks/flowView.js
+++ /dev/null
@@ -1,192 +0,0 @@
-import reduceView, * as viewActions from './utils/view'
-import * as flowActions from './flows'
-import Filt from '../filt/filt'
-import { RequestUtils } from '../flow/utils'
-
-export const UPDATE_FILTER = 'FLOWVIEW_UPDATE_FILTER'
-export const UPDATE_SORT = 'FLOWVIEW_UPDATE_SORT'
-export const UPDATE_HIGHLIGHT = 'FLOWVIEW_UPDATE_HIGHLIGHT'
-
-
-const sortKeyFuns = {
-
- TLSColumn: flow => flow.request.scheme,
-
- PathColumn: flow => RequestUtils.pretty_url(flow.request),
-
- MethodColumn: flow => flow.request.method,
-
- StatusColumn: flow => flow.response && flow.response.status_code,
-
- TimeColumn: flow => flow.response && flow.response.timestamp_end - flow.request.timestamp_start,
-
- SizeColumn: flow => {
- let total = flow.request.contentLength
- if (flow.response) {
- total += flow.response.contentLength || 0
- }
- return total
- },
-}
-
-export function makeFilter(filter) {
- if (!filter) {
- return
- }
- return Filt.parse(filter)
-}
-
-export function makeSort({ column, desc }) {
- const sortKeyFun = sortKeyFuns[column]
- if (!sortKeyFun) {
- return
- }
- 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
- }
-}
-
-
-const defaultState = {
- highlight: null,
- filter: null,
- sort: { column: null, desc: false },
- ...reduceView(undefined, {})
-}
-
-export default function reduce(state = defaultState, action) {
- switch (action.type) {
-
- case UPDATE_HIGHLIGHT:
- return {
- ...state,
- highlight: action.highlight,
- }
-
- case UPDATE_FILTER:
- return {
- ...reduceView(
- state,
- viewActions.updateFilter(
- action.flows,
- makeFilter(action.filter),
- makeSort(state.sort)
- )
- ),
- filter: action.filter,
- }
-
- case UPDATE_SORT:
- const sort = { column: action.column, desc: action.desc }
- return {
- ...reduceView(
- state,
- viewActions.updateSort(
- makeSort(sort)
- )
- ),
- sort,
- }
-
- case flowActions.ADD:
- return {
- ...reduceView(
- state,
- viewActions.add(
- action.item,
- makeFilter(state.filter),
- makeSort(state.sort)
- )
- ),
- }
-
- case flowActions.UPDATE:
- return {
- ...reduceView(
- state,
- viewActions.update(
- action.item,
- makeFilter(state.filter),
- makeSort(state.sort)
- )
- ),
- }
-
- case flowActions.REMOVE:
- /* FIXME: Implement select switch on remove
- return (dispatch, getState) => {
- let currentIndex = getState().flowView.indexOf[getState().flows.selected[0]]
- let maxIndex = getState().flowView.data.length - 1
- let deleteLastEntry = maxIndex == 0
- if (deleteLastEntry)
- dispatch(select())
- else
- dispatch(selectRelative(currentIndex == maxIndex ? -1 : 1) )
- */
- return {
- ...reduceView(
- state,
- viewActions.remove(
- action.id
- )
- ),
- }
-
- case flowActions.RECEIVE:
- return {
- ...reduceView(
- state,
- viewActions.receive(
- action.flows,
- makeFilter(state.filter),
- makeSort(state.sort)
- )
- ),
- }
-
- default:
- return {
- ...reduceView(state, action),
- }
- }
-}
-
-export function updateFilter(filter) {
- return (dispatch, getState) => {
- dispatch({ type: UPDATE_FILTER, filter, flows: getState().flows.data })
- }
-}
-
-export function updateHighlight(highlight) {
- return { type: UPDATE_HIGHLIGHT, highlight }
-}
-
-export function updateSort(column, desc) {
- return { type: UPDATE_SORT, column, desc }
-}
-
-export function selectRelative(shift) {
- return (dispatch, getState) => {
- let currentSelectionIndex = getState().flowView.indexOf[getState().flows.selected[0]]
- let minIndex = 0
- let maxIndex = getState().flowView.data.length - 1
- let newIndex
- if (currentSelectionIndex === undefined) {
- newIndex = (shift < 0) ? minIndex : maxIndex
- } else {
- newIndex = currentSelectionIndex + shift
- newIndex = Math.max(newIndex, minIndex)
- newIndex = Math.min(newIndex, maxIndex)
- }
- let flow = getState().flowView.data[newIndex]
- dispatch(flowActions.select(flow ? flow.id : undefined))
- }
-}
diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js
index 519b5e7df..3375e4bd8 100644
--- a/web/src/js/ducks/flows.js
+++ b/web/src/js/ducks/flows.js
@@ -1,47 +1,58 @@
-import { fetchApi } from '../utils'
-import reduceList, * as listActions from './utils/list'
-import { selectRelative } from './flowView'
+import { fetchApi } from "../utils"
+import reduceStore from "./utils/store"
+import * as storeActions from "./utils/store"
+import Filt from "../filt/filt"
+import { RequestUtils } from "../flow/utils"
-export const ADD = 'FLOWS_ADD'
-export const UPDATE = 'FLOWS_UPDATE'
-export const REMOVE = 'FLOWS_REMOVE'
-export const RECEIVE = 'FLOWS_RECEIVE'
+export const ADD = 'FLOWS_ADD'
+export const UPDATE = 'FLOWS_UPDATE'
+export const REMOVE = 'FLOWS_REMOVE'
+export const RECEIVE = 'FLOWS_RECEIVE'
+export const SELECT = 'FLOWS_SELECT'
+export const SET_FILTER = 'FLOWS_SET_FILTER'
+export const SET_SORT = 'FLOWS_SET_SORT'
+export const SET_HIGHLIGHT = 'FLOWS_SET_HIGHLIGHT'
export const REQUEST_ACTION = 'FLOWS_REQUEST_ACTION'
-export const UNKNOWN_CMD = 'FLOWS_UNKNOWN_CMD'
-export const FETCH_ERROR = 'FLOWS_FETCH_ERROR'
-export const SELECT = 'FLOWS_SELECT'
const defaultState = {
+ highlight: null,
+ filter: null,
+ sort: { column: null, desc: false },
selected: [],
- ...reduceList(undefined, {}),
+ ...reduceStore(undefined, {})
}
export default function reduce(state = defaultState, action) {
switch (action.type) {
case ADD:
- return {
- ...state,
- ...reduceList(state, listActions.add(action.item)),
- }
-
case UPDATE:
- return {
- ...state,
- ...reduceList(state, listActions.update(action.item)),
- }
-
case REMOVE:
+ case RECEIVE:
+ // FIXME: Implement select switch for remove
+ let storeAction = storeActions[action.cmd](
+ action.data,
+ makeFilter(state.filter),
+ makeSort(state.sort)
+ )
return {
...state,
- ...reduceList(state, listActions.remove(action.id)),
+ ...reduceStore(state, storeAction)
}
- case RECEIVE:
+ case SET_FILTER:
return {
...state,
- ...reduceList(state, listActions.receive(action.flows)),
+ filter: action.filter,
+ ...reduceStore(state, storeActions.setFilter(makeFilter(action.filter), makeSort(state.sort)))
+ }
+
+ case SET_SORT:
+ return {
+ ...state,
+ sort: action.sort,
+ ...reduceStore(state, storeActions.setSort(makeSort(action.sort)))
}
case SELECT:
@@ -51,13 +62,88 @@ export default function reduce(state = defaultState, action) {
}
default:
- return {
- ...state,
- ...reduceList(state, action),
- }
+ return state
}
}
+
+const sortKeyFuns = {
+
+ TLSColumn: flow => flow.request.scheme,
+
+ PathColumn: flow => RequestUtils.pretty_url(flow.request),
+
+ MethodColumn: flow => flow.request.method,
+
+ StatusColumn: flow => flow.response && flow.response.status_code,
+
+ TimeColumn: flow => flow.response && flow.response.timestamp_end - flow.request.timestamp_start,
+
+ SizeColumn: flow => {
+ let total = flow.request.contentLength
+ if (flow.response) {
+ total += flow.response.contentLength || 0
+ }
+ return total
+ },
+}
+
+export function makeFilter(filter) {
+ if (!filter) {
+ return
+ }
+ return Filt.parse(filter)
+}
+
+export function makeSort({ column, desc }) {
+ const sortKeyFun = sortKeyFuns[column]
+ if (!sortKeyFun) {
+ return
+ }
+ 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
+ }
+}
+
+export function setFilter(filter) {
+ return { type: SET_FILTER, filter }
+}
+
+export function setHighlight(highlight) {
+ return { type: SET_HIGHLIGHT, highlight }
+}
+
+export function setSort(column, desc) {
+ return { type: SET_SORT, sort: { column, desc } }
+}
+
+export function selectRelative(shift) {
+ return (dispatch, getState) => {
+ let currentSelectionIndex = getState().flows.viewIndex[getState().flows.selected[0]]
+ let minIndex = 0
+ let maxIndex = getState().flows.view.length - 1
+ let newIndex
+ if (currentSelectionIndex === undefined) {
+ newIndex = (shift < 0) ? minIndex : maxIndex
+ } else {
+ newIndex = currentSelectionIndex + shift
+ newIndex = window.Math.max(newIndex, minIndex)
+ newIndex = window.Math.min(newIndex, maxIndex)
+ }
+ let flow = getState().flows.view[newIndex]
+ dispatch(select(flow ? flow.id : undefined))
+ }
+}
+
+
export function accept(flow) {
return dispatch => fetchApi(`/flows/${flow.id}/accept`, { method: 'POST' })
}
@@ -88,9 +174,9 @@ export function update(flow, data) {
export function uploadContent(flow, file, type) {
const body = new FormData()
- file = new Blob([file], {type: 'plain/text'})
+ file = new window.Blob([file], { type: 'plain/text' })
body.append('file', file)
- return dispatch => fetchApi(`/flows/${flow.id}/${type}/content`, {method: 'post', body} )
+ return dispatch => fetchApi(`/flows/${flow.id}/${type}/content`, { method: 'post', body })
}
diff --git a/web/src/js/ducks/index.js b/web/src/js/ducks/index.js
index b90b24ff0..753075fa0 100644
--- a/web/src/js/ducks/index.js
+++ b/web/src/js/ducks/index.js
@@ -1,18 +1,12 @@
import { combineReducers } from 'redux'
import eventLog from './eventLog'
-import websocket from './websocket'
import flows from './flows'
-import flowView from './flowView'
import settings from './settings'
import ui from './ui/index'
-import msgQueue from './msgQueue'
export default combineReducers({
eventLog,
- websocket,
flows,
- flowView,
settings,
ui,
- msgQueue,
})
diff --git a/web/src/js/ducks/settings.js b/web/src/js/ducks/settings.js
index 32afe3be4..a2e360de1 100644
--- a/web/src/js/ducks/settings.js
+++ b/web/src/js/ducks/settings.js
@@ -13,7 +13,7 @@ export default function reducer(state = defaultState, action) {
switch (action.type) {
case RECEIVE:
- return action.settings
+ return action.data
case UPDATE:
return {
diff --git a/web/src/js/ducks/ui/flow.js b/web/src/js/ducks/ui/flow.js
index 4a6d64cdc..b5f6f78bd 100644
--- a/web/src/js/ducks/ui/flow.js
+++ b/web/src/js/ducks/ui/flow.js
@@ -60,7 +60,7 @@ export default function reducer(state = defaultState, action) {
// There is no explicit "stop edit" event.
// We stop editing when we receive an update for
// the currently edited flow from the server
- if (action.item.id === state.modifiedFlow.id) {
+ if (action.data.id === state.modifiedFlow.id) {
return {
...state,
modifiedFlow: false,
diff --git a/web/src/js/ducks/ui/keyboard.js b/web/src/js/ducks/ui/keyboard.js
index 10c698535..7418eca96 100644
--- a/web/src/js/ducks/ui/keyboard.js
+++ b/web/src/js/ducks/ui/keyboard.js
@@ -1,5 +1,4 @@
import { Key } from '../../utils'
-import { selectRelative as selectFlowRelative } from '../flowView'
import { selectTab } from './flow'
import * as flowsActions from '../flows'
@@ -20,29 +19,29 @@ export function onKeyDown(e) {
switch (key) {
case Key.K:
case Key.UP:
- dispatch(selectFlowRelative(-1))
+ dispatch(flowsActions.selectRelative(-1))
break
case Key.J:
case Key.DOWN:
- dispatch(selectFlowRelative(+1))
+ dispatch(flowsActions.selectRelative(+1))
break
case Key.SPACE:
case Key.PAGE_DOWN:
- dispatch(selectFlowRelative(+10))
+ dispatch(flowsActions.selectRelative(+10))
break
case Key.PAGE_UP:
- dispatch(selectFlowRelative(-10))
+ dispatch(flowsActions.selectRelative(-10))
break
case Key.END:
- dispatch(selectFlowRelative(+1e10))
+ dispatch(flowsActions.selectRelative(+1e10))
break
case Key.HOME:
- dispatch(selectFlowRelative(-1e10))
+ dispatch(flowsActions.selectRelative(-1e10))
break
case Key.ESC:
diff --git a/web/src/js/ducks/utils/list.js b/web/src/js/ducks/utils/list.js
deleted file mode 100644
index 339fb921d..000000000
--- a/web/src/js/ducks/utils/list.js
+++ /dev/null
@@ -1,93 +0,0 @@
-import _ from 'lodash'
-
-export const ADD = 'LIST_ADD'
-export const UPDATE = 'LIST_UPDATE'
-export const REMOVE = 'LIST_REMOVE'
-export const RECEIVE = 'LIST_RECEIVE'
-
-const defaultState = {
- data: [],
- byId: {},
- indexOf: {},
-}
-
-export default function reduce(state = defaultState, action) {
- switch (action.type) {
-
- case ADD:
- return {
- ...state,
- data: [...state.data, action.item],
- byId: { ...state.byId, [action.item.id]: action.item },
- indexOf: { ...state.indexOf, [action.item.id]: state.data.length },
- }
-
- case UPDATE: {
- const index = state.indexOf[action.item.id]
-
- if (index == null) {
- return state
- }
-
- const data = [...state.data]
-
- data[index] = action.item
-
- return {
- ...state,
- data,
- byId: { ...state.byId, [action.item.id]: action.item }
- }
- }
-
- case REMOVE: {
- const index = state.indexOf[action.id]
-
- if (index == null) {
- return state
- }
-
- const data = [...state.data]
- const indexOf = { ...state.indexOf, [action.id]: null }
-
- data.splice(index, 1)
- for (let i = data.length - 1; i >= index; i--) {
- indexOf[data[i].id] = i
- }
-
- return {
- ...state,
- data,
- indexOf,
- byId: { ...state.byId, [action.id]: null },
- }
- }
-
- case RECEIVE:
- return {
- ...state,
- data: action.list,
- byId: _.fromPairs(action.list.map(item => [item.id, item])),
- indexOf: _.fromPairs(action.list.map((item, index) => [item.id, index])),
- }
-
- default:
- return state
- }
-}
-
-export function add(item) {
- return { type: ADD, item }
-}
-
-export function update(item) {
- return { type: UPDATE, item }
-}
-
-export function remove(id) {
- return { type: REMOVE, id }
-}
-
-export function receive(list) {
- return { type: RECEIVE, list }
-}
diff --git a/web/src/js/ducks/utils/store.js b/web/src/js/ducks/utils/store.js
new file mode 100644
index 000000000..3252d6979
--- /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
+}
diff --git a/web/src/js/ducks/utils/view.js b/web/src/js/ducks/utils/view.js
deleted file mode 100755
index 6bf0a63ea..000000000
--- a/web/src/js/ducks/utils/view.js
+++ /dev/null
@@ -1,189 +0,0 @@
-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
-}
diff --git a/web/src/js/urlState.js b/web/src/js/urlState.js
index 118211db8..77b393932 100644
--- a/web/src/js/urlState.js
+++ b/web/src/js/urlState.js
@@ -1,6 +1,5 @@
-import { select } from "./ducks/flows"
+import { select, setFilter, setHighlight } from "./ducks/flows"
import { selectTab } from "./ducks/ui/flow"
-import { updateFilter, updateHighlight } from "./ducks/flowView"
import { toggleVisibility } from "./ducks/eventLog"
const Query = {
@@ -10,7 +9,7 @@ const Query = {
};
function updateStoreFromUrl(store) {
- const [path, query] = window.location.hash.substr(1).split("?", 2)
+ const [path, query] = window.location.hash.substr(1).split("?", 2)
const path_components = path.substr(1).split("/")
if (path_components[0] === "flows") {
@@ -28,14 +27,14 @@ function updateStoreFromUrl(store) {
const [key, value] = x.split("=", 2)
switch (key) {
case Query.SEARCH:
- store.dispatch(updateFilter(value))
+ store.dispatch(setFilter(value))
break
case Query.HIGHLIGHT:
- store.dispatch(updateHighlight(value))
+ store.dispatch(setHighlight(value))
break
case Query.SHOW_EVENTLOG:
- if(!store.getState().eventLog.visible)
- store.dispatch(toggleVisibility(value))
+ if (!store.getState().eventLog.visible)
+ store.dispatch(toggleVisibility())
break
default:
console.error(`unimplemented query arg: ${x}`)
@@ -45,10 +44,10 @@ function updateStoreFromUrl(store) {
}
function updateUrlFromStore(store) {
- const state = store.getState()
- let query = {
- [Query.SEARCH]: state.flowView.filter,
- [Query.HIGHLIGHT]: state.flowView.highlight,
+ const state = store.getState()
+ let query = {
+ [Query.SEARCH]: state.flows.filter,
+ [Query.HIGHLIGHT]: state.flows.highlight,
[Query.SHOW_EVENTLOG]: state.eventLog.visible,
}
const queryStr = Object.keys(query)