From 0d64cc93278d39bd4c87cf5110d326f57574c8a1 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Thu, 18 Sep 2014 02:22:10 +0200 Subject: flowtable: add selection indicator, add keyboard navigation --- web/src/js/components/flowtable-columns.jsx.js | 95 ++++++++++++ web/src/js/components/flowtable.jsx.js | 192 ++++++++++++------------- web/src/js/components/proxyapp.jsx.js | 2 +- web/src/js/stores/flowstore.js | 9 +- web/src/js/utils.js | 10 ++ 5 files changed, 204 insertions(+), 104 deletions(-) create mode 100644 web/src/js/components/flowtable-columns.jsx.js (limited to 'web/src/js') diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js new file mode 100644 index 00000000..e0cee365 --- /dev/null +++ b/web/src/js/components/flowtable-columns.jsx.js @@ -0,0 +1,95 @@ +/** @jsx React.DOM */ + + +var TLSColumn = React.createClass({ + statics: { + renderTitle: function(){ + return ; + } + }, + render: function(){ + var flow = this.props.flow; + var ssl = (flow.request.scheme == "https"); + return ; + } +}); + + +var IconColumn = React.createClass({ + statics: { + renderTitle: function(){ + return ; + } + }, + render: function(){ + var flow = this.props.flow; + return ; + } +}); + +var PathColumn = React.createClass({ + statics: { + renderTitle: function(){ + return Path; + } + }, + render: function(){ + var flow = this.props.flow; + return {flow.request.scheme + "://" + flow.request.host + flow.request.path}; + } +}); + + +var MethodColumn = React.createClass({ + statics: { + renderTitle: function(){ + return Method; + } + }, + render: function(){ + var flow = this.props.flow; + return {flow.request.method}; + } +}); + + +var StatusColumn = React.createClass({ + statics: { + renderTitle: function(){ + return Status; + } + }, + render: function(){ + var flow = this.props.flow; + var status; + if(flow.response){ + status = flow.response.code; + } else { + status = null; + } + return {status}; + } +}); + + +var TimeColumn = React.createClass({ + statics: { + renderTitle: function(){ + return Time; + } + }, + render: function(){ + var flow = this.props.flow; + var time; + if(flow.response){ + time = Math.round(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))+"ms"; + } else { + time = "..."; + } + return {time}; + } +}); + + +var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn]; + diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js index 39721baf..b1b6fa98 100644 --- a/web/src/js/components/flowtable.jsx.js +++ b/web/src/js/components/flowtable.jsx.js @@ -9,7 +9,14 @@ var FlowRow = React.createClass({ flow: flow }); }.bind(this)); - return {columns}; + var className = ""; + if(this.props.selected){ + className += "selected"; + } + return ( + + {columns} + ); } }); @@ -25,107 +32,20 @@ var FlowTableHead = React.createClass({ var FlowTableBody = React.createClass({ render: function(){ var rows = this.props.flows.map(function(flow){ - //TODO: Add UUID - return ; + var selected = (flow == this.props.selected); + return ; }.bind(this)); - return {rows}; - } -}); - - -var TLSColumn = React.createClass({ - statics: { - renderTitle: function(){ - return ; - } - }, - render: function(){ - var flow = this.props.flow; - var ssl = (flow.request.scheme == "https"); - return ; - } -}); - - -var IconColumn = React.createClass({ - statics: { - renderTitle: function(){ - return ; - } - }, - render: function(){ - var flow = this.props.flow; - return ; - } -}); - -var PathColumn = React.createClass({ - statics: { - renderTitle: function(){ - return Path; - } - }, - render: function(){ - var flow = this.props.flow; - return {flow.request.scheme + "://" + flow.request.host + flow.request.path}; - } -}); - - -var MethodColumn = React.createClass({ - statics: { - renderTitle: function(){ - return Method; - } - }, - render: function(){ - var flow = this.props.flow; - return {flow.request.method}; + return {rows}; } }); -var StatusColumn = React.createClass({ - statics: { - renderTitle: function(){ - return Status; - } - }, - render: function(){ - var flow = this.props.flow; - var status; - if(flow.response){ - status = flow.response.code; - } else { - status = null; - } - return {status}; - } -}); - - -var TimeColumn = React.createClass({ - statics: { - renderTitle: function(){ - return Time; - } - }, - render: function(){ - var flow = this.props.flow; - var time; - if(flow.response){ - time = Math.round(1000 * (flow.response.timestamp_end - flow.request.timestamp_start))+"ms"; - } else { - time = "..."; - } - return {time}; - } -}); - - -var all_columns = [TLSColumn, IconColumn, PathColumn, MethodColumn, StatusColumn, TimeColumn]; - - var FlowTable = React.createClass({ getInitialState: function () { return { @@ -146,18 +66,88 @@ var FlowTable = React.createClass({ flows: this.flowStore.getAll() }); }, - onClick: function(e){ - console.log("rowclick", e); + selectFlow: function(flow){ + this.setState({ + selected: flow + }); + + // Now comes the fun part: Scroll the flow into the view. + var viewport = this.getDOMNode(); + var flowNode = this.refs.body.refs[flow.id].getDOMNode(); + var viewport_top = viewport.scrollTop; + var viewport_bottom = viewport_top + viewport.offsetHeight; + var flowNode_top = flowNode.offsetTop; + var flowNode_bottom = flowNode_top + flowNode.offsetHeight; + + // Account for pinned thead by pretending that the flowNode starts + // -thead_height pixel earlier. + flowNode_top -= this.refs.body.getDOMNode().offsetTop; + + if(flowNode_top < viewport_top){ + viewport.scrollTop = flowNode_top; + } else if(flowNode_bottom > viewport_bottom) { + viewport.scrollTop = flowNode_bottom - viewport.offsetHeight; + } + }, + selectRowRelative: function(i){ + var index; + if(!this.state.selected){ + if(i > 0){ + index = this.flows.length-1; + } else { + index = 0; + } + } else { + index = _.findIndex(this.state.flows, function(f){ + return f === this.state.selected; + }.bind(this)); + index = Math.min(Math.max(0, index+i), this.state.flows.length-1); + } + this.selectFlow(this.state.flows[index]); + }, + onKeyDown: function(e){ + switch(e.keyCode){ + case Key.DOWN: + this.selectRowRelative(+1); + return false; + break; + case Key.UP: + this.selectRowRelative(-1); + return false; + break; + case Key.ENTER: + console.log("Open details pane...", this.state.selected); + break; + case Key.ESC: + console.log("") + default: + console.debug("keydown", e.keyCode); + return; + } + return false; + }, + onScroll: function(e){ + //Abusing CSS transforms to set thead into position:fixed. + var head = this.refs.head.getDOMNode(); + head.style.transform = "translate(0,"+this.getDOMNode().scrollTop+"px)"; }, render: function () { var flows = this.state.flows.map(function(flow){ return
{flow.request.method} {flow.request.scheme}://{flow.request.host}{flow.request.path}
; }); return ( +
- - + +
+
); } }); diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js index 63998ffe..486e723f 100644 --- a/web/src/js/components/proxyapp.jsx.js +++ b/web/src/js/components/proxyapp.jsx.js @@ -26,7 +26,7 @@ var ProxyAppMain = React.createClass({ return (
-
+ {this.state.settings.showEventLog ? : null}
diff --git a/web/src/js/stores/flowstore.js b/web/src/js/stores/flowstore.js index a5cb74ba..ba5b0788 100644 --- a/web/src/js/stores/flowstore.js +++ b/web/src/js/stores/flowstore.js @@ -35,9 +35,8 @@ _.extend(FlowView.prototype, EventEmitter.prototype, { this.emit("change"); }, _update: function(flow){ - console.debug("FIXME: Use UUID"); var idx = _.findIndex(this.flows, function(f){ - return flow.request.timestamp_start == f.request.timestamp_start; + return flow.id === f.id; }); if(idx < 0){ @@ -61,7 +60,13 @@ _.extend(_FlowStore.prototype, EventEmitter.prototype, { var view = new FlowView(this, !since); $.getJSON("/static/flows.json", function(flows){ + flows = flows.concat(_.cloneDeep(flows)).concat(_.cloneDeep(flows)); + var id = 1; + flows.forEach(function(flow){ + flow.id = "uuid-"+id++; + }) view.add_bulk(flows); + }); return view; diff --git a/web/src/js/utils.js b/web/src/js/utils.js index 39ad92fb..6e545d8a 100644 --- a/web/src/js/utils.js +++ b/web/src/js/utils.js @@ -11,3 +11,13 @@ var AutoScrollMixin = { } }, }; + + +var Key = { + UP: 38, + DOWN: 40, + LEFT: 37, + RIGHT: 39, + ENTER: 13, + ESC: 27 +} \ No newline at end of file -- cgit v1.2.3