UNPKG

@plone/volto

Version:
514 lines (490 loc) 14.5 kB
/** * Content Types component. * @module components/manage/Controlpanels/ContentTypes */ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { compose } from 'redux'; import { Link } from 'react-router-dom'; import { getParentUrl, getId } from '@plone/volto/helpers/Url/Url'; import { createPortal } from 'react-dom'; import last from 'lodash/last'; import { Confirm, Container, Table, Button, Header } from 'semantic-ui-react'; import { toast } from 'react-toastify'; import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'; import Error from '@plone/volto/components/theme/Error/Error'; import Icon from '@plone/volto/components/theme/Icon/Icon'; import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar'; import Toast from '@plone/volto/components/manage/Toast/Toast'; import ContentTypesActions from '@plone/volto/components/manage/Controlpanels/ContentTypesActions'; import { getControlpanel, postControlpanel, deleteControlpanel, } from '@plone/volto/actions/controlpanels/controlpanels'; import addSVG from '@plone/volto/icons/add-document.svg'; import backSVG from '@plone/volto/icons/back.svg'; import { ModalForm } from '@plone/volto/components/manage/Form'; const messages = defineMessages({ add: { id: 'Add', defaultMessage: 'Add', }, back: { id: 'Back', defaultMessage: 'Back', }, yes: { id: 'Yes', defaultMessage: 'Yes', }, no: { id: 'No', defaultMessage: 'No', }, addTypeFormTitle: { id: 'Add new content type', defaultMessage: 'Add new content type', }, addTypeButtonTitle: { id: 'Add new content type', defaultMessage: 'Add new content type', }, addTypeFormTitleTitle: { id: 'Title', defaultMessage: 'Title', }, addTypeFormDescriptionTitle: { id: 'Description', defaultMessage: 'Description', }, success: { id: 'Success', defaultMessage: 'Success', }, typeCreated: { id: 'Content type created', defaultMessage: 'Content type created', }, deleteConfirmTitle: { id: 'Delete Type', defaultMessage: 'Delete Type', }, typeDeleted: { id: 'Content type deleted', defaultMessage: 'Content type deleted', }, }); /** * ContentTypes class. * @class ContentTypes * @extends Component */ class ContentTypes extends Component { /** * Property types. * @property {Object} propTypes Property types. * @static */ static propTypes = { id: PropTypes.string.isRequired, getControlpanel: PropTypes.func.isRequired, postControlpanel: PropTypes.func.isRequired, deleteControlpanel: PropTypes.func.isRequired, pathname: PropTypes.string.isRequired, cpanelRequest: PropTypes.objectOf(PropTypes.any).isRequired, controlpanel: PropTypes.shape({ '@id': PropTypes.string, items: PropTypes.arrayOf( PropTypes.shape({ '@id': PropTypes.string, title: PropTypes.string, description: PropTypes.string, count: PropTypes.integer, }), ), }), }; /** * Constructor * @method constructor * @param {Object} props Component properties * @constructs Types */ constructor(props) { super(props); this.onAddTypeSubmit = this.onAddTypeSubmit.bind(this); this.onAddTypeError = this.onAddTypeError.bind(this); this.onAddTypeSuccess = this.onAddTypeSuccess.bind(this); this.onEdit = this.onEdit.bind(this); this.onLayout = this.onLayout.bind(this); this.onSchema = this.onSchema.bind(this); this.onDelete = this.onDelete.bind(this); this.onDeleteCancel = this.onDeleteCancel.bind(this); this.onDeleteOk = this.onDeleteOk.bind(this); this.onDeleteTypeSuccess = this.onDeleteTypeSuccess.bind(this); this.state = { showAddType: false, addTypeError: '', showDelete: false, typeToDelete: undefined, error: null, isClient: false, }; } /** * Component did mount * @method componentDidMount * @returns {undefined} */ componentDidMount() { this.props.getControlpanel(this.props.id); this.setState({ isClient: true }); } UNSAFE_componentWillReceiveProps(nextProps) { // Get if ( this.props.cpanelRequest.get.loading && nextProps.cpanelRequest.get.error ) { this.setState({ error: nextProps.cpanelRequest.get.error, }); } // Create if ( this.props.cpanelRequest.post.loading && nextProps.cpanelRequest.post.loaded ) { this.props.getControlpanel(this.props.id); this.onAddTypeSuccess(); } if ( this.props.cpanelRequest.post.loading && nextProps.cpanelRequest.post.error ) { this.onAddTypeError(nextProps.cpanelRequest.post.error); } // Delete if ( this.props.cpanelRequest.delete.loading && nextProps.cpanelRequest.delete.loaded ) { this.props.getControlpanel(this.props.id); this.onDeleteTypeSuccess(); } } /** Add */ /** * @param {object} data Form data from the ModalForm. * @param {func} callback to set new form data in the ModalForm * @memberof ContentTypes * @returns {undefined} */ onAddTypeSubmit(data, callback) { this.props.postControlpanel(this.props.id, data); this.setState({ addTypeSetFormDataCallback: callback, }); } /** * Handle Errors after postControlpanel() * * @param {*} error object. Requires the property .message * @memberof ContentTypes * @returns {undefined} */ onAddTypeError(error) { this.setState({ addTypeError: error.message, }); } /** * Handle Success after postControlpanel() * * @memberof ContentTypes * @returns {undefined} */ onAddTypeSuccess() { this.state.addTypeSetFormDataCallback({}); this.setState({ showAddType: false, addTypeError: undefined, addTypeSetFormDataCallback: undefined, }); toast.success( <Toast success title={this.props.intl.formatMessage(messages.success)} content={this.props.intl.formatMessage(messages.typeCreated)} />, ); } /** Edit */ /** * @param {*} event Event object. * @param {*} { value } * @memberof ContentTypes * @returns {undefined} */ onEdit(event, { value }) { this.props.history.push(value); } /** * Layout button click * @param {*} event * @param {string} value * @returns {undefined} */ onLayout(event, { value }) { this.props.history.push(value); } /** Delete */ /** * @param {*} event Event object. * @param {*} { value } * @memberof ContentTypes * @returns {undefined} */ onDelete(event, { value }) { if (value) { this.setState({ showDelete: true, typeToDelete: value, }); } } /** Folder * @param {Object} event Event object. * @param {string} { value } * @memberof ContentTypes * @returns {undefined} */ onSchema(event, { value }) { if (value) { this.props.history.push(`${this.props.pathname}/${value}/schema`); } } /** * On delete ok * @method onDeleteOk * @memberof ContentTypes * @returns {undefined} */ onDeleteOk() { const item = getId(this.state.typeToDelete); this.props.deleteControlpanel(this.props.id, item); this.setState({ showDelete: false, typeToDelete: undefined, }); } /** * On delete cancel * @method onDeleteCancel * @memberof ContentTypes * @returns {undefined} */ onDeleteCancel() { this.setState({ showDelete: false, typeToDelete: undefined, }); } /** * Handle Success after deleteControlpanel() * * @method onDeleteTypeSuccess * @memberof ContentTypes * @returns {undefined} */ onDeleteTypeSuccess() { toast.success( <Toast success title={this.props.intl.formatMessage(messages.success)} content={this.props.intl.formatMessage(messages.typeDeleted)} />, ); } /** * Render method. * @method render * @returns {string} Markup for the component. */ render() { // Error if (this.state.error) { return <Error error={this.state.error} />; } if (!this.props.controlpanel) { return <div />; } return ( <Container className="types-control-panel"> <div className="container"> <Confirm open={this.state.showDelete} header={this.props.intl.formatMessage(messages.deleteConfirmTitle)} cancelButton={this.props.intl.formatMessage(messages.no)} confirmButton={this.props.intl.formatMessage(messages.yes)} content={ <div className="content"> <ul className="content"> <FormattedMessage id="Do you really want to delete the type {typename}?" defaultMessage="Do you really want to delete type {typename}?" values={{ typename: <b>{getId(this.state.typeToDelete || '')}</b>, }} /> </ul> </div> } onCancel={this.onDeleteCancel} onConfirm={this.onDeleteOk} /> <ModalForm open={this.state.showAddType} className="modal" onSubmit={this.onAddTypeSubmit} submitError={this.state.addTypeError} onCancel={() => this.setState({ showAddType: false })} title={this.props.intl.formatMessage(messages.addTypeFormTitle)} loading={this.props.cpanelRequest.post.loading} schema={{ fieldsets: [ { id: 'default', title: 'Content type', fields: ['title', 'description'], }, ], properties: { title: { title: this.props.intl.formatMessage( messages.addTypeFormTitleTitle, ), type: 'string', description: '', }, description: { title: this.props.intl.formatMessage( messages.addTypeFormDescriptionTitle, ), type: 'string', description: '', }, }, required: ['title'], }} /> </div> <Container> <article id="content"> <Header disabled>{this.props.controlpanel.title}</Header> <section id="content-core"> <Table compact singleLine striped> <Table.Header> <Table.Row> <Table.HeaderCell> <FormattedMessage id="Type" defaultMessage="Type" /> </Table.HeaderCell> <Table.HeaderCell> <FormattedMessage id="Description" defaultMessage="Description" /> </Table.HeaderCell> <Table.HeaderCell> <FormattedMessage id="Items" defaultMessage="Items" /> </Table.HeaderCell> <Table.HeaderCell textAlign="right"> <FormattedMessage id="Actions" defaultMessage="Actions" /> </Table.HeaderCell> </Table.Row> </Table.Header> <Table.Body> {this.props.controlpanel.items.map((item) => ( <Table.Row key={item['@id']}> <Table.Cell> <Link to={`${this.props.pathname}/${item['id']}`}> {item.title} </Link> </Table.Cell> <Table.Cell>{item.description}</Table.Cell> <Table.Cell>{item.count}</Table.Cell> <Table.Cell textAlign="right"> <ContentTypesActions item={item} path={this.props.pathname} onEdit={this.onEdit} onDelete={this.onDelete} onSchema={this.onSchema} onLayout={this.onLayout} /> </Table.Cell> </Table.Row> ))} </Table.Body> </Table> </section> </article> </Container> {this.state.isClient && createPortal( <Toolbar pathname={this.props.pathname} hideDefaultViewButtons inner={ <> <Link to={getParentUrl(this.props.pathname)} className="item"> <Icon name={backSVG} size="30px" className="contents circled" title={this.props.intl.formatMessage(messages.back)} /> </Link> <Button className="add" aria-label={this.props.intl.formatMessage(messages.add)} tabIndex={0} id="toolbar-add" onClick={() => { this.setState({ showAddType: true }); }} > <Icon name={addSVG} title={this.props.intl.formatMessage( messages.addTypeButtonTitle, )} /> </Button> </> } />, document.getElementById('toolbar'), )} </Container> ); } } export default compose( injectIntl, connect( (state, props) => ({ controlpanel: state.controlpanels.controlpanel, cpanelRequest: state.controlpanels, pathname: props.location.pathname, id: last(props.location.pathname.split('/')), }), { getControlpanel, postControlpanel, deleteControlpanel, }, ), )(ContentTypes);