design-comuni-plone-theme
Version:
Volto Theme for Italia design guidelines
254 lines (238 loc) • 6.92 kB
JSX
/**
* FileWidget component.
* @module components/manage/Widgets/FileWidget
*/
import React from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { Dimmer, Button } from 'design-react-kit';
import { readAsDataURL } from 'promise-file-reader';
import { injectIntl, defineMessages, useIntl } from 'react-intl';
import loadable from '@loadable/component';
import { flattenToAppURL } from '@plone/volto/helpers';
import { Icon } from 'design-comuni-plone-theme/components/ItaliaTheme';
const Dropzone = loadable(() => import('react-dropzone'));
const imageMimetypes = [
'image/png',
'image/jpeg',
'image/webp',
'image/jpg',
'image/gif',
'image/svg+xml',
];
const messages = defineMessages({
releaseDrag: {
id: 'Trascina qui il file ...',
defaultMessage: 'Trascina qui il file...',
},
editFile: {
id: 'Trascina qui un file per sostituire quello esistente',
defaultMessage: 'Trascina qui un file per sostituire quello esistente',
},
fileDrag: {
id: 'Trascina qui un file per caricare un nuovo file',
defaultMessage: 'Trascina qui un file per caricare un nuovo file',
},
replaceFile: {
id: 'Clicca qui per sostituire il file',
defaultMessage: 'Clicca qui per sostituire il file',
},
addNewFile: {
id: 'Seleziona un file',
defaultMessage: 'Seleziona un file',
},
deleteFile: {
id: 'Rimuovi il file',
defaultMessage: 'Rimuovi il file',
},
});
/**
* FileWidget component class.
* @function FileWidget
* @returns {string} Markup of the component.
*/
const FileWidget = (props) => {
const {
id,
value,
onChange,
label,
onEdit,
infoText,
required,
invalid,
validationText,
} = props;
const [isImage, setIsImage] = React.useState(false);
const intl = useIntl();
React.useEffect(() => {
if (value && imageMimetypes.includes(value['content-type'])) {
setIsImage(true);
} else {
setIsImage(false);
}
}, [value]);
/**
* Drop handler
* @method onDrop
* @param {array} files File objects
* @returns {undefined}
*/
const onDrop = (files) => {
const file = files[0];
readAsDataURL(file).then((data) => {
const fields = data.match(/^data:(.*);(.*),(.*)$/);
onChange(id, {
data: fields[3],
encoding: fields[2],
'content-type': fields[1],
filename: file.name,
});
});
let reader = new FileReader();
reader.onload = function () {
const fields = reader.result.match(/^data:(.*);(.*),(.*)$/);
if (imageMimetypes.includes(fields[1])) {
setIsImage(true);
let imagePreview = document.getElementById(`field-${id}-image`);
imagePreview.src = reader.result;
} else {
setIsImage(false);
}
};
reader.readAsDataURL(files[0]);
};
let attributes = {};
if (required) {
attributes.required = true;
attributes['aria-required'] = true;
}
const isInvalid = invalid === true || invalid === 'true';
if (isInvalid) {
attributes['aria-invalid'] = true;
}
return (
<div className="form-group">
<label htmlFor={`field-${id}`} className="active">
{label}
</label>
<div className="form-input-file">
<Dropzone onDrop={onDrop} multiple={false}>
{({ getRootProps, getInputProps, isDragActive }) => (
<div className="file-widget-dropzone" {...getRootProps()}>
{isDragActive && <Dimmer></Dimmer>}
{isImage ? (
<img
className="image-preview"
id={`field-${id}-image`}
src={
value?.download
? `${flattenToAppURL(value.download)}?id=${Date.now()}`
: null
}
alt=""
aria-hidden="true"
loading="lazy"
/>
) : (
<div className="dropzone-placeholder">
{isDragActive ? (
<p className="dropzone-text">
{intl.formatMessage(messages.releaseDrag)}
</p>
) : value ? (
<p className="dropzone-text">
{intl.formatMessage(messages.editFile)}
</p>
) : (
<p className="dropzone-text">
{intl.formatMessage(messages.fileDrag)}
</p>
)}
</div>
)}
<small className="form-text">
{value
? intl.formatMessage(messages.replaceFile)
: intl.formatMessage(messages.addNewFile)}
</small>
{!onEdit && (
<input
{...getInputProps({
type: 'file',
style: { display: 'none' },
})}
id={`field-${id}`}
name={id}
type="file"
disabled={props.disabled || null}
{...attributes}
/>
)}
</div>
)}
</Dropzone>
{infoText && (
<small
className={cx('form-text', {
'invalid-feedback form-feedback just-validate-error-label form-feedback just-validate-error-label':
invalid,
})}
>
{infoText}
</small>
)}
{value && (
<div className="field-file-name">
{value.filename}
<Button
className="delete-button"
aria-label={intl.formatMessage(messages.deleteFile)}
onClick={() => {
onChange(id, null);
setIsImage(false);
}}
>
<Icon color="primary" icon={'trash'} />
</Button>
</div>
)}
</div>
{validationText && (
<div className="invalid-feedback form-feedback just-validate-error-label form-text form-feedback just-validate-error-label">
{validationText}
</div>
)}
</div>
);
};
/**
* Property types.
* @property {Object} propTypes Property types.
* @static
*/
FileWidget.propTypes = {
id: PropTypes.string,
label: PropTypes.string,
description: PropTypes.string,
required: PropTypes.bool,
error: PropTypes.arrayOf(PropTypes.string),
value: PropTypes.shape({
'@type': PropTypes.string,
title: PropTypes.string,
}),
onChange: PropTypes.func.isRequired,
onEdit: PropTypes.bool,
};
/**
* Default properties.
* @property {Object} defaultProps Default properties.
* @static
*/
FileWidget.defaultProps = {
description: null,
required: false,
error: [],
value: null,
};
export default injectIntl(FileWidget);