scaffolder: refactor TemplateFormPreviewer to use common components

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2022-04-29 14:35:13 +02:00
parent 3c87f60481
commit c98cfa0e6c
@@ -13,23 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React, { useCallback, useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import useDebounce from 'react-use/lib/useDebounce';
import { Entity } from '@backstage/catalog-model';
import { InfoCard } from '@backstage/core-components';
import { alertApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api';
import { alertApiRef, useApi } from '@backstage/core-plugin-api';
import {
catalogApiRef,
humanizeEntityRef,
} from '@backstage/plugin-catalog-react';
import { JsonObject } from '@backstage/types';
import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml';
import { showPanel } from '@codemirror/view';
import { StreamLanguage } from '@codemirror/language';
import {
FormControl,
Grid,
IconButton,
InputLabel,
LinearProgress,
@@ -38,13 +29,12 @@ import {
Select,
} from '@material-ui/core';
import CloseIcon from '@material-ui/icons/Close';
import { IChangeEvent } from '@rjsf/core';
import CodeMirror from '@uiw/react-codemirror';
import React, { useCallback, useState } from 'react';
import useAsync from 'react-use/lib/useAsync';
import yaml from 'yaml';
import { FieldExtensionOptions } from '../../extensions';
import { TemplateParameterSchema } from '../../types';
import { MultistepJsonForm } from '../MultistepJsonForm';
import { createValidator } from '../TemplatePage';
import { TemplateEditorForm } from './TemplateEditor/TemplateEditorForm';
import { TemplateEditorTextArea } from './TemplateEditor/TemplateEditorTextArea';
const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI
parameters:
@@ -91,17 +81,28 @@ type TemplateOption = {
};
const useStyles = makeStyles(theme => ({
root: {
gridArea: 'pageContent',
display: 'grid',
gridTemplateAreas: `
"controls controls"
"textArea preview"
`,
gridTemplateRows: 'auto 1fr',
gridTemplateColumns: '1fr 1fr',
},
controls: {
gridArea: 'controls',
display: 'flex',
flexFlow: 'row nowrap',
alignItems: 'center',
marginBottom: theme.spacing(1),
margin: theme.spacing(1),
},
grid: {
height: '100%',
textArea: {
gridArea: 'textArea',
},
codeMirror: {
height: '95%',
preview: {
gridArea: 'preview',
},
}));
@@ -117,12 +118,8 @@ export const TemplateFormPreviewer = ({
const classes = useStyles();
const alertApi = useApi(alertApiRef);
const catalogApi = useApi(catalogApiRef);
const apiHolder = useApiHolder();
const [selectedTemplate, setSelectedTemplate] = useState('');
const [schema, setSchema] = useState<TemplateParameterSchema>({
title: '',
steps: [],
});
const [errorText, setErrorText] = useState<string>();
const [templateOptions, setTemplateOptions] = useState<TemplateOption[]>([]);
const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate);
const [formState, setFormState] = useState({});
@@ -161,30 +158,6 @@ export const TemplateFormPreviewer = ({
[catalogApi],
);
const errorPanel = document.createElement('div');
errorPanel.style.color = 'red';
useDebounce(
() => {
try {
const parsedTemplate = yaml.parse(templateYaml);
setSchema({
title: 'Preview',
steps: parsedTemplate.parameters.map((param: JsonObject) => ({
title: param.title,
schema: param,
})),
});
setFormState({});
} catch (e) {
errorPanel.textContent = e.message;
}
},
250,
[setFormState, setSchema, templateYaml],
);
const handleSelectChange = useCallback(
selected => {
setSelectedTemplate(selected);
@@ -193,90 +166,51 @@ export const TemplateFormPreviewer = ({
[setTemplateYaml],
);
const handleFormReset = () => setFormState({});
const handleFormChange = useCallback(
(e: IChangeEvent) => setFormState(e.formData),
[setFormState],
);
const handleCodeChange = useCallback(
(code: string) => {
setTemplateYaml(code);
},
[setTemplateYaml],
);
const customFieldComponents = Object.fromEntries(
customFieldExtensions.map(({ name, component }) => [name, component]),
);
const customFieldValidators = Object.fromEntries(
customFieldExtensions.map(({ name, validation }) => [name, validation]),
);
return (
<>
{loading && <LinearProgress />}
<Grid container className={classes.grid}>
<Grid item xs={6}>
<div className={classes.controls}>
<FormControl variant="outlined" size="small" fullWidth>
<InputLabel id="select-template-label">
Load Existing Template
</InputLabel>
<Select
value={selectedTemplate}
label="Load Existing Template"
labelId="select-template-label"
onChange={e => handleSelectChange(e.target.value)}
>
{templateOptions.map((option, idx) => (
<MenuItem key={idx} value={option.value as any}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<main className={classes.root}>
<div className={classes.controls}>
<FormControl variant="outlined" size="small" fullWidth>
<InputLabel id="select-template-label">
Load Existing Template
</InputLabel>
<Select
value={selectedTemplate}
label="Load Existing Template"
labelId="select-template-label"
onChange={e => handleSelectChange(e.target.value)}
>
{templateOptions.map((option, idx) => (
<MenuItem key={idx} value={option.value as any}>
{option.label}
</MenuItem>
))}
</Select>
</FormControl>
<IconButton size="medium" onClick={onClose}>
<CloseIcon />
</IconButton>
</div>
<CodeMirror
className={classes.codeMirror}
value={templateYaml}
theme="dark"
height="100%"
extensions={[
StreamLanguage.define(yamlSupport),
showPanel.of(() => ({ dom: errorPanel, top: true })),
]}
onChange={handleCodeChange}
<IconButton size="medium" onClick={onClose}>
<CloseIcon />
</IconButton>
</div>
<div className={classes.textArea}>
<TemplateEditorTextArea
content={templateYaml}
onUpdate={setTemplateYaml}
errorText={errorText}
/>
</Grid>
<Grid item xs={6}>
{schema && (
<InfoCard key={JSON.stringify(schema)}>
<MultistepJsonForm
formData={formState}
fields={customFieldComponents}
onChange={handleFormChange}
onReset={handleFormReset}
steps={schema.steps.map(step => {
return {
...step,
validate: createValidator(
step.schema,
customFieldValidators,
{ apiHolder },
),
};
})}
/>
</InfoCard>
)}
</Grid>
</Grid>
</div>
<div className={classes.preview}>
<TemplateEditorForm
content={templateYaml}
contentIsSpec
fieldExtensions={customFieldExtensions}
data={formState}
onUpdate={setFormState}
setErrorText={setErrorText}
/>
</div>
</main>
</>
);
};