Add additional error boundaries, improve display and reload behavior

This commit is contained in:
Tom Moor
2023-04-05 21:57:58 -04:00
parent 24729fa0d4
commit 1f3d7506d7
8 changed files with 45 additions and 25 deletions

View File

@@ -325,7 +325,7 @@ function Editor(props: Props, ref: React.RefObject<SharedEditor> | null) {
); );
return ( return (
<ErrorBoundary reloadOnChunkMissing> <ErrorBoundary component="div" reloadOnChunkMissing>
<> <>
<LazyLoadedEditor <LazyLoadedEditor
ref={mergeRefs([ref, localRef, handleRefChanged])} ref={mergeRefs([ref, localRef, handleRefChanged])}

View File

@@ -3,7 +3,7 @@ import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { withTranslation, Trans, WithTranslation } from "react-i18next"; import { withTranslation, Trans, WithTranslation } from "react-i18next";
import styled from "styled-components"; import styled from "styled-components";
import { githubIssuesUrl } from "@shared/utils/urlHelpers"; import { githubIssuesUrl, feedbackUrl } from "@shared/utils/urlHelpers";
import Button from "~/components/Button"; import Button from "~/components/Button";
import CenteredContent from "~/components/CenteredContent"; import CenteredContent from "~/components/CenteredContent";
import PageTitle from "~/components/PageTitle"; import PageTitle from "~/components/PageTitle";
@@ -13,7 +13,12 @@ import Logger from "~/utils/Logger";
import isCloudHosted from "~/utils/isCloudHosted"; import isCloudHosted from "~/utils/isCloudHosted";
type Props = WithTranslation & { type Props = WithTranslation & {
/** Whether to reload the page if a chunk fails to load. */
reloadOnChunkMissing?: boolean; reloadOnChunkMissing?: boolean;
/** Whether to show a title heading. */
showTitle?: boolean;
/** The wrapping component to use. */
component?: React.ComponentType | string;
}; };
@observer @observer
@@ -51,26 +56,31 @@ class ErrorBoundary extends React.Component<Props> {
}; };
handleReportBug = () => { handleReportBug = () => {
window.open(githubIssuesUrl()); window.open(isCloudHosted ? feedbackUrl() : githubIssuesUrl());
}; };
render() { render() {
const { t } = this.props; const { t, component: Component = CenteredContent, showTitle } = this.props;
if (this.error) { if (this.error) {
const error = this.error; const error = this.error;
const isReported = !!env.SENTRY_DSN && isCloudHosted; const isReported = !!env.SENTRY_DSN && isCloudHosted;
const isChunkError = this.error.message.match( const isChunkError = [
/dynamically imported module/ "module script failed",
); "dynamically imported module",
].some((msg) => this.error?.message?.includes(msg));
if (isChunkError) { if (isChunkError) {
return ( return (
<CenteredContent> <Component>
<PageTitle title={t("Module failed to load")} /> {showTitle && (
<h1> <>
<Trans>Loading Failed</Trans> <PageTitle title={t("Module failed to load")} />
</h1> <h1>
<Trans>Loading Failed</Trans>
</h1>
</>
)}
<Text type="secondary"> <Text type="secondary">
<Trans> <Trans>
Sorry, part of the application failed to load. This may be Sorry, part of the application failed to load. This may be
@@ -81,16 +91,20 @@ class ErrorBoundary extends React.Component<Props> {
<p> <p>
<Button onClick={this.handleReload}>{t("Reload")}</Button> <Button onClick={this.handleReload}>{t("Reload")}</Button>
</p> </p>
</CenteredContent> </Component>
); );
} }
return ( return (
<CenteredContent> <Component>
<PageTitle title={t("Something Unexpected Happened")} /> {showTitle && (
<h1> <>
<Trans>Something Unexpected Happened</Trans> <PageTitle title={t("Something Unexpected Happened")} />
</h1> <h1>
<Trans>Something Unexpected Happened</Trans>
</h1>
</>
)}
<Text type="secondary"> <Text type="secondary">
<Trans <Trans
defaults="Sorry, an unrecoverable error occurred{{notified}}. Please try reloading the page, it may have been a temporary glitch." defaults="Sorry, an unrecoverable error occurred{{notified}}. Please try reloading the page, it may have been a temporary glitch."
@@ -114,7 +128,7 @@ class ErrorBoundary extends React.Component<Props> {
</Button> </Button>
)} )}
</p> </p>
</CenteredContent> </Component>
); );
} }

View File

@@ -10,6 +10,7 @@ const Flex = styled.div<{
column?: boolean; column?: boolean;
align?: AlignValues; align?: AlignValues;
justify?: JustifyValues; justify?: JustifyValues;
wrap?: boolean;
shrink?: boolean; shrink?: boolean;
reverse?: boolean; reverse?: boolean;
gap?: number; gap?: number;
@@ -26,6 +27,7 @@ const Flex = styled.div<{
: "row"}; : "row"};
align-items: ${({ align }) => align}; align-items: ${({ align }) => align};
justify-content: ${({ justify }) => justify}; justify-content: ${({ justify }) => justify};
flex-wrap: ${({ wrap }) => (wrap ? "wrap" : "initial")};
flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")}; flex-shrink: ${({ shrink }) => (shrink ? 1 : "initial")};
gap: ${({ gap }) => (gap ? `${gap}px` : "initial")}; gap: ${({ gap }) => (gap ? `${gap}px` : "initial")};
min-height: 0; min-height: 0;

View File

@@ -30,7 +30,7 @@ export default function LoadingError({ error, retry, ...rest }: Props) {
return ( return (
<Content {...rest}> <Content {...rest}>
<Flex align="center" gap={4}> <Flex align="center" gap={4} wrap>
{message}{" "} {message}{" "}
<ButtonLink onClick={() => retry()}>{t("Click to retry")}</ButtonLink> <ButtonLink onClick={() => retry()}>{t("Click to retry")}</ButtonLink>
</Flex> </Flex>

View File

@@ -16,6 +16,7 @@ import usePrevious from "~/hooks/usePrevious";
import useUnmount from "~/hooks/useUnmount"; import useUnmount from "~/hooks/useUnmount";
import { fadeAndScaleIn } from "~/styles/animations"; import { fadeAndScaleIn } from "~/styles/animations";
import Desktop from "~/utils/Desktop"; import Desktop from "~/utils/Desktop";
import ErrorBoundary from "./ErrorBoundary";
let openModals = 0; let openModals = 0;
type Props = { type Props = {
@@ -82,7 +83,9 @@ const Modal: React.FC<Props> = ({
column column
reverse reverse
> >
<SmallContent shadow>{children}</SmallContent> <SmallContent shadow>
<ErrorBoundary component="div">{children}</ErrorBoundary>
</SmallContent>
<Header> <Header>
{title && ( {title && (
<Text as="span" size="large"> <Text as="span" size="large">
@@ -112,7 +115,7 @@ const Modal: React.FC<Props> = ({
<Content> <Content>
<Centered onClick={(ev) => ev.stopPropagation()} column> <Centered onClick={(ev) => ev.stopPropagation()} column>
{title && <h1>{title}</h1>} {title && <h1>{title}</h1>}
{children} <ErrorBoundary>{children}</ErrorBoundary>
</Centered> </Centered>
</Content> </Content>
<Close onClick={onRequestClose}> <Close onClick={onRequestClose}>

View File

@@ -4,6 +4,7 @@ import * as React from "react";
import styled, { useTheme } from "styled-components"; import styled, { useTheme } from "styled-components";
import breakpoint from "styled-components-breakpoint"; import breakpoint from "styled-components-breakpoint";
import { depths } from "@shared/styles"; import { depths } from "@shared/styles";
import ErrorBoundary from "~/components/ErrorBoundary";
import Flex from "~/components/Flex"; import Flex from "~/components/Flex";
import ResizeBorder from "~/components/Sidebar/components/ResizeBorder"; import ResizeBorder from "~/components/Sidebar/components/ResizeBorder";
import useMobile from "~/hooks/useMobile"; import useMobile from "~/hooks/useMobile";
@@ -94,7 +95,7 @@ function Right({ children, border, className }: Props) {
className={className} className={className}
> >
<Position style={style} column> <Position style={style} column>
{children} <ErrorBoundary>{children}</ErrorBoundary>
{!isMobile && ( {!isMobile && (
<ResizeBorder <ResizeBorder
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}

View File

@@ -53,7 +53,7 @@ if (element) {
<Provider {...stores}> <Provider {...stores}>
<Analytics> <Analytics>
<Theme> <Theme>
<ErrorBoundary> <ErrorBoundary showTitle>
<KBarProvider actions={[]} options={commandBarOptions}> <KBarProvider actions={[]} options={commandBarOptions}>
<LazyPolyfill> <LazyPolyfill>
<LazyMotion features={loadFeatures}> <LazyMotion features={loadFeatures}>

View File

@@ -400,7 +400,7 @@ class DocumentScene extends React.Component<Props> {
: updateDocumentUrl(this.props.match.url, document); : updateDocumentUrl(this.props.match.url, document);
return ( return (
<ErrorBoundary> <ErrorBoundary showTitle>
{this.props.location.pathname !== canonicalUrl && ( {this.props.location.pathname !== canonicalUrl && (
<Redirect <Redirect
to={{ to={{