From 381486904ba25b25ae7623bd3d0e041e55b61f8b Mon Sep 17 00:00:00 2001
From: DingDongSoLong4 <99329275+DingDongSoLong4@users.noreply.github.com>
Date: Fri, 3 Mar 2023 03:18:46 +0200
Subject: [PATCH] Improve dynamic import error message (#3500)
* Improve dynamic import error message
---------
Co-authored-by: WithoutPants <53250216+WithoutPants@users.noreply.github.com>
---
ui/v2.5/src/App.tsx | 37 +++++++++------
ui/v2.5/src/components/ErrorBoundary.tsx | 24 ++++++++--
ui/v2.5/src/components/Help/context.tsx | 5 +-
.../components/Scenes/SceneDetails/Scene.tsx | 46 ++++++++++---------
.../Scenes/SceneDetails/SceneEditPanel.tsx | 7 +--
ui/v2.5/src/components/Scenes/Scenes.tsx | 11 +++--
ui/v2.5/src/hooks/Lightbox/context.tsx | 5 +-
ui/v2.5/src/index.scss | 2 +-
ui/v2.5/src/locales/en-GB.json | 4 ++
ui/v2.5/src/utils/lazyComponent.ts | 24 ++++++++++
10 files changed, 111 insertions(+), 54 deletions(-)
create mode 100644 ui/v2.5/src/utils/lazyComponent.ts
diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx
index 7cea6a357..10660e884 100644
--- a/ui/v2.5/src/App.tsx
+++ b/ui/v2.5/src/App.tsx
@@ -1,4 +1,4 @@
-import React, { lazy, Suspense, useEffect, useState } from "react";
+import React, { Suspense, useEffect, useState } from "react";
import { Route, Switch, useRouteMatch } from "react-router-dom";
import { IntlProvider, CustomFormats } from "react-intl";
import { Helmet } from "react-helmet";
@@ -31,25 +31,32 @@ import { ReleaseNotesDialog } from "./components/Dialogs/ReleaseNotesDialog";
import { IUIConfig } from "./core/config";
import { releaseNotes } from "./docs/en/ReleaseNotes";
import { getPlatformURL, getBaseURL } from "./core/createClient";
+import { lazyComponent } from "./utils/lazyComponent";
-const Performers = lazy(() => import("./components/Performers/Performers"));
-const FrontPage = lazy(() => import("./components/FrontPage/FrontPage"));
-const Scenes = lazy(() => import("./components/Scenes/Scenes"));
-const Settings = lazy(() => import("./components/Settings/Settings"));
-const Stats = lazy(() => import("./components/Stats"));
-const Studios = lazy(() => import("./components/Studios/Studios"));
-const Galleries = lazy(() => import("./components/Galleries/Galleries"));
+const Performers = lazyComponent(
+ () => import("./components/Performers/Performers")
+);
+const FrontPage = lazyComponent(
+ () => import("./components/FrontPage/FrontPage")
+);
+const Scenes = lazyComponent(() => import("./components/Scenes/Scenes"));
+const Settings = lazyComponent(() => import("./components/Settings/Settings"));
+const Stats = lazyComponent(() => import("./components/Stats"));
+const Studios = lazyComponent(() => import("./components/Studios/Studios"));
+const Galleries = lazyComponent(
+ () => import("./components/Galleries/Galleries")
+);
-const Movies = lazy(() => import("./components/Movies/Movies"));
-const Tags = lazy(() => import("./components/Tags/Tags"));
-const Images = lazy(() => import("./components/Images/Images"));
-const Setup = lazy(() => import("./components/Setup/Setup"));
-const Migrate = lazy(() => import("./components/Setup/Migrate"));
+const Movies = lazyComponent(() => import("./components/Movies/Movies"));
+const Tags = lazyComponent(() => import("./components/Tags/Tags"));
+const Images = lazyComponent(() => import("./components/Images/Images"));
+const Setup = lazyComponent(() => import("./components/Setup/Setup"));
+const Migrate = lazyComponent(() => import("./components/Setup/Migrate"));
-const SceneFilenameParser = lazy(
+const SceneFilenameParser = lazyComponent(
() => import("./components/SceneFilenameParser/SceneFilenameParser")
);
-const SceneDuplicateChecker = lazy(
+const SceneDuplicateChecker = lazyComponent(
() => import("./components/SceneDuplicateChecker/SceneDuplicateChecker")
);
diff --git a/ui/v2.5/src/components/ErrorBoundary.tsx b/ui/v2.5/src/components/ErrorBoundary.tsx
index 302abb2cc..a6e466efb 100644
--- a/ui/v2.5/src/components/ErrorBoundary.tsx
+++ b/ui/v2.5/src/components/ErrorBoundary.tsx
@@ -1,4 +1,6 @@
import React from "react";
+import { FormattedMessage } from "react-intl";
+import { isLazyComponentError } from "src/utils/lazyComponent";
interface IErrorBoundaryProps {
children?: React.ReactNode;
@@ -10,6 +12,7 @@ type ErrorInfo = {
interface IErrorBoundaryState {
error?: Error;
+ errorHelpId?: string;
errorInfo?: ErrorInfo;
}
@@ -23,22 +26,35 @@ export class ErrorBoundary extends React.Component<
}
public componentDidCatch(error: Error, errorInfo: ErrorInfo) {
+ let errorHelpId: string | undefined;
+ if (isLazyComponentError(error)) {
+ errorHelpId = "errors.lazy_component_error_help";
+ }
this.setState({
error,
+ errorHelpId,
errorInfo,
});
}
public render() {
- if (this.state.errorInfo) {
+ const { error, errorHelpId, errorInfo } = this.state;
+ if (errorInfo) {
// Error path
return (
-
Something went wrong.
+
+
+
+ {errorHelpId && (
+
+
+
+ )}
- {this.state.error && this.state.error.toString()}
+ {error?.toString()}
- {this.state.errorInfo.componentStack}
+ {errorInfo.componentStack.trim().replaceAll(/^\s*/gm, " ")}
);
diff --git a/ui/v2.5/src/components/Help/context.tsx b/ui/v2.5/src/components/Help/context.tsx
index 0268b28e3..4034e3fcb 100644
--- a/ui/v2.5/src/components/Help/context.tsx
+++ b/ui/v2.5/src/components/Help/context.tsx
@@ -1,6 +1,7 @@
-import React, { lazy, Suspense, useState } from "react";
+import React, { Suspense, useState } from "react";
+import { lazyComponent } from "src/utils/lazyComponent";
-const Manual = lazy(() => import("./Manual"));
+const Manual = lazyComponent(() => import("./Manual"));
interface IManualContextState {
openManual: (tab?: string) => void;
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
index 7e244580a..21ba9d5c4 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx
@@ -1,12 +1,5 @@
import { Tab, Nav, Dropdown, Button, ButtonGroup } from "react-bootstrap";
-import React, {
- useEffect,
- useState,
- useMemo,
- useContext,
- lazy,
- useRef,
-} from "react";
+import React, { useEffect, useState, useMemo, useContext, useRef } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import { useParams, useLocation, useHistory, Link } from "react-router-dom";
import { Helmet } from "react-helmet";
@@ -36,29 +29,38 @@ import { OrganizedButton } from "./OrganizedButton";
import { ConfigurationContext } from "src/hooks/Config";
import { getPlayerPosition } from "src/components/ScenePlayer/util";
import { faEllipsisV } from "@fortawesome/free-solid-svg-icons";
+import { lazyComponent } from "src/utils/lazyComponent";
-const SubmitStashBoxDraft = lazy(
+const SubmitStashBoxDraft = lazyComponent(
() => import("src/components/Dialogs/SubmitDraft")
);
-const ScenePlayer = lazy(
+const ScenePlayer = lazyComponent(
() => import("src/components/ScenePlayer/ScenePlayer")
);
-const GalleryViewer = lazy(
+const GalleryViewer = lazyComponent(
() => import("src/components/Galleries/GalleryViewer")
);
-const ExternalPlayerButton = lazy(() => import("./ExternalPlayerButton"));
+const ExternalPlayerButton = lazyComponent(
+ () => import("./ExternalPlayerButton")
+);
-const QueueViewer = lazy(() => import("./QueueViewer"));
-const SceneMarkersPanel = lazy(() => import("./SceneMarkersPanel"));
-const SceneFileInfoPanel = lazy(() => import("./SceneFileInfoPanel"));
-const SceneEditPanel = lazy(() => import("./SceneEditPanel"));
-const SceneDetailPanel = lazy(() => import("./SceneDetailPanel"));
-const SceneMoviePanel = lazy(() => import("./SceneMoviePanel"));
-const SceneGalleriesPanel = lazy(() => import("./SceneGalleriesPanel"));
-const DeleteScenesDialog = lazy(() => import("../DeleteScenesDialog"));
-const GenerateDialog = lazy(() => import("../../Dialogs/GenerateDialog"));
-const SceneVideoFilterPanel = lazy(() => import("./SceneVideoFilterPanel"));
+const QueueViewer = lazyComponent(() => import("./QueueViewer"));
+const SceneMarkersPanel = lazyComponent(() => import("./SceneMarkersPanel"));
+const SceneFileInfoPanel = lazyComponent(() => import("./SceneFileInfoPanel"));
+const SceneEditPanel = lazyComponent(() => import("./SceneEditPanel"));
+const SceneDetailPanel = lazyComponent(() => import("./SceneDetailPanel"));
+const SceneMoviePanel = lazyComponent(() => import("./SceneMoviePanel"));
+const SceneGalleriesPanel = lazyComponent(
+ () => import("./SceneGalleriesPanel")
+);
+const DeleteScenesDialog = lazyComponent(() => import("../DeleteScenesDialog"));
+const GenerateDialog = lazyComponent(
+ () => import("../../Dialogs/GenerateDialog")
+);
+const SceneVideoFilterPanel = lazyComponent(
+ () => import("./SceneVideoFilterPanel")
+);
import { objectPath, objectTitle } from "src/core/files";
interface IProps {
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx
index 701d3c1a4..c6437568b 100644
--- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx
+++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneEditPanel.tsx
@@ -1,4 +1,4 @@
-import React, { useEffect, useState, useMemo, lazy } from "react";
+import React, { useEffect, useState, useMemo } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import {
Button,
@@ -50,9 +50,10 @@ import {
import { objectTitle } from "src/core/files";
import { galleryTitle } from "src/core/galleries";
import { useRatingKeybinds } from "src/hooks/keybinds";
+import { lazyComponent } from "src/utils/lazyComponent";
-const SceneScrapeDialog = lazy(() => import("./SceneScrapeDialog"));
-const SceneQueryModal = lazy(() => import("./SceneQueryModal"));
+const SceneScrapeDialog = lazyComponent(() => import("./SceneScrapeDialog"));
+const SceneQueryModal = lazyComponent(() => import("./SceneQueryModal"));
interface IProps {
scene: Partial;
diff --git a/ui/v2.5/src/components/Scenes/Scenes.tsx b/ui/v2.5/src/components/Scenes/Scenes.tsx
index 6bbbd1c68..b6f465857 100644
--- a/ui/v2.5/src/components/Scenes/Scenes.tsx
+++ b/ui/v2.5/src/components/Scenes/Scenes.tsx
@@ -1,14 +1,15 @@
-import React, { lazy } from "react";
+import React from "react";
import { Route, Switch } from "react-router-dom";
import { useIntl } from "react-intl";
import { Helmet } from "react-helmet";
import { TITLE_SUFFIX } from "src/components/Shared/constants";
import { PersistanceLevel } from "src/hooks/ListHook";
+import { lazyComponent } from "src/utils/lazyComponent";
-const SceneList = lazy(() => import("./SceneList"));
-const SceneMarkerList = lazy(() => import("./SceneMarkerList"));
-const Scene = lazy(() => import("./SceneDetails/Scene"));
-const SceneCreate = lazy(() => import("./SceneDetails/SceneCreate"));
+const SceneList = lazyComponent(() => import("./SceneList"));
+const SceneMarkerList = lazyComponent(() => import("./SceneMarkerList"));
+const Scene = lazyComponent(() => import("./SceneDetails/Scene"));
+const SceneCreate = lazyComponent(() => import("./SceneDetails/SceneCreate"));
const Scenes: React.FC = () => {
const intl = useIntl();
diff --git a/ui/v2.5/src/hooks/Lightbox/context.tsx b/ui/v2.5/src/hooks/Lightbox/context.tsx
index 6c9240542..55df6559c 100644
--- a/ui/v2.5/src/hooks/Lightbox/context.tsx
+++ b/ui/v2.5/src/hooks/Lightbox/context.tsx
@@ -1,7 +1,8 @@
-import React, { lazy, Suspense, useCallback, useState } from "react";
+import React, { Suspense, useCallback, useState } from "react";
+import { lazyComponent } from "src/utils/lazyComponent";
import { ILightboxImage } from "./types";
-const LightboxComponent = lazy(() => import("./Lightbox"));
+const LightboxComponent = lazyComponent(() => import("./Lightbox"));
export interface IState {
images: ILightboxImage[];
diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss
index 15f87ffa8..26946ecf1 100755
--- a/ui/v2.5/src/index.scss
+++ b/ui/v2.5/src/index.scss
@@ -739,7 +739,7 @@ div.dropdown-menu {
}
.error-message {
- white-space: "pre-wrap";
+ white-space: pre-wrap;
}
.btn-toolbar .form-control {
diff --git a/ui/v2.5/src/locales/en-GB.json b/ui/v2.5/src/locales/en-GB.json
index c4d766e74..7fcc59a62 100644
--- a/ui/v2.5/src/locales/en-GB.json
+++ b/ui/v2.5/src/locales/en-GB.json
@@ -840,6 +840,10 @@
"warmth": "Warmth"
},
"empty_server": "Add some scenes to your server to view recommendations on this page.",
+ "errors": {
+ "something_went_wrong": "Something went wrong.",
+ "lazy_component_error_help": "If you recently upgraded Stash, please reload the page or clear your browser cache."
+ },
"ethnicity": "Ethnicity",
"existing_value": "existing value",
"eye_color": "Eye Colour",
diff --git a/ui/v2.5/src/utils/lazyComponent.ts b/ui/v2.5/src/utils/lazyComponent.ts
new file mode 100644
index 000000000..38bacceed
--- /dev/null
+++ b/ui/v2.5/src/utils/lazyComponent.ts
@@ -0,0 +1,24 @@
+import { ComponentType, lazy } from "react";
+
+interface ILazyComponentError {
+ __lazyComponentError?: true;
+}
+
+export const isLazyComponentError = (e: unknown) => {
+ return !!(e as ILazyComponentError).__lazyComponentError;
+};
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export const lazyComponent = >(
+ factory: Parameters>[0]
+) => {
+ return lazy(async () => {
+ try {
+ return await factory();
+ } catch (e) {
+ // set flag to identify lazy component loading errors
+ (e as ILazyComponentError).__lazyComponentError = true;
+ throw e;
+ }
+ });
+};