From f0ae2d7c698c23e7bd84e39350d4d4ed6dec833b Mon Sep 17 00:00:00 2001 From: Miu Razvan Date: Wed, 13 Dec 2023 12:32:36 +0200 Subject: [PATCH] feat: add creatable select widget --- jest.setup.js | 31 +- locales/de/LC_MESSAGES/volto.po | 55 ++++ locales/en/LC_MESSAGES/volto.po | 55 ++++ locales/it/LC_MESSAGES/volto.po | 55 ++++ locales/ro/LC_MESSAGES/volto.po | 55 ++++ locales/volto.pot | 57 +++- package.json | 2 +- .../__snapshots__/Edit.test.jsx.snap | 35 +- src/Widgets/CreatableSelectWidget.jsx | 300 ++++++++++++++++++ src/Widgets/index.js | 3 +- src/Widgets/schema.js | 55 +--- src/index.js | 7 +- 12 files changed, 653 insertions(+), 57 deletions(-) create mode 100644 src/Widgets/CreatableSelectWidget.jsx diff --git a/jest.setup.js b/jest.setup.js index 8eae0c6..26c855d 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -10,14 +10,42 @@ global.store = mockStore({ }, content: { create: {}, + data: { + '@id': 'http://localhost:3000/my-page', + }, subrequests: [], }, - connected_data_parameters: {}, + connected_data_parameters: { + byContextPath: {}, + }, }); +const mockReactRouter = jest.requireActual('react-router'); const mockSemanticComponents = jest.requireActual('semantic-ui-react'); const mockComponents = jest.requireActual('@plone/volto/components'); +jest.mock('react-router', () => { + return { + ...mockReactRouter, + withRouter: (WrappedComponent) => { + return (props) => { + return ( + + ); + }; + }, + }; +}); + jest.mock('semantic-ui-react', () => ({ ...mockSemanticComponents, Popup: ({ content, trigger }) => { @@ -32,7 +60,6 @@ jest.mock('semantic-ui-react', () => ({ jest.doMock('@plone/volto/components', () => { return { - __esModule: true, ...mockComponents, Toast: ({ children }) =>
{children}
, SidebarPortal: ({ children }) => , diff --git a/locales/de/LC_MESSAGES/volto.po b/locales/de/LC_MESSAGES/volto.po index 3d75ed8..832991f 100644 --- a/locales/de/LC_MESSAGES/volto.po +++ b/locales/de/LC_MESSAGES/volto.po @@ -16,7 +16,62 @@ msgstr "" msgid "CSS height" msgstr "" +#: Widgets/CreatableSelectWidget +# defaultMessage: Choices +msgid "Choices" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Close +msgid "Close" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Default +msgid "Default" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Description +msgid "Description" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No options +msgid "No options" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No value +msgid "No value" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Required +msgid "Required" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Select… +msgid "Select…" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Short Name +msgid "Short Name" +msgstr "" + #: Blocks/EmbedTableauVisualization/schema # defaultMessage: Tableau height msgid "Tableau height" msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Title +msgid "Title" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Used for programmatic access to the fieldset. +msgid "Used for programmatic access to the fieldset." +msgstr "" diff --git a/locales/en/LC_MESSAGES/volto.po b/locales/en/LC_MESSAGES/volto.po index 3d75ed8..832991f 100644 --- a/locales/en/LC_MESSAGES/volto.po +++ b/locales/en/LC_MESSAGES/volto.po @@ -16,7 +16,62 @@ msgstr "" msgid "CSS height" msgstr "" +#: Widgets/CreatableSelectWidget +# defaultMessage: Choices +msgid "Choices" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Close +msgid "Close" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Default +msgid "Default" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Description +msgid "Description" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No options +msgid "No options" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No value +msgid "No value" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Required +msgid "Required" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Select… +msgid "Select…" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Short Name +msgid "Short Name" +msgstr "" + #: Blocks/EmbedTableauVisualization/schema # defaultMessage: Tableau height msgid "Tableau height" msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Title +msgid "Title" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Used for programmatic access to the fieldset. +msgid "Used for programmatic access to the fieldset." +msgstr "" diff --git a/locales/it/LC_MESSAGES/volto.po b/locales/it/LC_MESSAGES/volto.po index 3d75ed8..832991f 100644 --- a/locales/it/LC_MESSAGES/volto.po +++ b/locales/it/LC_MESSAGES/volto.po @@ -16,7 +16,62 @@ msgstr "" msgid "CSS height" msgstr "" +#: Widgets/CreatableSelectWidget +# defaultMessage: Choices +msgid "Choices" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Close +msgid "Close" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Default +msgid "Default" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Description +msgid "Description" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No options +msgid "No options" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No value +msgid "No value" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Required +msgid "Required" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Select… +msgid "Select…" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Short Name +msgid "Short Name" +msgstr "" + #: Blocks/EmbedTableauVisualization/schema # defaultMessage: Tableau height msgid "Tableau height" msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Title +msgid "Title" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Used for programmatic access to the fieldset. +msgid "Used for programmatic access to the fieldset." +msgstr "" diff --git a/locales/ro/LC_MESSAGES/volto.po b/locales/ro/LC_MESSAGES/volto.po index 3d75ed8..832991f 100644 --- a/locales/ro/LC_MESSAGES/volto.po +++ b/locales/ro/LC_MESSAGES/volto.po @@ -16,7 +16,62 @@ msgstr "" msgid "CSS height" msgstr "" +#: Widgets/CreatableSelectWidget +# defaultMessage: Choices +msgid "Choices" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Close +msgid "Close" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Default +msgid "Default" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Description +msgid "Description" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No options +msgid "No options" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No value +msgid "No value" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Required +msgid "Required" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Select… +msgid "Select…" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Short Name +msgid "Short Name" +msgstr "" + #: Blocks/EmbedTableauVisualization/schema # defaultMessage: Tableau height msgid "Tableau height" msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Title +msgid "Title" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Used for programmatic access to the fieldset. +msgid "Used for programmatic access to the fieldset." +msgstr "" diff --git a/locales/volto.pot b/locales/volto.pot index 3f6f70d..5e56137 100644 --- a/locales/volto.pot +++ b/locales/volto.pot @@ -1,7 +1,7 @@ msgid "" msgstr "" "Project-Id-Version: Plone\n" -"POT-Creation-Date: 2023-11-29T13:30:10.494Z\n" +"POT-Creation-Date: 2023-12-13T10:32:08.626Z\n" "Last-Translator: Plone i18n \n" "Language-Team: Plone i18n \n" "MIME-Version: 1.0\n" @@ -18,7 +18,62 @@ msgstr "" msgid "CSS height" msgstr "" +#: Widgets/CreatableSelectWidget +# defaultMessage: Choices +msgid "Choices" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Close +msgid "Close" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Default +msgid "Default" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Description +msgid "Description" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No options +msgid "No options" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: No value +msgid "No value" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Required +msgid "Required" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Select… +msgid "Select…" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Short Name +msgid "Short Name" +msgstr "" + #: Blocks/EmbedTableauVisualization/schema # defaultMessage: Tableau height msgid "Tableau height" msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Title +msgid "Title" +msgstr "" + +#: Widgets/CreatableSelectWidget +# defaultMessage: Used for programmatic access to the fieldset. +msgid "Used for programmatic access to the fieldset." +msgstr "" diff --git a/package.json b/package.json index 653823d..de91f37 100644 --- a/package.json +++ b/package.json @@ -75,4 +75,4 @@ "cypress:open": "make cypress-open", "prepare": "husky install" } -} \ No newline at end of file +} diff --git a/src/Blocks/EmbedTableauVisualization/__snapshots__/Edit.test.jsx.snap b/src/Blocks/EmbedTableauVisualization/__snapshots__/Edit.test.jsx.snap index f0580e7..1036db9 100644 --- a/src/Blocks/EmbedTableauVisualization/__snapshots__/Edit.test.jsx.snap +++ b/src/Blocks/EmbedTableauVisualization/__snapshots__/Edit.test.jsx.snap @@ -50,12 +50,31 @@ Array [ className="ui segment form attached" >
Tableau visualization - - No description +
+

+ When using context query parameters please use the corresponding field name from the Tableau service. +

+

+ NOTE: The embeded tableau dashboard must have the parameters defined in the + + + 'Dynamic parameters' + + + list so that the context query parameters can take effect. +

+
-
- Query parameters: - - - When using page level parameters to filter the dashboard, please use the corresponding field name from the Tableau service. -
- Extra options + Parameters
Static parameters - diff --git a/src/Widgets/CreatableSelectWidget.jsx b/src/Widgets/CreatableSelectWidget.jsx new file mode 100644 index 0000000..f8cf4b2 --- /dev/null +++ b/src/Widgets/CreatableSelectWidget.jsx @@ -0,0 +1,300 @@ +/** + * CreatableSelectWidget component. + * @module components/manage/Widgets/CreatableSelectWidget + */ + +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { compose } from 'redux'; +import { map } from 'lodash'; +import { defineMessages, injectIntl } from 'react-intl'; +import { + getVocabFromHint, + getVocabFromField, + getVocabFromItems, +} from '@plone/volto/helpers'; +import { FormFieldWrapper } from '@plone/volto/components'; +import { getVocabulary, getVocabularyTokenTitle } from '@plone/volto/actions'; +import { normalizeValue } from '@plone/volto/components/manage/Widgets/SelectUtils'; + +import { + customSelectStyles, + DropdownIndicator, + ClearIndicator, + Option, + selectTheme, + MenuList, + MultiValueContainer, +} from '@plone/volto/components/manage/Widgets/SelectStyling'; +import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable'; + +const messages = defineMessages({ + default: { + id: 'Default', + defaultMessage: 'Default', + }, + idTitle: { + id: 'Short Name', + defaultMessage: 'Short Name', + }, + idDescription: { + id: 'Used for programmatic access to the fieldset.', + defaultMessage: 'Used for programmatic access to the fieldset.', + }, + title: { + id: 'Title', + defaultMessage: 'Title', + }, + description: { + id: 'Description', + defaultMessage: 'Description', + }, + close: { + id: 'Close', + defaultMessage: 'Close', + }, + choices: { + id: 'Choices', + defaultMessage: 'Choices', + }, + required: { + id: 'Required', + defaultMessage: 'Required', + }, + select: { + id: 'Select…', + defaultMessage: 'Select…', + }, + no_value: { + id: 'No value', + defaultMessage: 'No value', + }, + no_options: { + id: 'No options', + defaultMessage: 'No options', + }, +}); + +/** + * CreatableSelectWidget component class. + * @function CreatableSelectWidget + * @returns {string} Markup of the component. + */ +class CreatableSelectWidget extends Component { + /** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ + static propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + required: PropTypes.bool, + error: PropTypes.arrayOf(PropTypes.string), + getVocabulary: PropTypes.func.isRequired, + getVocabularyTokenTitle: PropTypes.func.isRequired, + choices: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.object, PropTypes.array]), + ), + items: PropTypes.shape({ + vocabulary: PropTypes.object, + }), + widgetOptions: PropTypes.shape({ + vocabulary: PropTypes.object, + }), + value: PropTypes.oneOfType([ + PropTypes.object, + PropTypes.string, + PropTypes.bool, + PropTypes.func, + PropTypes.array, + ]), + onChange: PropTypes.func.isRequired, + onBlur: PropTypes.func, + onClick: PropTypes.func, + onEdit: PropTypes.func, + onDelete: PropTypes.func, + wrapped: PropTypes.bool, + noValueOption: PropTypes.bool, + customOptionStyling: PropTypes.any, + isMulti: PropTypes.bool, + creatable: PropTypes.bool, + placeholder: PropTypes.string, + }; + + /** + * Default properties + * @property {Object} defaultProps Default properties. + * @static + */ + static defaultProps = { + description: null, + required: false, + items: { + vocabulary: null, + }, + widgetOptions: { + vocabulary: null, + }, + error: [], + choices: [], + value: null, + onChange: () => {}, + onBlur: () => {}, + onClick: () => {}, + onEdit: null, + onDelete: null, + noValueOption: true, + customOptionStyling: null, + }; + + /** + * Component did mount + * @method componentDidMount + * @returns {undefined} + */ + componentDidMount() { + if ( + (!this.props.choices || this.props.choices?.length === 0) && + this.props.vocabBaseUrl + ) { + this.props.getVocabulary({ + vocabNameOrURL: this.props.vocabBaseUrl, + size: -1, + subrequest: this.props.lang, + }); + } + } + + /** + * Render method. + * @method render + * @returns {string} Markup for the component. + */ + render() { + const { id, choices, value, intl, onChange } = this.props; + const normalizedValue = normalizeValue(choices, value, intl); + // Make sure that both disabled and isDisabled (from the DX layout feat work) + const disabled = this.props.disabled || this.props.isDisabled; + const Select = this.props.creatable + ? this.props.reactSelectCreateable.default + : this.props.reactSelect.default; + + let options = this.props.vocabBaseUrl + ? this.props.choices + : [ + ...map(choices, (option) => ({ + value: option[0], + label: + // Fix "None" on the serializer, to remove when fixed in p.restapi + option[1] !== 'None' && option[1] ? option[1] : option[0], + })), + // Only set "no-value" option if there's no default in the field + // TODO: also if this.props.defaultValue? + ...(this.props.noValueOption && !this.props.default + ? [ + { + label: this.props.intl.formatMessage(messages.no_value), + value: 'no-value', + }, + ] + : []), + ]; + + const isMulti = this.props.isMulti + ? this.props.isMulti + : id === 'roles' || id === 'groups' || this.props.type === 'array'; + + return ( + +