mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-25 18:03:50 +00:00
web: simplify flow storage
This commit is contained in:
parent
85476d9915
commit
c2a130dced
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
|
@ -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 => (
|
||||
<th className={classnames(Column.headerClass, sortColumn === Column.name && sortType)}
|
||||
key={Column.name}
|
||||
onClick={() => updateSort(Column.name, Column.name !== sortColumn ? false : !sortDesc)}>
|
||||
onClick={() => setSort(Column.name, Column.name !== sortColumn ? false : !sortDesc)}>
|
||||
{Column.headerName}
|
||||
</th>
|
||||
))}
|
||||
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
}
|
@ -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 })
|
||||
}
|
||||
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
|
@ -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:
|
||||
|
@ -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 }
|
||||
}
|
194
web/src/js/ducks/utils/store.js
Normal file
194
web/src/js/ducks/utils/store.js
Normal file
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user