UNPKG

@plone/volto

Version:
1,140 lines (1,080 loc) 38.4 kB
/** * Form component. * @module components/manage/Form/Form */ import Icon from '@plone/volto/components/theme/Icon/Icon'; import Toast from '@plone/volto/components/manage/Toast/Toast'; import { Field, BlocksForm } from '@plone/volto/components/manage/Form'; import BlocksToolbar from '@plone/volto/components/manage/Form/BlocksToolbar'; import UndoToolbar from '@plone/volto/components/manage/Form/UndoToolbar'; import { difference } from '@plone/volto/helpers/Utils/Utils'; import FormValidation from '@plone/volto/helpers/FormValidation/FormValidation'; import { getBlocksFieldname, getBlocksLayoutFieldname, hasBlocksData, } from '@plone/volto/helpers/Blocks/Blocks'; import { applySchemaEnhancer } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer'; import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels'; import aheadSVG from '@plone/volto/icons/ahead.svg'; import clearSVG from '@plone/volto/icons/clear.svg'; import upSVG from '@plone/volto/icons/up-key.svg'; import downSVG from '@plone/volto/icons/down-key.svg'; import findIndex from 'lodash/findIndex'; import isEmpty from 'lodash/isEmpty'; import isEqual from 'lodash/isEqual'; import keys from 'lodash/keys'; import map from 'lodash/map'; import mapValues from 'lodash/mapValues'; import pickBy from 'lodash/pickBy'; import without from 'lodash/without'; import cloneDeep from 'lodash/cloneDeep'; import xor from 'lodash/xor'; import isBoolean from 'lodash/isBoolean'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { injectIntl } from 'react-intl'; import { createPortal } from 'react-dom'; import { connect } from 'react-redux'; import { Accordion, Button, Container as SemanticContainer, Form as UiForm, Message, Segment, Tab, } from 'semantic-ui-react'; import { v4 as uuid } from 'uuid'; import { toast } from 'react-toastify'; import { setMetadataFieldsets, resetMetadataFocus, setSidebarTab, } from '@plone/volto/actions/sidebar/sidebar'; import { setFormData, setUIState } from '@plone/volto/actions/form/form'; import { compose } from 'redux'; import config from '@plone/volto/registry'; import SlotRenderer from '@plone/volto/components/theme/SlotRenderer/SlotRenderer'; /** * Form container class. * @class Form * @extends Component */ class Form extends Component { /** * Property types. * @property {Object} propTypes Property types. * @static */ static propTypes = { schema: PropTypes.shape({ fieldsets: PropTypes.arrayOf( PropTypes.shape({ fields: PropTypes.arrayOf(PropTypes.string), id: PropTypes.string, title: PropTypes.string, }), ), properties: PropTypes.objectOf(PropTypes.any), definitions: PropTypes.objectOf(PropTypes.any), required: PropTypes.arrayOf(PropTypes.string), }), widgets: PropTypes.objectOf(PropTypes.any), component: PropTypes.any, formData: PropTypes.objectOf(PropTypes.any), globalData: PropTypes.objectOf(PropTypes.any), metadataFieldsets: PropTypes.arrayOf(PropTypes.string), metadataFieldFocus: PropTypes.string, pathname: PropTypes.string, onSubmit: PropTypes.func, onCancel: PropTypes.func, submitLabel: PropTypes.string, cancelLabel: PropTypes.string, textButtons: PropTypes.bool, buttonComponent: PropTypes.any, resetAfterSubmit: PropTypes.bool, resetOnCancel: PropTypes.bool, isEditForm: PropTypes.bool, isAdminForm: PropTypes.bool, title: PropTypes.string, error: PropTypes.shape({ message: PropTypes.string, }), loading: PropTypes.bool, hideActions: PropTypes.bool, description: PropTypes.string, visual: PropTypes.bool, blocks: PropTypes.arrayOf(PropTypes.object), isFormSelected: PropTypes.bool, onSelectForm: PropTypes.func, editable: PropTypes.bool, onChangeFormData: PropTypes.func, requestError: PropTypes.string, allowedBlocks: PropTypes.arrayOf(PropTypes.string), showRestricted: PropTypes.bool, global: PropTypes.bool, }; /** * Default properties. * @property {Object} defaultProps Default properties. * @static */ static defaultProps = { formData: null, widgets: null, component: null, onSubmit: null, onCancel: null, submitLabel: null, cancelLabel: null, textButtons: false, buttonComponent: null, resetAfterSubmit: false, resetOnCancel: false, isEditForm: false, isAdminForm: false, title: null, description: null, error: null, loading: null, hideActions: false, visual: false, blocks: [], pathname: '', schema: {}, isFormSelected: true, onSelectForm: null, editable: true, requestError: null, allowedBlocks: null, global: false, }; /** * Constructor * @method constructor * @param {Object} props Component properties * @constructs Form */ constructor(props) { super(props); const ids = { title: uuid(), text: uuid(), }; let { formData, schema: originalSchema } = props; const blocksFieldname = getBlocksFieldname(formData); const blocksLayoutFieldname = getBlocksLayoutFieldname(formData); const schema = this.removeBlocksLayoutFields(originalSchema); this.props.setMetadataFieldsets( schema?.fieldsets ? schema.fieldsets.map((fieldset) => fieldset.id) : [], ); if (!props.isEditForm) { // It's a normal (add form), get defaults from schema formData = { ...mapValues(props.schema.properties, 'default'), ...formData, }; } // We initialize the formData snapshot in here, before the initial data checks const initialFormData = cloneDeep(formData); // Adding fallback in case the fields are empty, so we are sure that the edit form // shows at least the default blocks if ( formData.hasOwnProperty(blocksFieldname) && formData.hasOwnProperty(blocksLayoutFieldname) ) { if ( !formData[blocksLayoutFieldname] || isEmpty(formData[blocksLayoutFieldname].items) ) { formData[blocksLayoutFieldname] = { items: [ids.title, ids.text], }; } if (!formData[blocksFieldname] || isEmpty(formData[blocksFieldname])) { formData[blocksFieldname] = { [ids.title]: { '@type': 'title', }, [ids.text]: { '@type': config.settings.defaultBlockType, }, }; } } let selectedBlock = null; if ( formData.hasOwnProperty(blocksLayoutFieldname) && formData[blocksLayoutFieldname].items.length > 0 ) { if (config.blocks?.initialBlocksFocus === null) { selectedBlock = null; } else if (this.props.type in config.blocks?.initialBlocksFocus) { // Default selected is not the first block, but the one from config. // TODO Select first block and not an arbitrary one. Object.keys(formData[blocksFieldname]).forEach((b_key) => { if ( formData[blocksFieldname][b_key]['@type'] === config.blocks?.initialBlocksFocus?.[this.props.type] ) { selectedBlock = b_key; } }); } else { selectedBlock = formData[blocksLayoutFieldname].items[0]; } } // Sync state to global state if (this.props.global) { this.props.setFormData(formData); } this.props.setUIState({ selected: selectedBlock, multiSelected: [], hovered: null, }); // Set initial state this.state = { formData, initialFormData, errors: {}, isClient: false, // Ensure focus remain in field after change inFocus: {}, sidebarMetadataIsAvailable: false, }; this.onChangeField = this.onChangeField.bind(this); this.onSelectBlock = this.onSelectBlock.bind(this); this.onSubmit = this.onSubmit.bind(this); this.onCancel = this.onCancel.bind(this); this.onTabChange = this.onTabChange.bind(this); this.onBlurField = this.onBlurField.bind(this); this.onClickInput = this.onClickInput.bind(this); this.onToggleMetadataFieldset = this.onToggleMetadataFieldset.bind(this); } /** * On updates caused by props change * if errors from Backend come, these will be shown to their corresponding Fields * also the first Tab to have any errors will be selected * @param {Object} prevProps */ async componentDidUpdate(prevProps, prevState) { let { requestError } = this.props; let errors = {}; let activeIndex = 0; if (!this.props.isFormSelected && prevProps.isFormSelected) { this.props.setUIState({ selected: null, }); } if (requestError && prevProps.requestError !== requestError) { errors = FormValidation.giveServerErrorsToCorrespondingFields(requestError); activeIndex = FormValidation.showFirstTabWithErrors({ errors, schema: this.props.schema, }); this.setState({ errors, activeIndex, }); } if (this.props.onChangeFormData) { if (!isEqual(prevState?.formData, this.state.formData)) { this.props.onChangeFormData(this.state.formData); } } if ( this.props.global && !isEqual(this.props.globalData, prevProps.globalData) ) { this.setState({ formData: this.props.globalData, }); } if (!isEqual(prevProps.schema, this.props.schema)) { this.props.setMetadataFieldsets( this.removeBlocksLayoutFields(this.props.schema).fieldsets.map( (fieldset) => fieldset.id, ), ); } if ( this.props.metadataFieldFocus !== '' && !isEqual(prevProps.metadataFieldFocus, this.props.metadataFieldFocus) ) { // Scroll into view document .querySelector(`.field-wrapper-${this.props.metadataFieldFocus}`) .scrollIntoView(); // Set focus to first input if available document .querySelector(`.field-wrapper-${this.props.metadataFieldFocus} input`) ?.focus(); // Reset focus field this.props.resetMetadataFocus(); } if ( !this.state.sidebarMetadataIsAvailable && document.getElementById('sidebar-metadata') ) { this.setState(() => ({ sidebarMetadataIsAvailable: true })); } } /** * Tab selection is done only by setting activeIndex in state */ onTabChange(e, { activeIndex }) { const defaultFocus = this.props.schema.fieldsets[activeIndex].fields[0]; this.setState({ activeIndex, ...(defaultFocus ? { inFocus: { [defaultFocus]: true } } : {}), }); } /** * If user clicks on input, the form will be not considered pristine * this will avoid onBlur effects without interaction with the form * @param {Object} e event */ onClickInput(e) { this.setState({ isFormPristine: false }); } /** * Validate fields on blur * @method onBlurField * @param {string} id Id of the field * @param {*} value Value of the field * @returns {undefined} */ onBlurField(id, value) { if (!this.state.isFormPristine) { const errors = FormValidation.validateFieldsPerFieldset({ schema: this.props.schema, formData: this.state.formData, formatMessage: this.props.intl.formatMessage, touchedField: { [id]: value }, }); this.setState({ errors, }); } } /** * Component did mount * @method componentDidMount * @returns {undefined} */ componentDidMount() { this.setState({ isClient: true }); } /** * Change field handler * Remove errors for changed field * @method onChangeField * @param {string} id Id of the field * @param {*} value Value of the field * @returns {undefined} */ onChangeField(id, value) { this.setState((prevState) => { const { errors, formData } = prevState; const newFormData = { ...formData, // We need to catch also when the value equals false this fixes #888 [id]: value || (value !== undefined && isBoolean(value)) ? value : null, }; delete errors[id]; if (this.props.global) { this.props.setFormData(newFormData); } return { errors, formData: newFormData, // Changing the form data re-renders the select widget which causes the // focus to get lost. To circumvent this, we set the focus back to // the input. // This could fix other widgets too but currently targeted // against the select widget only. // Ensure field to be in focus after the change inFocus: { [id]: true }, }; }); } /** * Select block handler * @method onSelectBlock * @param {string} id Id of the field * @param {string} isMultipleSelection true if multiple blocks are selected * @returns {undefined} */ onSelectBlock(id, isMultipleSelection, event) { let multiSelected = []; let selected = id; const formData = this.state.formData; if (isMultipleSelection) { selected = null; const blocksLayoutFieldname = getBlocksLayoutFieldname(formData); const blocks_layout = formData[blocksLayoutFieldname].items; if (event.shiftKey) { const anchor = this.props.uiState.multiSelected.length > 0 ? blocks_layout.indexOf(this.props.uiState.multiSelected[0]) : blocks_layout.indexOf(this.props.uiState.selected); const focus = blocks_layout.indexOf(id); if (anchor === focus) { multiSelected = [id]; } else if (focus > anchor) { multiSelected = [...blocks_layout.slice(anchor, focus + 1)]; } else { multiSelected = [...blocks_layout.slice(focus, anchor + 1)]; } window.getSelection().empty(); } if ((event.ctrlKey || event.metaKey) && !event.shiftKey) { multiSelected = this.props.uiState.multiSelected || []; if (!this.props.uiState.multiSelected.includes(this.state.selected)) { multiSelected = [...multiSelected, this.props.uiState.selected]; selected = null; } if (this.props.uiState.multiSelected.includes(id)) { selected = null; multiSelected = without(multiSelected, id); } else { multiSelected = [...multiSelected, id]; } } } this.props.setUIState({ selected, multiSelected, gridSelected: null, }); if (this.props.onSelectForm) { if (event) event.nativeEvent.stopImmediatePropagation(); this.props.onSelectForm(); } } /** * Cancel handler * It prevents event from triggering submit, reset form if props.resetAfterSubmit * and calls this.props.onCancel * @method onCancel * @param {Object} event Event object. * @returns {undefined} */ onCancel(event) { if (event) { event.preventDefault(); } if (this.props.resetOnCancel || this.props.resetAfterSubmit) { this.setState({ formData: this.props.formData, }); if (this.props.global) { this.props.setFormData(this.props.formData); } } this.props.onCancel(event); } /** * Submit handler also validate form and collect errors * @method onSubmit * @param {Object} event Event object. * @returns {undefined} */ onSubmit(event) { const formData = this.state.formData; if (event) { event.preventDefault(); } const errors = this.props.schema ? FormValidation.validateFieldsPerFieldset({ schema: this.props.schema, formData, formatMessage: this.props.intl.formatMessage, }) : {}; let blocksErrors = {}; if (hasBlocksData(formData)) { // Validate blocks const blocks = this.state.formData[getBlocksFieldname(formData)]; const blocksLayout = this.state.formData[getBlocksLayoutFieldname(formData)]; const defaultSchema = { properties: {}, fieldsets: [], required: [], }; blocksLayout.items.forEach((block) => { let blockSchema = config.blocks.blocksConfig[blocks[block]['@type']].blockSchema || defaultSchema; if (typeof blockSchema === 'function') { blockSchema = blockSchema({ intl: this.props.intl, formData: blocks[block], }); } if (config.blocks.blocksConfig[blocks[block]['@type']].blockSchema) { blockSchema = applySchemaEnhancer({ schema: blockSchema, formData: blocks[block], intl: this.props.intl, blocksConfig: config.blocks.blocksConfig, navRoot: this.props.navRoot, contentType: this.props.content['@type'], }); } const blockErrors = FormValidation.validateFieldsPerFieldset({ schema: blockSchema, formData: blocks[block], formatMessage: this.props.intl.formatMessage, }); if (keys(blockErrors).length > 0) { blocksErrors = { ...blocksErrors, [block]: { ...blockErrors }, }; } }); } if (keys(errors).length > 0 || keys(blocksErrors).length > 0) { const activeIndex = FormValidation.showFirstTabWithErrors({ errors, schema: this.props.schema, }); this.setState({ errors: { ...errors, ...(!isEmpty(blocksErrors) && { blocks: blocksErrors }), }, activeIndex, }); if (keys(errors).length > 0) { // Changes the focus to the metadata tab in the sidebar if error Object.keys(errors).forEach((err) => toast.error( <Toast error title={this.props.schema.properties[err].title || err} content={errors[err].join(', ')} />, ), ); this.props.setSidebarTab(0); } else if (keys(blocksErrors).length > 0) { const errorField = Object.entries( Object.entries(blocksErrors)[0][1], )[0][0]; const errorMessage = Object.entries( Object.entries(blocksErrors)[0][1], )[0][1]; const errorFieldTitle = errorMessage.title || errorField; toast.error( <Toast error title={this.props.intl.formatMessage( messages.blocksFieldsErrorTitle, { errorField: errorFieldTitle }, )} content={errorMessage} />, ); this.props.setSidebarTab(1); this.props.setUIState({ selected: Object.keys(blocksErrors)[0], multiSelected: [], hovered: null, }); } } else { // Get only the values that have been modified (Edit forms), send all in case that // it's an add form if (this.props.isEditForm) { this.props.onSubmit(this.getOnlyFormModifiedValues()); } else { this.props.onSubmit(formData); } if (this.props.resetAfterSubmit) { this.setState({ formData: this.props.formData, }); if (this.props.global) { this.props.setFormData(this.props.formData); } } } } /** * getOnlyFormModifiedValues handler * It returns only the values of the fields that are have really changed since the * form was loaded. Useful for edit forms and PATCH operations, when we only want to * send the changed data. * @method getOnlyFormModifiedValues * @param {Object} event Event object. * @returns {undefined} */ getOnlyFormModifiedValues = () => { const formData = this.state.formData; const fieldsModified = Object.keys( difference(formData, this.state.initialFormData), ); return { ...pickBy(formData, (value, key) => fieldsModified.includes(key)), ...(formData['@static_behaviors'] && { '@static_behaviors': formData['@static_behaviors'], }), }; }; /** * Removed blocks and blocks_layout fields from the form. * @method removeBlocksLayoutFields * @param {object} schema The schema definition of the form. * @returns A modified copy of the given schema. */ removeBlocksLayoutFields = (schema) => { const newSchema = { ...schema }; const layoutFieldsetIndex = findIndex( newSchema.fieldsets, (fieldset) => fieldset.id === 'layout', ); if (layoutFieldsetIndex > -1) { const layoutFields = newSchema.fieldsets[layoutFieldsetIndex].fields; newSchema.fieldsets[layoutFieldsetIndex].fields = layoutFields.filter( (field) => field !== 'blocks' && field !== 'blocks_layout', ); if (newSchema.fieldsets[layoutFieldsetIndex].fields.length === 0) { newSchema.fieldsets = [ ...newSchema.fieldsets.slice(0, layoutFieldsetIndex), ...newSchema.fieldsets.slice(layoutFieldsetIndex + 1), ]; } } return newSchema; }; /** * Toggle metadata fieldset handler * @method onToggleMetadataFieldset * @param {Object} event Event object. * @param {Object} blockProps Block properties. * @returns {undefined} */ onToggleMetadataFieldset(event, blockProps) { const { index } = blockProps; this.props.setMetadataFieldsets(xor(this.props.metadataFieldsets, [index])); } /** * Render method. * @method render * @returns {string} Markup for the component. */ render() { const { settings } = config; const { schema: originalSchema, onCancel, onSubmit, navRoot, type, metadataFieldsets, component, buttonComponent, } = this.props; const formData = this.state.formData; const schema = this.removeBlocksLayoutFields(originalSchema); const Container = config.getComponent({ name: 'Container' }).component || SemanticContainer; const FormComponent = component || UiForm; const ButtonComponent = buttonComponent || Button; return this.props.visual ? ( // Removing this from SSR is important, since react-beautiful-dnd supports SSR, // but draftJS don't like it much and the hydration gets messed up this.state.isClient && ( <> <SlotRenderer name="aboveContent" content={this.props.content} navRoot={navRoot} /> <Container> <> <BlocksToolbar formData={formData} selectedBlock={this.props.uiState.selected} selectedBlocks={this.props.uiState.multiSelected} onChangeBlocks={(newBlockData) => { const newFormData = { ...formData, ...newBlockData, }; this.setState({ formData: newFormData, }); if (this.props.global) { this.props.setFormData(newFormData); } }} onSetSelectedBlocks={(blockIds) => this.props.setUIState({ multiSelected: blockIds }) } onSelectBlock={this.onSelectBlock} /> <UndoToolbar state={{ formData, selected: this.props.uiState.selected, multiSelected: this.props.uiState.multiSelected, }} enableHotKeys onUndoRedo={({ state }) => { if (this.props.global) { this.props.setFormData(state.formData); } return this.setState(state); }} /> <BlocksForm onChangeFormData={(newData) => { const newFormData = { ...formData, ...newData, }; this.setState({ formData: newFormData, }); if (this.props.global) { this.props.setFormData(newFormData); } }} onChangeField={this.onChangeField} onSelectBlock={this.onSelectBlock} properties={formData} navRoot={navRoot} type={type} pathname={this.props.pathname} selectedBlock={this.props.uiState.selected} multiSelected={this.props.uiState.multiSelected} manage={this.props.isAdminForm} allowedBlocks={this.props.allowedBlocks} showRestricted={this.props.showRestricted} editable={this.props.editable} isMainForm={this.props.editable} // Properties to pass to the BlocksForm to match the View ones history={this.props.history} location={this.props.location} token={this.props.token} errors={this.state.errors} blocksErrors={this.state.errors.blocks} /> {this.state.isClient && this.state.sidebarMetadataIsAvailable && this.props.editable && createPortal( <UiForm method="post" onSubmit={this.onSubmit} error={keys(this.state.errors).length > 0} > {schema && map(schema.fieldsets, (fieldset) => ( <Accordion fluid styled className="form" key={fieldset.title} > <div key={fieldset.id} id={`metadataform-fieldset-${fieldset.id}`} > <Accordion.Title active={metadataFieldsets.includes(fieldset.id)} index={fieldset.id} onClick={this.onToggleMetadataFieldset} > {fieldset.title} {metadataFieldsets.includes(fieldset.id) ? ( <Icon name={upSVG} size="20px" /> ) : ( <Icon name={downSVG} size="20px" /> )} </Accordion.Title> <Accordion.Content active={metadataFieldsets.includes(fieldset.id)} > <Segment className="attached"> {map(fieldset.fields, (field, index) => ( <Field {...schema.properties[field]} id={field} fieldSet={fieldset.title.toLowerCase()} formData={formData} focus={ this.state.isClient && document .getElementById('sidebar-metadata') ?.contains(document.activeElement) ? this.state.inFocus[field] : false } value={formData?.[field]} required={ schema.required.indexOf(field) !== -1 } onChange={this.onChangeField} onBlur={this.onBlurField} onClick={this.onClickInput} key={field} error={this.state.errors[field]} /> ))} </Segment> </Accordion.Content> </div> </Accordion> ))} </UiForm>, document.getElementById('sidebar-metadata'), )} <SlotRenderer name="belowContent" content={this.props.content} navRoot={navRoot} /> </> </Container> </> ) ) : ( <Container> <FormComponent method="post" onSubmit={this.onSubmit} error={keys(this.state.errors).length > 0} className={settings.verticalFormTabs ? 'vertical-form' : ''} > <fieldset className="invisible"> <Segment.Group raised> {schema && schema.fieldsets.length > 1 && ( <> {settings.verticalFormTabs && this.props.title && ( <Segment secondary attached key={this.props.title}> {this.props.title} </Segment> )} <Tab menu={{ secondary: true, pointing: true, attached: true, tabular: true, className: 'formtabs', vertical: settings.verticalFormTabs, }} grid={{ paneWidth: 9, tabWidth: 3, stackable: true }} onTabChange={this.onTabChange} activeIndex={this.state.activeIndex} panes={map(schema.fieldsets, (item) => ({ menuItem: item.title, render: () => [ !settings.verticalFormTabs && this.props.title && ( <Segment secondary attached key={this.props.title}> {this.props.title} </Segment> ), item.description && ( <Message attached="bottom"> {item.description} </Message> ), ...map(item.fields, (field, index) => ( <Field widgets={this.props.widgets} {...schema.properties[field]} id={field} formData={formData} fieldSet={item.id} focus={this.state.inFocus[field]} value={formData?.[field]} required={schema.required.indexOf(field) !== -1} onChange={ this.props.editable ? this.onChangeField : () => {} } onBlur={this.onBlurField} onClick={this.onClickInput} key={field} error={this.state.errors[field]} /> )), ], }))} /> </> )} {schema && schema.fieldsets.length === 1 && ( <Segment> {this.props.title && ( <Segment className="primary"> <h1 style={{ fontSize: '16px' }}> {this.props.title}</h1> </Segment> )} {this.props.description && ( <Segment secondary>{this.props.description}</Segment> )} {keys(this.state.errors).length > 0 && ( <Message icon="warning" negative attached header={this.props.intl.formatMessage(messages.error)} content={this.props.intl.formatMessage( messages.thereWereSomeErrors, )} /> )} {this.props.error && ( <Message icon="warning" negative attached header={this.props.intl.formatMessage(messages.error)} content={this.props.error.message} /> )} {map(schema.fieldsets[0].fields, (field) => ( <Field widgets={this.props.widgets} {...schema.properties[field]} id={field} value={formData?.[field]} required={schema.required.indexOf(field) !== -1} onChange={this.onChangeField} onBlur={this.onBlurField} onClick={this.onClickInput} key={field} error={this.state.errors[field]} /> ))} </Segment> )} {!this.props.hideActions && ( <Segment className="actions" clearing> {onSubmit && (this.props.textButtons ? ( <ButtonComponent primary floated="right" type="submit" aria-label={ this.props.submitLabel ? this.props.submitLabel : this.props.intl.formatMessage(messages.save) } title={ this.props.submitLabel ? this.props.submitLabel : this.props.intl.formatMessage(messages.save) } loading={this.props.loading} > {this.props.submitLabel ? this.props.submitLabel : this.props.intl.formatMessage(messages.save)} </ButtonComponent> ) : ( <ButtonComponent basic primary floated="right" type="submit" aria-label={ this.props.submitLabel ? this.props.submitLabel : this.props.intl.formatMessage(messages.save) } title={ this.props.submitLabel ? this.props.submitLabel : this.props.intl.formatMessage(messages.save) } loading={this.props.loading} > <Icon className="circled" name={aheadSVG} size="30px" /> </ButtonComponent> ))} {onCancel && (this.props.textButtons ? ( <ButtonComponent secondary type="button" aria-label={ this.props.cancelLabel ? this.props.cancelLabel : this.props.intl.formatMessage(messages.cancel) } title={ this.props.cancelLabel ? this.props.cancelLabel : this.props.intl.formatMessage(messages.cancel) } floated="right" onClick={this.onCancel} > {this.props.cancelLabel ? this.props.cancelLabel : this.props.intl.formatMessage(messages.cancel)} </ButtonComponent> ) : ( <ButtonComponent basic secondary type="button" aria-label={ this.props.cancelLabel ? this.props.cancelLabel : this.props.intl.formatMessage(messages.cancel) } title={ this.props.cancelLabel ? this.props.cancelLabel : this.props.intl.formatMessage(messages.cancel) } floated="right" onClick={this.onCancel} > <Icon className="circled" name={clearSVG} size="30px" /> </ButtonComponent> ))} </Segment> )} </Segment.Group> </fieldset> </FormComponent> </Container> ); } } const FormIntl = injectIntl(Form, { forwardRef: true }); export default compose( connect( (state, props) => ({ content: state.content.data, globalData: state.form?.global, uiState: state.form?.ui, metadataFieldsets: state.sidebar?.metadataFieldsets, metadataFieldFocus: state.sidebar?.metadataFieldFocus, }), { setMetadataFieldsets, setSidebarTab, setFormData, setUIState, resetMetadataFocus, }, null, { forwardRef: true }, ), )(FormIntl);