@scm-manager/ui-components 4.0.0-REACT19-20251102-220352 → 4.0.0-REACT19-20251104-141645
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scm-manager/ui-components",
|
|
3
|
-
"version": "4.0.0-REACT19-
|
|
3
|
+
"version": "4.0.0-REACT19-20251104-141645",
|
|
4
4
|
"description": "UI Components for SCM-Manager and its plugins",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"files": [
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"@scm-manager/eslint-config": "^2.18.2",
|
|
35
35
|
"@scm-manager/prettier-config": "^2.12.0",
|
|
36
36
|
"@scm-manager/tsconfig": "^2.13.0",
|
|
37
|
-
"@scm-manager/ui-syntaxhighlighting": "4.0.0-REACT19-
|
|
38
|
-
"@scm-manager/ui-types": "4.0.0-REACT19-
|
|
37
|
+
"@scm-manager/ui-syntaxhighlighting": "4.0.0-REACT19-20251104-141645",
|
|
38
|
+
"@scm-manager/ui-types": "4.0.0-REACT19-20251104-141645",
|
|
39
39
|
"@storybook/addon-actions": "^9.0.8",
|
|
40
40
|
"@storybook/addon-docs": "^9.1.5",
|
|
41
41
|
"@storybook/addon-essentials": "^9.0.0-alpha.12",
|
|
@@ -66,9 +66,9 @@
|
|
|
66
66
|
"vitest": "^3.2.4"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@scm-manager/ui-api": "4.0.0-REACT19-
|
|
70
|
-
"@scm-manager/ui-core": "4.0.0-REACT19-
|
|
71
|
-
"@scm-manager/ui-extensions": "4.0.0-REACT19-
|
|
69
|
+
"@scm-manager/ui-api": "4.0.0-REACT19-20251104-141645",
|
|
70
|
+
"@scm-manager/ui-core": "4.0.0-REACT19-20251104-141645",
|
|
71
|
+
"@scm-manager/ui-extensions": "4.0.0-REACT19-20251104-141645",
|
|
72
72
|
"deepmerge": "^4.2.2",
|
|
73
73
|
"hast-util-sanitize": "^3.0.2",
|
|
74
74
|
"react-diff-view": "2.6.0",
|
package/src/ErrorBoundary.tsx
CHANGED
|
@@ -15,34 +15,26 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import React, { FC, ReactNode, useEffect } from "react";
|
|
18
|
-
import { useLocation } from "react-router-dom";
|
|
19
|
-
import { ErrorBoundary as ReactErrorBoundary, FallbackProps } from "react-error-boundary";
|
|
18
|
+
import { isRouteErrorResponse, useLocation, useRouteError } from "react-router-dom";
|
|
20
19
|
import { useTranslation } from "react-i18next";
|
|
21
20
|
import classNames from "classnames";
|
|
22
21
|
import styled from "styled-components";
|
|
23
22
|
import { MissingLinkError, urls, useIndexLink } from "@scm-manager/ui-api";
|
|
23
|
+
import { ErrorBoundary as ReactErrorBoundary } from "react-error-boundary";
|
|
24
24
|
import { useDocumentTitle } from "@scm-manager/ui-core";
|
|
25
25
|
import ErrorNotification from "./ErrorNotification";
|
|
26
26
|
import ErrorPage from "./ErrorPage";
|
|
27
27
|
import { Subtitle, Title } from "./layout";
|
|
28
28
|
import Icon from "./Icon";
|
|
29
29
|
|
|
30
|
-
type
|
|
30
|
+
type ErrorDisplayProps = {
|
|
31
31
|
error: Error;
|
|
32
|
+
errorInfo?: { componentStack?: string }; // Optional gemacht
|
|
33
|
+
fallback?: React.ComponentType<{ error: Error }>;
|
|
32
34
|
};
|
|
33
35
|
|
|
34
|
-
type
|
|
35
|
-
fallback?: React.ComponentType<
|
|
36
|
-
children?: ReactNode;
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
type ErrorDisplayProps = {
|
|
40
|
-
error: Error;
|
|
41
|
-
// react-error-boundary does not pass errorInfo to the fallback component by default
|
|
42
|
-
// for performance reasons, but it can be logged via the `onError` prop.
|
|
43
|
-
// We keep it in the signature for compatibility with the custom fallback prop.
|
|
44
|
-
errorInfo?: { componentStack: string };
|
|
45
|
-
fallback?: React.ComponentType<ErrorState>;
|
|
36
|
+
type GlobalErrorBoundaryProps = {
|
|
37
|
+
fallback?: React.ComponentType<{ error: Error }>;
|
|
46
38
|
};
|
|
47
39
|
|
|
48
40
|
const RedirectIconContainer = styled.div`
|
|
@@ -52,6 +44,7 @@ const RedirectIconContainer = styled.div`
|
|
|
52
44
|
const RedirectPage: FC = () => {
|
|
53
45
|
const [t] = useTranslation("commons");
|
|
54
46
|
useDocumentTitle(t("errorNotification.prefix"));
|
|
47
|
+
|
|
55
48
|
return (
|
|
56
49
|
<section className="section">
|
|
57
50
|
<div className="container">
|
|
@@ -72,7 +65,7 @@ const RedirectPage: FC = () => {
|
|
|
72
65
|
);
|
|
73
66
|
};
|
|
74
67
|
|
|
75
|
-
const ErrorDisplay: FC<ErrorDisplayProps> = ({ error,
|
|
68
|
+
const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, fallback: FallbackComponent }) => {
|
|
76
69
|
const loginLink = useIndexLink("login");
|
|
77
70
|
const [t] = useTranslation("commons");
|
|
78
71
|
const location = useLocation();
|
|
@@ -87,41 +80,47 @@ const ErrorDisplay: FC<ErrorDisplayProps> = ({ error, errorInfo, fallback: Fallb
|
|
|
87
80
|
if (isMissingLink) {
|
|
88
81
|
if (loginLink) {
|
|
89
82
|
return <RedirectPage />;
|
|
83
|
+
} else {
|
|
84
|
+
return (
|
|
85
|
+
<ErrorPage error={error} title={t("errorNotification.prefix")} subtitle={t("errorNotification.forbidden")} />
|
|
86
|
+
);
|
|
90
87
|
}
|
|
91
|
-
return (
|
|
92
|
-
<ErrorPage error={error} title={t("errorNotification.prefix")} subtitle={t("errorNotification.forbidden")} />
|
|
93
|
-
);
|
|
94
88
|
}
|
|
95
89
|
|
|
96
|
-
const fallbackProps = {
|
|
97
|
-
error,
|
|
98
|
-
errorInfo,
|
|
99
|
-
};
|
|
100
|
-
|
|
101
90
|
if (FallbackComponent) {
|
|
102
|
-
return <FallbackComponent {
|
|
91
|
+
return <FallbackComponent error={error} />;
|
|
103
92
|
}
|
|
93
|
+
|
|
104
94
|
return <ErrorNotification error={error} />;
|
|
105
95
|
};
|
|
106
96
|
|
|
107
|
-
const ErrorBoundary: FC<
|
|
97
|
+
const ErrorBoundary: FC<GlobalErrorBoundaryProps & { children?: ReactNode }> = ({ fallback, children }) => {
|
|
98
|
+
const rawError = useRouteError();
|
|
108
99
|
const location = useLocation();
|
|
109
100
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
error={error}
|
|
113
|
-
|
|
114
|
-
// The actual component stack is available in the `onError` callback below.
|
|
115
|
-
errorInfo={{ componentStack: "" }}
|
|
116
|
-
fallback={fallback}
|
|
117
|
-
/>
|
|
118
|
-
);
|
|
101
|
+
if (!rawError) {
|
|
102
|
+
const ErrorBoundaryFallback = ({ error }: { error: Error }) => (
|
|
103
|
+
<ErrorDisplay error={error} errorInfo={{ componentStack: "" }} fallback={fallback} />
|
|
104
|
+
);
|
|
119
105
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
106
|
+
return (
|
|
107
|
+
<ReactErrorBoundary resetKeys={[location.pathname]} FallbackComponent={ErrorBoundaryFallback}>
|
|
108
|
+
{children}
|
|
109
|
+
</ReactErrorBoundary>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let error: Error;
|
|
114
|
+
|
|
115
|
+
if (isRouteErrorResponse(rawError)) {
|
|
116
|
+
error = new Error(`${rawError.status} ${rawError.statusText}`);
|
|
117
|
+
} else if (rawError instanceof Error) {
|
|
118
|
+
error = rawError;
|
|
119
|
+
} else {
|
|
120
|
+
error = new Error(rawError ? String(rawError) : "Unknown Error");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return <ErrorDisplay error={error} errorInfo={{ componentStack: "Loader/Action Error" }} fallback={fallback} />;
|
|
125
124
|
};
|
|
126
125
|
|
|
127
126
|
export default ErrorBoundary;
|
|
@@ -57,7 +57,7 @@ class ConfigurationBinder {
|
|
|
57
57
|
return { path, element: Component };
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
bindAdminSetting(to: string, labelI18nKey: string, linkName: string, ConfigurationComponent: any, sortKey?: string) {
|
|
61
61
|
// create predicate based on the link name of the index resource
|
|
62
62
|
// if the linkname is not available, the navigation link and the route are not bound to the extension points
|
|
63
63
|
const configPredicate = (props: any) => {
|
|
@@ -74,7 +74,7 @@ class ConfigurationBinder {
|
|
|
74
74
|
binder.bind("admin.setting", ConfigNavLink, configPredicate, sortKey ?? linkName);
|
|
75
75
|
|
|
76
76
|
// route for global configuration, passes the link from the index resource to component
|
|
77
|
-
const ConfigRoute = ({ ...additionalProps }: GlobalRouteProps) => {
|
|
77
|
+
const ConfigRoute = ({ ...additionalProps }: GlobalRouteProps = {} as GlobalRouteProps) => {
|
|
78
78
|
return this.route(
|
|
79
79
|
sanitizedTo,
|
|
80
80
|
<TitledGlobalSettingComponent i18nNamespace={this.i18nNamespace} label={labelI18nKey}>
|
|
@@ -84,28 +84,19 @@ class ConfigurationBinder {
|
|
|
84
84
|
};
|
|
85
85
|
|
|
86
86
|
// bind config route to extension point
|
|
87
|
-
binder.bind("admin.route", ConfigRoute);
|
|
87
|
+
binder.bind("admin.settings.route", ConfigRoute);
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
bindAdmin(
|
|
91
|
-
to: string,
|
|
92
|
-
labelI18nKey: string,
|
|
93
|
-
icon: string,
|
|
94
|
-
linkName: string,
|
|
95
|
-
Component: React.ComponentType<{ link: string }>,
|
|
96
|
-
) {
|
|
97
|
-
const predicate = ({ links }: extensionPoints.AdminRoute["props"]) => links![linkName];
|
|
98
|
-
|
|
90
|
+
bindAdmin(to: string, labelI18nKey: string, icon: string, Component: React.ComponentType) {
|
|
99
91
|
const AdminNavLink = withTranslation(this.i18nNamespace)(
|
|
100
92
|
({ t }: WithTranslation & extensionPoints.AdminNavigation["props"]) =>
|
|
101
93
|
this.navLink("/admin/" + to, labelI18nKey, t, { icon }),
|
|
102
94
|
);
|
|
103
|
-
binder.bind("admin.navigation", AdminNavLink
|
|
95
|
+
binder.bind("admin.navigation", AdminNavLink);
|
|
104
96
|
|
|
105
|
-
const AdminRoute: extensionPoints.AdminRoute["type"] = (
|
|
106
|
-
this.route(to, <Component link={(links![linkName] as Link).href} />);
|
|
97
|
+
const AdminRoute: extensionPoints.AdminRoute["type"] = () => this.route(to, <Component />);
|
|
107
98
|
|
|
108
|
-
binder.bind<extensionPoints.AdminRoute>("admin.route", AdminRoute
|
|
99
|
+
binder.bind<extensionPoints.AdminRoute>("admin.route", AdminRoute);
|
|
109
100
|
}
|
|
110
101
|
|
|
111
102
|
bindRepository(to: string, labelI18nKey: string, linkName: string, RepositoryComponent: any) {
|
|
@@ -29,16 +29,13 @@ const useMatchesRoute = (pattern?: string) => {
|
|
|
29
29
|
const useActiveMatch = ({ to, activeWhenMatch, activeOnlyWhenExact = true }: RoutingProps) => {
|
|
30
30
|
const location = useLocation();
|
|
31
31
|
const resolved = useResolvedPath(to);
|
|
32
|
-
const isActive =
|
|
32
|
+
const isActive = !resolved.hash && location.pathname.startsWith(resolved.pathname);
|
|
33
33
|
const match = useMatchesRoute(activeWhenMatch);
|
|
34
34
|
|
|
35
35
|
if (activeOnlyWhenExact) {
|
|
36
36
|
return isActive;
|
|
37
37
|
} else if (activeWhenMatch) {
|
|
38
|
-
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
return match;
|
|
38
|
+
return isActive || match;
|
|
42
39
|
}
|
|
43
40
|
return false;
|
|
44
41
|
};
|