@craftercms/studio-ui
Version:
Services, components, models & utils to build CrafterCMS authoring extensions.
381 lines (379 loc) • 11.6 kB
JavaScript
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright (C) 2007-2022 Crafter Software Corporation. All Rights Reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import FormGroup from '@mui/material/FormGroup';
import FormControlLabel from '@mui/material/FormControlLabel';
import Checkbox from '@mui/material/Checkbox';
import React, { useRef, useState } from 'react';
import { makeStyles } from 'tss-react/mui';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import SelectButton from '../ConfirmDropdown';
import Typography from '@mui/material/Typography';
import { cancelPackage, fetchPackage } from '../../services/publishing';
import List from '@mui/material/List';
import ListItem from '@mui/material/ListItem';
import CircularProgress from '@mui/material/CircularProgress';
import '../../styles/animations.scss';
import { READY_FOR_LIVE } from './constants';
import { alpha } from '@mui/material/styles';
import palette from '../../styles/palette';
import PrimaryButton from '../PrimaryButton';
const useStyles = makeStyles()((theme) => ({
package: {
padding: '20px 8px 20px 0',
'& .loading-header': {
display: 'flex',
alignItems: 'center',
height: '42px'
},
'& .name': {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '10px'
},
'& .status': {
display: 'flex',
justifyContent: 'space-between',
marginBottom: '10px'
},
'& .comment': {
display: 'flex',
'& p:first-child': {
marginRight: '20px',
marginBottom: '10px'
},
'& span': {
color: theme.palette.text.secondary
}
},
'& .files': {
marginTop: '10px'
}
},
checkbox: {
marginRight: 'auto'
},
thRow: {
background: theme.palette.background.default
},
th: {
fontWeight: 600
},
list: {
'& li': {
display: 'flex',
justifyContent: 'space-between'
}
},
spinner: {
marginRight: '10px',
color: theme.palette.text.secondary
},
packageLoading: {
WebkitAnimation: 'pulse 3s infinite ease-in-out',
animation: 'pulse 3s infinite ease-in-out',
pointerEvents: 'none'
},
cancelButton: {
paddingRight: '10px',
color: palette.orange.main,
border: `1px solid ${alpha(palette.orange.main, 0.5)}`,
'&:hover': {
backgroundColor: alpha(palette.orange.main, 0.08)
}
},
username: {
maxWidth: '390px',
overflow: 'hidden',
textOverflow: 'ellipsis',
display: 'inline-block',
marginBottom: '-5px'
}
}));
const translations = defineMessages({
cancelText: {
id: 'publishingDashboard.cancelItemButtonText',
defaultMessage: 'Cancel'
},
cancel: {
id: 'publishingDashboard.no',
defaultMessage: 'No'
},
confirm: {
id: 'publishingDashboard.yes',
defaultMessage: 'Yes'
},
confirmHelperText: {
id: 'publishingDashboard.confirmHelperText',
defaultMessage: 'Set item state to "Cancelled"?'
},
fetchPackagesFiles: {
id: 'publishingDashboard.fetchPackagesFiles',
defaultMessage: 'Fetch Packages Files'
},
status: {
id: 'publishingDashboard.status',
defaultMessage: 'Status is {state} for {environment} target'
},
comment: {
id: 'publishingDashboard.comment',
defaultMessage: 'Comment'
},
commentNotProvided: {
id: 'publishingDashboard.commentNotProvided',
defaultMessage: '(submission comment not provided)'
},
filesList: {
id: 'publishingDashboard.filesList',
defaultMessage: 'files list'
},
path: {
id: 'words.path',
defaultMessage: 'Path'
},
type: {
id: 'words.type',
defaultMessage: 'Type'
},
item: {
id: 'words.item',
defaultMessage: 'Item'
},
asset: {
id: 'words.asset',
defaultMessage: 'Asset'
},
script: {
id: 'words.script',
defaultMessage: 'Script'
},
page: {
id: 'words.page',
defaultMessage: 'Page'
},
renderingTemplate: {
id: 'words.template',
defaultMessage: 'Template'
},
component: {
id: 'words.component',
defaultMessage: 'Component'
},
unknown: {
id: 'words.unknown',
defaultMessage: 'Unknown'
}
});
export function PublishingPackage(props) {
const { classes, cx } = useStyles();
const { formatMessage } = useIntl();
const {
id,
approver,
schedule,
state,
comment,
environment,
siteId,
selected,
setSelected,
pending,
setPending,
getPackages,
setApiState,
filesPerPackage,
setFilesPerPackage,
readOnly
} = props;
const [loading, setLoading] = useState(null);
const { current: ref } = useRef({});
ref.cancelComplete = (packageId) => {
setPending({ ...pending, [packageId]: false });
getPackages(siteId);
};
function onSelect(event, id, checked) {
if (checked) {
setSelected({ ...selected, [id]: false });
} else {
setSelected({ ...selected, [id]: true });
}
}
function handleCancel(packageId) {
setPending({ ...pending, [packageId]: true });
cancelPackage(siteId, [packageId]).subscribe(
() => {
ref.cancelComplete(packageId);
},
({ response }) => {
setApiState({ error: true, errorResponse: response });
}
);
}
function onFetchPackages(packageId) {
setLoading(true);
fetchPackage(siteId, packageId).subscribe({
next: (pkg) => {
setLoading(false);
setFilesPerPackage({ ...filesPerPackage, [packageId]: pkg.items });
},
error: ({ response }) => {
setApiState({ error: true, errorResponse: response });
}
});
}
function renderFiles(files) {
return files.map((file, index) => {
return React.createElement(
ListItem,
{ key: index, divider: true },
React.createElement(Typography, { variant: 'body2' }, file.path),
React.createElement(
Typography,
{ variant: 'body2', color: 'textSecondary' },
file.contentTypeClass in translations
? formatMessage(translations[file.contentTypeClass])
: file.contentTypeClass
)
);
});
}
const checked = selected[id] ? selected[id] : false;
return React.createElement(
'div',
{ className: cx(classes.package, pending[id] && classes.packageLoading) },
React.createElement(
'section',
{ className: 'name' },
pending[id]
? React.createElement(
'header',
{ className: 'loading-header' },
React.createElement(CircularProgress, { size: 15, className: classes.spinner, color: 'inherit' }),
React.createElement(Typography, { variant: 'body1' }, React.createElement('strong', null, id))
)
: state === READY_FOR_LIVE
? React.createElement(
FormGroup,
{ className: classes.checkbox },
React.createElement(FormControlLabel, {
control: React.createElement(Checkbox, {
color: 'primary',
checked: checked,
onChange: (event) => onSelect(event, id, checked),
disabled: readOnly
}),
label: React.createElement('strong', null, id)
})
)
: React.createElement(Typography, { variant: 'body1' }, React.createElement('strong', null, id)),
state === READY_FOR_LIVE &&
React.createElement(SelectButton, {
classes: { button: classes.cancelButton },
text: formatMessage(translations.cancelText),
cancelText: formatMessage(translations.cancel),
confirmText: formatMessage(translations.confirm),
confirmHelperText: formatMessage(translations.confirmHelperText),
onConfirm: () => handleCancel(id),
disabled: readOnly
})
),
React.createElement(
'div',
{ className: 'status' },
React.createElement(
Typography,
{ variant: 'body2' },
React.createElement(FormattedMessage, {
id: 'publishingDashboard.scheduled',
defaultMessage: 'Scheduled for <b>{schedule, date, medium} {schedule, time, short}</b> by <b>{approver}</b>',
values: {
schedule: new Date(schedule),
approver: approver,
b: (content) => React.createElement('strong', { key: content[0], className: classes.username }, content[0])
}
})
),
React.createElement(
Typography,
{ variant: 'body2' },
formatMessage(translations.status, {
state: React.createElement('strong', { key: state }, state),
environment: React.createElement('strong', { key: environment }, environment)
})
)
),
React.createElement(
'div',
{ className: 'comment' },
React.createElement(Typography, { variant: 'body2' }, formatMessage(translations.comment)),
React.createElement(
Typography,
{ variant: 'body2' },
comment ? comment : React.createElement('span', null, formatMessage(translations.commentNotProvided))
)
),
React.createElement(
'div',
{ className: 'files' },
filesPerPackage &&
filesPerPackage[id] &&
React.createElement(
List,
{ 'aria-label': formatMessage(translations.filesList), className: classes.list },
React.createElement(
ListItem,
{ className: classes.thRow, divider: true },
React.createElement(
Typography,
{ variant: 'caption', className: classes.th },
formatMessage(translations.item),
' (',
formatMessage(translations.path).toLowerCase(),
')'
),
React.createElement(
Typography,
{ variant: 'caption', className: classes.th },
formatMessage(translations.type)
)
),
renderFiles(filesPerPackage[id])
),
(filesPerPackage === null || !filesPerPackage[id]) &&
React.createElement(
PrimaryButton,
{ variant: 'outlined', onClick: () => onFetchPackages(id), disabled: !!loading, loading: loading },
formatMessage(translations.fetchPackagesFiles)
)
)
);
}
export default PublishingPackage;