Merge pull request #4122 from matrix-org/foldleft/better-errors
Catch errors sooner so users can recover more easilypull/21833/head
commit
fba57b1ff3
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -503,6 +503,7 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTilesForEvent(prevEvent, mxEv, last) {
|
_getTilesForEvent(prevEvent, mxEv, last) {
|
||||||
|
const TileErrorBoundary = sdk.getComponent('messages.TileErrorBoundary');
|
||||||
const EventTile = sdk.getComponent('rooms.EventTile');
|
const EventTile = sdk.getComponent('rooms.EventTile');
|
||||||
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
const DateSeparator = sdk.getComponent('messages.DateSeparator');
|
||||||
const ret = [];
|
const ret = [];
|
||||||
|
@ -577,25 +578,27 @@ export default class MessagePanel extends React.Component {
|
||||||
ref={this._collectEventNode.bind(this, eventId)}
|
ref={this._collectEventNode.bind(this, eventId)}
|
||||||
data-scroll-tokens={scrollToken}
|
data-scroll-tokens={scrollToken}
|
||||||
>
|
>
|
||||||
<EventTile mxEvent={mxEv}
|
<TileErrorBoundary mxEvent={mxEv}>
|
||||||
continuation={continuation}
|
<EventTile mxEvent={mxEv}
|
||||||
isRedacted={mxEv.isRedacted()}
|
continuation={continuation}
|
||||||
replacingEventId={mxEv.replacingEventId()}
|
isRedacted={mxEv.isRedacted()}
|
||||||
editState={isEditing && this.props.editState}
|
replacingEventId={mxEv.replacingEventId()}
|
||||||
onHeightChanged={this._onHeightChanged}
|
editState={isEditing && this.props.editState}
|
||||||
readReceipts={readReceipts}
|
onHeightChanged={this._onHeightChanged}
|
||||||
readReceiptMap={this._readReceiptMap}
|
readReceipts={readReceipts}
|
||||||
showUrlPreview={this.props.showUrlPreview}
|
readReceiptMap={this._readReceiptMap}
|
||||||
checkUnmounting={this._isUnmounting.bind(this)}
|
showUrlPreview={this.props.showUrlPreview}
|
||||||
eventSendStatus={mxEv.getAssociatedStatus()}
|
checkUnmounting={this._isUnmounting.bind(this)}
|
||||||
tileShape={this.props.tileShape}
|
eventSendStatus={mxEv.getAssociatedStatus()}
|
||||||
isTwelveHour={this.props.isTwelveHour}
|
tileShape={this.props.tileShape}
|
||||||
permalinkCreator={this.props.permalinkCreator}
|
isTwelveHour={this.props.isTwelveHour}
|
||||||
last={last}
|
permalinkCreator={this.props.permalinkCreator}
|
||||||
isSelectedEvent={highlight}
|
last={last}
|
||||||
getRelationsForEvent={this.props.getRelationsForEvent}
|
isSelectedEvent={highlight}
|
||||||
showReactions={this.props.showReactions}
|
getRelationsForEvent={this.props.getRelationsForEvent}
|
||||||
/>
|
showReactions={this.props.showReactions}
|
||||||
|
/>
|
||||||
|
</TileErrorBoundary>
|
||||||
</li>,
|
</li>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -757,6 +760,7 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const ErrorBoundary = sdk.getComponent('elements.ErrorBoundary');
|
||||||
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
const ScrollPanel = sdk.getComponent("structures.ScrollPanel");
|
||||||
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
|
const WhoIsTypingTile = sdk.getComponent("rooms.WhoIsTypingTile");
|
||||||
const Spinner = sdk.getComponent("elements.Spinner");
|
const Spinner = sdk.getComponent("elements.Spinner");
|
||||||
|
@ -789,22 +793,24 @@ export default class MessagePanel extends React.Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ScrollPanel
|
<ErrorBoundary>
|
||||||
ref={this._scrollPanel}
|
<ScrollPanel
|
||||||
className={className}
|
ref={this._scrollPanel}
|
||||||
onScroll={this.props.onScroll}
|
className={className}
|
||||||
onResize={this.onResize}
|
onScroll={this.props.onScroll}
|
||||||
onFillRequest={this.props.onFillRequest}
|
onResize={this.onResize}
|
||||||
onUnfillRequest={this.props.onUnfillRequest}
|
onFillRequest={this.props.onFillRequest}
|
||||||
style={style}
|
onUnfillRequest={this.props.onUnfillRequest}
|
||||||
stickyBottom={this.props.stickyBottom}
|
style={style}
|
||||||
resizeNotifier={this.props.resizeNotifier}
|
stickyBottom={this.props.stickyBottom}
|
||||||
>
|
resizeNotifier={this.props.resizeNotifier}
|
||||||
{ topSpinner }
|
>
|
||||||
{ this._getEventTiles() }
|
{ topSpinner }
|
||||||
{ whoIsTyping }
|
{ this._getEventTiles() }
|
||||||
{ bottomSpinner }
|
{ whoIsTyping }
|
||||||
</ScrollPanel>
|
{ bottomSpinner }
|
||||||
|
</ScrollPanel>
|
||||||
|
</ErrorBoundary>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 (<div className={classNames(classes)}>
|
||||||
|
<div className="mx_EventTile_line">
|
||||||
|
<span>
|
||||||
|
{_t("Can't load this message")}
|
||||||
|
{ mxEvent && ` (${mxEvent.getType()})` }
|
||||||
|
<a onClick={this._onBugReport} href="#">
|
||||||
|
{_t("Submit logs")}
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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?",
|
"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 at %(date)s. Click to view edits.": "Edited at %(date)s. Click to view edits.",
|
||||||
"edited": "edited",
|
"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",
|
"Removed or unknown message type": "Removed or unknown message type",
|
||||||
"Message removed by %(userId)s": "Message removed by %(userId)s",
|
"Message removed by %(userId)s": "Message removed by %(userId)s",
|
||||||
"Message removed": "Message removed",
|
"Message removed": "Message removed",
|
||||||
|
|
Loading…
Reference in New Issue