From 2674fe4eb264400ec51ab79cf86874ec34da5000 Mon Sep 17 00:00:00 2001 From: Sabrina Bongiovanni Date: Thu, 14 Mar 2024 16:58:01 +0100 Subject: [PATCH] feat: added download link to filename in file widget --- RELEASE.md | 1 + .../components/manage/Widgets/FileWidget.jsx | 232 ++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 src/customizations/volto/components/manage/Widgets/FileWidget.jsx diff --git a/RELEASE.md b/RELEASE.md index ba627ee57..b758d48a9 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -47,6 +47,7 @@ - Ora la fascia del footer contenente il logo e il nome del sito รจ configurabile da pannello di controllo - Nel pannello di controllo dei Feedback, ora gli utenti con permesso di eliminare elementi da questa sezione, possono eliminare i feedback. +- E' possibile scaricare il file o l'immagine caricata dal widget di upload file cliccando sul nome del file stesso. ### Migliorie diff --git a/src/customizations/volto/components/manage/Widgets/FileWidget.jsx b/src/customizations/volto/components/manage/Widgets/FileWidget.jsx new file mode 100644 index 000000000..0b879046a --- /dev/null +++ b/src/customizations/volto/components/manage/Widgets/FileWidget.jsx @@ -0,0 +1,232 @@ +// CUSTOMIZATION: +// - 177-179: Added link to download uploaded file + +/** + * FileWidget component. + * @module components/manage/Widgets/FileWidget + */ + +import React from 'react'; +import PropTypes from 'prop-types'; +import { Button, Image, Dimmer } from 'semantic-ui-react'; +import { readAsDataURL } from 'promise-file-reader'; +import { injectIntl } from 'react-intl'; +import deleteSVG from '@plone/volto/icons/delete.svg'; +import { Icon, FormFieldWrapper, UniversalLink } from '@plone/volto/components'; +import loadable from '@loadable/component'; +import { flattenToAppURL, validateFileUploadSize } from '@plone/volto/helpers'; +import { defineMessages, useIntl } from 'react-intl'; + +const imageMimetypes = [ + 'image/png', + 'image/jpeg', + 'image/webp', + 'image/jpg', + 'image/gif', + 'image/svg+xml', +]; +const Dropzone = loadable(() => import('react-dropzone')); + +const messages = defineMessages({ + releaseDrag: { + id: 'Drop files here ...', + defaultMessage: 'Drop files here ...', + }, + editFile: { + id: 'Drop file here to replace the existing file', + defaultMessage: 'Drop file here to replace the existing file', + }, + fileDrag: { + id: 'Drop file here to upload a new file', + defaultMessage: 'Drop file here to upload a new file', + }, + replaceFile: { + id: 'Replace existing file', + defaultMessage: 'Replace existing file', + }, + addNewFile: { + id: 'Choose a file', + defaultMessage: 'Choose a file', + }, +}); + +/** + * FileWidget component class. + * @function FileWidget + * @returns {string} Markup of the component. + * + * To use it, in schema properties, declare a field like: + * + * ```jsx + * { + * title: "File", + * widget: 'file', + * } + * ``` + * or: + * + * ```jsx + * { + * title: "File", + * type: 'object', + * } + * ``` + * + */ +const FileWidget = (props) => { + const { id, value, onChange, isDisabled } = props; + const [fileType, setFileType] = React.useState(false); + const intl = useIntl(); + + React.useEffect(() => { + if (value && imageMimetypes.includes(value['content-type'])) { + setFileType(true); + } + }, [value]); + + const imgsrc = value?.download + ? `${flattenToAppURL(value?.download)}?id=${Date.now()}` + : null || value?.data + ? `data:${value['content-type']};${value.encoding},${value.data}` + : null; + + /** + * Drop handler + * @method onDrop + * @param {array} files File objects + * @returns {undefined} + */ + const onDrop = (files) => { + const file = files[0]; + if (!validateFileUploadSize(file, intl.formatMessage)) return; + 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])) { + setFileType(true); + let imagePreview = document.getElementById(`field-${id}-image`); + imagePreview.src = reader.result; + } else { + setFileType(false); + } + }; + reader.readAsDataURL(files[0]); + }; + + return ( + + + {({ getRootProps, getInputProps, isDragActive }) => ( +
+ {isDragActive && } + {fileType ? ( + + ) : ( +
+ {isDragActive ? ( +

+ {intl.formatMessage(messages.releaseDrag)} +

+ ) : value ? ( +

+ {intl.formatMessage(messages.editFile)} +

+ ) : ( +

+ {intl.formatMessage(messages.fileDrag)} +

+ )} +
+ )} + + + +
+ )} +
+
+ {value && ( + + {value.filename} + + )} + {value && ( + + )} +
+
+ ); +}; + +/** + * Property types. + * @property {Object} propTypes Property types. + * @static + */ +FileWidget.propTypes = { + id: PropTypes.string.isRequired, + title: PropTypes.string.isRequired, + description: PropTypes.string, + required: PropTypes.bool, + error: PropTypes.arrayOf(PropTypes.string), + value: PropTypes.shape({ + '@type': PropTypes.string, + title: PropTypes.string, + }), + onChange: PropTypes.func.isRequired, + wrapped: PropTypes.bool, +}; + +/** + * Default properties. + * @property {Object} defaultProps Default properties. + * @static + */ +FileWidget.defaultProps = { + description: null, + required: false, + error: [], + value: null, +}; + +export default injectIntl(FileWidget);