From ffd6b44c555cea3a4339ac160ae4e6c064026aa1 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Fri, 26 Jan 2024 14:56:18 +0100 Subject: [PATCH 1/4] custom_fields: compose fields from given list --- .../widgets/custom_fields/ComposeFields.js | 100 ++++++++++ .../widgets/custom_fields/CustomFields.js | 83 +++++--- .../forms/widgets/custom_fields/Extensions.js | 187 ++++++++++++++++++ src/lib/forms/widgets/custom_fields/index.js | 1 + src/lib/forms/widgets/loader.js | 5 +- 5 files changed, 352 insertions(+), 24 deletions(-) create mode 100644 src/lib/forms/widgets/custom_fields/ComposeFields.js create mode 100644 src/lib/forms/widgets/custom_fields/Extensions.js diff --git a/src/lib/forms/widgets/custom_fields/ComposeFields.js b/src/lib/forms/widgets/custom_fields/ComposeFields.js new file mode 100644 index 00000000..d37c8ceb --- /dev/null +++ b/src/lib/forms/widgets/custom_fields/ComposeFields.js @@ -0,0 +1,100 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { Divider } from "semantic-ui-react"; +import { AccordionField } from "../../AccordionField"; +import { FieldLabel } from "../../FieldLabel"; +import { Extensions } from "./Extensions"; + +export class ComposeFields extends Component { + constructor(props) { + super(props); + const { composeSections } = this.props; + + this.state = { sections: composeSections, tempFields: [] }; + } + + getFieldsConfig = (sectionCfg) => { + const cfg = {}; + for (const section of sectionCfg) { + for (const fieldCfg of section.fieldsConfig) { + const { field, props, ui_widget, ...otherCfg } = fieldCfg; + cfg[field] = { ui_widget: ui_widget, section: section, ...props, ...otherCfg }; + } + } + + return cfg; + }; + + getFieldsWithValues = (sectionFields) => { + const { record } = this.props; + const { tempFields } = this.state; + const filledFields = []; + for (const field of sectionFields) { + if ( + Object.keys(record.custom_fields).includes( + field.key.replace("custom_fields.", "") + ) || + tempFields.includes(field) + ) { + filledFields.push(field); + } + } + return filledFields; + }; + + getSectionOfField = (field) => { + const { sections } = this.state; + for (const section of sections) { + if (section.fields.map((field) => field.key).includes(field.key)) { + return section.section; + } + } + }; + + addFieldCallback = (field) => { + const { sections: prevSections, tempFields: prevTempFields } = this.state; + const sections = [...prevSections]; + const sectionToUpdate = this.getSectionOfField(field); + for (const section of sections) { + if (section.section === sectionToUpdate) { + section["fields"] = [...section.fields, field]; + } + } + this.setState({ sections: [...sections], tempFields: [...prevTempFields, field] }); + }; + + render() { + const { templateLoaders, record } = this.props; + const { sections } = this.state; + + const fields = this.getFieldsConfig(sections); + return ( + + {sections.map(({ fields, paths, ...sectionConfig }) => ( +
+ + +
{this.getFieldsWithValues(fields)}
+
+ ))} + +
+ ); + } +} + +ComposeFields.propTypes = { + templateLoaders: PropTypes.array.isRequired, + composeSections: PropTypes.array.isRequired, + record: PropTypes.object.isRequired, +}; diff --git a/src/lib/forms/widgets/custom_fields/CustomFields.js b/src/lib/forms/widgets/custom_fields/CustomFields.js index d410d470..84afd6e9 100644 --- a/src/lib/forms/widgets/custom_fields/CustomFields.js +++ b/src/lib/forms/widgets/custom_fields/CustomFields.js @@ -7,54 +7,90 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; +import { ComposeFields } from "./ComposeFields"; import { AccordionField } from "../../AccordionField"; import { loadWidgetsFromConfig } from "../loader"; export class CustomFields extends Component { - state = { sections: [] }; + constructor(props) { + super(props); + this.state = { sections: undefined, composeSections: undefined }; + } componentDidMount() { + this.populateConfig(); + } + + populateConfig = async () => { const { includesPaths, fieldPathPrefix } = this.props; - // use of `Promise.then()` as eslint is giving an error when calling setState() directly - // in the componentDidMount() method - this.loadCustomFieldsWidgets() - .then((sections) => { - sections = sections.map((sectionCfg) => { - const paths = includesPaths(sectionCfg.fields, fieldPathPrefix); - return { ...sectionCfg, paths }; - }); - this.setState({ sections }); - }) - .catch((error) => { - console.error("Couldn't load custom fields widgets.", error); + try { + const { sectionsConfig, composeSectionConfig } = + await this.loadCustomFieldsWidgets(); + const sections = sectionsConfig.map((sectionCfg) => { + const paths = includesPaths(sectionCfg.fields, fieldPathPrefix); + return { ...sectionCfg, paths }; }); - } + + const composeSections = composeSectionConfig.map((sectionCfg) => { + const paths = includesPaths(sectionCfg.fields, fieldPathPrefix); + return { ...sectionCfg, paths }; + }); + + this.setState({ sections: sections, composeSections: composeSections }); + } catch (error) { + console.error("Couldn't load custom fields widgets.", error); + } + }; async loadCustomFieldsWidgets() { - const { config, fieldPathPrefix, templateLoaders } = this.props; + const { config, fieldPathPrefix, templateLoaders, record } = this.props; const sections = []; + const composeFieldSections = []; for (const sectionCfg of config) { // Path to end user's folder defining custom fields ui widgets const fields = await loadWidgetsFromConfig({ templateLoaders: templateLoaders, fieldPathPrefix: fieldPathPrefix, fields: sectionCfg.fields, + record: record, }); - sections.push({ ...sectionCfg, fields }); + if (sectionCfg.compose_fields) { + composeFieldSections.push({ + ...sectionCfg, + fields: fields, + fieldsConfig: sectionCfg.fields, + }); + } else { + sections.push({ ...sectionCfg, fields }); + } } - return sections; + return { sectionsConfig: sections, composeSectionConfig: composeFieldSections }; } render() { - const { sections } = this.state; + const { sections, composeSections } = this.state; + const { templateLoaders, record } = this.props; return ( <> - {sections.map(({ section, fields, paths }) => ( - - {fields} - - ))} + {sections && + sections.map(({ fields, paths, ...sectionConfig }) => ( + + {fields} + + ))} + {composeSections && composeSections && ( + + )} ); } @@ -76,6 +112,7 @@ CustomFields.propTypes = { templateLoaders: PropTypes.array.isRequired, fieldPathPrefix: PropTypes.string.isRequired, includesPaths: PropTypes.func, + record: PropTypes.object.isRequired, }; CustomFields.defaultProps = { diff --git a/src/lib/forms/widgets/custom_fields/Extensions.js b/src/lib/forms/widgets/custom_fields/Extensions.js new file mode 100644 index 00000000..23e18352 --- /dev/null +++ b/src/lib/forms/widgets/custom_fields/Extensions.js @@ -0,0 +1,187 @@ +// This file is part of Invenio-RDM-Records +// Copyright (C) 2020-2023 CERN. +// Copyright (C) 2020-2022 Northwestern University. +// +// Invenio-RDM-Records is free software; you can redistribute it and/or modify it +// under the terms of the MIT License; see LICENSE file for more details. + +import React, { Component } from "react"; +import { importWidget } from "../loader"; + +import { + Button, + Icon, + Modal, + Item, + Divider, + Label, + Grid, + Segment, +} from "semantic-ui-react"; + +import PropTypes from "prop-types"; + +export class Extensions extends Component { + constructor(props) { + super(props); + this.state = { + modalOpen: false, + selectedField: undefined, + selectedFieldTarget: undefined, + fields: [], + }; + } + + handleModalOpen = () => { + this.setState({ modalOpen: true }); + }; + + handleModalClosed = () => { + this.setState({ modalOpen: false }); + }; + + handleSelectField = (e, fieldName, field) => { + const { selectedFieldTarget: previousSelected } = this.state; + if (previousSelected) { + previousSelected.classList.toggle("selected-background"); + } + const newField = { + field: fieldName, + props: { ...field }, + ui_widget: field.ui_widget, + }; + this.setState({ + selectedField: { ...newField }, + selectedFieldTarget: e.currentTarget, + }); + e.currentTarget.classList.toggle("selected-background"); + }; + + handleAddField = async (withClose = false) => { + const { selectedField } = this.state; + const { fieldPath, templateLoaders, addFieldCallback } = this.props; + const field = await importWidget(templateLoaders, { + ...selectedField, + fieldPath: `${fieldPath}.${selectedField.field}`, + }); + const { fields: prevFields } = this.state; + this.setState({ fields: [...prevFields, field] }); + if (withClose) { + this.handleModalClosed(); + } + addFieldCallback(field); + console.log(`${fieldPath}.${selectedField.field}`); + }; + + render() { + const { + fieldPath, // injected by the custom field loader via the `field` config property + icon, + label, + record, + templateLoaders, + addFieldCallback, + ...fieldsList + } = this.props; + const { modalOpen, fields } = this.state; + console.warn(fieldsList, "---------------%%%%%%%%%%%%%%%%%%"); + + return ( + <> + + + + Add domain specific fields + + test + + + + {Object.entries(fieldsList).map(([key, value]) => { + const names = key.split(":"); + const isDisabled = fields + .map((field) => field.key) + .includes(`${fieldPath}.${key}`); + + return ( + + !isDisabled ? this.handleSelectField(e, key, value) : {} + } + > + + + {value.label} + + + + {value.note} + + + + + {value.multiple_values === true && ( + + )} + {value.type === "text" && ( + + )} + + + + ); + })} + + + + + + + + + ); + } +} + +Extensions.propTypes = { + fieldPath: PropTypes.string.isRequired, + record: PropTypes.object.isRequired, + icon: PropTypes.string, + label: PropTypes.string, + templateLoaders: PropTypes.array.isRequired, + addFieldCallback: PropTypes.func.isRequired, +}; + +Extensions.defaultProps = { + icon: undefined, + label: undefined, +}; diff --git a/src/lib/forms/widgets/custom_fields/index.js b/src/lib/forms/widgets/custom_fields/index.js index b57a438d..a8c85b78 100644 --- a/src/lib/forms/widgets/custom_fields/index.js +++ b/src/lib/forms/widgets/custom_fields/index.js @@ -1 +1,2 @@ export { CustomFields } from "./CustomFields"; +export { Extensions } from "./Extensions"; diff --git a/src/lib/forms/widgets/loader.js b/src/lib/forms/widgets/loader.js index 5081d6e4..84a135b6 100644 --- a/src/lib/forms/widgets/loader.js +++ b/src/lib/forms/widgets/loader.js @@ -9,7 +9,7 @@ import React from "react"; */ export async function importWidget( templateLoaders, - { ui_widget: UIWidget, fieldPath, props } + { ui_widget: UIWidget, fieldPath, record, props } ) { let component = undefined; @@ -36,6 +36,7 @@ export async function importWidget( return React.createElement(component, { ...props, + record: record, key: fieldPath, fieldPath: fieldPath, }); @@ -72,6 +73,7 @@ export async function loadWidgetsFromConfig({ templateLoaders, fieldPathPrefix, fields, + record, }) { const importWidgetsFromFolder = (templateFolder, fieldPathPrefix, fieldsConfig) => { const tplPromises = []; @@ -82,6 +84,7 @@ export async function loadWidgetsFromConfig({ fieldPath: fieldPathPrefix ? `${fieldPathPrefix}.${fieldCfg.field}` : fieldCfg.field, + record, }) ); }); From 7b7d99a78f9b2bfb21abcd95f427a659ddb215d3 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Fri, 26 Jan 2024 18:25:58 +0100 Subject: [PATCH 2/4] custom_fields: add filtering and search on compose fields --- .../widgets/custom_fields/ComposeFields.js | 10 +- .../forms/widgets/custom_fields/Extensions.js | 84 ++-------- .../ListAndFilterCustomFields.js | 157 ++++++++++++++++++ 3 files changed, 179 insertions(+), 72 deletions(-) create mode 100644 src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js diff --git a/src/lib/forms/widgets/custom_fields/ComposeFields.js b/src/lib/forms/widgets/custom_fields/ComposeFields.js index d37c8ceb..88e935cf 100644 --- a/src/lib/forms/widgets/custom_fields/ComposeFields.js +++ b/src/lib/forms/widgets/custom_fields/ComposeFields.js @@ -11,6 +11,8 @@ export class ComposeFields extends Component { const { composeSections } = this.props; this.state = { sections: composeSections, tempFields: [] }; + this.fieldsCfg = this.getFieldsConfig(composeSections); + this.sectionsList = composeSections.map((section) => section.section); } getFieldsConfig = (sectionCfg) => { @@ -57,7 +59,9 @@ export class ComposeFields extends Component { const sectionToUpdate = this.getSectionOfField(field); for (const section of sections) { if (section.section === sectionToUpdate) { - section["fields"] = [...section.fields, field]; + section["fields"] = [...section.fields, field].sort((a, b) => + a.key.localeCompare(b.key) + ); } } this.setState({ sections: [...sections], tempFields: [...prevTempFields, field] }); @@ -67,7 +71,6 @@ export class ComposeFields extends Component { const { templateLoaders, record } = this.props; const { sections } = this.state; - const fields = this.getFieldsConfig(sections); return ( {sections.map(({ fields, paths, ...sectionConfig }) => ( @@ -83,9 +86,10 @@ export class ComposeFields extends Component { ))} diff --git a/src/lib/forms/widgets/custom_fields/Extensions.js b/src/lib/forms/widgets/custom_fields/Extensions.js index 23e18352..47c6a1eb 100644 --- a/src/lib/forms/widgets/custom_fields/Extensions.js +++ b/src/lib/forms/widgets/custom_fields/Extensions.js @@ -6,18 +6,10 @@ // under the terms of the MIT License; see LICENSE file for more details. import React, { Component } from "react"; +import { ListAndFilterCustomFields } from "./ListAndFilterCustomFields"; import { importWidget } from "../loader"; -import { - Button, - Icon, - Modal, - Item, - Divider, - Label, - Grid, - Segment, -} from "semantic-ui-react"; +import { Button, Icon, Modal, Divider } from "semantic-ui-react"; import PropTypes from "prop-types"; @@ -31,7 +23,6 @@ export class Extensions extends Component { fields: [], }; } - handleModalOpen = () => { this.setState({ modalOpen: true }); }; @@ -69,8 +60,8 @@ export class Extensions extends Component { if (withClose) { this.handleModalClosed(); } + this.setState({ selectedField: undefined, selectedFieldTarget: undefined }); addFieldCallback(field); - console.log(`${fieldPath}.${selectedField.field}`); }; render() { @@ -81,10 +72,10 @@ export class Extensions extends Component { record, templateLoaders, addFieldCallback, + sections, ...fieldsList } = this.props; const { modalOpen, fields } = this.state; - console.warn(fieldsList, "---------------%%%%%%%%%%%%%%%%%%"); return ( <> @@ -95,60 +86,13 @@ export class Extensions extends Component { Add domain specific fields - - test - - - - {Object.entries(fieldsList).map(([key, value]) => { - const names = key.split(":"); - const isDisabled = fields - .map((field) => field.key) - .includes(`${fieldPath}.${key}`); - - return ( - - !isDisabled ? this.handleSelectField(e, key, value) : {} - } - > - - - {value.label} - - - - {value.note} - - - - - {value.multiple_values === true && ( - - )} - {value.type === "text" && ( - - )} - - - - ); - })} - - + - @@ -179,9 +123,11 @@ Extensions.propTypes = { label: PropTypes.string, templateLoaders: PropTypes.array.isRequired, addFieldCallback: PropTypes.func.isRequired, + sections: PropTypes.array, }; Extensions.defaultProps = { icon: undefined, label: undefined, + sections: undefined, }; diff --git a/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js b/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js new file mode 100644 index 00000000..4647c6bc --- /dev/null +++ b/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js @@ -0,0 +1,157 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { + Dropdown, + Grid, + Icon, + Input, + Item, + Label, + List, + Modal, + Segment, +} from "semantic-ui-react"; + +export class ListAndFilterCustomFields extends Component { + constructor(props) { + super(props); + const { fieldsList } = props; + this.state = { filteredFieldsList: fieldsList }; + } + + resetFilter = () => { + const { fieldsList } = this.props; + this.setState({ filteredFieldsList: fieldsList }); + }; + handleSearch = (e, { value }) => { + const { fieldsList } = this.props; + if (value) { + const filteredResults = Object.fromEntries( + Object.entries(fieldsList).filter(([key, val]) => { + return val.label.toLowerCase().includes(value.toLowerCase()); + }) + ); + + this.setState({ filteredFieldsList: filteredResults }); + } else { + this.resetFilter(); + } + }; + + handleDomainFilter = (e, { value }) => { + const { fieldsList } = this.props; + if (value) { + const filteredResults = Object.fromEntries( + Object.entries(fieldsList).filter(([key, val]) => val.section.section === value) + ); + + this.setState({ filteredFieldsList: filteredResults }); + } else { + this.resetFilter(); + } + }; + + render() { + const { filteredFieldsList } = this.state; + const { alreadyAddedFields, fieldPath, handleSelectField, sections } = this.props; + const dropdownOptions = sections.map((section) => ({ + key: section, + text: section, + value: section, + })); + + return ( + <> + + + + + + + + in:{" "} + + + + + + + + {Object.entries(filteredFieldsList).map(([key, value]) => { + const names = key.split(":"); + + const isDisabled = alreadyAddedFields + .map((field) => field.key) + .includes(`${fieldPath}.${key}`); + + return ( + (!isDisabled ? handleSelectField(e, key, value) : {})} + > + + + {value.label} + + + + {value.note} + + + + + {value.multiple_values === true && ( + + )} + {value.type === "text" && ( + + )} + + + + ); + })}{" "} + + + + ); + } +} + +ListAndFilterCustomFields.propTypes = { + alreadyAddedFields: PropTypes.array.isRequired, + fieldsList: PropTypes.array.isRequired, + fieldPath: PropTypes.string.isRequired, + handleSelectField: PropTypes.func.isRequired, + sections: PropTypes.array, +}; + +ListAndFilterCustomFields.defaultProps = { + sections: undefined, +}; From f632fa820d8aa04afd335bc61e9b1794e64c688f Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Mon, 29 Jan 2024 14:54:30 +0100 Subject: [PATCH 3/4] composeFields: improve user experience * chore: code cleanup --- package-lock.json | 3 +- .../widgets/custom_fields/ComposeFields.js | 78 ++++++++++++------- .../forms/widgets/custom_fields/Extensions.js | 77 ++++++++++++++---- .../ListAndFilterCustomFields.js | 67 +++++++++------- 4 files changed, 150 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index fc35fdbd..5280a655 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,7 +5,8 @@ "requires": true, "packages": { "": { - "version": "2.8.2", + "name": "react-invenio-forms", + "version": "2.8.4", "license": "MIT", "devDependencies": { "@babel/cli": "^7.5.0", diff --git a/src/lib/forms/widgets/custom_fields/ComposeFields.js b/src/lib/forms/widgets/custom_fields/ComposeFields.js index 88e935cf..b789969d 100644 --- a/src/lib/forms/widgets/custom_fields/ComposeFields.js +++ b/src/lib/forms/widgets/custom_fields/ComposeFields.js @@ -1,3 +1,4 @@ +import _isEmpty from "lodash/isEmpty"; import React, { Component } from "react"; import PropTypes from "prop-types"; import { Divider } from "semantic-ui-react"; @@ -8,9 +9,11 @@ import { Extensions } from "./Extensions"; export class ComposeFields extends Component { constructor(props) { super(props); - const { composeSections } = this.props; - - this.state = { sections: composeSections, tempFields: [] }; + const { composeSections, record } = props; + const filled = Object.keys(record.custom_fields).map( + (key) => `custom_fields.${key}` + ); + this.state = { sections: composeSections, tempFields: [], recordFields: filled }; this.fieldsCfg = this.getFieldsConfig(composeSections); this.sectionsList = composeSections.map((section) => section.section); } @@ -29,15 +32,13 @@ export class ComposeFields extends Component { getFieldsWithValues = (sectionFields) => { const { record } = this.props; - const { tempFields } = this.state; + const { tempFields, recordFields } = this.state; const filledFields = []; + if (!record.custom_fields) { + return []; + } for (const field of sectionFields) { - if ( - Object.keys(record.custom_fields).includes( - field.key.replace("custom_fields.", "") - ) || - tempFields.includes(field) - ) { + if (recordFields.includes(field.key) || tempFields.includes(field)) { filledFields.push(field); } } @@ -53,37 +54,53 @@ export class ComposeFields extends Component { } }; - addFieldCallback = (field) => { + addFieldCallback = (fields) => { const { sections: prevSections, tempFields: prevTempFields } = this.state; + const sections = [...prevSections]; - const sectionToUpdate = this.getSectionOfField(field); - for (const section of sections) { - if (section.section === sectionToUpdate) { - section["fields"] = [...section.fields, field].sort((a, b) => - a.key.localeCompare(b.key) - ); + for (const field of fields) { + const sectionToUpdate = this.getSectionOfField(field); + for (const section of sections) { + if (section.section === sectionToUpdate) { + section["fields"] = [...section.fields, field].sort((a, b) => + a.key.localeCompare(b.key) + ); + } } } - this.setState({ sections: [...sections], tempFields: [...prevTempFields, field] }); + this.setState({ + sections: [...sections], + tempFields: [...prevTempFields, ...fields], + }); }; render() { const { templateLoaders, record } = this.props; - const { sections } = this.state; + const { sections, tempFields, recordFields } = this.state; + const existingFields = [ + ...Object.entries(tempFields).map(([key, value]) => value.key), + ...recordFields, + ]; return ( - {sections.map(({ fields, paths, ...sectionConfig }) => ( -
- - -
{this.getFieldsWithValues(fields)}
-
- ))} + {sections.map(({ fields, paths, ...sectionConfig }) => { + const recordCustomFields = this.getFieldsWithValues(fields); + if (_isEmpty(recordCustomFields)) { + return undefined; + } + return ( +
+ + +
{recordCustomFields}
+
+ ); + })}
); diff --git a/src/lib/forms/widgets/custom_fields/Extensions.js b/src/lib/forms/widgets/custom_fields/Extensions.js index 47c6a1eb..7e745095 100644 --- a/src/lib/forms/widgets/custom_fields/Extensions.js +++ b/src/lib/forms/widgets/custom_fields/Extensions.js @@ -16,13 +16,17 @@ import PropTypes from "prop-types"; export class Extensions extends Component { constructor(props) { super(props); + const { existingFields } = props; this.state = { modalOpen: false, selectedField: undefined, selectedFieldTarget: undefined, - fields: [], + addFields: [], + existingFields: [...existingFields], + loading: false, }; } + handleModalOpen = () => { this.setState({ modalOpen: true }); }; @@ -36,6 +40,7 @@ export class Extensions extends Component { if (previousSelected) { previousSelected.classList.toggle("selected-background"); } + e.currentTarget.classList.toggle("selected-background"); const newField = { field: fieldName, props: { ...field }, @@ -45,23 +50,50 @@ export class Extensions extends Component { selectedField: { ...newField }, selectedFieldTarget: e.currentTarget, }); - e.currentTarget.classList.toggle("selected-background"); }; handleAddField = async (withClose = false) => { - const { selectedField } = this.state; + const { + selectedField, + addFields: prevFields, + selectedFieldTarget, + existingFields: prevExisting, + } = this.state; const { fieldPath, templateLoaders, addFieldCallback } = this.props; + this.setState({ loading: true }); const field = await importWidget(templateLoaders, { ...selectedField, fieldPath: `${fieldPath}.${selectedField.field}`, }); - const { fields: prevFields } = this.state; - this.setState({ fields: [...prevFields, field] }); - if (withClose) { - this.handleModalClosed(); - } - this.setState({ selectedField: undefined, selectedFieldTarget: undefined }); - addFieldCallback(field); + + const performCallback = (selectedFieldTarget) => { + const { addFields } = this.state; + + if (withClose) { + addFieldCallback(addFields); + this.setState({ addFields: [], existingFields: [] }); + this.handleModalClosed(); + } + }; + selectedFieldTarget.classList.toggle("selected-background"); + this.setState( + { + addFields: [...prevFields, field], + existingFields: [...prevExisting, field.key], + selectedField: undefined, + selectedFieldTarget: undefined, + loading: false, + }, + () => performCallback(selectedFieldTarget) + ); + }; + + handleCancel = () => { + const { addFields } = this.state; + const { addFieldCallback } = this.props; + addFieldCallback(addFields); + this.setState({ addFields: [] }); + this.handleModalClosed(); }; render() { @@ -73,10 +105,10 @@ export class Extensions extends Component { templateLoaders, addFieldCallback, sections, + existingFields: selected, ...fieldsList } = this.props; - const { modalOpen, fields } = this.state; - + const { modalOpen, existingFields, loading } = this.state; return ( <> @@ -89,23 +121,35 @@ export class Extensions extends Component { - @@ -124,6 +168,7 @@ Extensions.propTypes = { templateLoaders: PropTypes.array.isRequired, addFieldCallback: PropTypes.func.isRequired, sections: PropTypes.array, + existingFields: PropTypes.array.isRequired, }; Extensions.defaultProps = { diff --git a/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js b/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js index 4647c6bc..2c03a3a9 100644 --- a/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js +++ b/src/lib/forms/widgets/custom_fields/ListAndFilterCustomFields.js @@ -7,7 +7,6 @@ import { Input, Item, Label, - List, Modal, Segment, } from "semantic-ui-react"; @@ -16,39 +15,47 @@ export class ListAndFilterCustomFields extends Component { constructor(props) { super(props); const { fieldsList } = props; - this.state = { filteredFieldsList: fieldsList }; + this.state = { + filteredFieldsList: fieldsList, + searchPhrase: undefined, + filter: undefined, + }; } resetFilter = () => { const { fieldsList } = this.props; this.setState({ filteredFieldsList: fieldsList }); }; - handleSearch = (e, { value }) => { - const { fieldsList } = this.props; - if (value) { - const filteredResults = Object.fromEntries( - Object.entries(fieldsList).filter(([key, val]) => { - return val.label.toLowerCase().includes(value.toLowerCase()); - }) - ); - this.setState({ filteredFieldsList: filteredResults }); - } else { + filter = () => { + const { fieldsList } = this.props; + const { searchPhrase, filter } = this.state; + if (!searchPhrase && !filter) { this.resetFilter(); } + const filteredResults = Object.fromEntries( + Object.entries(fieldsList).filter(([key, val]) => { + if (filter && searchPhrase) { + return ( + val.section.section === filter && + val.label.toLowerCase().includes(searchPhrase.toLowerCase()) + ); + } else if (filter) { + return val.section.section === filter; + } else if (searchPhrase) { + return val.label.toLowerCase().includes(searchPhrase.toLowerCase()); + } + }) + ); + this.setState({ filteredFieldsList: filteredResults }); }; - handleDomainFilter = (e, { value }) => { - const { fieldsList } = this.props; - if (value) { - const filteredResults = Object.fromEntries( - Object.entries(fieldsList).filter(([key, val]) => val.section.section === value) - ); + handleSearch = (e, { value }) => { + this.setState({ searchPhrase: value }, () => this.filter()); + }; - this.setState({ filteredFieldsList: filteredResults }); - } else { - this.resetFilter(); - } + handleDomainFilter = (e, { value }) => { + this.setState({ filter: value }, () => this.filter()); }; render() { @@ -59,7 +66,6 @@ export class ListAndFilterCustomFields extends Component { text: section, value: section, })); - return ( <> @@ -68,7 +74,7 @@ export class ListAndFilterCustomFields extends Component { @@ -94,9 +100,7 @@ export class ListAndFilterCustomFields extends Component { {Object.entries(filteredFieldsList).map(([key, value]) => { const names = key.split(":"); - const isDisabled = alreadyAddedFields - .map((field) => field.key) - .includes(`${fieldPath}.${key}`); + const isDisabled = alreadyAddedFields.includes(`${fieldPath}.${key}`); return ( - {value.label} + <> + {value.label}{" "} + {isDisabled && ( + + Added + + )} + From 55e0e1e70cf85230c94877203a46388c356223b8 Mon Sep 17 00:00:00 2001 From: Karolina Przerwa Date: Tue, 30 Jan 2024 17:38:21 +0100 Subject: [PATCH 4/4] release v3.0.0 --- CHANGES.md | 4 ++++ package-lock.json | 2 +- package.json | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 27afc522..d4d0e812 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # Changes +Version 3.0.0 (released 2024-01-30) + +- add discoverable custom fields components + Version 2.8.4 (released 2023-12-12) - Replace CKEditor with TinyMCE diff --git a/package-lock.json b/package-lock.json index 5280a655..5d932791 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "react-invenio-forms", - "version": "2.8.4", + "version": "3.0.0", "lockfileVersion": 2, "requires": true, "packages": { diff --git a/package.json b/package.json index ee41c2dc..4c6dfd71 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-invenio-forms", - "version": "2.8.4", + "version": "3.0.0", "description": "React components to build forms in Invenio", "main": "dist/cjs/index.js", "browser": "dist/cjs/index.js",