From 4b3ba2b0488aef09857fbfa5bfc62ab5b19287af Mon Sep 17 00:00:00 2001 From: benjdlambert Date: Wed, 6 Aug 2025 14:24:17 +0200 Subject: [PATCH] chore: make more things swoppable Signed-off-by: benjdlambert Signed-off-by: benjdlambert --- .../compatWrapper/ForwardsCompatProvider.tsx | 16 +- .../src/compatWrapper/compatWrapper.test.tsx | 6 +- .../DefaultSwappableComponentsApi.test.tsx} | 19 ++- .../DefaultSwappableComponentsApi.ts} | 12 +- .../index.ts | 2 +- ...ef.ts => InternalSwappableComponentRef.ts} | 8 +- .../frontend-internal/src/wiring/index.ts | 2 +- packages/frontend-plugin-api/report.api.md | 99 ++++++------ ...onentsApi.ts => SwappableComponentsApi.ts} | 14 +- .../src/apis/definitions/index.ts | 2 +- .../blueprints/SwappableComponentBlueprint.ts | 17 +- .../components/createSwappableComponent.tsx | 33 ++-- .../src/components/index.ts | 2 +- plugins/app/report.api.md | 150 +++++++++++------- ...nentsApi.tsx => SwappableComponentsApi.ts} | 12 +- plugins/app/src/extensions/index.ts | 2 +- plugins/app/src/plugin.ts | 4 +- 17 files changed, 228 insertions(+), 172 deletions(-) rename packages/frontend-app-api/src/apis/implementations/{ComponentsApi/DefaultComponentsApi.test.tsx => SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx} (75%) rename packages/frontend-app-api/src/apis/implementations/{ComponentsApi/DefaultComponentsApi.ts => SwappableComponentsApi/DefaultSwappableComponentsApi.ts} (85%) rename packages/frontend-app-api/src/apis/implementations/{ComponentsApi => SwappableComponentsApi}/index.ts (88%) rename packages/frontend-internal/src/wiring/{InternalComponentRef.ts => InternalSwappableComponentRef.ts} (81%) rename packages/frontend-plugin-api/src/apis/definitions/{ComponentsApi.ts => SwappableComponentsApi.ts} (74%) rename plugins/app/src/extensions/{ComponentsApi.tsx => SwappableComponentsApi.ts} (78%) diff --git a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx index ce2ce29bdb..099cb836bb 100644 --- a/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx +++ b/packages/core-compat-api/src/compatWrapper/ForwardsCompatProvider.tsx @@ -22,8 +22,8 @@ import { } from '@backstage/core-plugin-api'; import { AnyRouteRefParams, - ComponentRef, - ComponentsApi, + SwappableComponentRef, + SwappableComponentsApi, CoreErrorBoundaryFallbackProps, CoreNotFoundErrorPageProps, CoreProgressProps, @@ -34,7 +34,7 @@ import { RouteRef, RouteResolutionApi, SubRouteRef, - componentsApiRef, + swappableComponentsApiRef, iconsApiRef, routeResolutionApiRef, Progress, @@ -51,7 +51,7 @@ import { useVersionedContext } from '@backstage/version-bridge'; import { type RouteResolver } from '../../../core-plugin-api/src/routing/useRouteRef'; import { convertLegacyRouteRef } from '../convertLegacyRouteRef'; -class CompatComponentsApi implements ComponentsApi { +class CompatComponentsApi implements SwappableComponentsApi { readonly #Progress: ComponentType; readonly #NotFoundErrorPage: ComponentType; readonly #ErrorBoundaryFallback: ComponentType; @@ -69,11 +69,11 @@ class CompatComponentsApi implements ComponentsApi { this.#ErrorBoundaryFallback = ErrorBoundaryFallback; } - getComponent< + getComponentLoader< TInnerComponentProps extends {}, TExternalComponentProps extends {} = TInnerComponentProps, >( - ref: ComponentRef, + ref: SwappableComponentRef, ): | (() => (props: TInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>) @@ -135,7 +135,7 @@ class CompatRouteResolutionApi implements RouteResolutionApi { } class ForwardsCompatApis implements ApiHolder { - readonly #componentsApi: ComponentsApi; + readonly #componentsApi: SwappableComponentsApi; readonly #iconsApi: IconsApi; readonly #routeResolutionApi: RouteResolutionApi; @@ -146,7 +146,7 @@ class ForwardsCompatApis implements ApiHolder { } get(ref: ApiRef): T | undefined { - if (ref.id === componentsApiRef.id) { + if (ref.id === swappableComponentsApiRef.id) { return this.#componentsApi as T; } else if (ref.id === iconsApiRef.id) { return this.#iconsApi as T; diff --git a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx index 86af1f4212..9accec81a6 100644 --- a/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx +++ b/packages/core-compat-api/src/compatWrapper/compatWrapper.test.tsx @@ -15,7 +15,7 @@ */ import { - componentsApiRef, + swappableComponentsApiRef, coreExtensionData, createExtension, iconsApiRef, @@ -106,7 +106,7 @@ describe('ForwardsCompatProvider', () => { }; function Component() { - const components = useApi(componentsApiRef); + const components = useApi(swappableComponentsApiRef); const icons = useApi(iconsApiRef); return (
@@ -114,7 +114,7 @@ describe('ForwardsCompatProvider', () => { {Object.entries(defaultComponentRefs) .map( ([name, ref]) => - `${name}=${Boolean(components.getComponent(ref))}`, + `${name}=${Boolean(components.getComponentLoader(ref))}`, ) .join(', ')} {'\n'} diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx similarity index 75% rename from packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx rename to packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx index 7c550103f3..6afe3c2e19 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx +++ b/packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/DefaultSwappableComponentsApi.test.tsx @@ -15,7 +15,7 @@ */ import { createSwappableComponent } from '@backstage/frontend-plugin-api'; -import { DefaultComponentsApi } from './DefaultComponentsApi'; +import { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi'; import { render, screen } from '@testing-library/react'; const { ref: testRefA } = createSwappableComponent({ id: 'test.a' }); @@ -24,14 +24,16 @@ const { ref: testRefB2 } = createSwappableComponent({ id: 'test.b' }); describe('DefaultComponentsApi', () => { it('should provide components', () => { - const api = DefaultComponentsApi.fromComponents([ + const api = DefaultSwappableComponentsApi.fromComponents([ { ref: testRefA, loader: () => () =>
test.a
, }, ]); - const ComponentA = api.getComponent(testRefA)?.() as () => JSX.Element; + const ComponentA = api.getComponentLoader( + testRefA, + )?.() as () => JSX.Element; render(); @@ -40,15 +42,20 @@ describe('DefaultComponentsApi', () => { it('should key extension refs by ID', () => { const mockLoader = jest.fn(() =>
test.b
); - const api = DefaultComponentsApi.fromComponents([ + const api = DefaultSwappableComponentsApi.fromComponents([ { ref: testRefB1, loader: () => mockLoader, }, ]); - const ComponentB2 = api.getComponent(testRefB2)?.() as () => JSX.Element; - const ComponentB1 = api.getComponent(testRefB1)?.() as () => JSX.Element; + const ComponentB2 = api.getComponentLoader( + testRefB2, + )?.() as () => JSX.Element; + + const ComponentB1 = api.getComponentLoader( + testRefB1, + )?.() as () => JSX.Element; expect(ComponentB1).toBe(ComponentB2); diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/DefaultSwappableComponentsApi.ts similarity index 85% rename from packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts rename to packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/DefaultSwappableComponentsApi.ts index 6f5aac8275..885b7bdddb 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/DefaultSwappableComponentsApi.ts @@ -15,8 +15,8 @@ */ import { - ComponentRef, - ComponentsApi, + SwappableComponentRef, + SwappableComponentsApi, SwappableComponentBlueprint, } from '@backstage/frontend-plugin-api'; @@ -25,7 +25,7 @@ import { * * @internal */ -export class DefaultComponentsApi implements ComponentsApi { +export class DefaultSwappableComponentsApi implements SwappableComponentsApi { #components: Map< string, | (() => (props: object) => JSX.Element | null) @@ -36,7 +36,7 @@ export class DefaultComponentsApi implements ComponentsApi { static fromComponents( components: Array, ) { - return new DefaultComponentsApi( + return new DefaultSwappableComponentsApi( new Map(components.map(entry => [entry.ref.id, entry.loader])), ); } @@ -45,8 +45,8 @@ export class DefaultComponentsApi implements ComponentsApi { this.#components = components; } - getComponent( - ref: ComponentRef, + getComponentLoader( + ref: SwappableComponentRef, ): | (() => (props: object) => JSX.Element | null) | (() => Promise<(props: object) => JSX.Element | null>) diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts b/packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/index.ts similarity index 88% rename from packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts rename to packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/index.ts index 18604f15de..20f90eb32d 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts +++ b/packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { DefaultComponentsApi } from './DefaultComponentsApi'; +export { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi'; diff --git a/packages/frontend-internal/src/wiring/InternalComponentRef.ts b/packages/frontend-internal/src/wiring/InternalSwappableComponentRef.ts similarity index 81% rename from packages/frontend-internal/src/wiring/InternalComponentRef.ts rename to packages/frontend-internal/src/wiring/InternalSwappableComponentRef.ts index edd1355b76..7fe27920a4 100644 --- a/packages/frontend-internal/src/wiring/InternalComponentRef.ts +++ b/packages/frontend-internal/src/wiring/InternalSwappableComponentRef.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { ComponentRef } from '@backstage/frontend-plugin-api'; +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { OpaqueType } from '@internal/opaque'; -export const OpaqueComponentRef = OpaqueType.create<{ - public: ComponentRef; +export const OpaqueSwappableComponentRef = OpaqueType.create<{ + public: SwappableComponentRef; versions: { readonly version: 'v1'; readonly transformProps?: (props: object) => object; @@ -28,5 +28,5 @@ export const OpaqueComponentRef = OpaqueType.create<{ }; }>({ versions: ['v1'], - type: '@backstage/ComponentRef', + type: '@backstage/SwappableComponentRef', }); diff --git a/packages/frontend-internal/src/wiring/index.ts b/packages/frontend-internal/src/wiring/index.ts index bc2314f25e..b61294cb1f 100644 --- a/packages/frontend-internal/src/wiring/index.ts +++ b/packages/frontend-internal/src/wiring/index.ts @@ -15,6 +15,6 @@ */ export { createExtensionDataContainer } from './createExtensionDataContainer'; -export { OpaqueComponentRef } from './InternalComponentRef'; +export { OpaqueSwappableComponentRef } from './InternalSwappableComponentRef'; export { OpaqueExtensionDefinition } from './InternalExtensionDefinition'; export { OpaqueFrontendPlugin } from './InternalFrontendPlugin'; diff --git a/packages/frontend-plugin-api/report.api.md b/packages/frontend-plugin-api/report.api.md index de85709f4f..e4815fb98b 100644 --- a/packages/frontend-plugin-api/report.api.md +++ b/packages/frontend-plugin-api/report.api.md @@ -350,34 +350,6 @@ export { bitbucketAuthApiRef }; export { bitbucketServerAuthApiRef }; -// @public (undocumented) -export type ComponentRef< - TInnerComponentProps extends {} = {}, - TExternalComponentProps extends {} = TInnerComponentProps, -> = { - id: string; - TProps: TInnerComponentProps; - TExternalProps: TExternalComponentProps; - $$type: '@backstage/ComponentRef'; -}; - -// @public -export interface ComponentsApi { - // (undocumented) - getComponent< - TInnerComponentProps extends {}, - TExternalComponentProps extends {} = TInnerComponentProps, - >( - ref: ComponentRef, - ): - | (() => (props: TInnerComponentProps) => JSX.Element | null) - | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>) - | undefined; -} - -// @public -export const componentsApiRef: ApiRef; - export { ConfigApi }; export { configApiRef }; @@ -832,8 +804,8 @@ export function createSwappableComponent< TInnerComponentProps, TExternalComponentProps >, -): ((props: TExternalComponentProps) => JSX.Element) & { - ref: ComponentRef; +): ((props: TExternalComponentProps) => JSX.Element | null) & { + ref: SwappableComponentRef; }; // @public @@ -901,8 +873,8 @@ export { errorApiRef }; // @public (undocumented) export const ErrorBoundary: (( props: CoreErrorBoundaryFallbackProps, -) => JSX.Element) & { - ref: ComponentRef< +) => JSX.Element | null) & { + ref: SwappableComponentRef< CoreErrorBoundaryFallbackProps, CoreErrorBoundaryFallbackProps >; @@ -1566,8 +1538,11 @@ export const NavItemBlueprint: ExtensionBlueprint<{ // @public (undocumented) export const NotFoundErrorPage: (( props: CoreNotFoundErrorPageProps, -) => JSX.Element) & { - ref: ComponentRef; +) => JSX.Element | null) & { + ref: SwappableComponentRef< + CoreNotFoundErrorPageProps, + CoreNotFoundErrorPageProps + >; }; export { OAuthApi }; @@ -1655,8 +1630,8 @@ export { ProfileInfo }; export { ProfileInfoApi }; // @public (undocumented) -export const Progress: ((props: CoreProgressProps) => JSX.Element) & { - ref: ComponentRef; +export const Progress: ((props: CoreProgressProps) => JSX.Element | null) & { + ref: SwappableComponentRef; }; // @public @@ -1854,24 +1829,30 @@ export interface SubRouteRef< // @public export const SwappableComponentBlueprint: ExtensionBlueprint<{ kind: 'component'; - params: >(params: { - component: Ref extends ComponentRef + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) : never; }) => ExtensionBlueprintParams<{ - component: Ref extends ComponentRef + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) @@ -1879,7 +1860,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ }>; output: ExtensionDataRef< { - ref: ComponentRef; + ref: SwappableComponentRef; loader: | (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>); @@ -1893,7 +1874,7 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ dataRefs: { component: ConfigurableExtensionDataRef< { - ref: ComponentRef; + ref: SwappableComponentRef; loader: | (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>); @@ -1904,6 +1885,34 @@ export const SwappableComponentBlueprint: ExtensionBlueprint<{ }; }>; +// @public (undocumented) +export type SwappableComponentRef< + TInnerComponentProps extends {} = {}, + TExternalComponentProps extends {} = TInnerComponentProps, +> = { + id: string; + TProps: TInnerComponentProps; + TExternalProps: TExternalComponentProps; + $$type: '@backstage/SwappableComponentRef'; +}; + +// @public +export interface SwappableComponentsApi { + // (undocumented) + getComponentLoader< + TInnerComponentProps extends {}, + TExternalComponentProps extends {} = TInnerComponentProps, + >( + ref: SwappableComponentRef, + ): + | (() => (props: TInnerComponentProps) => JSX.Element | null) + | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>) + | undefined; +} + +// @public +export const swappableComponentsApiRef: ApiRef; + // @public export const ThemeBlueprint: ExtensionBlueprint<{ kind: 'theme'; diff --git a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts similarity index 74% rename from packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts rename to packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts index a36ca8adc6..aeb33afca9 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/ComponentsApi.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/SwappableComponentsApi.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { ComponentRef } from '../../components'; +import { SwappableComponentRef } from '../../components'; import { createApiRef } from '@backstage/core-plugin-api'; /** @@ -22,12 +22,12 @@ import { createApiRef } from '@backstage/core-plugin-api'; * * @public */ -export interface ComponentsApi { - getComponent< +export interface SwappableComponentsApi { + getComponentLoader< TInnerComponentProps extends {}, TExternalComponentProps extends {} = TInnerComponentProps, >( - ref: ComponentRef, + ref: SwappableComponentRef, ): | (() => (props: TInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>) @@ -35,10 +35,10 @@ export interface ComponentsApi { } /** - * The `ApiRef` of {@link ComponentsApi}. + * The `ApiRef` of {@link SwappableComponentsApi}. * * @public */ -export const componentsApiRef = createApiRef({ - id: 'core.components', +export const swappableComponentsApiRef = createApiRef({ + id: 'core.swappableComponents', }); diff --git a/packages/frontend-plugin-api/src/apis/definitions/index.ts b/packages/frontend-plugin-api/src/apis/definitions/index.ts index 0da23fcacb..7533481d01 100644 --- a/packages/frontend-plugin-api/src/apis/definitions/index.ts +++ b/packages/frontend-plugin-api/src/apis/definitions/index.ts @@ -34,7 +34,7 @@ export * from './auth'; export * from './AlertApi'; export * from './AppThemeApi'; -export * from './ComponentsApi'; +export * from './SwappableComponentsApi'; export * from './ConfigApi'; export * from './DiscoveryApi'; export * from './ErrorApi'; diff --git a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts index 71466fc608..60807f96c5 100644 --- a/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts +++ b/packages/frontend-plugin-api/src/blueprints/SwappableComponentBlueprint.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ComponentRef } from '../components'; +import { SwappableComponentRef } from '../components'; import { createExtensionBlueprint, createExtensionBlueprintParams, @@ -21,14 +21,14 @@ import { } from '../wiring'; export const componentDataRef = createExtensionDataRef<{ - ref: ComponentRef; + ref: SwappableComponentRef; loader: | (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>); }>().with({ id: 'core.component.component' }); /** - * Blueprint for creating swappable components from a componentRef and a loader + * Blueprint for creating swappable components from a SwappableComponentRef and a loader * * @public */ @@ -39,11 +39,14 @@ export const SwappableComponentBlueprint = createExtensionBlueprint({ dataRefs: { component: componentDataRef, }, - defineParams>(params: { - component: Ref extends ComponentRef - ? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element) + defineParams>(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > + ? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>) diff --git a/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx b/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx index 4089c9d011..1218dbc8d2 100644 --- a/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx +++ b/packages/frontend-plugin-api/src/components/createSwappableComponent.tsx @@ -14,19 +14,19 @@ * limitations under the License. */ -import { OpaqueComponentRef } from '@internal/frontend'; -import { componentsApiRef, useApi } from '../apis'; +import { OpaqueSwappableComponentRef } from '@internal/frontend'; +import { swappableComponentsApiRef, useApi } from '../apis'; import { lazy, Suspense } from 'react'; /** @public */ -export type ComponentRef< +export type SwappableComponentRef< TInnerComponentProps extends {} = {}, TExternalComponentProps extends {} = TInnerComponentProps, > = { id: string; TProps: TInnerComponentProps; TExternalProps: TExternalComponentProps; - $$type: '@backstage/ComponentRef'; + $$type: '@backstage/SwappableComponentRef'; }; /** @@ -47,7 +47,7 @@ export type CreateSwappableComponentOptions< const useComponentRefApi = () => { try { - return useApi(componentsApiRef); + return useApi(swappableComponentsApiRef); } catch (e) { return undefined; } @@ -59,9 +59,9 @@ function makeComponentFromRef< >({ ref, }: { - ref: ComponentRef; + ref: SwappableComponentRef; }): (props: ExternalComponentProps) => JSX.Element { - const internalRef = OpaqueComponentRef.toInternal(ref); + const internalRef = OpaqueSwappableComponentRef.toInternal(ref); const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
); @@ -69,7 +69,7 @@ function makeComponentFromRef< const ComponentRefImpl = (props: ExternalComponentProps) => { const api = useComponentRefApi(); const ComponentOrPromise = - api?.getComponent(ref)?.() ?? + api?.getComponentLoader(ref)?.() ?? internalRef.loader?.() ?? FallbackComponent; @@ -108,25 +108,26 @@ export function createSwappableComponent< TInnerComponentProps, TExternalComponentProps >, -): ((props: TExternalComponentProps) => JSX.Element) & { - ref: ComponentRef; +): ((props: TExternalComponentProps) => JSX.Element | null) & { + ref: SwappableComponentRef; } { - const ref = OpaqueComponentRef.createInstance('v1', { + const ref = OpaqueSwappableComponentRef.createInstance('v1', { id: options.id, TProps: null as unknown as TInnerComponentProps, TExternalProps: null as unknown as TExternalComponentProps, toString() { - return `ComponentRef{id=${options.id}}`; + return `SwappableComponentRef{id=${options.id}}`; }, - loader: options.loader as (typeof OpaqueComponentRef.TInternal)['loader'], + loader: + options.loader as (typeof OpaqueSwappableComponentRef.TInternal)['loader'], transformProps: - options.transformProps as (typeof OpaqueComponentRef.TInternal)['transformProps'], + options.transformProps as (typeof OpaqueSwappableComponentRef.TInternal)['transformProps'], }); const component = makeComponentFromRef({ ref }); Object.assign(component, { ref }); return component as { - ref: ComponentRef; - } & ((props: object) => JSX.Element); + ref: SwappableComponentRef; + } & ((props: object) => JSX.Element | null); } diff --git a/packages/frontend-plugin-api/src/components/index.ts b/packages/frontend-plugin-api/src/components/index.ts index 6e28d769c9..450224bdc4 100644 --- a/packages/frontend-plugin-api/src/components/index.ts +++ b/packages/frontend-plugin-api/src/components/index.ts @@ -21,7 +21,7 @@ export { export { createSwappableComponent, type CreateSwappableComponentOptions, - type ComponentRef, + type SwappableComponentRef, } from './createSwappableComponent'; export { useAppNode } from './AppNodeProvider'; export * from './DefaultSwappableComponents'; diff --git a/plugins/app/report.api.md b/plugins/app/report.api.md index 6b9a4d3a54..c203990916 100644 --- a/plugins/app/report.api.md +++ b/plugins/app/report.api.md @@ -8,7 +8,6 @@ import { AnyApiFactory } from '@backstage/frontend-plugin-api'; import { AnyRouteRefParams } from '@backstage/frontend-plugin-api'; import { ApiFactory } from '@backstage/frontend-plugin-api'; import { AppTheme } from '@backstage/frontend-plugin-api'; -import { ComponentRef } from '@backstage/frontend-plugin-api'; import { ComponentType } from 'react'; import { ConfigurableExtensionDataRef } from '@backstage/frontend-plugin-api'; import { ExtensionBlueprintParams } from '@backstage/frontend-plugin-api'; @@ -23,6 +22,7 @@ import { NavContentComponent } from '@backstage/frontend-plugin-api'; import { ReactNode } from 'react'; import { RouteRef } from '@backstage/frontend-plugin-api'; import { SignInPageProps } from '@backstage/core-plugin-api'; +import { SwappableComponentRef } from '@backstage/frontend-plugin-api'; import { TranslationMessages } from '@backstage/frontend-plugin-api'; import { TranslationResource } from '@backstage/frontend-plugin-api'; @@ -315,38 +315,6 @@ const appPlugin: FrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; - 'api:app/components': ExtensionDefinition<{ - config: {}; - configInput: {}; - output: ExtensionDataRef; - inputs: { - components: ExtensionInput< - ConfigurableExtensionDataRef< - { - ref: ComponentRef; - loader: - | (() => (props: {}) => JSX.Element | null) - | (() => Promise<(props: {}) => JSX.Element | null>); - }, - 'core.component.component', - {} - >, - { - singleton: false; - optional: false; - } - >; - }; - kind: 'api'; - name: 'components'; - params: < - TApi, - TImpl extends TApi, - TDeps extends { [name in string]: unknown }, - >( - params: ApiFactory, - ) => ExtensionBlueprintParams; - }>; 'api:app/dialog': ExtensionDefinition<{ kind: 'api'; name: 'dialog'; @@ -616,6 +584,38 @@ const appPlugin: FrontendPlugin< params: ApiFactory, ) => ExtensionBlueprintParams; }>; + 'api:app/swappable-components': ExtensionDefinition<{ + config: {}; + configInput: {}; + output: ExtensionDataRef; + inputs: { + components: ExtensionInput< + ConfigurableExtensionDataRef< + { + ref: SwappableComponentRef; + loader: + | (() => (props: {}) => JSX.Element | null) + | (() => Promise<(props: {}) => JSX.Element | null>); + }, + 'core.component.component', + {} + >, + { + singleton: false; + optional: false; + } + >; + }; + kind: 'api'; + name: 'swappable-components'; + params: < + TApi, + TImpl extends TApi, + TDeps extends { [name in string]: unknown }, + >( + params: ApiFactory, + ) => ExtensionBlueprintParams; + }>; 'api:app/translations': ExtensionDefinition<{ config: {}; configInput: {}; @@ -735,7 +735,7 @@ const appPlugin: FrontendPlugin< configInput: {}; output: ExtensionDataRef< { - ref: ComponentRef; + ref: SwappableComponentRef; loader: | (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>); @@ -744,13 +744,19 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: >(params: { - component: Ref extends ComponentRef + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise< @@ -758,12 +764,18 @@ const appPlugin: FrontendPlugin< >) : never; }) => ExtensionBlueprintParams<{ - component: Ref extends ComponentRef + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise< @@ -779,7 +791,7 @@ const appPlugin: FrontendPlugin< configInput: {}; output: ExtensionDataRef< { - ref: ComponentRef; + ref: SwappableComponentRef; loader: | (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>); @@ -788,13 +800,19 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: >(params: { - component: Ref extends ComponentRef + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise< @@ -802,12 +820,18 @@ const appPlugin: FrontendPlugin< >) : never; }) => ExtensionBlueprintParams<{ - component: Ref extends ComponentRef + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise< @@ -823,7 +847,7 @@ const appPlugin: FrontendPlugin< configInput: {}; output: ExtensionDataRef< { - ref: ComponentRef; + ref: SwappableComponentRef; loader: | (() => (props: {}) => JSX.Element | null) | (() => Promise<(props: {}) => JSX.Element | null>); @@ -832,13 +856,19 @@ const appPlugin: FrontendPlugin< {} >; inputs: {}; - params: >(params: { - component: Ref extends ComponentRef + params: >(params: { + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise< @@ -846,12 +876,18 @@ const appPlugin: FrontendPlugin< >) : never; }) => ExtensionBlueprintParams<{ - component: Ref extends ComponentRef + component: Ref extends SwappableComponentRef< + any, + infer IExternalComponentProps + > ? { ref: Ref; - } & ((props: IExternalComponentProps) => JSX.Element) + } & ((props: IExternalComponentProps) => JSX.Element | null) : never; - loader: Ref extends ComponentRef + loader: Ref extends SwappableComponentRef< + infer IInnerComponentProps, + any + > ? | (() => (props: IInnerComponentProps) => JSX.Element | null) | (() => Promise< diff --git a/plugins/app/src/extensions/ComponentsApi.tsx b/plugins/app/src/extensions/SwappableComponentsApi.ts similarity index 78% rename from plugins/app/src/extensions/ComponentsApi.tsx rename to plugins/app/src/extensions/SwappableComponentsApi.ts index a457e05f3e..5809f90be2 100644 --- a/plugins/app/src/extensions/ComponentsApi.tsx +++ b/plugins/app/src/extensions/SwappableComponentsApi.ts @@ -18,16 +18,16 @@ import { SwappableComponentBlueprint, createExtensionInput, ApiBlueprint, - componentsApiRef, + swappableComponentsApiRef, } from '@backstage/frontend-plugin-api'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports -import { DefaultComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/ComponentsApi'; +import { DefaultSwappableComponentsApi } from '../../../../packages/frontend-app-api/src/apis/implementations/SwappableComponentsApi'; /** * Contains the shareable components installed into the app. */ -export const ComponentsApi = ApiBlueprint.makeWithOverrides({ - name: 'components', +export const SwappableComponentsApi = ApiBlueprint.makeWithOverrides({ + name: 'swappable-components', inputs: { components: createExtensionInput( [SwappableComponentBlueprint.dataRefs.component], @@ -37,10 +37,10 @@ export const ComponentsApi = ApiBlueprint.makeWithOverrides({ factory: (originalFactory, { inputs }) => { return originalFactory(defineParams => defineParams({ - api: componentsApiRef, + api: swappableComponentsApiRef, deps: {}, factory: () => - DefaultComponentsApi.fromComponents( + DefaultSwappableComponentsApi.fromComponents( inputs.components.map(i => i.get(SwappableComponentBlueprint.dataRefs.component), ), diff --git a/plugins/app/src/extensions/index.ts b/plugins/app/src/extensions/index.ts index 4bf77b1bb5..3eea920bbd 100644 --- a/plugins/app/src/extensions/index.ts +++ b/plugins/app/src/extensions/index.ts @@ -20,7 +20,7 @@ export { AppNav } from './AppNav'; export { AppRoot } from './AppRoot'; export { AppRoutes } from './AppRoutes'; export { AppThemeApi, DarkTheme, LightTheme } from './AppThemeApi'; -export { ComponentsApi } from './ComponentsApi'; +export { SwappableComponentsApi } from './SwappableComponentsApi'; export { IconsApi } from './IconsApi'; export { FeatureFlagsApi } from './FeatureFlagsApi'; export { TranslationsApi } from './TranslationsApi'; diff --git a/plugins/app/src/plugin.ts b/plugins/app/src/plugin.ts index 6a965216cf..c898334430 100644 --- a/plugins/app/src/plugin.ts +++ b/plugins/app/src/plugin.ts @@ -25,7 +25,7 @@ import { AppThemeApi, DarkTheme, LightTheme, - ComponentsApi, + SwappableComponentsApi, IconsApi, FeatureFlagsApi, TranslationsApi, @@ -54,7 +54,7 @@ export const appPlugin = createFrontendPlugin({ AppThemeApi, DarkTheme, LightTheme, - ComponentsApi, + SwappableComponentsApi, IconsApi, FeatureFlagsApi, TranslationsApi,