@plone/volto
Version:
Volto
514 lines (490 loc) • 14.5 kB
JSX
/**
* 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);