Merge pull request #25729 from backstage/blam/implement-icon-bundle
Export `dataRefs` from `Blueprints` and implement `IconBundleBlueprint`
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
---
|
||||
'@backstage/frontend-plugin-api': patch
|
||||
---
|
||||
|
||||
Added a new `IconBundleBlueprint` that lets you create icon bundle extensions that can be installed in an App in order to override or add new app icons.
|
||||
|
||||
```tsx
|
||||
import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
|
||||
|
||||
const exampleIconBundle = IconBundleBlueprint.make({
|
||||
name: 'example-bundle',
|
||||
params: {
|
||||
icons: {
|
||||
user: MyOwnUserIcon,
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/frontend-app-api': patch
|
||||
---
|
||||
|
||||
Support icon overriding with the new `IconBundleBlueprint` API.
|
||||
@@ -45,6 +45,7 @@ This extension is the first extension attached to the extension tree. It is resp
|
||||
| themes | The app themes list. | [createThemeExtension.themeDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension.themedataref) | false | See [default themes](#default-theme-extensions). | [createThemeExtension](https://backstage.io/docs/reference/frontend-plugin-api.createthemeextension) |
|
||||
| components | The app components list. | [createComponentExtension.componentDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension.componentdataref) | false | See [default components](#default-components-extensions). | [createComponentExtension](https://backstage.io/docs/reference/frontend-plugin-api.createcomponentextension) |
|
||||
| translations | The app translations list. | [createTranslationExtension.translationDataRef](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension.translationdataref) | false | - | [createTranslationExtension](https://backstage.io/docs/reference/frontend-plugin-api.createtranslationextension) |
|
||||
| icons | The app icons list. | [IconBundleBlueprint.dataRefs.icons](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint.dataRefs.icons) | true | - | [IconBundleBlueprint](https://backstage.io/docs/reference/frontend-plugin-api.iconbundleblueprint) |
|
||||
|
||||
#### Default theme extensions
|
||||
|
||||
|
||||
@@ -314,6 +314,31 @@ const app = createApp({
|
||||
});
|
||||
```
|
||||
|
||||
### `icons`
|
||||
|
||||
Icons are now installed as extensions, using the `IconBundleBlueprint` to make new instances which can be added to the app.
|
||||
|
||||
```ts
|
||||
import { IconBundleBlueprint } from '@backstage/frontend-plugin-api';
|
||||
|
||||
const exampleIconBundle = IconBundleBlueprint.make({
|
||||
name: 'example-bundle',
|
||||
params: {
|
||||
icons: {
|
||||
user: MyOwnUserIcon,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const app = createApp({
|
||||
features: [
|
||||
createExtensionOverrides({
|
||||
extensions: [exampleIconBundle],
|
||||
}),
|
||||
],
|
||||
});
|
||||
```
|
||||
|
||||
### `bindRoutes`
|
||||
|
||||
Route bindings can still be done using this option, but you now also have the ability to bind routes using static configuration instead. See the section on [binding routes](../architecture/07-routes.md#binding-external-route-references) for more information.
|
||||
|
||||
@@ -38,6 +38,10 @@ Sign-in page extension have a single purpose - to implement a custom sign-in pag
|
||||
|
||||
Theme extensions provide custom themes for the app. They are always attached to the app extension and you can have any number of themes extensions installed in an app at once, letting the user choose which theme to use.
|
||||
|
||||
### Icons - [Reference](../../reference/frontend-plugin-api.iconbundleblueprint.md)
|
||||
|
||||
Icon bundle extensions provide the ability to replace or provide new icons to the app. You can use the above blueprint to make new extension instances which can be installed into the app.
|
||||
|
||||
### Translation - [Reference](../../reference/frontend-plugin-api.createtranslationextension.md)
|
||||
|
||||
Translation extension provide custom translation messages for the app. They can be used both to override the default english messages to custom ones, as well as provide translations for additional languages.
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
createExtensionInput,
|
||||
createThemeExtension,
|
||||
createTranslationExtension,
|
||||
IconBundleBlueprint,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
|
||||
export const App = createExtension({
|
||||
@@ -42,6 +43,9 @@ export const App = createExtension({
|
||||
translations: createExtensionInput({
|
||||
translation: createTranslationExtension.translationDataRef,
|
||||
}),
|
||||
icons: createExtensionInput({
|
||||
icon: IconBundleBlueprint.dataRefs.icons,
|
||||
}),
|
||||
root: createExtensionInput(
|
||||
{
|
||||
element: coreExtensionData.reactElement,
|
||||
|
||||
@@ -25,6 +25,7 @@ import {
|
||||
createThemeExtension,
|
||||
createTranslationExtension,
|
||||
FrontendFeature,
|
||||
IconBundleBlueprint,
|
||||
iconsApiRef,
|
||||
RouteResolutionApi,
|
||||
routeResolutionApiRef,
|
||||
@@ -171,6 +172,7 @@ export interface CreateAppFeatureLoader {
|
||||
|
||||
/** @public */
|
||||
export function createApp(options?: {
|
||||
/** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */
|
||||
icons?: { [key in string]: IconComponent };
|
||||
features?: (FrontendFeature | CreateAppFeatureLoader)[];
|
||||
configLoader?: () => Promise<{ config: ConfigApi }>;
|
||||
@@ -246,6 +248,7 @@ export function createApp(options?: {
|
||||
* @public
|
||||
*/
|
||||
export function createSpecializedApp(options?: {
|
||||
/** @deprecated - Please use {@link @backstage/frontend-plugin-api#IconBundleBlueprint} to make new icon bundles which can be installed in the app seperately */
|
||||
icons?: { [key in string]: IconComponent };
|
||||
features?: FrontendFeature[];
|
||||
config?: ConfigApi;
|
||||
@@ -373,6 +376,11 @@ function createApiHolder(
|
||||
(x): x is typeof createTranslationExtension.translationDataRef.T => !!x,
|
||||
) ?? [];
|
||||
|
||||
const extensionIcons = tree.root.edges.attachments
|
||||
.get('icons')
|
||||
?.map(e => e.instance?.getData(IconBundleBlueprint.dataRefs.icons))
|
||||
.reduce((acc, bundle) => ({ ...acc, ...bundle }), {});
|
||||
|
||||
for (const factory of pluginApis) {
|
||||
factoryRegistry.register('default', factory);
|
||||
}
|
||||
@@ -413,7 +421,8 @@ function createApiHolder(
|
||||
factoryRegistry.register('static', {
|
||||
api: iconsApiRef,
|
||||
deps: {},
|
||||
factory: () => new DefaultIconsApi({ ...defaultIcons, ...icons }),
|
||||
factory: () =>
|
||||
new DefaultIconsApi({ ...defaultIcons, ...extensionIcons, ...icons }),
|
||||
});
|
||||
|
||||
factoryRegistry.register('static', {
|
||||
|
||||
@@ -522,9 +522,16 @@ export function createExtensionBlueprint<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap = never,
|
||||
>(
|
||||
options: CreateExtensionBlueprintOptions<TParams, TInputs, TOutput, TConfig>,
|
||||
): ExtensionBlueprint<TParams, TInputs, TOutput, TConfig>;
|
||||
options: CreateExtensionBlueprintOptions<
|
||||
TParams,
|
||||
TInputs,
|
||||
TOutput,
|
||||
TConfig,
|
||||
TDataRefs
|
||||
>,
|
||||
): ExtensionBlueprint<TParams, TInputs, TOutput, TConfig, TDataRefs>;
|
||||
|
||||
// @public (undocumented)
|
||||
export interface CreateExtensionBlueprintOptions<
|
||||
@@ -532,6 +539,7 @@ export interface CreateExtensionBlueprintOptions<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap,
|
||||
> {
|
||||
// (undocumented)
|
||||
attachTo: {
|
||||
@@ -541,6 +549,8 @@ export interface CreateExtensionBlueprintOptions<
|
||||
// (undocumented)
|
||||
configSchema?: PortableSchema<TConfig>;
|
||||
// (undocumented)
|
||||
dataRefs?: TDataRefs;
|
||||
// (undocumented)
|
||||
disabled?: boolean;
|
||||
// (undocumented)
|
||||
factory(
|
||||
@@ -917,7 +927,10 @@ export interface ExtensionBlueprint<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap,
|
||||
> {
|
||||
// (undocumented)
|
||||
dataRefs: TDataRefs;
|
||||
// (undocumented)
|
||||
make(args: {
|
||||
namespace?: string;
|
||||
@@ -1085,6 +1098,35 @@ export { gitlabAuthApiRef };
|
||||
|
||||
export { googleAuthApiRef };
|
||||
|
||||
// @public (undocumented)
|
||||
export const IconBundleBlueprint: ExtensionBlueprint<
|
||||
{
|
||||
icons: {
|
||||
[x: string]: IconComponent;
|
||||
};
|
||||
},
|
||||
AnyExtensionInputMap,
|
||||
{
|
||||
icons: ConfigurableExtensionDataRef<
|
||||
'core.icons',
|
||||
{
|
||||
[x: string]: IconComponent;
|
||||
},
|
||||
{}
|
||||
>;
|
||||
},
|
||||
unknown,
|
||||
{
|
||||
icons: ConfigurableExtensionDataRef<
|
||||
'core.icons',
|
||||
{
|
||||
[x: string]: IconComponent;
|
||||
},
|
||||
{}
|
||||
>;
|
||||
}
|
||||
>;
|
||||
|
||||
// @public
|
||||
export type IconComponent = ComponentType<
|
||||
| {
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2024 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { IconComponent } from '../icons';
|
||||
import { createExtensionBlueprint, createExtensionDataRef } from '../wiring';
|
||||
|
||||
const iconsDataRef = createExtensionDataRef<{
|
||||
[key in string]: IconComponent;
|
||||
}>().with({ id: 'core.icons' });
|
||||
|
||||
/** @public */
|
||||
export const IconBundleBlueprint = createExtensionBlueprint({
|
||||
kind: 'icon-bundle',
|
||||
namespace: 'app',
|
||||
attachTo: { id: 'app', input: 'icons' },
|
||||
output: {
|
||||
icons: iconsDataRef,
|
||||
},
|
||||
factory: (params: { icons: { [key in string]: IconComponent } }) => params,
|
||||
dataRefs: {
|
||||
icons: iconsDataRef,
|
||||
},
|
||||
});
|
||||
@@ -25,3 +25,4 @@ export { createSignInPageExtension } from './createSignInPageExtension';
|
||||
export { createThemeExtension } from './createThemeExtension';
|
||||
export { createComponentExtension } from './createComponentExtension';
|
||||
export { createTranslationExtension } from './createTranslationExtension';
|
||||
export { IconBundleBlueprint } from './IconBundleBlueprint';
|
||||
|
||||
@@ -18,6 +18,7 @@ import React from 'react';
|
||||
import { coreExtensionData } from './coreExtensionData';
|
||||
import { createExtensionBlueprint } from './createExtensionBlueprint';
|
||||
import { createExtensionTester } from '@backstage/frontend-test-utils';
|
||||
import { createExtensionDataRef } from './createExtensionDataRef';
|
||||
|
||||
describe('createExtensionBlueprint', () => {
|
||||
it('should allow creation of extension blueprints', () => {
|
||||
@@ -102,4 +103,28 @@ describe('createExtensionBlueprint', () => {
|
||||
const { container } = createExtensionTester(extension).render();
|
||||
expect(container.querySelector('h2')).toHaveTextContent('Hello, world!');
|
||||
});
|
||||
|
||||
it('should allow exporting the dataRefs from the extension blueprint', () => {
|
||||
const dataRef = createExtensionDataRef<string>().with({ id: 'test.data' });
|
||||
|
||||
const TestExtensionBlueprint = createExtensionBlueprint({
|
||||
kind: 'test-extension',
|
||||
attachTo: { id: 'test', input: 'default' },
|
||||
output: {
|
||||
element: coreExtensionData.reactElement,
|
||||
},
|
||||
dataRefs: {
|
||||
data: dataRef,
|
||||
},
|
||||
factory(params: { text: string }) {
|
||||
return {
|
||||
element: <h1>{params.text}</h1>,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
expect(TestExtensionBlueprint.dataRefs).toEqual({
|
||||
data: dataRef,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,7 @@ export interface CreateExtensionBlueprintOptions<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap,
|
||||
> {
|
||||
kind: string;
|
||||
namespace?: string;
|
||||
@@ -50,6 +51,8 @@ export interface CreateExtensionBlueprintOptions<
|
||||
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
|
||||
},
|
||||
): Expand<ExtensionDataValues<TOutput>>;
|
||||
|
||||
dataRefs?: TDataRefs;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,7 +63,10 @@ export interface ExtensionBlueprint<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap,
|
||||
> {
|
||||
dataRefs: TDataRefs;
|
||||
|
||||
make(args: {
|
||||
namespace?: string;
|
||||
name?: string;
|
||||
@@ -97,15 +103,21 @@ class ExtensionBlueprintImpl<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap,
|
||||
> {
|
||||
constructor(
|
||||
private readonly options: CreateExtensionBlueprintOptions<
|
||||
TParams,
|
||||
TInputs,
|
||||
TOutput,
|
||||
TConfig
|
||||
TConfig,
|
||||
TDataRefs
|
||||
>,
|
||||
) {}
|
||||
) {
|
||||
this.dataRefs = options.dataRefs!;
|
||||
}
|
||||
|
||||
dataRefs: TDataRefs;
|
||||
|
||||
public make(args: {
|
||||
namespace?: string;
|
||||
@@ -184,8 +196,15 @@ export function createExtensionBlueprint<
|
||||
TInputs extends AnyExtensionInputMap,
|
||||
TOutput extends AnyExtensionDataMap,
|
||||
TConfig,
|
||||
TDataRefs extends AnyExtensionDataMap = never,
|
||||
>(
|
||||
options: CreateExtensionBlueprintOptions<TParams, TInputs, TOutput, TConfig>,
|
||||
): ExtensionBlueprint<TParams, TInputs, TOutput, TConfig> {
|
||||
options: CreateExtensionBlueprintOptions<
|
||||
TParams,
|
||||
TInputs,
|
||||
TOutput,
|
||||
TConfig,
|
||||
TDataRefs
|
||||
>,
|
||||
): ExtensionBlueprint<TParams, TInputs, TOutput, TConfig, TDataRefs> {
|
||||
return new ExtensionBlueprintImpl(options);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user