design-comuni-plone-theme
Version:
Volto Theme for Italia design guidelines
376 lines (359 loc) • 12.1 kB
JSX
// CUSTOMIZATION:
// - 196-202 - 230-262: added file upload restraint message as per agid regulations
/**
* Contents upload modal.
* @module components/manage/Contents/ContentsUploadModal
*/
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import {
Button,
Dimmer,
Header,
Icon,
Image,
Loader,
Modal,
Table,
Segment,
TableCell,
} from 'semantic-ui-react';
import loadable from '@loadable/component';
import { concat, filter, map } from 'lodash';
import filesize from 'filesize';
import { readAsDataURL } from 'promise-file-reader';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
import { FormattedRelativeDate } from '@plone/volto/components';
import { createContent, getTypes } from '@plone/volto/actions';
import { validateFileUploadSize, getBaseUrl } from '@plone/volto/helpers';
const Dropzone = loadable(() => import('react-dropzone'));
const messages = defineMessages({
cancel: {
id: 'Cancel',
defaultMessage: 'Cancel',
},
upload: {
id: '{count, plural, one {Upload {count} file} other {Upload {count} files}}',
defaultMessage:
'{count, plural, one {Upload {count} file} other {Upload {count} files}}',
},
});
const SUBREQUEST = 'batch-upload';
/**
* ContentsUploadModal class.
* @class ContentsUploadModal
* @extends Component
*/
class ContentsUploadModal extends Component {
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
static propTypes = {
createContent: PropTypes.func.isRequired,
pathname: PropTypes.string.isRequired,
request: PropTypes.shape({
loading: PropTypes.bool,
loaded: PropTypes.bool,
}).isRequired,
open: PropTypes.bool.isRequired,
onOk: PropTypes.func.isRequired,
onCancel: PropTypes.func.isRequired,
getTypes: PropTypes.func,
};
/**
* Constructor
* @method constructor
* @param {Object} props Component properties
* @constructs ContentsUploadModal
*/
constructor(props) {
super(props);
this.onRemoveFile = this.onRemoveFile.bind(this);
this.onDrop = this.onDrop.bind(this);
this.onCancel = this.onCancel.bind(this);
this.onSubmit = this.onSubmit.bind(this);
this.state = {
files: [],
};
}
/**
* Component will receive props
* @method componentWillReceiveProps
* @param {Object} nextProps Next properties
* @returns {undefined}
*/
UNSAFE_componentWillReceiveProps(nextProps) {
if (this.props.request.loading && nextProps.request.loaded) {
this.props.onOk();
this.setState({
files: [],
});
}
}
/**
* Remove file handler
* @method onRemoveFile
* @param {Object} event Event object
* @returns {undefined}
*/
onRemoveFile(event) {
this.setState({
files: filter(
this.state.files,
(file, index) =>
index !== parseInt(event.target.getAttribute('value'), 10),
),
});
}
/**
* Drop handler
* @method onDrop
* @param {array} files File objects
* @returns {undefined}
*/
onDrop = async (files) => {
const validFiles = [];
for (let i = 0; i < files.length; i++) {
if (validateFileUploadSize(files[i], this.props.intl.formatMessage)) {
await readAsDataURL(files[i]).then((data) => {
const fields = data.match(/^data:(.*);(.*),(.*)$/);
files[i].preview = fields[0];
});
validFiles.push(files[i]);
}
}
this.setState({
files: concat(this.state.files, validFiles),
});
};
/**
* Cancel handler
* @method onCancel
* @returns {undefined}
*/
onCancel() {
this.props.onCancel();
this.setState({
files: [],
});
}
/**
* Submit handler
* @method onSubmit
* @returns {undefined}
*/
onSubmit() {
Promise.all(map(this.state.files, (file) => readAsDataURL(file))).then(
(files) => {
this.props.createContent(
this.props.pathname,
map(this.state.files, (file, index) => {
const fields = files[index].match(/^data:(.*);(.*),(.*)$/);
const image = fields[1].split('/')[0] === 'image';
return {
'@type': image ? 'Image' : 'File',
title: file.name,
[image ? 'image' : 'file']: {
data: fields[3],
encoding: fields[2],
'content-type': fields[1],
filename: file.name,
},
};
}),
SUBREQUEST,
);
},
);
}
componentDidMount() {
this.props.getTypes(getBaseUrl(this.props.pathname));
}
/**
* Render method.
* @method render
* @returns {string} Markup for the component.
*/
render() {
// as per agid guidelines, files cannot be uploaded in modulistica folder inside ct servizio
// show restraint and hide upload button when page is called modulistica and when there's a restraint
// this is enough to identify this only case bc even if another page is called 'modulistica', it will have 'File'
// and "Modulo" as addable type
const showFileRestraint =
getBaseUrl(this.props.pathname).match(/\/servizi\/.*\/modulistica\/?$/) &&
!this.props.types.some(
(type) => type.id === 'File' || type.id === 'Modulo',
);
return (
this.props.open && (
<Modal open={this.props.open}>
<Header>
<FormattedMessage id="Upload files" defaultMessage="Upload files" />
</Header>
<Dimmer active={this.props.request.loading}>
<Loader>
<FormattedMessage
id="Uploading files"
defaultMessage="Uploading files"
/>
</Loader>
</Dimmer>
<Modal.Content>
<Dropzone
onDrop={this.onDrop}
className="dropzone"
noDragEventsBubbling={true}
multiple={true}
>
{({ getRootProps, getInputProps }) => (
<div {...getRootProps({ className: 'dashed' })}>
<Segment>
<Table basic="very">
<Table.Body>
{showFileRestraint ? (
<Table.Row>
<TableCell>
<FormattedMessage
id="modulistica_restraint"
defaultMessage="As per the official information architecture outlined in measure 1.4.1, all forms must be properly uploaded in the designated section within Amministrazione > Documenti e Dati > Modulistica, and linked to the relevant service sheet. It is no longer possible to upload files and attachments directly into this container."
/>
</TableCell>
</Table.Row>
) : (
<Table.Row>
<Table.Cell>
<FormattedMessage
id="Drag and drop files from your computer onto this area or click the “Browse” button."
defaultMessage="Drag and drop files from your computer onto this area or click the “Browse” button."
/>
</Table.Cell>
<Table.Cell>
<Button className="ui button primary">
<FormattedMessage
id="Browse"
defaultMessage="Browse"
/>
</Button>
<input
{...getInputProps({
type: 'file',
style: { display: 'none' },
})}
/>
</Table.Cell>
</Table.Row>
)}
</Table.Body>
</Table>
</Segment>
</div>
)}
</Dropzone>
{this.state.files.length > 0 && (
<Table compact singleLine>
<Table.Header>
<Table.Row>
<Table.HeaderCell width={8}>
<FormattedMessage
id="Filename"
defaultMessage="Filename"
/>
</Table.HeaderCell>
<Table.HeaderCell width={4}>
<FormattedMessage
id="Last modified"
defaultMessage="Last modified"
/>
</Table.HeaderCell>
<Table.HeaderCell width={4}>
<FormattedMessage
id="File size"
defaultMessage="File size"
/>
</Table.HeaderCell>
<Table.HeaderCell width={4}>
<FormattedMessage id="Preview" defaultMessage="Preview" />
</Table.HeaderCell>
<Table.HeaderCell />
</Table.Row>
</Table.Header>
<Table.Body>
{map(this.state.files, (file, index) => (
<Table.Row className="upload-row" key={file.name}>
<Table.Cell>{file.name}</Table.Cell>
<Table.Cell>
{file.lastModifiedDate && (
<FormattedRelativeDate date={file.lastModifiedDate} />
)}
</Table.Cell>
<Table.Cell>
{filesize(file.size, { round: 0 })}
</Table.Cell>
<Table.Cell>
{file.type.split('/')[0] === 'image' && (
<Image src={file.preview} height={60} />
)}
</Table.Cell>
<Table.Cell>
<Icon
name="close"
value={index}
link
onClick={this.onRemoveFile}
/>
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table>
)}
</Modal.Content>
<Modal.Actions>
{this.state.files.length > 0 && (
<Button
basic
circular
primary
floated="right"
icon="arrow right"
aria-label={this.props.intl.formatMessage(messages.upload, {
count: this.state.files.length,
})}
onClick={this.onSubmit}
title={this.props.intl.formatMessage(messages.upload, {
count: this.state.files.length,
})}
size="big"
/>
)}
<Button
basic
circular
secondary
icon="remove"
aria-label={this.props.intl.formatMessage(messages.cancel)}
title={this.props.intl.formatMessage(messages.cancel)}
floated="right"
size="big"
onClick={this.onCancel}
/>
</Modal.Actions>
</Modal>
)
);
}
}
export default compose(
injectIntl,
connect(
(state) => ({
request: state.content.subrequests?.[SUBREQUEST] || {},
types: filter(state.types.types, 'addable'),
}),
{ createContent, getTypes },
),
)(ContentsUploadModal);