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 (
);
+ }
+
+ 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",