@plone/volto
Version:
Volto
355 lines (329 loc) • 9.22 kB
JSX
/**
* Content type schema.
* @module components/manage/Controlpanels/ContentTypeSchema
*/
import { getSchema, putSchema } from '@plone/volto/actions/schema/schema';
import { getParentUrl } from '@plone/volto/helpers/Url/Url';
import nth from 'lodash/nth';
import Error from '@plone/volto/components/theme/Error/Error';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import { Form } from '@plone/volto/components/manage/Form';
import clearSVG from '@plone/volto/icons/clear.svg';
import saveSVG from '@plone/volto/icons/save.svg';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import { toast } from 'react-toastify';
import { compose } from 'redux';
import { Button, Header } from 'semantic-ui-react';
const messages = defineMessages({
title: {
id: '{id} Schema',
defaultMessage: '{id} Schema',
},
success: {
id: 'Success',
defaultMessage: 'Success',
},
save: {
id: 'Save',
defaultMessage: 'Save',
},
cancel: {
id: 'Cancel',
defaultMessage: 'Cancel',
},
error: {
id: 'Error',
defaultMessage: 'Error',
},
typeCreated: {
id: 'Schema updates',
defaultMessage: 'Schema updates',
},
info: {
id: 'Info',
defaultMessage: 'Info',
},
changesSaved: {
id: 'Changes saved.',
defaultMessage: 'Changes saved.',
},
});
/**
* ContentTypeSchema class.
* @class ContentTypeSchema
* @extends Component
*/
class ContentTypeSchema extends Component {
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
static propTypes = {
getSchema: PropTypes.func.isRequired,
putSchema: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
};
/**
* Default properties
* @property {Object} defaultProps Default properties.
* @static
*/
static defaultProps = {};
/**
* Constructor
* @method constructor
* @param {Object} props Component properties
* @constructs WysiwygEditor
*/
constructor(props) {
super(props);
this.state = {
error: null,
schema: null,
content: null,
isClient: false,
};
this.onCancel = this.onCancel.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.form = React.createRef();
}
/**
* Component did mount
* @method componentDidMount
* @returns {undefined}
*/
componentDidMount() {
this.props.getSchema(this.props.id);
this.setState({ isClient: true });
}
/**
* Component will receive props
* @method componentWillReceiveProps
* @param {Object} nextProps Next properties
* @returns {undefined}
*/
UNSAFE_componentWillReceiveProps(nextProps) {
// Schema error
if (this.props.schemaRequest.loading && nextProps.schemaRequest.error) {
this.setState({
error: nextProps.schemaRequest.error,
});
}
// Schema GET
if (this.props.schemaRequest.loading && nextProps.schemaRequest.loaded) {
let properties = nextProps.schema?.properties || {};
let content = {};
let value, key;
for (key in properties) {
value = properties[key].default;
if (value) {
content[key] = value;
}
}
this.setState({
schema: nextProps.schema,
content: content,
});
}
// Schema updated
if (
this.props.schemaRequest.put.loading &&
nextProps.schemaRequest.put.loaded
) {
// this.props.getSchema(this.props.id);
toast.info(
<Toast
info
title={this.props.intl.formatMessage(messages.info)}
content={this.props.intl.formatMessage(messages.changesSaved)}
/>,
);
}
// Schema update error
if (
this.props.schemaRequest.put.loading &&
nextProps.schemaRequest.put.error
) {
toast.error(
<Toast
error
title={this.props.intl.formatMessage(messages.error)}
content={JSON.stringify(
nextProps.schemaRequest.put.error.response.body?.message ||
nextProps.schemaRequest.put.error.response.text,
)}
/>,
);
}
}
/**
* Submit handler
* @method onSubmit
* @param {object} data Form data.
* @returns {undefined}
*/
onSubmit(data) {
this.props.putSchema(this.props.id, data.schema);
}
/**
* Cancel handler
* @method onCancel
* @returns {undefined}
*/
onCancel() {
let url = getParentUrl(this.props.pathname);
this.props.history.push(getParentUrl(url));
}
form = React.createRef();
makeSchemaList = (schema) => {
const result = {
title: 'Schema',
type: 'object',
fieldsets: [
{
fields: ['schema'],
id: 'default',
title: 'Default',
},
],
properties: {
schema: {
description: 'Form schema',
title: 'Form schema',
type: 'schema',
id: 'schema',
widget: 'schema',
},
},
required: [],
layouts: null,
};
result.layouts = schema.layouts.slice();
return result;
};
isEditable = (field) =>
!field.behavior || field.behavior.includes('generated');
makeSchemaData = (schema, contentType) => {
const fieldsets = schema.fieldsets.map((fieldset) => {
const readOnlyFields = fieldset.fields.filter(
(fieldId) =>
!this.isEditable(schema.properties[fieldId]) &&
fieldId !== 'changeNote',
);
const userCreatedFields = fieldset.fields.filter((fieldId) =>
this.isEditable(schema.properties[fieldId]),
);
const changeNote = fieldset.fields.filter(
(fieldId) => fieldId === 'changeNote',
);
return {
...fieldset,
fields: [...readOnlyFields, ...userCreatedFields, ...changeNote],
};
});
const result = {
...schema,
fieldsets,
contentType,
};
return { schema: JSON.stringify(result) };
};
/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
// Error
if (this.state.error) {
return <Error error={this.state.error} />;
}
if (this.state.schema) {
const contentTypeSchema = this.makeSchemaList(this.state.schema);
const schemaData = this.makeSchemaData(this.state.schema, this.props.id);
return (
<div id="page-controlpanel-schema" className="ui container">
<Header disabled>
{this.props.intl.formatMessage(messages.title, {
id: this.props?.schema?.title || this.props.id,
})}
</Header>
<Form
ref={this.form}
schema={contentTypeSchema}
formData={schemaData}
pathname={this.props.pathname}
onSubmit={this.onSubmit}
onCancel={this.onCancel}
hideActions
/>
{this.state.isClient &&
createPortal(
<Toolbar
pathname={this.props.pathname}
hideDefaultViewButtons
inner={
<>
<Button
id="toolbar-save"
className="save"
aria-label={this.props.intl.formatMessage(messages.save)}
onClick={() => this.form.current.onSubmit()}
disabled={this.props.schemaRequest.put.loading}
loading={this.props.schemaRequest.put.loading}
>
<Icon
name={saveSVG}
className="circled"
size="30px"
title={this.props.intl.formatMessage(messages.save)}
/>
</Button>
<Button
className="cancel"
aria-label={this.props.intl.formatMessage(
messages.cancel,
)}
onClick={() => this.onCancel()}
>
<Icon
name={clearSVG}
className="circled"
size="30px"
title={this.props.intl.formatMessage(messages.cancel)}
/>
</Button>
</>
}
/>,
document.getElementById('toolbar'),
)}
</div>
);
}
return <div />;
}
}
export default compose(
injectIntl,
connect(
(state, props) => ({
schema: state.schema.schema,
schemaRequest: state.schema,
pathname: props.location.pathname,
id: nth(props.location.pathname.split('/'), -2),
}),
{
getSchema,
putSchema,
},
),
)(ContentTypeSchema);