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 {calcVScroll} from "./helpers/VirtualScroll";
|
||||
import flowtable_columns from "./flowtable-columns.js";
|
||||
import Filt from "../filt/filt";
|
||||
|
||||
|
||||
FlowRow.propTypes = {
|
||||
selectFlow: React.PropTypes.func.isRequired,
|
||||
columns: React.PropTypes.array.isRequired,
|
||||
flow: React.PropTypes.object.isRequired,
|
||||
highlighted: React.PropTypes.bool,
|
||||
highlight: React.PropTypes.string,
|
||||
selected: React.PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -22,7 +24,7 @@ function FlowRow(props) {
|
|||
|
||||
const className = classNames({
|
||||
"selected": props.selected,
|
||||
"highlighted": props.highlighted,
|
||||
"highlighted": props.highlight && parseFilter(props.highlight)(flow),
|
||||
"intercepted": flow.intercepted,
|
||||
"has-request": flow.request,
|
||||
"has-response": flow.response,
|
||||
|
@ -39,9 +41,12 @@ function FlowRow(props) {
|
|||
|
||||
const FlowRowContainer = connect(
|
||||
(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);
|
||||
|
||||
|
@ -102,10 +107,6 @@ class FlowTableHead extends React.Component {
|
|||
|
||||
class FlowTable extends React.Component {
|
||||
|
||||
static contextTypes = {
|
||||
view: React.PropTypes.object.isRequired,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
rowHeight: React.PropTypes.number,
|
||||
};
|
||||
|
@ -117,26 +118,23 @@ class FlowTable extends React.Component {
|
|||
constructor(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);
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
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() {
|
||||
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);
|
||||
this.context.view.removeListener("recalculate", this.onChange);
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if(nextProps.selected && nextProps.selected !== this.props.selected){
|
||||
window.setTimeout(() => this.scrollIntoView(nextProps.selected), 1)
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
|
@ -150,7 +148,7 @@ class FlowTable extends React.Component {
|
|||
const vScroll = calcVScroll({
|
||||
viewportTop,
|
||||
viewportHeight: viewport.offsetHeight,
|
||||
itemCount: this.state.flows.length,
|
||||
itemCount: this.props.flows.length,
|
||||
rowHeight: this.props.rowHeight,
|
||||
});
|
||||
|
||||
|
@ -160,13 +158,9 @@ class FlowTable extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
onChange() {
|
||||
this.setState({ flows: this.context.view.list });
|
||||
}
|
||||
|
||||
scrollIntoView(flow) {
|
||||
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 head = ReactDOM.findDOMNode(this.refs.head);
|
||||
|
||||
|
@ -188,8 +182,7 @@ class FlowTable extends React.Component {
|
|||
|
||||
render() {
|
||||
const vScroll = this.state.vScroll;
|
||||
const highlight = this.context.view._highlight;
|
||||
const flows = this.state.flows.slice(vScroll.start, vScroll.end);
|
||||
const flows = this.props.flows.slice(vScroll.start, vScroll.end);
|
||||
|
||||
const transform = `translate(0,${this.state.viewportTop}px)`;
|
||||
|
||||
|
@ -206,11 +199,9 @@ class FlowTable extends React.Component {
|
|||
<tr style={{ height: vScroll.paddingTop }}></tr>
|
||||
{flows.map(flow => (
|
||||
<FlowRowContainer
|
||||
flowId={flow.id}
|
||||
key={flow.id}
|
||||
flowId={flow.id}
|
||||
columns={flowtable_columns}
|
||||
selected={flow === this.props.selected}
|
||||
highlighted={highlight && highlight[flow.id]}
|
||||
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 {Query} from "../actions.js";
|
||||
import {Key} from "../utils.js";
|
||||
import {StoreView} from "../store/view.js";
|
||||
import Filt from "../filt/filt.js";
|
||||
import {Splitter} from "./common.js"
|
||||
import FlowTable from "./flowtable.js";
|
||||
import FlowView from "./flowview/index.js";
|
||||
import {connect} from 'react-redux'
|
||||
import {selectFlow, setFilter, setHighlight} from "../ducks/flows";
|
||||
|
||||
|
||||
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) {
|
||||
var filterChanged = this.state.view.filt.filtStr !== nextProps.location.query[Query.SEARCH];
|
||||
var highlightChanged = this.state.view.filt.highlightStr !== nextProps.location.query[Query.HIGHLIGHT];
|
||||
if (filterChanged || highlightChanged) {
|
||||
this.state.view.recalculate(this.getViewFilt(), this.state.sortKeyFun);
|
||||
// Update redux store with route changes
|
||||
if(nextProps.routeParams.flowId !== (nextProps.selectedFlow || {}).id) {
|
||||
this.props.selectFlow(nextProps.routeParams.flowId)
|
||||
}
|
||||
},
|
||||
onRecalculate: function () {
|
||||
this.forceUpdate();
|
||||
var selected = this.getSelected();
|
||||
if (selected) {
|
||||
this.refs.flowTable.scrollIntoView(selected);
|
||||
if(nextProps.location.query[Query.SEARCH] !== nextProps.filter) {
|
||||
this.props.setFilter(nextProps.location.query[Query.SEARCH], false)
|
||||
}
|
||||
},
|
||||
onUpdate: function (flow) {
|
||||
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);
|
||||
if (nextProps.location.query[Query.HIGHLIGHT] !== nextProps.highlight) {
|
||||
this.props.setHighlight(nextProps.location.query[Query.HIGHLIGHT], false)
|
||||
}
|
||||
},
|
||||
setSortKeyFun: function (sortKeyFun) {
|
||||
this.setState({
|
||||
sortKeyFun: sortKeyFun
|
||||
});
|
||||
this.state.view.recalculate(this.getViewFilt(), sortKeyFun);
|
||||
// FIXME: Move to redux. This requires that sortKeyFun is not a function anymore.
|
||||
},
|
||||
selectFlow: function (flow) {
|
||||
// TODO: This belongs into redux
|
||||
if (flow) {
|
||||
var tab = this.props.routeParams.detailTab || "request";
|
||||
let tab = this.props.routeParams.detailTab || "request";
|
||||
this.props.updateLocation(`/flows/${flow.id}/${tab}`);
|
||||
this.refs.flowTable.scrollIntoView(flow);
|
||||
} else {
|
||||
this.props.updateLocation("/flows");
|
||||
}
|
||||
},
|
||||
selectFlowRelative: function (shift) {
|
||||
var flows = this.state.view.list;
|
||||
var index;
|
||||
// TODO: This belongs into redux
|
||||
let flows = this.props.flows,
|
||||
index
|
||||
if (!this.props.routeParams.flowId) {
|
||||
if (shift < 0) {
|
||||
index = flows.length - 1;
|
||||
index = flows.length - 1
|
||||
} else {
|
||||
index = 0;
|
||||
index = 0
|
||||
}
|
||||
} else {
|
||||
var currFlowId = this.props.routeParams.flowId;
|
||||
var i = flows.length;
|
||||
while (i--) {
|
||||
if (flows[i].id === currFlowId) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
index = flows.indexOf(this.props.selectedFlow)
|
||||
index = Math.min(
|
||||
Math.max(0, index + shift),
|
||||
flows.length - 1);
|
||||
flows.length - 1
|
||||
)
|
||||
}
|
||||
this.selectFlow(flows[index]);
|
||||
this.selectFlow(flows[index])
|
||||
},
|
||||
onMainKeyDown: function (e) {
|
||||
var flow = this.getSelected();
|
||||
var flow = this.props.selectedFlow;
|
||||
if (e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
@ -210,14 +141,10 @@ var MainView = React.createClass({
|
|||
}
|
||||
e.preventDefault();
|
||||
},
|
||||
getSelected: function () {
|
||||
return this.context.flowStore.get(this.props.routeParams.flowId);
|
||||
},
|
||||
render: function () {
|
||||
var selected = this.getSelected();
|
||||
|
||||
var details;
|
||||
if (selected) {
|
||||
var details = null;
|
||||
if (this.props.selectedFlow) {
|
||||
details = [
|
||||
<Splitter key="splitter"/>,
|
||||
<FlowView
|
||||
|
@ -226,10 +153,8 @@ var MainView = React.createClass({
|
|||
tab={this.props.routeParams.detailTab}
|
||||
query={this.props.query}
|
||||
updateLocation={this.props.updateLocation}
|
||||
flow={selected}/>
|
||||
];
|
||||
} else {
|
||||
details = null;
|
||||
flow={this.props.selectedFlow}/>
|
||||
]
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -237,11 +162,27 @@ var MainView = React.createClass({
|
|||
<FlowTable ref="flowTable"
|
||||
selectFlow={this.selectFlow}
|
||||
setSortKeyFun={this.setSortKeyFun}
|
||||
selected={selected} />
|
||||
selected={this.props.selectedFlow} />
|
||||
{details}
|
||||
</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 {Header, MainMenu} from "./header.js";
|
||||
import EventLog from "./eventlog.js"
|
||||
import {FlowStore, SettingsStore} from "../store/store.js";
|
||||
import {SettingsStore} from "../store/store.js";
|
||||
import {Key} from "../utils.js";
|
||||
|
||||
|
||||
|
@ -23,7 +23,6 @@ var Reports = React.createClass({
|
|||
|
||||
var ProxyAppMain = React.createClass({
|
||||
childContextTypes: {
|
||||
flowStore: React.PropTypes.object.isRequired,
|
||||
returnFocus: React.PropTypes.func.isRequired,
|
||||
location: React.PropTypes.object.isRequired,
|
||||
},
|
||||
|
@ -61,13 +60,11 @@ var ProxyAppMain = React.createClass({
|
|||
},
|
||||
getChildContext: function () {
|
||||
return {
|
||||
flowStore: this.state.flowStore,
|
||||
returnFocus: this.focus,
|
||||
location: this.props.location
|
||||
};
|
||||
},
|
||||
getInitialState: function () {
|
||||
var flowStore = new FlowStore();
|
||||
var settingsStore = new SettingsStore();
|
||||
|
||||
this.settingsStore = settingsStore;
|
||||
|
@ -75,7 +72,6 @@ var ProxyAppMain = React.createClass({
|
|||
_.extend(settingsStore.dict, {});
|
||||
return {
|
||||
settings: settingsStore.dict,
|
||||
flowStore: flowStore,
|
||||
};
|
||||
},
|
||||
focus: function () {
|
||||
|
@ -84,7 +80,7 @@ var ProxyAppMain = React.createClass({
|
|||
ReactDOM.findDOMNode(this).focus();
|
||||
},
|
||||
getMainComponent: function () {
|
||||
return this.refs.view;
|
||||
return this.refs.view.getWrappedInstance ? this.refs.view.getWrappedInstance() : this.refs.view;
|
||||
},
|
||||
onKeydown: function (e) {
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export default function reducer(state = defaultState, action) {
|
|||
...state,
|
||||
filter,
|
||||
filteredEvents: updateViewFilter(
|
||||
state.events.list,
|
||||
state.events,
|
||||
x => filter[x.level]
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,11 @@
|
|||
import makeList from "./utils/list"
|
||||
import Filt from "../filt/filt"
|
||||
import {updateViewFilter, updateViewList} from "./utils/view"
|
||||
|
||||
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 {
|
||||
reduceList,
|
||||
|
@ -11,6 +16,14 @@ const {
|
|||
|
||||
const defaultState = {
|
||||
all: reduceList(),
|
||||
selected: [],
|
||||
view: [],
|
||||
filter: undefined,
|
||||
highlight: undefined,
|
||||
}
|
||||
|
||||
function makeFilterFn(filter) {
|
||||
return filter ? Filt.parse(filter) : () => true;
|
||||
}
|
||||
|
||||
export default function reducer(state = defaultState, action) {
|
||||
|
@ -20,10 +33,48 @@ export default function reducer(state = defaultState, action) {
|
|||
return {
|
||||
...state,
|
||||
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:
|
||||
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}
|
|
@ -15,13 +15,15 @@ const makeCompareFn = sortFn => {
|
|||
return 0
|
||||
}
|
||||
}
|
||||
if (sortFn.reverse)
|
||||
return (a, b) => compareFn(b, a)
|
||||
// need to adjust sortedIndexOf as well
|
||||
// if (sortFn.reverse)
|
||||
// return (a, b) => compareFn(b, a)
|
||||
return compareFn
|
||||
}
|
||||
|
||||
const sortedInsert = (list, sortFn, item) => {
|
||||
let l = [...list, item]
|
||||
l.indexOf = x => sortedIndexOf(l, x, sortFn)
|
||||
let compareFn = makeCompareFn(sortFn)
|
||||
|
||||
// only sort if sorting order is not correct yet
|
||||
|
@ -35,21 +37,54 @@ const sortedInsert = (list, sortFn, item) => {
|
|||
|
||||
const sortedRemove = (list, sortFn, item) => {
|
||||
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
|
||||
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) {
|
||||
case REQUEST_LIST:
|
||||
return state
|
||||
return currentView
|
||||
case RECEIVE_LIST:
|
||||
return updateViewFilter(nextList.list, filterFn, sortFn)
|
||||
return updateViewFilter(nextList, filterFn, sortFn)
|
||||
case ADD:
|
||||
if (filterFn(action.item)) {
|
||||
return sortedInsert(state, sortFn, action.item)
|
||||
return sortedInsert(currentView, sortFn, action.item)
|
||||
}
|
||||
return state
|
||||
return currentView
|
||||
case UPDATE:
|
||||
// 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],
|
||||
|
@ -58,30 +93,34 @@ export function updateViewList(state, currentList, nextList, action, filterFn =
|
|||
shouldBeInView = filterFn(nextItemState)
|
||||
|
||||
if (!isInView && shouldBeInView)
|
||||
return sortedInsert(state, sortFn, action.item)
|
||||
return sortedInsert(currentView, sortFn, action.item)
|
||||
if (isInView && !shouldBeInView)
|
||||
return sortedRemove(state, sortFn, action.item)
|
||||
if (isInView && shouldBeInView && sortFn(currentItemState) !== sortFn(nextItemState)) {
|
||||
let s = [...state]
|
||||
s.sort(sortFn)
|
||||
return sortedRemove(currentView, sortFn, action.item)
|
||||
if (isInView && shouldBeInView && sortFn && sortFn(currentItemState) !== sortFn(nextItemState)) {
|
||||
let s = [...currentView]
|
||||
s.sort(makeCompareFn(sortFn))
|
||||
s.indexOf = x => sortedIndexOf(s, x, sortFn)
|
||||
return s
|
||||
}
|
||||
return state
|
||||
return currentView
|
||||
case REMOVE:
|
||||
let isInView_ = filterFn(currentList.byId[action.item.id])
|
||||
if (isInView_) {
|
||||
return sortedRemove(state, sortFn, action.item)
|
||||
return sortedRemove(currentView, sortFn, action.item)
|
||||
}
|
||||
return state
|
||||
return currentView
|
||||
default:
|
||||
console.error("Unknown list action: ", action)
|
||||
return state
|
||||
return currentView
|
||||
}
|
||||
}
|
||||
|
||||
export function updateViewFilter(list, filterFn = defaultFilterFn, sortFn = defaultSortFn) {
|
||||
let filtered = list.filter(filterFn)
|
||||
if (sortFn)
|
||||
let filtered = list.list.filter(filterFn)
|
||||
if (sortFn){
|
||||
filtered.sort(makeCompareFn(sortFn))
|
||||
}
|
||||
filtered.indexOf = x => sortedIndexOf(filtered, x, sortFn)
|
||||
|
||||
return filtered
|
||||
}
|
|
@ -6,55 +6,6 @@ import {ActionTypes, StoreCmds} from "../actions.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() {
|
||||
EventEmitter.call(this);
|
||||
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) {
|
||||
DictStore.call(this);
|
||||
LiveStoreMixin.call(this, type);
|
||||
|
@ -146,10 +91,6 @@ function LiveDictStore(type) {
|
|||
_.extend(LiveDictStore.prototype, DictStore.prototype, LiveStoreMixin.prototype);
|
||||
|
||||
|
||||
export function FlowStore() {
|
||||
return new LiveListStore(ActionTypes.FLOW_STORE);
|
||||
}
|
||||
|
||||
export function SettingsStore() {
|
||||
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