@backstage/core-app-api 1.19.5-next.0 → 1.19.5-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 +10 -0
- package/dist/apis/implementations/AppLanguageApi/AppLanguageSelector.esm.js +24 -3
- package/dist/apis/implementations/AppLanguageApi/AppLanguageSelector.esm.js.map +1 -1
- package/dist/apis/implementations/AppThemeApi/AppThemeSelector.esm.js +24 -3
- package/dist/apis/implementations/AppThemeApi/AppThemeSelector.esm.js.map +1 -1
- package/dist/app/AppManager.esm.js +5 -5
- package/dist/app/AppManager.esm.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @backstage/core-app-api
|
|
2
2
|
|
|
3
|
+
## 1.19.5-next.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 5a71e7a: Fixed memory leak caused by duplicate `AppThemeSelector` instances and missing cleanup in `AppThemeSelector` and `AppLanguageSelector`. Added `dispose()` method to both selectors for proper resource cleanup.
|
|
8
|
+
- 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.
|
|
9
|
+
- Updated dependencies
|
|
10
|
+
- @backstage/core-plugin-api@1.12.3-next.1
|
|
11
|
+
- @backstage/version-bridge@1.0.12-next.0
|
|
12
|
+
|
|
3
13
|
## 1.19.4-next.0
|
|
4
14
|
|
|
5
15
|
### Patch Changes
|
|
@@ -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":["../../../../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":["../../../../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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"}
|
|
@@ -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 };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppThemeSelector.esm.js","sources":["../../../../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":["../../../../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;;;;"}
|
|
@@ -82,6 +82,7 @@ class AppManager {
|
|
|
82
82
|
defaultApis;
|
|
83
83
|
bindRoutes;
|
|
84
84
|
appLanguageApi;
|
|
85
|
+
appThemeApi;
|
|
85
86
|
translationResources;
|
|
86
87
|
appIdentityProxy = new AppIdentityProxy();
|
|
87
88
|
apiFactoryRegistry;
|
|
@@ -100,6 +101,7 @@ class AppManager {
|
|
|
100
101
|
defaultLanguage: options.__experimentalTranslations?.defaultLanguage,
|
|
101
102
|
availableLanguages: options.__experimentalTranslations?.availableLanguages
|
|
102
103
|
});
|
|
104
|
+
this.appThemeApi = AppThemeSelector.createWithStorage(this.themes);
|
|
103
105
|
this.translationResources = options.__experimentalTranslations?.resources ?? [];
|
|
104
106
|
}
|
|
105
107
|
getPlugins() {
|
|
@@ -134,10 +136,7 @@ class AppManager {
|
|
|
134
136
|
let routeValidationError = void 0;
|
|
135
137
|
const Provider = ({ children }) => {
|
|
136
138
|
const needsFeatureFlagRegistrationRef = useRef(true);
|
|
137
|
-
const appThemeApi =
|
|
138
|
-
() => AppThemeSelector.createWithStorage(this.themes),
|
|
139
|
-
[]
|
|
140
|
-
);
|
|
139
|
+
const appThemeApi = this.appThemeApi;
|
|
141
140
|
const { routing, featureFlags } = useMemo(() => {
|
|
142
141
|
const usesReactRouterBeta = isReactRouterBeta();
|
|
143
142
|
if (usesReactRouterBeta) {
|
|
@@ -289,7 +288,8 @@ DEPRECATION WARNING: React Router Beta is deprecated and support for it will be
|
|
|
289
288
|
this.apiFactoryRegistry.register("static", {
|
|
290
289
|
api: appThemeApiRef,
|
|
291
290
|
deps: {},
|
|
292
|
-
|
|
291
|
+
// Use the shared AppThemeSelector instance to avoid duplicate event listeners
|
|
292
|
+
factory: () => this.appThemeApi
|
|
293
293
|
});
|
|
294
294
|
this.apiFactoryRegistry.register("static", {
|
|
295
295
|
api: configApiRef,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AppManager.esm.js","sources":["../../src/app/AppManager.tsx"],"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 { Config } from '@backstage/config';\nimport {\n ComponentType,\n PropsWithChildren,\n Suspense,\n useMemo,\n useRef,\n} from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport {\n ApiProvider,\n AppThemeSelector,\n ConfigReader,\n LocalStorageFeatureFlags,\n} from '../apis';\nimport {\n AnyApiFactory,\n ApiHolder,\n IconComponent,\n AppTheme,\n appThemeApiRef,\n configApiRef,\n AppThemeApi,\n ConfigApi,\n featureFlagsApiRef,\n identityApiRef,\n BackstagePlugin,\n FeatureFlag,\n fetchApiRef,\n discoveryApiRef,\n errorApiRef,\n} from '@backstage/core-plugin-api';\nimport {\n AppLanguageApi,\n appLanguageApiRef,\n translationApiRef,\n TranslationMessages,\n TranslationResource,\n} from '@backstage/core-plugin-api/alpha';\nimport { ApiFactoryRegistry, ApiResolver } from '../apis/system';\nimport {\n childDiscoverer,\n routeElementDiscoverer,\n traverseElementTree,\n} from '../extensions/traversal';\nimport { pluginCollector } from '../plugins/collectors';\nimport {\n featureFlagCollector,\n routingV1Collector,\n routingV2Collector,\n} from '../routing/collectors';\nimport { RoutingProvider } from '../routing/RoutingProvider';\nimport {\n validateRouteParameters,\n validateRouteBindings,\n} from '../routing/validation';\nimport { AppContextProvider } from './AppContext';\nimport { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';\nimport {\n AppComponents,\n AppConfigLoader,\n AppContext,\n AppOptions,\n BackstageApp,\n} from './types';\nimport { AppThemeProvider } from './AppThemeProvider';\nimport { defaultConfigLoader } from './defaultConfigLoader';\nimport { ApiRegistry } from '../apis/system/ApiRegistry';\nimport { resolveRouteBindings } from './resolveRouteBindings';\nimport { isReactRouterBeta } from './isReactRouterBeta';\nimport { InternalAppContext } from './InternalAppContext';\nimport { AppRouter, getBasePath } from './AppRouter';\nimport { AppLanguageSelector } from '../apis/implementations/AppLanguageApi';\nimport { I18nextTranslationApi } from '../apis/implementations/TranslationApi';\nimport { overrideBaseUrlConfigs } from './overrideBaseUrlConfigs';\nimport { isProtectedApp } from './isProtectedApp';\n\ntype CompatiblePlugin =\n | BackstagePlugin\n | (Omit<BackstagePlugin, 'getFeatureFlags'> & {\n output(): Array<{ type: 'feature-flag'; name: string }>;\n });\n\nfunction useConfigLoader(\n configLoader: AppConfigLoader | undefined,\n components: AppComponents,\n appThemeApi: AppThemeApi,\n): { api: ConfigApi } | { node: JSX.Element } {\n // Keeping this synchronous when a config loader isn't set simplifies tests a lot\n const hasConfig = Boolean(configLoader);\n const config = useAsync(configLoader || (() => Promise.resolve([])));\n\n let noConfigNode = undefined;\n\n if (hasConfig && config.loading) {\n const { Progress } = components;\n noConfigNode = <Progress />;\n } else if (config.error) {\n const { BootErrorPage } = components;\n noConfigNode = <BootErrorPage step=\"load-config\" error={config.error} />;\n }\n\n const { ThemeProvider = AppThemeProvider } = components;\n\n // Before the config is loaded we can't use a router, so exit early\n if (noConfigNode) {\n return {\n node: (\n <ApiProvider apis={ApiRegistry.with(appThemeApiRef, appThemeApi)}>\n <ThemeProvider>{noConfigNode}</ThemeProvider>\n </ApiProvider>\n ),\n };\n }\n\n const configReader = ConfigReader.fromConfigs(\n config.value?.length ? overrideBaseUrlConfigs(config.value) : [],\n );\n\n return { api: configReader };\n}\n\nclass AppContextImpl implements AppContext {\n constructor(private readonly app: AppManager) {}\n\n getPlugins(): BackstagePlugin[] {\n return this.app.getPlugins();\n }\n\n getSystemIcon(key: string): IconComponent | undefined {\n return this.app.getSystemIcon(key);\n }\n\n getSystemIcons(): Record<string, IconComponent> {\n return this.app.getSystemIcons();\n }\n\n getComponents(): AppComponents {\n return this.app.getComponents();\n }\n}\n\nexport class AppManager implements BackstageApp {\n private apiHolder?: ApiHolder;\n private configApi?: ConfigApi;\n\n private readonly apis: Iterable<AnyApiFactory>;\n private readonly icons: NonNullable<AppOptions['icons']>;\n private readonly plugins: Set<CompatiblePlugin>;\n private readonly featureFlags: (FeatureFlag &\n Omit<FeatureFlag, 'pluginId'>)[];\n private readonly components: AppComponents;\n private readonly themes: AppTheme[];\n private readonly configLoader?: AppConfigLoader;\n private readonly defaultApis: Iterable<AnyApiFactory>;\n private readonly bindRoutes: AppOptions['bindRoutes'];\n private readonly appLanguageApi: AppLanguageApi;\n private readonly translationResources: Array<\n TranslationResource | TranslationMessages\n >;\n\n private readonly appIdentityProxy = new AppIdentityProxy();\n private readonly apiFactoryRegistry: ApiFactoryRegistry;\n\n constructor(options: AppOptions) {\n this.apis = options.apis ?? [];\n this.icons = options.icons;\n this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []);\n this.featureFlags = options.featureFlags ?? [];\n this.components = options.components;\n this.themes = options.themes as AppTheme[];\n this.configLoader = options.configLoader ?? defaultConfigLoader;\n this.defaultApis = options.defaultApis ?? [];\n this.bindRoutes = options.bindRoutes;\n this.apiFactoryRegistry = new ApiFactoryRegistry();\n this.appLanguageApi = AppLanguageSelector.createWithStorage({\n defaultLanguage: options.__experimentalTranslations?.defaultLanguage,\n availableLanguages:\n options.__experimentalTranslations?.availableLanguages,\n });\n this.translationResources =\n options.__experimentalTranslations?.resources ?? [];\n }\n\n getPlugins(): BackstagePlugin[] {\n return Array.from(this.plugins) as BackstagePlugin[];\n }\n\n getSystemIcon(key: string): IconComponent | undefined {\n return this.icons[key];\n }\n\n getSystemIcons(): Record<string, IconComponent> {\n return this.icons;\n }\n\n getComponents(): AppComponents {\n return this.components;\n }\n\n createRoot(element: JSX.Element): ComponentType<PropsWithChildren<{}>> {\n const AppProvider = this.getProvider();\n const AppRoot = () => {\n return <AppProvider>{element}</AppProvider>;\n };\n return AppRoot;\n }\n\n #getProviderCalled = false;\n getProvider(): ComponentType<PropsWithChildren<{}>> {\n if (this.#getProviderCalled) {\n throw new Error(\n 'app.getProvider() or app.createRoot() has already been called, and can only be called once',\n );\n }\n this.#getProviderCalled = true;\n\n const appContext = new AppContextImpl(this);\n\n // We only bind and validate routes once\n let routeBindings: ReturnType<typeof resolveRouteBindings>;\n // Store and keep throwing the same error if we encounter one\n let routeValidationError: Error | undefined = undefined;\n\n const Provider = ({ children }: PropsWithChildren<{}>) => {\n const needsFeatureFlagRegistrationRef = useRef(true);\n const appThemeApi = useMemo(\n () => AppThemeSelector.createWithStorage(this.themes),\n [],\n );\n\n const { routing, featureFlags } = useMemo(() => {\n const usesReactRouterBeta = isReactRouterBeta();\n if (usesReactRouterBeta) {\n // eslint-disable-next-line no-console\n console.warn(`\nDEPRECATION WARNING: React Router Beta is deprecated and support for it will be removed in a future release.\n Please migrate to use React Router v6 stable.\n See https://backstage.io/docs/tutorials/react-router-stable-migration\n`);\n }\n\n const result = traverseElementTree({\n root: children,\n discoverers: [childDiscoverer, routeElementDiscoverer],\n collectors: {\n routing: usesReactRouterBeta\n ? routingV1Collector\n : routingV2Collector,\n collectedPlugins: pluginCollector,\n featureFlags: featureFlagCollector,\n },\n });\n\n // TODO(Rugvip): Restructure the public API so that we can get an immediate view of\n // the app, rather than having to wait for the provider to render.\n // For now we need to push the additional plugins we find during\n // collection and then make sure we initialize things afterwards.\n result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));\n this.verifyPlugins(this.plugins);\n\n // Initialize APIs once all plugins are available\n this.getApiHolder();\n return result;\n }, [children]);\n\n const loadedConfig = useConfigLoader(\n this.configLoader,\n this.components,\n appThemeApi,\n );\n\n const hasConfigApi = 'api' in loadedConfig;\n if (hasConfigApi) {\n const { api } = loadedConfig as { api: Config };\n this.configApi = api;\n }\n\n if ('node' in loadedConfig) {\n // Loading or error\n return loadedConfig.node;\n }\n\n if (routeValidationError) {\n throw routeValidationError;\n } else if (!routeBindings) {\n try {\n routeBindings = resolveRouteBindings(\n this.bindRoutes,\n loadedConfig.api,\n this.plugins,\n );\n\n validateRouteParameters(routing.paths, routing.parents);\n validateRouteBindings(routeBindings, this.plugins);\n } catch (error) {\n routeValidationError = error;\n throw error;\n }\n }\n\n // We can't register feature flags just after the element traversal, because the\n // config API isn't available yet and implementations frequently depend on it.\n // Instead we make it happen immediately, to make sure all flags are available\n // for the first render.\n if (hasConfigApi && needsFeatureFlagRegistrationRef.current) {\n needsFeatureFlagRegistrationRef.current = false;\n\n const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;\n\n if (featureFlagsApi) {\n for (const flag of this.featureFlags) {\n featureFlagsApi.registerFlag({\n ...flag,\n pluginId: '',\n });\n }\n for (const plugin of this.plugins.values()) {\n if ('getFeatureFlags' in plugin) {\n for (const flag of plugin.getFeatureFlags()) {\n featureFlagsApi.registerFlag({\n name: flag.name,\n pluginId: plugin.getId(),\n });\n }\n } else {\n for (const output of plugin.output()) {\n if (output.type === 'feature-flag') {\n featureFlagsApi.registerFlag({\n name: output.name,\n pluginId: plugin.getId(),\n });\n }\n }\n }\n }\n\n // Go through the featureFlags returned from the traversal and\n // register those now the configApi has been loaded\n const registeredFlags = featureFlagsApi.getRegisteredFlags();\n const flagNames = new Set(registeredFlags.map(f => f.name));\n for (const name of featureFlags) {\n // Prevents adding duplicate feature flags\n if (!flagNames.has(name)) {\n featureFlagsApi.registerFlag({ name, pluginId: '' });\n }\n }\n }\n }\n\n const { ThemeProvider = AppThemeProvider, Progress } = this.components;\n\n const apis = this.getApiHolder();\n\n if (isProtectedApp()) {\n const errorApi = apis.get(errorApiRef);\n const fetchApi = apis.get(fetchApiRef);\n const discoveryApi = apis.get(discoveryApiRef);\n if (!errorApi || !fetchApi || !discoveryApi) {\n throw new Error(\n 'App is running in protected mode but missing required APIs',\n );\n }\n this.appIdentityProxy.enableCookieAuth({\n errorApi,\n fetchApi,\n discoveryApi,\n });\n }\n\n return (\n <ApiProvider apis={apis}>\n <AppContextProvider appContext={appContext}>\n <ThemeProvider>\n <RoutingProvider\n routePaths={routing.paths}\n routeParents={routing.parents}\n routeObjects={routing.objects}\n routeBindings={routeBindings}\n basePath={getBasePath(loadedConfig.api)}\n >\n <InternalAppContext.Provider\n value={{\n routeObjects: routing.objects,\n appIdentityProxy: this.appIdentityProxy,\n }}\n >\n <Suspense fallback={<Progress />}>{children}</Suspense>\n </InternalAppContext.Provider>\n </RoutingProvider>\n </ThemeProvider>\n </AppContextProvider>\n </ApiProvider>\n );\n };\n return Provider;\n }\n\n getRouter(): ComponentType<PropsWithChildren<{}>> {\n return AppRouter;\n }\n\n private getApiHolder(): ApiHolder {\n if (this.apiHolder) {\n // Register additional plugins if they have been added.\n // Routes paths, objects and others are already updated in the provider when children of it change\n for (const plugin of this.plugins) {\n for (const factory of plugin.getApis()) {\n if (!this.apiFactoryRegistry.get(factory.api)) {\n this.apiFactoryRegistry.register('default', factory);\n }\n }\n }\n ApiResolver.validateFactories(\n this.apiFactoryRegistry,\n this.apiFactoryRegistry.getAllApis(),\n );\n return this.apiHolder;\n }\n this.apiFactoryRegistry.register('static', {\n api: appThemeApiRef,\n deps: {},\n factory: () => AppThemeSelector.createWithStorage(this.themes),\n });\n this.apiFactoryRegistry.register('static', {\n api: configApiRef,\n deps: {},\n factory: () => {\n if (!this.configApi) {\n throw new Error(\n 'Tried to access config API before config was loaded',\n );\n }\n return this.configApi;\n },\n });\n this.apiFactoryRegistry.register('static', {\n api: identityApiRef,\n deps: {},\n factory: () => this.appIdentityProxy,\n });\n this.apiFactoryRegistry.register('static', {\n api: appLanguageApiRef,\n deps: {},\n factory: () => this.appLanguageApi,\n });\n\n // The translation API is registered as a default API so that it can be overridden.\n // It will be up to the implementer of the new API to register translation resources.\n this.apiFactoryRegistry.register('default', {\n api: translationApiRef,\n deps: { languageApi: appLanguageApiRef },\n factory: ({ languageApi }) =>\n I18nextTranslationApi.create({\n languageApi,\n resources: this.translationResources,\n }),\n });\n\n // It's possible to replace the feature flag API, but since we must have at least\n // one implementation we add it here directly instead of through the defaultApis.\n this.apiFactoryRegistry.register('default', {\n api: featureFlagsApiRef,\n deps: {},\n factory: () => new LocalStorageFeatureFlags(),\n });\n for (const factory of this.defaultApis) {\n this.apiFactoryRegistry.register('default', factory);\n }\n\n for (const plugin of this.plugins) {\n for (const factory of plugin.getApis()) {\n if (!this.apiFactoryRegistry.register('default', factory)) {\n throw new Error(\n `Plugin ${plugin.getId()} tried to register duplicate or forbidden API factory for ${\n factory.api\n }`,\n );\n }\n }\n }\n\n for (const factory of this.apis) {\n if (!this.apiFactoryRegistry.register('app', factory)) {\n throw new Error(\n `Duplicate or forbidden API factory for ${factory.api} in app`,\n );\n }\n }\n\n ApiResolver.validateFactories(\n this.apiFactoryRegistry,\n this.apiFactoryRegistry.getAllApis(),\n );\n\n this.apiHolder = new ApiResolver(this.apiFactoryRegistry);\n return this.apiHolder;\n }\n\n private verifyPlugins(plugins: Iterable<CompatiblePlugin>) {\n const pluginIds = new Set<string>();\n\n for (const plugin of plugins) {\n const id = plugin.getId();\n if (pluginIds.has(id)) {\n throw new Error(`Duplicate plugin found '${id}'`);\n }\n pluginIds.add(id);\n }\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AppManager.esm.js","sources":["../../src/app/AppManager.tsx"],"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 { Config } from '@backstage/config';\nimport {\n ComponentType,\n PropsWithChildren,\n Suspense,\n useMemo,\n useRef,\n} from 'react';\nimport useAsync from 'react-use/esm/useAsync';\nimport {\n ApiProvider,\n AppThemeSelector,\n ConfigReader,\n LocalStorageFeatureFlags,\n} from '../apis';\nimport {\n AnyApiFactory,\n ApiHolder,\n IconComponent,\n AppTheme,\n appThemeApiRef,\n configApiRef,\n AppThemeApi,\n ConfigApi,\n featureFlagsApiRef,\n identityApiRef,\n BackstagePlugin,\n FeatureFlag,\n fetchApiRef,\n discoveryApiRef,\n errorApiRef,\n} from '@backstage/core-plugin-api';\nimport {\n AppLanguageApi,\n appLanguageApiRef,\n translationApiRef,\n TranslationMessages,\n TranslationResource,\n} from '@backstage/core-plugin-api/alpha';\nimport { ApiFactoryRegistry, ApiResolver } from '../apis/system';\nimport {\n childDiscoverer,\n routeElementDiscoverer,\n traverseElementTree,\n} from '../extensions/traversal';\nimport { pluginCollector } from '../plugins/collectors';\nimport {\n featureFlagCollector,\n routingV1Collector,\n routingV2Collector,\n} from '../routing/collectors';\nimport { RoutingProvider } from '../routing/RoutingProvider';\nimport {\n validateRouteParameters,\n validateRouteBindings,\n} from '../routing/validation';\nimport { AppContextProvider } from './AppContext';\nimport { AppIdentityProxy } from '../apis/implementations/IdentityApi/AppIdentityProxy';\nimport {\n AppComponents,\n AppConfigLoader,\n AppContext,\n AppOptions,\n BackstageApp,\n} from './types';\nimport { AppThemeProvider } from './AppThemeProvider';\nimport { defaultConfigLoader } from './defaultConfigLoader';\nimport { ApiRegistry } from '../apis/system/ApiRegistry';\nimport { resolveRouteBindings } from './resolveRouteBindings';\nimport { isReactRouterBeta } from './isReactRouterBeta';\nimport { InternalAppContext } from './InternalAppContext';\nimport { AppRouter, getBasePath } from './AppRouter';\nimport { AppLanguageSelector } from '../apis/implementations/AppLanguageApi';\nimport { I18nextTranslationApi } from '../apis/implementations/TranslationApi';\nimport { overrideBaseUrlConfigs } from './overrideBaseUrlConfigs';\nimport { isProtectedApp } from './isProtectedApp';\n\ntype CompatiblePlugin =\n | BackstagePlugin\n | (Omit<BackstagePlugin, 'getFeatureFlags'> & {\n output(): Array<{ type: 'feature-flag'; name: string }>;\n });\n\nfunction useConfigLoader(\n configLoader: AppConfigLoader | undefined,\n components: AppComponents,\n appThemeApi: AppThemeApi,\n): { api: ConfigApi } | { node: JSX.Element } {\n // Keeping this synchronous when a config loader isn't set simplifies tests a lot\n const hasConfig = Boolean(configLoader);\n const config = useAsync(configLoader || (() => Promise.resolve([])));\n\n let noConfigNode = undefined;\n\n if (hasConfig && config.loading) {\n const { Progress } = components;\n noConfigNode = <Progress />;\n } else if (config.error) {\n const { BootErrorPage } = components;\n noConfigNode = <BootErrorPage step=\"load-config\" error={config.error} />;\n }\n\n const { ThemeProvider = AppThemeProvider } = components;\n\n // Before the config is loaded we can't use a router, so exit early\n if (noConfigNode) {\n return {\n node: (\n <ApiProvider apis={ApiRegistry.with(appThemeApiRef, appThemeApi)}>\n <ThemeProvider>{noConfigNode}</ThemeProvider>\n </ApiProvider>\n ),\n };\n }\n\n const configReader = ConfigReader.fromConfigs(\n config.value?.length ? overrideBaseUrlConfigs(config.value) : [],\n );\n\n return { api: configReader };\n}\n\nclass AppContextImpl implements AppContext {\n constructor(private readonly app: AppManager) {}\n\n getPlugins(): BackstagePlugin[] {\n return this.app.getPlugins();\n }\n\n getSystemIcon(key: string): IconComponent | undefined {\n return this.app.getSystemIcon(key);\n }\n\n getSystemIcons(): Record<string, IconComponent> {\n return this.app.getSystemIcons();\n }\n\n getComponents(): AppComponents {\n return this.app.getComponents();\n }\n}\n\nexport class AppManager implements BackstageApp {\n private apiHolder?: ApiHolder;\n private configApi?: ConfigApi;\n\n private readonly apis: Iterable<AnyApiFactory>;\n private readonly icons: NonNullable<AppOptions['icons']>;\n private readonly plugins: Set<CompatiblePlugin>;\n private readonly featureFlags: (FeatureFlag &\n Omit<FeatureFlag, 'pluginId'>)[];\n private readonly components: AppComponents;\n private readonly themes: AppTheme[];\n private readonly configLoader?: AppConfigLoader;\n private readonly defaultApis: Iterable<AnyApiFactory>;\n private readonly bindRoutes: AppOptions['bindRoutes'];\n private readonly appLanguageApi: AppLanguageApi;\n private readonly appThemeApi: AppThemeApi;\n private readonly translationResources: Array<\n TranslationResource | TranslationMessages\n >;\n\n private readonly appIdentityProxy = new AppIdentityProxy();\n private readonly apiFactoryRegistry: ApiFactoryRegistry;\n\n constructor(options: AppOptions) {\n this.apis = options.apis ?? [];\n this.icons = options.icons;\n this.plugins = new Set((options.plugins as CompatiblePlugin[]) ?? []);\n this.featureFlags = options.featureFlags ?? [];\n this.components = options.components;\n this.themes = options.themes as AppTheme[];\n this.configLoader = options.configLoader ?? defaultConfigLoader;\n this.defaultApis = options.defaultApis ?? [];\n this.bindRoutes = options.bindRoutes;\n this.apiFactoryRegistry = new ApiFactoryRegistry();\n this.appLanguageApi = AppLanguageSelector.createWithStorage({\n defaultLanguage: options.__experimentalTranslations?.defaultLanguage,\n availableLanguages:\n options.__experimentalTranslations?.availableLanguages,\n });\n // Create a single AppThemeSelector instance to be shared between\n // the loading phase and the main app, avoiding duplicate event listeners\n this.appThemeApi = AppThemeSelector.createWithStorage(this.themes);\n this.translationResources =\n options.__experimentalTranslations?.resources ?? [];\n }\n\n getPlugins(): BackstagePlugin[] {\n return Array.from(this.plugins) as BackstagePlugin[];\n }\n\n getSystemIcon(key: string): IconComponent | undefined {\n return this.icons[key];\n }\n\n getSystemIcons(): Record<string, IconComponent> {\n return this.icons;\n }\n\n getComponents(): AppComponents {\n return this.components;\n }\n\n createRoot(element: JSX.Element): ComponentType<PropsWithChildren<{}>> {\n const AppProvider = this.getProvider();\n const AppRoot = () => {\n return <AppProvider>{element}</AppProvider>;\n };\n return AppRoot;\n }\n\n #getProviderCalled = false;\n getProvider(): ComponentType<PropsWithChildren<{}>> {\n if (this.#getProviderCalled) {\n throw new Error(\n 'app.getProvider() or app.createRoot() has already been called, and can only be called once',\n );\n }\n this.#getProviderCalled = true;\n\n const appContext = new AppContextImpl(this);\n\n // We only bind and validate routes once\n let routeBindings: ReturnType<typeof resolveRouteBindings>;\n // Store and keep throwing the same error if we encounter one\n let routeValidationError: Error | undefined = undefined;\n\n const Provider = ({ children }: PropsWithChildren<{}>) => {\n const needsFeatureFlagRegistrationRef = useRef(true);\n // Use the shared AppThemeSelector instance created in the constructor\n // to avoid creating duplicate event listeners and subscriptions\n const appThemeApi = this.appThemeApi;\n\n const { routing, featureFlags } = useMemo(() => {\n const usesReactRouterBeta = isReactRouterBeta();\n if (usesReactRouterBeta) {\n // eslint-disable-next-line no-console\n console.warn(`\nDEPRECATION WARNING: React Router Beta is deprecated and support for it will be removed in a future release.\n Please migrate to use React Router v6 stable.\n See https://backstage.io/docs/tutorials/react-router-stable-migration\n`);\n }\n\n const result = traverseElementTree({\n root: children,\n discoverers: [childDiscoverer, routeElementDiscoverer],\n collectors: {\n routing: usesReactRouterBeta\n ? routingV1Collector\n : routingV2Collector,\n collectedPlugins: pluginCollector,\n featureFlags: featureFlagCollector,\n },\n });\n\n // TODO(Rugvip): Restructure the public API so that we can get an immediate view of\n // the app, rather than having to wait for the provider to render.\n // For now we need to push the additional plugins we find during\n // collection and then make sure we initialize things afterwards.\n result.collectedPlugins.forEach(plugin => this.plugins.add(plugin));\n this.verifyPlugins(this.plugins);\n\n // Initialize APIs once all plugins are available\n this.getApiHolder();\n return result;\n }, [children]);\n\n const loadedConfig = useConfigLoader(\n this.configLoader,\n this.components,\n appThemeApi,\n );\n\n const hasConfigApi = 'api' in loadedConfig;\n if (hasConfigApi) {\n const { api } = loadedConfig as { api: Config };\n this.configApi = api;\n }\n\n if ('node' in loadedConfig) {\n // Loading or error\n return loadedConfig.node;\n }\n\n if (routeValidationError) {\n throw routeValidationError;\n } else if (!routeBindings) {\n try {\n routeBindings = resolveRouteBindings(\n this.bindRoutes,\n loadedConfig.api,\n this.plugins,\n );\n\n validateRouteParameters(routing.paths, routing.parents);\n validateRouteBindings(routeBindings, this.plugins);\n } catch (error) {\n routeValidationError = error;\n throw error;\n }\n }\n\n // We can't register feature flags just after the element traversal, because the\n // config API isn't available yet and implementations frequently depend on it.\n // Instead we make it happen immediately, to make sure all flags are available\n // for the first render.\n if (hasConfigApi && needsFeatureFlagRegistrationRef.current) {\n needsFeatureFlagRegistrationRef.current = false;\n\n const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;\n\n if (featureFlagsApi) {\n for (const flag of this.featureFlags) {\n featureFlagsApi.registerFlag({\n ...flag,\n pluginId: '',\n });\n }\n for (const plugin of this.plugins.values()) {\n if ('getFeatureFlags' in plugin) {\n for (const flag of plugin.getFeatureFlags()) {\n featureFlagsApi.registerFlag({\n name: flag.name,\n pluginId: plugin.getId(),\n });\n }\n } else {\n for (const output of plugin.output()) {\n if (output.type === 'feature-flag') {\n featureFlagsApi.registerFlag({\n name: output.name,\n pluginId: plugin.getId(),\n });\n }\n }\n }\n }\n\n // Go through the featureFlags returned from the traversal and\n // register those now the configApi has been loaded\n const registeredFlags = featureFlagsApi.getRegisteredFlags();\n const flagNames = new Set(registeredFlags.map(f => f.name));\n for (const name of featureFlags) {\n // Prevents adding duplicate feature flags\n if (!flagNames.has(name)) {\n featureFlagsApi.registerFlag({ name, pluginId: '' });\n }\n }\n }\n }\n\n const { ThemeProvider = AppThemeProvider, Progress } = this.components;\n\n const apis = this.getApiHolder();\n\n if (isProtectedApp()) {\n const errorApi = apis.get(errorApiRef);\n const fetchApi = apis.get(fetchApiRef);\n const discoveryApi = apis.get(discoveryApiRef);\n if (!errorApi || !fetchApi || !discoveryApi) {\n throw new Error(\n 'App is running in protected mode but missing required APIs',\n );\n }\n this.appIdentityProxy.enableCookieAuth({\n errorApi,\n fetchApi,\n discoveryApi,\n });\n }\n\n return (\n <ApiProvider apis={apis}>\n <AppContextProvider appContext={appContext}>\n <ThemeProvider>\n <RoutingProvider\n routePaths={routing.paths}\n routeParents={routing.parents}\n routeObjects={routing.objects}\n routeBindings={routeBindings}\n basePath={getBasePath(loadedConfig.api)}\n >\n <InternalAppContext.Provider\n value={{\n routeObjects: routing.objects,\n appIdentityProxy: this.appIdentityProxy,\n }}\n >\n <Suspense fallback={<Progress />}>{children}</Suspense>\n </InternalAppContext.Provider>\n </RoutingProvider>\n </ThemeProvider>\n </AppContextProvider>\n </ApiProvider>\n );\n };\n return Provider;\n }\n\n getRouter(): ComponentType<PropsWithChildren<{}>> {\n return AppRouter;\n }\n\n private getApiHolder(): ApiHolder {\n if (this.apiHolder) {\n // Register additional plugins if they have been added.\n // Routes paths, objects and others are already updated in the provider when children of it change\n for (const plugin of this.plugins) {\n for (const factory of plugin.getApis()) {\n if (!this.apiFactoryRegistry.get(factory.api)) {\n this.apiFactoryRegistry.register('default', factory);\n }\n }\n }\n ApiResolver.validateFactories(\n this.apiFactoryRegistry,\n this.apiFactoryRegistry.getAllApis(),\n );\n return this.apiHolder;\n }\n this.apiFactoryRegistry.register('static', {\n api: appThemeApiRef,\n deps: {},\n // Use the shared AppThemeSelector instance to avoid duplicate event listeners\n factory: () => this.appThemeApi,\n });\n this.apiFactoryRegistry.register('static', {\n api: configApiRef,\n deps: {},\n factory: () => {\n if (!this.configApi) {\n throw new Error(\n 'Tried to access config API before config was loaded',\n );\n }\n return this.configApi;\n },\n });\n this.apiFactoryRegistry.register('static', {\n api: identityApiRef,\n deps: {},\n factory: () => this.appIdentityProxy,\n });\n this.apiFactoryRegistry.register('static', {\n api: appLanguageApiRef,\n deps: {},\n factory: () => this.appLanguageApi,\n });\n\n // The translation API is registered as a default API so that it can be overridden.\n // It will be up to the implementer of the new API to register translation resources.\n this.apiFactoryRegistry.register('default', {\n api: translationApiRef,\n deps: { languageApi: appLanguageApiRef },\n factory: ({ languageApi }) =>\n I18nextTranslationApi.create({\n languageApi,\n resources: this.translationResources,\n }),\n });\n\n // It's possible to replace the feature flag API, but since we must have at least\n // one implementation we add it here directly instead of through the defaultApis.\n this.apiFactoryRegistry.register('default', {\n api: featureFlagsApiRef,\n deps: {},\n factory: () => new LocalStorageFeatureFlags(),\n });\n for (const factory of this.defaultApis) {\n this.apiFactoryRegistry.register('default', factory);\n }\n\n for (const plugin of this.plugins) {\n for (const factory of plugin.getApis()) {\n if (!this.apiFactoryRegistry.register('default', factory)) {\n throw new Error(\n `Plugin ${plugin.getId()} tried to register duplicate or forbidden API factory for ${\n factory.api\n }`,\n );\n }\n }\n }\n\n for (const factory of this.apis) {\n if (!this.apiFactoryRegistry.register('app', factory)) {\n throw new Error(\n `Duplicate or forbidden API factory for ${factory.api} in app`,\n );\n }\n }\n\n ApiResolver.validateFactories(\n this.apiFactoryRegistry,\n this.apiFactoryRegistry.getAllApis(),\n );\n\n this.apiHolder = new ApiResolver(this.apiFactoryRegistry);\n return this.apiHolder;\n }\n\n private verifyPlugins(plugins: Iterable<CompatiblePlugin>) {\n const pluginIds = new Set<string>();\n\n for (const plugin of plugins) {\n const id = plugin.getId();\n if (pluginIds.has(id)) {\n throw new Error(`Duplicate plugin found '${id}'`);\n }\n pluginIds.add(id);\n }\n }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmGA,SAAS,eAAA,CACP,YAAA,EACA,UAAA,EACA,WAAA,EAC4C;AAE5C,EAAA,MAAM,SAAA,GAAY,QAAQ,YAAY,CAAA;AACtC,EAAA,MAAM,MAAA,GAAS,SAAS,YAAA,KAAiB,MAAM,QAAQ,OAAA,CAAQ,EAAE,CAAA,CAAE,CAAA;AAEnE,EAAA,IAAI,YAAA,GAAe,MAAA;AAEnB,EAAA,IAAI,SAAA,IAAa,OAAO,OAAA,EAAS;AAC/B,IAAA,MAAM,EAAE,UAAS,GAAI,UAAA;AACrB,IAAA,YAAA,uBAAgB,QAAA,EAAA,EAAS,CAAA;AAAA,EAC3B,CAAA,MAAA,IAAW,OAAO,KAAA,EAAO;AACvB,IAAA,MAAM,EAAE,eAAc,GAAI,UAAA;AAC1B,IAAA,YAAA,uBAAgB,aAAA,EAAA,EAAc,IAAA,EAAK,aAAA,EAAc,KAAA,EAAO,OAAO,KAAA,EAAO,CAAA;AAAA,EACxE;AAEA,EAAA,MAAM,EAAE,aAAA,GAAgB,gBAAA,EAAiB,GAAI,UAAA;AAG7C,EAAA,IAAI,YAAA,EAAc;AAChB,IAAA,OAAO;AAAA,MACL,IAAA,kBACE,GAAA,CAAC,WAAA,EAAA,EAAY,IAAA,EAAM,WAAA,CAAY,IAAA,CAAK,cAAA,EAAgB,WAAW,CAAA,EAC7D,QAAA,kBAAA,GAAA,CAAC,aAAA,EAAA,EAAe,QAAA,EAAA,YAAA,EAAa,CAAA,EAC/B;AAAA,KAEJ;AAAA,EACF;AAEA,EAAA,MAAM,eAAe,YAAA,CAAa,WAAA;AAAA,IAChC,OAAO,KAAA,EAAO,MAAA,GAAS,uBAAuB,MAAA,CAAO,KAAK,IAAI;AAAC,GACjE;AAEA,EAAA,OAAO,EAAE,KAAK,YAAA,EAAa;AAC7B;AAEA,MAAM,cAAA,CAAqC;AAAA,EACzC,YAA6B,GAAA,EAAiB;AAAjB,IAAA,IAAA,CAAA,GAAA,GAAA,GAAA;AAAA,EAAkB;AAAA,EAE/C,UAAA,GAAgC;AAC9B,IAAA,OAAO,IAAA,CAAK,IAAI,UAAA,EAAW;AAAA,EAC7B;AAAA,EAEA,cAAc,GAAA,EAAwC;AACpD,IAAA,OAAO,IAAA,CAAK,GAAA,CAAI,aAAA,CAAc,GAAG,CAAA;AAAA,EACnC;AAAA,EAEA,cAAA,GAAgD;AAC9C,IAAA,OAAO,IAAA,CAAK,IAAI,cAAA,EAAe;AAAA,EACjC;AAAA,EAEA,aAAA,GAA+B;AAC7B,IAAA,OAAO,IAAA,CAAK,IAAI,aAAA,EAAc;AAAA,EAChC;AACF;AAEO,MAAM,UAAA,CAAmC;AAAA,EACtC,SAAA;AAAA,EACA,SAAA;AAAA,EAES,IAAA;AAAA,EACA,KAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EAEA,UAAA;AAAA,EACA,MAAA;AAAA,EACA,YAAA;AAAA,EACA,WAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,WAAA;AAAA,EACA,oBAAA;AAAA,EAIA,gBAAA,GAAmB,IAAI,gBAAA,EAAiB;AAAA,EACxC,kBAAA;AAAA,EAEjB,YAAY,OAAA,EAAqB;AAC/B,IAAA,IAAA,CAAK,IAAA,GAAO,OAAA,CAAQ,IAAA,IAAQ,EAAC;AAC7B,IAAA,IAAA,CAAK,QAAQ,OAAA,CAAQ,KAAA;AACrB,IAAA,IAAA,CAAK,UAAU,IAAI,GAAA,CAAK,OAAA,CAAQ,OAAA,IAAkC,EAAE,CAAA;AACpE,IAAA,IAAA,CAAK,YAAA,GAAe,OAAA,CAAQ,YAAA,IAAgB,EAAC;AAC7C,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,UAAA;AAC1B,IAAA,IAAA,CAAK,SAAS,OAAA,CAAQ,MAAA;AACtB,IAAA,IAAA,CAAK,YAAA,GAAe,QAAQ,YAAA,IAAgB,mBAAA;AAC5C,IAAA,IAAA,CAAK,WAAA,GAAc,OAAA,CAAQ,WAAA,IAAe,EAAC;AAC3C,IAAA,IAAA,CAAK,aAAa,OAAA,CAAQ,UAAA;AAC1B,IAAA,IAAA,CAAK,kBAAA,GAAqB,IAAI,kBAAA,EAAmB;AACjD,IAAA,IAAA,CAAK,cAAA,GAAiB,oBAAoB,iBAAA,CAAkB;AAAA,MAC1D,eAAA,EAAiB,QAAQ,0BAAA,EAA4B,eAAA;AAAA,MACrD,kBAAA,EACE,QAAQ,0BAAA,EAA4B;AAAA,KACvC,CAAA;AAGD,IAAA,IAAA,CAAK,WAAA,GAAc,gBAAA,CAAiB,iBAAA,CAAkB,IAAA,CAAK,MAAM,CAAA;AACjE,IAAA,IAAA,CAAK,oBAAA,GACH,OAAA,CAAQ,0BAAA,EAA4B,SAAA,IAAa,EAAC;AAAA,EACtD;AAAA,EAEA,UAAA,GAAgC;AAC9B,IAAA,OAAO,KAAA,CAAM,IAAA,CAAK,IAAA,CAAK,OAAO,CAAA;AAAA,EAChC;AAAA,EAEA,cAAc,GAAA,EAAwC;AACpD,IAAA,OAAO,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,EACvB;AAAA,EAEA,cAAA,GAAgD;AAC9C,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AAAA,EAEA,aAAA,GAA+B;AAC7B,IAAA,OAAO,IAAA,CAAK,UAAA;AAAA,EACd;AAAA,EAEA,WAAW,OAAA,EAA4D;AACrE,IAAA,MAAM,WAAA,GAAc,KAAK,WAAA,EAAY;AACrC,IAAA,MAAM,UAAU,MAAM;AACpB,MAAA,uBAAO,GAAA,CAAC,eAAa,QAAA,EAAA,OAAA,EAAQ,CAAA;AAAA,IAC/B,CAAA;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AAAA,EAEA,kBAAA,GAAqB,KAAA;AAAA,EACrB,WAAA,GAAoD;AAClD,IAAA,IAAI,KAAK,kBAAA,EAAoB;AAC3B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,IAAA,CAAK,kBAAA,GAAqB,IAAA;AAE1B,IAAA,MAAM,UAAA,GAAa,IAAI,cAAA,CAAe,IAAI,CAAA;AAG1C,IAAA,IAAI,aAAA;AAEJ,IAAA,IAAI,oBAAA,GAA0C,MAAA;AAE9C,IAAA,MAAM,QAAA,GAAW,CAAC,EAAE,QAAA,EAAS,KAA6B;AACxD,MAAA,MAAM,+BAAA,GAAkC,OAAO,IAAI,CAAA;AAGnD,MAAA,MAAM,cAAc,IAAA,CAAK,WAAA;AAEzB,MAAA,MAAM,EAAE,OAAA,EAAS,YAAA,EAAa,GAAI,QAAQ,MAAM;AAC9C,QAAA,MAAM,sBAAsB,iBAAA,EAAkB;AAC9C,QAAA,IAAI,mBAAA,EAAqB;AAEvB,UAAA,OAAA,CAAQ,IAAA,CAAK;AAAA;AAAA;AAAA;AAAA,CAItB,CAAA;AAAA,QACO;AAEA,QAAA,MAAM,SAAS,mBAAA,CAAoB;AAAA,UACjC,IAAA,EAAM,QAAA;AAAA,UACN,WAAA,EAAa,CAAC,eAAA,EAAiB,sBAAsB,CAAA;AAAA,UACrD,UAAA,EAAY;AAAA,YACV,OAAA,EAAS,sBACL,kBAAA,GACA,kBAAA;AAAA,YACJ,gBAAA,EAAkB,eAAA;AAAA,YAClB,YAAA,EAAc;AAAA;AAChB,SACD,CAAA;AAMD,QAAA,MAAA,CAAO,iBAAiB,OAAA,CAAQ,CAAA,MAAA,KAAU,KAAK,OAAA,CAAQ,GAAA,CAAI,MAAM,CAAC,CAAA;AAClE,QAAA,IAAA,CAAK,aAAA,CAAc,KAAK,OAAO,CAAA;AAG/B,QAAA,IAAA,CAAK,YAAA,EAAa;AAClB,QAAA,OAAO,MAAA;AAAA,MACT,CAAA,EAAG,CAAC,QAAQ,CAAC,CAAA;AAEb,MAAA,MAAM,YAAA,GAAe,eAAA;AAAA,QACnB,IAAA,CAAK,YAAA;AAAA,QACL,IAAA,CAAK,UAAA;AAAA,QACL;AAAA,OACF;AAEA,MAAA,MAAM,eAAe,KAAA,IAAS,YAAA;AAC9B,MAAA,IAAI,YAAA,EAAc;AAChB,QAAA,MAAM,EAAE,KAAI,GAAI,YAAA;AAChB,QAAA,IAAA,CAAK,SAAA,GAAY,GAAA;AAAA,MACnB;AAEA,MAAA,IAAI,UAAU,YAAA,EAAc;AAE1B,QAAA,OAAO,YAAA,CAAa,IAAA;AAAA,MACtB;AAEA,MAAA,IAAI,oBAAA,EAAsB;AACxB,QAAA,MAAM,oBAAA;AAAA,MACR,CAAA,MAAA,IAAW,CAAC,aAAA,EAAe;AACzB,QAAA,IAAI;AACF,UAAA,aAAA,GAAgB,oBAAA;AAAA,YACd,IAAA,CAAK,UAAA;AAAA,YACL,YAAA,CAAa,GAAA;AAAA,YACb,IAAA,CAAK;AAAA,WACP;AAEA,UAAA,uBAAA,CAAwB,OAAA,CAAQ,KAAA,EAAO,OAAA,CAAQ,OAAO,CAAA;AACtD,UAAA,qBAAA,CAAsB,aAAA,EAAe,KAAK,OAAO,CAAA;AAAA,QACnD,SAAS,KAAA,EAAO;AACd,UAAA,oBAAA,GAAuB,KAAA;AACvB,UAAA,MAAM,KAAA;AAAA,QACR;AAAA,MACF;AAMA,MAAA,IAAI,YAAA,IAAgB,gCAAgC,OAAA,EAAS;AAC3D,QAAA,+BAAA,CAAgC,OAAA,GAAU,KAAA;AAE1C,QAAA,MAAM,eAAA,GAAkB,IAAA,CAAK,YAAA,EAAa,CAAE,IAAI,kBAAkB,CAAA;AAElE,QAAA,IAAI,eAAA,EAAiB;AACnB,UAAA,KAAA,MAAW,IAAA,IAAQ,KAAK,YAAA,EAAc;AACpC,YAAA,eAAA,CAAgB,YAAA,CAAa;AAAA,cAC3B,GAAG,IAAA;AAAA,cACH,QAAA,EAAU;AAAA,aACX,CAAA;AAAA,UACH;AACA,UAAA,KAAA,MAAW,MAAA,IAAU,IAAA,CAAK,OAAA,CAAQ,MAAA,EAAO,EAAG;AAC1C,YAAA,IAAI,qBAAqB,MAAA,EAAQ;AAC/B,cAAA,KAAA,MAAW,IAAA,IAAQ,MAAA,CAAO,eAAA,EAAgB,EAAG;AAC3C,gBAAA,eAAA,CAAgB,YAAA,CAAa;AAAA,kBAC3B,MAAM,IAAA,CAAK,IAAA;AAAA,kBACX,QAAA,EAAU,OAAO,KAAA;AAAM,iBACxB,CAAA;AAAA,cACH;AAAA,YACF,CAAA,MAAO;AACL,cAAA,KAAA,MAAW,MAAA,IAAU,MAAA,CAAO,MAAA,EAAO,EAAG;AACpC,gBAAA,IAAI,MAAA,CAAO,SAAS,cAAA,EAAgB;AAClC,kBAAA,eAAA,CAAgB,YAAA,CAAa;AAAA,oBAC3B,MAAM,MAAA,CAAO,IAAA;AAAA,oBACb,QAAA,EAAU,OAAO,KAAA;AAAM,mBACxB,CAAA;AAAA,gBACH;AAAA,cACF;AAAA,YACF;AAAA,UACF;AAIA,UAAA,MAAM,eAAA,GAAkB,gBAAgB,kBAAA,EAAmB;AAC3D,UAAA,MAAM,SAAA,GAAY,IAAI,GAAA,CAAI,eAAA,CAAgB,IAAI,CAAA,CAAA,KAAK,CAAA,CAAE,IAAI,CAAC,CAAA;AAC1D,UAAA,KAAA,MAAW,QAAQ,YAAA,EAAc;AAE/B,YAAA,IAAI,CAAC,SAAA,CAAU,GAAA,CAAI,IAAI,CAAA,EAAG;AACxB,cAAA,eAAA,CAAgB,YAAA,CAAa,EAAE,IAAA,EAAM,QAAA,EAAU,IAAI,CAAA;AAAA,YACrD;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAEA,MAAA,MAAM,EAAE,aAAA,GAAgB,gBAAA,EAAkB,QAAA,KAAa,IAAA,CAAK,UAAA;AAE5D,MAAA,MAAM,IAAA,GAAO,KAAK,YAAA,EAAa;AAE/B,MAAA,IAAI,gBAAe,EAAG;AACpB,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,WAAW,CAAA;AACrC,QAAA,MAAM,QAAA,GAAW,IAAA,CAAK,GAAA,CAAI,WAAW,CAAA;AACrC,QAAA,MAAM,YAAA,GAAe,IAAA,CAAK,GAAA,CAAI,eAAe,CAAA;AAC7C,QAAA,IAAI,CAAC,QAAA,IAAY,CAAC,QAAA,IAAY,CAAC,YAAA,EAAc;AAC3C,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA,QACF;AACA,QAAA,IAAA,CAAK,iBAAiB,gBAAA,CAAiB;AAAA,UACrC,QAAA;AAAA,UACA,QAAA;AAAA,UACA;AAAA,SACD,CAAA;AAAA,MACH;AAEA,MAAA,2BACG,WAAA,EAAA,EAAY,IAAA,EACX,8BAAC,kBAAA,EAAA,EAAmB,UAAA,EAClB,8BAAC,aAAA,EAAA,EACC,QAAA,kBAAA,GAAA;AAAA,QAAC,eAAA;AAAA,QAAA;AAAA,UACC,YAAY,OAAA,CAAQ,KAAA;AAAA,UACpB,cAAc,OAAA,CAAQ,OAAA;AAAA,UACtB,cAAc,OAAA,CAAQ,OAAA;AAAA,UACtB,aAAA;AAAA,UACA,QAAA,EAAU,WAAA,CAAY,YAAA,CAAa,GAAG,CAAA;AAAA,UAEtC,QAAA,kBAAA,GAAA;AAAA,YAAC,kBAAA,CAAmB,QAAA;AAAA,YAAnB;AAAA,cACC,KAAA,EAAO;AAAA,gBACL,cAAc,OAAA,CAAQ,OAAA;AAAA,gBACtB,kBAAkB,IAAA,CAAK;AAAA,eACzB;AAAA,cAEA,8BAAC,QAAA,EAAA,EAAS,QAAA,kBAAU,GAAA,CAAC,QAAA,EAAA,EAAS,GAAK,QAAA,EAAS;AAAA;AAAA;AAC9C;AAAA,OACF,EACF,GACF,CAAA,EACF,CAAA;AAAA,IAEJ,CAAA;AACA,IAAA,OAAO,QAAA;AAAA,EACT;AAAA,EAEA,SAAA,GAAkD;AAChD,IAAA,OAAO,SAAA;AAAA,EACT;AAAA,EAEQ,YAAA,GAA0B;AAChC,IAAA,IAAI,KAAK,SAAA,EAAW;AAGlB,MAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,QAAA,KAAA,MAAW,OAAA,IAAW,MAAA,CAAO,OAAA,EAAQ,EAAG;AACtC,UAAA,IAAI,CAAC,IAAA,CAAK,kBAAA,CAAmB,GAAA,CAAI,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC7C,YAAA,IAAA,CAAK,kBAAA,CAAmB,QAAA,CAAS,SAAA,EAAW,OAAO,CAAA;AAAA,UACrD;AAAA,QACF;AAAA,MACF;AACA,MAAA,WAAA,CAAY,iBAAA;AAAA,QACV,IAAA,CAAK,kBAAA;AAAA,QACL,IAAA,CAAK,mBAAmB,UAAA;AAAW,OACrC;AACA,MAAA,OAAO,IAAA,CAAK,SAAA;AAAA,IACd;AACA,IAAA,IAAA,CAAK,kBAAA,CAAmB,SAAS,QAAA,EAAU;AAAA,MACzC,GAAA,EAAK,cAAA;AAAA,MACL,MAAM,EAAC;AAAA;AAAA,MAEP,OAAA,EAAS,MAAM,IAAA,CAAK;AAAA,KACrB,CAAA;AACD,IAAA,IAAA,CAAK,kBAAA,CAAmB,SAAS,QAAA,EAAU;AAAA,MACzC,GAAA,EAAK,YAAA;AAAA,MACL,MAAM,EAAC;AAAA,MACP,SAAS,MAAM;AACb,QAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACnB,UAAA,MAAM,IAAI,KAAA;AAAA,YACR;AAAA,WACF;AAAA,QACF;AACA,QAAA,OAAO,IAAA,CAAK,SAAA;AAAA,MACd;AAAA,KACD,CAAA;AACD,IAAA,IAAA,CAAK,kBAAA,CAAmB,SAAS,QAAA,EAAU;AAAA,MACzC,GAAA,EAAK,cAAA;AAAA,MACL,MAAM,EAAC;AAAA,MACP,OAAA,EAAS,MAAM,IAAA,CAAK;AAAA,KACrB,CAAA;AACD,IAAA,IAAA,CAAK,kBAAA,CAAmB,SAAS,QAAA,EAAU;AAAA,MACzC,GAAA,EAAK,iBAAA;AAAA,MACL,MAAM,EAAC;AAAA,MACP,OAAA,EAAS,MAAM,IAAA,CAAK;AAAA,KACrB,CAAA;AAID,IAAA,IAAA,CAAK,kBAAA,CAAmB,SAAS,SAAA,EAAW;AAAA,MAC1C,GAAA,EAAK,iBAAA;AAAA,MACL,IAAA,EAAM,EAAE,WAAA,EAAa,iBAAA,EAAkB;AAAA,MACvC,SAAS,CAAC,EAAE,WAAA,EAAY,KACtB,sBAAsB,MAAA,CAAO;AAAA,QAC3B,WAAA;AAAA,QACA,WAAW,IAAA,CAAK;AAAA,OACjB;AAAA,KACJ,CAAA;AAID,IAAA,IAAA,CAAK,kBAAA,CAAmB,SAAS,SAAA,EAAW;AAAA,MAC1C,GAAA,EAAK,kBAAA;AAAA,MACL,MAAM,EAAC;AAAA,MACP,OAAA,EAAS,MAAM,IAAI,wBAAA;AAAyB,KAC7C,CAAA;AACD,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,WAAA,EAAa;AACtC,MAAA,IAAA,CAAK,kBAAA,CAAmB,QAAA,CAAS,SAAA,EAAW,OAAO,CAAA;AAAA,IACrD;AAEA,IAAA,KAAA,MAAW,MAAA,IAAU,KAAK,OAAA,EAAS;AACjC,MAAA,KAAA,MAAW,OAAA,IAAW,MAAA,CAAO,OAAA,EAAQ,EAAG;AACtC,QAAA,IAAI,CAAC,IAAA,CAAK,kBAAA,CAAmB,QAAA,CAAS,SAAA,EAAW,OAAO,CAAA,EAAG;AACzD,UAAA,MAAM,IAAI,KAAA;AAAA,YACR,UAAU,MAAA,CAAO,KAAA,EAAO,CAAA,0DAAA,EACtB,QAAQ,GACV,CAAA;AAAA,WACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,IAAA,KAAA,MAAW,OAAA,IAAW,KAAK,IAAA,EAAM;AAC/B,MAAA,IAAI,CAAC,IAAA,CAAK,kBAAA,CAAmB,QAAA,CAAS,KAAA,EAAO,OAAO,CAAA,EAAG;AACrD,QAAA,MAAM,IAAI,KAAA;AAAA,UACR,CAAA,uCAAA,EAA0C,QAAQ,GAAG,CAAA,OAAA;AAAA,SACvD;AAAA,MACF;AAAA,IACF;AAEA,IAAA,WAAA,CAAY,iBAAA;AAAA,MACV,IAAA,CAAK,kBAAA;AAAA,MACL,IAAA,CAAK,mBAAmB,UAAA;AAAW,KACrC;AAEA,IAAA,IAAA,CAAK,SAAA,GAAY,IAAI,WAAA,CAAY,IAAA,CAAK,kBAAkB,CAAA;AACxD,IAAA,OAAO,IAAA,CAAK,SAAA;AAAA,EACd;AAAA,EAEQ,cAAc,OAAA,EAAqC;AACzD,IAAA,MAAM,SAAA,uBAAgB,GAAA,EAAY;AAElC,IAAA,KAAA,MAAW,UAAU,OAAA,EAAS;AAC5B,MAAA,MAAM,EAAA,GAAK,OAAO,KAAA,EAAM;AACxB,MAAA,IAAI,SAAA,CAAU,GAAA,CAAI,EAAE,CAAA,EAAG;AACrB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,EAAE,CAAA,CAAA,CAAG,CAAA;AAAA,MAClD;AACA,MAAA,SAAA,CAAU,IAAI,EAAE,CAAA;AAAA,IAClB;AAAA,EACF;AACF;;;;"}
|
package/dist/index.d.ts
CHANGED
|
@@ -496,6 +496,7 @@ declare class NoOpAnalyticsApi implements AnalyticsApi {
|
|
|
496
496
|
* @public
|
|
497
497
|
*/
|
|
498
498
|
declare class AppThemeSelector implements AppThemeApi {
|
|
499
|
+
#private;
|
|
499
500
|
private readonly themes;
|
|
500
501
|
static createWithStorage(themes: AppTheme[]): AppThemeSelector;
|
|
501
502
|
private activeThemeId;
|
|
@@ -505,6 +506,12 @@ declare class AppThemeSelector implements AppThemeApi {
|
|
|
505
506
|
activeThemeId$(): Observable<string | undefined>;
|
|
506
507
|
getActiveThemeId(): string | undefined;
|
|
507
508
|
setActiveThemeId(themeId?: string): void;
|
|
509
|
+
/**
|
|
510
|
+
* Cleans up resources created by createWithStorage().
|
|
511
|
+
* Call this method when the selector is no longer needed to prevent memory leaks.
|
|
512
|
+
* This is particularly useful in testing scenarios or when the app is unmounted.
|
|
513
|
+
*/
|
|
514
|
+
dispose(): void;
|
|
508
515
|
}
|
|
509
516
|
|
|
510
517
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backstage/core-app-api",
|
|
3
|
-
"version": "1.19.5-next.
|
|
3
|
+
"version": "1.19.5-next.1",
|
|
4
4
|
"description": "Core app API used by Backstage apps",
|
|
5
5
|
"backstage": {
|
|
6
6
|
"role": "web-library"
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
53
|
"@backstage/config": "1.3.6",
|
|
54
|
-
"@backstage/core-plugin-api": "1.12.3-next.
|
|
54
|
+
"@backstage/core-plugin-api": "1.12.3-next.1",
|
|
55
55
|
"@backstage/types": "1.2.2",
|
|
56
|
-
"@backstage/version-bridge": "1.0.
|
|
56
|
+
"@backstage/version-bridge": "1.0.12-next.0",
|
|
57
57
|
"@types/prop-types": "^15.7.3",
|
|
58
58
|
"history": "^5.0.0",
|
|
59
59
|
"i18next": "^22.4.15",
|
|
@@ -64,8 +64,8 @@
|
|
|
64
64
|
"zod": "^3.25.76"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@backstage/cli": "0.35.4-next.
|
|
68
|
-
"@backstage/test-utils": "1.7.15-next.
|
|
67
|
+
"@backstage/cli": "0.35.4-next.2",
|
|
68
|
+
"@backstage/test-utils": "1.7.15-next.2",
|
|
69
69
|
"@testing-library/dom": "^10.0.0",
|
|
70
70
|
"@testing-library/jest-dom": "^6.0.0",
|
|
71
71
|
"@testing-library/react": "^16.0.0",
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
"react": "^18.0.2",
|
|
78
78
|
"react-dom": "^18.0.2",
|
|
79
79
|
"react-router-beta": "npm:react-router@6.0.0-beta.0",
|
|
80
|
-
"react-router-dom": "^6.
|
|
80
|
+
"react-router-dom": "^6.30.2",
|
|
81
81
|
"react-router-dom-beta": "npm:react-router-dom@6.0.0-beta.0",
|
|
82
82
|
"react-router-dom-stable": "npm:react-router-dom@^6.3.0",
|
|
83
83
|
"react-router-stable": "npm:react-router@^6.3.0"
|
|
@@ -86,7 +86,7 @@
|
|
|
86
86
|
"@types/react": "^17.0.0 || ^18.0.0",
|
|
87
87
|
"react": "^17.0.0 || ^18.0.0",
|
|
88
88
|
"react-dom": "^17.0.0 || ^18.0.0",
|
|
89
|
-
"react-router-dom": "^6.
|
|
89
|
+
"react-router-dom": "^6.30.2"
|
|
90
90
|
},
|
|
91
91
|
"peerDependenciesMeta": {
|
|
92
92
|
"@types/react": {
|