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 ViewImage = React.createClass({ statics: { matches: function (message) { return image_regex.test(MessageUtils.getContentType(message)); } }, render: function () { var url = MessageUtils.getContentURL(this.props.flow, this.props.message); return <div className="flowview-image"> <img src={url} alt="preview" className="img-thumbnail"/> </div>; } }); var RawMixin = { getInitialState: function () { return { content: undefined, request: undefined } }, requestContent: function (nextProps) { if (this.state.request) { this.state.request.abort(); } var request = MessageUtils.getContent(nextProps.flow, nextProps.message); this.setState({ content: undefined, request: request }); request.done(function (data) { this.setState({content: data}); }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { if (textStatus === "abort") { return; } this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown}); }.bind(this)).always(function () { this.setState({request: undefined}); }.bind(this)); }, componentWillMount: function () { this.requestContent(this.props); }, componentWillReceiveProps: function (nextProps) { if (nextProps.message !== this.props.message) { this.requestContent(nextProps); } }, componentWillUnmount: function () { if (this.state.request) { this.state.request.abort(); } }, render: function () { if (!this.state.content) { return <div className="text-center"> <i className="fa fa-spinner fa-spin"></i> </div>; } return this.renderContent(); } }; var ViewRaw = React.createClass({ mixins: [RawMixin], statics: { matches: function (message) { return true; } }, renderContent: function () { return <pre>{this.state.content}</pre>; } }); var json_regex = /^application\/json$/i; var ViewJSON = React.createClass({ mixins: [RawMixin], statics: { matches: function (message) { return json_regex.test(MessageUtils.getContentType(message)); } }, renderContent: function () { var json = this.state.content; try { json = JSON.stringify(JSON.parse(json), null, 2); } catch (e) { } return <pre>{json}</pre>; } }); var ViewAuto = React.createClass({ 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 = ViewAuto.findView(this.props.message); return <View {...this.props}/>; } }); var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw]; var ContentEmpty = React.createClass({ render: function () { var message_name = this.props.flow.request === this.props.message ? "request" : "response"; return <div className="alert alert-info">No {message_name} content.</div>; } }); var ContentMissing = React.createClass({ render: function () { var message_name = this.props.flow.request === this.props.message ? "Request" : "Response"; return <div className="alert alert-info">{message_name} content missing.</div>; } }); var TooLarge = React.createClass({ statics: { isTooLarge: function (message) { var max_mb = ViewImage.matches(message) ? 10 : 0.2; return message.contentLength > 1024 * 1024 * max_mb; } }, render: function () { var size = utils.formatSize(this.props.message.contentLength); return <div className="alert alert-warning"> <button onClick={this.props.onClick} className="btn btn-xs btn-warning pull-right">Display anyway</button> {size} content size. </div>; } }); var ViewSelector = React.createClass({ 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 === ViewAuto) { text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", ""); } else { text = view.displayName.toLowerCase().replace("view", ""); } views.push( <button key={view.displayName} onClick={this.props.selectView.bind(null, view)} className={className}> {text} </button> ); } return <div className="view-selector btn-group btn-group-xs">{views}</div>; } }); var ContentView = React.createClass({ getInitialState: function () { return { displayLarge: false, View: ViewAuto }; }, propTypes: { // It may seem a bit weird at the first glance: // Every view takes the flow and the message as props, e.g. // <Auto flow={flow} message={flow.request}/> 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 <ContentEmpty {...this.props}/>; } else if (message.contentLength === null) { return <ContentMissing {...this.props}/>; } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) { return <TooLarge {...this.props} onClick={this.displayLarge}/>; } var downloadUrl = MessageUtils.getContentURL(this.props.flow, message); return <div> <this.state.View {...this.props} /> <div className="view-options text-center"> <ViewSelector selectView={this.selectView} active={this.state.View} message={message}/> <a className="btn btn-default btn-xs" href={downloadUrl}> <i className="fa fa-download"/> </a> </div> </div>; } }); module.exports = ContentView;