aboutsummaryrefslogtreecommitdiffstats
path: root/web
diff options
context:
space:
mode:
authorMaximilian Hils <git@maximilianhils.com>2016-06-04 18:53:41 -0700
committerMaximilian Hils <git@maximilianhils.com>2016-06-04 18:53:41 -0700
commitd53a2de0ba69bea6c7aefa87782ad249cfb4ea76 (patch)
tree25e474bf95e4f83022708e84184d8d7d56a6e3ce /web
parente880f532ad3c66ebfded4655b7fa67a367a83cc7 (diff)
downloadmitmproxy-d53a2de0ba69bea6c7aefa87782ad249cfb4ea76.tar.gz
mitmproxy-d53a2de0ba69bea6c7aefa87782ad249cfb4ea76.tar.bz2
mitmproxy-d53a2de0ba69bea6c7aefa87782ad249cfb4ea76.zip
web: completely move flow state to redux
Diffstat (limited to 'web')
-rw-r--r--web/src/js/components/flowtable.js64
-rw-r--r--web/src/js/components/mainview.js149
-rw-r--r--web/src/js/components/proxyapp.js8
-rw-r--r--web/src/js/ducks/eventLog.js2
-rw-r--r--web/src/js/ducks/flows.js51
-rw-r--r--web/src/js/ducks/utils/view.js77
-rw-r--r--web/src/js/store/store.js59
-rw-r--r--web/src/js/store/view.js111
8 files changed, 191 insertions, 330 deletions
diff --git a/web/src/js/components/flowtable.js b/web/src/js/components/flowtable.js
index 1a616eee..0241cd78 100644
--- a/web/src/js/components/flowtable.js
+++ b/web/src/js/components/flowtable.js
@@ -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;
diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js
index 964e82db..22895991 100644
--- a/web/src/js/components/mainview.js
+++ b/web/src/js/components/mainview.js
@@ -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;
- },
+var MainView = React.createClass({
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;
diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js
index 9e4bd0a4..e4489e18 100644
--- a/web/src/js/components/proxyapp.js
+++ b/web/src/js/components/proxyapp.js
@@ -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) {
diff --git a/web/src/js/ducks/eventLog.js b/web/src/js/ducks/eventLog.js
index e3661fe7..44b67b2c 100644
--- a/web/src/js/ducks/eventLog.js
+++ b/web/src/js/ducks/eventLog.js
@@ -35,7 +35,7 @@ export default function reducer(state = defaultState, action) {
...state,
filter,
filteredEvents: updateViewFilter(
- state.events.list,
+ state.events,
x => filter[x.level]
)
}
diff --git a/web/src/js/ducks/flows.js b/web/src/js/ducks/flows.js
index fb934489..fdbc42ee 100644
--- a/web/src/js/ducks/flows.js
+++ b/web/src/js/ducks/flows.js
@@ -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} \ No newline at end of file
diff --git a/web/src/js/ducks/utils/view.js b/web/src/js/ducks/utils/view.js
index 55fdf6c7..5535ed83 100644
--- a/web/src/js/ducks/utils/view.js
+++ b/web/src/js/ducks/utils/view.js
@@ -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
} \ No newline at end of file
diff --git a/web/src/js/store/store.js b/web/src/js/store/store.js
index 65355684..f3e2074f 100644
--- a/web/src/js/store/store.js
+++ b/web/src/js/store/store.js
@@ -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);
} \ No newline at end of file
diff --git a/web/src/js/store/view.js b/web/src/js/store/view.js
index d8aeba60..e69de29b 100644
--- a/web/src/js/store/view.js
+++ b/web/src/js/store/view.js
@@ -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;
- }
- }
- }
-});