@backstage/plugin-app 0.5.0 → 0.5.1-next.1
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/CHANGELOG.md +29 -0
- package/dist/extensions/AppRoot.esm.js +5 -4
- package/dist/extensions/AppRoot.esm.js.map +1 -1
- package/dist/extensions/BreadcrumbsRegistryProvider.esm.js +39 -0
- package/dist/extensions/BreadcrumbsRegistryProvider.esm.js.map +1 -0
- package/dist/extensions/components.esm.js +14 -2
- package/dist/extensions/components.esm.js.map +1 -1
- package/dist/plugins/app/package.json.esm.js +1 -1
- package/package.json +17 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,34 @@
|
|
|
1
1
|
# @backstage/plugin-app
|
|
2
2
|
|
|
3
|
+
## 0.5.1-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a5b2811: Adds `useBreadcrumbEntries` hook, `BreadcrumbEntry` component, and `BreadcrumbsRegistryProvider` for managing breadcrumb trails across the component tree for plugins using new frontend system. Completes wiring so that new frontend system plugin `Pages` and `SubPages` get automatic-population of `PluginHeader` breadcrumbs.
|
|
8
|
+
|
|
9
|
+
- The app plugin's `PageLayout` registers a root breadcrumb for each plugin page and passes the breadcrumb trail to `PluginHeader`
|
|
10
|
+
- `PageBlueprint` automatically wraps each sub-page route element with `BreadcrumbEntry`, so sub-pages contribute to the PluginHeader breadcrumb trail without extra wiring.
|
|
11
|
+
- Plugin authors who need breadcrumbs for internal routes within a sub-page can wrap their route content with `BreadcrumbEntry` manually.
|
|
12
|
+
- `plugin-scaffolder` internal routes have been wrapped as an example
|
|
13
|
+
|
|
14
|
+
- Updated dependencies
|
|
15
|
+
- @backstage/frontend-plugin-api@0.17.3-next.1
|
|
16
|
+
- @backstage/ui@0.17.0-next.1
|
|
17
|
+
|
|
18
|
+
## 0.5.1-next.0
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
- @backstage/ui@0.17.0-next.0
|
|
24
|
+
- @backstage/filter-predicates@0.1.4-next.0
|
|
25
|
+
- @backstage/frontend-plugin-api@0.17.3-next.0
|
|
26
|
+
- @backstage/core-components@0.18.12-next.0
|
|
27
|
+
- @backstage/core-plugin-api@1.12.8-next.0
|
|
28
|
+
- @backstage/integration-react@1.2.20-next.0
|
|
29
|
+
- @backstage/plugin-app-react@0.2.5-next.0
|
|
30
|
+
- @backstage/plugin-permission-react@0.5.3-next.0
|
|
31
|
+
|
|
3
32
|
## 0.5.0
|
|
4
33
|
|
|
5
34
|
### Minor Changes
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
2
|
import { useState } from 'react';
|
|
3
3
|
import { createExtension, coreExtensionData, discoveryApiRef, errorApiRef, fetchApiRef, pluginWrapperApiRef, ExtensionBoundary, createExtensionInput, routeResolutionApiRef, useAnalytics } from '@backstage/frontend-plugin-api';
|
|
4
|
+
import { BreadcrumbsRegistryProvider } from './BreadcrumbsRegistryProvider.esm.js';
|
|
4
5
|
import { AppRootWrapperBlueprint, RouterBlueprint, SignInPageBlueprint } from '@backstage/plugin-app-react';
|
|
5
6
|
import { BUIProvider } from '@backstage/ui';
|
|
6
7
|
import { identityApiRef, useApi, configApiRef } from '@backstage/core-plugin-api';
|
|
@@ -168,13 +169,13 @@ function AppRouter(props) {
|
|
|
168
169
|
{ signOutTargetUrl: basePath || "/" }
|
|
169
170
|
);
|
|
170
171
|
}
|
|
171
|
-
return /* @__PURE__ */ jsx(RouterComponent, { children: /* @__PURE__ */
|
|
172
|
+
return /* @__PURE__ */ jsx(RouterComponent, { children: /* @__PURE__ */ jsx(BUIProvider, { useAnalytics, children: /* @__PURE__ */ jsxs(BreadcrumbsRegistryProvider, { children: [
|
|
172
173
|
...extraElements,
|
|
173
174
|
/* @__PURE__ */ jsx(RouteTracker, { routeObjects }),
|
|
174
175
|
children
|
|
175
|
-
] }) });
|
|
176
|
+
] }) }) });
|
|
176
177
|
}
|
|
177
|
-
return /* @__PURE__ */ jsx(RouterComponent, { children: /* @__PURE__ */
|
|
178
|
+
return /* @__PURE__ */ jsx(RouterComponent, { children: /* @__PURE__ */ jsx(BUIProvider, { useAnalytics, children: /* @__PURE__ */ jsxs(BreadcrumbsRegistryProvider, { children: [
|
|
178
179
|
...extraElements,
|
|
179
180
|
/* @__PURE__ */ jsx(RouteTracker, { routeObjects }),
|
|
180
181
|
/* @__PURE__ */ jsx(
|
|
@@ -185,7 +186,7 @@ function AppRouter(props) {
|
|
|
185
186
|
children
|
|
186
187
|
}
|
|
187
188
|
)
|
|
188
|
-
] }) });
|
|
189
|
+
] }) }) });
|
|
189
190
|
}
|
|
190
191
|
|
|
191
192
|
export { AppRoot, AppRouter };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppRoot.esm.js","sources":["../../src/extensions/AppRoot.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ComponentType,\n PropsWithChildren,\n ReactNode,\n useState,\n JSX,\n} from 'react';\nimport {\n ExtensionBoundary,\n coreExtensionData,\n discoveryApiRef,\n fetchApiRef,\n errorApiRef,\n createExtension,\n createExtensionInput,\n routeResolutionApiRef,\n pluginWrapperApiRef,\n useAnalytics,\n} from '@backstage/frontend-plugin-api';\nimport {\n AppRootWrapperBlueprint,\n RouterBlueprint,\n SignInPageBlueprint,\n} from '@backstage/plugin-app-react';\nimport { BUIProvider } from '@backstage/ui';\nimport {\n DiscoveryApi,\n ErrorApi,\n FetchApi,\n IdentityApi,\n ProfileInfo,\n SignInPageProps,\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp';\nimport { BrowserRouter } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath';\n\nexport const AppRoot = createExtension({\n name: 'root',\n attachTo: { id: 'app', input: 'root' },\n inputs: {\n router: createExtensionInput([RouterBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n children: createExtensionInput([coreExtensionData.reactElement], {\n singleton: true,\n optional: true,\n }),\n elements: createExtensionInput([coreExtensionData.reactElement]),\n wrappers: createExtensionInput(\n [AppRootWrapperBlueprint.dataRefs.component],\n {\n internal: true,\n },\n ),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs, apis, node }) {\n if (isProtectedApp()) {\n const identityApi = apis.get(identityApiRef);\n if (!identityApi) {\n throw new Error('App requires an Identity API implementation');\n }\n const appIdentityProxy = toAppIdentityProxy(identityApi);\n const discoveryApi = apis.get(discoveryApiRef);\n const errorApi = apis.get(errorApiRef);\n const fetchApi = apis.get(fetchApiRef);\n if (!discoveryApi || !errorApi || !fetchApi) {\n throw new Error(\n 'App is running in protected mode but missing required APIs',\n );\n }\n appIdentityProxy.enableCookieAuth({\n discoveryApi,\n errorApi,\n fetchApi,\n });\n }\n\n let content = inputs.children?.get(coreExtensionData.reactElement);\n\n for (const wrapper of inputs.wrappers) {\n const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);\n if (Component) {\n content = <Component>{content}</Component>;\n }\n }\n\n const pluginWrapperApi = apis.get(pluginWrapperApiRef);\n const RootWrapper = pluginWrapperApi?.getRootWrapper();\n if (RootWrapper) {\n content = <RootWrapper>{content}</RootWrapper>;\n }\n\n return [\n coreExtensionData.reactElement(\n <ExtensionBoundary node={node}>\n <AppRouter\n SignInPageComponent={inputs.signInPage?.get(\n SignInPageBlueprint.dataRefs.component,\n )}\n RouterComponent={inputs.router?.get(\n RouterBlueprint.dataRefs.component,\n )}\n extraElements={inputs.elements?.map(el =>\n el.get(coreExtensionData.reactElement),\n )}\n >\n {content}\n </AppRouter>\n </ExtensionBoundary>,\n ),\n ];\n },\n});\n\n// This wraps the sign-in page and waits for sign-in to be completed before rendering the app\nfunction SignInPageWrapper({\n component: Component,\n appIdentityProxy,\n children,\n}: {\n component: ComponentType<SignInPageProps>;\n appIdentityProxy: AppIdentityProxy;\n children: ReactNode;\n}) {\n const [identityApi, setIdentityApi] = useState<IdentityApi>();\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n\n if (!identityApi) {\n return <Component onSignInSuccess={setIdentityApi} />;\n }\n\n appIdentityProxy.setTarget(identityApi, {\n signOutTargetUrl: basePath || '/',\n });\n return <>{children}</>;\n}\n\ntype AppIdentityProxy = IdentityApi & {\n enableCookieAuth(ctx: {\n errorApi: ErrorApi;\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n }): void;\n setTarget(\n impl: IdentityApi & /* backwards compat stuff */ {\n getUserId?(): string;\n getIdToken?(): Promise<string | undefined>;\n getProfile?(): ProfileInfo;\n },\n options: { signOutTargetUrl: string },\n ): void;\n};\n\nfunction toAppIdentityProxy(identityApi: IdentityApi): AppIdentityProxy {\n if (!('enableCookieAuth' in identityApi)) {\n throw new Error('Unexpected Identity API implementation');\n }\n return identityApi as AppIdentityProxy;\n}\n\ntype RouteResolverProxy = {\n getRouteObjects(): any[];\n};\n\n/**\n * Props for the {@link AppRouter} component.\n * @public\n */\nexport interface AppRouterProps {\n children?: ReactNode;\n SignInPageComponent?: ComponentType<SignInPageProps>;\n RouterComponent?: (props: { children: ReactNode }) => JSX.Element | null;\n extraElements?: Array<JSX.Element>;\n}\n\nfunction DefaultRouter(props: PropsWithChildren<{}>) {\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n return (\n <BrowserRouter\n basename={basePath}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {props.children}\n </BrowserRouter>\n );\n}\n\n/**\n * App router and sign-in page wrapper.\n *\n * @remarks\n *\n * The AppRouter provides the routing context and renders the sign-in page.\n * Until the user has successfully signed in, this component will render\n * the sign-in page. Once the user has signed-in, it will instead render\n * the app, while providing routing and route tracking for the app.\n */\nexport function AppRouter(props: AppRouterProps) {\n const {\n children,\n SignInPageComponent,\n RouterComponent = DefaultRouter,\n extraElements = [],\n } = props;\n\n const configApi = useApi(configApiRef);\n const appIdentityProxy = toAppIdentityProxy(useApi(identityApiRef));\n const routeResolutionsApi = useApi(routeResolutionApiRef);\n const basePath = getBasePath(configApi);\n\n // TODO: Private access for now, probably replace with path -> node lookup method on the API\n if (!('getRouteObjects' in routeResolutionsApi)) {\n throw new Error('Unexpected route resolution API implementation');\n }\n const routeObjects = (\n routeResolutionsApi as RouteResolverProxy\n ).getRouteObjects();\n\n // If the app hasn't configured a sign-in page, we just continue as guest.\n if (!SignInPageComponent) {\n if (!isProtectedApp()) {\n appIdentityProxy.setTarget(\n {\n getUserId: () => 'guest',\n getIdToken: async () => undefined,\n getProfile: () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getProfileInfo: async () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getBackstageIdentity: async () => ({\n type: 'user',\n userEntityRef: 'user:default/guest',\n ownershipEntityRefs: ['user:default/guest'],\n }),\n getCredentials: async () => ({}),\n signOut: async () => {},\n },\n { signOutTargetUrl: basePath || '/' },\n );\n }\n\n return (\n <RouterComponent>\n <BUIProvider useAnalytics={useAnalytics}>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n {children}\n </BUIProvider>\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent>\n <BUIProvider useAnalytics={useAnalytics}>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {children}\n </SignInPageWrapper>\n </BUIProvider>\n </RouterComponent>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppRoot.esm.js","sources":["../../src/extensions/AppRoot.tsx"],"sourcesContent":["/*\n * Copyright 2023 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n ComponentType,\n PropsWithChildren,\n ReactNode,\n useState,\n JSX,\n} from 'react';\nimport {\n ExtensionBoundary,\n coreExtensionData,\n discoveryApiRef,\n fetchApiRef,\n errorApiRef,\n createExtension,\n createExtensionInput,\n routeResolutionApiRef,\n pluginWrapperApiRef,\n useAnalytics,\n} from '@backstage/frontend-plugin-api';\nimport { BreadcrumbsRegistryProvider } from './BreadcrumbsRegistryProvider';\nimport {\n AppRootWrapperBlueprint,\n RouterBlueprint,\n SignInPageBlueprint,\n} from '@backstage/plugin-app-react';\nimport { BUIProvider } from '@backstage/ui';\nimport {\n DiscoveryApi,\n ErrorApi,\n FetchApi,\n IdentityApi,\n ProfileInfo,\n SignInPageProps,\n configApiRef,\n identityApiRef,\n useApi,\n} from '@backstage/core-plugin-api';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { isProtectedApp } from '../../../../packages/core-app-api/src/app/isProtectedApp';\nimport { BrowserRouter } from 'react-router-dom';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { RouteTracker } from '../../../../packages/frontend-app-api/src/routing/RouteTracker';\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { getBasePath } from '../../../../packages/frontend-app-api/src/routing/getBasePath';\n\nexport const AppRoot = createExtension({\n name: 'root',\n attachTo: { id: 'app', input: 'root' },\n inputs: {\n router: createExtensionInput([RouterBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n signInPage: createExtensionInput([SignInPageBlueprint.dataRefs.component], {\n singleton: true,\n optional: true,\n internal: true,\n }),\n children: createExtensionInput([coreExtensionData.reactElement], {\n singleton: true,\n optional: true,\n }),\n elements: createExtensionInput([coreExtensionData.reactElement]),\n wrappers: createExtensionInput(\n [AppRootWrapperBlueprint.dataRefs.component],\n {\n internal: true,\n },\n ),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs, apis, node }) {\n if (isProtectedApp()) {\n const identityApi = apis.get(identityApiRef);\n if (!identityApi) {\n throw new Error('App requires an Identity API implementation');\n }\n const appIdentityProxy = toAppIdentityProxy(identityApi);\n const discoveryApi = apis.get(discoveryApiRef);\n const errorApi = apis.get(errorApiRef);\n const fetchApi = apis.get(fetchApiRef);\n if (!discoveryApi || !errorApi || !fetchApi) {\n throw new Error(\n 'App is running in protected mode but missing required APIs',\n );\n }\n appIdentityProxy.enableCookieAuth({\n discoveryApi,\n errorApi,\n fetchApi,\n });\n }\n\n let content = inputs.children?.get(coreExtensionData.reactElement);\n\n for (const wrapper of inputs.wrappers) {\n const Component = wrapper.get(AppRootWrapperBlueprint.dataRefs.component);\n if (Component) {\n content = <Component>{content}</Component>;\n }\n }\n\n const pluginWrapperApi = apis.get(pluginWrapperApiRef);\n const RootWrapper = pluginWrapperApi?.getRootWrapper();\n if (RootWrapper) {\n content = <RootWrapper>{content}</RootWrapper>;\n }\n\n return [\n coreExtensionData.reactElement(\n <ExtensionBoundary node={node}>\n <AppRouter\n SignInPageComponent={inputs.signInPage?.get(\n SignInPageBlueprint.dataRefs.component,\n )}\n RouterComponent={inputs.router?.get(\n RouterBlueprint.dataRefs.component,\n )}\n extraElements={inputs.elements?.map(el =>\n el.get(coreExtensionData.reactElement),\n )}\n >\n {content}\n </AppRouter>\n </ExtensionBoundary>,\n ),\n ];\n },\n});\n\n// This wraps the sign-in page and waits for sign-in to be completed before rendering the app\nfunction SignInPageWrapper({\n component: Component,\n appIdentityProxy,\n children,\n}: {\n component: ComponentType<SignInPageProps>;\n appIdentityProxy: AppIdentityProxy;\n children: ReactNode;\n}) {\n const [identityApi, setIdentityApi] = useState<IdentityApi>();\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n\n if (!identityApi) {\n return <Component onSignInSuccess={setIdentityApi} />;\n }\n\n appIdentityProxy.setTarget(identityApi, {\n signOutTargetUrl: basePath || '/',\n });\n return <>{children}</>;\n}\n\ntype AppIdentityProxy = IdentityApi & {\n enableCookieAuth(ctx: {\n errorApi: ErrorApi;\n fetchApi: FetchApi;\n discoveryApi: DiscoveryApi;\n }): void;\n setTarget(\n impl: IdentityApi & /* backwards compat stuff */ {\n getUserId?(): string;\n getIdToken?(): Promise<string | undefined>;\n getProfile?(): ProfileInfo;\n },\n options: { signOutTargetUrl: string },\n ): void;\n};\n\nfunction toAppIdentityProxy(identityApi: IdentityApi): AppIdentityProxy {\n if (!('enableCookieAuth' in identityApi)) {\n throw new Error('Unexpected Identity API implementation');\n }\n return identityApi as AppIdentityProxy;\n}\n\ntype RouteResolverProxy = {\n getRouteObjects(): any[];\n};\n\n/**\n * Props for the {@link AppRouter} component.\n * @public\n */\nexport interface AppRouterProps {\n children?: ReactNode;\n SignInPageComponent?: ComponentType<SignInPageProps>;\n RouterComponent?: (props: { children: ReactNode }) => JSX.Element | null;\n extraElements?: Array<JSX.Element>;\n}\n\nfunction DefaultRouter(props: PropsWithChildren<{}>) {\n const configApi = useApi(configApiRef);\n const basePath = getBasePath(configApi);\n return (\n <BrowserRouter\n basename={basePath}\n future={{\n v7_relativeSplatPath: false,\n v7_startTransition: false,\n }}\n >\n {props.children}\n </BrowserRouter>\n );\n}\n\n/**\n * App router and sign-in page wrapper.\n *\n * @remarks\n *\n * The AppRouter provides the routing context and renders the sign-in page.\n * Until the user has successfully signed in, this component will render\n * the sign-in page. Once the user has signed-in, it will instead render\n * the app, while providing routing and route tracking for the app.\n */\nexport function AppRouter(props: AppRouterProps) {\n const {\n children,\n SignInPageComponent,\n RouterComponent = DefaultRouter,\n extraElements = [],\n } = props;\n\n const configApi = useApi(configApiRef);\n const appIdentityProxy = toAppIdentityProxy(useApi(identityApiRef));\n const routeResolutionsApi = useApi(routeResolutionApiRef);\n const basePath = getBasePath(configApi);\n\n // TODO: Private access for now, probably replace with path -> node lookup method on the API\n if (!('getRouteObjects' in routeResolutionsApi)) {\n throw new Error('Unexpected route resolution API implementation');\n }\n const routeObjects = (\n routeResolutionsApi as RouteResolverProxy\n ).getRouteObjects();\n\n // If the app hasn't configured a sign-in page, we just continue as guest.\n if (!SignInPageComponent) {\n if (!isProtectedApp()) {\n appIdentityProxy.setTarget(\n {\n getUserId: () => 'guest',\n getIdToken: async () => undefined,\n getProfile: () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getProfileInfo: async () => ({\n email: 'guest@example.com',\n displayName: 'Guest',\n }),\n getBackstageIdentity: async () => ({\n type: 'user',\n userEntityRef: 'user:default/guest',\n ownershipEntityRefs: ['user:default/guest'],\n }),\n getCredentials: async () => ({}),\n signOut: async () => {},\n },\n { signOutTargetUrl: basePath || '/' },\n );\n }\n\n return (\n <RouterComponent>\n <BUIProvider useAnalytics={useAnalytics}>\n <BreadcrumbsRegistryProvider>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n {children}\n </BreadcrumbsRegistryProvider>\n </BUIProvider>\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent>\n <BUIProvider useAnalytics={useAnalytics}>\n <BreadcrumbsRegistryProvider>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {children}\n </SignInPageWrapper>\n </BreadcrumbsRegistryProvider>\n </BUIProvider>\n </RouterComponent>\n );\n}\n"],"names":[],"mappings":";;;;;;;;;;;;AA6DO,MAAM,UAAU,eAAA,CAAgB;AAAA,EACrC,IAAA,EAAM,MAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,KAAA,EAAO,OAAO,MAAA,EAAO;AAAA,EACrC,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB,CAAC,eAAA,CAAgB,QAAA,CAAS,SAAS,CAAA,EAAG;AAAA,MACjE,SAAA,EAAW,IAAA;AAAA,MACX,QAAA,EAAU,IAAA;AAAA,MACV,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,IACD,YAAY,oBAAA,CAAqB,CAAC,mBAAA,CAAoB,QAAA,CAAS,SAAS,CAAA,EAAG;AAAA,MACzE,SAAA,EAAW,IAAA;AAAA,MACX,QAAA,EAAU,IAAA;AAAA,MACV,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,IACD,QAAA,EAAU,oBAAA,CAAqB,CAAC,iBAAA,CAAkB,YAAY,CAAA,EAAG;AAAA,MAC/D,SAAA,EAAW,IAAA;AAAA,MACX,QAAA,EAAU;AAAA,KACX,CAAA;AAAA,IACD,QAAA,EAAU,oBAAA,CAAqB,CAAC,iBAAA,CAAkB,YAAY,CAAC,CAAA;AAAA,IAC/D,QAAA,EAAU,oBAAA;AAAA,MACR,CAAC,uBAAA,CAAwB,QAAA,CAAS,SAAS,CAAA;AAAA,MAC3C;AAAA,QACE,QAAA,EAAU;AAAA;AACZ;AACF,GACF;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAQ,IAAA,EAAM,MAAK,EAAG;AAC9B,IAAA,IAAI,gBAAe,EAAG;AACpB,MAAA,MAAM,WAAA,GAAc,IAAA,CAAK,GAAA,CAAI,cAAc,CAAA;AAC3C,MAAA,IAAI,CAAC,WAAA,EAAa;AAChB,QAAA,MAAM,IAAI,MAAM,6CAA6C,CAAA;AAAA,MAC/D;AACA,MAAA,MAAM,gBAAA,GAAmB,mBAAmB,WAAW,CAAA;AACvD,MAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,eAAe,CAAA;AAC7C,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,WAAW,CAAA;AACrC,MAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,WAAW,CAAA;AACrC,MAAA,IAAI,CAAC,YAAA,IAAgB,CAAC,QAAA,IAAY,CAAC,QAAA,EAAU;AAC3C,QAAA,MAAM,IAAI,KAAA;AAAA,UACR;AAAA,SACF;AAAA,MACF;AACA,MAAA,gBAAA,CAAiB,gBAAA,CAAiB;AAAA,QAChC,YAAA;AAAA,QACA,QAAA;AAAA,QACA;AAAA,OACD,CAAA;AAAA,IACH;AAEA,IAAA,IAAI,OAAA,GAAU,MAAA,CAAO,QAAA,EAAU,GAAA,CAAI,kBAAkB,YAAY,CAAA;AAEjE,IAAA,KAAA,MAAW,OAAA,IAAW,OAAO,QAAA,EAAU;AACrC,MAAA,MAAM,SAAA,GAAY,OAAA,CAAQ,GAAA,CAAI,uBAAA,CAAwB,SAAS,SAAS,CAAA;AACxE,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,OAAA,mBAAU,GAAA,CAAC,aAAW,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,MAChC;AAAA,IACF;AAEA,IAAA,MAAM,gBAAA,GAAmB,IAAA,CAAK,GAAA,CAAI,mBAAmB,CAAA;AACrD,IAAA,MAAM,WAAA,GAAc,kBAAkB,cAAA,EAAe;AACrD,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,OAAA,mBAAU,GAAA,CAAC,eAAa,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,IAClC;AAEA,IAAA,OAAO;AAAA,MACL,iBAAA,CAAkB,YAAA;AAAA,wBAChB,GAAA,CAAC,qBAAkB,IAAA,EACjB,QAAA,kBAAA,GAAA;AAAA,UAAC,SAAA;AAAA,UAAA;AAAA,YACC,mBAAA,EAAqB,OAAO,UAAA,EAAY,GAAA;AAAA,cACtC,oBAAoB,QAAA,CAAS;AAAA,aAC/B;AAAA,YACA,eAAA,EAAiB,OAAO,MAAA,EAAQ,GAAA;AAAA,cAC9B,gBAAgB,QAAA,CAAS;AAAA,aAC3B;AAAA,YACA,aAAA,EAAe,OAAO,QAAA,EAAU,GAAA;AAAA,cAAI,CAAA,EAAA,KAClC,EAAA,CAAG,GAAA,CAAI,iBAAA,CAAkB,YAAY;AAAA,aACvC;AAAA,YAEC,QAAA,EAAA;AAAA;AAAA,SACH,EACF;AAAA;AACF,KACF;AAAA,EACF;AACF,CAAC;AAGD,SAAS,iBAAA,CAAkB;AAAA,EACzB,SAAA,EAAW,SAAA;AAAA,EACX,gBAAA;AAAA,EACA;AACF,CAAA,EAIG;AACD,EAAA,MAAM,CAAC,WAAA,EAAa,cAAc,CAAA,GAAI,QAAA,EAAsB;AAC5D,EAAA,MAAM,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAA,MAAM,QAAA,GAAW,YAAY,SAAS,CAAA;AAEtC,EAAA,IAAI,CAAC,WAAA,EAAa;AAChB,IAAA,uBAAO,GAAA,CAAC,SAAA,EAAA,EAAU,eAAA,EAAiB,cAAA,EAAgB,CAAA;AAAA,EACrD;AAEA,EAAA,gBAAA,CAAiB,UAAU,WAAA,EAAa;AAAA,IACtC,kBAAkB,QAAA,IAAY;AAAA,GAC/B,CAAA;AACD,EAAA,uCAAU,QAAA,EAAS,CAAA;AACrB;AAkBA,SAAS,mBAAmB,WAAA,EAA4C;AACtE,EAAA,IAAI,EAAE,sBAAsB,WAAA,CAAA,EAAc;AACxC,IAAA,MAAM,IAAI,MAAM,wCAAwC,CAAA;AAAA,EAC1D;AACA,EAAA,OAAO,WAAA;AACT;AAiBA,SAAS,cAAc,KAAA,EAA8B;AACnD,EAAA,MAAM,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAA,MAAM,QAAA,GAAW,YAAY,SAAS,CAAA;AACtC,EAAA,uBACE,GAAA;AAAA,IAAC,aAAA;AAAA,IAAA;AAAA,MACC,QAAA,EAAU,QAAA;AAAA,MACV,MAAA,EAAQ;AAAA,QACN,oBAAA,EAAsB,KAAA;AAAA,QACtB,kBAAA,EAAoB;AAAA,OACtB;AAAA,MAEC,QAAA,EAAA,KAAA,CAAM;AAAA;AAAA,GACT;AAEJ;AAYO,SAAS,UAAU,KAAA,EAAuB;AAC/C,EAAA,MAAM;AAAA,IACJ,QAAA;AAAA,IACA,mBAAA;AAAA,IACA,eAAA,GAAkB,aAAA;AAAA,IAClB,gBAAgB;AAAC,GACnB,GAAI,KAAA;AAEJ,EAAA,MAAM,SAAA,GAAY,OAAO,YAAY,CAAA;AACrC,EAAA,MAAM,gBAAA,GAAmB,kBAAA,CAAmB,MAAA,CAAO,cAAc,CAAC,CAAA;AAClE,EAAA,MAAM,mBAAA,GAAsB,OAAO,qBAAqB,CAAA;AACxD,EAAA,MAAM,QAAA,GAAW,YAAY,SAAS,CAAA;AAGtC,EAAA,IAAI,EAAE,qBAAqB,mBAAA,CAAA,EAAsB;AAC/C,IAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,EAClE;AACA,EAAA,MAAM,YAAA,GACJ,oBACA,eAAA,EAAgB;AAGlB,EAAA,IAAI,CAAC,mBAAA,EAAqB;AACxB,IAAA,IAAI,CAAC,gBAAe,EAAG;AACrB,MAAA,gBAAA,CAAiB,SAAA;AAAA,QACf;AAAA,UACE,WAAW,MAAM,OAAA;AAAA,UACjB,YAAY,YAAY,MAAA;AAAA,UACxB,YAAY,OAAO;AAAA,YACjB,KAAA,EAAO,mBAAA;AAAA,YACP,WAAA,EAAa;AAAA,WACf,CAAA;AAAA,UACA,gBAAgB,aAAa;AAAA,YAC3B,KAAA,EAAO,mBAAA;AAAA,YACP,WAAA,EAAa;AAAA,WACf,CAAA;AAAA,UACA,sBAAsB,aAAa;AAAA,YACjC,IAAA,EAAM,MAAA;AAAA,YACN,aAAA,EAAe,oBAAA;AAAA,YACf,mBAAA,EAAqB,CAAC,oBAAoB;AAAA,WAC5C,CAAA;AAAA,UACA,cAAA,EAAgB,aAAa,EAAC,CAAA;AAAA,UAC9B,SAAS,YAAY;AAAA,UAAC;AAAA,SACxB;AAAA,QACA,EAAE,gBAAA,EAAkB,QAAA,IAAY,GAAA;AAAI,OACtC;AAAA,IACF;AAEA,IAAA,2BACG,eAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,YAAA,EACX,+BAAC,2BAAA,EAAA,EACE,QAAA,EAAA;AAAA,MAAA,GAAG,aAAA;AAAA,sBACJ,GAAA,CAAC,gBAAa,YAAA,EAA4B,CAAA;AAAA,MACzC;AAAA,KAAA,EACH,GACF,CAAA,EACF,CAAA;AAAA,EAEJ;AAEA,EAAA,2BACG,eAAA,EAAA,EACC,QAAA,kBAAA,GAAA,CAAC,WAAA,EAAA,EAAY,YAAA,EACX,+BAAC,2BAAA,EAAA,EACE,QAAA,EAAA;AAAA,IAAA,GAAG,aAAA;AAAA,oBACJ,GAAA,CAAC,gBAAa,YAAA,EAA4B,CAAA;AAAA,oBAC1C,GAAA;AAAA,MAAC,iBAAA;AAAA,MAAA;AAAA,QACC,SAAA,EAAW,mBAAA;AAAA,QACX,gBAAA;AAAA,QAEC;AAAA;AAAA;AACH,GAAA,EACF,GACF,CAAA,EACF,CAAA;AAEJ;;;;"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { jsx } from 'react/jsx-runtime';
|
|
2
|
+
import { useState, useCallback, useMemo } from 'react';
|
|
3
|
+
import { createVersionedContext, createVersionedValueMap } from '@backstage/version-bridge';
|
|
4
|
+
|
|
5
|
+
const BreadcrumbsContext = createVersionedContext(
|
|
6
|
+
"breadcrumbs-context"
|
|
7
|
+
);
|
|
8
|
+
function BreadcrumbsRegistryProvider(props) {
|
|
9
|
+
const [entries, setEntries] = useState([]);
|
|
10
|
+
const register = useCallback((entry) => {
|
|
11
|
+
const record = { ...entry };
|
|
12
|
+
setEntries((prev) => [...prev, record].sort((a, b) => a.depth - b.depth));
|
|
13
|
+
return {
|
|
14
|
+
update(label, href) {
|
|
15
|
+
if (record.label === label && record.href === href) return;
|
|
16
|
+
record.label = label;
|
|
17
|
+
record.href = href;
|
|
18
|
+
setEntries((prev) => [...prev]);
|
|
19
|
+
},
|
|
20
|
+
unregister() {
|
|
21
|
+
setEntries((prev) => prev.filter((e) => e !== record));
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
}, []);
|
|
25
|
+
const breadcrumbs = useMemo(
|
|
26
|
+
() => ({
|
|
27
|
+
items: entries.map(({ label, href, depth }) => ({ label, href, depth }))
|
|
28
|
+
}),
|
|
29
|
+
[entries]
|
|
30
|
+
);
|
|
31
|
+
const value = useMemo(
|
|
32
|
+
() => createVersionedValueMap({ 1: { breadcrumbs, register } }),
|
|
33
|
+
[breadcrumbs, register]
|
|
34
|
+
);
|
|
35
|
+
return /* @__PURE__ */ jsx(BreadcrumbsContext.Provider, { value, children: props.children });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export { BreadcrumbsRegistryProvider };
|
|
39
|
+
//# sourceMappingURL=BreadcrumbsRegistryProvider.esm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"BreadcrumbsRegistryProvider.esm.js","sources":["../../src/extensions/BreadcrumbsRegistryProvider.tsx"],"sourcesContent":["/*\n * Copyright 2026 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { useCallback, useMemo, useState, type ReactNode } from 'react';\nimport type { BreadcrumbEntryData } from '@backstage/frontend-plugin-api';\nimport {\n createVersionedContext,\n createVersionedValueMap,\n} from '@backstage/version-bridge';\n\ninterface Registration {\n update(label: string, href: string): void;\n unregister(): void;\n}\n\ninterface BreadcrumbsContextValue {\n breadcrumbs: { items: BreadcrumbEntryData[] };\n register: (entry: BreadcrumbEntryData) => Registration;\n}\n\ntype ContextMap = { 1: BreadcrumbsContextValue };\n\nconst BreadcrumbsContext = createVersionedContext<ContextMap>(\n 'breadcrumbs-context',\n);\n\n/**\n * Provides the breadcrumb registry to the component tree. This is the\n * provider side of the breadcrumbs system; the consumer side\n * ({@link @backstage/frontend-plugin-api#BreadcrumbEntry},\n * {@link @backstage/frontend-plugin-api#useBreadcrumbEntries}) lives in\n * `@backstage/frontend-plugin-api` and reads from the same versioned\n * context key (`'breadcrumbs-context'`).\n *\n * @internal\n */\nexport function BreadcrumbsRegistryProvider(props: { children: ReactNode }) {\n const [entries, setEntries] = useState<BreadcrumbEntryData[]>([]);\n\n const register = useCallback((entry: BreadcrumbEntryData): Registration => {\n const record = { ...entry };\n setEntries(prev => [...prev, record].sort((a, b) => a.depth - b.depth));\n return {\n update(label: string, href: string) {\n if (record.label === label && record.href === href) return;\n record.label = label;\n record.href = href;\n setEntries(prev => [...prev]);\n },\n unregister() {\n setEntries(prev => prev.filter(e => e !== record));\n },\n };\n }, []);\n\n const breadcrumbs = useMemo(\n () => ({\n items: entries.map(({ label, href, depth }) => ({ label, href, depth })),\n }),\n [entries],\n );\n\n const value = useMemo(\n () => createVersionedValueMap({ 1: { breadcrumbs, register } }),\n [breadcrumbs, register],\n );\n\n return (\n <BreadcrumbsContext.Provider value={value}>\n {props.children}\n </BreadcrumbsContext.Provider>\n );\n}\n"],"names":[],"mappings":";;;;AAmCA,MAAM,kBAAA,GAAqB,sBAAA;AAAA,EACzB;AACF,CAAA;AAYO,SAAS,4BAA4B,KAAA,EAAgC;AAC1E,EAAA,MAAM,CAAC,OAAA,EAAS,UAAU,CAAA,GAAI,QAAA,CAAgC,EAAE,CAAA;AAEhE,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,CAAC,KAAA,KAA6C;AACzE,IAAA,MAAM,MAAA,GAAS,EAAE,GAAG,KAAA,EAAM;AAC1B,IAAA,UAAA,CAAW,CAAA,IAAA,KAAQ,CAAC,GAAG,IAAA,EAAM,MAAM,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,EAAG,CAAA,KAAM,CAAA,CAAE,KAAA,GAAQ,CAAA,CAAE,KAAK,CAAC,CAAA;AACtE,IAAA,OAAO;AAAA,MACL,MAAA,CAAO,OAAe,IAAA,EAAc;AAClC,QAAA,IAAI,MAAA,CAAO,KAAA,KAAU,KAAA,IAAS,MAAA,CAAO,SAAS,IAAA,EAAM;AACpD,QAAA,MAAA,CAAO,KAAA,GAAQ,KAAA;AACf,QAAA,MAAA,CAAO,IAAA,GAAO,IAAA;AACd,QAAA,UAAA,CAAW,CAAA,IAAA,KAAQ,CAAC,GAAG,IAAI,CAAC,CAAA;AAAA,MAC9B,CAAA;AAAA,MACA,UAAA,GAAa;AACX,QAAA,UAAA,CAAW,UAAQ,IAAA,CAAK,MAAA,CAAO,CAAA,CAAA,KAAK,CAAA,KAAM,MAAM,CAAC,CAAA;AAAA,MACnD;AAAA,KACF;AAAA,EACF,CAAA,EAAG,EAAE,CAAA;AAEL,EAAA,MAAM,WAAA,GAAc,OAAA;AAAA,IAClB,OAAO;AAAA,MACL,KAAA,EAAO,OAAA,CAAQ,GAAA,CAAI,CAAC,EAAE,KAAA,EAAO,IAAA,EAAM,KAAA,EAAM,MAAO,EAAE,KAAA,EAAO,IAAA,EAAM,OAAM,CAAE;AAAA,KACzE,CAAA;AAAA,IACA,CAAC,OAAO;AAAA,GACV;AAEA,EAAA,MAAM,KAAA,GAAQ,OAAA;AAAA,IACZ,MAAM,wBAAwB,EAAE,CAAA,EAAG,EAAE,WAAA,EAAa,QAAA,IAAY,CAAA;AAAA,IAC9D,CAAC,aAAa,QAAQ;AAAA,GACxB;AAEA,EAAA,2BACG,kBAAA,CAAmB,QAAA,EAAnB,EAA4B,KAAA,EAC1B,gBAAM,QAAA,EACT,CAAA;AAEJ;;;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
|
-
import { Progress as Progress$2, NotFoundErrorPage as NotFoundErrorPage$1, ErrorDisplay as ErrorDisplay$1, PageLayout as PageLayout$1 } from '@backstage/frontend-plugin-api';
|
|
2
|
+
import { Progress as Progress$2, NotFoundErrorPage as NotFoundErrorPage$1, ErrorDisplay as ErrorDisplay$1, PageLayout as PageLayout$1, useBreadcrumbEntries, BreadcrumbEntry } from '@backstage/frontend-plugin-api';
|
|
3
3
|
import { SwappableComponentBlueprint } from '@backstage/plugin-app-react';
|
|
4
4
|
import { Progress as Progress$1, ErrorPage, ErrorPanel } from '@backstage/core-components';
|
|
5
5
|
import { PluginHeader } from '@backstage/ui';
|
|
@@ -55,22 +55,34 @@ const PageLayout = SwappableComponentBlueprint.make({
|
|
|
55
55
|
})),
|
|
56
56
|
[tabs, parentPath]
|
|
57
57
|
);
|
|
58
|
+
const { items: breadcrumbs } = useBreadcrumbEntries();
|
|
58
59
|
if (noHeader) {
|
|
59
60
|
return /* @__PURE__ */ jsx(Fragment, { children });
|
|
60
61
|
}
|
|
61
|
-
|
|
62
|
+
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
62
63
|
/* @__PURE__ */ jsx(
|
|
63
64
|
PluginHeader,
|
|
64
65
|
{
|
|
65
66
|
title,
|
|
66
67
|
icon,
|
|
67
68
|
titleLink,
|
|
69
|
+
breadcrumbs,
|
|
68
70
|
tabs: resolvedTabs,
|
|
69
71
|
customActions: headerActions
|
|
70
72
|
}
|
|
71
73
|
),
|
|
72
74
|
children
|
|
73
75
|
] });
|
|
76
|
+
if (!title) {
|
|
77
|
+
return content;
|
|
78
|
+
}
|
|
79
|
+
return /* @__PURE__ */ jsx(
|
|
80
|
+
BreadcrumbEntry,
|
|
81
|
+
{
|
|
82
|
+
entry: { label: title, href: titleLink ?? (parentPath || "/") },
|
|
83
|
+
children: content
|
|
84
|
+
}
|
|
85
|
+
);
|
|
74
86
|
}
|
|
75
87
|
})
|
|
76
88
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"components.esm.js","sources":["../../src/extensions/components.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n NotFoundErrorPage as SwappableNotFoundErrorPage,\n Progress as SwappableProgress,\n ErrorDisplay as SwappableErrorDisplay,\n PageLayout as SwappablePageLayout,\n type PageLayoutProps,\n} from '@backstage/frontend-plugin-api';\nimport { SwappableComponentBlueprint } from '@backstage/plugin-app-react';\nimport {\n ErrorPage,\n ErrorPanel,\n Progress as ProgressComponent,\n} from '@backstage/core-components';\nimport { PluginHeader } from '@backstage/ui';\nimport Button from '@material-ui/core/Button';\nimport { useMemo } from 'react';\nimport { useResolvedPath } from 'react-router-dom';\n\nexport const Progress = SwappableComponentBlueprint.make({\n name: 'core-progress',\n params: define =>\n define({\n component: SwappableProgress,\n loader: () => ProgressComponent,\n }),\n});\n\nexport const NotFoundErrorPage = SwappableComponentBlueprint.make({\n name: 'core-not-found-error-page',\n params: define =>\n define({\n component: SwappableNotFoundErrorPage,\n loader: () => () =>\n <ErrorPage status=\"404\" statusMessage=\"PAGE NOT FOUND\" />,\n }),\n});\n\nexport const ErrorDisplay = SwappableComponentBlueprint.make({\n name: 'core-error-display',\n params: define =>\n define({\n component: SwappableErrorDisplay,\n loader: () => props => {\n const { plugin, error, resetError } = props;\n const title = `Error in ${plugin?.id}`;\n return (\n <ErrorPanel title={title} error={error} defaultExpanded>\n <Button variant=\"outlined\" onClick={resetError}>\n Retry\n </Button>\n </ErrorPanel>\n );\n },\n }),\n});\n\nexport const PageLayout = SwappableComponentBlueprint.make({\n name: 'core-page-layout',\n params: define =>\n define({\n component: SwappablePageLayout,\n loader: () => (props: PageLayoutProps) => {\n const {\n title,\n icon,\n noHeader,\n titleLink,\n headerActions,\n tabs,\n children,\n } = props;\n // TODO(Rugvip): Different solution to this path handling would be good\n const parentPath = useResolvedPath('.').pathname.replace(/\\/$/, '');\n const resolvedTabs = useMemo(\n () =>\n tabs?.map(tab => ({\n ...tab,\n href: tab.href.startsWith('/')\n ? tab.href\n : `${parentPath}/${tab.href}`.replace(/\\/{2,}/g, '/'),\n matchStrategy: 'prefix' as const,\n })),\n [tabs, parentPath],\n );\n\n if (noHeader) {\n return <>{children}</>;\n }\n\n
|
|
1
|
+
{"version":3,"file":"components.esm.js","sources":["../../src/extensions/components.tsx"],"sourcesContent":["/*\n * Copyright 2025 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport {\n NotFoundErrorPage as SwappableNotFoundErrorPage,\n Progress as SwappableProgress,\n ErrorDisplay as SwappableErrorDisplay,\n PageLayout as SwappablePageLayout,\n type PageLayoutProps,\n} from '@backstage/frontend-plugin-api';\nimport { SwappableComponentBlueprint } from '@backstage/plugin-app-react';\nimport {\n ErrorPage,\n ErrorPanel,\n Progress as ProgressComponent,\n} from '@backstage/core-components';\nimport {\n BreadcrumbEntry,\n useBreadcrumbEntries,\n} from '@backstage/frontend-plugin-api';\nimport { PluginHeader } from '@backstage/ui';\nimport Button from '@material-ui/core/Button';\nimport { useMemo } from 'react';\nimport { useResolvedPath } from 'react-router-dom';\n\nexport const Progress = SwappableComponentBlueprint.make({\n name: 'core-progress',\n params: define =>\n define({\n component: SwappableProgress,\n loader: () => ProgressComponent,\n }),\n});\n\nexport const NotFoundErrorPage = SwappableComponentBlueprint.make({\n name: 'core-not-found-error-page',\n params: define =>\n define({\n component: SwappableNotFoundErrorPage,\n loader: () => () =>\n <ErrorPage status=\"404\" statusMessage=\"PAGE NOT FOUND\" />,\n }),\n});\n\nexport const ErrorDisplay = SwappableComponentBlueprint.make({\n name: 'core-error-display',\n params: define =>\n define({\n component: SwappableErrorDisplay,\n loader: () => props => {\n const { plugin, error, resetError } = props;\n const title = `Error in ${plugin?.id}`;\n return (\n <ErrorPanel title={title} error={error} defaultExpanded>\n <Button variant=\"outlined\" onClick={resetError}>\n Retry\n </Button>\n </ErrorPanel>\n );\n },\n }),\n});\n\nexport const PageLayout = SwappableComponentBlueprint.make({\n name: 'core-page-layout',\n params: define =>\n define({\n component: SwappablePageLayout,\n loader: () => (props: PageLayoutProps) => {\n const {\n title,\n icon,\n noHeader,\n titleLink,\n headerActions,\n tabs,\n children,\n } = props;\n // TODO(Rugvip): Different solution to this path handling would be good\n const parentPath = useResolvedPath('.').pathname.replace(/\\/$/, '');\n const resolvedTabs = useMemo(\n () =>\n tabs?.map(tab => ({\n ...tab,\n href: tab.href.startsWith('/')\n ? tab.href\n : `${parentPath}/${tab.href}`.replace(/\\/{2,}/g, '/'),\n matchStrategy: 'prefix' as const,\n })),\n [tabs, parentPath],\n );\n\n const { items: breadcrumbs } = useBreadcrumbEntries();\n\n if (noHeader) {\n return <>{children}</>;\n }\n\n const content = (\n <>\n <PluginHeader\n title={title}\n icon={icon}\n titleLink={titleLink}\n breadcrumbs={breadcrumbs}\n tabs={resolvedTabs}\n customActions={headerActions}\n />\n {children}\n </>\n );\n\n // in practice title is always provided by PageBlueprint (falls back to pluginId).\n if (!title) {\n return content;\n }\n\n return (\n <BreadcrumbEntry\n entry={{ label: title, href: titleLink ?? (parentPath || '/') }}\n >\n {content}\n </BreadcrumbEntry>\n );\n },\n }),\n});\n"],"names":["SwappableProgress","ProgressComponent","SwappableNotFoundErrorPage","SwappableErrorDisplay","SwappablePageLayout"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-app",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1-next.1",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "frontend-plugin",
|
|
6
6
|
"pluginId": "app",
|
|
@@ -63,17 +63,17 @@
|
|
|
63
63
|
"test": "backstage-cli package test"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@backstage/core-components": "
|
|
67
|
-
"@backstage/core-plugin-api": "
|
|
68
|
-
"@backstage/filter-predicates": "
|
|
69
|
-
"@backstage/frontend-plugin-api": "
|
|
70
|
-
"@backstage/integration-react": "
|
|
71
|
-
"@backstage/plugin-app-react": "
|
|
72
|
-
"@backstage/plugin-permission-react": "
|
|
73
|
-
"@backstage/theme": "
|
|
74
|
-
"@backstage/types": "
|
|
75
|
-
"@backstage/ui": "
|
|
76
|
-
"@backstage/version-bridge": "
|
|
66
|
+
"@backstage/core-components": "0.18.12-next.0",
|
|
67
|
+
"@backstage/core-plugin-api": "1.12.8-next.0",
|
|
68
|
+
"@backstage/filter-predicates": "0.1.4-next.0",
|
|
69
|
+
"@backstage/frontend-plugin-api": "0.17.3-next.1",
|
|
70
|
+
"@backstage/integration-react": "1.2.20-next.0",
|
|
71
|
+
"@backstage/plugin-app-react": "0.2.5-next.0",
|
|
72
|
+
"@backstage/plugin-permission-react": "0.5.3-next.0",
|
|
73
|
+
"@backstage/theme": "0.7.3",
|
|
74
|
+
"@backstage/types": "1.2.2",
|
|
75
|
+
"@backstage/ui": "0.17.0-next.1",
|
|
76
|
+
"@backstage/version-bridge": "1.0.12",
|
|
77
77
|
"@material-ui/core": "^4.9.13",
|
|
78
78
|
"@material-ui/icons": "^4.9.1",
|
|
79
79
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
|
@@ -87,11 +87,11 @@
|
|
|
87
87
|
"zod": "^4.0.0"
|
|
88
88
|
},
|
|
89
89
|
"devDependencies": {
|
|
90
|
-
"@backstage/cli": "
|
|
91
|
-
"@backstage/dev-utils": "
|
|
92
|
-
"@backstage/frontend-defaults": "
|
|
93
|
-
"@backstage/frontend-test-utils": "
|
|
94
|
-
"@backstage/test-utils": "
|
|
90
|
+
"@backstage/cli": "0.36.4-next.1",
|
|
91
|
+
"@backstage/dev-utils": "1.1.25-next.0",
|
|
92
|
+
"@backstage/frontend-defaults": "0.5.4-next.0",
|
|
93
|
+
"@backstage/frontend-test-utils": "0.6.2-next.0",
|
|
94
|
+
"@backstage/test-utils": "1.7.20-next.0",
|
|
95
95
|
"@testing-library/jest-dom": "^6.0.0",
|
|
96
96
|
"@testing-library/react": "^16.0.0",
|
|
97
97
|
"@testing-library/user-event": "^14.0.0",
|