diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index 43e12ae85f..97c10c34ec 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -55,6 +55,7 @@ type Props = { widgets?: FormProps['widgets']; fields?: FormProps['fields']; finishButtonLabel?: string; + layout?: FormProps['ObjectFieldTemplate']; }; export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { @@ -119,6 +120,7 @@ export const MultistepJsonForm = (props: Props) => { fields, widgets, finishButtonLabel, + layout, } = props; const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); @@ -203,6 +205,7 @@ export const MultistepJsonForm = (props: Props) => {
{ ), ), ]; + + const customLayouts = useElementFilter(outlet, elements => + elements + .selectByComponentData({ + key: LAYOUTS_WRAPPER_KEY, + }) + .findComponentData({ + key: LAYOUTS_KEY, + }), + ); + + const layout = customLayouts?.[0] ?? DEFAULT_SCAFFOLDER_LAYOUT; + /** * This component can be deleted once the older routes have been deprecated. */ @@ -142,7 +161,10 @@ export const Router = (props: RouterProps) => { path={selectedTemplateRouteRef.path} element={ - + } /> @@ -159,6 +181,7 @@ export const Router = (props: RouterProps) => { } diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx index cc4dd8c9a1..68ad355b61 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -16,6 +16,7 @@ import { makeStyles } from '@material-ui/core'; import React, { useState } from 'react'; import { FieldExtensionOptions } from '../../extensions'; +import { LayoutOptions } from '../../layouts'; import { TemplateDirectoryAccess } from '../../lib/filesystem'; import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { DryRunProvider } from './DryRunContext'; @@ -57,6 +58,7 @@ const useStyles = makeStyles({ export const TemplateEditor = (props: { directory: TemplateDirectoryAccess; fieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; onClose?: () => void; }) => { const classes = useStyles(); @@ -77,6 +79,7 @@ export const TemplateEditor = (props: {
diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx index d802ab07dc..8b9fea6b30 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx @@ -20,6 +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 { TemplateParameterSchema } from '../../types'; import { MultistepJsonForm } from '../MultistepJsonForm'; import { createValidator } from '../TemplatePage'; @@ -83,6 +84,7 @@ interface TemplateEditorFormProps { onDryRun?: (data: JsonObject) => Promise; fieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; } function isJsonObject(value: JsonValue | undefined): value is JsonObject { @@ -99,6 +101,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { onDryRun, setErrorText, fieldExtensions = [], + layout = DEFAULT_SCAFFOLDER_LAYOUT, } = props; const classes = useStyles(); const apiHolder = useApiHolder(); @@ -188,6 +191,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { onReset={() => onUpdate({})} finishButtonLabel={onDryRun && 'Try It'} onFinish={onDryRun && (() => onDryRun(data))} + layout={layout.component} /> @@ -197,7 +201,10 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { /** A version of the TemplateEditorForm that is connected to the DirectoryEditor and DryRun contexts */ export function TemplateEditorFormDirectoryEditorDryRun( - props: Pick, + props: Pick< + TemplateEditorFormProps, + 'setErrorText' | 'fieldExtensions' | 'layout' + >, ) { const { setErrorText, fieldExtensions = [] } = props; const dryRun = useDryRun(); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index fce2881190..2995678248 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -23,6 +23,7 @@ import { TemplateEditorIntro } from './TemplateEditorIntro'; import { TemplateEditor } from './TemplateEditor'; import { TemplateFormPreviewer } from './TemplateFormPreviewer'; import { FieldExtensionOptions } from '../../extensions'; +import { LayoutOptions } from '../../layouts'; type Selection = | { @@ -36,6 +37,7 @@ type Selection = interface TemplateEditorPageProps { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; } export function TemplateEditorPage(props: TemplateEditorPageProps) { @@ -48,6 +50,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { directory={selection.directory} fieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} + layout={props.layout} /> ); } else if (selection?.type === 'form') { @@ -56,6 +59,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { defaultPreviewTemplate={props.defaultPreviewTemplate} customFieldExtensions={props.customFieldExtensions} onClose={() => setSelection(undefined)} + layout={props.layout} /> ); } else { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index bfa7071d48..1e3758ebba 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -33,6 +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 { TemplateEditorForm } from './TemplateEditorForm'; import { TemplateEditorTextArea } from './TemplateEditorTextArea'; @@ -110,10 +111,12 @@ export const TemplateFormPreviewer = ({ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, customFieldExtensions = [], onClose, + layout = DEFAULT_SCAFFOLDER_LAYOUT, }: { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; + layout?: LayoutOptions; }) => { const classes = useStyles(); const alertApi = useApi(alertApiRef); @@ -208,6 +211,7 @@ export const TemplateFormPreviewer = ({ data={formState} onUpdate={setFormState} setErrorText={setErrorText} + layout={layout} /> diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx index 03ce57136f..2d2ab3989e 100644 --- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx +++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.tsx @@ -39,6 +39,7 @@ import { useRouteRefParams, } from '@backstage/core-plugin-api'; import { stringifyEntityRef } from '@backstage/catalog-model'; +import { DEFAULT_SCAFFOLDER_LAYOUT, LayoutOptions } from '../../layouts'; const useTemplateParameterSchema = (templateRef: string) => { const scaffolderApi = useApi(scaffolderApiRef); @@ -51,8 +52,10 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplatePage = ({ customFieldExtensions = [], + layout = DEFAULT_SCAFFOLDER_LAYOUT, }: { customFieldExtensions?: FieldExtensionOptions[]; + layout?: LayoutOptions; }) => { const apiHolder = useApiHolder(); const secretsContext = useContext(SecretsContext); @@ -146,6 +149,7 @@ export const TemplatePage = ({ onChange={handleChange} onReset={handleFormReset} onFinish={handleCreate} + layout={layout.component} steps={schema.steps.map(step => { return { ...step, diff --git a/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx b/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx new file mode 100644 index 0000000000..871f568383 --- /dev/null +++ b/plugins/scaffolder/src/components/layouts/DefaultStepFormLayout/DefaultStepFormLayout.tsx @@ -0,0 +1,79 @@ +/* + * 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 ( + <> +

THIS IS OUR OBJECTFIELDTEMPLATE!!!!!!

+ {(uiSchema['ui:title'] || title) && ( + + )} + {description && ( + + )} + + {properties.map((element, index) => + // Remove the if the inner element is hidden as the + // itself would otherwise still take up space. + element.hidden ? ( + element.content + ) : ( + + {element.content} + + ), + )} + {canExpand(schema, uiSchema, formData) && ( + + +