From 9192427d7b845a6389c4a44c930e067c479cdf0d Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Mon, 29 Feb 2016 02:16:38 +0100 Subject: [PATCH] web: fix router --- .gitattributes | 4 +- mitmproxy/web/static/app.css | 3 +- mitmproxy/web/static/app.js | 269 +++++++++++++----------- web/src/css/flowtable.less | 3 +- web/src/js/components/common.js | 59 ++---- web/src/js/components/eventlog.js | 19 +- web/src/js/components/flowview/index.js | 17 +- web/src/js/components/header.js | 18 +- web/src/js/components/mainview.js | 32 +-- web/src/js/components/prompt.js | 4 +- web/src/js/components/proxyapp.js | 23 +- 11 files changed, 234 insertions(+), 217 deletions(-) diff --git a/.gitattributes b/.gitattributes index c0efd4e4a..3ecf8f083 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,2 @@ -mitmproxy/mitmproxy/web/static/**/* -diff -mitmproxy/web/src/js/filt/filt.js -diff +mitmproxy/web/static/**/* -diff +web/src/js/filt/filt.js -diff diff --git a/mitmproxy/web/static/app.css b/mitmproxy/web/static/app.css index 1f64abe7a..e6891e44c 100644 --- a/mitmproxy/web/static/app.css +++ b/mitmproxy/web/static/app.css @@ -165,7 +165,8 @@ header .menu { } .flow-table { width: 100%; - overflow: auto; + overflow-y: scroll; + overflow-x: hidden; } .flow-table table { width: 100%; diff --git a/mitmproxy/web/static/app.js b/mitmproxy/web/static/app.js index 400c3c51b..481883451 100644 --- a/mitmproxy/web/static/app.js +++ b/mitmproxy/web/static/app.js @@ -478,20 +478,31 @@ function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { de Object.defineProperty(exports, "__esModule", { value: true }); -var React = require("react"); -var ReactDOM = require("react-dom"); -var ReactRouter = require("react-router"); -var _ = require("lodash"); +exports.Splitter = exports.Router = exports.ChildFocus = exports.SettingsState = exports.StickyHeadMixin = exports.AutoScrollMixin = undefined; + +var _react = require("react"); + +var _react2 = _interopRequireDefault(_react); + +var _reactDom = require("react-dom"); + +var _reactDom2 = _interopRequireDefault(_reactDom); + +var _lodash = require("lodash"); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } // http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) var AutoScrollMixin = exports.AutoScrollMixin = { componentWillUpdate: function componentWillUpdate() { - var node = ReactDOM.findDOMNode(this); + var node = _reactDom2.default.findDOMNode(this); this._shouldScrollBottom = node.scrollTop !== 0 && node.scrollTop + node.clientHeight === node.scrollHeight; }, componentDidUpdate: function componentDidUpdate() { if (this._shouldScrollBottom) { - var node = ReactDOM.findDOMNode(this); + var node = _reactDom2.default.findDOMNode(this); node.scrollTop = node.scrollHeight; } } @@ -502,13 +513,13 @@ var StickyHeadMixin = exports.StickyHeadMixin = { // Abusing CSS transforms to set the element // referenced as head into some kind of position:sticky. var head = this.refs.head; - head.style.transform = "translate(0," + ReactDOM.findDOMNode(this).scrollTop + "px)"; + head.style.transform = "translate(0," + _reactDom2.default.findDOMNode(this).scrollTop + "px)"; } }; var SettingsState = exports.SettingsState = { contextTypes: { - settingsStore: React.PropTypes.object.isRequired + settingsStore: _react2.default.PropTypes.object.isRequired }, getInitialState: function getInitialState() { return { @@ -530,59 +541,45 @@ var SettingsState = exports.SettingsState = { var ChildFocus = exports.ChildFocus = { contextTypes: { - returnFocus: React.PropTypes.func + returnFocus: _react2.default.PropTypes.func }, returnFocus: function returnFocus() { - ReactDOM.findDOMNode(this).blur(); + _reactDom2.default.findDOMNode(this).blur(); window.getSelection().removeAllRanges(); this.context.returnFocus(); } }; -var Navigation = exports.Navigation = { +var Router = exports.Router = { contextTypes: { - routerFoo: React.PropTypes.object, - router: React.PropTypes.object.isRequired + location: _react2.default.PropTypes.object, + router: _react2.default.PropTypes.object.isRequired }, - setQuery: function setQuery(dict) { - var q = this.context.routerFoo.location.query; - for (var i in dict) { - if (dict.hasOwnProperty(i)) { - q[i] = dict[i] || undefined; //falsey values shall be removed. + updateLocation: function updateLocation(pathname, queryUpdate) { + if (pathname === undefined) { + pathname = this.context.location.pathname; + } + var query = this.context.location.query; + if (queryUpdate !== undefined) { + for (var i in queryUpdate) { + if (queryUpdate.hasOwnProperty(i)) { + query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. + } } } - this.replaceWith(undefined, q); - }, - replaceWith: function replaceWith(pathname, query) { - if (pathname === undefined) { - pathname = this.context.routerFoo.location.pathname; - } - if (query === undefined) { - query = this.context.routerFoo.query; - } - console.log({ pathname: pathname, query: query }); this.context.router.replace({ pathname: pathname, query: query }); - } -}; - -// react-router is fairly good at changing its API regularly. -// We keep the old method for now - if it should turn out that their changes are permanent, -// we may remove this mixin and access react-router directly again. -var RouterState = exports.RouterState = { - contextTypes: { - routerFoo: React.PropTypes.object }, getQuery: function getQuery() { // For whatever reason, react-router always returns the same object, which makes comparing // the current props with nextProps impossible. As a workaround, we just clone the query object. - return _.clone(this.context.routerFoo.location.query); + return _lodash2.default.clone(this.context.location.query); }, getParams: function getParams() { - return _.clone(this.context.routerFoo.params); + return this.props.routeParams; } }; -var Splitter = exports.Splitter = React.createClass({ +var Splitter = exports.Splitter = _react2.default.createClass({ displayName: "Splitter", getDefaultProps: function getDefaultProps() { @@ -608,7 +605,7 @@ var Splitter = exports.Splitter = React.createClass({ window.addEventListener("dragend", this.onDragEnd); }, onDragEnd: function onDragEnd() { - ReactDOM.findDOMNode(this).style.transform = ""; + _reactDom2.default.findDOMNode(this).style.transform = ""; window.removeEventListener("dragend", this.onDragEnd); window.removeEventListener("mouseup", this.onMouseUp); window.removeEventListener("mousemove", this.onMouseMove); @@ -616,7 +613,7 @@ var Splitter = exports.Splitter = React.createClass({ onMouseUp: function onMouseUp(e) { this.onDragEnd(); - var node = ReactDOM.findDOMNode(this); + var node = _reactDom2.default.findDOMNode(this); var prev = node.previousElementSibling; var next = node.nextElementSibling; @@ -645,7 +642,7 @@ var Splitter = exports.Splitter = React.createClass({ } else { dY = e.pageY - this.state.startY; } - ReactDOM.findDOMNode(this).style.transform = "translate(" + dX + "px," + dY + "px)"; + _reactDom2.default.findDOMNode(this).style.transform = "translate(" + dX + "px," + dY + "px)"; }, onResize: function onResize() { // Trigger a global resize event. This notifies components that employ virtual scrolling @@ -658,7 +655,7 @@ var Splitter = exports.Splitter = React.createClass({ if (!this.state.applied) { return; } - var node = ReactDOM.findDOMNode(this); + var node = _reactDom2.default.findDOMNode(this); var prev = node.previousElementSibling; var next = node.nextElementSibling; @@ -682,15 +679,15 @@ var Splitter = exports.Splitter = React.createClass({ } else { className += " splitter-y"; } - return React.createElement( + return _react2.default.createElement( "div", { className: className }, - React.createElement("div", { onMouseDown: this.onMouseDown, draggable: "true" }) + _react2.default.createElement("div", { onMouseDown: this.onMouseDown, draggable: "true" }) ); } }); -},{"lodash":"lodash","react":"react","react-dom":"react-dom","react-router":"react-router"}],5:[function(require,module,exports){ +},{"lodash":"lodash","react":"react","react-dom":"react-dom"}],5:[function(require,module,exports){ "use strict"; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; @@ -941,16 +938,31 @@ module.exports = { },{"../utils.js":26,"./common.js":4,"react":"react","react-dom":"react-dom"}],6:[function(require,module,exports){ "use strict"; +Object.defineProperty(exports, "__esModule", { + value: true +}); + +var _react = require("react"); + +var _react2 = _interopRequireDefault(_react); + +var _common = require("./common.js"); + +var _actions = require("../actions.js"); + var _virtualscroll = require("./virtualscroll.js"); -var React = require("react"); -var common = require("./common.js"); -var Query = require("../actions.js").Query; +var _view = require("../store/view.js"); -var views = require("../store/view.js"); -var _ = require("lodash"); +var _view2 = _interopRequireDefault(_view); -var LogMessage = React.createClass({ +var _lodash = require("lodash"); + +var _lodash2 = _interopRequireDefault(_lodash); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +var LogMessage = _react2.default.createClass({ displayName: "LogMessage", render: function render() { @@ -958,15 +970,15 @@ var LogMessage = React.createClass({ var indicator; switch (entry.level) { case "web": - indicator = React.createElement("i", { className: "fa fa-fw fa-html5" }); + indicator = _react2.default.createElement("i", { className: "fa fa-fw fa-html5" }); break; case "debug": - indicator = React.createElement("i", { className: "fa fa-fw fa-bug" }); + indicator = _react2.default.createElement("i", { className: "fa fa-fw fa-bug" }); break; default: - indicator = React.createElement("i", { className: "fa fa-fw fa-info" }); + indicator = _react2.default.createElement("i", { className: "fa fa-fw fa-info" }); } - return React.createElement( + return _react2.default.createElement( "div", null, indicator, @@ -979,18 +991,18 @@ var LogMessage = React.createClass({ } }); -var EventLogContents = React.createClass({ +var EventLogContents = _react2.default.createClass({ displayName: "EventLogContents", contextTypes: { - eventStore: React.PropTypes.object.isRequired + eventStore: _react2.default.PropTypes.object.isRequired }, - mixins: [common.AutoScrollMixin, _virtualscroll.VirtualScrollMixin], + mixins: [_common.AutoScrollMixin, _virtualscroll.VirtualScrollMixin], getInitialState: function getInitialState() { var filterFn = function filterFn(entry) { return this.props.filter[entry.level]; }; - var view = new views.StoreView(this.context.eventStore, filterFn.bind(this)); + var view = new _view2.default.StoreView(this.context.eventStore, filterFn.bind(this)); view.addListener("add", this.onEventLogChange); view.addListener("recalculate", this.onEventLogChange); @@ -1021,13 +1033,13 @@ var EventLogContents = React.createClass({ }; }, renderRow: function renderRow(elem) { - return React.createElement(LogMessage, { key: elem.id, entry: elem }); + return _react2.default.createElement(LogMessage, { key: elem.id, entry: elem }); }, render: function render() { var entries = this.state.view.list; var rows = this.renderRows(entries); - return React.createElement( + return _react2.default.createElement( "pre", { onScroll: this.onScroll }, this.getPlaceholderTop(entries.length), @@ -1037,7 +1049,7 @@ var EventLogContents = React.createClass({ } }); -var ToggleFilter = React.createClass({ +var ToggleFilter = _react2.default.createClass({ displayName: "ToggleFilter", toggle: function toggle(e) { @@ -1051,7 +1063,7 @@ var ToggleFilter = React.createClass({ } else { className += "label-default"; } - return React.createElement( + return _react2.default.createElement( "a", { href: "#", @@ -1062,10 +1074,10 @@ var ToggleFilter = React.createClass({ } }); -var EventLog = React.createClass({ +var EventLog = _react2.default.createClass({ displayName: "EventLog", - mixins: [common.Navigation], + mixins: [_common.Router], getInitialState: function getInitialState() { return { filter: { @@ -1077,37 +1089,38 @@ var EventLog = React.createClass({ }, close: function close() { var d = {}; - d[Query.SHOW_EVENTLOG] = undefined; - this.setQuery(d); + d[_actions.Query.SHOW_EVENTLOG] = undefined; + + this.updateLocation(undefined, d); }, toggleLevel: function toggleLevel(level) { - var filter = _.extend({}, this.state.filter); + var filter = _lodash2.default.extend({}, this.state.filter); filter[level] = !filter[level]; this.setState({ filter: filter }); }, render: function render() { - return React.createElement( + return _react2.default.createElement( "div", { className: "eventlog" }, - React.createElement( + _react2.default.createElement( "div", null, "Eventlog", - React.createElement( + _react2.default.createElement( "div", { className: "pull-right" }, - React.createElement(ToggleFilter, { name: "debug", active: this.state.filter.debug, toggleLevel: this.toggleLevel }), - React.createElement(ToggleFilter, { name: "info", active: this.state.filter.info, toggleLevel: this.toggleLevel }), - React.createElement(ToggleFilter, { name: "web", active: this.state.filter.web, toggleLevel: this.toggleLevel }), - React.createElement("i", { onClick: this.close, className: "fa fa-close" }) + _react2.default.createElement(ToggleFilter, { name: "debug", active: this.state.filter.debug, toggleLevel: this.toggleLevel }), + _react2.default.createElement(ToggleFilter, { name: "info", active: this.state.filter.info, toggleLevel: this.toggleLevel }), + _react2.default.createElement(ToggleFilter, { name: "web", active: this.state.filter.web, toggleLevel: this.toggleLevel }), + _react2.default.createElement("i", { onClick: this.close, className: "fa fa-close" }) ) ), - React.createElement(EventLogContents, { filter: this.state.filter }) + _react2.default.createElement(EventLogContents, { filter: this.state.filter }) ); } }); -module.exports = EventLog; +exports.default = EventLog; },{"../actions.js":2,"../store/view.js":25,"./common.js":4,"./virtualscroll.js":19,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){ "use strict"; @@ -2129,10 +2142,11 @@ module.exports = Details; },{"../../utils.js":26,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ "use strict"; +var _common = require("../common.js"); + var React = require("react"); var _ = require("lodash"); -var common = require("../common.js"); var Nav = require("./nav.js"); var Messages = require("./messages.js"); var Details = require("./details.js"); @@ -2148,7 +2162,7 @@ var allTabs = { var FlowView = React.createClass({ displayName: "FlowView", - mixins: [common.StickyHeadMixin, common.Navigation, common.RouterState], + mixins: [_common.StickyHeadMixin, _common.Router], getInitialState: function getInitialState() { return { prompt: false @@ -2166,20 +2180,17 @@ var FlowView = React.createClass({ }, nextTab: function nextTab(i) { var tabs = this.getTabs(this.props.flow); - var currentIndex = tabs.indexOf(this.getActive()); + var currentIndex = tabs.indexOf(this.props.tab); // JS modulo operator doesn't correct negative numbers, make sure that we are positive. var nextIndex = (currentIndex + i + tabs.length) % tabs.length; this.selectTab(tabs[nextIndex]); }, selectTab: function selectTab(panel) { - this.replaceWith("/flows/" + this.getParams().flowId + "/" + panel); - }, - getActive: function getActive() { - return this.getParams().detailTab; + this.updateLocation("/flows/" + this.getParams().flowId + "/" + panel); }, promptEdit: function promptEdit() { var options; - switch (this.getActive()) { + switch (this.props.tab) { case "request": options = ["method", "url", { text: "http version", key: "v" }, "header" /*, "content"*/]; @@ -2191,7 +2202,7 @@ var FlowView = React.createClass({ case "details": return; default: - throw "Unknown tab for edit: " + this.getActive(); + throw "Unknown tab for edit: " + this.props.tab; } this.setState({ @@ -2209,7 +2220,7 @@ var FlowView = React.createClass({ render: function render() { var flow = this.props.flow; var tabs = this.getTabs(flow); - var active = this.getActive(); + var active = this.props.tab; if (tabs.indexOf(active) < 0) { if (active === "response" && flow.error) { @@ -2706,13 +2717,15 @@ module.exports = Footer; },{"./common.js":4,"react":"react"}],15:[function(require,module,exports){ "use strict"; +var _common = require("./common.js"); + var React = require("react"); var ReactDOM = require('react-dom'); var $ = require("jquery"); var Filt = require("../filt/filt.js"); var utils = require("../utils.js"); -var common = require("./common.js"); + var actions = require("../actions.js"); var Query = require("../actions.js").Query; @@ -2786,7 +2799,7 @@ var FilterDocs = React.createClass({ var FilterInput = React.createClass({ displayName: "FilterInput", - mixins: [common.ChildFocus], + mixins: [_common.ChildFocus], getInitialState: function getInitialState() { // Consider both focus and mouseover for showing/hiding the tooltip, // because onBlur of the input is triggered before the click on the tooltip @@ -2900,7 +2913,7 @@ var FilterInput = React.createClass({ var MainMenu = React.createClass({ displayName: "MainMenu", - mixins: [common.Navigation, common.RouterState, common.SettingsState], + mixins: [_common.Router, _common.SettingsState], statics: { title: "Start", route: "flows" @@ -2908,12 +2921,12 @@ var MainMenu = React.createClass({ onSearchChange: function onSearchChange(val) { var d = {}; d[Query.SEARCH] = val; - this.setQuery(d); + this.updateLocation(undefined, d); }, onHighlightChange: function onHighlightChange(val) { var d = {}; d[Query.HIGHLIGHT] = val; - this.setQuery(d); + this.updateLocation(undefined, d); }, onInterceptChange: function onInterceptChange(val) { actions.SettingsActions.update({ intercept: val }); @@ -2963,7 +2976,7 @@ var ViewMenu = React.createClass({ title: "View", route: "flows" }, - mixins: [common.Navigation, common.RouterState], + mixins: [_common.Router], toggleEventLog: function toggleEventLog() { var d = {}; @@ -2973,7 +2986,7 @@ var ViewMenu = React.createClass({ d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short } - this.setQuery(d); + this.updateLocation(undefined, d); }, render: function render() { var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; @@ -3098,7 +3111,7 @@ var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; var Header = React.createClass({ displayName: "Header", - mixins: [common.Navigation], + mixins: [_common.Router], getInitialState: function getInitialState() { return { active: header_entries[0] @@ -3106,7 +3119,7 @@ var Header = React.createClass({ }, handleClick: function handleClick(active, e) { e.preventDefault(); - this.replaceWith(active.route); + this.updateLocation(active.route); this.setState({ active: active }); }, render: function render() { @@ -3153,6 +3166,8 @@ module.exports = { },{"../actions.js":2,"../filt/filt.js":22,"../utils.js":26,"./common.js":4,"jquery":"jquery","react":"react","react-dom":"react-dom"}],16:[function(require,module,exports){ "use strict"; +var _common = require("./common.js"); + var React = require("react"); var actions = require("../actions.js"); @@ -3161,14 +3176,13 @@ var utils = require("../utils.js"); var views = require("../store/view.js"); var Filt = require("../filt/filt.js"); -var common = require("./common.js"); var FlowTable = require("./flowtable.js"); var FlowView = require("./flowview/index.js"); var MainView = React.createClass({ displayName: "MainView", - mixins: [common.Navigation, common.RouterState], + mixins: [_common.Router], contextTypes: { flowStore: React.PropTypes.object.isRequired }, @@ -3204,22 +3218,27 @@ var MainView = React.createClass({ return true; }; var highlightStr = this.getQuery()[Query.HIGHLIGHT]; - var highlight = highlightStr ? Filt.parse(highlightStr) : false; + var highlight = highlightStr ? Filt.parse(highlightStr) : function () { + return false; + }; } catch (e) { console.error("Error when processing filter: " + e); } - return function filter_and_highlight(flow) { + var fun = function filter_and_highlight(flow) { if (!this._highlight) { this._highlight = {}; } - this._highlight[flow.id] = highlight && highlight(flow); + this._highlight[flow.id] = highlight(flow); return filt(flow); }; + fun.highlightStr = highlightStr; + fun.filtStr = filtStr; + return fun; }, componentWillReceiveProps: function componentWillReceiveProps(nextProps) { - var filterChanged = this.props.query[Query.SEARCH] !== nextProps.query[Query.SEARCH]; - var highlightChanged = this.props.query[Query.HIGHLIGHT] !== nextProps.query[Query.HIGHLIGHT]; + 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); } @@ -3251,10 +3270,10 @@ var MainView = React.createClass({ selectFlow: function selectFlow(flow) { if (flow) { var tab = this.getParams().detailTab || "request"; - this.replaceWith("/flows/" + flow.id + "/" + tab); + this.updateLocation("/flows/" + flow.id + "/" + tab); this.refs.flowTable.scrollIntoView(flow); } else { - this.replaceWith("/flows"); + this.updateLocation("/flows"); } }, selectFlowRelative: function selectFlowRelative(shift) { @@ -3374,7 +3393,11 @@ var MainView = React.createClass({ var details; if (selected) { - details = [React.createElement(common.Splitter, { key: "splitter" }), React.createElement(FlowView, { key: "flowDetails", ref: "flowDetails", flow: selected })]; + details = [React.createElement(_common.Splitter, { key: "splitter" }), React.createElement(FlowView, { + key: "flowDetails", + ref: "flowDetails", + tab: this.getParams().detailTab, + flow: selected })]; } else { details = null; } @@ -3396,17 +3419,19 @@ module.exports = MainView; },{"../actions.js":2,"../filt/filt.js":22,"../store/view.js":25,"../utils.js":26,"./common.js":4,"./flowtable.js":8,"./flowview/index.js":11,"react":"react"}],17:[function(require,module,exports){ "use strict"; +var _common = require("./common.js"); + var React = require("react"); var ReactDOM = require('react-dom'); var _ = require("lodash"); var utils = require("../utils.js"); -var common = require("./common.js"); + var Prompt = React.createClass({ displayName: "Prompt", - mixins: [common.ChildFocus], + mixins: [_common.ChildFocus], propTypes: { options: React.PropTypes.array.isRequired, done: React.PropTypes.func.isRequired, @@ -3522,18 +3547,25 @@ Object.defineProperty(exports, "__esModule", { }); exports.app = undefined; +var _common = require("./common.js"); + +var _eventlog = require("./eventlog.js"); + +var _eventlog2 = _interopRequireDefault(_eventlog); + var _reactRouter = require("react-router"); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + var React = require("react"); var ReactDOM = require("react-dom"); -var ReactRouter = require("react-router"); var _ = require("lodash"); var common = require("./common.js"); var MainView = require("./mainview.js"); var Footer = require("./footer.js"); var header = require("./header.js"); -var EventLog = require("./eventlog.js"); + var store = require("../store/store.js"); var Query = require("../actions.js").Query; var Key = require("../utils.js").Key; @@ -3554,13 +3586,13 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ displayName: "ProxyAppMain", - mixins: [common.RouterState], + mixins: [_common.Router], childContextTypes: { settingsStore: React.PropTypes.object.isRequired, flowStore: React.PropTypes.object.isRequired, eventStore: React.PropTypes.object.isRequired, returnFocus: React.PropTypes.func.isRequired, - routerFoo: React.PropTypes.object + location: React.PropTypes.object.isRequired }, componentDidMount: function componentDidMount() { this.focus(); @@ -3571,10 +3603,7 @@ var ProxyAppMain = React.createClass({ flowStore: this.state.flowStore, eventStore: this.state.eventStore, returnFocus: this.focus, - routerFoo: { - location: this.props.location, - params: this.props.params - } + location: this.props.location }; }, getInitialState: function getInitialState() { @@ -3627,11 +3656,11 @@ var ProxyAppMain = React.createClass({ render: function render() { var eventlog; if (this.props.location.query[Query.SHOW_EVENTLOG]) { - eventlog = [React.createElement(common.Splitter, { key: "splitter", axis: "y" }), React.createElement(EventLog, { key: "eventlog" })]; + eventlog = [React.createElement(_common.Splitter, { key: "splitter", axis: "y" }), React.createElement(_eventlog2.default, { key: "eventlog" })]; } else { eventlog = null; } - var children = React.cloneElement(this.props.children, { ref: "view", query: this.props.location.query }); + var children = React.cloneElement(this.props.children, { ref: "view", location: this.props.location }); return React.createElement( "div", { id: "container", tabIndex: "0", onKeyDown: this.onKeydown }, diff --git a/web/src/css/flowtable.less b/web/src/css/flowtable.less index 3533983ce..1b560ebaa 100644 --- a/web/src/css/flowtable.less +++ b/web/src/css/flowtable.less @@ -10,7 +10,8 @@ .flow-table { width: 100%; - overflow: auto; + overflow-y: scroll; + overflow-x: hidden; table { width: 100%; diff --git a/web/src/js/components/common.js b/web/src/js/components/common.js index 03b2ef8c7..f910b3d89 100644 --- a/web/src/js/components/common.js +++ b/web/src/js/components/common.js @@ -1,15 +1,14 @@ -var React = require("react"); -var ReactDOM = require("react-dom"); -var ReactRouter = require("react-router"); -var _ = require("lodash"); +import React from "react" +import ReactDOM from "react-dom" +import _ from "lodash" // http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) export var AutoScrollMixin = { componentWillUpdate: function () { var node = ReactDOM.findDOMNode(this); this._shouldScrollBottom = ( - node.scrollTop !== 0 && - node.scrollTop + node.clientHeight === node.scrollHeight + node.scrollTop !== 0 && + node.scrollTop + node.clientHeight === node.scrollHeight ); }, componentDidUpdate: function () { @@ -57,7 +56,7 @@ export var ChildFocus = { contextTypes: { returnFocus: React.PropTypes.func }, - returnFocus: function(){ + returnFocus: function () { ReactDOM.findDOMNode(this).blur(); window.getSelection().removeAllRanges(); this.context.returnFocus(); @@ -65,46 +64,32 @@ export var ChildFocus = { }; -export var Navigation = { +export var Router = { contextTypes: { - routerFoo: React.PropTypes.object, + location: React.PropTypes.object, router: React.PropTypes.object.isRequired }, - setQuery: function (dict) { - var q = this.context.routerFoo.location.query; - for (var i in dict) { - if (dict.hasOwnProperty(i)) { - q[i] = dict[i] || undefined; //falsey values shall be removed. + updateLocation: function (pathname, queryUpdate) { + if (pathname === undefined) { + pathname = this.context.location.pathname; + } + var query = this.context.location.query; + if (queryUpdate !== undefined) { + for (var i in queryUpdate) { + if (queryUpdate.hasOwnProperty(i)) { + query[i] = queryUpdate[i] || undefined; //falsey values shall be removed. + } } } - this.replaceWith(undefined, q); - }, - replaceWith: function (pathname, query) { - if (pathname === undefined) { - pathname = this.context.routerFoo.location.pathname; - } - if (query === undefined) { - query = this.context.routerFoo.query; - } - console.log({ pathname, query }); - this.context.router.replace({ pathname, query }); - } -}; - -// react-router is fairly good at changing its API regularly. -// We keep the old method for now - if it should turn out that their changes are permanent, -// we may remove this mixin and access react-router directly again. -export var RouterState = { - contextTypes: { - routerFoo: React.PropTypes.object, + this.context.router.replace({pathname, query}); }, getQuery: function () { // For whatever reason, react-router always returns the same object, which makes comparing // the current props with nextProps impossible. As a workaround, we just clone the query object. - return _.clone(this.context.routerFoo.location.query); + return _.clone(this.context.location.query); }, - getParams: function () { - return _.clone(this.context.routerFoo.params); + getParams: function() { + return this.props.routeParams; } }; diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js index 9dcd2c386..0f463eb05 100644 --- a/web/src/js/components/eventlog.js +++ b/web/src/js/components/eventlog.js @@ -1,9 +1,9 @@ -var React = require("react"); -var common = require("./common.js"); -var Query = require("../actions.js").Query; +import React from "react" +import {AutoScrollMixin, Router} from "./common.js" +import {Query} from "../actions.js" import { VirtualScrollMixin } from "./virtualscroll.js" -var views = require("../store/view.js"); -var _ = require("lodash"); +import views from "../store/view.js" +import _ from "lodash" var LogMessage = React.createClass({ render: function () { @@ -34,7 +34,7 @@ var EventLogContents = React.createClass({ contextTypes: { eventStore: React.PropTypes.object.isRequired }, - mixins: [common.AutoScrollMixin, VirtualScrollMixin], + mixins: [AutoScrollMixin, VirtualScrollMixin], getInitialState: function () { var filterFn = function (entry) { return this.props.filter[entry.level]; @@ -108,7 +108,7 @@ var ToggleFilter = React.createClass({ }); var EventLog = React.createClass({ - mixins: [common.Navigation], + mixins: [Router], getInitialState: function () { return { filter: { @@ -121,7 +121,8 @@ var EventLog = React.createClass({ close: function () { var d = {}; d[Query.SHOW_EVENTLOG] = undefined; - this.setQuery(d); + + this.updateLocation(undefined, d); }, toggleLevel: function (level) { var filter = _.extend({}, this.state.filter); @@ -147,4 +148,4 @@ var EventLog = React.createClass({ } }); -module.exports = EventLog; \ No newline at end of file +export default EventLog; \ No newline at end of file diff --git a/web/src/js/components/flowview/index.js b/web/src/js/components/flowview/index.js index 91b17dd23..bd34fe8d4 100644 --- a/web/src/js/components/flowview/index.js +++ b/web/src/js/components/flowview/index.js @@ -1,7 +1,7 @@ var React = require("react"); var _ = require("lodash"); -var common = require("../common.js"); +import { Router, StickyHeadMixin } from "../common.js" var Nav = require("./nav.js"); var Messages = require("./messages.js"); var Details = require("./details.js"); @@ -16,7 +16,7 @@ var allTabs = { }; var FlowView = React.createClass({ - mixins: [common.StickyHeadMixin, common.Navigation, common.RouterState], + mixins: [StickyHeadMixin, Router], getInitialState: function () { return { prompt: false @@ -34,20 +34,17 @@ var FlowView = React.createClass({ }, nextTab: function (i) { var tabs = this.getTabs(this.props.flow); - var currentIndex = tabs.indexOf(this.getActive()); + var currentIndex = tabs.indexOf(this.props.tab); // JS modulo operator doesn't correct negative numbers, make sure that we are positive. var nextIndex = (currentIndex + i + tabs.length) % tabs.length; this.selectTab(tabs[nextIndex]); }, selectTab: function (panel) { - this.replaceWith(`/flows/${this.getParams().flowId}/${panel}`); - }, - getActive: function(){ - return this.getParams().detailTab; + this.updateLocation(`/flows/${this.getParams().flowId}/${panel}`); }, promptEdit: function () { var options; - switch(this.getActive()){ + switch(this.props.tab){ case "request": options = [ "method", @@ -67,7 +64,7 @@ var FlowView = React.createClass({ case "details": return; default: - throw "Unknown tab for edit: " + this.getActive(); + throw "Unknown tab for edit: " + this.props.tab; } this.setState({ @@ -85,7 +82,7 @@ var FlowView = React.createClass({ render: function () { var flow = this.props.flow; var tabs = this.getTabs(flow); - var active = this.getActive(); + var active = this.props.tab; if (tabs.indexOf(active) < 0) { if (active === "response" && flow.error) { diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js index f2cc3fc53..d55c6443a 100644 --- a/web/src/js/components/header.js +++ b/web/src/js/components/header.js @@ -4,7 +4,7 @@ var $ = require("jquery"); var Filt = require("../filt/filt.js"); var utils = require("../utils.js"); -var common = require("./common.js"); +import {Router, SettingsState, ChildFocus} from "./common.js"; var actions = require("../actions.js"); var Query = require("../actions.js").Query; @@ -51,7 +51,7 @@ var FilterDocs = React.createClass({ } }); var FilterInput = React.createClass({ - mixins: [common.ChildFocus], + mixins: [ChildFocus], getInitialState: function () { // Consider both focus and mouseover for showing/hiding the tooltip, // because onBlur of the input is triggered before the click on the tooltip @@ -159,7 +159,7 @@ var FilterInput = React.createClass({ }); var MainMenu = React.createClass({ - mixins: [common.Navigation, common.RouterState, common.SettingsState], + mixins: [Router, SettingsState], statics: { title: "Start", route: "flows" @@ -167,12 +167,12 @@ var MainMenu = React.createClass({ onSearchChange: function (val) { var d = {}; d[Query.SEARCH] = val; - this.setQuery(d); + this.updateLocation(undefined, d); }, onHighlightChange: function (val) { var d = {}; d[Query.HIGHLIGHT] = val; - this.setQuery(d); + this.updateLocation(undefined, d); }, onInterceptChange: function (val) { actions.SettingsActions.update({intercept: val}); @@ -219,7 +219,7 @@ var ViewMenu = React.createClass({ title: "View", route: "flows" }, - mixins: [common.Navigation, common.RouterState], + mixins: [Router], toggleEventLog: function () { var d = {}; @@ -229,7 +229,7 @@ var ViewMenu = React.createClass({ d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short } - this.setQuery(d); + this.updateLocation(undefined, d); }, render: function () { var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; @@ -348,7 +348,7 @@ var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; var Header = React.createClass({ - mixins: [common.Navigation], + mixins: [Router], getInitialState: function () { return { active: header_entries[0] @@ -356,7 +356,7 @@ var Header = React.createClass({ }, handleClick: function (active, e) { e.preventDefault(); - this.replaceWith(active.route); + this.updateLocation(active.route); this.setState({active: active}); }, render: function () { diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js index 86666e398..5c9afe0c0 100644 --- a/web/src/js/components/mainview.js +++ b/web/src/js/components/mainview.js @@ -5,13 +5,12 @@ var Query = require("../actions.js").Query; var utils = require("../utils.js"); var views = require("../store/view.js"); var Filt = require("../filt/filt.js"); - -var common = require("./common.js"); +import { Router, Splitter} from "./common.js" var FlowTable = require("./flowtable.js"); var FlowView = require("./flowview/index.js"); var MainView = React.createClass({ - mixins: [common.Navigation, common.RouterState], + mixins: [Router], contextTypes: { flowStore: React.PropTypes.object.isRequired, }, @@ -43,24 +42,27 @@ var MainView = React.createClass({ getViewFilt: function () { try { var filtStr = this.getQuery()[Query.SEARCH]; - var filt = filtStr ? Filt.parse(filtStr) : function(){return true}; + var filt = filtStr ? Filt.parse(filtStr) : () => true; var highlightStr = this.getQuery()[Query.HIGHLIGHT]; - var highlight = highlightStr ? Filt.parse(highlightStr) : false; + var highlight = highlightStr ? Filt.parse(highlightStr) : () => false; } catch (e) { console.error("Error when processing filter: " + e); } - return function filter_and_highlight(flow) { + var fun = function filter_and_highlight(flow) { if (!this._highlight) { this._highlight = {}; } - this._highlight[flow.id] = highlight && highlight(flow); + this._highlight[flow.id] = highlight(flow); return filt(flow); }; + fun.highlightStr = highlightStr; + fun.filtStr = filtStr; + return fun; }, componentWillReceiveProps: function (nextProps) { - var filterChanged = (this.props.query[Query.SEARCH] !== nextProps.query[Query.SEARCH]); - var highlightChanged = (this.props.query[Query.HIGHLIGHT] !== nextProps.query[Query.HIGHLIGHT]); + 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); } @@ -92,10 +94,10 @@ var MainView = React.createClass({ selectFlow: function (flow) { if (flow) { var tab = this.getParams().detailTab || "request"; - this.replaceWith(`/flows/${flow.id}/${tab}`); + this.updateLocation(`/flows/${flow.id}/${tab}`); this.refs.flowTable.scrollIntoView(flow); } else { - this.replaceWith("/flows"); + this.updateLocation("/flows"); } }, selectFlowRelative: function (shift) { @@ -218,8 +220,12 @@ var MainView = React.createClass({ var details; if (selected) { details = [ - , - + , + ]; } else { details = null; diff --git a/web/src/js/components/prompt.js b/web/src/js/components/prompt.js index 9695bd948..b4777934c 100644 --- a/web/src/js/components/prompt.js +++ b/web/src/js/components/prompt.js @@ -3,10 +3,10 @@ var ReactDOM = require('react-dom'); var _ = require("lodash"); var utils = require("../utils.js"); -var common = require("./common.js"); +import {ChildFocus} from "./common.js" var Prompt = React.createClass({ - mixins: [common.ChildFocus], + mixins: [ChildFocus], propTypes: { options: React.PropTypes.array.isRequired, done: React.PropTypes.func.isRequired, diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js index 9c2d87146..fe3e00080 100644 --- a/web/src/js/components/proxyapp.js +++ b/web/src/js/components/proxyapp.js @@ -1,13 +1,13 @@ var React = require("react"); var ReactDOM = require("react-dom"); -var ReactRouter = require("react-router"); var _ = require("lodash"); +import {Router, Splitter} from "./common.js" var common = require("./common.js"); var MainView = require("./mainview.js"); var Footer = require("./footer.js"); var header = require("./header.js"); -var EventLog = require("./eventlog.js"); +import EventLog from "./eventlog.js" var store = require("../store/store.js"); var Query = require("../actions.js").Query; var Key = require("../utils.js").Key; @@ -22,13 +22,13 @@ var Reports = React.createClass({ var ProxyAppMain = React.createClass({ - mixins: [common.RouterState], + mixins: [Router], childContextTypes: { settingsStore: React.PropTypes.object.isRequired, flowStore: React.PropTypes.object.isRequired, eventStore: React.PropTypes.object.isRequired, returnFocus: React.PropTypes.func.isRequired, - routerFoo: React.PropTypes.object, + location: React.PropTypes.object.isRequired, }, componentDidMount: function () { this.focus(); @@ -39,10 +39,7 @@ var ProxyAppMain = React.createClass({ flowStore: this.state.flowStore, eventStore: this.state.eventStore, returnFocus: this.focus, - routerFoo: { - location: this.props.location, - params: this.props.params - } + location: this.props.location }; }, getInitialState: function () { @@ -96,7 +93,7 @@ var ProxyAppMain = React.createClass({ var eventlog; if (this.props.location.query[Query.SHOW_EVENTLOG]) { eventlog = [ - , + , ]; } else { @@ -104,7 +101,7 @@ var ProxyAppMain = React.createClass({ } var children = React.cloneElement( this.props.children, - { ref: "view", query: this.props.location.query } + { ref: "view", location: this.props.location } ); return (
@@ -118,15 +115,15 @@ var ProxyAppMain = React.createClass({ }); -import { Route, Router, hashHistory, Redirect} from "react-router"; +import { Route, Router as ReactRouter, hashHistory, Redirect} from "react-router"; export var app = ( - + - + ); \ No newline at end of file