diff --git a/res/css/views/rooms/_EventTile.scss b/res/css/views/rooms/_EventTile.scss
index e015f30e48..0dc60226b8 100644
--- a/res/css/views/rooms/_EventTile.scss
+++ b/res/css/views/rooms/_EventTile.scss
@@ -661,3 +661,23 @@ div.mx_EventTile_notSent.mx_EventTile_redacted .mx_UnknownBody {
}
}
}
+
+.mx_EventTile_tileError {
+ color: red;
+ text-align: center;
+
+ // Remove some of the default tile padding so that the error is centered
+ margin-right: 0;
+ .mx_EventTile_line {
+ padding-left: 0;
+ margin-right: 0;
+ }
+
+ .mx_EventTile_line span {
+ padding: 4px 8px;
+ }
+
+ a {
+ margin-left: 1em;
+ }
+}
diff --git a/src/components/structures/MessagePanel.js b/src/components/structures/MessagePanel.js
index c3a2bdbc59..6fbfdb504b 100644
--- a/src/components/structures/MessagePanel.js
+++ b/src/components/structures/MessagePanel.js
@@ -503,6 +503,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 = [];
@@ -577,25 +578,27 @@ export default class MessagePanel extends React.Component {
ref={this._collectEventNode.bind(this, eventId)}
data-scroll-tokens={scrollToken}
>
-
+
+
+
,
);
@@ -757,6 +760,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");
@@ -789,22 +793,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..e42ddab16a
--- /dev/null
+++ b/src/components/views/messages/TileErrorBoundary.js
@@ -0,0 +1,72 @@
+/*
+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 { mxEvent } = this.props;
+ 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 80d466724b..35c6fb729b 100644
--- a/src/i18n/strings/en_EN.json
+++ b/src/i18n/strings/en_EN.json
@@ -1336,6 +1336,8 @@
"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",
+ "Can't load this message": "Can't load this message",
+ "Submit logs": "Submit logs",
"Removed or unknown message type": "Removed or unknown message type",
"Message removed by %(userId)s": "Message removed by %(userId)s",
"Message removed": "Message removed",