From ac5d74d42c0824b5789cc030bf39a447951e4804 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 21 Mar 2015 21:55:02 +0100 Subject: web: fix bugs --- libmproxy/web/static/app.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) (limited to 'libmproxy/web') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index dae10a34..ef53c08f 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -421,6 +421,7 @@ module.exports = { ConnectionActions: ConnectionActions, FlowActions: FlowActions, StoreCmds: StoreCmds, + SettingsActions: SettingsActions, Query: Query }; @@ -622,6 +623,7 @@ var common = require("./common.js"); var Query = require("../actions.js").Query; var VirtualScrollMixin = require("./virtualscroll.js"); var views = require("../store/view.js"); +var _ = require("lodash"); var LogMessage = React.createClass({displayName: "LogMessage", render: function () { @@ -775,7 +777,7 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1774,7 +1776,7 @@ var MainMenu = React.createClass({displayName: "MainMenu", this.setQuery(d); }, onInterceptChange: function (val) { - SettingsActions.update({intercept: val}); + actions.SettingsActions.update({intercept: val}); }, render: function () { var filter = this.getQuery()[Query.FILTER] || ""; @@ -2196,6 +2198,8 @@ var MainView = React.createClass({displayName: "MainView", actions.FlowActions.revert(flow); } break; + case toputils.Key.SHIFT: + break; default: console.debug("keydown", e.keyCode); return; @@ -4514,8 +4518,6 @@ var default_filt = function (elem) { function StoreView(store, filt, sortfun) { EventEmitter.call(this); - filt = filt || default_filt; - sortfun = sortfun || default_sort; this.store = store; @@ -4539,10 +4541,10 @@ _.extend(StoreView.prototype, EventEmitter.prototype, { this.store.removeListener("recalculate", this.recalculate); }, recalculate: function (filt, sortfun) { - filt = filt || default_filt; - sortfun = sortfun || default_sort; + filt = filt || this.filt || default_filt; + sortfun = sortfun || this.sortfun || default_sort; filt = filt.bind(this); - sortfun = sortfun.bind(this) + sortfun = sortfun.bind(this); this.filt = filt; this.sortfun = sortfun; @@ -4633,6 +4635,7 @@ var Key = { TAB: 9, SPACE: 32, BACKSPACE: 8, + SHIFT: 16 }; // Add A-Z for (var i = 65; i <= 90; i++) { -- cgit v1.2.3 From 02a61ea45dc1ca6d0c88b44adf83f68b791130e7 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 21 Mar 2015 22:49:51 +0100 Subject: structure components --- libmproxy/web/static/app.js | 1031 ++++++++++++++++++++++--------------------- 1 file changed, 528 insertions(+), 503 deletions(-) (limited to 'libmproxy/web') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index ef53c08f..04d6f282 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -443,7 +443,7 @@ $(function () { -},{"./components/proxyapp.js":12,"./connection":14,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":15,"./connection":17,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -777,156 +777,399 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":19,"./common.js":4,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":22,"./common.js":4,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); -var _ = require("lodash"); - -var common = require("./common.js"); -var actions = require("../actions.js"); -var flowutils = require("../flow/utils.js"); -var toputils = require("../utils.js"); +var RequestUtils = require("../flow/utils.js").RequestUtils; +var ResponseUtils = require("../flow/utils.js").ResponseUtils; +var utils = require("../utils.js"); -var NavAction = React.createClass({displayName: "NavAction", - onClick: function (e) { - e.preventDefault(); - this.props.onClick(); +var TLSColumn = React.createClass({displayName: "TLSColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); + } + }), + sortKeyFun: function(flow){ + return flow.request.scheme; + } }, render: function () { - return ( - React.createElement("a", {title: this.props.title, - href: "#", - className: "nav-action", - onClick: this.onClick}, - React.createElement("i", {className: "fa fa-fw " + this.props.icon}) - ) - ); + 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 React.createElement("td", {className: classes}); } }); -var FlowDetailNav = React.createClass({displayName: "FlowDetailNav", + +var IconColumn = React.createClass({displayName: "IconColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); + } + }) + }, 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 React.createElement("a", {key: e, - href: "#", - className: className, - onClick: onClick}, str); - }.bind(this)); + var icon; + if (flow.response) { + var contentType = ResponseUtils.getContentType(flow.response); - var acceptButton = null; - if(flow.intercepted){ - acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + //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"; + } } - var revertButton = null; - if(flow.modified){ - revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + if (!icon) { + icon = "resource-icon-plain"; } - return ( - React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, - tabs, - React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), - React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), - React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), - acceptButton, - revertButton - ) + + icon += " resource-icon"; + return React.createElement("td", {className: "col-icon"}, + React.createElement("div", {className: icon}) ); } }); -var Headers = React.createClass({displayName: "Headers", +var PathColumn = React.createClass({displayName: "PathColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); + } + }), + sortKeyFun: function(flow){ + return RequestUtils.pretty_url(flow.request); + } + }, render: function () { - var rows = this.props.message.headers.map(function (header, i) { - return ( - React.createElement("tr", {key: i}, - React.createElement("td", {className: "header-name"}, header[0] + ":"), - React.createElement("td", {className: "header-value"}, header[1]) - ) - ); - }); - return ( - React.createElement("table", {className: "header-table"}, - React.createElement("tbody", null, - rows - ) - ) + var flow = this.props.flow; + return React.createElement("td", {className: "col-path"}, + flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, + flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, + RequestUtils.pretty_url(flow.request) ); } }); -var FlowDetailRequest = React.createClass({displayName: "FlowDetailRequest", + +var MethodColumn = React.createClass({displayName: "MethodColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); + } + }), + sortKeyFun: function(flow){ + return flow.request.method; + } + }, 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); + return React.createElement("td", {className: "col-method"}, flow.request.method); + } +}); + + +var StatusColumn = React.createClass({displayName: "StatusColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); + } + }), + sortKeyFun: function(flow){ + return flow.response ? flow.response.code : undefined; + } + }, + render: function () { + var flow = this.props.flow; + var status; + if (flow.response) { + status = flow.response.code; } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + status = null; } + return React.createElement("td", {className: "col-status"}, status); + } +}); - //TODO: Styling - return ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.request}), - React.createElement("hr", null), - content - ) - ); +var SizeColumn = React.createClass({displayName: "SizeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); + } + }), + sortKeyFun: function(flow){ + var total = flow.request.contentLength; + if (flow.response) { + total += flow.response.contentLength || 0; + } + return total; + } + }, + 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 React.createElement("td", {className: "col-size"}, size); } }); -var FlowDetailResponse = React.createClass({displayName: "FlowDetailResponse", + +var TimeColumn = React.createClass({displayName: "TimeColumn", + statics: { + Title: React.createClass({displayName: "Title", + render: function(){ + return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); + } + }), + sortKeyFun: function(flow){ + if(flow.response) { + return flow.response.timestamp_end - flow.request.timestamp_start; + } + } + }, 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); + var time; + if (flow.response) { + time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + time = "..."; } + return React.createElement("td", {className: "col-time"}, time); + } +}); - //TODO: Styling + +var all_columns = [ + TLSColumn, + IconColumn, + PathColumn, + MethodColumn, + StatusColumn, + SizeColumn, + TimeColumn +]; + +module.exports = all_columns; + +},{"../flow/utils.js":20,"../utils.js":23,"react":"react"}],7:[function(require,module,exports){ +var React = require("react"); +var common = require("./common.js"); +var utils = require("../utils.js"); +var _ = require("lodash"); + +var VirtualScrollMixin = require("./virtualscroll.js"); +var flowtable_columns = require("./flowtable-columns.js"); + +var FlowRow = React.createClass({displayName: "FlowRow", + render: function () { + var flow = this.props.flow; + var columns = this.props.columns.map(function (Column) { + return React.createElement(Column, {key: Column.displayName, flow: flow}); + }.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 ( - React.createElement("section", null, - React.createElement("div", {className: "first-line"}, first_line ), - React.createElement(Headers, {message: flow.response}), - React.createElement("hr", null), - content - ) + React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, + 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({displayName: "FlowTableHead", + getInitialState: function(){ + return { + sortColumn: undefined, + sortDesc: false + }; + }, + onClick: function(Column){ + var sortDesc = this.state.sortDesc; + var hasSort = Column.sortKeyFun; + if(Column === this.state.sortColumn){ + sortDesc = !sortDesc; + this.setState({ + sortDesc: sortDesc + }); + } else { + this.setState({ + sortColumn: hasSort && Column, + sortDesc: false + }) + } + var sortKeyFun; + if(!sortDesc){ + sortKeyFun = Column.sortKeyFun; + } else { + sortKeyFun = hasSort && function(){ + var k = Column.sortKeyFun.apply(this, arguments); + if(_.isString(k)){ + return utils.reverseString(""+k); + } else { + return -k; + } + } + } + this.props.setSortKeyFun(sortKeyFun); + }, + render: function () { + var columns = this.props.columns.map(function (Column) { + var onClick = this.onClick.bind(this, Column); + var className; + if(this.state.sortColumn === Column) { + if(this.state.sortDesc){ + className = "sort-desc"; + } else { + className = "sort-asc"; + } + } + return React.createElement(Column.Title, { + key: Column.displayName, + onClick: onClick, + className: className}); + }.bind(this)); + return React.createElement("thead", null, + React.createElement("tr", null, columns) + ); + } +}); + + +var ROW_HEIGHT = 32; + +var FlowTable = React.createClass({displayName: "FlowTable", + mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], + getInitialState: function () { + return { + columns: flowtable_columns + }; + }, + _listen: function(view){ + if(!view){ + return; + } + view.addListener("add", this.onChange); + view.addListener("update", this.onChange); + view.addListener("remove", this.onChange); + view.addListener("recalculate", this.onChange); + }, + componentWillMount: function () { + this._listen(this.props.view); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.view !== this.props.view) { + if (this.props.view) { + this.props.view.removeListener("add"); + this.props.view.removeListener("update"); + this.props.view.removeListener("remove"); + this.props.view.removeListener("recalculate"); + } + this._listen(nextProps.view); + } + }, + 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 React.createElement(FlowRow, {key: flow.id, + ref: flow.id, + flow: flow, + columns: this.state.columns, + selected: selected, + highlighted: highlighted, + selectFlow: this.props.selectFlow} ); - } -}); - -var FlowDetailError = React.createClass({displayName: "FlowDetailError", + }, render: function () { - var flow = this.props.flow; + //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 ( - React.createElement("section", null, - React.createElement("div", {className: "alert alert-warning"}, - flow.error.msg, - React.createElement("div", null, - React.createElement("small", null, toputils.formatTimeStamp(flow.error.timestamp) ) + React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, + React.createElement("table", null, + React.createElement(FlowTableHead, {ref: "head", + columns: this.state.columns, + setSortKeyFun: this.props.setSortKeyFun}), + React.createElement("tbody", {ref: "body"}, + this.getPlaceholderTop(flows.length), + rows, + this.getPlaceholderBottom(flows.length) ) ) ) @@ -934,6 +1177,15 @@ var FlowDetailError = React.createClass({displayName: "FlowDetailError", } }); +module.exports = FlowTable; + + +},{"../utils.js":23,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var utils = require("../../utils.js"); + var TimeStamp = React.createClass({displayName: "TimeStamp", render: function () { @@ -942,11 +1194,11 @@ var TimeStamp = React.createClass({displayName: "TimeStamp", return React.createElement("tr", null); } - var ts = toputils.formatTimeStamp(this.props.t); + var ts = utils.formatTimeStamp(this.props.t); var delta; if (this.props.deltaTo) { - delta = toputils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); + delta = utils.formatTimeDelta(1000 * (this.props.t - this.props.deltaTo)); delta = React.createElement("span", {className: "text-muted"}, "(" + delta + ")"); } else { delta = null; @@ -1086,7 +1338,7 @@ var Timing = React.createClass({displayName: "Timing", } }); -var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnectionInfo", +var Details = React.createClass({displayName: "Details", render: function () { var flow = this.props.flow; var client_conn = flow.client_conn; @@ -1109,14 +1361,25 @@ var FlowDetailConnectionInfo = React.createClass({displayName: "FlowDetailConnec } }); +module.exports = Details; + +},{"../../utils.js":23,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +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"); + var allTabs = { - request: FlowDetailRequest, - response: FlowDetailResponse, - error: FlowDetailError, - details: FlowDetailConnectionInfo + request: Messages.Request, + response: Messages.Response, + error: Messages.Error, + details: Details }; -var FlowDetail = React.createClass({displayName: "FlowDetail", +var FlowView = React.createClass({displayName: "FlowView", mixins: [common.StickyHeadMixin, common.Navigation, common.State], getTabs: function (flow) { var tabs = []; @@ -1163,7 +1426,7 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", var Tab = allTabs[active]; return ( React.createElement("div", {className: "flow-detail", onScroll: this.adjustHead}, - React.createElement(FlowDetailNav, {ref: "head", + React.createElement(Nav, {ref: "head", flow: flow, tabs: tabs, active: active, @@ -1174,414 +1437,176 @@ var FlowDetail = React.createClass({displayName: "FlowDetail", } }); -module.exports = { - FlowDetail: FlowDetail -}; +module.exports = FlowView; -},{"../actions.js":2,"../flow/utils.js":17,"../utils.js":20,"./common.js":4,"lodash":"lodash","react":"react"}],7:[function(require,module,exports){ +},{"../common.js":4,"./details.js":8,"./messages.js":10,"./nav.js":11,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ var React = require("react"); -var RequestUtils = require("../flow/utils.js").RequestUtils; -var ResponseUtils = require("../flow/utils.js").ResponseUtils; -var utils = require("../utils.js"); - -var TLSColumn = React.createClass({displayName: "TLSColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-tls " + (this.props.className || "") })); - } - }), - sortKeyFun: function(flow){ - return flow.request.scheme; - } - }, - 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 React.createElement("td", {className: classes}); - } -}); - - -var IconColumn = React.createClass({displayName: "IconColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-icon " + (this.props.className || "") })); - } - }) - }, - render: function () { - var flow = this.props.flow; - - var icon; - if (flow.response) { - var contentType = 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 React.createElement("td", {className: "col-icon"}, - React.createElement("div", {className: icon}) - ); - } -}); +var flowutils = require("../../flow/utils.js"); +var utils = require("../../utils.js"); -var PathColumn = React.createClass({displayName: "PathColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-path " + (this.props.className || "") }), "Path"); - } - }), - sortKeyFun: function(flow){ - return RequestUtils.pretty_url(flow.request); - } - }, +var Headers = React.createClass({displayName: "Headers", render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-path"}, - flow.request.is_replay ? React.createElement("i", {className: "fa fa-fw fa-repeat pull-right"}) : null, - flow.intercepted ? React.createElement("i", {className: "fa fa-fw fa-pause pull-right"}) : null, - RequestUtils.pretty_url(flow.request) + var rows = this.props.message.headers.map(function (header, i) { + return ( + React.createElement("tr", {key: i}, + React.createElement("td", {className: "header-name"}, header[0] + ":"), + React.createElement("td", {className: "header-value"}, header[1]) + ) + ); + }); + return ( + React.createElement("table", {className: "header-table"}, + React.createElement("tbody", null, + rows + ) + ) ); } }); - -var MethodColumn = React.createClass({displayName: "MethodColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-method " + (this.props.className || "") }), "Method"); - } - }), - sortKeyFun: function(flow){ - return flow.request.method; - } - }, - render: function () { - var flow = this.props.flow; - return React.createElement("td", {className: "col-method"}, flow.request.method); - } -}); - - -var StatusColumn = React.createClass({displayName: "StatusColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-status " + (this.props.className || "") }), "Status"); - } - }), - sortKeyFun: function(flow){ - return flow.response ? flow.response.code : undefined; - } - }, - render: function () { - var flow = this.props.flow; - var status; - if (flow.response) { - status = flow.response.code; - } else { - status = null; - } - return React.createElement("td", {className: "col-status"}, status); - } -}); - - -var SizeColumn = React.createClass({displayName: "SizeColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-size " + (this.props.className || "") }), "Size"); - } - }), - sortKeyFun: function(flow){ - var total = flow.request.contentLength; - if (flow.response) { - total += flow.response.contentLength || 0; - } - return total; - } - }, - 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 React.createElement("td", {className: "col-size"}, size); - } -}); - - -var TimeColumn = React.createClass({displayName: "TimeColumn", - statics: { - Title: React.createClass({displayName: "Title", - render: function(){ - return React.createElement("th", React.__spread({}, this.props, {className: "col-time " + (this.props.className || "") }), "Time"); - } - }), - sortKeyFun: function(flow){ - if(flow.response) { - return flow.response.timestamp_end - flow.request.timestamp_start; - } - } - }, +var Request = React.createClass({displayName: "Request", render: function () { var flow = this.props.flow; - var time; - if (flow.response) { - time = utils.formatTimeDelta(1000 * (flow.response.timestamp_end - flow.request.timestamp_start)); + 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: " + utils.formatSize(flow.request.contentLength); } else { - time = "..."; - } - return React.createElement("td", {className: "col-time"}, time); - } -}); - - -var all_columns = [ - TLSColumn, - IconColumn, - PathColumn, - MethodColumn, - StatusColumn, - SizeColumn, - TimeColumn -]; - -module.exports = all_columns; - -},{"../flow/utils.js":17,"../utils.js":20,"react":"react"}],8:[function(require,module,exports){ -var React = require("react"); -var common = require("./common.js"); -var utils = require("../utils.js"); -var _ = require("lodash"); + content = React.createElement("div", {className: "alert alert-info"}, "No Content"); + } -var VirtualScrollMixin = require("./virtualscroll.js"); -var flowtable_columns = require("./flowtable-columns.js"); + //TODO: Styling -var FlowRow = React.createClass({displayName: "FlowRow", + return ( + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.request}), + React.createElement("hr", null), + content + ) + ); + } +}); + +var Response = React.createClass({displayName: "Response", render: function () { var flow = this.props.flow; - var columns = this.props.columns.map(function (Column) { - return React.createElement(Column, {key: Column.displayName, flow: flow}); - }.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"; + 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: " + utils.formatSize(flow.response.contentLength); + } else { + content = React.createElement("div", {className: "alert alert-info"}, "No Content"); } + //TODO: Styling + return ( - React.createElement("tr", {className: className, onClick: this.props.selectFlow.bind(null, flow)}, - 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) - //); + React.createElement("section", null, + React.createElement("div", {className: "first-line"}, first_line ), + React.createElement(Headers, {message: flow.response}), + React.createElement("hr", null), + content + ) + ); } }); -var FlowTableHead = React.createClass({displayName: "FlowTableHead", - getInitialState: function(){ - return { - sortColumn: undefined, - sortDesc: false - }; - }, - onClick: function(Column){ - var sortDesc = this.state.sortDesc; - var hasSort = Column.sortKeyFun; - if(Column === this.state.sortColumn){ - sortDesc = !sortDesc; - this.setState({ - sortDesc: sortDesc - }); - } else { - this.setState({ - sortColumn: hasSort && Column, - sortDesc: false - }) - } - var sortKeyFun; - if(!sortDesc){ - sortKeyFun = Column.sortKeyFun; - } else { - sortKeyFun = hasSort && function(){ - var k = Column.sortKeyFun.apply(this, arguments); - if(_.isString(k)){ - return utils.reverseString(""+k); - } else { - return -k; - } - } - } - this.props.setSortKeyFun(sortKeyFun); - }, +var Error = React.createClass({displayName: "Error", render: function () { - var columns = this.props.columns.map(function (Column) { - var onClick = this.onClick.bind(this, Column); - var className; - if(this.state.sortColumn === Column) { - if(this.state.sortDesc){ - className = "sort-desc"; - } else { - className = "sort-asc"; - } - } - return React.createElement(Column.Title, { - key: Column.displayName, - onClick: onClick, - className: className}); - }.bind(this)); - return React.createElement("thead", null, - React.createElement("tr", null, columns) + var flow = this.props.flow; + return ( + React.createElement("section", null, + React.createElement("div", {className: "alert alert-warning"}, + flow.error.msg, + React.createElement("div", null, + React.createElement("small", null, utils.formatTimeStamp(flow.error.timestamp) ) + ) + ) + ) ); } }); +module.exports = { + Request: Request, + Response: Response, + Error: Error +}; + +},{"../../flow/utils.js":20,"../../utils.js":23,"react":"react"}],11:[function(require,module,exports){ +var React = require("react"); -var ROW_HEIGHT = 32; +var actions = require("../../actions.js"); -var FlowTable = React.createClass({displayName: "FlowTable", - mixins: [common.StickyHeadMixin, common.AutoScrollMixin, VirtualScrollMixin], - getInitialState: function () { - return { - columns: flowtable_columns - }; - }, - _listen: function(view){ - if(!view){ - return; - } - view.addListener("add", this.onChange); - view.addListener("update", this.onChange); - view.addListener("remove", this.onChange); - view.addListener("recalculate", this.onChange); - }, - componentWillMount: function () { - this._listen(this.props.view); - }, - componentWillReceiveProps: function (nextProps) { - if (nextProps.view !== this.props.view) { - if (this.props.view) { - this.props.view.removeListener("add"); - this.props.view.removeListener("update"); - this.props.view.removeListener("remove"); - this.props.view.removeListener("recalculate"); - } - this._listen(nextProps.view); - } - }, - getDefaultProps: function () { - return { - rowHeight: ROW_HEIGHT - }; - }, - onScrollFlowTable: function () { - this.adjustHead(); - this.onScroll(); - }, - onChange: function () { - this.forceUpdate(); +var NavAction = React.createClass({displayName: "NavAction", + onClick: function (e) { + e.preventDefault(); + this.props.onClick(); }, - scrollIntoView: function (flow) { - this.scrollRowIntoView( - this.props.view.index(flow), - this.refs.body.getDOMNode().offsetTop + render: function () { + return ( + React.createElement("a", {title: this.props.title, + href: "#", + className: "nav-action", + onClick: this.onClick}, + React.createElement("i", {className: "fa fa-fw " + this.props.icon}) + ) ); - }, - renderRow: function (flow) { - var selected = (flow === this.props.selected); - var highlighted = - ( - this.props.view._highlight && - this.props.view._highlight[flow.id] - ); + } +}); - return React.createElement(FlowRow, {key: flow.id, - ref: flow.id, - flow: flow, - columns: this.state.columns, - selected: selected, - highlighted: highlighted, - selectFlow: this.props.selectFlow} - ); - }, +var Nav = React.createClass({displayName: "Nav", 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 flow = this.props.flow; - var rows = this.renderRows(flows); + 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 React.createElement("a", {key: e, + href: "#", + className: className, + onClick: onClick}, str); + }.bind(this)); + + var acceptButton = null; + if(flow.intercepted){ + acceptButton = React.createElement(NavAction, {title: "[a]ccept intercepted flow", icon: "fa-play", onClick: actions.FlowActions.accept.bind(null, flow)}); + } + var revertButton = null; + if(flow.modified){ + revertButton = React.createElement(NavAction, {title: "revert changes to flow [V]", icon: "fa-history", onClick: actions.FlowActions.revert.bind(null, flow)}); + } return ( - React.createElement("div", {className: "flow-table", onScroll: this.onScrollFlowTable}, - React.createElement("table", null, - React.createElement(FlowTableHead, {ref: "head", - columns: this.state.columns, - setSortKeyFun: this.props.setSortKeyFun}), - React.createElement("tbody", {ref: "body"}, - this.getPlaceholderTop(flows.length), - rows, - this.getPlaceholderBottom(flows.length) - ) - ) + React.createElement("nav", {ref: "head", className: "nav-tabs nav-tabs-sm"}, + tabs, + React.createElement(NavAction, {title: "[d]elete flow", icon: "fa-trash", onClick: actions.FlowActions.delete.bind(null, flow)}), + React.createElement(NavAction, {title: "[D]uplicate flow", icon: "fa-copy", onClick: actions.FlowActions.duplicate.bind(null, flow)}), + React.createElement(NavAction, {disabled: true, title: "[r]eplay flow", icon: "fa-repeat", onClick: actions.FlowActions.replay.bind(null, flow)}), + acceptButton, + revertButton ) ); } }); -module.exports = FlowTable; - +module.exports = Nav; -},{"../utils.js":20,"./common.js":4,"./flowtable-columns.js":7,"./virtualscroll.js":13,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1600,7 +1625,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],10:[function(require,module,exports){ +},{"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -1992,7 +2017,7 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":16,"../utils.js":20,"./common.js":4,"jquery":"jquery","react":"react"}],11:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":19,"../utils.js":23,"./common.js":4,"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); @@ -2002,7 +2027,7 @@ 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 FlowView = require("./flowview/index.js"); var MainView = React.createClass({displayName: "MainView", mixins: [common.Navigation, common.State], @@ -2216,7 +2241,7 @@ var MainView = React.createClass({displayName: "MainView", if (selected) { details = [ React.createElement(common.Splitter, {key: "splitter"}), - React.createElement(flowdetail.FlowDetail, {key: "flowDetails", ref: "flowDetails", flow: selected}) + React.createElement(FlowView, {key: "flowDetails", ref: "flowDetails", flow: selected}) ]; } else { details = null; @@ -2238,7 +2263,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":16,"../store/view.js":19,"../utils.js":20,"./common.js":4,"./flowdetail.js":6,"./flowtable.js":8,"react":"react"}],12:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":19,"../store/view.js":22,"../utils.js":23,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":9,"react":"react"}],15:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2333,7 +2358,7 @@ module.exports = { routes: routes }; -},{"../actions.js":2,"../store/store.js":18,"./common.js":4,"./eventlog.js":5,"./footer.js":9,"./header.js":10,"./mainview.js":11,"lodash":"lodash","react":"react","react-router":"react-router"}],13:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":21,"./common.js":4,"./eventlog.js":5,"./footer.js":12,"./header.js":13,"./mainview.js":14,"lodash":"lodash","react":"react","react-router":"react-router"}],16:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2420,7 +2445,7 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],14:[function(require,module,exports){ +},{"react":"react"}],17:[function(require,module,exports){ var actions = require("./actions.js"); @@ -2450,7 +2475,7 @@ function Connection(url) { module.exports = Connection; -},{"./actions.js":2}],15:[function(require,module,exports){ +},{"./actions.js":2}],18:[function(require,module,exports){ var flux = require("flux"); @@ -2474,7 +2499,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],16:[function(require,module,exports){ +},{"flux":"flux"}],19:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4250,10 +4275,10 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":17}],17:[function(require,module,exports){ +},{"../flow/utils.js":20}],20:[function(require,module,exports){ var _ = require("lodash"); -var _MessageUtils = { +var MessageUtils = { getContentType: function (message) { return this.get_first_header(message, /^Content-Type$/i); }, @@ -4295,7 +4320,7 @@ var defaultPorts = { "https": 443 }; -var RequestUtils = _.extend(_MessageUtils, { +var RequestUtils = _.extend(MessageUtils, { pretty_host: function (request) { //FIXME: Add hostheader return request.host; @@ -4309,16 +4334,16 @@ var RequestUtils = _.extend(_MessageUtils, { } }); -var ResponseUtils = _.extend(_MessageUtils, {}); +var ResponseUtils = _.extend(MessageUtils, {}); module.exports = { ResponseUtils: ResponseUtils, - RequestUtils: RequestUtils - -} + RequestUtils: RequestUtils, + MessageUtils: MessageUtils +}; -},{"lodash":"lodash"}],18:[function(require,module,exports){ +},{"lodash":"lodash"}],21:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4501,7 +4526,7 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":15,"../utils.js":20,"events":1,"jquery":"jquery","lodash":"lodash"}],19:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":18,"../utils.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); @@ -4617,7 +4642,7 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":20,"events":1,"lodash":"lodash"}],20:[function(require,module,exports){ +},{"../utils.js":23,"events":1,"lodash":"lodash"}],23:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); -- cgit v1.2.3 From 1143552e1690f8b96b3d95381f7f06cbb46ead59 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 00:21:38 +0100 Subject: web: add content views --- libmproxy/web/static/app.css | 10 ++ libmproxy/web/static/app.js | 215 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 192 insertions(+), 33 deletions(-) (limited to 'libmproxy/web') diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css index 4f24ddd9..cf2db2c6 100644 --- a/libmproxy/web/static/app.css +++ b/libmproxy/web/static/app.css @@ -290,6 +290,9 @@ header .menu { max-height: 100px; overflow-y: auto; } +.view-selector { + margin-top: 10px; +} .flow-detail table { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; width: 100%; @@ -316,6 +319,13 @@ header .menu { text-overflow: ellipsis; white-space: nowrap; } +.flowview-image { + text-align: center; +} +.flowview-image img { + max-width: 100%; + max-height: 100%; +} .eventlog { height: 200px; flex: 0 0 auto; diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 04d6f282..eb8ef45e 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -443,7 +443,7 @@ $(function () { -},{"./components/proxyapp.js":15,"./connection":17,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ +},{"./components/proxyapp.js":16,"./connection":18,"jquery":"jquery","react":"react","react-router":"react-router"}],4:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -777,7 +777,7 @@ var EventLog = React.createClass({displayName: "EventLog", module.exports = EventLog; -},{"../actions.js":2,"../store/view.js":22,"./common.js":4,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ +},{"../actions.js":2,"../store/view.js":23,"./common.js":4,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],6:[function(require,module,exports){ var React = require("react"); var RequestUtils = require("../flow/utils.js").RequestUtils; var ResponseUtils = require("../flow/utils.js").ResponseUtils; @@ -980,7 +980,7 @@ var all_columns = [ module.exports = all_columns; -},{"../flow/utils.js":20,"../utils.js":23,"react":"react"}],7:[function(require,module,exports){ +},{"../flow/utils.js":21,"../utils.js":24,"react":"react"}],7:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); var utils = require("../utils.js"); @@ -1180,7 +1180,167 @@ var FlowTable = React.createClass({displayName: "FlowTable", module.exports = FlowTable; -},{"../utils.js":23,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":16,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +},{"../utils.js":24,"./common.js":4,"./flowtable-columns.js":6,"./virtualscroll.js":17,"lodash":"lodash","react":"react"}],8:[function(require,module,exports){ +var React = require("react"); +var _ = require("lodash"); + +var MessageUtils = require("../../flow/utils.js").MessageUtils; +var utils = require("../../utils.js"); + +var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; +var Image = React.createClass({displayName: "Image", + statics: { + matches: function (message) { + return image_regex.test(MessageUtils.getContentType(message)); + } + }, + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + return React.createElement("div", {className: "flowview-image"}, + React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) + ); + } +}); + +var Raw = React.createClass({displayName: "Raw", + statics: { + matches: function (message) { + return true; + } + }, + render: function () { + //FIXME + return React.createElement("div", null, "raw"); + } +}); + + +var Auto = React.createClass({displayName: "Auto", + statics: { + matches: function () { + return false; // don't match itself + }, + findView: function (message) { + for (var i = 0; i < all.length; i++) { + if (all[i].matches(message)) { + return all[i]; + } + } + return all[all.length - 1]; + } + }, + render: function () { + var View = Auto.findView(this.props.message); + return React.createElement(View, React.__spread({}, this.props)); + } +}); + +var all = [Auto, Image, Raw]; + + +var ContentEmpty = React.createClass({displayName: "ContentEmpty", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "request" : "response"; + return React.createElement("div", {className: "alert alert-info"}, "No ", message_name, " content."); + } +}); + +var ContentMissing = React.createClass({displayName: "ContentMissing", + render: function () { + var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; + return React.createElement("div", {className: "alert alert-info"}, message_name, " content missing."); + } +}); + +var TooLarge = React.createClass({displayName: "TooLarge", + render: function () { + var size = utils.formatSize(this.props.message.contentLength); + return React.createElement("div", {className: "alert alert-warning"}, + React.createElement("button", {onClick: this.props.onClick, className: "btn btn-xs btn-warning pull-right"}, "Display anyway"), + size, " content size." + ); + } +}); + +var ViewSelector = React.createClass({displayName: "ViewSelector", + render: function () { + var views = []; + for (var i = 0; i < all.length; i++) { + var view = all[i]; + var className = "btn btn-default"; + if (view === this.props.active) { + className += " active"; + } + var text; + if (view === Auto) { + text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); + } else { + text = view.displayName.toLowerCase(); + } + views.push( + React.createElement("button", { + key: view.displayName, + onClick: this.props.selectView.bind(null, view), + className: className}, + text + ) + ); + } + + return React.createElement("div", {className: "view-selector btn-group btn-group-xs"}, views); + } +}); + +var ContentView = React.createClass({displayName: "ContentView", + getInitialState: function () { + return { + displayLarge: false, + View: Auto + }; + }, + propTypes: { + // It may seem a bit weird at the first glance: + // Every view takes the flow and the message as props, e.g. + // + flow: React.PropTypes.object.isRequired, + message: React.PropTypes.object.isRequired, + }, + selectView: function (view) { + this.setState({ + View: view + }); + }, + displayLarge: function () { + this.setState({displayLarge: true}); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.setState(this.getInitialState()); + } + }, + render: function () { + var message = this.props.message; + if (message.contentLength === 0) { + return React.createElement(ContentEmpty, React.__spread({}, this.props)); + } else if (message.contentLength === null) { + return React.createElement(ContentMissing, React.__spread({}, this.props)); + } else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { + return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); + } + + return React.createElement("div", null, + React.createElement(this.state.View, React.__spread({}, this.props)), + React.createElement("div", {className: "text-center"}, + React.createElement(ViewSelector, {selectView: this.selectView, active: this.state.View, message: message}) + ) + ); + } +}); + +module.exports = ContentView; + +},{"../../flow/utils.js":21,"../../utils.js":24,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1363,7 +1523,7 @@ var Details = React.createClass({displayName: "Details", module.exports = Details; -},{"../../utils.js":23,"lodash":"lodash","react":"react"}],9:[function(require,module,exports){ +},{"../../utils.js":24,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ var React = require("react"); var _ = require("lodash"); @@ -1439,11 +1599,12 @@ var FlowView = React.createClass({displayName: "FlowView", module.exports = FlowView; -},{"../common.js":4,"./details.js":8,"./messages.js":10,"./nav.js":11,"lodash":"lodash","react":"react"}],10:[function(require,module,exports){ +},{"../common.js":4,"./details.js":9,"./messages.js":11,"./nav.js":12,"lodash":"lodash","react":"react"}],11:[function(require,module,exports){ var React = require("react"); var flowutils = require("../../flow/utils.js"); var utils = require("../../utils.js"); +var ContentView = require("./contentview.js"); var Headers = React.createClass({displayName: "Headers", render: function () { @@ -1473,12 +1634,6 @@ var Request = React.createClass({displayName: "Request", flowutils.RequestUtils.pretty_url(flow.request), "HTTP/" + flow.request.httpversion.join(".") ].join(" "); - var content = null; - if (flow.request.contentLength > 0) { - content = "Request Content Size: " + utils.formatSize(flow.request.contentLength); - } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); - } //TODO: Styling @@ -1487,7 +1642,7 @@ var Request = React.createClass({displayName: "Request", React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.request}), React.createElement("hr", null), - content + React.createElement(ContentView, {flow: flow, message: flow.request}) ) ); } @@ -1501,12 +1656,6 @@ var Response = React.createClass({displayName: "Response", flow.response.code, flow.response.msg ].join(" "); - var content = null; - if (flow.response.contentLength > 0) { - content = "Response Content Size: " + utils.formatSize(flow.response.contentLength); - } else { - content = React.createElement("div", {className: "alert alert-info"}, "No Content"); - } //TODO: Styling @@ -1515,7 +1664,7 @@ var Response = React.createClass({displayName: "Response", React.createElement("div", {className: "first-line"}, first_line ), React.createElement(Headers, {message: flow.response}), React.createElement("hr", null), - content + React.createElement(ContentView, {flow: flow, message: flow.response}) ) ); } @@ -1543,7 +1692,7 @@ module.exports = { Error: Error }; -},{"../../flow/utils.js":20,"../../utils.js":23,"react":"react"}],11:[function(require,module,exports){ +},{"../../flow/utils.js":21,"../../utils.js":24,"./contentview.js":8,"react":"react"}],12:[function(require,module,exports){ var React = require("react"); var actions = require("../../actions.js"); @@ -1606,7 +1755,7 @@ var Nav = React.createClass({displayName: "Nav", module.exports = Nav; -},{"../../actions.js":2,"react":"react"}],12:[function(require,module,exports){ +},{"../../actions.js":2,"react":"react"}],13:[function(require,module,exports){ var React = require("react"); var Footer = React.createClass({displayName: "Footer", @@ -1625,7 +1774,7 @@ var Footer = React.createClass({displayName: "Footer", module.exports = Footer; -},{"react":"react"}],13:[function(require,module,exports){ +},{"react":"react"}],14:[function(require,module,exports){ var React = require("react"); var $ = require("jquery"); @@ -2017,7 +2166,7 @@ module.exports = { Header: Header } -},{"../actions.js":2,"../filt/filt.js":19,"../utils.js":23,"./common.js":4,"jquery":"jquery","react":"react"}],14:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../utils.js":24,"./common.js":4,"jquery":"jquery","react":"react"}],15:[function(require,module,exports){ var React = require("react"); var common = require("./common.js"); @@ -2263,7 +2412,7 @@ var MainView = React.createClass({displayName: "MainView", module.exports = MainView; -},{"../actions.js":2,"../filt/filt.js":19,"../store/view.js":22,"../utils.js":23,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":9,"react":"react"}],15:[function(require,module,exports){ +},{"../actions.js":2,"../filt/filt.js":20,"../store/view.js":23,"../utils.js":24,"./common.js":4,"./flowtable.js":7,"./flowview/index.js":10,"react":"react"}],16:[function(require,module,exports){ var React = require("react"); var ReactRouter = require("react-router"); var _ = require("lodash"); @@ -2358,7 +2507,7 @@ module.exports = { routes: routes }; -},{"../actions.js":2,"../store/store.js":21,"./common.js":4,"./eventlog.js":5,"./footer.js":12,"./header.js":13,"./mainview.js":14,"lodash":"lodash","react":"react","react-router":"react-router"}],16:[function(require,module,exports){ +},{"../actions.js":2,"../store/store.js":22,"./common.js":4,"./eventlog.js":5,"./footer.js":13,"./header.js":14,"./mainview.js":15,"lodash":"lodash","react":"react","react-router":"react-router"}],17:[function(require,module,exports){ var React = require("react"); var VirtualScrollMixin = { @@ -2445,7 +2594,7 @@ var VirtualScrollMixin = { module.exports = VirtualScrollMixin; -},{"react":"react"}],17:[function(require,module,exports){ +},{"react":"react"}],18:[function(require,module,exports){ var actions = require("./actions.js"); @@ -2475,7 +2624,7 @@ function Connection(url) { module.exports = Connection; -},{"./actions.js":2}],18:[function(require,module,exports){ +},{"./actions.js":2}],19:[function(require,module,exports){ var flux = require("flux"); @@ -2499,7 +2648,7 @@ module.exports = { AppDispatcher: AppDispatcher }; -},{"flux":"flux"}],19:[function(require,module,exports){ +},{"flux":"flux"}],20:[function(require,module,exports){ module.exports = (function() { /* * Generated by PEG.js 0.8.0. @@ -4275,7 +4424,7 @@ module.exports = (function() { }; })(); -},{"../flow/utils.js":20}],20:[function(require,module,exports){ +},{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); var MessageUtils = { @@ -4343,7 +4492,7 @@ module.exports = { MessageUtils: MessageUtils }; -},{"lodash":"lodash"}],21:[function(require,module,exports){ +},{"lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); @@ -4526,7 +4675,7 @@ module.exports = { FlowStore: FlowStore }; -},{"../actions.js":2,"../dispatcher.js":18,"../utils.js":23,"events":1,"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ +},{"../actions.js":2,"../dispatcher.js":19,"../utils.js":24,"events":1,"jquery":"jquery","lodash":"lodash"}],23:[function(require,module,exports){ var EventEmitter = require('events').EventEmitter; var _ = require("lodash"); @@ -4642,7 +4791,7 @@ module.exports = { StoreView: StoreView }; -},{"../utils.js":23,"events":1,"lodash":"lodash"}],23:[function(require,module,exports){ +},{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){ var $ = require("jquery"); var _ = require("lodash"); -- cgit v1.2.3 From 89d66360d6f7caa9760fe56fa146396b1b4251dc Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 00:28:08 +0100 Subject: tweak css --- libmproxy/web/static/app.css | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'libmproxy/web') diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css index cf2db2c6..91e847a4 100644 --- a/libmproxy/web/static/app.css +++ b/libmproxy/web/static/app.css @@ -290,6 +290,9 @@ header .menu { max-height: 100px; overflow-y: auto; } +.flow-detail hr { + margin: 0 0 5px; +} .view-selector { margin-top: 10px; } @@ -309,6 +312,9 @@ header .menu { width: 50%; padding-right: 1em; } +.header-table td { + line-height: 1.3em; +} .header-table .header-name { width: 33%; padding-right: 1em; -- cgit v1.2.3 From 941584623281905fec22d8857c5501d196c051f7 Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 02:25:47 +0100 Subject: web: raw content view --- libmproxy/web/static/app.css | 3 ++- libmproxy/web/static/app.js | 58 +++++++++++++++++++++++++++++++++++++++----- 2 files changed, 54 insertions(+), 7 deletions(-) (limited to 'libmproxy/web') diff --git a/libmproxy/web/static/app.css b/libmproxy/web/static/app.css index 91e847a4..ccaefe92 100644 --- a/libmproxy/web/static/app.css +++ b/libmproxy/web/static/app.css @@ -271,7 +271,8 @@ header .menu { } .flow-detail { width: 100%; - overflow: auto; + overflow-x: auto; + overflow-y: scroll; } .flow-detail nav { background-color: #F2F2F2; diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index eb8ef45e..2254b415 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -1195,23 +1195,56 @@ var Image = React.createClass({displayName: "Image", } }, render: function () { - var message_name = this.props.flow.request === this.props.message ? "request" : "response"; - var url = "/flows/" + this.props.flow.id + "/" + message_name + "/content"; + var url = MessageUtils.getContentURL(this.props.flow, this.props.message); return React.createElement("div", {className: "flowview-image"}, React.createElement("img", {src: url, alt: "preview", className: "img-thumbnail"}) ); } }); +var RawMixin = { + getInitialState: function () { + return { + content: undefined + } + }, + requestContent: function (nextProps) { + this.setState({content: undefined}); + var request = MessageUtils.getContent(nextProps.flow, nextProps.message); + request.done(function (data) { + this.setState({content: data}); + }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { + this.setState({content: "AJAX Error: " + textStatus}); + }.bind(this)); + + }, + componentWillMount: function () { + this.requestContent(this.props); + }, + componentWillReceiveProps: function (nextProps) { + if (nextProps.message !== this.props.message) { + this.requestContent(nextProps); + } + }, + render: function () { + if (!this.state.content) { + return React.createElement("div", {className: "text-center"}, + React.createElement("i", {className: "fa fa-spinner fa-spin"}) + ); + } + return this.renderContent(); + } +}; + var Raw = React.createClass({displayName: "Raw", + mixins: [RawMixin], statics: { matches: function (message) { return true; } }, - render: function () { - //FIXME - return React.createElement("div", null, "raw"); + renderContent: function () { + return React.createElement("pre", null, this.state.content); } }); @@ -4426,6 +4459,7 @@ module.exports = (function() { },{"../flow/utils.js":21}],21:[function(require,module,exports){ var _ = require("lodash"); +var $ = require("jquery"); var MessageUtils = { getContentType: function (message) { @@ -4461,6 +4495,18 @@ var MessageUtils = { } } return false; + }, + getContentURL: function(flow, message){ + if(message === flow.request){ + message = "request"; + } else if (message === flow.response){ + message = "response"; + } + return "/flows/" + flow.id + "/" + message + "/content"; + }, + getContent: function(flow, message){ + var url = MessageUtils.getContentURL(flow, message); + return $.get(url); } }; @@ -4492,7 +4538,7 @@ module.exports = { MessageUtils: MessageUtils }; -},{"lodash":"lodash"}],22:[function(require,module,exports){ +},{"jquery":"jquery","lodash":"lodash"}],22:[function(require,module,exports){ var _ = require("lodash"); var $ = require("jquery"); -- cgit v1.2.3 From cf9f91b0b4abe2020c544981d6dc2e2e85f4b4bd Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sun, 22 Mar 2015 14:33:42 +0100 Subject: web: upgrade to react 0.13 --- libmproxy/web/static/app.js | 44 +- libmproxy/web/static/vendor.css | 46 +- libmproxy/web/static/vendor.js | 11077 ++++++++++++++++++++++---------------- 3 files changed, 6529 insertions(+), 4638 deletions(-) (limited to 'libmproxy/web') diff --git a/libmproxy/web/static/app.js b/libmproxy/web/static/app.js index 2254b415..4f3998a9 100644 --- a/libmproxy/web/static/app.js +++ b/libmproxy/web/static/app.js @@ -478,31 +478,43 @@ var StickyHeadMixin = { var Navigation = _.extend({}, ReactRouter.Navigation, { setQuery: function (dict) { - var q = this.context.getCurrentQuery(); + var q = this.context.router.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/957 - this.replaceWith(this.context.getCurrentPath(), this.context.getCurrentParams(), q); + this.replaceWith(this.context.router.getCurrentPath(), this.context.router.getCurrentParams(), q); }, replaceWith: function(routeNameOrPath, params, query) { if(routeNameOrPath === undefined){ - routeNameOrPath = this.context.getCurrentPath(); + routeNameOrPath = this.context.router.getCurrentPath(); } if(params === undefined){ - params = this.context.getCurrentParams(); + params = this.context.router.getCurrentParams(); } if(query === undefined) { - query = this.context.getCurrentQuery(); + query = this.context.router.getCurrentQuery(); } - // FIXME: react-router is just broken. - ReactRouter.Navigation.replaceWith.call(this, routeNameOrPath, params, query); + // FIXME: react-router is just broken, + // we hopefully just need to wait for the next release with https://github.com/rackt/react-router/pull/957. + this.context.router.replaceWith(routeNameOrPath, params, 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 State = _.extend({}, ReactRouter.State, { + getQuery: function(){ + return this.context.router.getCurrentQuery(); + }, + getParams: function(){ + return this.context.router.getCurrentParams(); } }); -_.extend(Navigation.contextTypes, ReactRouter.State.contextTypes); var Splitter = React.createClass({displayName: "Splitter", getDefaultProps: function () { @@ -610,7 +622,7 @@ var Splitter = React.createClass({displayName: "Splitter", }); module.exports = { - State: ReactRouter.State, // keep here - react-router is pretty buggy, we may need workarounds in the future. + State: State, Navigation: Navigation, StickyHeadMixin: StickyHeadMixin, AutoScrollMixin: AutoScrollMixin, @@ -2166,15 +2178,17 @@ var Header = React.createClass({displayName: "Header", }, render: function () { var header = header_entries.map(function (entry, i) { - var classes = React.addons.classSet({ - active: entry == this.state.active - }); + var className; + if(entry === this.state.active){ + className = "active"; + } else { + className = ""; + } return ( React.createElement("a", {key: i, href: "#", - className: classes, - onClick: this.handleClick.bind(this, entry) - }, + className: className, + onClick: this.handleClick.bind(this, entry)}, entry.title ) ); diff --git a/libmproxy/web/static/vendor.css b/libmproxy/web/static/vendor.css index 149372c8..a170c49a 100644 --- a/libmproxy/web/static/vendor.css +++ b/libmproxy/web/static/vendor.css @@ -945,12 +945,24 @@ th { .glyphicon-bitcoin:before { content: "\e227"; } +.glyphicon-btc:before { + content: "\e227"; +} +.glyphicon-xbt:before { + content: "\e227"; +} .glyphicon-yen:before { content: "\00a5"; } +.glyphicon-jpy:before { + content: "\00a5"; +} .glyphicon-ruble:before { content: "\20bd"; } +.glyphicon-rub:before { + content: "\20bd"; +} .glyphicon-scale:before { content: "\e230"; } @@ -1147,6 +1159,9 @@ hr { overflow: visible; clip: auto; } +[role="button"] { + cursor: pointer; +} h1, h2, h3, @@ -2548,10 +2563,13 @@ output { .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { - cursor: not-allowed; background-color: #eeeeee; opacity: 1; } +.form-control[disabled], +fieldset[disabled] .form-control { + cursor: not-allowed; +} textarea.form-control { height: auto; } @@ -2618,6 +2636,7 @@ input[type="search"] { } .radio-inline, .checkbox-inline { + position: relative; display: inline-block; padding-left: 20px; margin-bottom: 0; @@ -2654,6 +2673,7 @@ fieldset[disabled] .checkbox label { padding-top: 7px; padding-bottom: 7px; margin-bottom: 0; + min-height: 34px; } .form-control-static.input-lg, .form-control-static.input-sm { @@ -2695,6 +2715,7 @@ select[multiple].form-group-sm .form-control { padding: 5px 10px; font-size: 12px; line-height: 1.5; + min-height: 32px; } .input-lg { height: 46px; @@ -2731,6 +2752,7 @@ select[multiple].form-group-lg .form-control { padding: 10px 16px; font-size: 18px; line-height: 1.3333333; + min-height: 38px; } .has-feedback { position: relative; @@ -3348,11 +3370,9 @@ input[type="button"].btn-block { } .collapse { display: none; - visibility: hidden; } .collapse.in { display: block; - visibility: visible; } tr.collapse.in { display: table-row; @@ -3377,7 +3397,7 @@ tbody.collapse.in { height: 0; margin-left: 2px; vertical-align: middle; - border-top: 4px solid; + border-top: 4px dashed; border-right: 4px solid transparent; border-left: 4px solid transparent; } @@ -4016,11 +4036,9 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .tab-content > .tab-pane { display: none; - visibility: hidden; } .tab-content > .active { display: block; - visibility: visible; } .nav-tabs .dropdown-menu { margin-top: -1px; @@ -4062,7 +4080,6 @@ select[multiple].input-group-sm > .input-group-btn > .btn { } .navbar-collapse.collapse { display: block !important; - visibility: visible !important; height: auto !important; padding-bottom: 0; overflow: visible !important; @@ -4791,7 +4808,8 @@ a.label:focus { position: relative; top: -1px; } -.btn-xs .badge { +.btn-xs .badge, +.btn-group-xs > .btn .badge { top: 0; padding: 1px 5px; } @@ -5614,10 +5632,10 @@ a.list-group-item-danger.active:focus { width: 100%; border: 0; } -.embed-responsive.embed-responsive-16by9 { +.embed-responsive-16by9 { padding-bottom: 56.25%; } -.embed-responsive.embed-responsive-4by3 { +.embed-responsive-4by3 { padding-bottom: 75%; } .well { @@ -5678,7 +5696,7 @@ button.close { right: 0; bottom: 0; left: 0; - z-index: 1040; + z-index: 1050; -webkit-overflow-scrolling: touch; outline: 0; } @@ -5719,10 +5737,12 @@ button.close { outline: 0; } .modal-backdrop { - position: absolute; + position: fixed; top: 0; right: 0; + bottom: 0; left: 0; + z-index: 1040; background-color: #000000; } .modal-backdrop.fade { @@ -5793,7 +5813,6 @@ button.close { position: absolute; z-index: 1070; display: block; - visibility: visible; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; font-size: 12px; font-weight: normal; @@ -6316,7 +6335,6 @@ button.close { } .hidden { display: none !important; - visibility: hidden !important; } .affix { position: fixed; diff --git a/libmproxy/web/static/vendor.js b/libmproxy/web/static/vendor.js index d98e50d9..6b34edb9 100644 --- a/libmproxy/web/static/vendor.js +++ b/libmproxy/web/static/vendor.js @@ -394,49 +394,432 @@ var invariant = function(condition, format, a, b, c, d, e, f) { module.exports = invariant; },{}],4:[function(require,module,exports){ -"use strict"; +module.exports = require('./lib/'); -/** - * Represents a cancellation caused by navigating away - * before the previous transition has fully resolved. - */ -function Cancellation() {} +},{"./lib/":5}],5:[function(require,module,exports){ +// Load modules -module.exports = Cancellation; -},{}],5:[function(require,module,exports){ -"use strict"; +var Stringify = require('./stringify'); +var Parse = require('./parse'); -var warning = require("react/lib/warning"); -var invariant = require("react/lib/invariant"); -function checkPropTypes(componentName, propTypes, props) { - for (var propName in propTypes) { - if (propTypes.hasOwnProperty(propName)) { - var error = propTypes[propName](props, propName, componentName); +// Declare internals - if (error instanceof Error) warning(false, error.message); +var internals = {}; + + +module.exports = { + stringify: Stringify, + parse: Parse +}; + +},{"./parse":6,"./stringify":7}],6:[function(require,module,exports){ +// Load modules + +var Utils = require('./utils'); + + +// Declare internals + +var internals = { + delimiter: '&', + depth: 5, + arrayLimit: 20, + parameterLimit: 1000 +}; + + +internals.parseValues = function (str, options) { + + var obj = {}; + var parts = str.split(options.delimiter, options.parameterLimit === Infinity ? undefined : options.parameterLimit); + + for (var i = 0, il = parts.length; i < il; ++i) { + var part = parts[i]; + var pos = part.indexOf(']=') === -1 ? part.indexOf('=') : part.indexOf(']=') + 1; + + if (pos === -1) { + obj[Utils.decode(part)] = ''; + } + else { + var key = Utils.decode(part.slice(0, pos)); + var val = Utils.decode(part.slice(pos + 1)); + + if (Object.prototype.hasOwnProperty(key)) { + continue; + } + + if (!obj.hasOwnProperty(key)) { + obj[key] = val; + } + else { + obj[key] = [].concat(obj[key]).concat(val); + } + } } - } -} -var Configuration = { + return obj; +}; + - statics: { +internals.parseObject = function (chain, val, options) { - validateProps: function validateProps(props) { - checkPropTypes(this.displayName, this.propTypes, props); + if (!chain.length) { + return val; } - }, + var root = chain.shift(); - render: function render() { - invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.displayName); - } + var obj = {}; + if (root === '[]') { + obj = []; + obj = obj.concat(internals.parseObject(chain, val, options)); + } + else { + var cleanRoot = root[0] === '[' && root[root.length - 1] === ']' ? root.slice(1, root.length - 1) : root; + var index = parseInt(cleanRoot, 10); + var indexString = '' + index; + if (!isNaN(index) && + root !== cleanRoot && + indexString === cleanRoot && + index >= 0 && + index <= options.arrayLimit) { + + obj = []; + obj[index] = internals.parseObject(chain, val, options); + } + else { + obj[cleanRoot] = internals.parseObject(chain, val, options); + } + } + + return obj; +}; + + +internals.parseKeys = function (key, val, options) { + + if (!key) { + return; + } + + // The regex chunks + + var parent = /^([^\[\]]*)/; + var child = /(\[[^\[\]]*\])/g; + + // Get the parent + + var segment = parent.exec(key); + + // Don't allow them to overwrite object prototype properties + + if (Object.prototype.hasOwnProperty(segment[1])) { + return; + } + + // Stash the parent if it exists + + var keys = []; + if (segment[1]) { + keys.push(segment[1]); + } + + // Loop through children appending to the array until we hit depth + + var i = 0; + while ((segment = child.exec(key)) !== null && i < options.depth) { + + ++i; + if (!Object.prototype.hasOwnProperty(segment[1].replace(/\[|\]/g, ''))) { + keys.push(segment[1]); + } + } + + // If there's a remainder, just add whatever is left + + if (segment) { + keys.push('[' + key.slice(segment.index) + ']'); + } + + return internals.parseObject(keys, val, options); +}; + + +module.exports = function (str, options) { + + if (str === '' || + str === null || + typeof str === 'undefined') { + + return {}; + } + + options = options || {}; + options.delimiter = typeof options.delimiter === 'string' || Utils.isRegExp(options.delimiter) ? options.delimiter : internals.delimiter; + options.depth = typeof options.depth === 'number' ? options.depth : internals.depth; + options.arrayLimit = typeof options.arrayLimit === 'number' ? options.arrayLimit : internals.arrayLimit; + options.parameterLimit = typeof options.parameterLimit === 'number' ? options.parameterLimit : internals.parameterLimit; + + var tempObj = typeof str === 'string' ? internals.parseValues(str, options) : str; + var obj = {}; + + // Iterate over the keys and setup the new object + + var keys = Object.keys(tempObj); + for (var i = 0, il = keys.length; i < il; ++i) { + var key = keys[i]; + var newObj = internals.parseKeys(key, tempObj[key], options); + obj = Utils.merge(obj, newObj); + } + + return Utils.compact(obj); +}; + +},{"./utils":8}],7:[function(require,module,exports){ +// Load modules + +var Utils = require('./utils'); + + +// Declare internals + +var internals = { + delimiter: '&', + arrayPrefixGenerators: { + brackets: function (prefix, key) { + return prefix + '[]'; + }, + indices: function (prefix, key) { + return prefix + '[' + key + ']'; + }, + repeat: function (prefix, key) { + return prefix; + } + } +}; + + +internals.stringify = function (obj, prefix, generateArrayPrefix) { + + if (Utils.isBuffer(obj)) { + obj = obj.toString(); + } + else if (obj instanceof Date) { + obj = obj.toISOString(); + } + else if (obj === null) { + obj = ''; + } + + if (typeof obj === 'string' || + typeof obj === 'number' || + typeof obj === 'boolean') { + + return [encodeURIComponent(prefix) + '=' + encodeURIComponent(obj)]; + } + + var values = []; + + if (typeof obj === 'undefined') { + return values; + } + + var objKeys = Object.keys(obj); + for (var i = 0, il = objKeys.length; i < il; ++i) { + var key = objKeys[i]; + if (Array.isArray(obj)) { + values = values.concat(internals.stringify(obj[key], generateArrayPrefix(prefix, key), generateArrayPrefix)); + } + else { + values = values.concat(internals.stringify(obj[key], prefix + '[' + key + ']', generateArrayPrefix)); + } + } + + return values; +}; + + +module.exports = function (obj, options) { + + options = options || {}; + var delimiter = typeof options.delimiter === 'undefined' ? internals.delimiter : options.delimiter; + + var keys = []; + + if (typeof obj !== 'object' || + obj === null) { + + return ''; + } + + var arrayFormat; + if (options.arrayFormat in internals.arrayPrefixGenerators) { + arrayFormat = options.arrayFormat; + } + else if ('indices' in options) { + arrayFormat = options.indices ? 'indices' : 'repeat'; + } + else { + arrayFormat = 'indices'; + } + + var generateArrayPrefix = internals.arrayPrefixGenerators[arrayFormat]; + + var objKeys = Object.keys(obj); + for (var i = 0, il = objKeys.length; i < il; ++i) { + var key = objKeys[i]; + keys = keys.concat(internals.stringify(obj[key], key, generateArrayPrefix)); + } + + return keys.join(delimiter); +}; + +},{"./utils":8}],8:[function(require,module,exports){ +// Load modules + + +// Declare internals + +var internals = {}; + + +exports.arrayToObject = function (source) { + + var obj = {}; + for (var i = 0, il = source.length; i < il; ++i) { + if (typeof source[i] !== 'undefined') { + + obj[i] = source[i]; + } + } + + return obj; +}; + + +exports.merge = function (target, source) { + + if (!source) { + return target; + } + + if (typeof source !== 'object') { + if (Array.isArray(target)) { + target.push(source); + } + else { + target[source] = true; + } + + return target; + } + + if (typeof target !== 'object') { + target = [target].concat(source); + return target; + } + + if (Array.isArray(target) && + !Array.isArray(source)) { + + target = exports.arrayToObject(target); + } + + var keys = Object.keys(source); + for (var k = 0, kl = keys.length; k < kl; ++k) { + var key = keys[k]; + var value = source[key]; + + if (!target[key]) { + target[key] = value; + } + else { + target[key] = exports.merge(target[key], value); + } + } + + return target; +}; + + +exports.decode = function (str) { + + try { + return decodeURIComponent(str.replace(/\+/g, ' ')); + } catch (e) { + return str; + } +}; + + +exports.compact = function (obj, refs) { + + if (typeof obj !== 'object' || + obj === null) { + + return obj; + } + + refs = refs || []; + var lookup = refs.indexOf(obj); + if (lookup !== -1) { + return refs[lookup]; + } + + refs.push(obj); + + if (Array.isArray(obj)) { + var compacted = []; + + for (var i = 0, il = obj.length; i < il; ++i) { + if (typeof obj[i] !== 'undefined') { + compacted.push(obj[i]); + } + } + + return compacted; + } + + var keys = Object.keys(obj); + for (i = 0, il = keys.length; i < il; ++i) { + var key = keys[i]; + obj[key] = exports.compact(obj[key], refs); + } + + return obj; +}; + + +exports.isRegExp = function (obj) { + return Object.prototype.toString.call(obj) === '[object RegExp]'; +}; + + +exports.isBuffer = function (obj) { + if (obj === null || + typeof obj === 'undefined') { + + return false; + } + + return !!(obj.constructor && + obj.constructor.isBuffer && + obj.constructor.isBuffer(obj)); }; -module.exports = Configuration; -},{"react/lib/invariant":182,"react/lib/warning":202}],6:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ +"use strict"; + +/** + * Represents a cancellation caused by navigating away + * before the previous transition has fully resolved. + */ +function Cancellation() {} + +module.exports = Cancellation; +},{}],10:[function(require,module,exports){ "use strict"; var invariant = require("react/lib/invariant"); @@ -467,10 +850,10 @@ var History = { }; module.exports = History; -},{"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],7:[function(require,module,exports){ +},{"react/lib/ExecutionEnvironment":60,"react/lib/invariant":189}],11:[function(require,module,exports){ "use strict"; -var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; @@ -520,7 +903,7 @@ var Match = (function () { this.routes = routes; } - _prototypeProperties(Match, { + _createClass(Match, null, { findMatch: { /** @@ -537,9 +920,7 @@ var Match = (function () { for (var i = 0, len = routes.length; match == null && i < len; ++i) match = deepSearch(routes[i], pathname, query); return match; - }, - writable: true, - configurable: true + } } }); @@ -547,11 +928,20 @@ var Match = (function () { })(); module.exports = Match; -},{"./PathUtils":10}],8:[function(require,module,exports){ +},{"./PathUtils":13}],12:[function(require,module,exports){ "use strict"; +var warning = require("react/lib/warning"); var PropTypes = require("./PropTypes"); +function deprecatedMethod(routerMethodName, fn) { + return function () { + warning(false, "Router.Navigation is deprecated. Please use this.context.router." + routerMethodName + "() instead"); + + return fn.apply(this, arguments); + }; +} + /** * A mixin for components that modify the URL. * @@ -559,11 +949,11 @@ var PropTypes = require("./PropTypes"); * * var MyLink = React.createClass({ * mixins: [ Router.Navigation ], - * handleClick: function (event) { + * handleClick(event) { * event.preventDefault(); * this.transitionTo('aRoute', { the: 'params' }, { the: 'query' }); * }, - * render: function () { + * render() { * return ( * Click me! * ); @@ -573,87 +963,52 @@ var PropTypes = require("./PropTypes"); var Navigation = { contextTypes: { - makePath: PropTypes.func.isRequired, - makeHref: PropTypes.func.isRequired, - transitionTo: PropTypes.func.isRequired, - replaceWith: PropTypes.func.isRequired, - goBack: PropTypes.func.isRequired + router: PropTypes.router.isRequired }, /** * Returns an absolute URL path created from the given route * name, URL parameters, and query values. */ - makePath: function makePath(to, params, query) { - return this.context.makePath(to, params, query); - }, + makePath: deprecatedMethod("makePath", function (to, params, query) { + return this.context.router.makePath(to, params, query); + }), /** * Returns a string that may safely be used as the href of a * link to the route with the given name. */ - makeHref: function makeHref(to, params, query) { - return this.context.makeHref(to, params, query); - }, + makeHref: deprecatedMethod("makeHref", function (to, params, query) { + return this.context.router.makeHref(to, params, query); + }), /** * Transitions to the URL specified in the arguments by pushing * a new URL onto the history stack. */ - transitionTo: function transitionTo(to, params, query) { - this.context.transitionTo(to, params, query); - }, + transitionTo: deprecatedMethod("transitionTo", function (to, params, query) { + this.context.router.transitionTo(to, params, query); + }), /** * Transitions to the URL specified in the arguments by replacing * the current URL in the history stack. */ - replaceWith: function replaceWith(to, params, query) { - this.context.replaceWith(to, params, query); - }, + replaceWith: deprecatedMethod("replaceWith", function (to, params, query) { + this.context.router.replaceWith(to, params, query); + }), /** * Transitions to the previous URL. */ - goBack: function goBack() { - return this.context.goBack(); - } + goBack: deprecatedMethod("goBack", function () { + return this.context.router.goBack(); + }) }; module.exports = Navigation; -},{"./PropTypes":11}],9:[function(require,module,exports){ -"use strict"; - -var PropTypes = require("./PropTypes"); - -/** - * Provides the router with context for Router.Navigation. - */ -var NavigationContext = { - - childContextTypes: { - makePath: PropTypes.func.isRequired, - makeHref: PropTypes.func.isRequired, - transitionTo: PropTypes.func.isRequired, - replaceWith: PropTypes.func.isRequired, - goBack: PropTypes.func.isRequired - }, - - getChildContext: function getChildContext() { - return { - makePath: this.constructor.makePath.bind(this.constructor), - makeHref: this.constructor.makeHref.bind(this.constructor), - transitionTo: this.constructor.transitionTo.bind(this.constructor), - replaceWith: this.constructor.replaceWith.bind(this.constructor), - goBack: this.constructor.goBack.bind(this.constructor) - }; - } - -}; - -module.exports = NavigationContext; -},{"./PropTypes":11}],10:[function(require,module,exports){ +},{"./PropTypes":14,"react/lib/warning":210}],13:[function(require,module,exports){ "use strict"; var invariant = require("react/lib/invariant"); @@ -797,7 +1152,7 @@ var PathUtils = { if (existingQuery) query = query ? merge(existingQuery, query) : existingQuery; - var queryString = qs.stringify(query, { indices: false }); + var queryString = qs.stringify(query, { arrayFormat: "brackets" }); if (queryString) { return PathUtils.withoutQuery(path) + "?" + queryString; @@ -807,27 +1162,39 @@ var PathUtils = { }; module.exports = PathUtils; -},{"qs":38,"qs/lib/utils":42,"react/lib/invariant":182}],11:[function(require,module,exports){ +},{"qs":4,"qs/lib/utils":8,"react/lib/invariant":189}],14:[function(require,module,exports){ "use strict"; var assign = require("react/lib/Object.assign"); var ReactPropTypes = require("react").PropTypes; +var Route = require("./Route"); -var PropTypes = assign({ +var PropTypes = assign({}, ReactPropTypes, { /** - * Requires that the value of a prop be falsy. + * Indicates that a prop should be falsy. */ falsy: function falsy(props, propName, componentName) { if (props[propName]) { return new Error("<" + componentName + "> may not have a \"" + propName + "\" prop"); } - } + }, + + /** + * Indicates that a prop should be a Route object. + */ + route: ReactPropTypes.instanceOf(Route), -}, ReactPropTypes); + /** + * Indicates that a prop should be a Router object. + */ + //router: ReactPropTypes.instanceOf(Router) // TODO + router: ReactPropTypes.func + +}); module.exports = PropTypes; -},{"react":"react","react/lib/Object.assign":70}],12:[function(require,module,exports){ +},{"./Route":16,"react":"react","react/lib/Object.assign":67}],15:[function(require,module,exports){ "use strict"; /** @@ -840,10 +1207,10 @@ function Redirect(to, params, query) { } module.exports = Redirect; -},{}],13:[function(require,module,exports){ +},{}],16:[function(require,module,exports){ "use strict"; -var _prototypeProperties = function (child, staticProps, instanceProps) { if (staticProps) Object.defineProperties(child, staticProps); if (instanceProps) Object.defineProperties(child.prototype, instanceProps); }; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; @@ -869,7 +1236,33 @@ var Route = (function () { this.handler = handler; } - _prototypeProperties(Route, { + _createClass(Route, { + appendChild: { + + /** + * Appends the given route to this route's child routes. + */ + + value: function appendChild(route) { + invariant(route instanceof Route, "route.appendChild must use a valid Route"); + + if (!this.childRoutes) this.childRoutes = []; + + this.childRoutes.push(route); + } + }, + toString: { + value: function toString() { + var string = ""; + + return string; + } + } + }, { createRoute: { /** @@ -928,7 +1321,7 @@ var Route = (function () { if (path && !(options.isDefault || options.isNotFound)) { if (PathUtils.isAbsolute(path)) { if (parentRoute) { - invariant(parentRoute.paramNames.length === 0, "You cannot nest path \"%s\" inside \"%s\"; the parent requires URL parameters", path, parentRoute.path); + invariant(path === parentRoute.path || parentRoute.paramNames.length === 0, "You cannot nest path \"%s\" inside \"%s\"; the parent requires URL parameters", path, parentRoute.path); } } else if (parentRoute) { // Relative paths extend their parent. @@ -968,9 +1361,7 @@ var Route = (function () { } return route; - }, - writable: true, - configurable: true + } }, createDefaultRoute: { @@ -981,9 +1372,7 @@ var Route = (function () { value: function createDefaultRoute(options) { return Route.createRoute(assign({}, options, { isDefault: true })); - }, - writable: true, - configurable: true + } }, createNotFoundRoute: { @@ -994,9 +1383,7 @@ var Route = (function () { value: function createNotFoundRoute(options) { return Route.createRoute(assign({}, options, { isNotFound: true })); - }, - writable: true, - configurable: true + } }, createRedirect: { @@ -1020,39 +1407,7 @@ var Route = (function () { transition.redirect(options.to, options.params || params, options.query || query); } })); - }, - writable: true, - configurable: true - } - }, { - appendChild: { - - /** - * Appends the given route to this route's child routes. - */ - - value: function appendChild(route) { - invariant(route instanceof Route, "route.appendChild must use a valid Route"); - - if (!this.childRoutes) this.childRoutes = []; - - this.childRoutes.push(route); - }, - writable: true, - configurable: true - }, - toString: { - value: function toString() { - var string = ""; - - return string; - }, - writable: true, - configurable: true + } } }); @@ -1060,67 +1415,12 @@ var Route = (function () { })(); module.exports = Route; -},{"./PathUtils":10,"react/lib/Object.assign":70,"react/lib/invariant":182,"react/lib/warning":202}],14:[function(require,module,exports){ +},{"./PathUtils":13,"react/lib/Object.assign":67,"react/lib/invariant":189,"react/lib/warning":210}],17:[function(require,module,exports){ "use strict"; -var React = require("react"); -var assign = require("react/lib/Object.assign"); -var PropTypes = require("./PropTypes"); - -var REF_NAME = "__routeHandler__"; - -var RouteHandlerMixin = { - - contextTypes: { - getRouteAtDepth: PropTypes.func.isRequired, - setRouteComponentAtDepth: PropTypes.func.isRequired, - routeHandlers: PropTypes.array.isRequired - }, - - childContextTypes: { - routeHandlers: PropTypes.array.isRequired - }, - - getChildContext: function getChildContext() { - return { - routeHandlers: this.context.routeHandlers.concat([this]) - }; - }, - - componentDidMount: function componentDidMount() { - this._updateRouteComponent(this.refs[REF_NAME]); - }, - - componentDidUpdate: function componentDidUpdate() { - this._updateRouteComponent(this.refs[REF_NAME]); - }, - - componentWillUnmount: function componentWillUnmount() { - this._updateRouteComponent(null); - }, - - _updateRouteComponent: function _updateRouteComponent(component) { - this.context.setRouteComponentAtDepth(this.getRouteDepth(), component); - }, - - getRouteDepth: function getRouteDepth() { - return this.context.routeHandlers.length; - }, - - createChildRouteHandler: function createChildRouteHandler(props) { - var route = this.context.getRouteAtDepth(this.getRouteDepth()); - return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; - } - -}; - -module.exports = RouteHandlerMixin; -},{"./PropTypes":11,"react":"react","react/lib/Object.assign":70}],15:[function(require,module,exports){ -"use strict"; - -var invariant = require("react/lib/invariant"); -var canUseDOM = require("react/lib/ExecutionEnvironment").canUseDOM; -var getWindowScrollPosition = require("./getWindowScrollPosition"); +var invariant = require("react/lib/invariant"); +var canUseDOM = require("react/lib/ExecutionEnvironment").canUseDOM; +var getWindowScrollPosition = require("./getWindowScrollPosition"); function shouldUpdateScroll(state, prevState) { if (!prevState) { @@ -1191,11 +1491,20 @@ var ScrollHistory = { }; module.exports = ScrollHistory; -},{"./getWindowScrollPosition":30,"react/lib/ExecutionEnvironment":64,"react/lib/invariant":182}],16:[function(require,module,exports){ +},{"./getWindowScrollPosition":32,"react/lib/ExecutionEnvironment":60,"react/lib/invariant":189}],18:[function(require,module,exports){ "use strict"; +var warning = require("react/lib/warning"); var PropTypes = require("./PropTypes"); +function deprecatedMethod(routerMethodName, fn) { + return function () { + warning(false, "Router.State is deprecated. Please use this.context.router." + routerMethodName + "() instead"); + + return fn.apply(this, arguments); + }; +} + /** * A mixin for components that need to know the path, routes, URL * params and query that are currently active. @@ -1204,7 +1513,7 @@ var PropTypes = require("./PropTypes"); * * var AboutLink = React.createClass({ * mixins: [ Router.State ], - * render: function () { + * render() { * var className = this.props.className; * * if (this.isActive('about')) @@ -1217,158 +1526,56 @@ var PropTypes = require("./PropTypes"); var State = { contextTypes: { - getCurrentPath: PropTypes.func.isRequired, - getCurrentRoutes: PropTypes.func.isRequired, - getCurrentPathname: PropTypes.func.isRequired, - getCurrentParams: PropTypes.func.isRequired, - getCurrentQuery: PropTypes.func.isRequired, - isActive: PropTypes.func.isRequired + router: PropTypes.router.isRequired }, /** * Returns the current URL path. */ - getPath: function getPath() { - return this.context.getCurrentPath(); - }, - - /** - * Returns an array of the routes that are currently active. - */ - getRoutes: function getRoutes() { - return this.context.getCurrentRoutes(); - }, + getPath: deprecatedMethod("getCurrentPath", function () { + return this.context.router.getCurrentPath(); + }), /** * Returns the current URL path without the query string. */ - getPathname: function getPathname() { - return this.context.getCurrentPathname(); - }, + getPathname: deprecatedMethod("getCurrentPathname", function () { + return this.context.router.getCurrentPathname(); + }), /** * Returns an object of the URL params that are currently active. */ - getParams: function getParams() { - return this.context.getCurrentParams(); - }, + getParams: deprecatedMethod("getCurrentParams", function () { + return this.context.router.getCurrentParams(); + }), /** * Returns an object of the query params that are currently active. */ - getQuery: function getQuery() { - return this.context.getCurrentQuery(); - }, - - /** - * A helper method to determine if a given route, params, and query - * are active. - */ - isActive: function isActive(to, params, query) { - return this.context.isActive(to, params, query); - } - -}; - -module.exports = State; -},{"./PropTypes":11}],17:[function(require,module,exports){ -"use strict"; - -var assign = require("react/lib/Object.assign"); -var PropTypes = require("./PropTypes"); -var PathUtils = require("./PathUtils"); - -function routeIsActive(activeRoutes, routeName) { - return activeRoutes.some(function (route) { - return route.name === routeName; - }); -} - -function paramsAreActive(activeParams, params) { - for (var property in params) if (String(activeParams[property]) !== String(params[property])) { - return false; - }return true; -} - -function queryIsActive(activeQuery, query) { - for (var property in query) if (String(activeQuery[property]) !== String(query[property])) { - return false; - }return true; -} - -/** - * Provides the router with context for Router.State. - */ -var StateContext = { - - /** - * Returns the current URL path + query string. - */ - getCurrentPath: function getCurrentPath() { - return this.state.path; - }, - - /** - * Returns a read-only array of the currently active routes. - */ - getCurrentRoutes: function getCurrentRoutes() { - return this.state.routes.slice(0); - }, - - /** - * Returns the current URL path without the query string. - */ - getCurrentPathname: function getCurrentPathname() { - return this.state.pathname; - }, + getQuery: deprecatedMethod("getCurrentQuery", function () { + return this.context.router.getCurrentQuery(); + }), /** - * Returns a read-only object of the currently active URL parameters. + * Returns an array of the routes that are currently active. */ - getCurrentParams: function getCurrentParams() { - return assign({}, this.state.params); - }, + getRoutes: deprecatedMethod("getCurrentRoutes", function () { + return this.context.router.getCurrentRoutes(); + }), /** - * Returns a read-only object of the currently active query parameters. - */ - getCurrentQuery: function getCurrentQuery() { - return assign({}, this.state.query); - }, - - /** - * Returns true if the given route, params, and query are active. + * A helper method to determine if a given route, params, and query + * are active. */ - isActive: function isActive(to, params, query) { - if (PathUtils.isAbsolute(to)) { - return to === this.state.path; - }return routeIsActive(this.state.routes, to) && paramsAreActive(this.state.params, params) && (query == null || queryIsActive(this.state.query, query)); - }, - - childContextTypes: { - getCurrentPath: PropTypes.func.isRequired, - getCurrentRoutes: PropTypes.func.isRequired, - getCurrentPathname: PropTypes.func.isRequired, - getCurrentParams: PropTypes.func.isRequired, - getCurrentQuery: PropTypes.func.isRequired, - isActive: PropTypes.func.isRequired - }, - - getChildContext: function getChildContext() { - return { - getCurrentPath: this.getCurrentPath, - getCurrentRoutes: this.getCurrentRoutes, - getCurrentPathname: this.getCurrentPathname, - getCurrentParams: this.getCurrentParams, - getCurrentQuery: this.getCurrentQuery, - isActive: this.isActive - }; - } + isActive: deprecatedMethod("isActive", function (to, params, query) { + return this.context.router.isActive(to, params, query); + }) }; -module.exports = StateContext; -},{"./PathUtils":10,"./PropTypes":11,"react/lib/Object.assign":70}],18:[function(require,module,exports){ +module.exports = State; +},{"./PropTypes":14,"react/lib/warning":210}],19:[function(require,module,exports){ "use strict"; /* jshint -W058 */ @@ -1444,7 +1651,7 @@ Transition.to = function (transition, routes, params, query, callback) { }; module.exports = Transition; -},{"./Cancellation":4,"./Redirect":12}],19:[function(require,module,exports){ +},{"./Cancellation":9,"./Redirect":15}],20:[function(require,module,exports){ "use strict"; /** @@ -1470,7 +1677,7 @@ var LocationActions = { }; module.exports = LocationActions; -},{}],20:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ "use strict"; var LocationActions = require("../actions/LocationActions"); @@ -1500,7 +1707,7 @@ var ImitateBrowserBehavior = { }; module.exports = ImitateBrowserBehavior; -},{"../actions/LocationActions":19}],21:[function(require,module,exports){ +},{"../actions/LocationActions":20}],22:[function(require,module,exports){ "use strict"; /** @@ -1516,12 +1723,56 @@ var ScrollToTopBehavior = { }; module.exports = ScrollToTopBehavior; -},{}],22:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + +/** + * This component is necessary to get around a context warning + * present in React 0.13.0. It sovles this by providing a separation + * between the "owner" and "parent" contexts. + */ + var React = require("react"); -var Configuration = require("../Configuration"); + +var ContextWrapper = (function (_React$Component) { + function ContextWrapper() { + _classCallCheck(this, ContextWrapper); + + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } + } + + _inherits(ContextWrapper, _React$Component); + + _createClass(ContextWrapper, { + render: { + value: function render() { + return this.props.children; + } + } + }); + + return ContextWrapper; +})(React.Component); + +module.exports = ContextWrapper; +},{"react":"react"}],24:[function(require,module,exports){ +"use strict"; + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var PropTypes = require("../PropTypes"); +var RouteHandler = require("./RouteHandler"); +var Route = require("./Route"); /** * A component is a special kind of that @@ -1529,32 +1780,49 @@ var PropTypes = require("../PropTypes"); * Only one such route may be used at any given level in the * route hierarchy. */ -var DefaultRoute = React.createClass({ - displayName: "DefaultRoute", +var DefaultRoute = (function (_Route) { + function DefaultRoute() { + _classCallCheck(this, DefaultRoute); - mixins: [Configuration], - - propTypes: { - name: PropTypes.string, - path: PropTypes.falsy, - children: PropTypes.falsy, - handler: PropTypes.func.isRequired + if (_Route != null) { + _Route.apply(this, arguments); + } } -}); + _inherits(DefaultRoute, _Route); + + return DefaultRoute; +})(Route); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +DefaultRoute.propTypes = { + name: PropTypes.string, + path: PropTypes.falsy, + children: PropTypes.falsy, + handler: PropTypes.func.isRequired +}; + +DefaultRoute.defaultProps = { + handler: RouteHandler +}; module.exports = DefaultRoute; -},{"../Configuration":5,"../PropTypes":11,"react":"react"}],23:[function(require,module,exports){ +},{"../PropTypes":14,"./Route":28,"./RouteHandler":29}],25:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var React = require("react"); -var classSet = require("react/lib/cx"); var assign = require("react/lib/Object.assign"); -var Navigation = require("../Navigation"); -var State = require("../State"); var PropTypes = require("../PropTypes"); -var Route = require("../Route"); function isLeftClickEvent(event) { return event.button === 0; @@ -1582,88 +1850,116 @@ function isModifiedEvent(event) { * * */ -var Link = React.createClass({ - displayName: "Link", +var Link = (function (_React$Component) { + function Link() { + _classCallCheck(this, Link); - mixins: [Navigation, State], + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } + } - propTypes: { - activeClassName: PropTypes.string.isRequired, - to: PropTypes.oneOfType([PropTypes.string, PropTypes.instanceOf(Route)]), - params: PropTypes.object, - query: PropTypes.object, - activeStyle: PropTypes.object, - onClick: PropTypes.func - }, + _inherits(Link, _React$Component); - getDefaultProps: function getDefaultProps() { - return { - activeClassName: "active" - }; - }, + _createClass(Link, { + handleClick: { + value: function handleClick(event) { + var allowTransition = true; + var clickResult; - handleClick: function handleClick(event) { - var allowTransition = true; - var clickResult; + if (this.props.onClick) clickResult = this.props.onClick(event); - if (this.props.onClick) clickResult = this.props.onClick(event); + if (isModifiedEvent(event) || !isLeftClickEvent(event)) { + return; + }if (clickResult === false || event.defaultPrevented === true) allowTransition = false; - if (isModifiedEvent(event) || !isLeftClickEvent(event)) { - return; - }if (clickResult === false || event.defaultPrevented === true) allowTransition = false; + event.preventDefault(); - event.preventDefault(); + if (allowTransition) this.context.router.transitionTo(this.props.to, this.props.params, this.props.query); + } + }, + getHref: { - if (allowTransition) this.transitionTo(this.props.to, this.props.params, this.props.query); - }, + /** + * Returns the value of the "href" attribute to use on the DOM element. + */ - /** - * Returns the value of the "href" attribute to use on the DOM element. - */ - getHref: function getHref() { - return this.makeHref(this.props.to, this.props.params, this.props.query); - }, + value: function getHref() { + return this.context.router.makeHref(this.props.to, this.props.params, this.props.query); + } + }, + getClassName: { - /** - * Returns the value of the "class" attribute to use on the DOM element, which contains - * the value of the activeClassName property when this is active. - */ - getClassName: function getClassName() { - var classNames = {}; + /** + * Returns the value of the "class" attribute to use on the DOM element, which contains + * the value of the activeClassName property when this is active. + */ - if (this.props.className) classNames[this.props.className] = true; + value: function getClassName() { + var className = this.props.className; - if (this.getActiveState()) classNames[this.props.activeClassName] = true; + if (this.getActiveState()) className += " " + this.props.activeClassName; - return classSet(classNames); - }, + return className; + } + }, + getActiveState: { + value: function getActiveState() { + return this.context.router.isActive(this.props.to, this.props.params, this.props.query); + } + }, + render: { + value: function render() { + var props = assign({}, this.props, { + href: this.getHref(), + className: this.getClassName(), + onClick: this.handleClick.bind(this) + }); - getActiveState: function getActiveState() { - return this.isActive(this.props.to, this.props.params, this.props.query); - }, + if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle; - render: function render() { - var props = assign({}, this.props, { - href: this.getHref(), - className: this.getClassName(), - onClick: this.handleClick - }); + return React.DOM.a(props, this.props.children); + } + } + }); - if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle; + return Link; +})(React.Component); - return React.DOM.a(props, this.props.children); - } +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 -}); +Link.contextTypes = { + router: PropTypes.router.isRequired +}; + +Link.propTypes = { + activeClassName: PropTypes.string.isRequired, + to: PropTypes.oneOfType([PropTypes.string, PropTypes.route]).isRequired, + params: PropTypes.object, + query: PropTypes.object, + activeStyle: PropTypes.object, + onClick: PropTypes.func +}; + +Link.defaultProps = { + activeClassName: "active", + className: "" +}; module.exports = Link; -},{"../Navigation":8,"../PropTypes":11,"../Route":13,"../State":16,"react":"react","react/lib/Object.assign":70,"react/lib/cx":160}],24:[function(require,module,exports){ +},{"../PropTypes":14,"react":"react","react/lib/Object.assign":67}],26:[function(require,module,exports){ "use strict"; -var React = require("react"); -var Configuration = require("../Configuration"); +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var PropTypes = require("../PropTypes"); +var RouteHandler = require("./RouteHandler"); +var Route = require("./Route"); /** * A is a special kind of that @@ -1672,56 +1968,95 @@ var PropTypes = require("../PropTypes"); * Only one such route may be used at any given level in the * route hierarchy. */ -var NotFoundRoute = React.createClass({ - - displayName: "NotFoundRoute", - mixins: [Configuration], +var NotFoundRoute = (function (_Route) { + function NotFoundRoute() { + _classCallCheck(this, NotFoundRoute); - propTypes: { - name: PropTypes.string, - path: PropTypes.falsy, - children: PropTypes.falsy, - handler: PropTypes.func.isRequired + if (_Route != null) { + _Route.apply(this, arguments); + } } -}); + _inherits(NotFoundRoute, _Route); + + return NotFoundRoute; +})(Route); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +NotFoundRoute.propTypes = { + name: PropTypes.string, + path: PropTypes.falsy, + children: PropTypes.falsy, + handler: PropTypes.func.isRequired +}; + +NotFoundRoute.defaultProps = { + handler: RouteHandler +}; module.exports = NotFoundRoute; -},{"../Configuration":5,"../PropTypes":11,"react":"react"}],25:[function(require,module,exports){ +},{"../PropTypes":14,"./Route":28,"./RouteHandler":29}],27:[function(require,module,exports){ "use strict"; -var React = require("react"); -var Configuration = require("../Configuration"); +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var PropTypes = require("../PropTypes"); +var Route = require("./Route"); /** * A component is a special kind of that always * redirects to another route when it matches. */ -var Redirect = React.createClass({ - displayName: "Redirect", +var Redirect = (function (_Route) { + function Redirect() { + _classCallCheck(this, Redirect); - mixins: [Configuration], - - propTypes: { - path: PropTypes.string, - from: PropTypes.string, // Alias for path. - to: PropTypes.string, - handler: PropTypes.falsy + if (_Route != null) { + _Route.apply(this, arguments); + } } -}); + _inherits(Redirect, _Route); + + return Redirect; +})(Route); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +Redirect.propTypes = { + path: PropTypes.string, + from: PropTypes.string, // Alias for path. + to: PropTypes.string, + handler: PropTypes.falsy +}; + +// Redirects should not have a default handler +Redirect.defaultProps = {}; module.exports = Redirect; -},{"../Configuration":5,"../PropTypes":11,"react":"react"}],26:[function(require,module,exports){ +},{"../PropTypes":14,"./Route":28}],28:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var React = require("react"); -var Configuration = require("../Configuration"); +var invariant = require("react/lib/invariant"); var PropTypes = require("../PropTypes"); var RouteHandler = require("./RouteHandler"); + /** * components specify components that are rendered to the page when the * URL matches a given pattern. @@ -1762,52 +2097,147 @@ var RouteHandler = require("./RouteHandler"); * * If no handler is provided for the route, it will render a matched child route. */ -var Route = React.createClass({ - displayName: "Route", +var Route = (function (_React$Component) { + function Route() { + _classCallCheck(this, Route); - mixins: [Configuration], + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } + } - propTypes: { - name: PropTypes.string, - path: PropTypes.string, - handler: PropTypes.func, - ignoreScrollBehavior: PropTypes.bool - }, + _inherits(Route, _React$Component); - getDefaultProps: function getDefaultProps() { - return { - handler: RouteHandler - }; - } + _createClass(Route, { + render: { + value: function render() { + invariant(false, "%s elements are for router configuration only and should not be rendered", this.constructor.name); + } + } + }); -}); + return Route; +})(React.Component); + +// TODO: Include these in the above class definition +// once we can use ES7 property initializers. +// https://github.com/babel/babel/issues/619 + +Route.propTypes = { + name: PropTypes.string, + path: PropTypes.string, + handler: PropTypes.func, + ignoreScrollBehavior: PropTypes.bool +}; + +Route.defaultProps = { + handler: RouteHandler +}; module.exports = Route; -},{"../Configuration":5,"../PropTypes":11,"./RouteHandler":27,"react":"react"}],27:[function(require,module,exports){ +},{"../PropTypes":14,"./RouteHandler":29,"react":"react","react/lib/invariant":189}],29:[function(require,module,exports){ "use strict"; +var _createClass = (function () { function defineProperties(target, props) { for (var key in props) { var prop = props[key]; prop.configurable = true; if (prop.value) prop.writable = true; } Object.defineProperties(target, props); } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })(); + +var _inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; }; + +var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; + var React = require("react"); -var RouteHandlerMixin = require("../RouteHandlerMixin"); +var ContextWrapper = require("./ContextWrapper"); +var assign = require("react/lib/Object.assign"); +var PropTypes = require("../PropTypes"); + +var REF_NAME = "__routeHandler__"; /** * A component renders the active child route handler * when routes are nested. */ -var RouteHandler = React.createClass({ - - displayName: "RouteHandler", - mixins: [RouteHandlerMixin], +var RouteHandler = (function (_React$Component) { + function RouteHandler() { + _classCallCheck(this, RouteHandler); - render: function render() { - return this.createChildRouteHandler(); + if (_React$Component != null) { + _React$Component.apply(this, arguments); + } } -}); + _inherits(RouteHandler, _React$Component); + + _createClass(RouteHandler, { + getChildContext: { + value: function getChildContext() { + return { + routeDepth: this.context.routeDepth + 1 + }; + } + }, + componentDidMount: { + value: function componentDidMount() { + this._updateRouteComponent(this.refs[REF_NAME]); + } + }, + componentDidUpdate: { + value: function componentDidUpdate() { + this._updateRouteComponent(this.refs[REF_NAME]); + } + }, + componentWillUnmount: { + value: function componentWillUnmount() { + this._updateRouteComponent(null); + } + }, + _updateRouteComponent: { + value: function _updateRouteComponent(component) { + this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component); + } + }, + getRouteDepth: { + value: function getRouteDepth() { + return this.context.routeDepth; + } + }, + createChildRouteHandler: { + value: function createChildRouteHandler(props) { + var route = this.context.router.getRouteAtDepth(this.getRouteDepth()); + return route ? React.createElement(route.handler, assign({}, props || this.props, { ref: REF_NAME })) : null; + } + }, + render: { + value: function render() { + var handler = this.createChildRouteHandler(); + //