mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-01-30 11:45:48 +00:00
445 lines
12 KiB
TypeScript
445 lines
12 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
// @ts-ignore
|
|
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|
import {
|
|
Button,
|
|
Select,
|
|
SelectItem,
|
|
TextInput,
|
|
Grid,
|
|
Column,
|
|
// @ts-ignore
|
|
} from '@carbon/react';
|
|
import validator from '@rjsf/validator-ajv8';
|
|
import { FormField, JsonSchemaForm } from '../interfaces';
|
|
import { Form } from '../themes/carbon';
|
|
import {
|
|
modifyProcessIdentifierForPathParam,
|
|
slugifyString,
|
|
underscorizeString,
|
|
} from '../helpers';
|
|
import HttpService from '../services/HttpService';
|
|
import { Notification } from '../components/Notification';
|
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
|
|
|
export default function JsonSchemaFormBuilder() {
|
|
const params = useParams();
|
|
const navigate = useNavigate();
|
|
const [searchParams] = useSearchParams();
|
|
const formFieldTypes = ['textbox', 'checkbox', 'select'];
|
|
|
|
const [formTitle, setFormTitle] = useState<string>('');
|
|
const [formDescription, setFormDescription] = useState<string>('');
|
|
const [formId, setFormId] = useState<string>('');
|
|
const [formFields, setFormFields] = useState<FormField[]>([]);
|
|
const [showNewFormField, setShowNewFormField] = useState<boolean>(false);
|
|
const [formFieldSelectOptions, setFormFieldSelectOptions] =
|
|
useState<string>('');
|
|
const [formIdHasBeenUpdatedByUser, setFormIdHasBeenUpdatedByUser] =
|
|
useState<boolean>(false);
|
|
const [formFieldIdHasBeenUpdatedByUser, setFormFieldIdHasBeenUpdatedByUser] =
|
|
useState<boolean>(false);
|
|
const [showFormFieldSelectTextField, setShowFormFieldSelectTextField] =
|
|
useState<boolean>(false);
|
|
|
|
const [formFieldId, setFormFieldId] = useState<string>('');
|
|
const [formFieldTitle, setFormFieldTitle] = useState<string>('');
|
|
const [formFieldType, setFormFieldType] = useState<string>('');
|
|
const [requiredFields, setRequiredFields] = useState<string[]>([]);
|
|
const [savedJsonSchema, setSavedJsonSchema] = useState<boolean>(false);
|
|
|
|
const modifiedProcessModelId = modifyProcessIdentifierForPathParam(
|
|
`${params.process_model_id}`
|
|
);
|
|
|
|
useEffect(() => {
|
|
const processResult = (result: JsonSchemaForm) => {
|
|
const jsonForm = JSON.parse(result.file_contents);
|
|
setFormTitle(jsonForm.title);
|
|
setFormDescription(jsonForm.description);
|
|
setRequiredFields(jsonForm.required);
|
|
const newFormId = (searchParams.get('file_name') || '').replace(
|
|
'-schema.json',
|
|
''
|
|
);
|
|
setFormId(newFormId);
|
|
const newFormFields: FormField[] = [];
|
|
Object.keys(jsonForm.properties).forEach((propertyId: string) => {
|
|
const propertyDetails = jsonForm.properties[propertyId];
|
|
newFormFields.push({
|
|
id: propertyId,
|
|
title: propertyDetails.title,
|
|
required: propertyDetails.required,
|
|
type: propertyDetails.type,
|
|
enum: propertyDetails.enum,
|
|
default: propertyDetails.default,
|
|
pattern: propertyDetails.pattern,
|
|
});
|
|
});
|
|
setFormFields(newFormFields);
|
|
};
|
|
if (searchParams.get('file_name')) {
|
|
HttpService.makeCallToBackend({
|
|
path: `/process-models/${modifiedProcessModelId}/files/${searchParams.get(
|
|
'file_name'
|
|
)}`,
|
|
successCallback: processResult,
|
|
});
|
|
}
|
|
}, [modifiedProcessModelId, searchParams]);
|
|
|
|
const formSubmitResultElement = () => {
|
|
if (savedJsonSchema) {
|
|
return (
|
|
<Notification
|
|
title="Form Saved"
|
|
onClose={() => setSavedJsonSchema(false)}
|
|
>
|
|
It saved
|
|
</Notification>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const renderFormJson = () => {
|
|
const formJson = {
|
|
title: formTitle,
|
|
description: formDescription,
|
|
properties: {},
|
|
required: requiredFields,
|
|
};
|
|
|
|
formFields.forEach((formField: FormField) => {
|
|
let jsonSchemaFieldType = formField.type;
|
|
if (['checkbox'].includes(formField.type)) {
|
|
jsonSchemaFieldType = 'boolean';
|
|
}
|
|
const formJsonObject: any = {
|
|
type: jsonSchemaFieldType || 'string',
|
|
title: formField.title,
|
|
};
|
|
|
|
if (formField.enum) {
|
|
formJsonObject.enum = formField.enum;
|
|
}
|
|
if (formField.default !== undefined) {
|
|
formJsonObject.default = formField.default;
|
|
}
|
|
if (formField.pattern) {
|
|
formJsonObject.pattern = formField.pattern;
|
|
}
|
|
(formJson.properties as any)[formField.id] = formJsonObject;
|
|
});
|
|
|
|
return JSON.stringify(formJson, null, 2);
|
|
};
|
|
|
|
const renderFormUiJson = () => {
|
|
const uiOrder = formFields.map((formField: FormField) => {
|
|
return formField.id;
|
|
});
|
|
return JSON.stringify({ 'ui:order': uiOrder }, null, 2);
|
|
};
|
|
|
|
const onFormFieldTitleChange = (newFormFieldTitle: string) => {
|
|
if (!formFieldIdHasBeenUpdatedByUser) {
|
|
setFormFieldId(underscorizeString(newFormFieldTitle));
|
|
}
|
|
setFormFieldTitle(newFormFieldTitle);
|
|
};
|
|
|
|
const onFormTitleChange = (newFormTitle: string) => {
|
|
if (!formIdHasBeenUpdatedByUser) {
|
|
setFormId(slugifyString(newFormTitle));
|
|
}
|
|
setFormTitle(newFormTitle);
|
|
};
|
|
|
|
const getFormFieldType = (indicatedType: string) => {
|
|
if (indicatedType === 'checkbox') {
|
|
return 'boolean';
|
|
}
|
|
// undefined or 'select' or 'textbox'
|
|
return 'string';
|
|
};
|
|
|
|
const addFormField = () => {
|
|
const newFormField: FormField = {
|
|
id: formFieldId,
|
|
title: formFieldTitle,
|
|
required: false,
|
|
type: getFormFieldType(formFieldType),
|
|
enum: showFormFieldSelectTextField
|
|
? formFieldSelectOptions.split(',')
|
|
: undefined,
|
|
};
|
|
|
|
setFormFieldIdHasBeenUpdatedByUser(false);
|
|
setShowNewFormField(false);
|
|
setFormFields([...formFields, newFormField]);
|
|
};
|
|
|
|
const handleFormFieldTypeChange = (event: any) => {
|
|
setFormFieldType(event.srcElement.value);
|
|
|
|
if (event.srcElement.value === 'select') {
|
|
setShowFormFieldSelectTextField(true);
|
|
} else {
|
|
setShowFormFieldSelectTextField(false);
|
|
}
|
|
};
|
|
|
|
const newFormFieldComponent = () => {
|
|
if (showNewFormField) {
|
|
return (
|
|
<>
|
|
<TextInput
|
|
id="form-field-title"
|
|
name="title"
|
|
labelText="Title"
|
|
value={formFieldTitle}
|
|
onChange={(event: any) => {
|
|
onFormFieldTitleChange(event.srcElement.value);
|
|
}}
|
|
/>
|
|
<TextInput
|
|
id="json-form-field-id"
|
|
name="id"
|
|
labelText="ID"
|
|
value={formFieldId}
|
|
onChange={(event: any) => {
|
|
setFormFieldIdHasBeenUpdatedByUser(true);
|
|
setFormFieldId(event.srcElement.value);
|
|
}}
|
|
/>
|
|
<Select
|
|
id="form-field-type"
|
|
labelText="Type"
|
|
onChange={handleFormFieldTypeChange}
|
|
>
|
|
{formFieldTypes.map((fft: string) => {
|
|
return <SelectItem text={fft} value={fft} />;
|
|
})}
|
|
</Select>
|
|
{showFormFieldSelectTextField ? (
|
|
<TextInput
|
|
id="json-form-field-select-options"
|
|
name="select-options"
|
|
labelText="Select Options"
|
|
onChange={(event: any) => {
|
|
setFormFieldSelectOptions(event.srcElement.value);
|
|
}}
|
|
/>
|
|
) : null}
|
|
<Button onClick={addFormField}>Add Field</Button>
|
|
</>
|
|
);
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const formFieldArea = () => {
|
|
if (formFields.length > 0) {
|
|
return formFields.map((formField: FormField) => {
|
|
return <p>Form Field: {formField.id}</p>;
|
|
});
|
|
}
|
|
return null;
|
|
};
|
|
|
|
const handleSaveCallback = () => {
|
|
setSavedJsonSchema(true);
|
|
};
|
|
|
|
const uploadFile = (file: File) => {
|
|
const url = `/process-models/${modifiedProcessModelId}/files`;
|
|
const httpMethod = 'POST';
|
|
const formData = new FormData();
|
|
formData.append('file', file);
|
|
formData.append('fileName', file.name);
|
|
|
|
HttpService.makeCallToBackend({
|
|
path: url,
|
|
successCallback: handleSaveCallback,
|
|
httpMethod,
|
|
postBody: formData,
|
|
});
|
|
};
|
|
|
|
const saveFile = () => {
|
|
setSavedJsonSchema(false);
|
|
let formJsonFileName = `${formId}-schema.json`;
|
|
let formUiJsonFileName: string | null = `${formId}-uischema.json`;
|
|
if (searchParams.get('file_name')) {
|
|
formJsonFileName = searchParams.get('file_name') as any;
|
|
if (formJsonFileName.match(/-schema\.json$/)) {
|
|
formUiJsonFileName = (searchParams.get('file_name') as any).replace(
|
|
'-schema.json',
|
|
'-uischema.json'
|
|
);
|
|
} else {
|
|
formUiJsonFileName = null;
|
|
}
|
|
}
|
|
|
|
uploadFile(new File([renderFormJson()], formJsonFileName));
|
|
if (formUiJsonFileName) {
|
|
uploadFile(new File([renderFormUiJson()], formUiJsonFileName));
|
|
}
|
|
};
|
|
|
|
const deleteFile = () => {
|
|
const url = `/process-models/${modifiedProcessModelId}/files/${params.file_name}`;
|
|
const httpMethod = 'DELETE';
|
|
|
|
const navigateToProcessModelShow = (_httpResult: any) => {
|
|
navigate(`/admin/process-models/${modifiedProcessModelId}`);
|
|
};
|
|
|
|
HttpService.makeCallToBackend({
|
|
path: url,
|
|
successCallback: navigateToProcessModelShow,
|
|
httpMethod,
|
|
});
|
|
};
|
|
|
|
const formIdTextField = () => {
|
|
if (searchParams.get('file_name')) {
|
|
return null;
|
|
}
|
|
return (
|
|
<TextInput
|
|
id="json-form-id"
|
|
name="id"
|
|
labelText="ID"
|
|
value={formId}
|
|
onChange={(event: any) => {
|
|
setFormIdHasBeenUpdatedByUser(true);
|
|
setFormId(event.srcElement.value);
|
|
}}
|
|
/>
|
|
);
|
|
};
|
|
|
|
const jsonFormButton = () => {
|
|
if (!searchParams.get('file_name')) {
|
|
return null;
|
|
}
|
|
return (
|
|
<>
|
|
<ButtonWithConfirmation
|
|
data-qa="delete-process-model-file"
|
|
description={`Delete file ${searchParams.get('file_name')}?`}
|
|
onConfirmation={deleteFile}
|
|
buttonLabel="Delete"
|
|
/>
|
|
<Button
|
|
onClick={() =>
|
|
navigate(
|
|
`/admin/process-models/${
|
|
params.process_model_id
|
|
}/form/${searchParams.get('file_name')}`
|
|
)
|
|
}
|
|
variant="danger"
|
|
data-qa="form-builder-button"
|
|
>
|
|
View Json
|
|
</Button>
|
|
</>
|
|
);
|
|
};
|
|
const processModelFileName = searchParams.get('file_name') || '';
|
|
|
|
const topOfPageElements = () => {
|
|
return (
|
|
<>
|
|
<ProcessBreadcrumb
|
|
hotCrumbs={[
|
|
['Process Groups', '/admin'],
|
|
{
|
|
entityToExplode: params.process_model_id || '',
|
|
entityType: 'process-model-id',
|
|
linkLastItem: true,
|
|
},
|
|
[processModelFileName],
|
|
]}
|
|
/>
|
|
<h1>
|
|
Process Model File{processModelFileName ? ': ' : ''}
|
|
{processModelFileName}
|
|
</h1>
|
|
</>
|
|
);
|
|
};
|
|
|
|
const jsonFormArea = () => {
|
|
return (
|
|
<>
|
|
{formSubmitResultElement()}
|
|
<Button onClick={saveFile}>Save</Button>
|
|
{jsonFormButton()}
|
|
<TextInput
|
|
id="json-form-title"
|
|
name="title"
|
|
labelText="Title"
|
|
value={formTitle}
|
|
onChange={(event: any) => {
|
|
onFormTitleChange(event.srcElement.value);
|
|
}}
|
|
/>
|
|
{formIdTextField()}
|
|
<TextInput
|
|
id="form-description"
|
|
name="description"
|
|
labelText="Description"
|
|
value={formDescription}
|
|
onChange={(event: any) => {
|
|
setFormDescription(event.srcElement.value);
|
|
}}
|
|
/>
|
|
<Button
|
|
onClick={() => {
|
|
setFormFieldId('');
|
|
setFormFieldTitle('');
|
|
setFormFieldType('');
|
|
setFormFieldSelectOptions('');
|
|
setShowFormFieldSelectTextField(false);
|
|
setShowNewFormField(true);
|
|
}}
|
|
>
|
|
New Field
|
|
</Button>
|
|
{formFieldArea()}
|
|
{newFormFieldComponent()}
|
|
</>
|
|
);
|
|
};
|
|
const schemaString = renderFormJson();
|
|
const uiSchemaString = renderFormUiJson();
|
|
const schema = JSON.parse(schemaString);
|
|
const uiSchema = JSON.parse(uiSchemaString);
|
|
|
|
return (
|
|
<>
|
|
{topOfPageElements()}
|
|
<Grid fullWidth>
|
|
<Column md={5} lg={8} sm={4}>
|
|
{jsonFormArea()}
|
|
</Column>
|
|
<Column md={5} lg={8} sm={4}>
|
|
<h2>Form Preview</h2>
|
|
<Form
|
|
formData={{}}
|
|
schema={schema}
|
|
uiSchema={uiSchema}
|
|
validator={validator}
|
|
/>
|
|
</Column>
|
|
</Grid>
|
|
</>
|
|
);
|
|
}
|