web: improve views

This commit is contained in:
Maximilian Hils 2015-03-22 15:19:35 +01:00
parent cf9f91b0b4
commit 39a8ac7e2a
7 changed files with 139 additions and 47 deletions

View File

@ -303,6 +303,7 @@ function isUndefined(arg) {
},{}],2:[function(require,module,exports){ },{}],2:[function(require,module,exports){
var $ = require("jquery"); var $ = require("jquery");
var AppDispatcher = require("./dispatcher.js").AppDispatcher;
var ActionTypes = { var ActionTypes = {
// Connection // Connection
@ -422,10 +423,11 @@ module.exports = {
FlowActions: FlowActions, FlowActions: FlowActions,
StoreCmds: StoreCmds, StoreCmds: StoreCmds,
SettingsActions: SettingsActions, SettingsActions: SettingsActions,
EventLogActions: EventLogActions,
Query: Query Query: Query
}; };
},{"jquery":"jquery"}],3:[function(require,module,exports){ },{"./dispatcher.js":19,"jquery":"jquery"}],3:[function(require,module,exports){
var React = require("react"); var React = require("react");
var ReactRouter = require("react-router"); var ReactRouter = require("react-router");
@ -1200,7 +1202,7 @@ var MessageUtils = require("../../flow/utils.js").MessageUtils;
var utils = require("../../utils.js"); var utils = require("../../utils.js");
var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i;
var Image = React.createClass({displayName: "Image", var ViewImage = React.createClass({displayName: "ViewImage",
statics: { statics: {
matches: function (message) { matches: function (message) {
return image_regex.test(MessageUtils.getContentType(message)); return image_regex.test(MessageUtils.getContentType(message));
@ -1217,16 +1219,28 @@ var Image = React.createClass({displayName: "Image",
var RawMixin = { var RawMixin = {
getInitialState: function () { getInitialState: function () {
return { return {
content: undefined content: undefined,
request: undefined
} }
}, },
requestContent: function (nextProps) { requestContent: function (nextProps) {
this.setState({content: undefined}); if(this.state.request){
this.state.request.abort();
}
var request = MessageUtils.getContent(nextProps.flow, nextProps.message); var request = MessageUtils.getContent(nextProps.flow, nextProps.message);
this.setState({
content: undefined,
request: request
});
request.done(function (data) { request.done(function (data) {
this.setState({content: data}); this.setState({content: data});
}.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) {
this.setState({content: "AJAX Error: " + textStatus}); if(textStatus === "abort"){
return;
}
this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown});
}.bind(this)).always(function(){
this.setState({request: undefined});
}.bind(this)); }.bind(this));
}, },
@ -1238,6 +1252,11 @@ var RawMixin = {
this.requestContent(nextProps); this.requestContent(nextProps);
} }
}, },
componentWillUnmount: function(){
if(this.state.request){
this.state.request.abort();
}
},
render: function () { render: function () {
if (!this.state.content) { if (!this.state.content) {
return React.createElement("div", {className: "text-center"}, return React.createElement("div", {className: "text-center"},
@ -1248,7 +1267,7 @@ var RawMixin = {
} }
}; };
var Raw = React.createClass({displayName: "Raw", var ViewRaw = React.createClass({displayName: "ViewRaw",
mixins: [RawMixin], mixins: [RawMixin],
statics: { statics: {
matches: function (message) { matches: function (message) {
@ -1260,8 +1279,25 @@ var Raw = React.createClass({displayName: "Raw",
} }
}); });
var json_regex = /^application\/json$/i;
var ViewJSON = React.createClass({displayName: "ViewJSON",
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 React.createElement("pre", null, json);
}
});
var Auto = React.createClass({displayName: "Auto", var ViewAuto = React.createClass({displayName: "ViewAuto",
statics: { statics: {
matches: function () { matches: function () {
return false; // don't match itself return false; // don't match itself
@ -1276,12 +1312,12 @@ var Auto = React.createClass({displayName: "Auto",
} }
}, },
render: function () { render: function () {
var View = Auto.findView(this.props.message); var View = ViewAuto.findView(this.props.message);
return React.createElement(View, React.__spread({}, this.props)); return React.createElement(View, React.__spread({}, this.props));
} }
}); });
var all = [Auto, Image, Raw]; var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw];
var ContentEmpty = React.createClass({displayName: "ContentEmpty", var ContentEmpty = React.createClass({displayName: "ContentEmpty",
@ -1299,6 +1335,12 @@ var ContentMissing = React.createClass({displayName: "ContentMissing",
}); });
var TooLarge = React.createClass({displayName: "TooLarge", var TooLarge = React.createClass({displayName: "TooLarge",
statics: {
isTooLarge: function(message){
var max_mb = ViewImage.matches(message) ? 10 : 0.2;
return message.contentLength > 1024 * 1024 * max_mb;
}
},
render: function () { render: function () {
var size = utils.formatSize(this.props.message.contentLength); var size = utils.formatSize(this.props.message.contentLength);
return React.createElement("div", {className: "alert alert-warning"}, return React.createElement("div", {className: "alert alert-warning"},
@ -1318,10 +1360,10 @@ var ViewSelector = React.createClass({displayName: "ViewSelector",
className += " active"; className += " active";
} }
var text; var text;
if (view === Auto) { if (view === ViewAuto) {
text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", "");
} else { } else {
text = view.displayName.toLowerCase(); text = view.displayName.toLowerCase().replace("view", "");
} }
views.push( views.push(
React.createElement("button", { React.createElement("button", {
@ -1341,7 +1383,7 @@ var ContentView = React.createClass({displayName: "ContentView",
getInitialState: function () { getInitialState: function () {
return { return {
displayLarge: false, displayLarge: false,
View: Auto View: ViewAuto
}; };
}, },
propTypes: { propTypes: {
@ -1370,7 +1412,7 @@ var ContentView = React.createClass({displayName: "ContentView",
return React.createElement(ContentEmpty, React.__spread({}, this.props)); return React.createElement(ContentEmpty, React.__spread({}, this.props));
} else if (message.contentLength === null) { } else if (message.contentLength === null) {
return React.createElement(ContentMissing, React.__spread({}, this.props)); return React.createElement(ContentMissing, React.__spread({}, this.props));
} else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) {
return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge})); return React.createElement(TooLarge, React.__spread({}, this.props, {onClick: this.displayLarge}));
} }
@ -2644,6 +2686,7 @@ module.exports = VirtualScrollMixin;
},{"react":"react"}],18:[function(require,module,exports){ },{"react":"react"}],18:[function(require,module,exports){
var actions = require("./actions.js"); var actions = require("./actions.js");
var AppDispatcher = require("./dispatcher.js").AppDispatcher;
function Connection(url) { function Connection(url) {
if (url[0] === "/") { if (url[0] === "/") {
@ -2660,18 +2703,18 @@ function Connection(url) {
}; };
ws.onerror = function () { ws.onerror = function () {
actions.ConnectionActions.error(); actions.ConnectionActions.error();
EventLogActions.add_event("WebSocket connection error."); actions.EventLogActions.add_event("WebSocket connection error.");
}; };
ws.onclose = function () { ws.onclose = function () {
actions.ConnectionActions.close(); actions.ConnectionActions.close();
EventLogActions.add_event("WebSocket connection closed."); actions.EventLogActions.add_event("WebSocket connection closed.");
}; };
return ws; return ws;
} }
module.exports = Connection; module.exports = Connection;
},{"./actions.js":2}],19:[function(require,module,exports){ },{"./actions.js":2,"./dispatcher.js":19}],19:[function(require,module,exports){
var flux = require("flux"); var flux = require("flux");
@ -2681,7 +2724,7 @@ const PayloadSources = {
}; };
AppDispatcher = new flux.Dispatcher(); var AppDispatcher = new flux.Dispatcher();
AppDispatcher.dispatchViewAction = function (action) { AppDispatcher.dispatchViewAction = function (action) {
action.source = PayloadSources.VIEW; action.source = PayloadSources.VIEW;
this.dispatch(action); this.dispatch(action);
@ -4477,7 +4520,7 @@ var $ = require("jquery");
var MessageUtils = { var MessageUtils = {
getContentType: function (message) { getContentType: function (message) {
return this.get_first_header(message, /^Content-Type$/i); return this.get_first_header(message, /^Content-Type$/i).split(";")[0].trim();
}, },
get_first_header: function (message, regex) { get_first_header: function (message, regex) {
//FIXME: Cache Invalidation. //FIXME: Cache Invalidation.
@ -4854,6 +4897,7 @@ module.exports = {
},{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){ },{"../utils.js":24,"events":1,"lodash":"lodash"}],24:[function(require,module,exports){
var $ = require("jquery"); var $ = require("jquery");
var _ = require("lodash"); var _ = require("lodash");
var actions = require("./actions.js");
var Key = { var Key = {
UP: 38, UP: 38,
@ -4921,13 +4965,13 @@ var end = String.fromCharCode(0xffff);
function reverseString(s){ function reverseString(s){
return String.fromCharCode.apply(String, return String.fromCharCode.apply(String,
_.map(s.split(""), function (c) { _.map(s.split(""), function (c) {
return 0xffff - c.charCodeAt(); return 0xffff - c.charCodeAt(0);
}) })
) + end; ) + end;
} }
function getCookie(name) { function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b"));
return r ? r[1] : undefined; return r ? r[1] : undefined;
} }
var xsrf = $.param({_xsrf: getCookie("_xsrf")}); var xsrf = $.param({_xsrf: getCookie("_xsrf")});
@ -4944,10 +4988,12 @@ $.ajaxPrefilter(function (options) {
}); });
// Log AJAX Errors // Log AJAX Errors
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
if(thrownError === "abort"){
return;
}
var message = jqXHR.responseText; var message = jqXHR.responseText;
console.error(message, arguments); console.error(thrownError, message, arguments);
EventLogActions.add_event(thrownError + ": " + message); actions.EventLogActions.add_event(thrownError + ": " + message);
window.alert(message);
}); });
module.exports = { module.exports = {
@ -4958,7 +5004,7 @@ module.exports = {
Key: Key Key: Key
}; };
},{"jquery":"jquery","lodash":"lodash"}]},{},[3]) },{"./actions.js":2,"jquery":"jquery","lodash":"lodash"}]},{},[3])
//# sourceMappingURL=app.js.map //# sourceMappingURL=app.js.map

View File

@ -1,4 +1,5 @@
var $ = require("jquery"); var $ = require("jquery");
var AppDispatcher = require("./dispatcher.js").AppDispatcher;
var ActionTypes = { var ActionTypes = {
// Connection // Connection
@ -118,5 +119,6 @@ module.exports = {
FlowActions: FlowActions, FlowActions: FlowActions,
StoreCmds: StoreCmds, StoreCmds: StoreCmds,
SettingsActions: SettingsActions, SettingsActions: SettingsActions,
EventLogActions: EventLogActions,
Query: Query Query: Query
}; };

View File

@ -5,7 +5,7 @@ var MessageUtils = require("../../flow/utils.js").MessageUtils;
var utils = require("../../utils.js"); var utils = require("../../utils.js");
var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i; var image_regex = /^image\/(png|jpe?g|gif|vnc.microsoft.icon|x-icon)$/i;
var Image = React.createClass({ var ViewImage = React.createClass({
statics: { statics: {
matches: function (message) { matches: function (message) {
return image_regex.test(MessageUtils.getContentType(message)); return image_regex.test(MessageUtils.getContentType(message));
@ -22,16 +22,28 @@ var Image = React.createClass({
var RawMixin = { var RawMixin = {
getInitialState: function () { getInitialState: function () {
return { return {
content: undefined content: undefined,
request: undefined
} }
}, },
requestContent: function (nextProps) { requestContent: function (nextProps) {
this.setState({content: undefined}); if(this.state.request){
this.state.request.abort();
}
var request = MessageUtils.getContent(nextProps.flow, nextProps.message); var request = MessageUtils.getContent(nextProps.flow, nextProps.message);
this.setState({
content: undefined,
request: request
});
request.done(function (data) { request.done(function (data) {
this.setState({content: data}); this.setState({content: data});
}.bind(this)).fail(function (jqXHR, textStatus, errorThrown) { }.bind(this)).fail(function (jqXHR, textStatus, errorThrown) {
this.setState({content: "AJAX Error: " + textStatus}); if(textStatus === "abort"){
return;
}
this.setState({content: "AJAX Error: " + textStatus + "\r\n" + errorThrown});
}.bind(this)).always(function(){
this.setState({request: undefined});
}.bind(this)); }.bind(this));
}, },
@ -43,6 +55,11 @@ var RawMixin = {
this.requestContent(nextProps); this.requestContent(nextProps);
} }
}, },
componentWillUnmount: function(){
if(this.state.request){
this.state.request.abort();
}
},
render: function () { render: function () {
if (!this.state.content) { if (!this.state.content) {
return <div className="text-center"> return <div className="text-center">
@ -53,7 +70,7 @@ var RawMixin = {
} }
}; };
var Raw = React.createClass({ var ViewRaw = React.createClass({
mixins: [RawMixin], mixins: [RawMixin],
statics: { statics: {
matches: function (message) { matches: function (message) {
@ -65,8 +82,25 @@ var Raw = React.createClass({
} }
}); });
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 Auto = React.createClass({ var ViewAuto = React.createClass({
statics: { statics: {
matches: function () { matches: function () {
return false; // don't match itself return false; // don't match itself
@ -81,12 +115,12 @@ var Auto = React.createClass({
} }
}, },
render: function () { render: function () {
var View = Auto.findView(this.props.message); var View = ViewAuto.findView(this.props.message);
return <View {...this.props}/>; return <View {...this.props}/>;
} }
}); });
var all = [Auto, Image, Raw]; var all = [ViewAuto, ViewImage, ViewJSON, ViewRaw];
var ContentEmpty = React.createClass({ var ContentEmpty = React.createClass({
@ -104,6 +138,12 @@ var ContentMissing = React.createClass({
}); });
var TooLarge = React.createClass({ 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 () { render: function () {
var size = utils.formatSize(this.props.message.contentLength); var size = utils.formatSize(this.props.message.contentLength);
return <div className="alert alert-warning"> return <div className="alert alert-warning">
@ -123,10 +163,10 @@ var ViewSelector = React.createClass({
className += " active"; className += " active";
} }
var text; var text;
if (view === Auto) { if (view === ViewAuto) {
text = "auto: " + Auto.findView(this.props.message).displayName.toLowerCase(); text = "auto: " + ViewAuto.findView(this.props.message).displayName.toLowerCase().replace("view", "");
} else { } else {
text = view.displayName.toLowerCase(); text = view.displayName.toLowerCase().replace("view", "");
} }
views.push( views.push(
<button <button
@ -146,7 +186,7 @@ var ContentView = React.createClass({
getInitialState: function () { getInitialState: function () {
return { return {
displayLarge: false, displayLarge: false,
View: Auto View: ViewAuto
}; };
}, },
propTypes: { propTypes: {
@ -175,7 +215,7 @@ var ContentView = React.createClass({
return <ContentEmpty {...this.props}/>; return <ContentEmpty {...this.props}/>;
} else if (message.contentLength === null) { } else if (message.contentLength === null) {
return <ContentMissing {...this.props}/>; return <ContentMissing {...this.props}/>;
} else if (message.contentLength > 1024 * 1024 * 3 && !this.state.displayLarge) { } else if (!this.state.displayLarge && TooLarge.isTooLarge(message)) {
return <TooLarge {...this.props} onClick={this.displayLarge}/>; return <TooLarge {...this.props} onClick={this.displayLarge}/>;
} }

View File

@ -1,5 +1,6 @@
var actions = require("./actions.js"); var actions = require("./actions.js");
var AppDispatcher = require("./dispatcher.js").AppDispatcher;
function Connection(url) { function Connection(url) {
if (url[0] === "/") { if (url[0] === "/") {
@ -16,11 +17,11 @@ function Connection(url) {
}; };
ws.onerror = function () { ws.onerror = function () {
actions.ConnectionActions.error(); actions.ConnectionActions.error();
EventLogActions.add_event("WebSocket connection error."); actions.EventLogActions.add_event("WebSocket connection error.");
}; };
ws.onclose = function () { ws.onclose = function () {
actions.ConnectionActions.close(); actions.ConnectionActions.close();
EventLogActions.add_event("WebSocket connection closed."); actions.EventLogActions.add_event("WebSocket connection closed.");
}; };
return ws; return ws;
} }

View File

@ -7,7 +7,7 @@ const PayloadSources = {
}; };
AppDispatcher = new flux.Dispatcher(); var AppDispatcher = new flux.Dispatcher();
AppDispatcher.dispatchViewAction = function (action) { AppDispatcher.dispatchViewAction = function (action) {
action.source = PayloadSources.VIEW; action.source = PayloadSources.VIEW;
this.dispatch(action); this.dispatch(action);

View File

@ -3,7 +3,7 @@ var $ = require("jquery");
var MessageUtils = { var MessageUtils = {
getContentType: function (message) { getContentType: function (message) {
return this.get_first_header(message, /^Content-Type$/i); return this.get_first_header(message, /^Content-Type$/i).split(";")[0].trim();
}, },
get_first_header: function (message, regex) { get_first_header: function (message, regex) {
//FIXME: Cache Invalidation. //FIXME: Cache Invalidation.

View File

@ -1,5 +1,6 @@
var $ = require("jquery"); var $ = require("jquery");
var _ = require("lodash"); var _ = require("lodash");
var actions = require("./actions.js");
var Key = { var Key = {
UP: 38, UP: 38,
@ -67,13 +68,13 @@ var end = String.fromCharCode(0xffff);
function reverseString(s){ function reverseString(s){
return String.fromCharCode.apply(String, return String.fromCharCode.apply(String,
_.map(s.split(""), function (c) { _.map(s.split(""), function (c) {
return 0xffff - c.charCodeAt(); return 0xffff - c.charCodeAt(0);
}) })
) + end; ) + end;
} }
function getCookie(name) { function getCookie(name) {
var r = document.cookie.match("\\b" + name + "=([^;]*)\\b"); var r = document.cookie.match(new RegExp("\\b" + name + "=([^;]*)\\b"));
return r ? r[1] : undefined; return r ? r[1] : undefined;
} }
var xsrf = $.param({_xsrf: getCookie("_xsrf")}); var xsrf = $.param({_xsrf: getCookie("_xsrf")});
@ -90,10 +91,12 @@ $.ajaxPrefilter(function (options) {
}); });
// Log AJAX Errors // Log AJAX Errors
$(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) { $(document).ajaxError(function (event, jqXHR, ajaxSettings, thrownError) {
if(thrownError === "abort"){
return;
}
var message = jqXHR.responseText; var message = jqXHR.responseText;
console.error(message, arguments); console.error(thrownError, message, arguments);
EventLogActions.add_event(thrownError + ": " + message); actions.EventLogActions.add_event(thrownError + ": " + message);
window.alert(message);
}); });
module.exports = { module.exports = {