replace ui:layout with the actual rjsf field ui:ObjectFieldTemplate

Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
Paul Cowan
2022-09-13 17:42:35 +01:00
committed by blam
parent 19378018bc
commit 392b15ad27
15 changed files with 31 additions and 195 deletions
@@ -36,7 +36,7 @@ import { transformSchemaToProps } from './schema';
import { Content, StructuredMetadataTable } from '@backstage/core-components';
import cloneDeep from 'lodash/cloneDeep';
import * as fieldOverrides from './FieldOverrides';
import { LayoutOptions, resolveStepLayout } from '../../layouts';
import { LayoutOptions } from '../../layouts';
const Form = withTheme(MuiTheme);
type Step = {
@@ -193,9 +193,7 @@ export const MultistepJsonForm = (props: Props) => {
<>
<Stepper activeStep={activeStep} orientation="vertical">
{steps.map(({ title, schema, ...formProps }, index) => {
const schemaProps = transformSchemaToProps(schema);
const Layout = resolveStepLayout(schemaProps.uiSchema, layouts);
const schemaProps = transformSchemaToProps(schema, layouts);
return (
<StepUI key={title}>
@@ -210,7 +208,6 @@ export const MultistepJsonForm = (props: Props) => {
</StepLabel>
<StepContent key={title}>
<Form
ObjectFieldTemplate={Layout}
showErrorList={false}
fields={{ ...fieldOverrides, ...fields }}
widgets={widgets}
@@ -15,7 +15,8 @@
*/
import { JsonObject } from '@backstage/types';
import { FormProps } from '@rjsf/core';
import { FormProps, UiSchema } from '@rjsf/core';
import { LayoutOptions } from '../../layouts';
function isObject(value: unknown): value is JsonObject {
return typeof value === 'object' && value !== null && !Array.isArray(value);
@@ -99,14 +100,29 @@ function extractUiSchema(schema: JsonObject, uiSchema: JsonObject) {
}
}
export function transformSchemaToProps(inputSchema: JsonObject): {
export function transformSchemaToProps(
inputSchema: JsonObject,
layouts: LayoutOptions[],
): {
schema: FormProps<any>['schema'];
uiSchema: FormProps<any>['uiSchema'];
} {
const customLayoutName = inputSchema['ui:ObjectFieldTemplate'];
inputSchema.type = inputSchema.type || 'object';
const schema = JSON.parse(JSON.stringify(inputSchema));
delete schema.title; // Rendered separately
const uiSchema = {};
const uiSchema: UiSchema = {};
extractUiSchema(schema, uiSchema);
if (customLayoutName) {
const Layout = layouts.find(
layout => layout.name === customLayoutName,
)?.component;
if (Layout) {
uiSchema['ui:ObjectFieldTemplate'] = Layout;
}
}
return { schema, uiSchema };
}
+2 -15
View File
@@ -45,12 +45,7 @@ import {
selectedTemplateRouteRef,
} from '../routes';
import { ListTasksPage } from './ListTasksPage';
import {
DEFAULT_SCAFFOLDER_LAYOUT,
LayoutOptions,
LAYOUTS_KEY,
LAYOUTS_WRAPPER_KEY,
} from '../layouts';
import { LayoutOptions, LAYOUTS_KEY, LAYOUTS_WRAPPER_KEY } from '../layouts';
/**
* The props for the entrypoint `ScaffolderPage` component the plugin.
@@ -122,16 +117,8 @@ export const Router = (props: RouterProps) => {
}),
);
if (
!customLayouts.find(
layout => layout.name === DEFAULT_SCAFFOLDER_LAYOUT.name,
)
) {
customLayouts.push(DEFAULT_SCAFFOLDER_LAYOUT);
}
/**
* This component can be deleted once the older routes have been deprecated.
* This component can be deleted once the older routes havest been deprecated.
*/
const RedirectingComponent = () => {
const { templateName } = useRouteRefParams(legacySelectedTemplateRouteRef);
@@ -20,7 +20,7 @@ import React, { Component, ReactNode, useMemo, useState } from 'react';
import useDebounce from 'react-use/lib/useDebounce';
import yaml from 'yaml';
import { FieldExtensionOptions } from '../../extensions';
import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts';
import { LayoutOptions } from '../../layouts';
import { TemplateParameterSchema } from '../../types';
import { MultistepJsonForm } from '../MultistepJsonForm';
import { createValidator } from '../TemplatePage';
@@ -101,7 +101,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) {
onDryRun,
setErrorText,
fieldExtensions = [],
layouts = [DEFAULT_SCAFFOLDER_LAYOUT],
layouts = [],
} = props;
const classes = useStyles();
const apiHolder = useApiHolder();
@@ -33,7 +33,7 @@ import React, { useCallback, useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import yaml from 'yaml';
import { FieldExtensionOptions } from '../../extensions';
import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts';
import { LayoutOptions } from '../../layouts';
import { TemplateEditorForm } from './TemplateEditorForm';
import { TemplateEditorTextArea } from './TemplateEditorTextArea';
@@ -111,7 +111,7 @@ export const TemplateFormPreviewer = ({
defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML,
customFieldExtensions = [],
onClose,
layouts = [DEFAULT_SCAFFOLDER_LAYOUT],
layouts = [],
}: {
defaultPreviewTemplate?: string;
customFieldExtensions?: FieldExtensionOptions<any, any>[];
@@ -39,7 +39,7 @@ import {
useRouteRefParams,
} from '@backstage/core-plugin-api';
import { stringifyEntityRef } from '@backstage/catalog-model';
import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts';
import { LayoutOptions } from '../../layouts';
const useTemplateParameterSchema = (templateRef: string) => {
const scaffolderApi = useApi(scaffolderApiRef);
@@ -52,7 +52,7 @@ const useTemplateParameterSchema = (templateRef: string) => {
export const TemplatePage = ({
customFieldExtensions = [],
layouts = [DEFAULT_SCAFFOLDER_LAYOUT],
layouts = [],
}: {
customFieldExtensions?: FieldExtensionOptions<any, any>[];
layouts?: LayoutOptions[];
@@ -1,78 +0,0 @@
/*
* Copyright 2022 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 React from 'react';
import { ObjectFieldTemplateProps, utils } from '@rjsf/core';
import { Button, Grid } from '@material-ui/core';
const { canExpand } = utils;
export const DefaultStepFormLayout = ({
DescriptionField,
description,
TitleField,
title,
properties,
required,
disabled,
readonly,
uiSchema,
idSchema,
schema,
formData,
onAddClick,
}: ObjectFieldTemplateProps) => {
return (
<>
{(uiSchema['ui:title'] || title) && (
<TitleField
id={`${idSchema.$id}-title`}
title={title}
required={required}
/>
)}
{description && (
<DescriptionField
id={`${idSchema.$id}-description`}
description={description}
/>
)}
<Grid container spacing={2} style={{ marginTop: '10px' }}>
{properties.map((element, index) =>
// Remove the <Grid> if the inner element is hidden as the <Grid>
// itself would otherwise still take up space.
element.hidden ? (
element.content
) : (
<Grid item xs={12} key={index} style={{ marginBottom: '10px' }}>
{element.content}
</Grid>
),
)}
{canExpand(schema, uiSchema, formData) && (
<Grid container justifyContent="flex-end">
<Grid item>
<Button
className="object-property-expand"
onClick={onAddClick(schema)}
disabled={disabled || readonly}
/>
</Grid>
</Grid>
)}
</Grid>
</>
);
};
@@ -1,16 +0,0 @@
/*
* Copyright 2022 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.
*/
export { DefaultStepFormLayout } from './DefaultStepFormLayout';
-21
View File
@@ -1,21 +0,0 @@
import { DefaultStepFormLayout } from '../components/layouts/DefaultStepFormLayout';
/*
* Copyright 2022 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.
*/
export const DEFAULT_SCAFFOLDER_LAYOUT = {
component: DefaultStepFormLayout,
name: 'DefaultStepFormLayout',
};
-4
View File
@@ -69,7 +69,3 @@ export const ScaffolderLayouts: React.ComponentType = (): JSX.Element | null =>
attachComponentData(ScaffolderLayouts, LAYOUTS_WRAPPER_KEY, true);
export type { LayoutOptions, LayoutTemplate } from './types';
export { DEFAULT_SCAFFOLDER_LAYOUT } from './default';
export { resolveStepLayout } from './resolveStepLayout';
@@ -1,39 +0,0 @@
/*
* Copyright 2022 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 { UiSchema } from '@rjsf/core';
import { DefaultStepFormLayout } from '../components/layouts/DefaultStepFormLayout';
import { DEFAULT_SCAFFOLDER_LAYOUT } from './default';
import { LayoutOptions, LayoutTemplate } from './types';
export function resolveStepLayout(
uiSchema: UiSchema = {},
layouts: LayoutOptions[],
): LayoutTemplate {
const layoutName = uiSchema?.['ui:layout'] ?? DEFAULT_SCAFFOLDER_LAYOUT.name;
delete uiSchema?.['ui:layout'];
const LayoutComponent = layouts.find(
layout => layout.name === layoutName,
)?.component;
if (!LayoutComponent) {
return DefaultStepFormLayout;
}
return LayoutComponent;
}
@@ -49,7 +49,6 @@ const useStyles = makeStyles(theme => ({
export interface StepperProps {
manifest: TemplateParameterSchema;
extensions: FieldExtensionOptions<any, any>[];
layout?: LayoutOptions;
}
const Form = withTheme(MuiTheme);
@@ -41,7 +41,6 @@ describe('useTemplateSchema', () => {
description: 'Step 2 Description',
schema: {
type: 'object',
'ui:layout': 'MyLayout',
properties: {
field2: { type: 'string', 'ui:field': 'MyCoolerComponent' },
},
@@ -75,7 +74,6 @@ describe('useTemplateSchema', () => {
expect(second.uiSchema).toEqual({
field2: { 'ui:field': 'MyCoolerComponent' },
'ui:layout': 'MyLayout',
});
expect(second.schema).toEqual({