From 8f13d2c856d2741e6644b1a48a9ff2de302d5f2b Mon Sep 17 00:00:00 2001 From: or-else Date: Sun, 17 Sep 2017 18:36:01 -0700 Subject: [PATCH] better error handling and reporting --- css/base.css | 50 +++++++++-- drafty.js | 4 - index.html | 249 ++++++++++++++++++++++++++++++++------------------- 3 files changed, 201 insertions(+), 102 deletions(-) diff --git a/css/base.css b/css/base.css index 75bfc322..e76698da 100644 --- a/css/base.css +++ b/css/base.css @@ -505,17 +505,52 @@ table.permission-editor td.checkbox { /* Area which displays an error message */ -#alert-box { - background-color: transparent; - height: 0; +.alert-box { + position: relative; + display: none; overflow: hidden; + padding: 1rem 1.5rem 1rem 0.5rem; } -#alert-box.error-alert { - padding: 0.5rem; +.alert-box .cancel { + position: absolute; + right: 0.25rem; + top: 0.5rem; +} + +.alert-box.error { + display: block; background-color: #ffcdd2; - border-color: #d32f2f; + border-bottom: 1px solid #ffbfc8; color: #b71c1c; - height: auto; +} +.alert-box.warning { + display: block; + background-color: #fff9c4; + border-bottom: 1px solid #fff59d; + color: #607d8b; +} +.alert-box .cancel .material-icons { + font-size: 1rem; +} +.alert-box.error .cancel .material-icons { + color: #b71c1c; +} +.alert-box.warning .cancel .material-icons { + color: #757575; +} +.alert-box .icon { + display: inline-block; + vertical-align: middle; + margin-right: 0.5rem; +} +.alert-box .icon .material-icons { + font-size: 1.5rem; +} +.alert-box.warning .icon .material-icons { + color: #fbc02d; +} +.alert-box.error .icon .material-icons { + color: #ef5350; } /* List of contacts */ @@ -1488,7 +1523,6 @@ ul.tabbar li.active > a:after { border-top: 0.35rem solid #1e88e5; width: 2rem; height: 2rem; - -webkit-animation: spin 1.5s linear infinite; animation: spin 1.5s linear infinite; } diff --git a/drafty.js b/drafty.js index b845c3ea..cb30fa56 100644 --- a/drafty.js +++ b/drafty.js @@ -521,8 +521,6 @@ Sample JSON representation of the text above: return a.at - b.at; }); - console.log(spans); - // Denormalize entities into spans. Create a copy of the objects to leave // original Drafty object unchanged. spans = spans.map(function(s) { @@ -536,8 +534,6 @@ Sample JSON representation of the text above: return {tp: tp, data: data, at: s.at, len: s.len}; }); - console.log(spans); - return forEach(txt, 0, txt.length, spans, formatter, context); }, diff --git a/index.html b/index.html index 83c8b517..69cecf43 100644 --- a/index.html +++ b/index.html @@ -15,9 +15,9 @@ - - - + + + @@ -133,7 +133,7 @@ /* BEGIN Context Menu: popup/dropdown menu */ // Function is used by context menu to set permissions. - function topicPermissionSetter(mode, topicName, uid) { + function topicPermissionSetter(mode, topicName, uid, errorHandler) { var topic = Tinode.getTopic(topicName); if (!topic) { console.log("Topic not found: " + topicName); @@ -151,7 +151,14 @@ } else { am = topic.getAccessMode().updateWant(mode).getWant(); } - topic.setMeta({sub: {user: uid, mode: am}}); + var instance = this; + topic.setMeta({sub: {user: uid, mode: am}}).catch(function(err) { + if (errorHandler) { + errorHandler(err.message, "err"); + } else { + console.log(err); + } + }); } // Context menu items. @@ -164,6 +171,7 @@ console.log("Topic not found: " + topicName); return; } + topic.delMessagesAll(false).then(function(){ console.log("refresh message list"); }); @@ -232,7 +240,7 @@ e.stopPropagation(); this.props.hide(); var item = this.props.items[e.currentTarget.dataset.id]; - item.handler(this.props.topic, this.props.user); + item.handler(this.props.topic, this.props.user, this.props.onError); }, render: function() { @@ -280,13 +288,17 @@ /* END Popup/dropdown menu */ /* The X menu to be displayed in title bars */ - var MenuCancel = React.createClass({ - render: function() { + class MenuCancel extends React.PureComponent { + constructor(props) { + super(props); + } + + render() { return ( close ); } - }); + } class LoadSpinner extends React.PureComponent { render() { @@ -714,31 +726,20 @@ login: this.props.login, password: '', hostName: this.props.serverAddress, - errorCleared: false }; }, - clearError: function() { - if (!this.state.errorCleared) { - this.props.onClearError(); - this.setState({errorCleared: true}); - } - }, handleLoginChange: function(e) { - this.clearError(); this.setState({login: e.target.value}); }, handlePasswordChange: function(e) { - this.clearError(); this.setState({password: e.target.value}); }, handleServerAddressChange: function(name) { - this.clearError(); this.setState({hostName: name}); this.props.onServerAddressChange(name); }, handleSubmit: function(e) { e.preventDefault(); - this.setState({errorCleared: false}); this.props.onLogin(this.state.login.trim(), this.state.password.trim()); }, @@ -1000,20 +1001,11 @@ ); } - }) + }); var SidepanelView = React.createClass({ - getInitialState: function() { - return { - showErrorText: true - }; - }, - handleClearErrorText: function() { - this.setState({showErrorText: false}); - }, handleLoginRequested: function(login, password) { this.props.onLoginRequest(login, password); - this.setState({showErrorText: true}); }, render: function() { var title = null; @@ -1046,17 +1038,16 @@ onNewTopic={this.props.onNewTopic} onCancel={this.props.onCancel} /> - + {this.props.state === 'login' ? : + onServerAddressChange={this.props.onConnectionSettings} /> : this.props.state === 'register' ? {errorText} +
+
{icon}
+ {this.props.text} +
+
); } }); @@ -1891,8 +1897,6 @@ resetDesc: function(topic) { var defacs = topic.getDefaultAccess() || {}; var acs = topic.getAccessMode(); - console.log("InfoView: reset desc"); - console.log(topic.public); this.setState({ owner: acs && acs.isOwner(), admin: acs && acs.isAdmin(), @@ -2104,6 +2108,10 @@ + {this.props.displayMobile ? + : null} {this.state.showMemberPanel ? + {this.props.displayMobile ? + : null}
    @@ -2770,6 +2791,8 @@ password: '', myUserId: null, errorText: '', + errorLevel: null, + liveConnection: navigator.onLine, topicSelected: '', loginDisabled: false, subscriptions: {}, @@ -2788,7 +2811,11 @@ }; }, componentDidMount: function() { + var instance = this; window.addEventListener('resize', this.handleResize); + window.addEventListener('online', function(e) { instance.handleOnline(true); }); + window.addEventListener('offline', function(e) { instance.handleOnline(false); }); + Tinode.enableLogging(true, true); Tinode.onConnect = this.handleConnected; Tinode.onDisconnect = this.handleDisconnect; @@ -2798,25 +2825,39 @@ tnSetup: function(serverAddress, transport) { Tinode.setup(APP_NAME, serverAddress, API_KEY, transport); }, - componentWillUnmount: function(){ - window.removeEventListener('resize', this.handleResize); - }, - handleResize: function(){ + handleResize: function() { var mobile = document.documentElement.clientWidth <= MEDIA_BREAKPOINT; if (this.state.displayMobile != mobile) { this.setState({displayMobile: mobile}); } }, + handleOnline: function(online) { + var newState = {liveConnection: online}; + if (online) { + this.handleError("", null); + } else { + this.handleError("No connection", "warn"); + } + this.setState({liveConnection: online}); + }, + + handleError: function(err, level) { + this.setState({errorText: err, errorLevel: level}); + }, + // User clicked Login button in the side panel. handleLoginRequest: function(login, password) { var instance = this; - this.setState({loginDisabled: true, errorText: '', login: login, password: password}); + this.setState({loginDisabled: true, login: login, password: password}); + this.handleError("", null); + if (Tinode.isConnected()) { this.doLogin(login, password); } else { Tinode.connect().catch(function(err) { // Socket error - instance.setState({loginDisabled: false, errorText: err.message}); + instance.setState({loginDisabled: false}); + instance.handleError(err.message, "err"); }); } }, @@ -2844,39 +2885,33 @@ if (promise) { promise.then(function() { instance.handleLoginSuccessful(instance); - }) - .catch(function(err) { + }).catch(function(err) { // Login failed, report error - instance.setState({loginDisabled: false, errorText: err.message}); + instance.setState({loginDisabled: false}); + instance.handleError(err.message, "err"); }); } }, handleLoginSuccessful: function(instance) { - instance.setState({errorText: ''}); + instance.handleError("", null); // Logged in fine, subscribe to 'me' attaching callbacks from the contacts view. var me = Tinode.getMeTopic(); me.onMetaDesc = instance.tnMeMetaDesc; instance.setState({sidePanelSelected: "contacts", myUserId: Tinode.getCurrentUserID()}); // Subscribe, fetch topic desc, the list of subscriptions. Messages are not fetched. - me.subscribe(me.startMetaQuery().withLaterSub().withDesc().build()); + me.subscribe(me.startMetaQuery().withLaterSub().withDesc().build()).catch(function(err){ + instance.handleError(err.message, "err"); + }); }, handleDisconnect: function(err) { this.setState({ - sidePanelSelected: 'login', - sidePanelTitle: null, - sidePanelAvatar: null, dialogSelected: null, - myUserId: null, - errorText: err && err.message ? err.message : 'disconnected', - topicSelected: '', + errorText: err && err.message ? err.message : 'Disconnected', + errorLevel: err && err.message ? "err" : "warn", loginDisabled: false, - subscriptions: {}, - showInfoPanel: false, - infoPanelShowMembers: false, - mobilePanel: 'sidepanel', contextMenuVisible: false, contextMenuBounds: null, contextMenuClickAt: null, @@ -2907,11 +2942,14 @@ var fnd = Tinode.getFndTopic(); fnd.onSubsUpdated = this.tnFndSubsUpdated; fnd.onMetaDesc = this.tnFndMetaDesc; + var instance = this; if (fnd.isSubscribed()) { this.setState({contactsSearchQuery: fnd.private ? fnd.private.toString() : ''}); this.tnFndSubsUpdated(); } else { - fnd.subscribe(fnd.startMetaQuery().withSub().withLaterDesc().build()); + fnd.subscribe(fnd.startMetaQuery().withSub().withLaterDesc().build()).catch(function(err){ + instance.handleError(err.message, "err"); + }); } }, @@ -2933,10 +2971,13 @@ @param query {Array} is an array of contacts to search for */ handleSearchContacts: function(query) { - var fnd = Tinode.getFndTopic(); - fnd.setMeta({desc: {private: query}}).then(function(ctrl) { - fnd.getMeta(fnd.startMetaQuery().withSub().withLaterDesc().build()); - }); + var fnd = Tinode.getFndTopic(); + var instance = this; + fnd.setMeta({desc: {private: query}}).then(function(ctrl) { + fnd.getMeta(fnd.startMetaQuery().withSub().withLaterDesc().build()); + }).catch(function(err){ + instance.handleError(err.message, "err"); + }); }, // User clicked on a contact in the side panel or deleted a contact. @@ -2968,14 +3009,19 @@ // User clicked on a message send button. handleSendMessage: function(msg) { var topic = Tinode.getTopic(this.state.topicSelected); + var instance = this; // Check if the message is formatted. var dft = Drafty.parse(msg); if (Drafty.isPlainText(dft)) { // The message is simple text. Sned if as text/plain. - topic.publish(msg); + topic.publish(msg).catch(function(err) { + instance.handleError(err.message, "err"); + });; } else { // Formatted message. Send it as a Drafty object. - topic.publish(dft, undefined, Drafty.getContentType()); + topic.publish(dft, undefined, Drafty.getContentType()).catch(function(err) { + instance.handleError(err.message, "err"); + }); } }, // User chose a Sign Up menu item. @@ -2990,21 +3036,24 @@ .then(function() { return Tinode.createAccountBasic(login_, password_, {public: public_, private: private_, tags: tags_}); - }) - .then(function() { + }).then(function() { instance.handleLoginSuccessful(instance); - }) - .catch(function(err) { - instance.setState({errorText: err.message}); + }).catch(function(err) { + instance.handleError(err.message, "err"); }); }, handleUpdateAccountRequest: function(password, pub, priv) { + var instance = this; if (pub || priv) { - Tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}}); + Tinode.getMeTopic().setMeta({desc: {public: pub, private: priv}}).catch(function(err) { + instance.handleError(err.message, "err"); + }); } if (password) { - Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password); + Tinode.updateAccountBasic(null, Tinode.getCurrentLogin(), password).catch(function(err) { + instance.handleError(err.message, "err"); + }); } }, // User chose Settings menu item. @@ -3043,7 +3092,6 @@ topic.subscribe(query.build(), setParams).then(function() { instance.setState({ - errorText: '', topicSelected: topic.name, sidePanelSelected: 'contacts', mobilePanel: 'topic-view', @@ -3051,11 +3099,12 @@ infoPanelShowMembers: peerName ? false : true }); }).catch(function(err) { - instance.setState({errorText: err.message}); + instance.handleError(err.message, "err"); }); }, handleTopicUpdateRequest: function(topicName, pub, priv, permissions) { + var instance = this; var topic = Tinode.getTopic(topicName); if (topic) { var params = {}; @@ -3068,11 +3117,14 @@ if (permissions) { params.defacs = permissions; } - topic.setMeta({desc: params}); + topic.setMeta({desc: params}).catch(function(err) { + instance.handleError(err.message, "err"); + }); } }, handleChangePermissions: function(topicName, mode, uid) { + var instance = this; var topic = Tinode.getTopic(topicName); if (topic) { var am = topic.getAccessMode(); @@ -3083,7 +3135,9 @@ am.updateWant(mode); mode = am.getWant(); } - topic.setMeta({sub: {user: uid, mode: mode}}); + topic.setMeta({sub: {user: uid, mode: mode}}).catch(function(err) { + instance.handleError(err.message, "err"); + }); } }, @@ -3100,6 +3154,8 @@ showInfoPanel: false, infoPanelShowMembers: false }); + }).catch(function(err) { + instance.handleError(err.message, "err"); }); }, @@ -3171,9 +3227,12 @@ }); } + var instance = this; if (removed && removed.length > 0) { removed.map(function(uid) { - topic.delSubscription(uid); + topic.delSubscription(uid).catch(function(err) { + instance.handleError(err.message, "err"); + });; }); } }, @@ -3194,11 +3253,12 @@ user={this.state.contextMenuUser} items={this.state.contextMenuItems} hide={this.handleHideContextMenu} - onAction={this.handleContextMenuAction} /> + onAction={this.handleContextMenuAction} + onError={this.handleError} /> {this.state.showInfoPanel ?