@plone/volto
Version:
Volto
249 lines (230 loc) • 6.71 kB
JSX
import { useEffect } from 'react';
import PropTypes from 'prop-types';
import { compose } from 'redux';
import { useDispatch, useSelector, shallowEqual } from 'react-redux';
import uniqBy from 'lodash/uniqBy';
import { toast } from 'react-toastify';
import { defineMessages, useIntl } from 'react-intl';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import { FormFieldWrapper } from '@plone/volto/components/manage/Widgets';
import { flattenToAppURL } from '@plone/volto/helpers/Url/Url';
import {
getWorkflowOptions,
getCurrentStateMapping,
} from '@plone/volto/helpers/Workflows/Workflows';
import { injectLazyLibs } from '@plone/volto/helpers/Loadable/Loadable';
import { getContent } from '@plone/volto/actions/content/content';
import {
getWorkflow,
transitionWorkflow,
} from '@plone/volto/actions/workflow/workflow';
import downSVG from '@plone/volto/icons/down-key.svg';
import upSVG from '@plone/volto/icons/up-key.svg';
import checkSVG from '@plone/volto/icons/check.svg';
const messages = defineMessages({
messageUpdated: {
id: 'Workflow updated.',
defaultMessage: 'Workflow updated.',
},
messageNoWorkflow: {
id: 'No workflow',
defaultMessage: 'No workflow',
},
state: {
id: 'State',
defaultMessage: 'State',
},
});
const SingleValue = injectLazyLibs('reactSelect')(({ children, ...props }) => {
const stateDecorator = {
marginRight: '10px',
display: 'inline-block',
backgroundColor: props.selectProps.value.color || null,
content: ' ',
height: '10px',
width: '10px',
borderRadius: '50%',
};
const { SingleValue } = props.reactSelect.components;
return (
<SingleValue {...props}>
<span style={stateDecorator} />
{children}
</SingleValue>
);
});
const Option = injectLazyLibs('reactSelect')((props) => {
const stateDecorator = {
marginRight: '10px',
display: 'inline-block',
backgroundColor:
props.selectProps.value.value === props.data.value
? props.selectProps.value.color
: null,
content: ' ',
height: '10px',
width: '10px',
borderRadius: '50%',
border:
props.selectProps.value.value !== props.data.value
? `1px solid ${props.data.color}`
: null,
};
const { Option } = props['reactSelect'].components;
return (
<Option {...props}>
<span style={stateDecorator} />
<div style={{ marginRight: 'auto' }}>{props.label}</div>
{props.isFocused && !props.isSelected && (
<Icon name={checkSVG} size="18px" color="#b8c6c8" />
)}
{props.isSelected && <Icon name={checkSVG} size="18px" color="#007bc1" />}
</Option>
);
});
const DropdownIndicator = injectLazyLibs('reactSelect')((props) => {
const { DropdownIndicator } = props.reactSelect.components;
return (
<DropdownIndicator {...props} data-testid="workflow-select-dropdown">
{props.selectProps.menuIsOpen ? (
<Icon name={upSVG} size="24px" color="#007bc1" />
) : (
<Icon name={downSVG} size="24px" color="#007bc1" />
)}
</DropdownIndicator>
);
});
const selectTheme = (theme) => ({
...theme,
borderRadius: 0,
colors: {
...theme.colors,
primary25: 'hotpink',
primary: '#b8c6c8',
},
});
const customSelectStyles = {
control: (styles, state) => ({
...styles,
border: 'none',
borderBottom: '2px solid #b8c6c8',
boxShadow: 'none',
borderBottomStyle: state.menuIsOpen ? 'dotted' : 'solid',
}),
menu: (styles, state) => ({
...styles,
top: null,
marginTop: 0,
boxShadow: 'none',
borderBottom: '2px solid #b8c6c8',
}),
indicatorSeparator: (styles) => ({
...styles,
width: null,
}),
valueContainer: (styles) => ({
...styles,
padding: 0,
}),
option: (styles, state) => ({
...styles,
backgroundColor: null,
minHeight: '50px',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
padding: '12px 12px',
color: state.isSelected
? '#007bc1'
: state.isFocused
? '#4a4a4a'
: 'inherit',
':active': {
backgroundColor: null,
},
span: {
flex: '0 0 auto',
},
svg: {
flex: '0 0 auto',
},
}),
};
function useWorkflow() {
const history = useSelector((state) => state.workflow.history, shallowEqual);
const transitions = useSelector(
(state) => state.workflow.transitions,
shallowEqual,
);
const loaded = useSelector((state) => state.workflow.transition.loaded);
const currentStateValue = useSelector(
(state) => getCurrentStateMapping(state.workflow.currentState),
shallowEqual,
);
return { loaded, history, transitions, currentStateValue };
}
const Workflow = (props) => {
const intl = useIntl();
const dispatch = useDispatch();
const { loaded, transitions, currentStateValue } = useWorkflow();
const content = useSelector((state) => state.content?.data, shallowEqual);
const { pathname } = props;
useEffect(() => {
dispatch(getWorkflow(pathname));
dispatch(getContent(pathname));
}, [dispatch, pathname, loaded]);
const transition = (selectedOption) => {
dispatch(transitionWorkflow(flattenToAppURL(selectedOption.url)));
toast.success(
<Toast
success
title={intl.formatMessage(messages.messageUpdated)}
content=""
/>,
);
};
const { Placeholder } = props.reactSelect.components;
const Select = props.reactSelect.default;
return (
<FormFieldWrapper
id="state-select"
title={intl.formatMessage(messages.state)}
intl={intl}
{...props}
>
<Select
name="state-select"
className="react-select-container"
classNamePrefix="react-select"
isDisabled={!content.review_state || transitions.length === 0}
options={uniqBy(
transitions.map((transition) => getWorkflowOptions(transition)),
'label',
).concat(currentStateValue)}
styles={customSelectStyles}
theme={selectTheme}
components={{
DropdownIndicator,
Placeholder,
Option,
SingleValue,
}}
onChange={transition}
value={
content.review_state
? currentStateValue
: {
label: intl.formatMessage(messages.messageNoWorkflow),
value: 'noworkflow',
}
}
isSearchable={false}
/>
</FormFieldWrapper>
);
};
Workflow.propTypes = {
pathname: PropTypes.string.isRequired,
};
export default compose(injectLazyLibs(['reactSelect']))(Workflow);