diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss index d292c729dd..68aca63459 100644 --- a/res/css/views/rooms/_EventTile.scss +++ b/res/css/views/rooms/_EventTile.scss @@ -653,3 +653,17 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody { } } } + +.mx_EventTile_tileError { + color: red; + + .mx_EventTile_line span { + padding: 4px 8px; + border-radius: 11px; + box-shadow: 0px 0px 3px red inset; + } + + a { + margin-left: 1em; + } +} \ No newline at end of file diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js index b8b11fbb31..e6f8de61a9 100644 --- a/src/components/structures/MessagePanel.js +++ b/src/components/structures/MessagePanel.js @@ -502,6 +502,7 @@ export default class MessagePanel extends React.Component { } _getTilesForEvent(prevEvent, mxEv, last) { + const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary'); const EventTile = sdk.getComponent('rooms.EventTile'); const DateSeparator = sdk.getComponent('messages.DateSeparator'); const ret = []; @@ -575,25 +576,27 @@ export default class MessagePanel extends React.Component { ref={this._collectEventNode.bind(this, eventId)} data-scroll-tokens={scrollToken} > - + + + , ); @@ -755,6 +758,7 @@ export default class MessagePanel extends React.Component { } render() { + const ErrorBoundary = sdk.getComponent('elements.ErrorBoundary'); const ScrollPanel = sdk.getComponent("structures.ScrollPanel"); const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile"); const Spinner = sdk.getComponent("elements.Spinner"); @@ -787,22 +791,24 @@ export default class MessagePanel extends React.Component { } return ( - - { topSpinner } - { this._getEventTiles() } - { whoIsTyping } - { bottomSpinner } - + + + { topSpinner } + { this._getEventTiles() } + { whoIsTyping } + { bottomSpinner } + + ); } } diff --git a/src/components/views/messages/TileErrorBoundary.js b/src/components/views/messages/TileErrorBoundary.js new file mode 100644 index 0000000000..372d402899 --- /dev/null +++ b/src/components/views/messages/TileErrorBoundary.js @@ -0,0 +1,70 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import classNames from 'classnames'; +import { _t } from '../../../languageHandler'; +import * as sdk from '../../../index'; +import Modal from '../../../Modal'; + +export default class TileErrorBoundary extends React.Component { + constructor(props) { + super(props); + + this.state = { + error: null, + }; + } + + static getDerivedStateFromError(error) { + // Side effects are not permitted here, so we only update the state so + // that the next render shows an error message. + return { error }; + } + + _onBugReport = () => { + const BugReportDialog = sdk.getComponent("dialogs.BugReportDialog"); + if (!BugReportDialog) { + return; + } + Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, { + label: 'react-soft-crash-tile', + }); + }; + + render() { + if (this.state.error) { + const classes = { + mx_EventTile: true, + mx_EventTile_info: true, + mx_EventTile_content: true, + mx_EventTile_tileError: true, + }; + return (
+
+ + {_t("An error occurred while rendering this event.")} + + {_t("Submit debug logs")} + + +
+
); + } + + return this.props.children; + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 1d030f5118..adce7f9a03 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1268,6 +1268,7 @@ "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?": "You are about to be taken to a third-party site so you can authenticate your account for use with %(integrationsUrl)s. Do you wish to continue?", "Edited at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.", "edited": "edited", + "An error occurred while rendering this event.": "An error occurred while rendering this event.", "Removed or unknown message type": "Removed or unknown message type", "Message removed by %(userId)s": "Message removed by %(userId)s", "Message removed": "Message removed",