mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-26 18:18:25 +00:00
web: completely move flow state to redux
This commit is contained in:
parent
e880f532ad
commit
d53a2de0ba
@ -0,0 +1,2 @@
|
|||||||
|
from mitmproxy.web import master
|
||||||
|
__all__ = ["master"]
|
File diff suppressed because it is too large
Load Diff
@ -8,12 +8,14 @@ import shallowEqual from "shallowequal";
|
|||||||
import AutoScroll from "./helpers/AutoScroll";
|
import AutoScroll from "./helpers/AutoScroll";
|
||||||
import {calcVScroll} from "./helpers/VirtualScroll";
|
import {calcVScroll} from "./helpers/VirtualScroll";
|
||||||
import flowtable_columns from "./flowtable-columns.js";
|
import flowtable_columns from "./flowtable-columns.js";
|
||||||
|
import Filt from "../filt/filt";
|
||||||
|
|
||||||
|
|
||||||
FlowRow.propTypes = {
|
FlowRow.propTypes = {
|
||||||
selectFlow: React.PropTypes.func.isRequired,
|
selectFlow: React.PropTypes.func.isRequired,
|
||||||
columns: React.PropTypes.array.isRequired,
|
columns: React.PropTypes.array.isRequired,
|
||||||
flow: React.PropTypes.object.isRequired,
|
flow: React.PropTypes.object.isRequired,
|
||||||
highlighted: React.PropTypes.bool,
|
highlight: React.PropTypes.string,
|
||||||
selected: React.PropTypes.bool,
|
selected: React.PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -22,7 +24,7 @@ function FlowRow(props) {
|
|||||||
|
|
||||||
const className = classNames({
|
const className = classNames({
|
||||||
"selected": props.selected,
|
"selected": props.selected,
|
||||||
"highlighted": props.highlighted,
|
"highlighted": props.highlight && parseFilter(props.highlight)(flow),
|
||||||
"intercepted": flow.intercepted,
|
"intercepted": flow.intercepted,
|
||||||
"has-request": flow.request,
|
"has-request": flow.request,
|
||||||
"has-response": flow.response,
|
"has-response": flow.response,
|
||||||
@ -39,9 +41,12 @@ function FlowRow(props) {
|
|||||||
|
|
||||||
const FlowRowContainer = connect(
|
const FlowRowContainer = connect(
|
||||||
(state, ownProps) => ({
|
(state, ownProps) => ({
|
||||||
flow: state.flows.all.byId[ownProps.flowId]
|
flow: state.flows.all.byId[ownProps.flowId],
|
||||||
|
highlight: state.flows.highlight,
|
||||||
|
selected: state.flows.selected.indexOf(ownProps.flowId) >= 0
|
||||||
}),
|
}),
|
||||||
dispatch => ({
|
(dispatch, ownProps) => ({
|
||||||
|
|
||||||
})
|
})
|
||||||
)(FlowRow);
|
)(FlowRow);
|
||||||
|
|
||||||
@ -102,10 +107,6 @@ class FlowTableHead extends React.Component {
|
|||||||
|
|
||||||
class FlowTable extends React.Component {
|
class FlowTable extends React.Component {
|
||||||
|
|
||||||
static contextTypes = {
|
|
||||||
view: React.PropTypes.object.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
rowHeight: React.PropTypes.number,
|
rowHeight: React.PropTypes.number,
|
||||||
};
|
};
|
||||||
@ -117,26 +118,23 @@ class FlowTable extends React.Component {
|
|||||||
constructor(props, context) {
|
constructor(props, context) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
this.state = { flows: [], vScroll: calcVScroll() };
|
this.state = { vScroll: calcVScroll() };
|
||||||
|
|
||||||
this.onChange = this.onChange.bind(this);
|
|
||||||
this.onViewportUpdate = this.onViewportUpdate.bind(this);
|
this.onViewportUpdate = this.onViewportUpdate.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
componentWillMount() {
|
||||||
window.addEventListener("resize", this.onViewportUpdate);
|
window.addEventListener("resize", this.onViewportUpdate);
|
||||||
this.context.view.addListener("add", this.onChange);
|
|
||||||
this.context.view.addListener("update", this.onChange);
|
|
||||||
this.context.view.addListener("remove", this.onChange);
|
|
||||||
this.context.view.addListener("recalculate", this.onChange);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
window.removeEventListener("resize", this.onViewportUpdate);
|
window.removeEventListener("resize", this.onViewportUpdate);
|
||||||
this.context.view.removeListener("add", this.onChange);
|
}
|
||||||
this.context.view.removeListener("update", this.onChange);
|
|
||||||
this.context.view.removeListener("remove", this.onChange);
|
componentWillReceiveProps(nextProps) {
|
||||||
this.context.view.removeListener("recalculate", this.onChange);
|
if(nextProps.selected && nextProps.selected !== this.props.selected){
|
||||||
|
window.setTimeout(() => this.scrollIntoView(nextProps.selected), 1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
@ -150,7 +148,7 @@ class FlowTable extends React.Component {
|
|||||||
const vScroll = calcVScroll({
|
const vScroll = calcVScroll({
|
||||||
viewportTop,
|
viewportTop,
|
||||||
viewportHeight: viewport.offsetHeight,
|
viewportHeight: viewport.offsetHeight,
|
||||||
itemCount: this.state.flows.length,
|
itemCount: this.props.flows.length,
|
||||||
rowHeight: this.props.rowHeight,
|
rowHeight: this.props.rowHeight,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -160,13 +158,9 @@ class FlowTable extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange() {
|
|
||||||
this.setState({ flows: this.context.view.list });
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollIntoView(flow) {
|
scrollIntoView(flow) {
|
||||||
const viewport = ReactDOM.findDOMNode(this);
|
const viewport = ReactDOM.findDOMNode(this);
|
||||||
const index = this.context.view.indexOf(flow);
|
const index = this.props.flows.indexOf(flow);
|
||||||
const rowHeight = this.props.rowHeight;
|
const rowHeight = this.props.rowHeight;
|
||||||
const head = ReactDOM.findDOMNode(this.refs.head);
|
const head = ReactDOM.findDOMNode(this.refs.head);
|
||||||
|
|
||||||
@ -188,8 +182,7 @@ class FlowTable extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const vScroll = this.state.vScroll;
|
const vScroll = this.state.vScroll;
|
||||||
const highlight = this.context.view._highlight;
|
const flows = this.props.flows.slice(vScroll.start, vScroll.end);
|
||||||
const flows = this.state.flows.slice(vScroll.start, vScroll.end);
|
|
||||||
|
|
||||||
const transform = `translate(0,${this.state.viewportTop}px)`;
|
const transform = `translate(0,${this.state.viewportTop}px)`;
|
||||||
|
|
||||||
@ -206,11 +199,9 @@ class FlowTable extends React.Component {
|
|||||||
<tr style={{ height: vScroll.paddingTop }}></tr>
|
<tr style={{ height: vScroll.paddingTop }}></tr>
|
||||||
{flows.map(flow => (
|
{flows.map(flow => (
|
||||||
<FlowRowContainer
|
<FlowRowContainer
|
||||||
flowId={flow.id}
|
|
||||||
key={flow.id}
|
key={flow.id}
|
||||||
|
flowId={flow.id}
|
||||||
columns={flowtable_columns}
|
columns={flowtable_columns}
|
||||||
selected={flow === this.props.selected}
|
|
||||||
highlighted={highlight && highlight[flow.id]}
|
|
||||||
selectFlow={this.props.selectFlow}
|
selectFlow={this.props.selectFlow}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
@ -222,4 +213,17 @@ class FlowTable extends React.Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AutoScroll(FlowTable);
|
FlowTable = AutoScroll(FlowTable)
|
||||||
|
|
||||||
|
|
||||||
|
const parseFilter = _.memoize(Filt.parse)
|
||||||
|
|
||||||
|
const FlowTableContainer = connect(
|
||||||
|
state => ({
|
||||||
|
flows: state.flows.view,
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
})
|
||||||
|
)(FlowTable)
|
||||||
|
|
||||||
|
export default FlowTableContainer;
|
||||||
|
@ -3,128 +3,59 @@ import React from "react";
|
|||||||
import {FlowActions} from "../actions.js";
|
import {FlowActions} from "../actions.js";
|
||||||
import {Query} from "../actions.js";
|
import {Query} from "../actions.js";
|
||||||
import {Key} from "../utils.js";
|
import {Key} from "../utils.js";
|
||||||
import {StoreView} from "../store/view.js";
|
|
||||||
import Filt from "../filt/filt.js";
|
|
||||||
import {Splitter} from "./common.js"
|
import {Splitter} from "./common.js"
|
||||||
import FlowTable from "./flowtable.js";
|
import FlowTable from "./flowtable.js";
|
||||||
import FlowView from "./flowview/index.js";
|
import FlowView from "./flowview/index.js";
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
import {selectFlow, setFilter, setHighlight} from "../ducks/flows";
|
||||||
|
|
||||||
|
|
||||||
var MainView = React.createClass({
|
var MainView = React.createClass({
|
||||||
contextTypes: {
|
|
||||||
flowStore: React.PropTypes.object.isRequired,
|
|
||||||
},
|
|
||||||
childContextTypes: {
|
|
||||||
view: React.PropTypes.object.isRequired,
|
|
||||||
},
|
|
||||||
getChildContext: function () {
|
|
||||||
return {
|
|
||||||
view: this.state.view
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getInitialState: function () {
|
|
||||||
var sortKeyFun = false;
|
|
||||||
var view = new StoreView(this.context.flowStore, this.getViewFilt(), sortKeyFun);
|
|
||||||
view.addListener("recalculate", this.onRecalculate);
|
|
||||||
view.addListener("add", this.onUpdate);
|
|
||||||
view.addListener("update", this.onUpdate);
|
|
||||||
view.addListener("remove", this.onUpdate);
|
|
||||||
view.addListener("remove", this.onRemove);
|
|
||||||
|
|
||||||
return {
|
|
||||||
view: view,
|
|
||||||
sortKeyFun: sortKeyFun
|
|
||||||
};
|
|
||||||
},
|
|
||||||
componentWillUnmount: function () {
|
|
||||||
this.state.view.close();
|
|
||||||
},
|
|
||||||
getViewFilt: function () {
|
|
||||||
try {
|
|
||||||
var filtStr = this.props.query[Query.SEARCH];
|
|
||||||
var filt = filtStr ? Filt.parse(filtStr) : () => true;
|
|
||||||
var highlightStr = this.props.query[Query.HIGHLIGHT];
|
|
||||||
var highlight = highlightStr ? Filt.parse(highlightStr) : () => false;
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error when processing filter: " + e);
|
|
||||||
}
|
|
||||||
|
|
||||||
var fun = function filter_and_highlight(flow) {
|
|
||||||
if (!this._highlight) {
|
|
||||||
this._highlight = {};
|
|
||||||
}
|
|
||||||
this._highlight[flow.id] = highlight(flow);
|
|
||||||
return filt(flow);
|
|
||||||
};
|
|
||||||
fun.highlightStr = highlightStr;
|
|
||||||
fun.filtStr = filtStr;
|
|
||||||
return fun;
|
|
||||||
},
|
|
||||||
componentWillReceiveProps: function (nextProps) {
|
componentWillReceiveProps: function (nextProps) {
|
||||||
var filterChanged = this.state.view.filt.filtStr !== nextProps.location.query[Query.SEARCH];
|
// Update redux store with route changes
|
||||||
var highlightChanged = this.state.view.filt.highlightStr !== nextProps.location.query[Query.HIGHLIGHT];
|
if(nextProps.routeParams.flowId !== (nextProps.selectedFlow || {}).id) {
|
||||||
if (filterChanged || highlightChanged) {
|
this.props.selectFlow(nextProps.routeParams.flowId)
|
||||||
this.state.view.recalculate(this.getViewFilt(), this.state.sortKeyFun);
|
|
||||||
}
|
}
|
||||||
},
|
if(nextProps.location.query[Query.SEARCH] !== nextProps.filter) {
|
||||||
onRecalculate: function () {
|
this.props.setFilter(nextProps.location.query[Query.SEARCH], false)
|
||||||
this.forceUpdate();
|
|
||||||
var selected = this.getSelected();
|
|
||||||
if (selected) {
|
|
||||||
this.refs.flowTable.scrollIntoView(selected);
|
|
||||||
}
|
}
|
||||||
},
|
if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) {
|
||||||
onUpdate: function (flow) {
|
this.props.setHighlight(nextProps.location.query[Query.HIGHLIGHT], false)
|
||||||
if (flow.id === this.props.routeParams.flowId) {
|
|
||||||
this.forceUpdate();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onRemove: function (flow_id, index) {
|
|
||||||
if (flow_id === this.props.routeParams.flowId) {
|
|
||||||
var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length - 1)];
|
|
||||||
this.selectFlow(flow_to_select);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setSortKeyFun: function (sortKeyFun) {
|
setSortKeyFun: function (sortKeyFun) {
|
||||||
this.setState({
|
// FIXME: Move to redux. This requires that sortKeyFun is not a function anymore.
|
||||||
sortKeyFun: sortKeyFun
|
|
||||||
});
|
|
||||||
this.state.view.recalculate(this.getViewFilt(), sortKeyFun);
|
|
||||||
},
|
},
|
||||||
selectFlow: function (flow) {
|
selectFlow: function (flow) {
|
||||||
|
// TODO: This belongs into redux
|
||||||
if (flow) {
|
if (flow) {
|
||||||
var tab = this.props.routeParams.detailTab || "request";
|
let tab = this.props.routeParams.detailTab || "request";
|
||||||
this.props.updateLocation(`/flows/${flow.id}/${tab}`);
|
this.props.updateLocation(`/flows/${flow.id}/${tab}`);
|
||||||
this.refs.flowTable.scrollIntoView(flow);
|
|
||||||
} else {
|
} else {
|
||||||
this.props.updateLocation("/flows");
|
this.props.updateLocation("/flows");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectFlowRelative: function (shift) {
|
selectFlowRelative: function (shift) {
|
||||||
var flows = this.state.view.list;
|
// TODO: This belongs into redux
|
||||||
var index;
|
let flows = this.props.flows,
|
||||||
|
index
|
||||||
if (!this.props.routeParams.flowId) {
|
if (!this.props.routeParams.flowId) {
|
||||||
if (shift < 0) {
|
if (shift < 0) {
|
||||||
index = flows.length - 1;
|
index = flows.length - 1
|
||||||
} else {
|
} else {
|
||||||
index = 0;
|
index = 0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var currFlowId = this.props.routeParams.flowId;
|
index = flows.indexOf(this.props.selectedFlow)
|
||||||
var i = flows.length;
|
|
||||||
while (i--) {
|
|
||||||
if (flows[i].id === currFlowId) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
index = Math.min(
|
index = Math.min(
|
||||||
Math.max(0, index + shift),
|
Math.max(0, index + shift),
|
||||||
flows.length - 1);
|
flows.length - 1
|
||||||
|
)
|
||||||
}
|
}
|
||||||
this.selectFlow(flows[index]);
|
this.selectFlow(flows[index])
|
||||||
},
|
},
|
||||||
onMainKeyDown: function (e) {
|
onMainKeyDown: function (e) {
|
||||||
var flow = this.getSelected();
|
var flow = this.props.selectedFlow;
|
||||||
if (e.ctrlKey) {
|
if (e.ctrlKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -210,14 +141,10 @@ var MainView = React.createClass({
|
|||||||
}
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
},
|
},
|
||||||
getSelected: function () {
|
|
||||||
return this.context.flowStore.get(this.props.routeParams.flowId);
|
|
||||||
},
|
|
||||||
render: function () {
|
render: function () {
|
||||||
var selected = this.getSelected();
|
|
||||||
|
|
||||||
var details;
|
var details = null;
|
||||||
if (selected) {
|
if (this.props.selectedFlow) {
|
||||||
details = [
|
details = [
|
||||||
<Splitter key="splitter"/>,
|
<Splitter key="splitter"/>,
|
||||||
<FlowView
|
<FlowView
|
||||||
@ -226,10 +153,8 @@ var MainView = React.createClass({
|
|||||||
tab={this.props.routeParams.detailTab}
|
tab={this.props.routeParams.detailTab}
|
||||||
query={this.props.query}
|
query={this.props.query}
|
||||||
updateLocation={this.props.updateLocation}
|
updateLocation={this.props.updateLocation}
|
||||||
flow={selected}/>
|
flow={this.props.selectedFlow}/>
|
||||||
];
|
]
|
||||||
} else {
|
|
||||||
details = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -237,11 +162,27 @@ var MainView = React.createClass({
|
|||||||
<FlowTable ref="flowTable"
|
<FlowTable ref="flowTable"
|
||||||
selectFlow={this.selectFlow}
|
selectFlow={this.selectFlow}
|
||||||
setSortKeyFun={this.setSortKeyFun}
|
setSortKeyFun={this.setSortKeyFun}
|
||||||
selected={selected} />
|
selected={this.props.selectedFlow} />
|
||||||
{details}
|
{details}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export default MainView;
|
const MainViewContainer = connect(
|
||||||
|
state => ({
|
||||||
|
flows: state.flows.view,
|
||||||
|
filter: state.flows.filter,
|
||||||
|
highlight: state.flows.highlight,
|
||||||
|
selectedFlow: state.flows.all.byId[state.flows.selected[0]]
|
||||||
|
}),
|
||||||
|
dispatch => ({
|
||||||
|
selectFlow: flowId => dispatch(selectFlow(flowId)),
|
||||||
|
setFilter: filter => dispatch(setFilter(filter)),
|
||||||
|
setHighlight: highlight => dispatch(setHighlight(highlight))
|
||||||
|
}),
|
||||||
|
undefined,
|
||||||
|
{withRef: true}
|
||||||
|
)(MainView);
|
||||||
|
|
||||||
|
export default MainViewContainer;
|
||||||
|
@ -9,7 +9,7 @@ import MainView from "./mainview.js";
|
|||||||
import Footer from "./footer.js";
|
import Footer from "./footer.js";
|
||||||
import {Header, MainMenu} from "./header.js";
|
import {Header, MainMenu} from "./header.js";
|
||||||
import EventLog from "./eventlog.js"
|
import EventLog from "./eventlog.js"
|
||||||
import {FlowStore, SettingsStore} from "../store/store.js";
|
import {SettingsStore} from "../store/store.js";
|
||||||
import {Key} from "../utils.js";
|
import {Key} from "../utils.js";
|
||||||
|
|
||||||
|
|
||||||
@ -23,7 +23,6 @@ var Reports = React.createClass({
|
|||||||
|
|
||||||
var ProxyAppMain = React.createClass({
|
var ProxyAppMain = React.createClass({
|
||||||
childContextTypes: {
|
childContextTypes: {
|
||||||
flowStore: React.PropTypes.object.isRequired,
|
|
||||||
returnFocus: React.PropTypes.func.isRequired,
|
returnFocus: React.PropTypes.func.isRequired,
|
||||||
location: React.PropTypes.object.isRequired,
|
location: React.PropTypes.object.isRequired,
|
||||||
},
|
},
|
||||||
@ -61,13 +60,11 @@ var ProxyAppMain = React.createClass({
|
|||||||
},
|
},
|
||||||
getChildContext: function () {
|
getChildContext: function () {
|
||||||
return {
|
return {
|
||||||
flowStore: this.state.flowStore,
|
|
||||||
returnFocus: this.focus,
|
returnFocus: this.focus,
|
||||||
location: this.props.location
|
location: this.props.location
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
getInitialState: function () {
|
getInitialState: function () {
|
||||||
var flowStore = new FlowStore();
|
|
||||||
var settingsStore = new SettingsStore();
|
var settingsStore = new SettingsStore();
|
||||||
|
|
||||||
this.settingsStore = settingsStore;
|
this.settingsStore = settingsStore;
|
||||||
@ -75,7 +72,6 @@ var ProxyAppMain = React.createClass({
|
|||||||
_.extend(settingsStore.dict, {});
|
_.extend(settingsStore.dict, {});
|
||||||
return {
|
return {
|
||||||
settings: settingsStore.dict,
|
settings: settingsStore.dict,
|
||||||
flowStore: flowStore,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
focus: function () {
|
focus: function () {
|
||||||
@ -84,7 +80,7 @@ var ProxyAppMain = React.createClass({
|
|||||||
ReactDOM.findDOMNode(this).focus();
|
ReactDOM.findDOMNode(this).focus();
|
||||||
},
|
},
|
||||||
getMainComponent: function () {
|
getMainComponent: function () {
|
||||||
return this.refs.view;
|
return this.refs.view.getWrappedInstance ? this.refs.view.getWrappedInstance() : this.refs.view;
|
||||||
},
|
},
|
||||||
onKeydown: function (e) {
|
onKeydown: function (e) {
|
||||||
|
|
||||||
|
@ -35,7 +35,7 @@ export default function reducer(state = defaultState, action) {
|
|||||||
...state,
|
...state,
|
||||||
filter,
|
filter,
|
||||||
filteredEvents: updateViewFilter(
|
filteredEvents: updateViewFilter(
|
||||||
state.events.list,
|
state.events,
|
||||||
x => filter[x.level]
|
x => filter[x.level]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
import makeList from "./utils/list"
|
import makeList from "./utils/list"
|
||||||
|
import Filt from "../filt/filt"
|
||||||
|
import {updateViewFilter, updateViewList} from "./utils/view"
|
||||||
|
|
||||||
export const UPDATE_FLOWS = "UPDATE_FLOWS"
|
export const UPDATE_FLOWS = "UPDATE_FLOWS"
|
||||||
|
export const SET_FILTER = "SET_FLOW_FILTER"
|
||||||
|
export const SET_HIGHLIGHT = "SET_FLOW_HIGHLIGHT"
|
||||||
|
export const SELECT_FLOW = "SELECT_FLOW"
|
||||||
|
|
||||||
const {
|
const {
|
||||||
reduceList,
|
reduceList,
|
||||||
@ -11,6 +16,14 @@ const {
|
|||||||
|
|
||||||
const defaultState = {
|
const defaultState = {
|
||||||
all: reduceList(),
|
all: reduceList(),
|
||||||
|
selected: [],
|
||||||
|
view: [],
|
||||||
|
filter: undefined,
|
||||||
|
highlight: undefined,
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeFilterFn(filter) {
|
||||||
|
return filter ? Filt.parse(filter) : () => true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function reducer(state = defaultState, action) {
|
export default function reducer(state = defaultState, action) {
|
||||||
@ -20,10 +33,48 @@ export default function reducer(state = defaultState, action) {
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
all,
|
all,
|
||||||
|
view: updateViewList(state.view, state.all, all, action, makeFilterFn(action.filter))
|
||||||
|
}
|
||||||
|
case SET_FILTER:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
filter: action.filter,
|
||||||
|
view: updateViewFilter(state.all, makeFilterFn(action.filter))
|
||||||
|
}
|
||||||
|
case SET_HIGHLIGHT:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
highlight: action.highlight
|
||||||
|
}
|
||||||
|
case SELECT_FLOW:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
selected: [action.flowId]
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function setFilter(filter) {
|
||||||
|
return {
|
||||||
|
type: SET_FILTER,
|
||||||
|
filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function setHighlight(highlight) {
|
||||||
|
return {
|
||||||
|
type: SET_HIGHLIGHT,
|
||||||
|
highlight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export function selectFlow(flowId) {
|
||||||
|
return {
|
||||||
|
type: SELECT_FLOW,
|
||||||
|
flowId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
export {updateList as updateFlows, fetchList as fetchFlows}
|
export {updateList as updateFlows, fetchList as fetchFlows}
|
@ -15,13 +15,15 @@ const makeCompareFn = sortFn => {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sortFn.reverse)
|
// need to adjust sortedIndexOf as well
|
||||||
return (a, b) => compareFn(b, a)
|
// if (sortFn.reverse)
|
||||||
|
// return (a, b) => compareFn(b, a)
|
||||||
return compareFn
|
return compareFn
|
||||||
}
|
}
|
||||||
|
|
||||||
const sortedInsert = (list, sortFn, item) => {
|
const sortedInsert = (list, sortFn, item) => {
|
||||||
let l = [...list, item]
|
let l = [...list, item]
|
||||||
|
l.indexOf = x => sortedIndexOf(l, x, sortFn)
|
||||||
let compareFn = makeCompareFn(sortFn)
|
let compareFn = makeCompareFn(sortFn)
|
||||||
|
|
||||||
// only sort if sorting order is not correct yet
|
// only sort if sorting order is not correct yet
|
||||||
@ -35,21 +37,54 @@ const sortedInsert = (list, sortFn, item) => {
|
|||||||
|
|
||||||
const sortedRemove = (list, sortFn, item) => {
|
const sortedRemove = (list, sortFn, item) => {
|
||||||
let itemId = item.id
|
let itemId = item.id
|
||||||
return list.filter(x => x.id !== itemId)
|
let l = list.filter(x => x.id !== itemId)
|
||||||
|
l.indexOf = x => sortedIndexOf(l, x, sortFn)
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sortedIndexOf(list, value, sortFn) {
|
||||||
|
if (sortFn === false){
|
||||||
|
let i = 0
|
||||||
|
while (i < list.length && list[i].id !== value.id){
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
let low = 0,
|
||||||
|
high = list.length,
|
||||||
|
val = sortFn(value),
|
||||||
|
mid;
|
||||||
|
while (low < high) {
|
||||||
|
mid = (low + high) >>> 1;
|
||||||
|
if ((sortFn(list[mid]) < val) ) {
|
||||||
|
low = mid + 1
|
||||||
|
} else {
|
||||||
|
high = mid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two flows may have the same sort value.
|
||||||
|
// we previously determined the leftmost flow with the same sort value,
|
||||||
|
// so no we need to scan linearly
|
||||||
|
while (list[low].id !== value.id && sortFn(list[low + 1]) === val) {
|
||||||
|
low++
|
||||||
|
}
|
||||||
|
return low;
|
||||||
}
|
}
|
||||||
|
|
||||||
// for when the list changes
|
// for when the list changes
|
||||||
export function updateViewList(state, currentList, nextList, action, filterFn = defaultFilterFn, sortFn = defaultSortFn) {
|
export function updateViewList(currentView, currentList, nextList, action, filterFn = defaultFilterFn, sortFn = defaultSortFn) {
|
||||||
switch (action.cmd) {
|
switch (action.cmd) {
|
||||||
case REQUEST_LIST:
|
case REQUEST_LIST:
|
||||||
return state
|
return currentView
|
||||||
case RECEIVE_LIST:
|
case RECEIVE_LIST:
|
||||||
return updateViewFilter(nextList.list, filterFn, sortFn)
|
return updateViewFilter(nextList, filterFn, sortFn)
|
||||||
case ADD:
|
case ADD:
|
||||||
if (filterFn(action.item)) {
|
if (filterFn(action.item)) {
|
||||||
return sortedInsert(state, sortFn, action.item)
|
return sortedInsert(currentView, sortFn, action.item)
|
||||||
}
|
}
|
||||||
return state
|
return currentView
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
// let's determine if it's in the view currently and if it should be in the view.
|
// let's determine if it's in the view currently and if it should be in the view.
|
||||||
let currentItemState = currentList.byId[action.item.id],
|
let currentItemState = currentList.byId[action.item.id],
|
||||||
@ -58,30 +93,34 @@ export function updateViewList(state, currentList, nextList, action, filterFn =
|
|||||||
shouldBeInView = filterFn(nextItemState)
|
shouldBeInView = filterFn(nextItemState)
|
||||||
|
|
||||||
if (!isInView && shouldBeInView)
|
if (!isInView && shouldBeInView)
|
||||||
return sortedInsert(state, sortFn, action.item)
|
return sortedInsert(currentView, sortFn, action.item)
|
||||||
if (isInView && !shouldBeInView)
|
if (isInView && !shouldBeInView)
|
||||||
return sortedRemove(state, sortFn, action.item)
|
return sortedRemove(currentView, sortFn, action.item)
|
||||||
if (isInView && shouldBeInView && sortFn(currentItemState) !== sortFn(nextItemState)) {
|
if (isInView && shouldBeInView && sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) {
|
||||||
let s = [...state]
|
let s = [...currentView]
|
||||||
s.sort(sortFn)
|
s.sort(makeCompareFn(sortFn))
|
||||||
|
s.indexOf = x => sortedIndexOf(s, x, sortFn)
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
return state
|
return currentView
|
||||||
case REMOVE:
|
case REMOVE:
|
||||||
let isInView_ = filterFn(currentList.byId[action.item.id])
|
let isInView_ = filterFn(currentList.byId[action.item.id])
|
||||||
if (isInView_) {
|
if (isInView_) {
|
||||||
return sortedRemove(state, sortFn, action.item)
|
return sortedRemove(currentView, sortFn, action.item)
|
||||||
}
|
}
|
||||||
return state
|
return currentView
|
||||||
default:
|
default:
|
||||||
console.error("Unknown list action: ", action)
|
console.error("Unknown list action: ", action)
|
||||||
return state
|
return currentView
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateViewFilter(list, filterFn = defaultFilterFn, sortFn = defaultSortFn) {
|
export function updateViewFilter(list, filterFn = defaultFilterFn, sortFn = defaultSortFn) {
|
||||||
let filtered = list.filter(filterFn)
|
let filtered = list.list.filter(filterFn)
|
||||||
if (sortFn)
|
if (sortFn){
|
||||||
filtered.sort(makeCompareFn(sortFn))
|
filtered.sort(makeCompareFn(sortFn))
|
||||||
|
}
|
||||||
|
filtered.indexOf = x => sortedIndexOf(filtered, x, sortFn)
|
||||||
|
|
||||||
return filtered
|
return filtered
|
||||||
}
|
}
|
@ -6,55 +6,6 @@ import {ActionTypes, StoreCmds} from "../actions.js";
|
|||||||
import {AppDispatcher} from "../dispatcher.js";
|
import {AppDispatcher} from "../dispatcher.js";
|
||||||
|
|
||||||
|
|
||||||
function ListStore() {
|
|
||||||
EventEmitter.call(this);
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
_.extend(ListStore.prototype, EventEmitter.prototype, {
|
|
||||||
add: function (elem) {
|
|
||||||
if (elem.id in this._pos_map) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._pos_map[elem.id] = this.list.length;
|
|
||||||
this.list.push(elem);
|
|
||||||
this.emit("add", elem);
|
|
||||||
},
|
|
||||||
update: function (elem) {
|
|
||||||
if (!(elem.id in this._pos_map)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.list[this._pos_map[elem.id]] = elem;
|
|
||||||
this.emit("update", elem);
|
|
||||||
},
|
|
||||||
remove: function (elem_id) {
|
|
||||||
if (!(elem_id in this._pos_map)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.list.splice(this._pos_map[elem_id], 1);
|
|
||||||
this._build_map();
|
|
||||||
this.emit("remove", elem_id);
|
|
||||||
},
|
|
||||||
reset: function (elems) {
|
|
||||||
this.list = elems || [];
|
|
||||||
this._build_map();
|
|
||||||
this.emit("recalculate");
|
|
||||||
},
|
|
||||||
_build_map: function () {
|
|
||||||
this._pos_map = {};
|
|
||||||
for (var i = 0; i < this.list.length; i++) {
|
|
||||||
var elem = this.list[i];
|
|
||||||
this._pos_map[elem.id] = i;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
get: function (elem_id) {
|
|
||||||
return this.list[this._pos_map[elem_id]];
|
|
||||||
},
|
|
||||||
index: function (elem_id) {
|
|
||||||
return this._pos_map[elem_id];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
function DictStore() {
|
function DictStore() {
|
||||||
EventEmitter.call(this);
|
EventEmitter.call(this);
|
||||||
this.reset();
|
this.reset();
|
||||||
@ -133,12 +84,6 @@ _.extend(LiveStoreMixin.prototype, {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function LiveListStore(type) {
|
|
||||||
ListStore.call(this);
|
|
||||||
LiveStoreMixin.call(this, type);
|
|
||||||
}
|
|
||||||
_.extend(LiveListStore.prototype, ListStore.prototype, LiveStoreMixin.prototype);
|
|
||||||
|
|
||||||
function LiveDictStore(type) {
|
function LiveDictStore(type) {
|
||||||
DictStore.call(this);
|
DictStore.call(this);
|
||||||
LiveStoreMixin.call(this, type);
|
LiveStoreMixin.call(this, type);
|
||||||
@ -146,10 +91,6 @@ function LiveDictStore(type) {
|
|||||||
_.extend(LiveDictStore.prototype, DictStore.prototype, LiveStoreMixin.prototype);
|
_.extend(LiveDictStore.prototype, DictStore.prototype, LiveStoreMixin.prototype);
|
||||||
|
|
||||||
|
|
||||||
export function FlowStore() {
|
|
||||||
return new LiveListStore(ActionTypes.FLOW_STORE);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsStore() {
|
export function SettingsStore() {
|
||||||
return new LiveDictStore(ActionTypes.SETTINGS_STORE);
|
return new LiveDictStore(ActionTypes.SETTINGS_STORE);
|
||||||
}
|
}
|
@ -1,111 +0,0 @@
|
|||||||
import {EventEmitter} from 'events';
|
|
||||||
import _ from "lodash";
|
|
||||||
|
|
||||||
import utils from "../utils.js";
|
|
||||||
|
|
||||||
function SortByStoreOrder(elem) {
|
|
||||||
return this.store.index(elem.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
var default_sort = SortByStoreOrder;
|
|
||||||
var default_filt = function (elem) {
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function StoreView(store, filt, sortfun) {
|
|
||||||
EventEmitter.call(this);
|
|
||||||
|
|
||||||
this.store = store;
|
|
||||||
|
|
||||||
this.add = this.add.bind(this);
|
|
||||||
this.update = this.update.bind(this);
|
|
||||||
this.remove = this.remove.bind(this);
|
|
||||||
this.recalculate = this.recalculate.bind(this);
|
|
||||||
this.store.addListener("add", this.add);
|
|
||||||
this.store.addListener("update", this.update);
|
|
||||||
this.store.addListener("remove", this.remove);
|
|
||||||
this.store.addListener("recalculate", this.recalculate);
|
|
||||||
|
|
||||||
this.recalculate(filt, sortfun);
|
|
||||||
}
|
|
||||||
|
|
||||||
_.extend(StoreView.prototype, EventEmitter.prototype, {
|
|
||||||
close: function () {
|
|
||||||
this.store.removeListener("add", this.add);
|
|
||||||
this.store.removeListener("update", this.update);
|
|
||||||
this.store.removeListener("remove", this.remove);
|
|
||||||
this.store.removeListener("recalculate", this.recalculate);
|
|
||||||
this.removeAllListeners();
|
|
||||||
},
|
|
||||||
recalculate: function (filt, sortfun) {
|
|
||||||
filt = filt || this.filt || default_filt;
|
|
||||||
sortfun = sortfun || this.sortfun || default_sort;
|
|
||||||
filt = filt.bind(this);
|
|
||||||
sortfun = sortfun.bind(this);
|
|
||||||
this.filt = filt;
|
|
||||||
this.sortfun = sortfun;
|
|
||||||
|
|
||||||
this.list = this.store.list.filter(filt);
|
|
||||||
this.list.sort(function (a, b) {
|
|
||||||
var akey = sortfun(a);
|
|
||||||
var bkey = sortfun(b);
|
|
||||||
if(akey < bkey){
|
|
||||||
return -1;
|
|
||||||
} else if(akey > bkey){
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.emit("recalculate");
|
|
||||||
},
|
|
||||||
indexOf: function (elem) {
|
|
||||||
return this.list.indexOf(elem, _.sortedIndexBy(this.list, elem, this.sortfun));
|
|
||||||
},
|
|
||||||
add: function (elem) {
|
|
||||||
if (this.filt(elem)) {
|
|
||||||
var idx = _.sortedIndexBy(this.list, elem, this.sortfun);
|
|
||||||
if (idx === this.list.length) { //happens often, .push is way faster.
|
|
||||||
this.list.push(elem);
|
|
||||||
} else {
|
|
||||||
this.list.splice(idx, 0, elem);
|
|
||||||
}
|
|
||||||
this.emit("add", elem, idx);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
update: function (elem) {
|
|
||||||
var idx;
|
|
||||||
var i = this.list.length;
|
|
||||||
// Search from the back, we usually update the latest entries.
|
|
||||||
while (i--) {
|
|
||||||
if (this.list[i].id === elem.id) {
|
|
||||||
idx = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (idx === -1) { //not contained in list
|
|
||||||
this.add(elem);
|
|
||||||
} else if (!this.filt(elem)) {
|
|
||||||
this.remove(elem.id);
|
|
||||||
} else {
|
|
||||||
if (this.sortfun(this.list[idx]) !== this.sortfun(elem)) { //sortpos has changed
|
|
||||||
this.remove(this.list[idx]);
|
|
||||||
this.add(elem);
|
|
||||||
} else {
|
|
||||||
this.list[idx] = elem;
|
|
||||||
this.emit("update", elem, idx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
remove: function (elem_id) {
|
|
||||||
var idx = this.list.length;
|
|
||||||
while (idx--) {
|
|
||||||
if (this.list[idx].id === elem_id) {
|
|
||||||
this.list.splice(idx, 1);
|
|
||||||
this.emit("remove", elem_id, idx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
Loading…
Reference in New Issue
Block a user