From c9ce5094c810b551de3cddebf14f277a61657e16 Mon Sep 17 00:00:00 2001 From: Aldo Cortesi Date: Thu, 1 Jan 2015 15:37:42 +1300 Subject: All files and only files in in js/components are jsx So remove the redundant naming --- web/src/js/app.js | 2 +- web/src/js/components/eventlog.js | 155 ++++++++++ web/src/js/components/eventlog.jsx.js | 155 ---------- web/src/js/components/flowdetail.js | 399 +++++++++++++++++++++++++ web/src/js/components/flowdetail.jsx.js | 399 ------------------------- web/src/js/components/flowtable-columns.js | 164 ++++++++++ web/src/js/components/flowtable-columns.jsx.js | 164 ---------- web/src/js/components/flowtable.js | 136 +++++++++ web/src/js/components/flowtable.jsx.js | 136 --------- web/src/js/components/footer.js | 17 ++ web/src/js/components/footer.jsx.js | 17 -- web/src/js/components/header.js | 389 ++++++++++++++++++++++++ web/src/js/components/header.jsx.js | 389 ------------------------ web/src/js/components/mainview.js | 232 ++++++++++++++ web/src/js/components/mainview.jsx.js | 232 -------------- web/src/js/components/proxyapp.js | 92 ++++++ web/src/js/components/proxyapp.jsx.js | 92 ------ web/src/js/components/utils.js | 196 ++++++++++++ web/src/js/components/utils.jsx.js | 196 ------------ web/src/js/components/virtualscroll.js | 85 ++++++ web/src/js/components/virtualscroll.jsx.js | 85 ------ 21 files changed, 1866 insertions(+), 1866 deletions(-) create mode 100644 web/src/js/components/eventlog.js delete mode 100644 web/src/js/components/eventlog.jsx.js create mode 100644 web/src/js/components/flowdetail.js delete mode 100644 web/src/js/components/flowdetail.jsx.js create mode 100644 web/src/js/components/flowtable-columns.js delete mode 100644 web/src/js/components/flowtable-columns.jsx.js create mode 100644 web/src/js/components/flowtable.js delete mode 100644 web/src/js/components/flowtable.jsx.js create mode 100644 web/src/js/components/footer.js delete mode 100644 web/src/js/components/footer.jsx.js create mode 100644 web/src/js/components/header.js delete mode 100644 web/src/js/components/header.jsx.js create mode 100644 web/src/js/components/mainview.js delete mode 100644 web/src/js/components/mainview.jsx.js create mode 100644 web/src/js/components/proxyapp.js delete mode 100644 web/src/js/components/proxyapp.jsx.js create mode 100644 web/src/js/components/utils.js delete mode 100644 web/src/js/components/utils.jsx.js create mode 100644 web/src/js/components/virtualscroll.js delete mode 100644 web/src/js/components/virtualscroll.jsx.js (limited to 'web') diff --git a/web/src/js/app.js b/web/src/js/app.js index f2ddc2ae..4ec7d699 100644 --- a/web/src/js/app.js +++ b/web/src/js/app.js @@ -4,7 +4,7 @@ var ReactRouter = require("react-router"); var $ = require("jquery"); var Connection = require("./connection"); -var proxyapp = require("./components/proxyapp.jsx.js"); +var proxyapp = require("./components/proxyapp.js"); $(function () { window.ws = new Connection("/updates"); diff --git a/web/src/js/components/eventlog.js b/web/src/js/components/eventlog.js new file mode 100644 index 00000000..9687096e --- /dev/null +++ b/web/src/js/components/eventlog.js @@ -0,0 +1,155 @@ +var React = require("react"); +var utils = require("./utils.js"); +var VirtualScrollMixin = require("./virtualscroll.js"); +var views = require("../store/view.js"); + +var LogMessage = React.createClass({ + render: function () { + var entry = this.props.entry; + var indicator; + switch (entry.level) { + case "web": + indicator = ; + break; + case "debug": + indicator = ; + break; + default: + indicator = ; + } + return ( +
+ { indicator } {entry.message} +
+ ); + }, + shouldComponentUpdate: function () { + return false; // log entries are immutable. + } +}); + +var EventLogContents = React.createClass({ + mixins: [utils.AutoScrollMixin, VirtualScrollMixin], + getInitialState: function () { + return { + log: [] + }; + }, + componentWillMount: function () { + this.openView(this.props.eventStore); + }, + componentWillUnmount: function () { + this.closeView(); + }, + openView: function (store) { + var view = new views.StoreView(store, function (entry) { + return this.props.filter[entry.level]; + }.bind(this)); + this.setState({ + view: view + }); + + view.addListener("add recalculate", this.onEventLogChange); + }, + closeView: function () { + this.state.view.close(); + }, + onEventLogChange: function () { + this.setState({ + log: this.state.view.list + }); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.filter !== this.props.filter) { + this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. + this.state.view.recalculate(); + } + if (nextProps.eventStore !== this.props.eventStore) { + this.closeView(); + this.openView(nextProps.eventStore); + } + }, + getDefaultProps: function () { + return { + rowHeight: 45, + rowHeightMin: 15, + placeholderTagName: "div" + }; + }, + renderRow: function (elem) { + return ; + }, + render: function () { + var rows = this.renderRows(this.state.log); + + return
+            { this.getPlaceholderTop(this.state.log.length) }
+            {rows}
+            { this.getPlaceholderBottom(this.state.log.length) }
+        
; + } +}); + +var ToggleFilter = React.createClass({ + toggle: function (e) { + e.preventDefault(); + return this.props.toggleLevel(this.props.name); + }, + render: function () { + var className = "label "; + if (this.props.active) { + className += "label-primary"; + } else { + className += "label-default"; + } + return ( + + {this.props.name} + + ); + } +}); + +var EventLog = React.createClass({ + getInitialState: function () { + return { + filter: { + "debug": false, + "info": true, + "web": true + } + }; + }, + close: function () { + var d = {}; + d[Query.SHOW_EVENTLOG] = undefined; + this.setQuery(d); + }, + toggleLevel: function (level) { + var filter = _.extend({}, this.state.filter); + filter[level] = !filter[level]; + this.setState({filter: filter}); + }, + render: function () { + return ( +
+
+ Eventlog +
+ + + + +
+ +
+ +
+ ); + } +}); + +module.exports = EventLog; \ No newline at end of file diff --git a/web/src/js/components/eventlog.jsx.js b/web/src/js/components/eventlog.jsx.js deleted file mode 100644 index 462b197a..00000000 --- a/web/src/js/components/eventlog.jsx.js +++ /dev/null @@ -1,155 +0,0 @@ -var React = require("react"); -var utils = require("./utils.jsx.js"); -var VirtualScrollMixin = require("./virtualscroll.jsx.js"); -var views = require("../store/view.js"); - -var LogMessage = React.createClass({ - render: function () { - var entry = this.props.entry; - var indicator; - switch (entry.level) { - case "web": - indicator = ; - break; - case "debug": - indicator = ; - break; - default: - indicator = ; - } - return ( -
- { indicator } {entry.message} -
- ); - }, - shouldComponentUpdate: function () { - return false; // log entries are immutable. - } -}); - -var EventLogContents = React.createClass({ - mixins: [utils.AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - return { - log: [] - }; - }, - componentWillMount: function () { - this.openView(this.props.eventStore); - }, - componentWillUnmount: function () { - this.closeView(); - }, - openView: function (store) { - var view = new views.StoreView(store, function (entry) { - return this.props.filter[entry.level]; - }.bind(this)); - this.setState({ - view: view - }); - - view.addListener("add recalculate", this.onEventLogChange); - }, - closeView: function () { - this.state.view.close(); - }, - onEventLogChange: function () { - this.setState({ - log: this.state.view.list - }); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.filter !== this.props.filter) { - this.props.filter = nextProps.filter; // Dirty: Make sure that view filter sees the update. - this.state.view.recalculate(); - } - if (nextProps.eventStore !== this.props.eventStore) { - this.closeView(); - this.openView(nextProps.eventStore); - } - }, - getDefaultProps: function () { - return { - rowHeight: 45, - rowHeightMin: 15, - placeholderTagName: "div" - }; - }, - renderRow: function (elem) { - return ; - }, - render: function () { - var rows = this.renderRows(this.state.log); - - return
-            { this.getPlaceholderTop(this.state.log.length) }
-            {rows}
-            { this.getPlaceholderBottom(this.state.log.length) }
-        
; - } -}); - -var ToggleFilter = React.createClass({ - toggle: function (e) { - e.preventDefault(); - return this.props.toggleLevel(this.props.name); - }, - render: function () { - var className = "label "; - if (this.props.active) { - className += "label-primary"; - } else { - className += "label-default"; - } - return ( - - {this.props.name} - - ); - } -}); - -var EventLog = React.createClass({ - getInitialState: function () { - return { - filter: { - "debug": false, - "info": true, - "web": true - } - }; - }, - close: function () { - var d = {}; - d[Query.SHOW_EVENTLOG] = undefined; - this.setQuery(d); - }, - toggleLevel: function (level) { - var filter = _.extend({}, this.state.filter); - filter[level] = !filter[level]; - this.setState({filter: filter}); - }, - render: function () { - return ( -
-
- Eventlog -
- - - - -
- -
- -
- ); - } -}); - -module.exports = EventLog; \ No newline at end of file diff --git a/web/src/js/components/flowdetail.js b/web/src/js/components/flowdetail.js new file mode 100644 index 00000000..ecb476e9 --- /dev/null +++ b/web/src/js/components/flowdetail.js @@ -0,0 +1,399 @@ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("./utils.js"); +var actions = require("../actions.js"); +var flowutils = require("../flow/utils.js"); +var toputils = require("../utils.js"); + +var NavAction = React.createClass({ + onClick: function (e) { + e.preventDefault(); + this.props.onClick(); + }, + render: function () { + return ( + + + + ); + } +}); + +var FlowDetailNav = React.createClass({ + render: function () { + var flow = this.props.flow; + + var tabs = this.props.tabs.map(function (e) { + var str = e.charAt(0).toUpperCase() + e.slice(1); + var className = this.props.active === e ? "active" : ""; + var onClick = function (event) { + this.props.selectTab(e); + event.preventDefault(); + }.bind(this); + return {str}; + }.bind(this)); + + var acceptButton = null; + if(flow.intercepted){ + acceptButton = ; + } + var revertButton = null; + if(flow.modified){ + revertButton = ; + } + + return ( + + ); + } +}); + +var Headers = React.createClass({ + render: function () { + var rows = this.props.message.headers.map(function (header, i) { + return ( + + {header[0] + ":"} + {header[1]} + + ); + }); + return ( + + + {rows} + +
+ ); + } +}); + +var FlowDetailRequest = React.createClass({ + render: function () { + var flow = this.props.flow; + var first_line = [ + flow.request.method, + flowutils.RequestUtils.pretty_url(flow.request), + "HTTP/" + flow.request.httpversion.join(".") + ].join(" "); + var content = null; + if (flow.request.contentLength > 0) { + content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); + } else { + content =
No Content
; + } + + //TODO: Styling + + return ( +
+
{ first_line }
+ +
+ {content} +
+ ); + } +}); + +var FlowDetailResponse = React.createClass({ + render: function () { + var flow = this.props.flow; + var first_line = [ + "HTTP/" + flow.response.httpversion.join("."), + flow.response.code, + flow.response.msg + ].join(" "); + var content = null; + if (flow.response.contentLength > 0) { + content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); + } else { + content =
No Content
; + } + + //TODO: Styling + + return ( +
+
{ first_line }
+ +
+ {content} +
+ ); + } +}); + +var FlowDetailError = React.createClass({ + render: function () { + var flow = this.props.flow; + return ( +
+
+ {flow.error.msg} +
+ { toputils.formatTimeStamp(flow.error.timestamp) } +
+
+
+ ); + } +}); + +var TimeStamp = React.createClass({ + render: function () { + + if (!this.props.t) { + //should be return null, but that triggers a React bug. + return ; + } + + var ts = toputils.formatTimeStamp(this.props.t); + + var delta; + if (this.props.deltaTo) { + delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); + delta = {"(" + delta + ")"}; + } else { + delta = null; + } + + return + {this.props.title + ":"} + {ts} {delta} + ; + } +}); + +var ConnectionInfo = React.createClass({ + + render: function () { + var conn = this.props.conn; + var address = conn.address.address.join(":"); + + var sni = ; //should be null, but that triggers a React bug. + if (conn.sni) { + sni = + + TLS SNI: + + {conn.sni} + ; + } + return ( + + + + + + + {sni} + +
Address:{address}
+ ); + } +}); + +var CertificateInfo = React.createClass({ + render: function () { + //TODO: We should fetch human-readable certificate representation + // from the server + var flow = this.props.flow; + var client_conn = flow.client_conn; + var server_conn = flow.server_conn; + + var preStyle = {maxHeight: 100}; + return ( +
+ {client_conn.cert ?

Client Certificate

: null} + {client_conn.cert ?
{client_conn.cert}
: null} + + {server_conn.cert ?

Server Certificate

: null} + {server_conn.cert ?
{server_conn.cert}
: null} +
+ ); + } +}); + +var Timing = React.createClass({ + render: function () { + var flow = this.props.flow; + var sc = flow.server_conn; + var cc = flow.client_conn; + var req = flow.request; + var resp = flow.response; + + var timestamps = [ + { + title: "Server conn. initiated", + t: sc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Server conn. TCP handshake", + t: sc.timestamp_tcp_setup, + deltaTo: req.timestamp_start + }, { + title: "Server conn. SSL handshake", + t: sc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "Client conn. established", + t: cc.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Client conn. SSL handshake", + t: cc.timestamp_ssl_setup, + deltaTo: req.timestamp_start + }, { + title: "First request byte", + t: req.timestamp_start, + }, { + title: "Request complete", + t: req.timestamp_end, + deltaTo: req.timestamp_start + } + ]; + + if (flow.response) { + timestamps.push( + { + title: "First response byte", + t: resp.timestamp_start, + deltaTo: req.timestamp_start + }, { + title: "Response complete", + t: resp.timestamp_end, + deltaTo: req.timestamp_start + } + ); + } + + //Add unique key for each row. + timestamps.forEach(function (e) { + e.key = e.title; + }); + + timestamps = _.sortBy(timestamps, 't'); + + var rows = timestamps.map(function (e) { + return ; + }); + + return ( +
+

Timing

+ + + {rows} + +
+
+ ); + } +}); + +var FlowDetailConnectionInfo = React.createClass({ + render: function () { + var flow = this.props.flow; + var client_conn = flow.client_conn; + var server_conn = flow.server_conn; + return ( +
+ +

Client Connection

+ + +

Server Connection

+ + + + + + +
+ ); + } +}); + +var allTabs = { + request: FlowDetailRequest, + response: FlowDetailResponse, + error: FlowDetailError, + details: FlowDetailConnectionInfo +}; + +var FlowDetail = React.createClass({ + mixins: [utils.StickyHeadMixin, utils.Navigation, utils.State], + getTabs: function (flow) { + var tabs = []; + ["request", "response", "error"].forEach(function (e) { + if (flow[e]) { + tabs.push(e); + } + }); + tabs.push("details"); + return tabs; + }, + nextTab: function (i) { + var tabs = this.getTabs(this.props.flow); + var currentIndex = tabs.indexOf(this.getParams().detailTab); + // 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( + "flow", + { + flowId: this.getParams().flowId, + detailTab: panel + } + ); + }, + render: function () { + var flow = this.props.flow; + var tabs = this.getTabs(flow); + var active = this.getParams().detailTab; + + if (!_.contains(tabs, active)) { + if (active === "response" && flow.error) { + active = "error"; + } else if (active === "error" && flow.response) { + active = "response"; + } else { + active = tabs[0]; + } + this.selectTab(active); + } + + var Tab = allTabs[active]; + return ( +
+ + +
+ ); + } +}); + +module.exports = { + FlowDetail: FlowDetail +}; \ No newline at end of file diff --git a/web/src/js/components/flowdetail.jsx.js b/web/src/js/components/flowdetail.jsx.js deleted file mode 100644 index 9e88a6e6..00000000 --- a/web/src/js/components/flowdetail.jsx.js +++ /dev/null @@ -1,399 +0,0 @@ -var React = require("react"); -var _ = require("lodash"); - -var utils = require("./utils.jsx.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); - -var NavAction = React.createClass({ - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); - }, - render: function () { - return ( - - - - ); - } -}); - -var FlowDetailNav = React.createClass({ - render: function () { - var flow = this.props.flow; - - var tabs = this.props.tabs.map(function (e) { - var str = e.charAt(0).toUpperCase() + e.slice(1); - var className = this.props.active === e ? "active" : ""; - var onClick = function (event) { - this.props.selectTab(e); - event.preventDefault(); - }.bind(this); - return {str}; - }.bind(this)); - - var acceptButton = null; - if(flow.intercepted){ - acceptButton = ; - } - var revertButton = null; - if(flow.modified){ - revertButton = ; - } - - return ( - - ); - } -}); - -var Headers = React.createClass({ - render: function () { - var rows = this.props.message.headers.map(function (header, i) { - return ( - - {header[0] + ":"} - {header[1]} - - ); - }); - return ( - - - {rows} - -
- ); - } -}); - -var FlowDetailRequest = React.createClass({ - render: function () { - var flow = this.props.flow; - var first_line = [ - flow.request.method, - flowutils.RequestUtils.pretty_url(flow.request), - "HTTP/" + flow.request.httpversion.join(".") - ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + toputils.formatSize(flow.request.contentLength); - } else { - content =
No Content
; - } - - //TODO: Styling - - return ( -
-
{ first_line }
- -
- {content} -
- ); - } -}); - -var FlowDetailResponse = React.createClass({ - render: function () { - var flow = this.props.flow; - var first_line = [ - "HTTP/" + flow.response.httpversion.join("."), - flow.response.code, - flow.response.msg - ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + toputils.formatSize(flow.response.contentLength); - } else { - content =
No Content
; - } - - //TODO: Styling - - return ( -
-
{ first_line }
- -
- {content} -
- ); - } -}); - -var FlowDetailError = React.createClass({ - render: function () { - var flow = this.props.flow; - return ( -
-
- {flow.error.msg} -
- { toputils.formatTimeStamp(flow.error.timestamp) } -
-
-
- ); - } -}); - -var TimeStamp = React.createClass({ - render: function () { - - if (!this.props.t) { - //should be return null, but that triggers a React bug. - return ; - } - - var ts = toputils.formatTimeStamp(this.props.t); - - var delta; - if (this.props.deltaTo) { - delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); - delta = {"(" + delta + ")"}; - } else { - delta = null; - } - - return - {this.props.title + ":"} - {ts} {delta} - ; - } -}); - -var ConnectionInfo = React.createClass({ - - render: function () { - var conn = this.props.conn; - var address = conn.address.address.join(":"); - - var sni = ; //should be null, but that triggers a React bug. - if (conn.sni) { - sni = - - TLS SNI: - - {conn.sni} - ; - } - return ( - - - - - - - {sni} - -
Address:{address}
- ); - } -}); - -var CertificateInfo = React.createClass({ - render: function () { - //TODO: We should fetch human-readable certificate representation - // from the server - var flow = this.props.flow; - var client_conn = flow.client_conn; - var server_conn = flow.server_conn; - - var preStyle = {maxHeight: 100}; - return ( -
- {client_conn.cert ?

Client Certificate

: null} - {client_conn.cert ?
{client_conn.cert}
: null} - - {server_conn.cert ?

Server Certificate

: null} - {server_conn.cert ?
{server_conn.cert}
: null} -
- ); - } -}); - -var Timing = React.createClass({ - render: function () { - var flow = this.props.flow; - var sc = flow.server_conn; - var cc = flow.client_conn; - var req = flow.request; - var resp = flow.response; - - var timestamps = [ - { - title: "Server conn. initiated", - t: sc.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Server conn. TCP handshake", - t: sc.timestamp_tcp_setup, - deltaTo: req.timestamp_start - }, { - title: "Server conn. SSL handshake", - t: sc.timestamp_ssl_setup, - deltaTo: req.timestamp_start - }, { - title: "Client conn. established", - t: cc.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Client conn. SSL handshake", - t: cc.timestamp_ssl_setup, - deltaTo: req.timestamp_start - }, { - title: "First request byte", - t: req.timestamp_start, - }, { - title: "Request complete", - t: req.timestamp_end, - deltaTo: req.timestamp_start - } - ]; - - if (flow.response) { - timestamps.push( - { - title: "First response byte", - t: resp.timestamp_start, - deltaTo: req.timestamp_start - }, { - title: "Response complete", - t: resp.timestamp_end, - deltaTo: req.timestamp_start - } - ); - } - - //Add unique key for each row. - timestamps.forEach(function (e) { - e.key = e.title; - }); - - timestamps = _.sortBy(timestamps, 't'); - - var rows = timestamps.map(function (e) { - return ; - }); - - return ( -
-

Timing

- - - {rows} - -
-
- ); - } -}); - -var FlowDetailConnectionInfo = React.createClass({ - render: function () { - var flow = this.props.flow; - var client_conn = flow.client_conn; - var server_conn = flow.server_conn; - return ( -
- -

Client Connection

- - -

Server Connection

- - - - - - -
- ); - } -}); - -var allTabs = { - request: FlowDetailRequest, - response: FlowDetailResponse, - error: FlowDetailError, - details: FlowDetailConnectionInfo -}; - -var FlowDetail = React.createClass({ - mixins: [utils.StickyHeadMixin, utils.Navigation, utils.State], - getTabs: function (flow) { - var tabs = []; - ["request", "response", "error"].forEach(function (e) { - if (flow[e]) { - tabs.push(e); - } - }); - tabs.push("details"); - return tabs; - }, - nextTab: function (i) { - var tabs = this.getTabs(this.props.flow); - var currentIndex = tabs.indexOf(this.getParams().detailTab); - // 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( - "flow", - { - flowId: this.getParams().flowId, - detailTab: panel - } - ); - }, - render: function () { - var flow = this.props.flow; - var tabs = this.getTabs(flow); - var active = this.getParams().detailTab; - - if (!_.contains(tabs, active)) { - if (active === "response" && flow.error) { - active = "error"; - } else if (active === "error" && flow.response) { - active = "response"; - } else { - active = tabs[0]; - } - this.selectTab(active); - } - - var Tab = allTabs[active]; - return ( -
- - -
- ); - } -}); - -module.exports = { - FlowDetail: FlowDetail -}; \ No newline at end of file diff --git a/web/src/js/components/flowtable-columns.js b/web/src/js/components/flowtable-columns.js new file mode 100644 index 00000000..39c4bd8d --- /dev/null +++ b/web/src/js/components/flowtable-columns.js @@ -0,0 +1,164 @@ +var React = require("react"); +var flowutils = require("../flow/utils.js"); +var utils = require("../utils.js"); + +var TLSColumn = React.createClass({ + statics: { + renderTitle: function () { + return ; + } + }, + render: function () { + var flow = this.props.flow; + var ssl = (flow.request.scheme == "https"); + var classes; + if (ssl) { + classes = "col-tls col-tls-https"; + } else { + classes = "col-tls col-tls-http"; + } + return ; + } +}); + + +var IconColumn = React.createClass({ + statics: { + renderTitle: function () { + return ; + } + }, + render: function () { + var flow = this.props.flow; + + var icon; + if (flow.response) { + var contentType = flowutils.ResponseUtils.getContentType(flow.response); + + //TODO: We should assign a type to the flow somewhere else. + if (flow.response.code == 304) { + icon = "resource-icon-not-modified"; + } else if (300 <= flow.response.code && flow.response.code < 400) { + icon = "resource-icon-redirect"; + } else if (contentType && contentType.indexOf("image") >= 0) { + icon = "resource-icon-image"; + } else if (contentType && contentType.indexOf("javascript") >= 0) { + icon = "resource-icon-js"; + } else if (contentType && contentType.indexOf("css") >= 0) { + icon = "resource-icon-css"; + } else if (contentType && contentType.indexOf("html") >= 0) { + icon = "resource-icon-document"; + } + } + if (!icon) { + icon = "resource-icon-plain"; + } + + + icon += " resource-icon"; + return +
+ ; + } +}); + +var PathColumn = React.createClass({ + statics: { + renderTitle: function () { + return Path; + } + }, + render: function () { + var flow = this.props.flow; + return + {flow.request.is_replay ? : null} + {flow.intercepted ? : null} + {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 SizeColumn = React.createClass({ + statics: { + renderTitle: function () { + return Size; + } + }, + render: function () { + var flow = this.props.flow; + + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + var size = utils.formatSize(total); + return {size}; + } +}); + + +var TimeColumn = React.createClass({ + statics: { + renderTitle: function () { + return Time; + } + }, + render: function () { + var flow = this.props.flow; + var time; + if (flow.response) { + time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); + } else { + time = "..."; + } + return {time}; + } +}); + + +var all_columns = [ + TLSColumn, + IconColumn, + PathColumn, + MethodColumn, + StatusColumn, + SizeColumn, + TimeColumn]; + + +module.exports = all_columns; + + diff --git a/web/src/js/components/flowtable-columns.jsx.js b/web/src/js/components/flowtable-columns.jsx.js deleted file mode 100644 index 39c4bd8d..00000000 --- a/web/src/js/components/flowtable-columns.jsx.js +++ /dev/null @@ -1,164 +0,0 @@ -var React = require("react"); -var flowutils = require("../flow/utils.js"); -var utils = require("../utils.js"); - -var TLSColumn = React.createClass({ - statics: { - renderTitle: function () { - return ; - } - }, - render: function () { - var flow = this.props.flow; - var ssl = (flow.request.scheme == "https"); - var classes; - if (ssl) { - classes = "col-tls col-tls-https"; - } else { - classes = "col-tls col-tls-http"; - } - return ; - } -}); - - -var IconColumn = React.createClass({ - statics: { - renderTitle: function () { - return ; - } - }, - render: function () { - var flow = this.props.flow; - - var icon; - if (flow.response) { - var contentType = flowutils.ResponseUtils.getContentType(flow.response); - - //TODO: We should assign a type to the flow somewhere else. - if (flow.response.code == 304) { - icon = "resource-icon-not-modified"; - } else if (300 <= flow.response.code && flow.response.code < 400) { - icon = "resource-icon-redirect"; - } else if (contentType && contentType.indexOf("image") >= 0) { - icon = "resource-icon-image"; - } else if (contentType && contentType.indexOf("javascript") >= 0) { - icon = "resource-icon-js"; - } else if (contentType && contentType.indexOf("css") >= 0) { - icon = "resource-icon-css"; - } else if (contentType && contentType.indexOf("html") >= 0) { - icon = "resource-icon-document"; - } - } - if (!icon) { - icon = "resource-icon-plain"; - } - - - icon += " resource-icon"; - return -
- ; - } -}); - -var PathColumn = React.createClass({ - statics: { - renderTitle: function () { - return Path; - } - }, - render: function () { - var flow = this.props.flow; - return - {flow.request.is_replay ? : null} - {flow.intercepted ? : null} - {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 SizeColumn = React.createClass({ - statics: { - renderTitle: function () { - return Size; - } - }, - render: function () { - var flow = this.props.flow; - - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - var size = utils.formatSize(total); - return {size}; - } -}); - - -var TimeColumn = React.createClass({ - statics: { - renderTitle: function () { - return Time; - } - }, - render: function () { - var flow = this.props.flow; - var time; - if (flow.response) { - time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); - } else { - time = "..."; - } - return {time}; - } -}); - - -var all_columns = [ - TLSColumn, - IconColumn, - PathColumn, - MethodColumn, - StatusColumn, - SizeColumn, - TimeColumn]; - - -module.exports = all_columns; - - diff --git a/web/src/js/components/flowtable.js b/web/src/js/components/flowtable.js new file mode 100644 index 00000000..c7ce147c --- /dev/null +++ b/web/src/js/components/flowtable.js @@ -0,0 +1,136 @@ +var React = require("react"); +var utils = require("./utils.js"); +var VirtualScrollMixin = require("./virtualscroll.js"); +var flowtable_columns = require("./flowtable-columns.js"); + +var FlowRow = React.createClass({ + render: function () { + var flow = this.props.flow; + var columns = this.props.columns.map(function (Column) { + return ; + }.bind(this)); + var className = ""; + if (this.props.selected) { + className += " selected"; + } + if (this.props.highlighted) { + className += " highlighted"; + } + if (flow.intercepted) { + className += " intercepted"; + } + if (flow.request) { + className += " has-request"; + } + if (flow.response) { + className += " has-response"; + } + + return ( + + {columns} + ); + }, + shouldComponentUpdate: function (nextProps) { + return true; + // Further optimization could be done here + // by calling forceUpdate on flow updates, selection changes and column changes. + //return ( + //(this.props.columns.length !== nextProps.columns.length) || + //(this.props.selected !== nextProps.selected) + //); + } +}); + +var FlowTableHead = React.createClass({ + render: function () { + var columns = this.props.columns.map(function (column) { + return column.renderTitle(); + }.bind(this)); + return + {columns} + ; + } +}); + + +var ROW_HEIGHT = 32; + +var FlowTable = React.createClass({ + mixins: [utils.StickyHeadMixin, utils.AutoScrollMixin, VirtualScrollMixin], + getInitialState: function () { + return { + columns: flowtable_columns + }; + }, + componentWillMount: function () { + if (this.props.view) { + this.props.view.addListener("add update remove recalculate", this.onChange); + } + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.view !== this.props.view) { + if (this.props.view) { + this.props.view.removeListener("add update remove recalculate"); + } + nextProps.view.addListener("add update remove recalculate", this.onChange); + } + }, + getDefaultProps: function () { + return { + rowHeight: ROW_HEIGHT + }; + }, + onScrollFlowTable: function () { + this.adjustHead(); + this.onScroll(); + }, + onChange: function () { + this.forceUpdate(); + }, + scrollIntoView: function (flow) { + this.scrollRowIntoView( + this.props.view.index(flow), + this.refs.body.getDOMNode().offsetTop + ); + }, + renderRow: function (flow) { + var selected = (flow === this.props.selected); + var highlighted = + ( + this.props.view._highlight && + this.props.view._highlight[flow.id] + ); + + return ; + }, + render: function () { + //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); + var flows = this.props.view ? this.props.view.list : []; + + var rows = this.renderRows(flows); + + return ( +
+ + + + { this.getPlaceholderTop(flows.length) } + {rows} + { this.getPlaceholderBottom(flows.length) } + +
+
+ ); + } +}); + +module.exports = FlowTable; diff --git a/web/src/js/components/flowtable.jsx.js b/web/src/js/components/flowtable.jsx.js deleted file mode 100644 index 90eebbc1..00000000 --- a/web/src/js/components/flowtable.jsx.js +++ /dev/null @@ -1,136 +0,0 @@ -var React = require("react"); -var utils = require("./utils.jsx.js"); -var VirtualScrollMixin = require("./virtualscroll.jsx.js"); -var flowtable_columns = require("./flowtable-columns.jsx.js"); - -var FlowRow = React.createClass({ - render: function () { - var flow = this.props.flow; - var columns = this.props.columns.map(function (Column) { - return ; - }.bind(this)); - var className = ""; - if (this.props.selected) { - className += " selected"; - } - if (this.props.highlighted) { - className += " highlighted"; - } - if (flow.intercepted) { - className += " intercepted"; - } - if (flow.request) { - className += " has-request"; - } - if (flow.response) { - className += " has-response"; - } - - return ( - - {columns} - ); - }, - shouldComponentUpdate: function (nextProps) { - return true; - // Further optimization could be done here - // by calling forceUpdate on flow updates, selection changes and column changes. - //return ( - //(this.props.columns.length !== nextProps.columns.length) || - //(this.props.selected !== nextProps.selected) - //); - } -}); - -var FlowTableHead = React.createClass({ - render: function () { - var columns = this.props.columns.map(function (column) { - return column.renderTitle(); - }.bind(this)); - return - {columns} - ; - } -}); - - -var ROW_HEIGHT = 32; - -var FlowTable = React.createClass({ - mixins: [utils.StickyHeadMixin, utils.AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - return { - columns: flowtable_columns - }; - }, - componentWillMount: function () { - if (this.props.view) { - this.props.view.addListener("add update remove recalculate", this.onChange); - } - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.view !== this.props.view) { - if (this.props.view) { - this.props.view.removeListener("add update remove recalculate"); - } - nextProps.view.addListener("add update remove recalculate", this.onChange); - } - }, - getDefaultProps: function () { - return { - rowHeight: ROW_HEIGHT - }; - }, - onScrollFlowTable: function () { - this.adjustHead(); - this.onScroll(); - }, - onChange: function () { - this.forceUpdate(); - }, - scrollIntoView: function (flow) { - this.scrollRowIntoView( - this.props.view.index(flow), - this.refs.body.getDOMNode().offsetTop - ); - }, - renderRow: function (flow) { - var selected = (flow === this.props.selected); - var highlighted = - ( - this.props.view._highlight && - this.props.view._highlight[flow.id] - ); - - return ; - }, - render: function () { - //console.log("render flowtable", this.state.start, this.state.stop, this.props.selected); - var flows = this.props.view ? this.props.view.list : []; - - var rows = this.renderRows(flows); - - return ( -
- - - - { this.getPlaceholderTop(flows.length) } - {rows} - { this.getPlaceholderBottom(flows.length) } - -
-
- ); - } -}); - -module.exports = FlowTable; diff --git a/web/src/js/components/footer.js b/web/src/js/components/footer.js new file mode 100644 index 00000000..d04fb615 --- /dev/null +++ b/web/src/js/components/footer.js @@ -0,0 +1,17 @@ +var React = require("react"); + +var Footer = React.createClass({ + render: function () { + var mode = this.props.settings.mode; + var intercept = this.props.settings.intercept; + return ( +
+ {mode != "regular" ? {mode} mode : null} +   + {intercept ? Intercept: {intercept} : null} +
+ ); + } +}); + +module.exports = Footer; \ No newline at end of file diff --git a/web/src/js/components/footer.jsx.js b/web/src/js/components/footer.jsx.js deleted file mode 100644 index d04fb615..00000000 --- a/web/src/js/components/footer.jsx.js +++ /dev/null @@ -1,17 +0,0 @@ -var React = require("react"); - -var Footer = React.createClass({ - render: function () { - var mode = this.props.settings.mode; - var intercept = this.props.settings.intercept; - return ( -
- {mode != "regular" ? {mode} mode : null} -   - {intercept ? Intercept: {intercept} : null} -
- ); - } -}); - -module.exports = Footer; \ No newline at end of file diff --git a/web/src/js/components/header.js b/web/src/js/components/header.js new file mode 100644 index 00000000..eb57b1de --- /dev/null +++ b/web/src/js/components/header.js @@ -0,0 +1,389 @@ +var React = require("react"); +var $ = require("jquery"); + +var utils = require("./utils.js"); + +var FilterDocs = React.createClass({ + statics: { + xhr: false, + doc: false + }, + componentWillMount: function () { + if (!FilterDocs.doc) { + FilterDocs.xhr = $.getJSON("/filter-help").done(function (doc) { + FilterDocs.doc = doc; + FilterDocs.xhr = false; + }); + } + if (FilterDocs.xhr) { + FilterDocs.xhr.done(function () { + this.forceUpdate(); + }.bind(this)); + } + }, + render: function () { + if (!FilterDocs.doc) { + return ; + } else { + var commands = FilterDocs.doc.commands.map(function (c) { + return + {c[0].replace(" ", '\u00a0')} + {c[1]} + ; + }); + commands.push( + + + +   mitmproxy docs + + ); + return + {commands} +
; + } + } +}); +var FilterInput = React.createClass({ + 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 + // finalized, hiding the tooltip just as the user clicks on it. + return { + value: this.props.value, + focus: false, + mousefocus: false + }; + }, + componentWillReceiveProps: function (nextProps) { + this.setState({value: nextProps.value}); + }, + onChange: function (e) { + var nextValue = e.target.value; + this.setState({ + value: nextValue + }); + // Only propagate valid filters upwards. + if (this.isValid(nextValue)) { + this.props.onChange(nextValue); + } + }, + isValid: function (filt) { + try { + Filt.parse(filt || this.state.value); + return true; + } catch (e) { + return false; + } + }, + getDesc: function () { + var desc; + try { + desc = Filt.parse(this.state.value).desc; + } catch (e) { + desc = "" + e; + } + if (desc !== "true") { + return desc; + } else { + return ( + + ); + } + }, + onFocus: function () { + this.setState({focus: true}); + }, + onBlur: function () { + this.setState({focus: false}); + }, + onMouseEnter: function () { + this.setState({mousefocus: true}); + }, + onMouseLeave: function () { + this.setState({mousefocus: false}); + }, + onKeyDown: function (e) { + if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) { + this.blur(); + // If closed using ESC/ENTER, hide the tooltip. + this.setState({mousefocus: false}); + } + }, + blur: function () { + this.refs.input.getDOMNode().blur(); + }, + focus: function () { + this.refs.input.getDOMNode().select(); + }, + render: function () { + var isValid = this.isValid(); + var icon = "fa fa-fw fa-" + this.props.type; + var groupClassName = "filter-input input-group" + (isValid ? "" : " has-error"); + + var popover; + if (this.state.focus || this.state.mousefocus) { + popover = ( +
+
+
+ {this.getDesc()} +
+
+ ); + } + + return ( +
+ + + + + {popover} +
+ ); + } +}); + +var MainMenu = React.createClass({ + mixins: [utils.Navigation, utils.State], + statics: { + title: "Start", + route: "flows" + }, + onFilterChange: function (val) { + var d = {}; + d[Query.FILTER] = val; + this.setQuery(d); + }, + onHighlightChange: function (val) { + var d = {}; + d[Query.HIGHLIGHT] = val; + this.setQuery(d); + }, + onInterceptChange: function (val) { + SettingsActions.update({intercept: val}); + }, + render: function () { + var filter = this.getQuery()[Query.FILTER] || ""; + var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; + var intercept = this.props.settings.intercept || ""; + + return ( +
+
+ + + +
+
+
+ ); + } +}); + + +var ViewMenu = React.createClass({ + statics: { + title: "View", + route: "flows" + }, + mixins: [utils.Navigation, utils.State], + toggleEventLog: function () { + var d = {}; + + if (this.getQuery()[Query.SHOW_EVENTLOG]) { + d[Query.SHOW_EVENTLOG] = undefined; + } else { + d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short + } + + this.setQuery(d); + }, + render: function () { + var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; + return ( +
+ + +
+ ); + } +}); + + +var ReportsMenu = React.createClass({ + statics: { + title: "Visualization", + route: "reports" + }, + render: function () { + return
Reports Menu
; + } +}); + +var FileMenu = React.createClass({ + getInitialState: function () { + return { + showFileMenu: false + }; + }, + handleFileClick: function (e) { + e.preventDefault(); + if (!this.state.showFileMenu) { + var close = function () { + this.setState({showFileMenu: false}); + document.removeEventListener("click", close); + }.bind(this); + document.addEventListener("click", close); + + this.setState({ + showFileMenu: true + }); + } + }, + handleNewClick: function (e) { + e.preventDefault(); + if (confirm("Delete all flows?")) { + FlowActions.clear(); + } + }, + handleOpenClick: function (e) { + e.preventDefault(); + console.error("unimplemented: handleOpenClick"); + }, + handleSaveClick: function (e) { + e.preventDefault(); + console.error("unimplemented: handleSaveClick"); + }, + handleShutdownClick: function (e) { + e.preventDefault(); + console.error("unimplemented: handleShutdownClick"); + }, + render: function () { + var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : ""); + + return ( + + ); + } +}); + + +var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; + + +var Header = React.createClass({ + mixins: [utils.Navigation], + getInitialState: function () { + return { + active: header_entries[0] + }; + }, + handleClick: function (active, e) { + e.preventDefault(); + this.replaceWith(active.route); + this.setState({active: active}); + }, + render: function () { + var header = header_entries.map(function (entry, i) { + var classes = React.addons.classSet({ + active: entry == this.state.active + }); + return ( + + { entry.title} + + ); + }.bind(this)); + + return ( +
+
+ mitmproxy { this.props.settings.version } +
+ +
+ +
+
+ ); + } +}); + + +module.exports = { + Header: Header +} \ No newline at end of file diff --git a/web/src/js/components/header.jsx.js b/web/src/js/components/header.jsx.js deleted file mode 100644 index d9fd9ab0..00000000 --- a/web/src/js/components/header.jsx.js +++ /dev/null @@ -1,389 +0,0 @@ -var React = require("react"); -var $ = require("jquery"); - -var utils = require("./utils.jsx.js"); - -var FilterDocs = React.createClass({ - statics: { - xhr: false, - doc: false - }, - componentWillMount: function () { - if (!FilterDocs.doc) { - FilterDocs.xhr = $.getJSON("/filter-help").done(function (doc) { - FilterDocs.doc = doc; - FilterDocs.xhr = false; - }); - } - if (FilterDocs.xhr) { - FilterDocs.xhr.done(function () { - this.forceUpdate(); - }.bind(this)); - } - }, - render: function () { - if (!FilterDocs.doc) { - return ; - } else { - var commands = FilterDocs.doc.commands.map(function (c) { - return - {c[0].replace(" ", '\u00a0')} - {c[1]} - ; - }); - commands.push( - - - -   mitmproxy docs - - ); - return - {commands} -
; - } - } -}); -var FilterInput = React.createClass({ - 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 - // finalized, hiding the tooltip just as the user clicks on it. - return { - value: this.props.value, - focus: false, - mousefocus: false - }; - }, - componentWillReceiveProps: function (nextProps) { - this.setState({value: nextProps.value}); - }, - onChange: function (e) { - var nextValue = e.target.value; - this.setState({ - value: nextValue - }); - // Only propagate valid filters upwards. - if (this.isValid(nextValue)) { - this.props.onChange(nextValue); - } - }, - isValid: function (filt) { - try { - Filt.parse(filt || this.state.value); - return true; - } catch (e) { - return false; - } - }, - getDesc: function () { - var desc; - try { - desc = Filt.parse(this.state.value).desc; - } catch (e) { - desc = "" + e; - } - if (desc !== "true") { - return desc; - } else { - return ( - - ); - } - }, - onFocus: function () { - this.setState({focus: true}); - }, - onBlur: function () { - this.setState({focus: false}); - }, - onMouseEnter: function () { - this.setState({mousefocus: true}); - }, - onMouseLeave: function () { - this.setState({mousefocus: false}); - }, - onKeyDown: function (e) { - if (e.keyCode === Key.ESC || e.keyCode === Key.ENTER) { - this.blur(); - // If closed using ESC/ENTER, hide the tooltip. - this.setState({mousefocus: false}); - } - }, - blur: function () { - this.refs.input.getDOMNode().blur(); - }, - focus: function () { - this.refs.input.getDOMNode().select(); - }, - render: function () { - var isValid = this.isValid(); - var icon = "fa fa-fw fa-" + this.props.type; - var groupClassName = "filter-input input-group" + (isValid ? "" : " has-error"); - - var popover; - if (this.state.focus || this.state.mousefocus) { - popover = ( -
-
-
- {this.getDesc()} -
-
- ); - } - - return ( -
- - - - - {popover} -
- ); - } -}); - -var MainMenu = React.createClass({ - mixins: [utils.Navigation, utils.State], - statics: { - title: "Start", - route: "flows" - }, - onFilterChange: function (val) { - var d = {}; - d[Query.FILTER] = val; - this.setQuery(d); - }, - onHighlightChange: function (val) { - var d = {}; - d[Query.HIGHLIGHT] = val; - this.setQuery(d); - }, - onInterceptChange: function (val) { - SettingsActions.update({intercept: val}); - }, - render: function () { - var filter = this.getQuery()[Query.FILTER] || ""; - var highlight = this.getQuery()[Query.HIGHLIGHT] || ""; - var intercept = this.props.settings.intercept || ""; - - return ( -
-
- - - -
-
-
- ); - } -}); - - -var ViewMenu = React.createClass({ - statics: { - title: "View", - route: "flows" - }, - mixins: [utils.Navigation, utils.State], - toggleEventLog: function () { - var d = {}; - - if (this.getQuery()[Query.SHOW_EVENTLOG]) { - d[Query.SHOW_EVENTLOG] = undefined; - } else { - d[Query.SHOW_EVENTLOG] = "t"; // any non-false value will do it, keep it short - } - - this.setQuery(d); - }, - render: function () { - var showEventLog = this.getQuery()[Query.SHOW_EVENTLOG]; - return ( -
- - -
- ); - } -}); - - -var ReportsMenu = React.createClass({ - statics: { - title: "Visualization", - route: "reports" - }, - render: function () { - return
Reports Menu
; - } -}); - -var FileMenu = React.createClass({ - getInitialState: function () { - return { - showFileMenu: false - }; - }, - handleFileClick: function (e) { - e.preventDefault(); - if (!this.state.showFileMenu) { - var close = function () { - this.setState({showFileMenu: false}); - document.removeEventListener("click", close); - }.bind(this); - document.addEventListener("click", close); - - this.setState({ - showFileMenu: true - }); - } - }, - handleNewClick: function (e) { - e.preventDefault(); - if (confirm("Delete all flows?")) { - FlowActions.clear(); - } - }, - handleOpenClick: function (e) { - e.preventDefault(); - console.error("unimplemented: handleOpenClick"); - }, - handleSaveClick: function (e) { - e.preventDefault(); - console.error("unimplemented: handleSaveClick"); - }, - handleShutdownClick: function (e) { - e.preventDefault(); - console.error("unimplemented: handleShutdownClick"); - }, - render: function () { - var fileMenuClass = "dropdown pull-left" + (this.state.showFileMenu ? " open" : ""); - - return ( - - ); - } -}); - - -var header_entries = [MainMenu, ViewMenu /*, ReportsMenu */]; - - -var Header = React.createClass({ - mixins: [utils.Navigation], - getInitialState: function () { - return { - active: header_entries[0] - }; - }, - handleClick: function (active, e) { - e.preventDefault(); - this.replaceWith(active.route); - this.setState({active: active}); - }, - render: function () { - var header = header_entries.map(function (entry, i) { - var classes = React.addons.classSet({ - active: entry == this.state.active - }); - return ( - - { entry.title} - - ); - }.bind(this)); - - return ( -
-
- mitmproxy { this.props.settings.version } -
- -
- -
-
- ); - } -}); - - -module.exports = { - Header: Header -} \ No newline at end of file diff --git a/web/src/js/components/mainview.js b/web/src/js/components/mainview.js new file mode 100644 index 00000000..b9836c64 --- /dev/null +++ b/web/src/js/components/mainview.js @@ -0,0 +1,232 @@ +var React = require("react"); + +var utils = require("./utils.js"); +var toputils = require("../utils.js"); +var views = require("../store/view.js"); +var Filt = require("../filt/filt.js"); +FlowTable = require("./flowtable.js"); +var flowdetail = require("./flowdetail.js"); + + +var MainView = React.createClass({ + mixins: [utils.Navigation, utils.State], + getInitialState: function () { + this.onQueryChange(Query.FILTER, function () { + this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); + }.bind(this)); + this.onQueryChange(Query.HIGHLIGHT, function () { + this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); + }.bind(this)); + return { + flows: [] + }; + }, + getViewFilt: function () { + try { + var filt = Filt.parse(this.getQuery()[Query.FILTER] || ""); + var highlightStr = this.getQuery()[Query.HIGHLIGHT]; + var highlight = highlightStr ? Filt.parse(highlightStr) : false; + } catch (e) { + console.error("Error when processing filter: " + e); + } + + return function filter_and_highlight(flow) { + if (!this._highlight) { + this._highlight = {}; + } + this._highlight[flow.id] = highlight && highlight(flow); + return filt(flow); + }; + }, + getViewSort: function () { + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.flowStore !== this.props.flowStore) { + this.closeView(); + this.openView(nextProps.flowStore); + } + }, + openView: function (store) { + var view = new views.StoreView(store, this.getViewFilt(), this.getViewSort()); + this.setState({ + view: view + }); + + view.addListener("recalculate", this.onRecalculate); + view.addListener("add update remove", this.onUpdate); + view.addListener("remove", this.onRemove); + }, + onRecalculate: function () { + this.forceUpdate(); + var selected = this.getSelected(); + if (selected) { + this.refs.flowTable.scrollIntoView(selected); + } + }, + onUpdate: function (flow) { + if (flow.id === this.getParams().flowId) { + this.forceUpdate(); + } + }, + onRemove: function (flow_id, index) { + if (flow_id === this.getParams().flowId) { + var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length -1)]; + this.selectFlow(flow_to_select); + } + }, + closeView: function () { + this.state.view.close(); + }, + componentWillMount: function () { + this.openView(this.props.flowStore); + }, + componentWillUnmount: function () { + this.closeView(); + }, + selectFlow: function (flow) { + if (flow) { + this.replaceWith( + "flow", + { + flowId: flow.id, + detailTab: this.getParams().detailTab || "request" + } + ); + this.refs.flowTable.scrollIntoView(flow); + } else { + this.replaceWith("flows", {}); + } + }, + selectFlowRelative: function (shift) { + var flows = this.state.view.list; + var index; + if (!this.getParams().flowId) { + if (shift > 0) { + index = flows.length - 1; + } else { + index = 0; + } + } else { + var currFlowId = this.getParams().flowId; + var i = flows.length; + while (i--) { + if (flows[i].id === currFlowId) { + index = i; + break; + } + } + index = Math.min( + Math.max(0, index + shift), + flows.length - 1); + } + this.selectFlow(flows[index]); + }, + onKeyDown: function (e) { + var flow = this.getSelected(); + if (e.ctrlKey) { + return; + } + switch (e.keyCode) { + case toputils.Key.K: + case toputils.Key.UP: + this.selectFlowRelative(-1); + break; + case toputils.Key.J: + case toputils.Key.DOWN: + this.selectFlowRelative(+1); + break; + case toputils.Key.SPACE: + case toputils.Key.PAGE_DOWN: + this.selectFlowRelative(+10); + break; + case toputils.Key.PAGE_UP: + this.selectFlowRelative(-10); + break; + case toputils.Key.END: + this.selectFlowRelative(+1e10); + break; + case toputils.Key.HOME: + this.selectFlowRelative(-1e10); + break; + case toputils.Key.ESC: + this.selectFlow(null); + break; + case toputils.Key.H: + case toputils.Key.LEFT: + if (this.refs.flowDetails) { + this.refs.flowDetails.nextTab(-1); + } + break; + case toputils.Key.L: + case toputils.Key.TAB: + case toputils.Key.RIGHT: + if (this.refs.flowDetails) { + this.refs.flowDetails.nextTab(+1); + } + break; + case toputils.Key.C: + if (e.shiftKey) { + FlowActions.clear(); + } + break; + case toputils.Key.D: + if (flow) { + if (e.shiftKey) { + FlowActions.duplicate(flow); + } else { + FlowActions.delete(flow); + } + } + break; + case toputils.Key.A: + if (e.shiftKey) { + FlowActions.accept_all(); + } else if (flow && flow.intercepted) { + FlowActions.accept(flow); + } + break; + case toputils.Key.R: + if (!e.shiftKey && flow) { + FlowActions.replay(flow); + } + break; + case toputils.Key.V: + if(e.shiftKey && flow && flow.modified) { + FlowActions.revert(flow); + } + break; + default: + console.debug("keydown", e.keyCode); + return; + } + e.preventDefault(); + }, + getSelected: function () { + return this.props.flowStore.get(this.getParams().flowId); + }, + render: function () { + var selected = this.getSelected(); + + var details; + if (selected) { + details = [ + , + + ]; + } else { + details = null; + } + + return ( +
+ + {details} +
+ ); + } +}); + +module.exports = MainView; diff --git a/web/src/js/components/mainview.jsx.js b/web/src/js/components/mainview.jsx.js deleted file mode 100644 index d2b4d2f9..00000000 --- a/web/src/js/components/mainview.jsx.js +++ /dev/null @@ -1,232 +0,0 @@ -var React = require("react"); - -var utils = require("./utils.jsx.js"); -var toputils = require("../utils.js"); -var views = require("../store/view.js"); -var Filt = require("../filt/filt.js"); -FlowTable = require("./flowtable.jsx.js"); -var flowdetail = require("./flowdetail.jsx.js"); - - -var MainView = React.createClass({ - mixins: [utils.Navigation, utils.State], - getInitialState: function () { - this.onQueryChange(Query.FILTER, function () { - this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); - }.bind(this)); - this.onQueryChange(Query.HIGHLIGHT, function () { - this.state.view.recalculate(this.getViewFilt(), this.getViewSort()); - }.bind(this)); - return { - flows: [] - }; - }, - getViewFilt: function () { - try { - var filt = Filt.parse(this.getQuery()[Query.FILTER] || ""); - var highlightStr = this.getQuery()[Query.HIGHLIGHT]; - var highlight = highlightStr ? Filt.parse(highlightStr) : false; - } catch (e) { - console.error("Error when processing filter: " + e); - } - - return function filter_and_highlight(flow) { - if (!this._highlight) { - this._highlight = {}; - } - this._highlight[flow.id] = highlight && highlight(flow); - return filt(flow); - }; - }, - getViewSort: function () { - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.flowStore !== this.props.flowStore) { - this.closeView(); - this.openView(nextProps.flowStore); - } - }, - openView: function (store) { - var view = new views.StoreView(store, this.getViewFilt(), this.getViewSort()); - this.setState({ - view: view - }); - - view.addListener("recalculate", this.onRecalculate); - view.addListener("add update remove", this.onUpdate); - view.addListener("remove", this.onRemove); - }, - onRecalculate: function () { - this.forceUpdate(); - var selected = this.getSelected(); - if (selected) { - this.refs.flowTable.scrollIntoView(selected); - } - }, - onUpdate: function (flow) { - if (flow.id === this.getParams().flowId) { - this.forceUpdate(); - } - }, - onRemove: function (flow_id, index) { - if (flow_id === this.getParams().flowId) { - var flow_to_select = this.state.view.list[Math.min(index, this.state.view.list.length -1)]; - this.selectFlow(flow_to_select); - } - }, - closeView: function () { - this.state.view.close(); - }, - componentWillMount: function () { - this.openView(this.props.flowStore); - }, - componentWillUnmount: function () { - this.closeView(); - }, - selectFlow: function (flow) { - if (flow) { - this.replaceWith( - "flow", - { - flowId: flow.id, - detailTab: this.getParams().detailTab || "request" - } - ); - this.refs.flowTable.scrollIntoView(flow); - } else { - this.replaceWith("flows", {}); - } - }, - selectFlowRelative: function (shift) { - var flows = this.state.view.list; - var index; - if (!this.getParams().flowId) { - if (shift > 0) { - index = flows.length - 1; - } else { - index = 0; - } - } else { - var currFlowId = this.getParams().flowId; - var i = flows.length; - while (i--) { - if (flows[i].id === currFlowId) { - index = i; - break; - } - } - index = Math.min( - Math.max(0, index + shift), - flows.length - 1); - } - this.selectFlow(flows[index]); - }, - onKeyDown: function (e) { - var flow = this.getSelected(); - if (e.ctrlKey) { - return; - } - switch (e.keyCode) { - case toputils.Key.K: - case toputils.Key.UP: - this.selectFlowRelative(-1); - break; - case toputils.Key.J: - case toputils.Key.DOWN: - this.selectFlowRelative(+1); - break; - case toputils.Key.SPACE: - case toputils.Key.PAGE_DOWN: - this.selectFlowRelative(+10); - break; - case toputils.Key.PAGE_UP: - this.selectFlowRelative(-10); - break; - case toputils.Key.END: - this.selectFlowRelative(+1e10); - break; - case toputils.Key.HOME: - this.selectFlowRelative(-1e10); - break; - case toputils.Key.ESC: - this.selectFlow(null); - break; - case toputils.Key.H: - case toputils.Key.LEFT: - if (this.refs.flowDetails) { - this.refs.flowDetails.nextTab(-1); - } - break; - case toputils.Key.L: - case toputils.Key.TAB: - case toputils.Key.RIGHT: - if (this.refs.flowDetails) { - this.refs.flowDetails.nextTab(+1); - } - break; - case toputils.Key.C: - if (e.shiftKey) { - FlowActions.clear(); - } - break; - case toputils.Key.D: - if (flow) { - if (e.shiftKey) { - FlowActions.duplicate(flow); - } else { - FlowActions.delete(flow); - } - } - break; - case toputils.Key.A: - if (e.shiftKey) { - FlowActions.accept_all(); - } else if (flow && flow.intercepted) { - FlowActions.accept(flow); - } - break; - case toputils.Key.R: - if (!e.shiftKey && flow) { - FlowActions.replay(flow); - } - break; - case toputils.Key.V: - if(e.shiftKey && flow && flow.modified) { - FlowActions.revert(flow); - } - break; - default: - console.debug("keydown", e.keyCode); - return; - } - e.preventDefault(); - }, - getSelected: function () { - return this.props.flowStore.get(this.getParams().flowId); - }, - render: function () { - var selected = this.getSelected(); - - var details; - if (selected) { - details = [ - , - - ]; - } else { - details = null; - } - - return ( -
- - {details} -
- ); - } -}); - -module.exports = MainView; diff --git a/web/src/js/components/proxyapp.js b/web/src/js/components/proxyapp.js new file mode 100644 index 00000000..6d235b50 --- /dev/null +++ b/web/src/js/components/proxyapp.js @@ -0,0 +1,92 @@ +var React = require("react"); +var ReactRouter = require("react-router"); +var _ = require("lodash"); + +var utils = require("./utils.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"); + + +//TODO: Move out of here, just a stub. +var Reports = React.createClass({ + render: function () { + return
ReportEditor
; + } +}); + + +var ProxyAppMain = React.createClass({ + mixins: [utils.State], + getInitialState: function () { + var eventStore = new store.EventLogStore(); + var flowStore = new store.FlowStore(); + var settings = new store.SettingsStore(); + + // Default Settings before fetch + _.extend(settings.dict,{ + }); + return { + settings: settings, + flowStore: flowStore, + eventStore: eventStore + }; + }, + componentDidMount: function () { + this.state.settings.addListener("recalculate", this.onSettingsChange); + window.app = this; + }, + componentWillUnmount: function () { + this.state.settings.removeListener("recalculate", this.onSettingsChange); + }, + onSettingsChange: function(){ + this.setState({ + settings: this.state.settings + }); + }, + render: function () { + + var eventlog; + if (this.getQuery()[Query.SHOW_EVENTLOG]) { + eventlog = [ + , + + ]; + } else { + eventlog = null; + } + + return ( +
+ + + {eventlog} +
+
+ ); + } +}); + + +var Route = ReactRouter.Route; +var RouteHandler = ReactRouter.RouteHandler; +var Redirect = ReactRouter.Redirect; +var DefaultRoute = ReactRouter.DefaultRoute; +var NotFoundRoute = ReactRouter.NotFoundRoute; + + +var routes = ( + + + + + + +); + +module.exports = { + routes: routes +}; + diff --git a/web/src/js/components/proxyapp.jsx.js b/web/src/js/components/proxyapp.jsx.js deleted file mode 100644 index ce313f78..00000000 --- a/web/src/js/components/proxyapp.jsx.js +++ /dev/null @@ -1,92 +0,0 @@ -var React = require("react"); -var ReactRouter = require("react-router"); -var _ = require("lodash"); - -var utils = require("./utils.jsx.js"); -var MainView = require("./mainview.jsx.js"); -var Footer = require("./footer.jsx.js"); -var header = require("./header.jsx.js"); -var EventLog = require("./eventlog.jsx.js"); -var store = require("../store/store.js"); - - -//TODO: Move out of here, just a stub. -var Reports = React.createClass({ - render: function () { - return
ReportEditor
; - } -}); - - -var ProxyAppMain = React.createClass({ - mixins: [utils.State], - getInitialState: function () { - var eventStore = new store.EventLogStore(); - var flowStore = new store.FlowStore(); - var settings = new store.SettingsStore(); - - // Default Settings before fetch - _.extend(settings.dict,{ - }); - return { - settings: settings, - flowStore: flowStore, - eventStore: eventStore - }; - }, - componentDidMount: function () { - this.state.settings.addListener("recalculate", this.onSettingsChange); - window.app = this; - }, - componentWillUnmount: function () { - this.state.settings.removeListener("recalculate", this.onSettingsChange); - }, - onSettingsChange: function(){ - this.setState({ - settings: this.state.settings - }); - }, - render: function () { - - var eventlog; - if (this.getQuery()[Query.SHOW_EVENTLOG]) { - eventlog = [ - , - - ]; - } else { - eventlog = null; - } - - return ( -
- - - {eventlog} -
-
- ); - } -}); - - -var Route = ReactRouter.Route; -var RouteHandler = ReactRouter.RouteHandler; -var Redirect = ReactRouter.Redirect; -var DefaultRoute = ReactRouter.DefaultRoute; -var NotFoundRoute = ReactRouter.NotFoundRoute; - - -var routes = ( - - - - - - -); - -module.exports = { - routes: routes -}; - diff --git a/web/src/js/components/utils.js b/web/src/js/components/utils.js new file mode 100644 index 00000000..9afcfbc7 --- /dev/null +++ b/web/src/js/components/utils.js @@ -0,0 +1,196 @@ +var React = require("react"); +var ReactRouter = require("react-router"); +var _ = require("lodash"); + +//React utils. For other utilities, see ../utils.js + +// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) +var AutoScrollMixin = { + componentWillUpdate: function () { + var node = this.getDOMNode(); + this._shouldScrollBottom = ( + node.scrollTop !== 0 && + node.scrollTop + node.clientHeight === node.scrollHeight + ); + }, + componentDidUpdate: function () { + if (this._shouldScrollBottom) { + var node = this.getDOMNode(); + node.scrollTop = node.scrollHeight; + } + }, +}; + + +var StickyHeadMixin = { + adjustHead: function () { + // Abusing CSS transforms to set the element + // referenced as head into some kind of position:sticky. + var head = this.refs.head.getDOMNode(); + head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; + } +}; + + +var Navigation = _.extend({}, ReactRouter.Navigation, { + setQuery: function (dict) { + var q = this.context.getCurrentQuery(); + for(var i in dict){ + if(dict.hasOwnProperty(i)){ + q[i] = dict[i] || undefined; //falsey values shall be removed. + } + } + q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 + this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + }, + replaceWith: function(routeNameOrPath, params, query) { + if(routeNameOrPath === undefined){ + routeNameOrPath = this.context.getCurrentPath(); + } + if(params === undefined){ + params = this.context.getCurrentParams(); + } + if(query === undefined){ + query = this.context.getCurrentQuery(); + } + ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + } +}); +_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); + +var State = _.extend({}, ReactRouter.State, { + getInitialState: function () { + this._query = this.context.getCurrentQuery(); + this._queryWatches = []; + return null; + }, + onQueryChange: function (key, callback) { + this._queryWatches.push({ + key: key, + callback: callback + }); + }, + componentWillReceiveProps: function (nextProps, nextState) { + var q = this.context.getCurrentQuery(); + for (var i = 0; i < this._queryWatches.length; i++) { + var watch = this._queryWatches[i]; + if (this._query[watch.key] !== q[watch.key]) { + watch.callback(this._query[watch.key], q[watch.key], watch.key); + } + } + this._query = q; + } +}); + +var Splitter = React.createClass({ + getDefaultProps: function () { + return { + axis: "x" + }; + }, + getInitialState: function () { + return { + applied: false, + startX: false, + startY: false + }; + }, + onMouseDown: function (e) { + this.setState({ + startX: e.pageX, + startY: e.pageY + }); + window.addEventListener("mousemove", this.onMouseMove); + window.addEventListener("mouseup", this.onMouseUp); + // Occasionally, only a dragEnd event is triggered, but no mouseUp. + window.addEventListener("dragend", this.onDragEnd); + }, + onDragEnd: function () { + this.getDOMNode().style.transform = ""; + window.removeEventListener("dragend", this.onDragEnd); + window.removeEventListener("mouseup", this.onMouseUp); + window.removeEventListener("mousemove", this.onMouseMove); + }, + onMouseUp: function (e) { + this.onDragEnd(); + + var node = this.getDOMNode(); + var prev = node.previousElementSibling; + var next = node.nextElementSibling; + + var dX = e.pageX - this.state.startX; + var dY = e.pageY - this.state.startY; + var flexBasis; + if (this.props.axis === "x") { + flexBasis = prev.offsetWidth + dX; + } else { + flexBasis = prev.offsetHeight + dY; + } + + prev.style.flex = "0 0 " + Math.max(0, flexBasis) + "px"; + next.style.flex = "1 1 auto"; + + this.setState({ + applied: true + }); + this.onResize(); + }, + onMouseMove: function (e) { + var dX = 0, dY = 0; + if (this.props.axis === "x") { + dX = e.pageX - this.state.startX; + } else { + dY = e.pageY - this.state.startY; + } + this.getDOMNode().style.transform = "translate(" + dX + "px," + dY + "px)"; + }, + onResize: function () { + // Trigger a global resize event. This notifies components that employ virtual scrolling + // that their viewport may have changed. + window.setTimeout(function () { + window.dispatchEvent(new CustomEvent("resize")); + }, 1); + }, + reset: function (willUnmount) { + if (!this.state.applied) { + return; + } + var node = this.getDOMNode(); + var prev = node.previousElementSibling; + var next = node.nextElementSibling; + + prev.style.flex = ""; + next.style.flex = ""; + + if (!willUnmount) { + this.setState({ + applied: false + }); + } + this.onResize(); + }, + componentWillUnmount: function () { + this.reset(true); + }, + render: function () { + var className = "splitter"; + if (this.props.axis === "x") { + className += " splitter-x"; + } else { + className += " splitter-y"; + } + return ( +
+
+
+ ); + } +}); + +module.exports = { + State: State, + Navigation: Navigation, + StickyHeadMixin: StickyHeadMixin, + AutoScrollMixin: AutoScrollMixin, + Splitter: Splitter +} \ No newline at end of file diff --git a/web/src/js/components/utils.jsx.js b/web/src/js/components/utils.jsx.js deleted file mode 100644 index 9afcfbc7..00000000 --- a/web/src/js/components/utils.jsx.js +++ /dev/null @@ -1,196 +0,0 @@ -var React = require("react"); -var ReactRouter = require("react-router"); -var _ = require("lodash"); - -//React utils. For other utilities, see ../utils.js - -// http://blog.vjeux.com/2013/javascript/scroll-position-with-react.html (also contains inverse example) -var AutoScrollMixin = { - componentWillUpdate: function () { - var node = this.getDOMNode(); - this._shouldScrollBottom = ( - node.scrollTop !== 0 && - node.scrollTop + node.clientHeight === node.scrollHeight - ); - }, - componentDidUpdate: function () { - if (this._shouldScrollBottom) { - var node = this.getDOMNode(); - node.scrollTop = node.scrollHeight; - } - }, -}; - - -var StickyHeadMixin = { - adjustHead: function () { - // Abusing CSS transforms to set the element - // referenced as head into some kind of position:sticky. - var head = this.refs.head.getDOMNode(); - head.style.transform = "translate(0," + this.getDOMNode().scrollTop + "px)"; - } -}; - - -var Navigation = _.extend({}, ReactRouter.Navigation, { - setQuery: function (dict) { - var q = this.context.getCurrentQuery(); - for(var i in dict){ - if(dict.hasOwnProperty(i)){ - q[i] = dict[i] || undefined; //falsey values shall be removed. - } - } - q._ = "_"; // workaround for https://github.com/rackt/react-router/pull/599 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); - }, - replaceWith: function(routeNameOrPath, params, query) { - if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); - } - if(params === undefined){ - params = this.context.getCurrentParams(); - } - if(query === undefined){ - query = this.context.getCurrentQuery(); - } - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); - } -}); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); - -var State = _.extend({}, ReactRouter.State, { - getInitialState: function () { - this._query = this.context.getCurrentQuery(); - this._queryWatches = []; - return null; - }, - onQueryChange: function (key, callback) { - this._queryWatches.push({ - key: key, - callback: callback - }); - }, - componentWillReceiveProps: function (nextProps, nextState) { - var q = this.context.getCurrentQuery(); - for (var i = 0; i < this._queryWatches.length; i++) { - var watch = this._queryWatches[i]; - if (this._query[watch.key] !== q[watch.key]) { - watch.callback(this._query[watch.key], q[watch.key], watch.key); - } - } - this._query = q; - } -}); - -var Splitter = React.createClass({ - getDefaultProps: function () { - return { - axis: "x" - }; - }, - getInitialState: function () { - return { - applied: false, - startX: false, - startY: false - }; - }, - onMouseDown: function (e) { - this.setState({ - startX: e.pageX, - startY: e.pageY - }); - window.addEventListener("mousemove", this.onMouseMove); - window.addEventListener("mouseup", this.onMouseUp); - // Occasionally, only a dragEnd event is triggered, but no mouseUp. - window.addEventListener("dragend", this.onDragEnd); - }, - onDragEnd: function () { - this.getDOMNode().style.transform = ""; - window.removeEventListener("dragend", this.onDragEnd); - window.removeEventListener("mouseup", this.onMouseUp); - window.removeEventListener("mousemove", this.onMouseMove); - }, - onMouseUp: function (e) { - this.onDragEnd(); - - var node = this.getDOMNode(); - var prev = node.previousElementSibling; - var next = node.nextElementSibling; - - var dX = e.pageX - this.state.startX; - var dY = e.pageY - this.state.startY; - var flexBasis; - if (this.props.axis === "x") { - flexBasis = prev.offsetWidth + dX; - } else { - flexBasis = prev.offsetHeight + dY; - } - - prev.style.flex = "0 0 " + Math.max(0, flexBasis) + "px"; - next.style.flex = "1 1 auto"; - - this.setState({ - applied: true - }); - this.onResize(); - }, - onMouseMove: function (e) { - var dX = 0, dY = 0; - if (this.props.axis === "x") { - dX = e.pageX - this.state.startX; - } else { - dY = e.pageY - this.state.startY; - } - this.getDOMNode().style.transform = "translate(" + dX + "px," + dY + "px)"; - }, - onResize: function () { - // Trigger a global resize event. This notifies components that employ virtual scrolling - // that their viewport may have changed. - window.setTimeout(function () { - window.dispatchEvent(new CustomEvent("resize")); - }, 1); - }, - reset: function (willUnmount) { - if (!this.state.applied) { - return; - } - var node = this.getDOMNode(); - var prev = node.previousElementSibling; - var next = node.nextElementSibling; - - prev.style.flex = ""; - next.style.flex = ""; - - if (!willUnmount) { - this.setState({ - applied: false - }); - } - this.onResize(); - }, - componentWillUnmount: function () { - this.reset(true); - }, - render: function () { - var className = "splitter"; - if (this.props.axis === "x") { - className += " splitter-x"; - } else { - className += " splitter-y"; - } - return ( -
-
-
- ); - } -}); - -module.exports = { - State: State, - Navigation: Navigation, - StickyHeadMixin: StickyHeadMixin, - AutoScrollMixin: AutoScrollMixin, - Splitter: Splitter -} \ No newline at end of file diff --git a/web/src/js/components/virtualscroll.js b/web/src/js/components/virtualscroll.js new file mode 100644 index 00000000..956e1a0b --- /dev/null +++ b/web/src/js/components/virtualscroll.js @@ -0,0 +1,85 @@ +var React = require("react"); + +var VirtualScrollMixin = { + getInitialState: function () { + return { + start: 0, + stop: 0 + }; + }, + componentWillMount: function () { + if (!this.props.rowHeight) { + console.warn("VirtualScrollMixin: No rowHeight specified", this); + } + }, + getPlaceholderTop: function (total) { + var Tag = this.props.placeholderTagName || "tr"; + // When a large trunk of elements is removed from the button, start may be far off the viewport. + // To make this issue less severe, limit the top placeholder to the total number of rows. + var style = { + height: Math.min(this.state.start, total) * this.props.rowHeight + }; + var spacer = ; + + if (this.state.start % 2 === 1) { + // fix even/odd rows + return [spacer, ]; + } else { + return spacer; + } + }, + getPlaceholderBottom: function (total) { + var Tag = this.props.placeholderTagName || "tr"; + var style = { + height: Math.max(0, total - this.state.stop) * this.props.rowHeight + }; + return ; + }, + componentDidMount: function () { + this.onScroll(); + window.addEventListener('resize', this.onScroll); + }, + componentWillUnmount: function(){ + window.removeEventListener('resize', this.onScroll); + }, + onScroll: function () { + var viewport = this.getDOMNode(); + var top = viewport.scrollTop; + var height = viewport.offsetHeight; + var start = Math.floor(top / this.props.rowHeight); + var stop = start + Math.ceil(height / (this.props.rowHeightMin || this.props.rowHeight)); + + this.setState({ + start: start, + stop: stop + }); + }, + renderRows: function (elems) { + var rows = []; + var max = Math.min(elems.length, this.state.stop); + + for (var i = this.state.start; i < max; i++) { + var elem = elems[i]; + rows.push(this.renderRow(elem)); + } + return rows; + }, + scrollRowIntoView: function (index, head_height) { + + var row_top = (index * this.props.rowHeight) + head_height; + var row_bottom = row_top + this.props.rowHeight; + + var viewport = this.getDOMNode(); + var viewport_top = viewport.scrollTop; + var viewport_bottom = viewport_top + viewport.offsetHeight; + + // Account for pinned thead + if (row_top - head_height < viewport_top) { + viewport.scrollTop = row_top - head_height; + } else if (row_bottom > viewport_bottom) { + viewport.scrollTop = row_bottom - viewport.offsetHeight; + } + }, +}; + +module.exports = VirtualScrollMixin; \ No newline at end of file diff --git a/web/src/js/components/virtualscroll.jsx.js b/web/src/js/components/virtualscroll.jsx.js deleted file mode 100644 index 956e1a0b..00000000 --- a/web/src/js/components/virtualscroll.jsx.js +++ /dev/null @@ -1,85 +0,0 @@ -var React = require("react"); - -var VirtualScrollMixin = { - getInitialState: function () { - return { - start: 0, - stop: 0 - }; - }, - componentWillMount: function () { - if (!this.props.rowHeight) { - console.warn("VirtualScrollMixin: No rowHeight specified", this); - } - }, - getPlaceholderTop: function (total) { - var Tag = this.props.placeholderTagName || "tr"; - // When a large trunk of elements is removed from the button, start may be far off the viewport. - // To make this issue less severe, limit the top placeholder to the total number of rows. - var style = { - height: Math.min(this.state.start, total) * this.props.rowHeight - }; - var spacer = ; - - if (this.state.start % 2 === 1) { - // fix even/odd rows - return [spacer, ]; - } else { - return spacer; - } - }, - getPlaceholderBottom: function (total) { - var Tag = this.props.placeholderTagName || "tr"; - var style = { - height: Math.max(0, total - this.state.stop) * this.props.rowHeight - }; - return ; - }, - componentDidMount: function () { - this.onScroll(); - window.addEventListener('resize', this.onScroll); - }, - componentWillUnmount: function(){ - window.removeEventListener('resize', this.onScroll); - }, - onScroll: function () { - var viewport = this.getDOMNode(); - var top = viewport.scrollTop; - var height = viewport.offsetHeight; - var start = Math.floor(top / this.props.rowHeight); - var stop = start + Math.ceil(height / (this.props.rowHeightMin || this.props.rowHeight)); - - this.setState({ - start: start, - stop: stop - }); - }, - renderRows: function (elems) { - var rows = []; - var max = Math.min(elems.length, this.state.stop); - - for (var i = this.state.start; i < max; i++) { - var elem = elems[i]; - rows.push(this.renderRow(elem)); - } - return rows; - }, - scrollRowIntoView: function (index, head_height) { - - var row_top = (index * this.props.rowHeight) + head_height; - var row_bottom = row_top + this.props.rowHeight; - - var viewport = this.getDOMNode(); - var viewport_top = viewport.scrollTop; - var viewport_bottom = viewport_top + viewport.offsetHeight; - - // Account for pinned thead - if (row_top - head_height < viewport_top) { - viewport.scrollTop = row_top - head_height; - } else if (row_bottom > viewport_bottom) { - viewport.scrollTop = row_bottom - viewport.offsetHeight; - } - }, -}; - -module.exports = VirtualScrollMixin; \ No newline at end of file -- cgit v1.2.3