@backstage/plugin-app 0.4.0-next.0 → 0.4.0-next.2
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 +25 -0
- package/dist/extensions/AppRoot.esm.js +11 -1
- package/dist/extensions/AppRoot.esm.js.map +1 -1
- package/dist/extensions/AppRoutes.esm.js +31 -3
- package/dist/extensions/AppRoutes.esm.js.map +1 -1
- package/dist/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.esm.js +24 -3
- package/dist/packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.esm.js.map +1 -1
- package/dist/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.esm.js +24 -3
- package/dist/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.esm.js.map +1 -1
- package/dist/plugins/app/package.json.esm.js +3 -3
- package/package.json +15 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# @backstage/plugin-app
|
|
2
2
|
|
|
3
|
+
## 0.4.0-next.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a7e0d50: Prepare for React Router v7 migration by updating to v6.30.2 across all NFS packages and enabling v7 future flags. Convert routes from splat paths to parent/child structure with Outlet components.
|
|
8
|
+
- Updated dependencies
|
|
9
|
+
- @backstage/frontend-plugin-api@0.14.0-next.2
|
|
10
|
+
- @backstage/integration-react@1.2.15-next.2
|
|
11
|
+
- @backstage/core-components@0.18.7-next.2
|
|
12
|
+
- @backstage/core-plugin-api@1.12.3-next.1
|
|
13
|
+
- @backstage/plugin-permission-react@0.4.40-next.1
|
|
14
|
+
- @backstage/version-bridge@1.0.12-next.0
|
|
15
|
+
- @backstage/theme@0.7.2-next.1
|
|
16
|
+
- @backstage/plugin-app-react@0.1.1-next.0
|
|
17
|
+
|
|
18
|
+
## 0.4.0-next.1
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- Updated dependencies
|
|
23
|
+
- @backstage/theme@0.7.2-next.0
|
|
24
|
+
- @backstage/frontend-plugin-api@0.14.0-next.1
|
|
25
|
+
- @backstage/core-components@0.18.7-next.1
|
|
26
|
+
- @backstage/integration-react@1.2.15-next.1
|
|
27
|
+
|
|
3
28
|
## 0.4.0-next.0
|
|
4
29
|
|
|
5
30
|
### Minor Changes
|
|
@@ -110,7 +110,17 @@ function toAppIdentityProxy(identityApi) {
|
|
|
110
110
|
function DefaultRouter(props) {
|
|
111
111
|
const configApi = useApi(configApiRef);
|
|
112
112
|
const basePath = getBasePath(configApi);
|
|
113
|
-
return /* @__PURE__ */ jsx(
|
|
113
|
+
return /* @__PURE__ */ jsx(
|
|
114
|
+
BrowserRouter,
|
|
115
|
+
{
|
|
116
|
+
basename: basePath,
|
|
117
|
+
future: {
|
|
118
|
+
v7_relativeSplatPath: true,
|
|
119
|
+
v7_startTransition: true
|
|
120
|
+
},
|
|
121
|
+
children: props.children
|
|
122
|
+
}
|
|
123
|
+
);
|
|
114
124
|
}
|
|
115
125
|
function AppRouter(props) {
|
|
116
126
|
const {
|
|
@@ -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 coreExtensionData,\n discoveryApiRef,\n fetchApiRef,\n errorApiRef,\n createExtension,\n createExtensionInput,\n routeResolutionApiRef,\n} from '@backstage/frontend-plugin-api';\nimport {\n AppRootWrapperBlueprint,\n RouterBlueprint,\n SignInPageBlueprint,\n} from '@backstage/plugin-app-react';\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 }),\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 }) {\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: ReactNode = inputs.children.get(\n coreExtensionData.reactElement,\n );\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 return [\n coreExtensionData.reactElement(\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 ),\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 <BrowserRouter basename={basePath}>{props.children}</BrowserRouter>;\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 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 return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n {children}\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {children}\n </SignInPageWrapper>\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 coreExtensionData,\n discoveryApiRef,\n fetchApiRef,\n errorApiRef,\n createExtension,\n createExtensionInput,\n routeResolutionApiRef,\n} from '@backstage/frontend-plugin-api';\nimport {\n AppRootWrapperBlueprint,\n RouterBlueprint,\n SignInPageBlueprint,\n} from '@backstage/plugin-app-react';\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 }),\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 }) {\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: ReactNode = inputs.children.get(\n coreExtensionData.reactElement,\n );\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 return [\n coreExtensionData.reactElement(\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 ),\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: true,\n v7_startTransition: true,\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 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 return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n {children}\n </RouterComponent>\n );\n }\n\n return (\n <RouterComponent>\n {...extraElements}\n <RouteTracker routeObjects={routeObjects} />\n <SignInPageWrapper\n component={SignInPageComponent}\n appIdentityProxy={appIdentityProxy}\n >\n {children}\n </SignInPageWrapper>\n </RouterComponent>\n );\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx } from 'react/jsx-runtime';
|
|
2
2
|
import { createExtension, coreExtensionData, createExtensionInput, NotFoundErrorPage } from '@backstage/frontend-plugin-api';
|
|
3
|
-
import { useRoutes } from 'react-router-dom';
|
|
3
|
+
import { useRoutes, Outlet } from 'react-router-dom';
|
|
4
4
|
|
|
5
5
|
const AppRoutes = createExtension({
|
|
6
6
|
name: "routes",
|
|
@@ -18,9 +18,37 @@ const AppRoutes = createExtension({
|
|
|
18
18
|
const element = useRoutes([
|
|
19
19
|
...inputs.routes.map((route) => {
|
|
20
20
|
const routePath = route.get(coreExtensionData.routePath);
|
|
21
|
+
const routeElement = route.get(coreExtensionData.reactElement);
|
|
22
|
+
if (routePath === "/") {
|
|
23
|
+
return {
|
|
24
|
+
path: "/",
|
|
25
|
+
element: /* @__PURE__ */ jsx(Outlet, {}),
|
|
26
|
+
children: [
|
|
27
|
+
{
|
|
28
|
+
index: true,
|
|
29
|
+
element: routeElement
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: "*",
|
|
33
|
+
element: routeElement
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
const normalizedPath = routePath.replace(/\/$/, "");
|
|
21
39
|
return {
|
|
22
|
-
path:
|
|
23
|
-
element:
|
|
40
|
+
path: normalizedPath,
|
|
41
|
+
element: /* @__PURE__ */ jsx(Outlet, {}),
|
|
42
|
+
children: [
|
|
43
|
+
{
|
|
44
|
+
index: true,
|
|
45
|
+
element: routeElement
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
path: "*",
|
|
49
|
+
element: routeElement
|
|
50
|
+
}
|
|
51
|
+
]
|
|
24
52
|
};
|
|
25
53
|
}),
|
|
26
54
|
{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { useRoutes } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n const Routes = () => {\n const element = useRoutes([\n ...inputs.routes.map(route => {\n const routePath = route.get(coreExtensionData.routePath);\n\n
|
|
1
|
+
{"version":3,"file":"AppRoutes.esm.js","sources":["../../src/extensions/AppRoutes.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 createExtension,\n coreExtensionData,\n createExtensionInput,\n NotFoundErrorPage,\n} from '@backstage/frontend-plugin-api';\nimport { useRoutes, Outlet } from 'react-router-dom';\n\nexport const AppRoutes = createExtension({\n name: 'routes',\n attachTo: { id: 'app/layout', input: 'content' },\n inputs: {\n routes: createExtensionInput([\n coreExtensionData.routePath,\n coreExtensionData.routeRef.optional(),\n coreExtensionData.reactElement,\n ]),\n },\n output: [coreExtensionData.reactElement],\n factory({ inputs }) {\n const Routes = () => {\n const element = useRoutes([\n ...inputs.routes.map(route => {\n const routePath = route.get(coreExtensionData.routePath);\n const routeElement = route.get(coreExtensionData.reactElement);\n\n // For v7_relativeSplatPath: convert splat paths to parent/child structure\n if (routePath === '/') {\n // Root route: parent with index and splat children\n return {\n path: '/',\n element: <Outlet />,\n children: [\n {\n index: true,\n element: routeElement,\n },\n {\n path: '*',\n element: routeElement,\n },\n ],\n };\n }\n\n // Non-root routes: parent route with splat child\n const normalizedPath = routePath.replace(/\\/$/, '');\n return {\n path: normalizedPath,\n element: <Outlet />,\n children: [\n {\n index: true,\n element: routeElement,\n },\n {\n path: '*',\n element: routeElement,\n },\n ],\n };\n }),\n {\n path: '*',\n element: <NotFoundErrorPage />,\n },\n ]);\n\n return element;\n };\n\n return [coreExtensionData.reactElement(<Routes />)];\n },\n});\n"],"names":[],"mappings":";;;;AAwBO,MAAM,YAAY,eAAA,CAAgB;AAAA,EACvC,IAAA,EAAM,QAAA;AAAA,EACN,QAAA,EAAU,EAAE,EAAA,EAAI,YAAA,EAAc,OAAO,SAAA,EAAU;AAAA,EAC/C,MAAA,EAAQ;AAAA,IACN,QAAQ,oBAAA,CAAqB;AAAA,MAC3B,iBAAA,CAAkB,SAAA;AAAA,MAClB,iBAAA,CAAkB,SAAS,QAAA,EAAS;AAAA,MACpC,iBAAA,CAAkB;AAAA,KACnB;AAAA,GACH;AAAA,EACA,MAAA,EAAQ,CAAC,iBAAA,CAAkB,YAAY,CAAA;AAAA,EACvC,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AAClB,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,MAAM,UAAU,SAAA,CAAU;AAAA,QACxB,GAAG,MAAA,CAAO,MAAA,CAAO,GAAA,CAAI,CAAA,KAAA,KAAS;AAC5B,UAAA,MAAM,SAAA,GAAY,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,SAAS,CAAA;AACvD,UAAA,MAAM,YAAA,GAAe,KAAA,CAAM,GAAA,CAAI,iBAAA,CAAkB,YAAY,CAAA;AAG7D,UAAA,IAAI,cAAc,GAAA,EAAK;AAErB,YAAA,OAAO;AAAA,cACL,IAAA,EAAM,GAAA;AAAA,cACN,OAAA,sBAAU,MAAA,EAAA,EAAO,CAAA;AAAA,cACjB,QAAA,EAAU;AAAA,gBACR;AAAA,kBACE,KAAA,EAAO,IAAA;AAAA,kBACP,OAAA,EAAS;AAAA,iBACX;AAAA,gBACA;AAAA,kBACE,IAAA,EAAM,GAAA;AAAA,kBACN,OAAA,EAAS;AAAA;AACX;AACF,aACF;AAAA,UACF;AAGA,UAAA,MAAM,cAAA,GAAiB,SAAA,CAAU,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AAClD,UAAA,OAAO;AAAA,YACL,IAAA,EAAM,cAAA;AAAA,YACN,OAAA,sBAAU,MAAA,EAAA,EAAO,CAAA;AAAA,YACjB,QAAA,EAAU;AAAA,cACR;AAAA,gBACE,KAAA,EAAO,IAAA;AAAA,gBACP,OAAA,EAAS;AAAA,eACX;AAAA,cACA;AAAA,gBACE,IAAA,EAAM,GAAA;AAAA,gBACN,OAAA,EAAS;AAAA;AACX;AACF,WACF;AAAA,QACF,CAAC,CAAA;AAAA,QACD;AAAA,UACE,IAAA,EAAM,GAAA;AAAA,UACN,OAAA,sBAAU,iBAAA,EAAA,EAAkB;AAAA;AAC9B,OACD,CAAA;AAED,MAAA,OAAO,OAAA;AAAA,IACT,CAAA;AAEA,IAAA,OAAO,CAAC,iBAAA,CAAkB,YAAA,iBAAa,GAAA,CAAC,MAAA,EAAA,EAAO,CAAE,CAAC,CAAA;AAAA,EACpD;AACF,CAAC;;;;"}
|
|
@@ -31,24 +31,30 @@ class AppLanguageSelector {
|
|
|
31
31
|
if (storedLanguage && languages.includes(storedLanguage)) {
|
|
32
32
|
selector.setLanguage(storedLanguage);
|
|
33
33
|
}
|
|
34
|
-
selector.language$().subscribe(({ language }) => {
|
|
34
|
+
const subscription = selector.language$().subscribe(({ language }) => {
|
|
35
35
|
if (language !== window.localStorage.getItem(STORAGE_KEY)) {
|
|
36
36
|
window.localStorage.setItem(STORAGE_KEY, language);
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
|
-
|
|
39
|
+
const storageListener = (event) => {
|
|
40
40
|
if (event.key === STORAGE_KEY) {
|
|
41
41
|
const language = localStorage.getItem(STORAGE_KEY) ?? void 0;
|
|
42
42
|
if (language) {
|
|
43
43
|
selector.setLanguage(language);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
|
-
}
|
|
46
|
+
};
|
|
47
|
+
window.addEventListener("storage", storageListener);
|
|
48
|
+
selector.#storageSubscription = subscription;
|
|
49
|
+
selector.#storageListener = storageListener;
|
|
47
50
|
return selector;
|
|
48
51
|
}
|
|
49
52
|
#languages;
|
|
50
53
|
#language;
|
|
51
54
|
#subject;
|
|
55
|
+
// References for cleanup when using createWithStorage
|
|
56
|
+
#storageSubscription;
|
|
57
|
+
#storageListener;
|
|
52
58
|
constructor(languages, initialLanguage) {
|
|
53
59
|
this.#languages = languages;
|
|
54
60
|
this.#language = initialLanguage;
|
|
@@ -80,6 +86,21 @@ class AppLanguageSelector {
|
|
|
80
86
|
language$() {
|
|
81
87
|
return this.#subject;
|
|
82
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Cleans up resources created by createWithStorage().
|
|
91
|
+
* Call this method when the selector is no longer needed to prevent memory leaks.
|
|
92
|
+
* This is particularly useful in testing scenarios or when the app is unmounted.
|
|
93
|
+
*/
|
|
94
|
+
dispose() {
|
|
95
|
+
if (this.#storageSubscription) {
|
|
96
|
+
this.#storageSubscription.unsubscribe();
|
|
97
|
+
this.#storageSubscription = void 0;
|
|
98
|
+
}
|
|
99
|
+
if (this.#storageListener) {
|
|
100
|
+
window.removeEventListener("storage", this.#storageListener);
|
|
101
|
+
this.#storageListener = void 0;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
83
104
|
}
|
|
84
105
|
|
|
85
106
|
export { AppLanguageSelector, DEFAULT_LANGUAGE };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppLanguageSelector.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.ts"],"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\n// Internal import to avoid code duplication, this will lead to duplication in build output\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppLanguageApi } from '@backstage/core-plugin-api/alpha';\nimport { Observable } from '@backstage/types';\nimport { BehaviorSubject } from '../../../lib';\n\nconst STORAGE_KEY = 'language';\nexport const DEFAULT_LANGUAGE = 'en';\n\n/** @alpha */\nexport interface AppLanguageSelectorOptions {\n defaultLanguage?: string;\n availableLanguages?: string[];\n}\n\n/**\n * Exposes the available languages in the app and allows for switching of the active language.\n *\n * @alpha\n */\nexport class AppLanguageSelector implements AppLanguageApi {\n static create(options?: AppLanguageSelectorOptions) {\n const languages = options?.availableLanguages ?? [DEFAULT_LANGUAGE];\n if (languages.length !== new Set(languages).size) {\n throw new Error(\n `Supported languages may not contain duplicates, got '${languages.join(\n \"', '\",\n )}'`,\n );\n }\n\n const initialLanguage = options?.defaultLanguage ?? DEFAULT_LANGUAGE;\n\n if (!languages.includes(initialLanguage)) {\n throw new Error(\n `Initial language must be one of the supported languages, got '${initialLanguage}'`,\n );\n }\n\n return new AppLanguageSelector(languages, initialLanguage);\n }\n\n static createWithStorage(options?: AppLanguageSelectorOptions) {\n const selector = AppLanguageSelector.create(options);\n\n if (!window.localStorage) {\n return selector;\n }\n\n const storedLanguage = window.localStorage.getItem(STORAGE_KEY);\n const { languages } = selector.getAvailableLanguages();\n if (storedLanguage && languages.includes(storedLanguage)) {\n selector.setLanguage(storedLanguage);\n }\n\n selector.language$().subscribe(({ language }) => {\n if (language !== window.localStorage.getItem(STORAGE_KEY)) {\n window.localStorage.setItem(STORAGE_KEY, language);\n }\n });\n\n
|
|
1
|
+
{"version":3,"file":"AppLanguageSelector.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/AppLanguageApi/AppLanguageSelector.ts"],"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\n// Internal import to avoid code duplication, this will lead to duplication in build output\n// eslint-disable-next-line @backstage/no-relative-monorepo-imports\nimport { AppLanguageApi } from '@backstage/core-plugin-api/alpha';\nimport { Observable } from '@backstage/types';\nimport { BehaviorSubject } from '../../../lib';\n\nconst STORAGE_KEY = 'language';\nexport const DEFAULT_LANGUAGE = 'en';\n\n/** @alpha */\nexport interface AppLanguageSelectorOptions {\n defaultLanguage?: string;\n availableLanguages?: string[];\n}\n\n/**\n * Exposes the available languages in the app and allows for switching of the active language.\n *\n * @alpha\n */\nexport class AppLanguageSelector implements AppLanguageApi {\n static create(options?: AppLanguageSelectorOptions) {\n const languages = options?.availableLanguages ?? [DEFAULT_LANGUAGE];\n if (languages.length !== new Set(languages).size) {\n throw new Error(\n `Supported languages may not contain duplicates, got '${languages.join(\n \"', '\",\n )}'`,\n );\n }\n\n const initialLanguage = options?.defaultLanguage ?? DEFAULT_LANGUAGE;\n\n if (!languages.includes(initialLanguage)) {\n throw new Error(\n `Initial language must be one of the supported languages, got '${initialLanguage}'`,\n );\n }\n\n return new AppLanguageSelector(languages, initialLanguage);\n }\n\n static createWithStorage(options?: AppLanguageSelectorOptions) {\n const selector = AppLanguageSelector.create(options);\n\n if (!window.localStorage) {\n return selector;\n }\n\n const storedLanguage = window.localStorage.getItem(STORAGE_KEY);\n const { languages } = selector.getAvailableLanguages();\n if (storedLanguage && languages.includes(storedLanguage)) {\n selector.setLanguage(storedLanguage);\n }\n\n const subscription = selector.language$().subscribe(({ language }) => {\n if (language !== window.localStorage.getItem(STORAGE_KEY)) {\n window.localStorage.setItem(STORAGE_KEY, language);\n }\n });\n\n const storageListener = (event: StorageEvent) => {\n if (event.key === STORAGE_KEY) {\n const language = localStorage.getItem(STORAGE_KEY) ?? undefined;\n if (language) {\n selector.setLanguage(language);\n }\n }\n };\n window.addEventListener('storage', storageListener);\n\n // Store cleanup references for potential disposal\n selector.#storageSubscription = subscription;\n selector.#storageListener = storageListener;\n\n return selector;\n }\n\n #languages: string[];\n #language: string;\n #subject: BehaviorSubject<{ language: string }>;\n\n // References for cleanup when using createWithStorage\n #storageSubscription?: { unsubscribe(): void };\n #storageListener?: (event: StorageEvent) => void;\n\n private constructor(languages: string[], initialLanguage: string) {\n this.#languages = languages;\n this.#language = initialLanguage;\n this.#subject = new BehaviorSubject<{ language: string }>({\n language: this.#language,\n });\n }\n\n getAvailableLanguages(): { languages: string[] } {\n return { languages: this.#languages.slice() };\n }\n\n setLanguage(language?: string | undefined): void {\n const lng = language ?? DEFAULT_LANGUAGE;\n if (lng === this.#language) {\n return;\n }\n if (lng && !this.#languages.includes(lng)) {\n throw new Error(\n `Failed to change language to '${lng}', available languages are '${this.#languages.join(\n \"', '\",\n )}'`,\n );\n }\n this.#language = lng;\n this.#subject.next({ language: lng });\n }\n\n getLanguage(): { language: string } {\n return { language: this.#language };\n }\n\n language$(): Observable<{ language: string }> {\n return this.#subject;\n }\n\n /**\n * Cleans up resources created by createWithStorage().\n * Call this method when the selector is no longer needed to prevent memory leaks.\n * This is particularly useful in testing scenarios or when the app is unmounted.\n */\n dispose(): void {\n if (this.#storageSubscription) {\n this.#storageSubscription.unsubscribe();\n this.#storageSubscription = undefined;\n }\n if (this.#storageListener) {\n window.removeEventListener('storage', this.#storageListener);\n this.#storageListener = undefined;\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.esm.js
CHANGED
|
@@ -12,23 +12,29 @@ class AppThemeSelector {
|
|
|
12
12
|
}
|
|
13
13
|
const initialThemeId = window.localStorage.getItem(STORAGE_KEY) ?? void 0;
|
|
14
14
|
selector.setActiveThemeId(initialThemeId);
|
|
15
|
-
selector.activeThemeId$().subscribe((themeId) => {
|
|
15
|
+
const subscription = selector.activeThemeId$().subscribe((themeId) => {
|
|
16
16
|
if (themeId) {
|
|
17
17
|
window.localStorage.setItem(STORAGE_KEY, themeId);
|
|
18
18
|
} else {
|
|
19
19
|
window.localStorage.removeItem(STORAGE_KEY);
|
|
20
20
|
}
|
|
21
21
|
});
|
|
22
|
-
|
|
22
|
+
const storageListener = (event) => {
|
|
23
23
|
if (event.key === STORAGE_KEY) {
|
|
24
24
|
const themeId = localStorage.getItem(STORAGE_KEY) ?? void 0;
|
|
25
25
|
selector.setActiveThemeId(themeId);
|
|
26
26
|
}
|
|
27
|
-
}
|
|
27
|
+
};
|
|
28
|
+
window.addEventListener("storage", storageListener);
|
|
29
|
+
selector.#storageSubscription = subscription;
|
|
30
|
+
selector.#storageListener = storageListener;
|
|
28
31
|
return selector;
|
|
29
32
|
}
|
|
30
33
|
activeThemeId;
|
|
31
34
|
subject = new BehaviorSubject(void 0);
|
|
35
|
+
// References for cleanup when using createWithStorage
|
|
36
|
+
#storageSubscription;
|
|
37
|
+
#storageListener;
|
|
32
38
|
getInstalledThemes() {
|
|
33
39
|
return this.themes.slice();
|
|
34
40
|
}
|
|
@@ -42,6 +48,21 @@ class AppThemeSelector {
|
|
|
42
48
|
this.activeThemeId = themeId;
|
|
43
49
|
this.subject.next(themeId);
|
|
44
50
|
}
|
|
51
|
+
/**
|
|
52
|
+
* Cleans up resources created by createWithStorage().
|
|
53
|
+
* Call this method when the selector is no longer needed to prevent memory leaks.
|
|
54
|
+
* This is particularly useful in testing scenarios or when the app is unmounted.
|
|
55
|
+
*/
|
|
56
|
+
dispose() {
|
|
57
|
+
if (this.#storageSubscription) {
|
|
58
|
+
this.#storageSubscription.unsubscribe();
|
|
59
|
+
this.#storageSubscription = void 0;
|
|
60
|
+
}
|
|
61
|
+
if (this.#storageListener) {
|
|
62
|
+
window.removeEventListener("storage", this.#storageListener);
|
|
63
|
+
this.#storageListener = void 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
45
66
|
}
|
|
46
67
|
|
|
47
68
|
export { AppThemeSelector };
|
package/dist/packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.esm.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppThemeSelector.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { AppThemeApi, AppTheme } from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\nimport { BehaviorSubject } from '../../../lib/subjects';\n\nconst STORAGE_KEY = 'theme';\n\n/**\n * Exposes the themes installed in the app, and permits switching the currently\n * active theme.\n *\n * @public\n */\nexport class AppThemeSelector implements AppThemeApi {\n static createWithStorage(themes: AppTheme[]) {\n const selector = new AppThemeSelector(themes);\n\n if (!window.localStorage) {\n return selector;\n }\n\n const initialThemeId =\n window.localStorage.getItem(STORAGE_KEY) ?? undefined;\n\n selector.setActiveThemeId(initialThemeId);\n\n selector.activeThemeId$().subscribe(themeId => {\n if (themeId) {\n window.localStorage.setItem(STORAGE_KEY, themeId);\n } else {\n window.localStorage.removeItem(STORAGE_KEY);\n }\n });\n\n
|
|
1
|
+
{"version":3,"file":"AppThemeSelector.esm.js","sources":["../../../../../../../../../packages/core-app-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts"],"sourcesContent":["/*\n * Copyright 2020 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 { AppThemeApi, AppTheme } from '@backstage/core-plugin-api';\nimport { Observable } from '@backstage/types';\nimport { BehaviorSubject } from '../../../lib/subjects';\n\nconst STORAGE_KEY = 'theme';\n\n/**\n * Exposes the themes installed in the app, and permits switching the currently\n * active theme.\n *\n * @public\n */\nexport class AppThemeSelector implements AppThemeApi {\n static createWithStorage(themes: AppTheme[]) {\n const selector = new AppThemeSelector(themes);\n\n if (!window.localStorage) {\n return selector;\n }\n\n const initialThemeId =\n window.localStorage.getItem(STORAGE_KEY) ?? undefined;\n\n selector.setActiveThemeId(initialThemeId);\n\n const subscription = selector.activeThemeId$().subscribe(themeId => {\n if (themeId) {\n window.localStorage.setItem(STORAGE_KEY, themeId);\n } else {\n window.localStorage.removeItem(STORAGE_KEY);\n }\n });\n\n const storageListener = (event: StorageEvent) => {\n if (event.key === STORAGE_KEY) {\n const themeId = localStorage.getItem(STORAGE_KEY) ?? undefined;\n selector.setActiveThemeId(themeId);\n }\n };\n window.addEventListener('storage', storageListener);\n\n // Store cleanup references for potential disposal\n selector.#storageSubscription = subscription;\n selector.#storageListener = storageListener;\n\n return selector;\n }\n\n private activeThemeId: string | undefined;\n private readonly subject = new BehaviorSubject<string | undefined>(undefined);\n\n // References for cleanup when using createWithStorage\n #storageSubscription?: { unsubscribe(): void };\n #storageListener?: (event: StorageEvent) => void;\n\n constructor(private readonly themes: AppTheme[]) {}\n\n getInstalledThemes(): AppTheme[] {\n return this.themes.slice();\n }\n\n activeThemeId$(): Observable<string | undefined> {\n return this.subject;\n }\n\n getActiveThemeId(): string | undefined {\n return this.activeThemeId;\n }\n\n setActiveThemeId(themeId?: string): void {\n this.activeThemeId = themeId;\n this.subject.next(themeId);\n }\n\n /**\n * Cleans up resources created by createWithStorage().\n * Call this method when the selector is no longer needed to prevent memory leaks.\n * This is particularly useful in testing scenarios or when the app is unmounted.\n */\n dispose(): void {\n if (this.#storageSubscription) {\n this.#storageSubscription.unsubscribe();\n this.#storageSubscription = undefined;\n }\n if (this.#storageListener) {\n window.removeEventListener('storage', this.#storageListener);\n this.#storageListener = undefined;\n }\n }\n}\n"],"names":[],"mappings":";;AAoBA,MAAM,WAAA,GAAc,OAAA;AAQb,MAAM,gBAAA,CAAwC;AAAA,EA2CnD,YAA6B,MAAA,EAAoB;AAApB,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAAqB;AAAA,EA1ClD,OAAO,kBAAkB,MAAA,EAAoB;AAC3C,IAAA,MAAM,QAAA,GAAW,IAAI,gBAAA,CAAiB,MAAM,CAAA;AAE5C,IAAA,IAAI,CAAC,OAAO,YAAA,EAAc;AACxB,MAAA,OAAO,QAAA;AAAA,IACT;AAEA,IAAA,MAAM,cAAA,GACJ,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,WAAW,CAAA,IAAK,MAAA;AAE9C,IAAA,QAAA,CAAS,iBAAiB,cAAc,CAAA;AAExC,IAAA,MAAM,YAAA,GAAe,QAAA,CAAS,cAAA,EAAe,CAAE,UAAU,CAAA,OAAA,KAAW;AAClE,MAAA,IAAI,OAAA,EAAS;AACX,QAAA,MAAA,CAAO,YAAA,CAAa,OAAA,CAAQ,WAAA,EAAa,OAAO,CAAA;AAAA,MAClD,CAAA,MAAO;AACL,QAAA,MAAA,CAAO,YAAA,CAAa,WAAW,WAAW,CAAA;AAAA,MAC5C;AAAA,IACF,CAAC,CAAA;AAED,IAAA,MAAM,eAAA,GAAkB,CAAC,KAAA,KAAwB;AAC/C,MAAA,IAAI,KAAA,CAAM,QAAQ,WAAA,EAAa;AAC7B,QAAA,MAAM,OAAA,GAAU,YAAA,CAAa,OAAA,CAAQ,WAAW,CAAA,IAAK,MAAA;AACrD,QAAA,QAAA,CAAS,iBAAiB,OAAO,CAAA;AAAA,MACnC;AAAA,IACF,CAAA;AACA,IAAA,MAAA,CAAO,gBAAA,CAAiB,WAAW,eAAe,CAAA;AAGlD,IAAA,QAAA,CAAS,oBAAA,GAAuB,YAAA;AAChC,IAAA,QAAA,CAAS,gBAAA,GAAmB,eAAA;AAE5B,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEQ,aAAA;AAAA,EACS,OAAA,GAAU,IAAI,eAAA,CAAoC,MAAS,CAAA;AAAA;AAAA,EAG5E,oBAAA;AAAA,EACA,gBAAA;AAAA,EAIA,kBAAA,GAAiC;AAC/B,IAAA,OAAO,IAAA,CAAK,OAAO,KAAA,EAAM;AAAA,EAC3B;AAAA,EAEA,cAAA,GAAiD;AAC/C,IAAA,OAAO,IAAA,CAAK,OAAA;AAAA,EACd;AAAA,EAEA,gBAAA,GAAuC;AACrC,IAAA,OAAO,IAAA,CAAK,aAAA;AAAA,EACd;AAAA,EAEA,iBAAiB,OAAA,EAAwB;AACvC,IAAA,IAAA,CAAK,aAAA,GAAgB,OAAA;AACrB,IAAA,IAAA,CAAK,OAAA,CAAQ,KAAK,OAAO,CAAA;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,OAAA,GAAgB;AACd,IAAA,IAAI,KAAK,oBAAA,EAAsB;AAC7B,MAAA,IAAA,CAAK,qBAAqB,WAAA,EAAY;AACtC,MAAA,IAAA,CAAK,oBAAA,GAAuB,MAAA;AAAA,IAC9B;AACA,IAAA,IAAI,KAAK,gBAAA,EAAkB;AACzB,MAAA,MAAA,CAAO,mBAAA,CAAoB,SAAA,EAAW,IAAA,CAAK,gBAAgB,CAAA;AAC3D,MAAA,IAAA,CAAK,gBAAA,GAAmB,MAAA;AAAA,IAC1B;AAAA,EACF;AACF;;;;"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
var name = "@backstage/plugin-app";
|
|
2
|
-
var version = "0.4.0-next.
|
|
2
|
+
var version = "0.4.0-next.2";
|
|
3
3
|
var backstage = {
|
|
4
4
|
role: "frontend-plugin",
|
|
5
5
|
pluginId: "app",
|
|
@@ -79,13 +79,13 @@ var devDependencies = {
|
|
|
79
79
|
msw: "^1.0.0",
|
|
80
80
|
react: "^18.0.2",
|
|
81
81
|
"react-dom": "^18.0.2",
|
|
82
|
-
"react-router-dom": "^6.
|
|
82
|
+
"react-router-dom": "^6.30.2"
|
|
83
83
|
};
|
|
84
84
|
var peerDependencies = {
|
|
85
85
|
"@types/react": "^17.0.0 || ^18.0.0",
|
|
86
86
|
react: "^17.0.0 || ^18.0.0",
|
|
87
87
|
"react-dom": "^17.0.0 || ^18.0.0",
|
|
88
|
-
"react-router-dom": "^6.
|
|
88
|
+
"react-router-dom": "^6.30.2"
|
|
89
89
|
};
|
|
90
90
|
var peerDependenciesMeta = {
|
|
91
91
|
"@types/react": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/plugin-app",
|
|
3
|
-
"version": "0.4.0-next.
|
|
3
|
+
"version": "0.4.0-next.2",
|
|
4
4
|
"backstage": {
|
|
5
5
|
"role": "frontend-plugin",
|
|
6
6
|
"pluginId": "app",
|
|
@@ -63,15 +63,15 @@
|
|
|
63
63
|
"test": "backstage-cli package test"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
|
-
"@backstage/core-components": "0.18.
|
|
67
|
-
"@backstage/core-plugin-api": "1.12.
|
|
68
|
-
"@backstage/frontend-plugin-api": "0.14.0-next.
|
|
69
|
-
"@backstage/integration-react": "1.2.15-next.
|
|
66
|
+
"@backstage/core-components": "0.18.7-next.2",
|
|
67
|
+
"@backstage/core-plugin-api": "1.12.3-next.1",
|
|
68
|
+
"@backstage/frontend-plugin-api": "0.14.0-next.2",
|
|
69
|
+
"@backstage/integration-react": "1.2.15-next.2",
|
|
70
70
|
"@backstage/plugin-app-react": "0.1.1-next.0",
|
|
71
|
-
"@backstage/plugin-permission-react": "0.4.40-next.
|
|
72
|
-
"@backstage/theme": "0.7.1",
|
|
71
|
+
"@backstage/plugin-permission-react": "0.4.40-next.1",
|
|
72
|
+
"@backstage/theme": "0.7.2-next.1",
|
|
73
73
|
"@backstage/types": "1.2.2",
|
|
74
|
-
"@backstage/version-bridge": "1.0.
|
|
74
|
+
"@backstage/version-bridge": "1.0.12-next.0",
|
|
75
75
|
"@material-ui/core": "^4.9.13",
|
|
76
76
|
"@material-ui/icons": "^4.9.1",
|
|
77
77
|
"@material-ui/lab": "^4.0.0-alpha.61",
|
|
@@ -80,11 +80,11 @@
|
|
|
80
80
|
"zod": "^3.25.76"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
|
-
"@backstage/cli": "0.35.
|
|
84
|
-
"@backstage/dev-utils": "1.1.20-next.
|
|
85
|
-
"@backstage/frontend-defaults": "0.
|
|
86
|
-
"@backstage/frontend-test-utils": "0.
|
|
87
|
-
"@backstage/test-utils": "1.7.15-next.
|
|
83
|
+
"@backstage/cli": "0.35.4-next.2",
|
|
84
|
+
"@backstage/dev-utils": "1.1.20-next.2",
|
|
85
|
+
"@backstage/frontend-defaults": "0.4.0-next.2",
|
|
86
|
+
"@backstage/frontend-test-utils": "0.5.0-next.2",
|
|
87
|
+
"@backstage/test-utils": "1.7.15-next.2",
|
|
88
88
|
"@testing-library/jest-dom": "^6.0.0",
|
|
89
89
|
"@testing-library/react": "^16.0.0",
|
|
90
90
|
"@testing-library/user-event": "^14.0.0",
|
|
@@ -92,13 +92,13 @@
|
|
|
92
92
|
"msw": "^1.0.0",
|
|
93
93
|
"react": "^18.0.2",
|
|
94
94
|
"react-dom": "^18.0.2",
|
|
95
|
-
"react-router-dom": "^6.
|
|
95
|
+
"react-router-dom": "^6.30.2"
|
|
96
96
|
},
|
|
97
97
|
"peerDependencies": {
|
|
98
98
|
"@types/react": "^17.0.0 || ^18.0.0",
|
|
99
99
|
"react": "^17.0.0 || ^18.0.0",
|
|
100
100
|
"react-dom": "^17.0.0 || ^18.0.0",
|
|
101
|
-
"react-router-dom": "^6.
|
|
101
|
+
"react-router-dom": "^6.30.2"
|
|
102
102
|
},
|
|
103
103
|
"peerDependenciesMeta": {
|
|
104
104
|
"@types/react": {
|