chore: make more things swoppable
Signed-off-by: benjdlambert <ben@blam.sh> Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -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<CoreProgressProps>;
|
||||
readonly #NotFoundErrorPage: ComponentType<CoreNotFoundErrorPageProps>;
|
||||
readonly #ErrorBoundaryFallback: ComponentType<CoreErrorBoundaryFallbackProps>;
|
||||
@@ -69,11 +69,11 @@ class CompatComponentsApi implements ComponentsApi {
|
||||
this.#ErrorBoundaryFallback = ErrorBoundaryFallback;
|
||||
}
|
||||
|
||||
getComponent<
|
||||
getComponentLoader<
|
||||
TInnerComponentProps extends {},
|
||||
TExternalComponentProps extends {} = TInnerComponentProps,
|
||||
>(
|
||||
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>,
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
|
||||
):
|
||||
| (() => (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<T>(ref: ApiRef<any>): 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;
|
||||
|
||||
@@ -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 (
|
||||
<div data-testid="ctx">
|
||||
@@ -114,7 +114,7 @@ describe('ForwardsCompatProvider', () => {
|
||||
{Object.entries(defaultComponentRefs)
|
||||
.map(
|
||||
([name, ref]) =>
|
||||
`${name}=${Boolean(components.getComponent(ref))}`,
|
||||
`${name}=${Boolean(components.getComponentLoader(ref))}`,
|
||||
)
|
||||
.join(', ')}
|
||||
{'\n'}
|
||||
|
||||
+13
-6
@@ -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: () => () => <div>test.a</div>,
|
||||
},
|
||||
]);
|
||||
|
||||
const ComponentA = api.getComponent(testRefA)?.() as () => JSX.Element;
|
||||
const ComponentA = api.getComponentLoader(
|
||||
testRefA,
|
||||
)?.() as () => JSX.Element;
|
||||
|
||||
render(<ComponentA />);
|
||||
|
||||
@@ -40,15 +42,20 @@ describe('DefaultComponentsApi', () => {
|
||||
|
||||
it('should key extension refs by ID', () => {
|
||||
const mockLoader = jest.fn(() => <div>test.b</div>);
|
||||
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);
|
||||
|
||||
+6
-6
@@ -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<typeof SwappableComponentBlueprint.dataRefs.component.T>,
|
||||
) {
|
||||
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<any>,
|
||||
getComponentLoader(
|
||||
ref: SwappableComponentRef<any>,
|
||||
):
|
||||
| (() => (props: object) => JSX.Element | null)
|
||||
| (() => Promise<(props: object) => JSX.Element | null>)
|
||||
+1
-1
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { DefaultComponentsApi } from './DefaultComponentsApi';
|
||||
export { DefaultSwappableComponentsApi } from './DefaultSwappableComponentsApi';
|
||||
+4
-4
@@ -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',
|
||||
});
|
||||
@@ -15,6 +15,6 @@
|
||||
*/
|
||||
|
||||
export { createExtensionDataContainer } from './createExtensionDataContainer';
|
||||
export { OpaqueComponentRef } from './InternalComponentRef';
|
||||
export { OpaqueSwappableComponentRef } from './InternalSwappableComponentRef';
|
||||
export { OpaqueExtensionDefinition } from './InternalExtensionDefinition';
|
||||
export { OpaqueFrontendPlugin } from './InternalFrontendPlugin';
|
||||
|
||||
@@ -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<TInnerComponentProps, TExternalComponentProps>,
|
||||
):
|
||||
| (() => (props: TInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const componentsApiRef: ApiRef<ComponentsApi>;
|
||||
|
||||
export { ConfigApi };
|
||||
|
||||
export { configApiRef };
|
||||
@@ -832,8 +804,8 @@ export function createSwappableComponent<
|
||||
TInnerComponentProps,
|
||||
TExternalComponentProps
|
||||
>,
|
||||
): ((props: TExternalComponentProps) => JSX.Element) & {
|
||||
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
): ((props: TExternalComponentProps) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
};
|
||||
|
||||
// @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<CoreNotFoundErrorPageProps, CoreNotFoundErrorPageProps>;
|
||||
) => 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<CoreProgressProps, CoreProgressProps>;
|
||||
export const Progress: ((props: CoreProgressProps) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<CoreProgressProps, CoreProgressProps>;
|
||||
};
|
||||
|
||||
// @public
|
||||
@@ -1854,24 +1829,30 @@ export interface SubRouteRef<
|
||||
// @public
|
||||
export const SwappableComponentBlueprint: ExtensionBlueprint<{
|
||||
kind: 'component';
|
||||
params: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
params: <Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
|
||||
: never;
|
||||
}) => ExtensionBlueprintParams<{
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (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<TInnerComponentProps, TExternalComponentProps>,
|
||||
):
|
||||
| (() => (props: TInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: TInnerComponentProps) => JSX.Element | null>)
|
||||
| undefined;
|
||||
}
|
||||
|
||||
// @public
|
||||
export const swappableComponentsApiRef: ApiRef<SwappableComponentsApi>;
|
||||
|
||||
// @public
|
||||
export const ThemeBlueprint: ExtensionBlueprint<{
|
||||
kind: 'theme';
|
||||
|
||||
+7
-7
@@ -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<TInnerComponentProps, TExternalComponentProps>,
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>,
|
||||
):
|
||||
| (() => (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<ComponentsApi>({
|
||||
id: 'core.components',
|
||||
export const swappableComponentsApiRef = createApiRef<SwappableComponentsApi>({
|
||||
id: 'core.swappableComponents',
|
||||
});
|
||||
@@ -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';
|
||||
|
||||
@@ -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<Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element)
|
||||
defineParams<Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? { ref: Ref } & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
loader: Ref extends SwappableComponentRef<infer IInnerComponentProps, any>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<(props: IInnerComponentProps) => JSX.Element | null>)
|
||||
|
||||
@@ -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<InternalComponentProps, ExternalComponentProps>;
|
||||
ref: SwappableComponentRef<InternalComponentProps, ExternalComponentProps>;
|
||||
}): (props: ExternalComponentProps) => JSX.Element {
|
||||
const internalRef = OpaqueComponentRef.toInternal(ref);
|
||||
const internalRef = OpaqueSwappableComponentRef.toInternal(ref);
|
||||
const FallbackComponent = (p: JSX.IntrinsicAttributes) => (
|
||||
<div data-testid={ref.id} {...p} />
|
||||
);
|
||||
@@ -69,7 +69,7 @@ function makeComponentFromRef<
|
||||
const ComponentRefImpl = (props: ExternalComponentProps) => {
|
||||
const api = useComponentRefApi();
|
||||
const ComponentOrPromise =
|
||||
api?.getComponent<any>(ref)?.() ??
|
||||
api?.getComponentLoader<any>(ref)?.() ??
|
||||
internalRef.loader?.() ??
|
||||
FallbackComponent;
|
||||
|
||||
@@ -108,25 +108,26 @@ export function createSwappableComponent<
|
||||
TInnerComponentProps,
|
||||
TExternalComponentProps
|
||||
>,
|
||||
): ((props: TExternalComponentProps) => JSX.Element) & {
|
||||
ref: ComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
): ((props: TExternalComponentProps) => JSX.Element | null) & {
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
} {
|
||||
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<TInnerComponentProps, TExternalComponentProps>;
|
||||
} & ((props: object) => JSX.Element);
|
||||
ref: SwappableComponentRef<TInnerComponentProps, TExternalComponentProps>;
|
||||
} & ((props: object) => JSX.Element | null);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export {
|
||||
export {
|
||||
createSwappableComponent,
|
||||
type CreateSwappableComponentOptions,
|
||||
type ComponentRef,
|
||||
type SwappableComponentRef,
|
||||
} from './createSwappableComponent';
|
||||
export { useAppNode } from './AppNodeProvider';
|
||||
export * from './DefaultSwappableComponents';
|
||||
|
||||
+93
-57
@@ -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<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/components': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
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<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/dialog': ExtensionDefinition<{
|
||||
kind: 'api';
|
||||
name: 'dialog';
|
||||
@@ -616,6 +584,38 @@ const appPlugin: FrontendPlugin<
|
||||
params: ApiFactory<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'api:app/swappable-components': ExtensionDefinition<{
|
||||
config: {};
|
||||
configInput: {};
|
||||
output: ExtensionDataRef<AnyApiFactory, 'core.api.factory', {}>;
|
||||
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<TApi, TImpl, TDeps>,
|
||||
) => ExtensionBlueprintParams<AnyApiFactory>;
|
||||
}>;
|
||||
'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: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
params: <Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
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<any, infer IExternalComponentProps>
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
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: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
params: <Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
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<any, infer IExternalComponentProps>
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
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: <Ref extends ComponentRef<any>>(params: {
|
||||
component: Ref extends ComponentRef<any, infer IExternalComponentProps>
|
||||
params: <Ref extends SwappableComponentRef<any>>(params: {
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
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<any, infer IExternalComponentProps>
|
||||
component: Ref extends SwappableComponentRef<
|
||||
any,
|
||||
infer IExternalComponentProps
|
||||
>
|
||||
? {
|
||||
ref: Ref;
|
||||
} & ((props: IExternalComponentProps) => JSX.Element)
|
||||
} & ((props: IExternalComponentProps) => JSX.Element | null)
|
||||
: never;
|
||||
loader: Ref extends ComponentRef<infer IInnerComponentProps, any>
|
||||
loader: Ref extends SwappableComponentRef<
|
||||
infer IInnerComponentProps,
|
||||
any
|
||||
>
|
||||
?
|
||||
| (() => (props: IInnerComponentProps) => JSX.Element | null)
|
||||
| (() => Promise<
|
||||
|
||||
+6
-6
@@ -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),
|
||||
),
|
||||
@@ -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';
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user