@plone/volto
Version:
Volto
898 lines (870 loc) • 29.1 kB
JSX
/**
* Configure content rule component.
* @module components/manage/Controlpanels/Rules/ConfigureRule
*/
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 { getBaseUrl, getParentUrl } from '@plone/volto/helpers/Url/Url';
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
import { createPortal } from 'react-dom';
import {
Button,
Card,
Container,
Dropdown,
Grid,
Segment,
} from 'semantic-ui-react';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import UniversalLink from '@plone/volto/components/manage/UniversalLink/UniversalLink';
import {
getControlPanelRule,
removeCondition,
addCondition,
editCondition,
removeAction,
addAction,
editAction,
getCondition,
getAction,
moveRuleCondition,
moveRuleAction,
} from '@plone/volto/actions/rules/rules';
import { toast } from 'react-toastify';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import backSVG from '@plone/volto/icons/back.svg';
import upSVG from '@plone/volto/icons/up.svg';
import downSVG from '@plone/volto/icons/down.svg';
import VariableModal from './components/VariableModal';
const messages = defineMessages({
back: {
id: 'Back',
defaultMessage: 'Back',
},
configRule: {
id: 'Configure content rule',
defaultMessage: 'Configure content rule',
},
success: {
id: 'Success',
defaultMessage: 'Success',
},
deleteCondition: {
id: 'Delete condition',
defaultMessage: 'Condition deleted',
},
addCondition: {
id: 'Add condition',
defaultMessage: 'Condition added',
},
deleteAction: {
id: 'Delete action',
defaultMessage: 'Action deleted',
},
addAction: {
id: 'Add action',
defaultMessage: 'Action added',
},
editAction: {
id: 'Action changed',
defaultMessage: 'Action changed',
},
editCondition: {
id: 'Condition changed',
defaultMessage: 'Condition changed',
},
move: {
id: 'Position changed',
defaultMessage: 'Position changed',
},
moveUp: {
id: 'Move up',
defaultMessage: 'Move up',
},
moveDown: {
id: 'Move down',
defaultMessage: 'Move down',
},
});
/**
* ConfigureRule class.
* @class ConfigureRule
* @extends Component
*/
class ConfigureRule extends Component {
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
static propTypes = {
getControlPanelRule: PropTypes.func.isRequired,
removeCondition: PropTypes.func.isRequired,
addCondition: PropTypes.func.isRequired,
removeAction: PropTypes.func.isRequired,
addAction: PropTypes.func.isRequired,
moveRuleCondition: PropTypes.func.isRequired,
moveRuleAction: PropTypes.func.isRequired,
};
/**
* Constructor
* @method constructor
* @param {Object} props Component properties
* @constructs Rules
*/
constructor(props) {
super(props);
this.openConditionModal = this.openConditionModal.bind(this);
this.openActionModal = this.openActionModal.bind(this);
this.handleConditionAdd = this.handleConditionAdd.bind(this);
this.handleActionAdd = this.handleActionAdd.bind(this);
this.openEditCondition = this.openEditCondition.bind(this);
this.openEditAction = this.openEditAction.bind(this);
this.handleEditCondition = this.handleEditCondition.bind(this);
this.handleEditAction = this.handleEditAction.bind(this);
this.state = {
isClient: false,
openModal: false,
selConditionToAdd: '',
selActionToAdd: '',
selConditionToEdit: '',
selActionToEdit: '',
conditionFormData: '',
actionFormData: '',
};
}
/**
* Component did mount
* @method componentDidMount
* @returns {undefined}
*/
componentDidMount() {
this.setState({ isClient: true });
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
/**
* Component did mount
* @method componentDidUpdate
* @returns {undefined}
*/
componentDidUpdate(prevProps, prevState) {
if (
this.state.selConditionToEdit &&
prevState.selConditionToEdit !== this.state.selConditionToEdit
) {
this.props.getCondition(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
this.state.selConditionToEdit.idx,
);
}
if (
this.props?.rule?.condition &&
this.props?.rule?.condition !== prevProps?.rule.condition
) {
const conditionFormData = this.props?.rule?.condition;
this.setState({ conditionFormData });
}
if (
this.state.selActionToEdit &&
prevState.selActionToEdit !== this.state.selActionToEdit
) {
this.props.getAction(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
this.state.selActionToEdit.idx,
);
}
if (
this.props?.rule?.action &&
this.props?.rule?.action !== prevProps?.rule.action
) {
const actionFormData = this.props?.rule?.action;
this.setState({ actionFormData });
}
}
/**
* Component will receive props
* @method componentWillReceiveProps
* @param {Object} nextProps Next properties
* @returns {undefined}
*/
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.rule.move.loading && nextProps.rule.move.loaded) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.move)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
if (
this.props.rule.deletecondition.loading &&
nextProps.rule.deletecondition.loaded
) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.deleteCondition)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
if (
this.props.rule.editcondition.loading &&
nextProps.rule.editcondition.loaded
) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.editCondition)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
if (
this.props.rule.editaction.loading &&
nextProps.rule.editaction.loaded
) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.editAction)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
if (
this.props.rule.addcondition.loading &&
nextProps.rule.addcondition.loaded
) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.addCondition)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
if (
this.props.rule.deleteaction.loading &&
nextProps.rule.deleteaction.loaded
) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.deleteAction)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
if (this.props.rule.addaction.loading && nextProps.rule.addaction.loaded) {
toast.success(
<Toast
success
title={this.props.intl.formatMessage(messages.success)}
content={this.props.intl.formatMessage(messages.addAction)}
/>,
);
this.props.getControlPanelRule(
getBaseUrl(this.props.pathname),
this.props?.match?.params?.id,
);
}
}
/**
* Back/Cancel handler
* @method onCancel
* @returns {undefined}
*/
onCancel() {
this.props.history.push(getParentUrl(getParentUrl(this.props.pathname)));
}
/**
* Remove condition handler
* @method handleRemoveCondition
* @returns {undefined}
*/
handleRemoveCondition(conditionId) {
const ruleId = this.props?.match?.params?.id;
this.props.removeCondition(
getBaseUrl(this.props.pathname),
ruleId,
conditionId,
);
}
/**
* Edit condition handler
* @method handleEditCondition
* @returns {undefined}
*/
handleEditCondition(val) {
const ruleId = this.props?.match?.params?.id;
if (this.state.selConditionToEdit) {
this.props.editCondition(
getBaseUrl(this.props.pathname),
ruleId,
val,
this.state.selConditionToEdit.idx,
);
}
}
/**
* Edit action handler
* @method handleEditAction
* @returns {undefined}
*/
handleEditAction(val) {
const ruleId = this.props?.match?.params?.id;
if (this.state.selActionToEdit) {
this.props.editAction(
getBaseUrl(this.props.pathname),
ruleId,
val,
this.state.selActionToEdit.idx,
);
}
}
/**
* open modal edit condition handler
* @method openEditCondition
* @returns {undefined}
*/
openEditCondition(cond_id) {
this.setState({
selConditionToEdit: cond_id,
openModal: true,
selActionToEdit: '',
});
}
/**
* open modal edit action handler
* @method openEditAction
* @returns {undefined}
*/
openEditAction(action_id) {
this.setState({
selActionToEdit: action_id,
openModal: true,
selConditionToEdit: '',
});
}
/**
* Add condition handler
* @method openConditionModal
* @returns {undefined}
*/
openConditionModal() {
if (this.state.selConditionToAdd) {
this.setState({ openModal: true });
}
}
/**
* Condition save handler
* @method handleConditionAdd
* @returns {undefined}
*/
handleConditionAdd(val) {
const ruleId = this.props?.match?.params?.id;
this.props.addCondition(getBaseUrl(this.props.pathname), ruleId, val);
this.setState({ selConditionToAdd: '' });
}
/**
* Add action handler
* @method openActionModal
* @returns {undefined}
*/
openActionModal() {
this.setState({ openModal: true });
}
/**
* Action save handler
* @method handleActionAdd
* @returns {undefined}
*/
handleActionAdd(val) {
const ruleId = this.props?.match?.params?.id;
this.props.addAction(getBaseUrl(this.props.pathname), ruleId, val);
this.setState({ selConditionToAdd: '' });
}
/**
* Remove action handler
* @method handleRemoveAction
* @returns {undefined}
*/
handleRemoveAction(actionId) {
const ruleId = this.props?.match?.params?.id;
this.props.removeAction(getBaseUrl(this.props.pathname), ruleId, actionId);
}
/**
* Move action handler
* @method handleMoveAction
* @returns {undefined}
*/
handleMoveAction(action, direction) {
const ruleId = this.props?.match?.params?.id;
this.props.moveRuleAction(
getBaseUrl(this.props.pathname),
{
'form.button.Move': direction === 'up' ? '_move_up' : '_move_down',
},
ruleId,
action,
);
}
/**
* Move action handler
* @method handleMoveCondition
* @returns {undefined}
*/
handleMoveCondition(condition, direction) {
const ruleId = this.props?.match?.params?.id;
this.props.moveRuleCondition(
getBaseUrl(this.props.pathname),
{
'form.button.Move': direction === 'up' ? '_move_up' : '_move_down',
},
ruleId,
condition,
);
}
/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
const { item = {} } = this.props.rule || {};
const {
actions = [],
addable_actions = [],
addable_conditions = [],
assignments = [],
conditions = [],
title = '',
} = item;
const conditions_options = addable_conditions.map((cond) => {
return { key: cond.title, text: cond.title, value: cond };
});
const actions_options = addable_actions.map((act) => {
return { key: act.title, text: act.title, value: act };
});
return (
<div id="page-rule-configure">
<Helmet title={this.props.intl.formatMessage(messages.configRule)} />
<Container>
<article id="content">
<Segment.Group raised>
<Segment className="primary">
<FormattedMessage
id="Configure Content Rule: {title}"
defaultMessage="Configure Content Rule: {title}"
values={{ title: <q>{title}</q> }}
/>
</Segment>
<Segment className="secondary">
<FormattedMessage
id="Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below."
defaultMessage="Rules execute when a triggering event occurs. Rule actions will only be invoked if all the rule's conditions are met. You can add new actions and conditions using the buttons below."
/>
</Segment>
<Segment>
<Grid>
<Grid.Row>
<Grid.Column
mobile={16}
tablet={16}
computer={6}
largeScreen={6}
>
<h4>
<FormattedMessage
id="If all of the following conditions are met:"
defaultMessage="If all of the following conditions are met:"
/>
</h4>
{conditions && conditions.length > 0 && (
<Card.Group>
{conditions.map((cond, i) => {
return (
<Card fluid key={i}>
<Card.Content>
<Card.Header>
<h4>{cond.title}</h4>
</Card.Header>
<Card.Description>
{cond.summary}
</Card.Description>
</Card.Content>
<Card.Content extra>
<Button
onClick={() => this.openEditCondition(cond)}
compact
size="tiny"
primary
>
Edit
</Button>
<Button
onClick={() =>
this.handleRemoveCondition(cond.idx)
}
compact
size="tiny"
color="youtube"
>
Remove
</Button>
<Button
compact
size="tiny"
primary
disabled={cond?.first}
onClick={() =>
this.handleMoveCondition(cond.idx, 'up')
}
>
<Button.Content>
<Icon
name={upSVG}
size="10px"
title={this.props.intl.formatMessage(
messages.moveUp,
)}
/>
</Button.Content>
</Button>
<Button
compact
size="tiny"
primary
disabled={cond?.last}
onClick={() =>
this.handleMoveCondition(cond.idx, 'down')
}
>
<Button.Content>
<Icon
name={downSVG}
size="10px"
title={this.props.intl.formatMessage(
messages.moveDown,
)}
/>
</Button.Content>
</Button>
</Card.Content>
</Card>
);
})}
</Card.Group>
)}
<Grid.Row>
<h4 style={{ marginTop: '15px' }}>
<FormattedMessage
id="Condition: "
defaultMessage="Condition: "
/>
</h4>
<Dropdown
style={{ margin: '5px 0' }}
placeholder="Select condition"
fluid
selection
options={conditions_options}
value={this.state.selConditionToAdd}
onChange={(e, { value }) =>
this.setState({ selConditionToAdd: value })
}
/>
<Button
compact
onClick={this.openConditionModal}
primary
>
<FormattedMessage id="Add" defaultMessage="Add" />
</Button>
</Grid.Row>
</Grid.Column>
<Grid.Column
mobile={16}
tablet={16}
computer={6}
largeScreen={6}
>
<h4>
<FormattedMessage
id="Perform the following actions:"
defaultMessage="Perform the following actions:"
/>
</h4>
{actions && actions.length > 0 && (
<Card.Group>
{actions.map((action, i) => {
return (
<Card fluid key={i}>
<Card.Content>
<Card.Header>
<h4>{action.title}</h4>
</Card.Header>
<Card.Description>
{action.summary}
</Card.Description>
</Card.Content>
<Card.Content extra>
<Button
onClick={() => this.openEditAction(action)}
compact
size="tiny"
primary
>
Edit
</Button>
<Button
onClick={() =>
this.handleRemoveAction(action.idx)
}
compact
size="tiny"
color="youtube"
>
Remove
</Button>
<Button
compact
size="tiny"
primary
disabled={action?.first}
onClick={() =>
this.handleMoveAction(action.idx, 'up')
}
>
<Button.Content>
<Icon
name={upSVG}
size="10px"
title={this.props.intl.formatMessage(
messages.moveUp,
)}
/>
</Button.Content>
</Button>
<Button
compact
size="tiny"
primary
disabled={action?.last}
onClick={() =>
this.handleMoveAction(action.idx, 'down')
}
>
<Button.Content>
<Icon
name={downSVG}
size="10px"
title={this.props.intl.formatMessage(
messages.moveDown,
)}
/>
</Button.Content>
</Button>
</Card.Content>
</Card>
);
})}
</Card.Group>
)}
<Grid.Row>
<h4 style={{ marginTop: '15px' }}>
<FormattedMessage
id="Action: "
defaultMessage="Action: "
/>
</h4>
<Dropdown
style={{ margin: '5px 0' }}
placeholder="Select action"
fluid
selection
options={actions_options}
onChange={(e, { value }) =>
this.setState({ selActionToAdd: value })
}
/>
<Button
compact
onClick={() => this.openActionModal()}
primary
>
<FormattedMessage id="Add" defaultMessage="Add" />
</Button>
</Grid.Row>
</Grid.Column>
</Grid.Row>
<Grid.Row stretched>
<Grid.Column>
<h4>
<FormattedMessage
id="Assignments"
defaultMessage="Assignments"
/>
</h4>
<FormattedMessage
id="This rule is assigned to the following locations:"
defaultMessage="This rule is assigned to the following locations:"
/>
{assignments.map((assignment, i) => (
<UniversalLink
key={i}
title={assignment.title}
href={getBaseUrl(assignment.url)}
>
{assignment.title}
</UniversalLink>
))}
</Grid.Column>
</Grid.Row>
</Grid>
</Segment>
</Segment.Group>
</article>
</Container>
{this.state.selConditionToAdd && (
<VariableModal
open={this.state.openModal}
onClose={() =>
this.setState({ openModal: false, selConditionToAdd: '' })
}
onOpen={() => this.setState({ openModal: true })}
onSave={(v) => this.handleConditionAdd(v)}
value={this.state.selConditionToAdd}
type="Condition"
action="add"
/>
)}
{this.state.selActionToAdd && (
<VariableModal
open={this.state.openModal}
onClose={() =>
this.setState({ openModal: false, selActionToAdd: '' })
}
onOpen={() => this.setState({ openModal: true })}
onSave={(v) => this.handleActionAdd(v)}
value={this.state.selActionToAdd}
type="Action"
action="add"
/>
)}
{this.state.selConditionToEdit && this.state.conditionFormData && (
<VariableModal
open={this.state.openModal}
onClose={() =>
this.setState({
openModal: false,
selConditionToEdit: '',
conditionFormData: '',
})
}
onOpen={() => this.setState({ openModal: true })}
onSave={(v) => this.handleEditCondition(v)}
value={this.state.selConditionToEdit}
formData={this.state.conditionFormData}
type="Condition"
action="edit"
/>
)}
{this.state.selActionToEdit && this.state.actionFormData && (
<VariableModal
open={this.state.openModal}
onClose={() =>
this.setState({
openModal: false,
selActionToEdit: '',
actionFormData: '',
})
}
onOpen={() => this.setState({ openModal: true })}
onSave={this.handleEditAction}
value={this.state.selActionToEdit}
formData={this.state.actionFormData}
type="Action"
action="edit"
/>
)}
{this.state.isClient &&
createPortal(
<Toolbar
pathname={this.props.pathname}
hideDefaultViewButtons
inner={
<Link className="item" to="#" onClick={() => this.onCancel()}>
<Icon
name={backSVG}
className="contents circled"
size="30px"
title={this.props.intl.formatMessage(messages.back)}
/>
</Link>
}
/>,
document.getElementById('toolbar'),
)}
</div>
);
}
}
export default compose(
injectIntl,
connect(
(state, props) => ({
pathname: props.location.pathname,
rule: state.controlpanelrule,
}),
{
getControlPanelRule,
removeCondition,
addCondition,
editCondition,
getCondition,
getAction,
removeAction,
addAction,
editAction,
moveRuleCondition,
moveRuleAction,
},
),
)(ConfigureRule);