From 1948bd4fbeb95acefca0dfa8bf7e289e354325ba Mon Sep 17 00:00:00 2001 From: Maksim Eltyshev Date: Wed, 9 Oct 2024 12:38:18 +0200 Subject: [PATCH] feat: Ability to upload multiple attachments at once Closes #908 --- .../CardModal/AttachmentAddStep.jsx | 2 +- .../AttachmentAddZone/AttachmentAddZone.jsx | 51 +++++++++++-------- .../components/FilePicker/FilePicker.jsx | 13 +++-- client/src/utils/element-helpers.js | 5 +- 4 files changed, 42 insertions(+), 29 deletions(-) diff --git a/client/src/components/CardModal/AttachmentAddStep.jsx b/client/src/components/CardModal/AttachmentAddStep.jsx index febec750e..b36ff95f7 100644 --- a/client/src/components/CardModal/AttachmentAddStep.jsx +++ b/client/src/components/CardModal/AttachmentAddStep.jsx @@ -28,7 +28,7 @@ const AttachmentAddStep = React.memo(({ onCreate, onClose }) => { - + {t('common.fromComputer', { context: 'title', diff --git a/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx b/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx index 66a0e8938..f5389c31e 100644 --- a/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx +++ b/client/src/components/CardModal/AttachmentAddZone/AttachmentAddZone.jsx @@ -5,6 +5,7 @@ import { useTranslation } from 'react-i18next'; import { closePopup } from '../../../lib/popup'; import { useModal } from '../../../hooks'; +import { isActiveTextElement } from '../../../utils/element-helpers'; import TextFileAddModal from './TextFileAddModal'; import styles from './AttachmentAddZone.module.scss'; @@ -24,13 +25,14 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => { const handleDropAccepted = useCallback( (files) => { - submit(files[0]); + files.forEach((file) => { + submit(file); + }); }, [submit], ); const { getRootProps, getInputProps, isDragActive } = useDropzone({ - multiple: false, noClick: true, noKeyboard: true, onDropAccepted: handleDropAccepted, @@ -49,38 +51,43 @@ const AttachmentAddZone = React.memo(({ children, onCreate }) => { return; } - const file = event.clipboardData.files[0]; + const { files, items } = event.clipboardData; - if (file) { - submit(file); - return; - } - - const item = event.clipboardData.items[0]; + if (files.length > 0) { + [...files].forEach((file) => { + submit(file); + }); - if (!item) { return; } - if (item.kind === 'file') { - submit(item.getAsFile()); + if (items.length === 0) { return; } - if ( - ['input', 'textarea'].includes(event.target.tagName.toLowerCase()) && - event.target === document.activeElement - ) { + if (items[0].kind === 'string') { + if (isActiveTextElement(event.target)) { + return; + } + + closePopup(); + event.preventDefault(); + + items[0].getAsString((content) => { + openModal({ + content, + }); + }); + return; } - closePopup(); - event.preventDefault(); + [...items].forEach((item) => { + if (item.kind !== 'file') { + return; + } - item.getAsString((content) => { - openModal({ - content, - }); + submit(item.getAsFile()); }); }; diff --git a/client/src/lib/custom-ui/components/FilePicker/FilePicker.jsx b/client/src/lib/custom-ui/components/FilePicker/FilePicker.jsx index 7741860db..df4a1ea59 100644 --- a/client/src/lib/custom-ui/components/FilePicker/FilePicker.jsx +++ b/client/src/lib/custom-ui/components/FilePicker/FilePicker.jsx @@ -3,7 +3,7 @@ import PropTypes from 'prop-types'; import styles from './FilePicker.module.css'; -const FilePicker = React.memo(({ children, accept, onSelect }) => { +const FilePicker = React.memo(({ children, accept, multiple, onSelect }) => { const field = useRef(null); const handleTriggerClick = useCallback(() => { @@ -12,11 +12,11 @@ const FilePicker = React.memo(({ children, accept, onSelect }) => { const handleFieldChange = useCallback( ({ target }) => { - if (target.files[0]) { - onSelect(target.files[0]); + [...target.files].forEach((file) => { + onSelect(file); + }); - target.value = null; // eslint-disable-line no-param-reassign - } + target.value = null; // eslint-disable-line no-param-reassign }, [onSelect], ); @@ -32,6 +32,7 @@ const FilePicker = React.memo(({ children, accept, onSelect }) => { ref={field} type="file" accept={accept} + multiple={multiple} className={styles.field} onChange={handleFieldChange} /> @@ -42,11 +43,13 @@ const FilePicker = React.memo(({ children, accept, onSelect }) => { FilePicker.propTypes = { children: PropTypes.element.isRequired, accept: PropTypes.string, + multiple: PropTypes.bool, onSelect: PropTypes.func.isRequired, }; FilePicker.defaultProps = { accept: undefined, + multiple: false, }; export default FilePicker; diff --git a/client/src/utils/element-helpers.js b/client/src/utils/element-helpers.js index 15fe9c4b1..0284fd3e2 100644 --- a/client/src/utils/element-helpers.js +++ b/client/src/utils/element-helpers.js @@ -1,5 +1,8 @@ -// eslint-disable-next-line import/prefer-default-export export const focusEnd = (element) => { element.focus(); element.setSelectionRange(element.value.length + 1, element.value.length + 1); }; + +export const isActiveTextElement = (element) => + ['input', 'textarea'].includes(element.tagName.toLowerCase()) && + element === document.activeElement;