web/all: add more error boundaries

This commit is contained in:
Tulir Asokan 2025-02-10 22:51:50 +02:00
parent 8ba9279cc7
commit 2461cad4f2
5 changed files with 77 additions and 27 deletions

View file

@ -16,6 +16,7 @@
import { JSX, use } from "react" import { JSX, use } from "react"
import type { UserID } from "@/api/types" import type { UserID } from "@/api/types"
import MainScreenContext from "../MainScreenContext.ts" import MainScreenContext from "../MainScreenContext.ts"
import ErrorBoundary from "../util/ErrorBoundary.tsx"
import MemberList from "./MemberList.tsx" import MemberList from "./MemberList.tsx"
import PinnedMessages from "./PinnedMessages.tsx" import PinnedMessages from "./PinnedMessages.tsx"
import UserInfo from "./UserInfo.tsx" import UserInfo from "./UserInfo.tsx"
@ -76,7 +77,9 @@ const RightPanel = (props: RightPanelProps) => {
<button onClick={mainScreen.closeRightPanel}><CloseIcon/></button> <button onClick={mainScreen.closeRightPanel}><CloseIcon/></button>
</div> </div>
<div className={`right-panel-content ${props.type}`}> <div className={`right-panel-content ${props.type}`}>
<ErrorBoundary thing="right panel content">
{renderRightPanelContent(props)} {renderRightPanelContent(props)}
</ErrorBoundary>
</div> </div>
</div> </div>
} }

View file

@ -18,6 +18,13 @@ div.room-view {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
> div.room-view-error {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
} }
div#mobile-event-menu-container { div#mobile-event-menu-container {

View file

@ -19,6 +19,7 @@ import MessageComposer from "../composer/MessageComposer.tsx"
import TypingNotifications from "../composer/TypingNotifications.tsx" import TypingNotifications from "../composer/TypingNotifications.tsx"
import RightPanel, { RightPanelProps } from "../rightpanel/RightPanel.tsx" import RightPanel, { RightPanelProps } from "../rightpanel/RightPanel.tsx"
import TimelineView from "../timeline/TimelineView.tsx" import TimelineView from "../timeline/TimelineView.tsx"
import ErrorBoundary from "../util/ErrorBoundary.tsx"
import RoomViewHeader from "./RoomViewHeader.tsx" import RoomViewHeader from "./RoomViewHeader.tsx"
import { RoomContext, RoomContextData } from "./roomcontext.ts" import { RoomContext, RoomContextData } from "./roomcontext.ts"
import "./RoomView.css" import "./RoomView.css"
@ -49,11 +50,13 @@ const RoomView = ({ room, rightPanelResizeHandle, rightPanel }: RoomViewProps) =
} }
return <RoomContext value={roomContextData}> return <RoomContext value={roomContextData}>
<div className="room-view" onClick={onClick}> <div className="room-view" onClick={onClick}>
<ErrorBoundary thing="room view" wrapperClassName="room-view-error">
<div id="mobile-event-menu-container"/> <div id="mobile-event-menu-container"/>
<RoomViewHeader room={room}/> <RoomViewHeader room={room}/>
<TimelineView/> <TimelineView/>
<MessageComposer/> <MessageComposer/>
<TypingNotifications/> <TypingNotifications/>
</ErrorBoundary>
</div> </div>
{rightPanelResizeHandle} {rightPanelResizeHandle}
{rightPanel && <RightPanel {...rightPanel}/>} {rightPanel && <RightPanel {...rightPanel}/>}

View file

@ -14,27 +14,12 @@
// You should have received a copy of the GNU Affero General Public License // You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>. // along with this program. If not, see <https://www.gnu.org/licenses/>.
import React from "react" import React from "react"
import ErrorBoundary from "@/ui/util/ErrorBoundary.tsx"
export default class ContentErrorBoundary extends React.Component<{ children: React.ReactNode }, { error?: Error }> { export default class ContentErrorBoundary extends ErrorBoundary {
constructor(props: { children: React.ReactNode }) { renderError(message: string): React.JSX.Element {
super(props)
this.state = { error: undefined }
}
static getDerivedStateFromError(error: unknown) {
if (error instanceof Error) {
error = new Error(`${error}`)
}
return { error }
}
render() {
if (this.state.error) {
return <div className="render-error-body"> return <div className="render-error-body">
Failed to render event: {this.state.error.message.replace(/^Error: /, "")} Failed to render event: {message}
</div> </div>
} }
return this.props.children
}
} }

View file

@ -0,0 +1,52 @@
// gomuks - A Matrix client written in Go.
// Copyright (C) 2024 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
import React from "react"
export interface ErrorBoundaryProps {
thing?: string
wrapperClassName?: string
children: React.ReactNode
}
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, { error?: string }> {
constructor(props: ErrorBoundaryProps) {
super(props)
this.state = { error: undefined }
}
static getDerivedStateFromError(error: unknown) {
return {
error: `${error}`.replace(/^Error: /, ""),
}
}
renderError(message: string) {
const inner = <>
Failed to render {this.props.thing ?? "component"}: {message}
</>
if (this.props.wrapperClassName) {
return <div className={this.props.wrapperClassName}>{inner}</div>
}
return inner
}
render() {
if (this.state.error) {
return this.renderError(this.state.error)
}
return this.props.children
}
}