replace ui:layout with the actual rjsf field ui:ObjectFieldTemplate
Signed-off-by: Paul Cowan <paul.cowan@cutting.scot>
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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[];
|
||||
|
||||
-78
@@ -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';
|
||||
@@ -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',
|
||||
};
|
||||
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user