chore: wip

Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
blam
2024-07-30 15:47:45 +02:00
parent 3cd012c2dc
commit 66523e8d01
3 changed files with 223 additions and 14 deletions
+41 -7
View File
@@ -20,6 +20,7 @@ import { pagesPlugin } from './examples/pagesPlugin';
import notFoundErrorPage from './examples/notFoundErrorPageExtension';
import userSettingsPlugin from '@backstage/plugin-user-settings/alpha';
import homePlugin, {
extensions,
titleExtensionDataRef,
} from '@backstage/plugin-home/alpha';
@@ -74,13 +75,46 @@ TODO:
/* app.tsx */
const homePageExtension = createExtension({
name: 'myhomepage',
attachTo: { id: 'page:home', input: 'props' },
output: {
children: coreExtensionData.reactElement,
title: titleExtensionDataRef,
},
// const homePageExtension = createExtension({
// name: 'myhomepage',
// attachTo: { id: 'page:home', input: 'props' },
// output: {
// children: coreExtensionData.reactElement,
// title: titleExtensionDataRef,
// },
// factory() {
// return { children: homePage, title: 'just a title' };
// },
// });
// const homePage = createPageExtension({
// defaultPath: '/home',
// routeRef: rootRouteRef,
// inputs: {
// props: createExtensionInput(
// {
// children: coreExtensionData.reactElement.optional(),
// title: titleExtensionDataRef.optional(),
// },
// {
// singleton: true,
// optional: true,
// },
// ),
// },
// loader: ({ inputs }) =>
// import('./components/').then(m =>
// compatWrapper(
// <m.HomepageCompositionRoot
// children={inputs.props?.output.children}
// title={inputs.props?.output.title}
// />,
// ),
// ),
// });
const homePageExtension = extensions.homePage.override({
factory() {
return { children: homePage, title: 'just a title' };
},
@@ -22,6 +22,9 @@ import {
createExtension,
ResolvedExtensionInputs,
AnyExtensionInputMap,
createExtensionBlueprint,
ExtensionInput,
AnyExtensionDataRef,
} from '../wiring';
import { RouteRef } from '../routing';
import { Expand } from '../types';
@@ -95,3 +98,131 @@ export function createPageExtension<
},
});
}
export function createNewPageExtension<
TConfig extends { path: string },
TInputs extends {
[inputName in string]: ExtensionInput<
AnyExtensionDataRef,
{ optional: boolean; singleton: boolean }
>;
},
>(
options: (
| {
defaultPath: string;
}
| {
configSchema: PortableSchema<TConfig>;
}
) & {
namespace?: string;
name?: string;
attachTo?: { id: string; input: string };
disabled?: boolean;
inputs?: TInputs;
routeRef?: RouteRef;
loader: (options: {
config: TConfig;
inputs: Expand<ResolvedExtensionInputs<TInputs>>;
}) => Promise<JSX.Element>;
},
): ExtensionDefinition<TConfig> {
const configSchema =
'configSchema' in options
? options.configSchema
: (createSchemaFromZod(z =>
z.object({ path: z.string().default(options.defaultPath) }),
) as PortableSchema<TConfig>);
return createExtension({
kind: 'page',
namespace: options.namespace,
name: options.name,
attachTo: options.attachTo ?? { id: 'app/routes', input: 'routes' },
configSchema,
inputs: options.inputs,
disabled: options.disabled,
output: [
coreExtensionData.routePath,
coreExtensionData.reactElement,
coreExtensionData.routeRef.optional(),
],
factory({ config, inputs, node }) {
const ExtensionComponent = lazy(() =>
options
.loader({ config, inputs })
.then(element => ({ default: () => element })),
);
const outputs = [
coreExtensionData.routePath(config.path),
coreExtensionData.reactElement(
<ExtensionBoundary node={node}>
<ExtensionComponent />
</ExtensionBoundary>,
),
];
if (options.routeRef) {
return [...outputs, coreExtensionData.routeRef(options.routeRef)];
}
return outputs;
},
});
}
/**
* A blueprint for creating extensions for routable React page components.
* @public
*/
export const PageExtensionBlueprint = createExtensionBlueprint({
kind: 'page',
attachTo: { id: 'app/routes', input: 'routes' },
output: [
coreExtensionData.routePath,
coreExtensionData.reactElement,
coreExtensionData.routeRef.optional(),
],
config: {
schema: {
path: z => z.string().optional(),
},
},
factory(
{
defaultPath,
loader,
routeRef,
}: {
defaultPath?: string;
// TODO(blam) This type is impossible to type properly here as we don't have access
// to the input type generic. Maybe not a deal breaker though.
// It means we have to override the factory function in the `.make` method instead.
loader: (opts: unknown) => Promise<JSX.Element>;
routeRef?: RouteRef;
},
{ config, inputs, node },
) {
const ExtensionComponent = lazy(() =>
loader({ config, inputs }).then(element => ({ default: () => element })),
);
// TODO(blam): this is a little awkward for optional returns. I wonder if we should be using generators or yield instead
// for a better API here.
const outputs = [
coreExtensionData.routePath(config.path ?? defaultPath!),
coreExtensionData.reactElement(
<ExtensionBoundary node={node}>
<ExtensionComponent />
</ExtensionBoundary>,
),
];
if (routeRef) {
return [...outputs, coreExtensionData.routeRef(routeRef)];
}
return outputs;
},
});
+51 -7
View File
@@ -25,6 +25,10 @@ import {
createRouteRef,
} from '@backstage/frontend-plugin-api';
import { compatWrapper } from '@backstage/core-compat-api';
import {
PageExtensionBlueprint,
createNewPageExtension,
} from '@backstage/frontend-plugin-api/src/extensions/createPageExtension';
const rootRouteRef = createRouteRef();
@@ -35,15 +39,15 @@ export const titleExtensionDataRef = createExtensionDataRef<string>().with({
id: 'title',
});
const homePage = createPageExtension({
const homePage = createNewPageExtension({
defaultPath: '/home',
routeRef: rootRouteRef,
inputs: {
props: createExtensionInput(
{
children: coreExtensionData.reactElement.optional(),
title: titleExtensionDataRef.optional(),
},
[
coreExtensionData.reactElement.optional(),
titleExtensionDataRef.optional(),
],
{
singleton: true,
@@ -55,13 +59,53 @@ const homePage = createPageExtension({
import('./components/').then(m =>
compatWrapper(
<m.HomepageCompositionRoot
children={inputs.props?.output.children}
title={inputs.props?.output.title}
children={inputs.props?.get(coreExtensionData.reactElement)}
title={inputs.props?.get(titleExtensionDataRef)}
/>,
),
),
});
homePage.override({
factory() {},
});
// const homePage2 = PageExtensionBlueprint.make({
// inputs: {
// props: createExtensionInput(
// [
// coreExtensionData.reactElement.optional(),
// titleExtensionDataRef.optional(),
// ],
// {
// singleton: true,
// optional: true,
// },
// ),
// },
// factory(origFactory, { config, inputs, node }) {
// return origFactory({
// defaultPath: '/home',
// routeRef: rootRouteRef,
// loader: () =>
// import('./components/').then(m =>
// compatWrapper(
// <m.HomepageCompositionRoot
// children={inputs.props?.[0].get(coreExtensionData.reactElement)}
// title={inputs.props?.[0].get(coreExtensionData.reactElement}
// />,
// ),
// },
// });
/**
* @alpha
* This will not be the way to export extensions eventually,
* will be something like `homePlugin.extensions.homePage` or `homePlugin.extensions.get('page')`
* Just haven't worked out a nice way to fix the types yet
*/
export const extensions = { homePage };
/**
* @alpha
*/