diff --git a/packages/neos-ui-editors/src/Editors/Button/index.js b/packages/neos-ui-editors/src/Editors/Button/index.js new file mode 100644 index 0000000000..8150c13611 --- /dev/null +++ b/packages/neos-ui-editors/src/Editors/Button/index.js @@ -0,0 +1,165 @@ +import React, {Component} from 'react'; +import PropTypes from 'prop-types'; +import {Icon, IconButton} from '@neos-project/react-ui-components'; +import style from './style.module.css'; +import cx from 'classnames'; +import {neos} from '@neos-project/neos-ui-decorators'; +import {connect} from 'react-redux'; +import {selectors} from '@neos-project/neos-ui-redux-store'; + +const getDataLoaderOptionsForProps = props => ({ + contextNodePath: props.focusedNodePath, + dataSourceIdentifier: props.options.dataSourceIdentifier, + dataSourceUri: props.options.dataSourceUri, + dataSourceAdditionalData: props.options.dataSourceAdditionalData, + dataSourceDisableCaching: Boolean(props.options.dataSourceDisableCaching) +}); + +@neos(globalRegistry => ({ + i18nRegistry: globalRegistry.get('i18n'), + dataSourcesDataLoader: globalRegistry.get('dataLoaders').get('DataSources') +})) +@connect(state => ({ + focusedNodePath: selectors.CR.Nodes.focusedNodePathSelector(state) +})) + +export default class ButtonEditor extends Component { + static propTypes = { + commit: PropTypes.func.isRequired, + highlight: PropTypes.bool, + value: PropTypes.any, + className: PropTypes.string, + options: PropTypes.shape({ + allowEmpty: PropTypes.bool, + multiple: PropTypes.bool, + disabled: PropTypes.bool, + values: PropTypes.objectOf(PropTypes.shape({ + label: PropTypes.string, + icon: PropTypes.string, + iconActive: PropTypes.string, + disabled: PropTypes.bool + })), + + dataSourceIdentifier: PropTypes.string, + dataSourceUri: PropTypes.string, + dataSourceDisableCaching: PropTypes.bool, + dataSourceAdditionalData: PropTypes.objectOf(PropTypes.any) + }).isRequired, + + i18nRegistry: PropTypes.object.isRequired + }; + + static defaultOptions = { + allowEmpty: true, + multiple: false, + disabled: false + }; + + state = { + selected: [], + isLoading: false, + buttons: [] + }; + + options = {}; + + initialValueType = 'string'; + + constructor(props) { + super(props); + const {commit, options, value} = props; + this.options = Object.assign({}, this.constructor.defaultOptions, options) + // TODO: get actual property data type + this.initialValueType = Array.isArray(value) ? 'array' : 'string'; + if (this.options.multiple && this.initialValueType === 'string') { + console.warn(`Misconfiguration in property "${props.identifier}". Multiple is activated but value type seems to be "string" but should be "array".`); + } + + this.state = { + selected: Array.isArray(value) ? value : (value ? [value] : []), + buttons: this.hasDataSource() ? [] : this.createButtonsFromConfiguration(this.options.values) + }; + this.commit = commit; + } + + hasDataSource() { + return this.options.dataSourceIdentifier || this.options.dataSourceUri; + } + + componentDidMount() { + if (this.hasDataSource()) { + this.loadDataSourceOptions(); + } + } + + loadDataSourceOptions() { + this.setState({isLoading: true}); + this.props.dataSourcesDataLoader.resolveValue(getDataLoaderOptionsForProps(this.props), this.props.value) + .then(configuredValues => { + this.setState({ + isLoading: false, + buttons: this.createButtonsFromConfiguration(configuredValues) + }); + }); + } + + createButtonsFromConfiguration(configuredValues) { + return Object.entries(configuredValues).map(([key, configuredValue]) => { + return { + value: configuredValue.value ?? key, + label: this.props.i18nRegistry.translate(configuredValue.label), + icon: configuredValue.icon, + iconActive: configuredValue.iconActive + }; + }); + } + + handleSelect(selectedValue) { + const {multiple, allowEmpty} = this.options; + // we'd like to have a copy instead of ref + let selected = [...this.state.selected]; + if (multiple === false) { + if (selected.includes(selectedValue)) { + selected = []; + } else { + selected = [selectedValue]; + } + } else if (selected.includes(selectedValue)) { + selected.splice(selected.indexOf(selectedValue), 1); + } else { + selected.push(selectedValue); + } + + // if allowEmpty is false but new selected length will be 0, drop changes + if (!allowEmpty && selected.length === 0) { + return; + } + this.setState({selected}); + + this.commit(this.initialValueType === 'string' ? selected[0] || '' : selected); + } + + isSelected(val) { + return this.state.selected.includes(val); + } + + render() { + return (
+ { + this.state.isLoading + ? + : this.state.buttons.map((button) => ()) + } +
); + } +} diff --git a/packages/neos-ui-editors/src/Editors/Button/style.module.css b/packages/neos-ui-editors/src/Editors/Button/style.module.css new file mode 100644 index 0000000000..c237559926 --- /dev/null +++ b/packages/neos-ui-editors/src/Editors/Button/style.module.css @@ -0,0 +1,12 @@ +.buttonEditor { + margin-inline: calc(var(--spacing-Quarter) / -2); +} + +.buttonEditor .button { + margin: calc(var(--spacing-Quarter) / 2); +} + +.buttonEditor.buttonEditorHighlight { + box-shadow: 0 0 0 2px var(--colors-Warn); + border-radius: 2px; +} diff --git a/packages/neos-ui-editors/src/Editors/index.js b/packages/neos-ui-editors/src/Editors/index.js index 9450b1e8b5..17a11ce728 100644 --- a/packages/neos-ui-editors/src/Editors/index.js +++ b/packages/neos-ui-editors/src/Editors/index.js @@ -16,6 +16,7 @@ import MasterPlugin from './MasterPlugin/index'; import PluginViews from './PluginViews/index'; import PluginView from './PluginView/index'; import UriPathSegment from './UriPathSegment/index'; +import ButtonEditor from './Button/index'; export { TextField, @@ -35,5 +36,6 @@ export { MasterPlugin, PluginViews, PluginView, - UriPathSegment + UriPathSegment, + ButtonEditor }; diff --git a/packages/neos-ui-editors/src/index.js b/packages/neos-ui-editors/src/index.js index fac72fe053..274fe55bdb 100644 --- a/packages/neos-ui-editors/src/index.js +++ b/packages/neos-ui-editors/src/index.js @@ -18,7 +18,8 @@ import { AssetEditor, PluginViews, PluginView, - UriPathSegment + UriPathSegment, + ButtonEditor } from './Editors/index'; import { @@ -58,6 +59,7 @@ export { PluginViews, PluginView, UriPathSegment, + ButtonEditor, CodeMirrorWrap, CKEditorWrap, @@ -93,6 +95,7 @@ export default { PluginViews, PluginView, UriPathSegment, + ButtonEditor, CodeMirrorWrap, CKEditorWrap, diff --git a/packages/neos-ui-editors/src/manifest.js b/packages/neos-ui-editors/src/manifest.js index 3c6749d5bc..c2d933c7cc 100644 --- a/packages/neos-ui-editors/src/manifest.js +++ b/packages/neos-ui-editors/src/manifest.js @@ -94,6 +94,10 @@ manifest('inspectorEditors', {}, globalRegistry => { component: Editors.UriPathSegment }); + editorsRegistry.set('Neos.Neos/Inspector/Editors/ButtonEditor', { + component: Editors.ButtonEditor + }); + // // Secondary inspector editors //