mirror of
https://github.com/stashapp/stash.git
synced 2025-12-16 20:07:05 +03:00
Error loading plugins (#5813)
* Improve error messages when unable to contact server * Improve error message presentation * Catch errors when configuration can't be loaded * Use ErrorMessage in PagedList * Add icon to error message
This commit is contained in:
@@ -6,7 +6,7 @@ import {
|
||||
useLocation,
|
||||
useRouteMatch,
|
||||
} from "react-router-dom";
|
||||
import { IntlProvider, CustomFormats } from "react-intl";
|
||||
import { IntlProvider, CustomFormats, FormattedMessage } from "react-intl";
|
||||
import { Helmet } from "react-helmet";
|
||||
import cloneDeep from "lodash-es/cloneDeep";
|
||||
import mergeWith from "lodash-es/mergeWith";
|
||||
@@ -49,6 +49,7 @@ import { ConnectionMonitor } from "./ConnectionMonitor";
|
||||
import { PatchFunction } from "./patch";
|
||||
|
||||
import moment from "moment/min/moment-with-locales";
|
||||
import { ErrorMessage } from "./components/Shared/ErrorMessage";
|
||||
|
||||
const Performers = lazyComponent(
|
||||
() => import("./components/Performers/Performers")
|
||||
@@ -102,6 +103,14 @@ const AppContainer: React.FC<React.PropsWithChildren<{}>> = PatchFunction(
|
||||
}
|
||||
) as React.FC;
|
||||
|
||||
const MainContainer: React.FC = ({ children }) => {
|
||||
return (
|
||||
<div className={`main container-fluid ${appleRendering ? "apple" : ""}`}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function translateLanguageLocale(l: string) {
|
||||
// intl doesn't support all locales, so we need to map some to supported ones
|
||||
switch (l) {
|
||||
@@ -287,14 +296,40 @@ export const App: React.FC = () => {
|
||||
|
||||
const titleProps = makeTitleProps();
|
||||
|
||||
if (!messages) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (config.error) {
|
||||
return (
|
||||
<IntlProvider
|
||||
locale={intlLanguage}
|
||||
messages={messages}
|
||||
formats={intlFormats}
|
||||
>
|
||||
<MainContainer>
|
||||
<ErrorMessage
|
||||
message={
|
||||
<FormattedMessage
|
||||
id="errors.loading_type"
|
||||
values={{ type: "configuration" }}
|
||||
/>
|
||||
}
|
||||
error={config.error.message}
|
||||
/>
|
||||
</MainContainer>
|
||||
</IntlProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorBoundary>
|
||||
{messages ? (
|
||||
<IntlProvider
|
||||
locale={intlLanguage}
|
||||
messages={messages}
|
||||
formats={intlFormats}
|
||||
>
|
||||
<IntlProvider
|
||||
locale={intlLanguage}
|
||||
messages={messages}
|
||||
formats={intlFormats}
|
||||
>
|
||||
<ToastProvider>
|
||||
<PluginsLoader>
|
||||
<AppContainer>
|
||||
<ConfigurationProvider
|
||||
@@ -302,31 +337,23 @@ export const App: React.FC = () => {
|
||||
loading={config.loading}
|
||||
>
|
||||
{maybeRenderReleaseNotes()}
|
||||
<ToastProvider>
|
||||
<ConnectionMonitor />
|
||||
<Suspense fallback={<LoadingIndicator />}>
|
||||
<LightboxProvider>
|
||||
<ManualProvider>
|
||||
<InteractiveProvider>
|
||||
<Helmet {...titleProps} />
|
||||
{maybeRenderNavbar()}
|
||||
<div
|
||||
className={`main container-fluid ${
|
||||
appleRendering ? "apple" : ""
|
||||
}`}
|
||||
>
|
||||
{renderContent()}
|
||||
</div>
|
||||
</InteractiveProvider>
|
||||
</ManualProvider>
|
||||
</LightboxProvider>
|
||||
</Suspense>
|
||||
</ToastProvider>
|
||||
<ConnectionMonitor />
|
||||
<Suspense fallback={<LoadingIndicator />}>
|
||||
<LightboxProvider>
|
||||
<ManualProvider>
|
||||
<InteractiveProvider>
|
||||
<Helmet {...titleProps} />
|
||||
{maybeRenderNavbar()}
|
||||
<MainContainer>{renderContent()}</MainContainer>
|
||||
</InteractiveProvider>
|
||||
</ManualProvider>
|
||||
</LightboxProvider>
|
||||
</Suspense>
|
||||
</ConfigurationProvider>
|
||||
</AppContainer>
|
||||
</PluginsLoader>
|
||||
</IntlProvider>
|
||||
) : null}
|
||||
</ToastProvider>
|
||||
</IntlProvider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -3,6 +3,8 @@ import { QueryResult } from "@apollo/client";
|
||||
import { ListFilterModel } from "src/models/list-filter/filter";
|
||||
import { Pagination, PaginationIndex } from "./Pagination";
|
||||
import { LoadingIndicator } from "../Shared/LoadingIndicator";
|
||||
import { ErrorMessage } from "../Shared/ErrorMessage";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
export const PagedList: React.FC<
|
||||
PropsWithChildren<{
|
||||
@@ -65,7 +67,17 @@ export const PagedList: React.FC<
|
||||
return <LoadingIndicator />;
|
||||
}
|
||||
if (result.error) {
|
||||
return <h1>{result.error.message}</h1>;
|
||||
return (
|
||||
<ErrorMessage
|
||||
message={
|
||||
<FormattedMessage
|
||||
id="errors.loading_type"
|
||||
values={{ type: "items" }}
|
||||
/>
|
||||
}
|
||||
error={result.error.message}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,26 @@
|
||||
import { faWarning } from "@fortawesome/free-solid-svg-icons";
|
||||
import React, { ReactNode } from "react";
|
||||
import { Alert } from "react-bootstrap";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { Icon } from "./Icon";
|
||||
|
||||
interface IProps {
|
||||
message?: React.ReactNode;
|
||||
error: string | ReactNode;
|
||||
}
|
||||
|
||||
export const ErrorMessage: React.FC<IProps> = ({ error }) => (
|
||||
<div className="row ErrorMessage">
|
||||
<h2 className="ErrorMessage-content">Error: {error}</h2>
|
||||
</div>
|
||||
);
|
||||
export const ErrorMessage: React.FC<IProps> = (props) => {
|
||||
const { error, message = <FormattedMessage id="errors.header" /> } = props;
|
||||
|
||||
return (
|
||||
<div className="ErrorMessage-container">
|
||||
<Alert variant="danger" className="ErrorMessage">
|
||||
<Alert.Heading className="ErrorMessage-header">
|
||||
<Icon icon={faWarning} />
|
||||
{message}
|
||||
</Alert.Heading>
|
||||
<div className="ErrorMessage-content code">{error}</div>
|
||||
</Alert>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -213,13 +213,29 @@ button.collapse-button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.ErrorMessage {
|
||||
align-items: center;
|
||||
height: 20rem;
|
||||
.ErrorMessage-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&-content {
|
||||
display: inline-block;
|
||||
.ErrorMessage {
|
||||
.fa-icon {
|
||||
color: $warning;
|
||||
font-size: 1.5em;
|
||||
margin-right: 0.3em;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
background-color: initial;
|
||||
border-color: $danger;
|
||||
color: $text-color;
|
||||
margin: 1rem;
|
||||
text-align: left;
|
||||
width: 500px;
|
||||
|
||||
@include media-breakpoint-down(xs) {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ const errorDelay = 5000;
|
||||
|
||||
let toastID = 0;
|
||||
|
||||
const ToastContext = createContext<(item: IToast) => void>(() => {});
|
||||
type ToastFn = (item: IToast) => void;
|
||||
|
||||
const ToastContext = createContext<ToastFn | null>(null);
|
||||
|
||||
export const ToastProvider: React.FC = ({ children }) => {
|
||||
const [toast, setToast] = useState<IActiveToast>();
|
||||
@@ -121,6 +123,10 @@ export const ToastProvider: React.FC = ({ children }) => {
|
||||
export const useToast = () => {
|
||||
const addToast = useContext(ToastContext);
|
||||
|
||||
if (!addToast) {
|
||||
throw new Error("useToast must be used within a ToastProvider");
|
||||
}
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
toast: addToast,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { PatchFunction } from "./patch";
|
||||
import { usePlugins } from "./core/StashService";
|
||||
import { useMemoOnce } from "./hooks/state";
|
||||
@@ -7,6 +7,7 @@ import useScript, { useCSS } from "./hooks/useScript";
|
||||
import { PluginsQuery } from "./core/generated-graphql";
|
||||
import { LoadingIndicator } from "./components/Shared/LoadingIndicator";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import { useToast } from "./hooks/Toast";
|
||||
|
||||
type PluginList = NonNullable<Required<PluginsQuery["plugins"]>>;
|
||||
|
||||
@@ -102,15 +103,25 @@ function useLoadPlugins() {
|
||||
);
|
||||
useCSS(pluginCSS ?? [], !pluginsLoading && !pluginsError);
|
||||
|
||||
return !pluginsLoading && !!pluginJavascripts && pluginJavascriptLoaded;
|
||||
return {
|
||||
loading: !pluginsLoading && !!pluginJavascripts && pluginJavascriptLoaded,
|
||||
error: pluginsError,
|
||||
};
|
||||
}
|
||||
|
||||
export const PluginsLoader: React.FC<React.PropsWithChildren<{}>> = ({
|
||||
children,
|
||||
}) => {
|
||||
const loaded = useLoadPlugins();
|
||||
const Toast = useToast();
|
||||
const { loading: loaded, error } = useLoadPlugins();
|
||||
|
||||
if (!loaded)
|
||||
useEffect(() => {
|
||||
if (error) {
|
||||
Toast.error(`Error loading plugins: ${error.message}`);
|
||||
}
|
||||
}, [Toast, error]);
|
||||
|
||||
if (!loaded && !error)
|
||||
return (
|
||||
<LoadingIndicator message={<FormattedMessage id="loading.plugins" />} />
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user