decap-cms-core
Version:
Decap CMS core application, see decap-cms package for the main distribution.
292 lines (266 loc) • 8.66 kB
JavaScript
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { css } from '@emotion/react';
import styled from '@emotion/styled';
import dayjs from 'dayjs';
import { translate } from 'react-polyglot';
import { colors, lengths } from 'decap-cms-ui-default';
import { status } from '../../constants/publishModes';
import { DragSource, DropTarget, HTML5DragDrop } from '../UI';
import WorkflowCard from './WorkflowCard';
import { selectEntryCollectionTitle } from '../../reducers/collections';
const WorkflowListContainer = styled.div`
min-height: 60%;
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
@media (min-width: 500px) {
gap: 14px;
}
@media (min-width: 800px) {
gap: 40px;
}
`;
const WorkflowListContainerOpenAuthoring = styled.div`
min-height: 60%;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 8px;
@media (min-width: 500px) {
gap: 14px;
}
@media (min-width: 800px) {
gap: 40px;
}
`;
const styles = {
columnPosition: idx =>
idx > 0 &&
css`
&:before {
content: '';
display: block;
position: absolute;
width: 2px;
height: 80%;
top: 36px;
background-color: ${colors.textFieldBorder};
@media (min-width: 800px) {
top: 76px;
}
}
&:before {
left: -7px;
@media (min-width: 500px) {
left: -10px;
}
@media (min-width: 800px) {
left: -23px;
}
}
`,
column: css`
transition: background-color 0.5s ease;
border: 2px dashed transparent;
border-radius: 4px;
position: relative;
height: 100%;
`,
columnHovered: css`
border-color: ${colors.active};
`,
hiddenColumn: css`
display: none;
`,
};
const ColumnHeader = styled.h2`
font-size: 16px;
font-weight: normal;
padding: 2px 6px;
border-radius: ${lengths.borderRadius};
white-space: nowrap;
margin-bottom: 6px;
@media (min-width: 800px) {
font-size: 20px;
padding: 4px 12px;
margin-bottom: 28px;
}
${props =>
props.name === 'draft' &&
css`
background-color: ${colors.statusDraftBackground};
color: ${colors.statusDraftText};
`}
${props =>
props.name === 'pending_review' &&
css`
background-color: ${colors.statusReviewBackground};
color: ${colors.statusReviewText};
`}
${props =>
props.name === 'pending_publish' &&
css`
background-color: ${colors.statusReadyBackground};
color: ${colors.statusReadyText};
`}
`;
const ColumnCount = styled.p`
font-size: 11px;
font-weight: 500;
color: ${colors.text};
text-transform: uppercase;
margin-bottom: 6px;
padding-inline: 6px;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
min-width: 0;
@media (min-width: 800px) {
font-size: 13px;
padding-inline: 12px;
}
`;
// This is a namespace so that we can only drop these elements on a DropTarget with the same
const DNDNamespace = 'cms-workflow';
function getColumnHeaderText(columnName, t) {
switch (columnName) {
case 'draft':
return t('workflow.workflowList.draftHeader');
case 'pending_review':
return t('workflow.workflowList.inReviewHeader');
case 'pending_publish':
return t('workflow.workflowList.readyHeader');
}
}
class WorkflowList extends React.Component {
static propTypes = {
entries: ImmutablePropTypes.orderedMap,
handleChangeStatus: PropTypes.func.isRequired,
handlePublish: PropTypes.func.isRequired,
handleDelete: PropTypes.func.isRequired,
t: PropTypes.func.isRequired,
isOpenAuthoring: PropTypes.bool,
collections: ImmutablePropTypes.map.isRequired,
};
componentDidMount() {
// Manually validate PropTypes - React 19 breaking change
PropTypes.checkPropTypes(WorkflowList.propTypes, this.props, 'prop', 'WorkflowList');
}
handleChangeStatus = (newStatus, dragProps) => {
const slug = dragProps.slug;
const collection = dragProps.collection;
const oldStatus = dragProps.ownStatus;
this.props.handleChangeStatus(collection, slug, oldStatus, newStatus);
};
requestDelete = (collection, slug, ownStatus) => {
if (window.confirm(this.props.t('workflow.workflowList.onDeleteEntry'))) {
this.props.handleDelete(collection, slug, ownStatus);
}
};
requestPublish = (collection, slug, ownStatus) => {
if (ownStatus !== status.last()) {
window.alert(this.props.t('workflow.workflowList.onPublishingNotReadyEntry'));
return;
} else if (!window.confirm(this.props.t('workflow.workflowList.onPublishEntry'))) {
return;
}
this.props.handlePublish(collection, slug);
};
// eslint-disable-next-line react/display-name
renderColumns = (entries, column) => {
const { isOpenAuthoring, collections, t } = this.props;
if (!entries) return null;
if (!column) {
return entries.entrySeq().map(([currColumn, currEntries], idx) => (
<DropTarget
namespace={DNDNamespace}
key={currColumn}
onDrop={this.handleChangeStatus.bind(this, currColumn)}
>
{(connect, { isHovered }) =>
connect(
<div style={{ height: '100%' }}>
<div
css={[
styles.column,
styles.columnPosition(idx),
isHovered && styles.columnHovered,
isOpenAuthoring && currColumn === 'pending_publish' && styles.hiddenColumn,
]}
>
<ColumnHeader name={currColumn}>
{getColumnHeaderText(currColumn, this.props.t)}
</ColumnHeader>
<ColumnCount>
{this.props.t('workflow.workflowList.currentEntries', {
smart_count: currEntries.size,
})}
</ColumnCount>
{this.renderColumns(currEntries, currColumn)}
</div>
</div>,
)
}
</DropTarget>
));
}
return (
<div>
{entries.map(entry => {
const timestamp = dayjs(entry.get('updatedOn')).format(t('workflow.workflow.dateFormat'));
const slug = entry.get('slug');
const collectionName = entry.get('collection');
const editLink = `collections/${collectionName}/entries/${slug}?ref=workflow`;
const ownStatus = entry.get('status');
const collection = collections.find(
collection => collection.get('name') === collectionName,
);
const collectionLabel = collection?.get('label');
const isModification = entry.get('isModification');
const allowPublish = collection?.get('publish');
const canPublish = ownStatus === status.last() && !entry.get('isPersisting', false);
const postAuthor = entry.get('author');
return (
<DragSource
namespace={DNDNamespace}
key={`${collectionName}-${slug}`}
slug={slug}
collection={collectionName}
ownStatus={ownStatus}
>
{connect =>
connect(
<div>
<WorkflowCard
collectionLabel={collectionLabel || collectionName}
title={selectEntryCollectionTitle(collection, entry)}
authorLastChange={entry.getIn(['metaData', 'user'])}
body={entry.getIn(['data', 'body'])}
isModification={isModification}
editLink={editLink}
timestamp={timestamp}
onDelete={this.requestDelete.bind(this, collectionName, slug, ownStatus)}
allowPublish={allowPublish}
canPublish={canPublish}
onPublish={this.requestPublish.bind(this, collectionName, slug, ownStatus)}
postAuthor={postAuthor}
/>
</div>,
)
}
</DragSource>
);
})}
</div>
);
};
render() {
const columns = this.renderColumns(this.props.entries);
const ListContainer = this.props.isOpenAuthoring
? WorkflowListContainerOpenAuthoring
: WorkflowListContainer;
return <ListContainer>{columns}</ListContainer>;
}
}
export default HTML5DragDrop(translate()(WorkflowList));