From d53a2de0ba69bea6c7aefa87782ad249cfb4ea76 Mon Sep 17 00:00:00 2001
From: Maximilian Hils <git@maximilianhils.com>
Date: Sat, 4 Jun 2016 18:53:41 -0700
Subject: web: completely move flow state to redux

---
 web/src/js/components/flowtable.js |  64 ++++++++--------
 web/src/js/components/mainview.js  | 149 +++++++++++--------------------------
 web/src/js/components/proxyapp.js  |   8 +-
 3 files changed, 81 insertions(+), 140 deletions(-)

(limited to 'web/src/js/components')

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) {
 
-- 
cgit v1.2.3