From 5ac7f061f0a5fadd4f36448656b44a640dc5236b Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Fri, 23 Aug 2024 17:50:45 +0200 Subject: [PATCH] generate form: refactor fields * add overridable components to admin forms --- .../src/details/AdminDetailsView.js | 100 ++++--- .../src/details/details.js | 38 ++- .../src/formik/fields/array.js | 28 ++ .../src/formik/fields/bool.js | 27 ++ .../src/formik/fields/dynamic.js | 62 +++++ .../src/formik/fields/fields.js | 256 ++++-------------- .../src/formik/fields/hidden.js | 25 ++ .../src/formik/fields/object.js | 65 +++++ .../src/formik/fields/props_generator.js | 58 ++++ .../src/formik/fields/vocabulary.js | 24 ++ .../invenio_administration/details.html | 1 + 11 files changed, 431 insertions(+), 253 deletions(-) create mode 100644 invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/bool.js create mode 100644 invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/dynamic.js create mode 100644 invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/hidden.js create mode 100644 invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/object.js create mode 100644 invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/props_generator.js create mode 100644 invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/vocabulary.js diff --git a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/AdminDetailsView.js b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/AdminDetailsView.js index 7343a11..c20a3c3 100644 --- a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/AdminDetailsView.js +++ b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/AdminDetailsView.js @@ -8,8 +8,9 @@ import { Actions } from "../actions/Actions"; import _isEmpty from "lodash/isEmpty"; import { sortFields } from "../components/utils"; import { Loader, ErrorPage } from "../components"; +import Overridable from "react-overridable"; -export default class AdminDetailsView extends Component { +class AdminDetailsView extends Component { constructor(props) { super(props); this.state = { @@ -73,51 +74,60 @@ export default class AdminDetailsView extends Component { displayDelete, displayEdit, uiSchema, + name, } = this.props; const { loading, data, error } = this.state; const sortedColumns = sortFields(uiSchema); return ( - - - - - -
{title}
-
- - - - - -
-
- - - - {this.childrenWithData(data, columns)} - -
-
+ + + + + + +
{title}
+
+ + + + + +
+
+ + + + {this.childrenWithData(data, columns)} + +
+
+
); } } @@ -137,9 +147,15 @@ AdminDetailsView.propTypes = { resourceSchema: PropTypes.object.isRequired, requestHeaders: PropTypes.object.isRequired, uiSchema: PropTypes.object.isRequired, + name: PropTypes.string.isRequired, }; AdminDetailsView.defaultProps = { actions: undefined, children: undefined, }; + +export default Overridable.component( + "InvenioAdministration.AdminDetailsView", + AdminDetailsView +); diff --git a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/details.js b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/details.js index e0a90eb..70ce9ed 100644 --- a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/details.js +++ b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/details/details.js @@ -8,6 +8,9 @@ import React from "react"; import ReactDOM from "react-dom"; import _get from "lodash/get"; import AdminDetailsView from "./AdminDetailsView"; +import { OverridableContext, overrideStore } from "react-overridable"; + +const overriddenComponents = overrideStore.getAll(); const domContainer = document.getElementById("invenio-details-config"); @@ -24,23 +27,28 @@ const listUIEndpoint = domContainer.dataset.listEndpoint; const resourceSchema = JSON.parse(domContainer.dataset?.resourceSchema); const requestHeaders = JSON.parse(domContainer.dataset?.requestHeaders); const uiSchema = JSON.parse(domContainer.dataset?.uiConfig); +const name = JSON.parse(domContainer.dataset?.name); domContainer && ReactDOM.render( - , + + + , + , domContainer ); diff --git a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/array.js b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/array.js index 914c593..f45e3bc 100644 --- a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/array.js +++ b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/array.js @@ -1,9 +1,37 @@ +import { mapFormFields } from "./fields"; +import { generateFieldProps } from "./props_generator"; import React from "react"; import { Array } from "react-invenio-forms"; import { Form, Button, Icon } from "semantic-ui-react"; import { i18next } from "@translations/invenio_administration/i18next"; import PropTypes from "prop-types"; +export const generateArrayFieldProps = ( + fieldName, + fieldSchema, + parentField, + isCreate, + formFieldConfig, + formikProps, + formFieldsConfig +) => { + const fieldProps = generateFieldProps( + fieldName, + fieldSchema, + parentField, + isCreate, + formFieldConfig, + formikProps + ); + const arrayFieldProps = { + fieldSchema: fieldSchema, + isCreate: isCreate, + mapFormFields: mapFormFields, + formFields: formFieldsConfig, + }; + return { ...fieldProps, ...arrayFieldProps }; +}; + const createEmptyArrayRowObject = (properties) => { const emptyRow = {}; for (let [key, schema] of Object.entries(properties)) { diff --git a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/bool.js b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/bool.js new file mode 100644 index 0000000..28a901a --- /dev/null +++ b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/bool.js @@ -0,0 +1,27 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { BooleanField } from "react-invenio-forms"; + +export class AdminBoolField extends Component { + render() { + const { fieldSchema, ...fieldProps } = this.props; + const description = fieldProps.description; + + return ( + <> + + {description && } + + ); + } +} + +AdminBoolField.propTypes = { + fieldProps: PropTypes.object.isRequired, + fieldSchema: PropTypes.object.isRequired, +}; diff --git a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/dynamic.js b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/dynamic.js new file mode 100644 index 0000000..701e5c8 --- /dev/null +++ b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/dynamic.js @@ -0,0 +1,62 @@ +import { generateFieldProps } from "./props_generator"; +import { LazyForm } from "../LazyForm"; +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Form, Segment, Header } from "semantic-ui-react"; + +export const generateDynamicFieldProps = ( + fieldName, + fieldSchema, + parentField, + isCreate, + formFieldConfig, + formikProps, + formFieldsConfig, + formData +) => { + const fieldProps = generateFieldProps( + fieldName, + fieldSchema, + parentField, + isCreate, + formFieldConfig, + formikProps + ); + const dynamicFieldProps = { + formData: formData, + formikProps: formikProps, + }; + return { ...fieldProps, ...dynamicFieldProps }; +}; + +export class DynamicSubFormField extends Component { + render() { + const { formikProps, fieldSchema, formData, ...fieldProps } = this.props; + + return ( + +
+ {fieldProps.label} +
+ + + + + +
+ ); + } +} + +DynamicSubFormField.propTypes = { + fieldProps: PropTypes.object.isRequired, + fieldSchema: PropTypes.object.isRequired, + formikProps: PropTypes.object.isRequired, + formData: PropTypes.object.isRequired, +}; diff --git a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/fields.js b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/fields.js index 0e36374..c310c12 100644 --- a/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/fields.js +++ b/invenio_administration/assets/semantic-ui/js/invenio_administration/src/formik/fields/fields.js @@ -1,86 +1,41 @@ -import { LazyForm } from "../LazyForm"; +import { generateObjectFieldProps, ObjectField } from "./object"; +import { DynamicSubFormField, generateDynamicFieldProps } from "./dynamic"; +import { generateHiddenFieldProps } from "./hidden"; import React from "react"; import { Input, AutocompleteDropdown, - BooleanField, Dropdown, TextArea, RichInput, } from "react-invenio-forms"; -import { Field, getIn } from "formik"; -import _capitalize from "lodash/capitalize"; +import { Field } from "formik"; import _get from "lodash/get"; -import { Form, Segment, Header } from "semantic-ui-react"; import { AdminArrayField } from "./array"; import _isEmpty from "lodash/isEmpty"; import { sortFields } from "../../components/utils"; +import { AdminBoolField } from "./bool"; +import { generateFieldProps } from "./props_generator"; +import { generateVocabularyFieldProps } from "./vocabulary"; +import { generateArrayFieldProps } from "./array"; const fieldsMap = { - string: Input, - integer: Input, - uuid: Input, - datetime: Input, - array: AdminArrayField, - bool: BooleanField, + string: { element: Input, props: generateFieldProps }, + integer: { element: Input, props: generateFieldProps }, + uuid: { element: Input, props: generateFieldProps }, + datetime: { element: Input, props: generateFieldProps }, + array: { element: AdminArrayField, props: generateArrayFieldProps }, + bool: { element: AdminBoolField, props: generateFieldProps }, + hidden: { element: Field, props: generateHiddenFieldProps }, + vocabulary: { element: AutocompleteDropdown, props: generateVocabularyFieldProps }, + dynamic: { element: DynamicSubFormField, props: generateDynamicFieldProps }, + object: { element: ObjectField, props: generateObjectFieldProps }, + dropdown: { element: Dropdown, props: generateFieldProps }, + textarea: { element: TextArea, props: generateFieldProps }, + html: { element: RichInput, props: generateFieldProps }, function: null, }; -const generateFieldProps = ( - fieldName, - fieldSchema, - parentField, - isCreate, - formFieldConfig, - formikProps -) => { - let currentFieldName; - - const fieldLabel = formFieldConfig?.text || fieldSchema?.title || fieldName; - const placeholder = - formFieldConfig?.placeholder || fieldSchema?.metadata?.placeholder; - - if (parentField) { - currentFieldName = `${parentField}.${fieldName}`; - } else { - currentFieldName = fieldName; - } - - const htmlDescription = ( - <> -

-

- - ); - - let dropdownOptions; - dropdownOptions = formFieldConfig?.options || fieldSchema?.metadata?.options; - - if (!dropdownOptions && fieldSchema.enum) { - dropdownOptions = fieldSchema.enum.map((value) => ({ - title_l10n: value, - id: value, - })); - } - - return { - fieldPath: currentFieldName, - key: currentFieldName, - label: _capitalize(fieldLabel), - description: htmlDescription, - required: fieldSchema.required, - disabled: fieldSchema.readOnly || (fieldSchema.createOnly && !isCreate), - placeholder, - options: dropdownOptions, - rows: formFieldConfig?.rows || fieldSchema?.metadata?.rows, - value: formFieldConfig.dump_default, - }; -}; - export const mapFormFields = ( obj, parentField, @@ -96,18 +51,48 @@ export const mapFormFields = ( const sortedFields = sortFields(formFieldsConfig); const elements = Object.entries(sortedFields).map(([fieldName]) => { const fieldSchema = _get(obj, fieldName); + const fieldConfig = formFieldsConfig[fieldName]; + if (fieldSchema.readOnly && dropDumpOnly) { return null; } - const fieldProps = generateFieldProps( + let fieldType = fieldSchema.type; + const isHidden = fieldSchema.metadata?.type === "hidden"; + + if (isHidden) { + fieldType = "hidden"; + } + if (fieldSchema.type === "object" && fieldSchema.metadata?.type === "dynamic") { + fieldType = "dynamic"; + } + + const options = + fieldConfig?.options || fieldSchema?.metadata?.options || fieldSchema.enum; + if (fieldSchema.type === "string" && options) { + fieldType = "dropdown"; + } + + const rows = formFieldsConfig[fieldName]?.rows || fieldSchema?.metadata?.rows; + if ((fieldSchema.type === "string" && rows) || fieldSchema.type === "dict") { + fieldType = "textarea"; + } + + const Element = fieldsMap[fieldType].element; + const fieldPropsGenerator = fieldsMap[fieldType].props; + + const fieldProps = fieldPropsGenerator( fieldName, fieldSchema, parentField, isCreate, - formFieldsConfig[fieldName] + fieldConfig, + formikProps, + formFieldsConfig, + formData, + mapFormFields ); - const isHidden = fieldSchema.metadata?.type === "hidden"; + const showField = _isEmpty(formFieldsConfig) || Object.prototype.hasOwnProperty.call(formFieldsConfig, fieldProps.fieldPath) || @@ -119,135 +104,14 @@ export const mapFormFields = ( if (!showField) { return null; } - if (isHidden) { - return ( - - ); - } - - if (fieldSchema.type === "array") { - return ( - - ); - } - - if (fieldSchema.type === "bool") { - const description = fieldProps.description; - - return ( - <> - - {description && } - - ); - } - - if (fieldSchema.type === "vocabulary") { - return ( - - ); - } - - if (fieldSchema.type === "object" && fieldSchema.metadata?.type === "dynamic") { - return ( - -
- {fieldProps.label} -
- - - - - -
- ); - } - if (fieldSchema.type === "object") { - // nested fields - return ( - -
- {fieldProps.label} -
- - - {mapFormFields( - fieldSchema.properties, - fieldProps.fieldPath, - isCreate, - formFieldsConfig - )} - - -
- ); - } - - if (fieldSchema.type === "string" && fieldProps.options) { - return ( - - ); - } - - const rows = formFieldsConfig[fieldName]?.rows || fieldSchema?.metadata?.rows; - if ((fieldSchema.type === "string" && rows) || fieldSchema.type === "dict") { - return ( -