diff --git a/web/src/ui/rightpanel/RightPanel.tsx b/web/src/ui/rightpanel/RightPanel.tsx index 86ce5d4..a891ee1 100644 --- a/web/src/ui/rightpanel/RightPanel.tsx +++ b/web/src/ui/rightpanel/RightPanel.tsx @@ -16,6 +16,7 @@ import { JSX, use } from "react" import type { UserID } from "@/api/types" import MainScreenContext from "../MainScreenContext.ts" +import ErrorBoundary from "../util/ErrorBoundary.tsx" import MemberList from "./MemberList.tsx" import PinnedMessages from "./PinnedMessages.tsx" import UserInfo from "./UserInfo.tsx" @@ -76,7 +77,9 @@ const RightPanel = (props: RightPanelProps) => {
- {renderRightPanelContent(props)} + + {renderRightPanelContent(props)} +
} diff --git a/web/src/ui/roomview/RoomView.css b/web/src/ui/roomview/RoomView.css index ec525b1..fbcaf22 100644 --- a/web/src/ui/roomview/RoomView.css +++ b/web/src/ui/roomview/RoomView.css @@ -18,6 +18,13 @@ div.room-view { justify-content: center; align-items: center; } + + > div.room-view-error { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } } div#mobile-event-menu-container { diff --git a/web/src/ui/roomview/RoomView.tsx b/web/src/ui/roomview/RoomView.tsx index a646395..d0cd8c6 100644 --- a/web/src/ui/roomview/RoomView.tsx +++ b/web/src/ui/roomview/RoomView.tsx @@ -19,6 +19,7 @@ import MessageComposer from "../composer/MessageComposer.tsx" import TypingNotifications from "../composer/TypingNotifications.tsx" import RightPanel, { RightPanelProps } from "../rightpanel/RightPanel.tsx" import TimelineView from "../timeline/TimelineView.tsx" +import ErrorBoundary from "../util/ErrorBoundary.tsx" import RoomViewHeader from "./RoomViewHeader.tsx" import { RoomContext, RoomContextData } from "./roomcontext.ts" import "./RoomView.css" @@ -49,11 +50,13 @@ const RoomView = ({ room, rightPanelResizeHandle, rightPanel }: RoomViewProps) = } return
-
- - - - + +
+ + + + +
{rightPanelResizeHandle} {rightPanel && } diff --git a/web/src/ui/timeline/content/ContentErrorBoundary.tsx b/web/src/ui/timeline/content/ContentErrorBoundary.tsx index 7265c81..2862c67 100644 --- a/web/src/ui/timeline/content/ContentErrorBoundary.tsx +++ b/web/src/ui/timeline/content/ContentErrorBoundary.tsx @@ -14,27 +14,12 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . import React from "react" +import ErrorBoundary from "@/ui/util/ErrorBoundary.tsx" -export default class ContentErrorBoundary extends React.Component<{ children: React.ReactNode }, { error?: Error }> { - constructor(props: { children: React.ReactNode }) { - 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
- Failed to render event: {this.state.error.message.replace(/^Error: /, "")} -
- } - - return this.props.children +export default class ContentErrorBoundary extends ErrorBoundary { + renderError(message: string): React.JSX.Element { + return
+ Failed to render event: {message} +
} } diff --git a/web/src/ui/util/ErrorBoundary.tsx b/web/src/ui/util/ErrorBoundary.tsx new file mode 100644 index 0000000..1c98bc2 --- /dev/null +++ b/web/src/ui/util/ErrorBoundary.tsx @@ -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 . +import React from "react" + +export interface ErrorBoundaryProps { + thing?: string + wrapperClassName?: string + children: React.ReactNode +} + +export default class ErrorBoundary extends React.Component { + 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
{inner}
+ } + return inner + } + + render() { + if (this.state.error) { + return this.renderError(this.state.error) + } + return this.props.children + } +}