diff --git a/package-lock.json b/package-lock.json index 15991ce..4bccc22 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@acpaas-ui/react-editorial-components", - "version": "1.5.6", + "version": "1.5.9", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -24692,9 +24692,9 @@ } }, "sanitize-html": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.5.2.tgz", - "integrity": "sha512-sJ1rO2YixFIqs2kIcEUb6PTrCjvz8DMq1XqWWuy0kjgjrn58GNLK1DKSIRybFZDO1WNgsEgD+WiEzTEYS8xEug==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.4.0.tgz", + "integrity": "sha512-Y1OgkUiTPMqwZNRLPERSEi39iOebn2XJLbeiGOBhaJD/yLqtLGu6GE5w7evx177LeGgSE+4p4e107LMiydOf6A==", "requires": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -24715,15 +24715,30 @@ "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==" + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, "postcss": { - "version": "8.3.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.9.tgz", - "integrity": "sha512-f/ZFyAKh9Dnqytx5X62jgjhhzttjZS7hMsohcI7HEI5tjELX/HxCy3EFhsRxyzGvrzFF+82XPvCS8T9TFleVJw==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "requires": { - "nanoid": "^3.1.28", - "picocolors": "^0.2.1", - "source-map-js": "^0.6.2" + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" } } }, diff --git a/package.json b/package.json index 6abc233..bb49610 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@acpaas-ui/react-editorial-components", - "version": "1.5.6", + "version": "1.5.9", "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "scripts": { @@ -46,7 +46,7 @@ "react-dom": "^16.13.0", "react-popper": "^2.2.4", "rxjs": "^6.6.3", - "sanitize-html": "^2.4.0" + "sanitize-html": "2.4.0" }, "devDependencies": { "@a-ui/flexboxgrid": "^1.0.1", diff --git a/src/components/FileUpload/FileUpload.const.js b/src/components/FileUpload/FileUpload.const.js index 654465d..9fab7c0 100644 --- a/src/components/FileUpload/FileUpload.const.js +++ b/src/components/FileUpload/FileUpload.const.js @@ -11,4 +11,5 @@ export const UPLOAD_OPTIONS_DEFAULT = { maxFileSize: 0, // 0 is infinite url: '', messages: VALIDATION_MESSAGES_DEFAULT, + autoUpload: true, }; diff --git a/src/components/FileUpload/FileUpload.jsx b/src/components/FileUpload/FileUpload.jsx index e480020..bf8f853 100644 --- a/src/components/FileUpload/FileUpload.jsx +++ b/src/components/FileUpload/FileUpload.jsx @@ -1,5 +1,8 @@ +/* eslint-disable react/require-default-props */ import PropTypes from 'prop-types'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { + forwardRef, useEffect, useImperativeHandle, useMemo, useRef, useState, +} from 'react'; import { isNumber } from '../../helpers'; import { useSlot } from '../../hooks/useSlot'; @@ -10,16 +13,17 @@ import { FileUploadZone } from './FileUploadZone'; import { Uploader } from './Uploader'; import { ValidationList } from './ValidationList'; -const FileUpload = ({ +const FileUpload = forwardRef(({ id = '', ariaLabelRemove = 'Verwijder', disabled = false, files = [], options = UPLOAD_OPTIONS_DEFAULT, selectUploadedFiles = () => null, + selectQueuedFiles = () => null, removeFile = () => null, children, -}) => { +}, ref) => { /** * Hooks */ @@ -27,6 +31,14 @@ const FileUpload = ({ const fileUploadMessageSlot = useSlot(FileUploadMessage, children); const [uploader, setUploader] = useState(null); const [invalidFiles, setInvalidFiles] = useState([]); + const [queuedFiles, setQueuedFiles] = useState([]); + const uploadZoneRef = useRef(); + + useImperativeHandle(ref, () => ({ + startUpload(extraHeaders) { + return uploadZoneRef.current.uploadFiles(queuedFiles, extraHeaders); + }, + })); useEffect(() => { if (!uploader) { @@ -42,6 +54,14 @@ const FileUpload = ({ return true; }, [options.fileLimit, files]); + useEffect(() => { + if (!selectQueuedFiles) { + return; + } + + selectQueuedFiles(queuedFiles); + }, [queuedFiles, selectQueuedFiles]); + /** * Methods */ @@ -49,6 +69,10 @@ const FileUpload = ({ setInvalidFiles(invFiles); }; + const onQueuedFiles = (qFiles) => { + setQueuedFiles(qFiles); + }; + const onRequestError = (error) => { setInvalidFiles(error.files.map((file) => ({ file, @@ -60,6 +84,15 @@ const FileUpload = ({ setInvalidFiles(invalidFiles.filter((file, i) => i !== index)); }; + const onRemoveFile = (fileId, index) => { + // If the file is queued, just delete it. + if (!fileId && queuedFiles?.[index]) { + return setQueuedFiles(queuedFiles.filter((_, fIndex) => index !== fIndex)); + } + + removeFile(fileId, index); + }; + /** * Render */ @@ -79,7 +112,7 @@ const FileUpload = ({
  • {file.name} -
  • @@ -91,7 +124,10 @@ const FileUpload = ({ return (
    ) } - { renderFiles(files) } + { renderFiles([...files, ...queuedFiles]) }
    ); -}; +}); FileUpload.propTypes = { id: PropTypes.string.isRequired, disabled: PropTypes.bool, ariaLabelRemove: PropTypes.string, options: PropTypes.shape({ + autoUpload: PropTypes.bool, allowedMimeTypes: PropTypes.arrayOf(PropTypes.string), allowedFileTypes: PropTypes.arrayOf(PropTypes.string), maxFileSize: PropTypes.number, @@ -146,12 +183,17 @@ FileUpload.propTypes = { key: PropTypes.string, value: PropTypes.string, }), + requestHeaders: PropTypes.arrayOf(PropTypes.shape({ + key: PropTypes.string, + value: PropTypes.string, + })), }), files: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, })), selectUploadedFiles: PropTypes.func, + selectQueuedFiles: PropTypes.func, removeFile: PropTypes.func, children: PropTypes.node, }; diff --git a/src/components/FileUpload/FileUpload.stories.mdx b/src/components/FileUpload/FileUpload.stories.mdx index ab8132b..b2c9d63 100644 --- a/src/components/FileUpload/FileUpload.stories.mdx +++ b/src/components/FileUpload/FileUpload.stories.mdx @@ -1,4 +1,4 @@ -import { useState, useMemo } from 'react'; +import { useState, useMemo, useRef } from 'react'; import { action } from '@storybook/addon-actions' import { Meta, Story, Preview, Props } from '@storybook/addon-docs/blocks'; @@ -183,6 +183,43 @@ A file upload component ) }} + + {() => { + const [files, setFiles] = useState([]); + const ref = useRef(); + return ( + <> + console.log(files)} + files={files} + removeFile={(id, index) => setFiles(files.filter(file => file.id === id))} + selectUploadedFiles={action()} + > + Drag your files here or click to upload + Optional description message + + + + ) + }} + ## Props diff --git a/src/components/FileUpload/FileUploadZone/FileUploadZone.jsx b/src/components/FileUpload/FileUploadZone/FileUploadZone.jsx index d403231..3ae9222 100644 --- a/src/components/FileUpload/FileUploadZone/FileUploadZone.jsx +++ b/src/components/FileUpload/FileUploadZone/FileUploadZone.jsx @@ -1,13 +1,16 @@ +/* eslint-disable react/require-default-props */ import classnames from 'classnames'; import PropTypes from 'prop-types'; -import React, { useMemo, useRef, useState } from 'react'; +import React, { + forwardRef, useImperativeHandle, useMemo, useRef, useState, +} from 'react'; import { useSlot } from '../../../hooks/useSlot'; import { ProgressBar } from '../../ProgressBar'; import { FileUploadDescription, FileUploadMessage } from '../FileUpload.slots'; import { Uploader } from '../Uploader'; -const FileUploadZone = ({ +const FileUploadZone = forwardRef(({ autoUpload = true, id = '', ariaId = '', @@ -18,12 +21,12 @@ const FileUploadZone = ({ onCustomDrop, uploadedFiles = () => null, invalidFiles = () => null, + queuedFiles = () => null, onRequestError = () => null, allowedMimeTypes = [], allowedFileTypes = [], children, - -}) => { +}, ref) => { /** * Hooks */ @@ -36,6 +39,13 @@ const FileUploadZone = ({ const accept = useMemo(() => allowedFileTypes.map((type) => `.${type}`).concat(allowedMimeTypes).join(','), [allowedFileTypes, allowedMimeTypes]); + useImperativeHandle(ref, () => ({ + uploadFiles(files, extraHeaders) { + // eslint-disable-next-line no-use-before-define + return uploadFiles(files, extraHeaders); + }, + })); + /** * Methods */ @@ -47,13 +57,13 @@ const FileUploadZone = ({ } }; - const uploadFiles = (files) => { + const uploadFiles = (files, extraHeaders) => new Promise((resolve, reject) => { // Reset progress setUploadProgress(0); setUploadingFiles(files); // upload - uploader.uploadFiles(files).subscribe( + uploader.uploadFiles(files, extraHeaders).subscribe( (response) => { if (response.progress) { setUploadProgress(Math.floor(response.progress * 100)); @@ -79,14 +89,18 @@ const FileUploadZone = ({ files, error, }); + + reject(error); }, () => { setUploadProgress(0); setUploadingFiles([]); clearFileInput(); + + resolve(); }, ); - }; + }); const handleFiles = async (files, customHandler) => { const response = await Promise.resolve(uploader.validateFiles(files)); @@ -95,9 +109,14 @@ const FileUploadZone = ({ if (customHandler) { customHandler(response.validFiles); } + if (autoUpload && response.validFiles.length > 0) { uploadFiles(response.validFiles); } + + if (!autoUpload && response.validFiles.length > 0) { + queuedFiles(response.validFiles); + } }; const handleCustomClick = (e) => { @@ -200,7 +219,7 @@ const FileUploadZone = ({ ); -}; +}); FileUploadZone.propTypes = { autoUpload: PropTypes.bool, @@ -211,6 +230,7 @@ FileUploadZone.propTypes = { ariaId: PropTypes.string, uploadedFiles: PropTypes.func, invalidFiles: PropTypes.func, + queuedFiles: PropTypes.func, onRequestError: PropTypes.func, onCustomClick: PropTypes.func, onCustomDrop: PropTypes.func, diff --git a/src/components/FileUpload/Uploader/uploader.class.js b/src/components/FileUpload/Uploader/uploader.class.js index 0763439..d065663 100644 --- a/src/components/FileUpload/Uploader/uploader.class.js +++ b/src/components/FileUpload/Uploader/uploader.class.js @@ -16,7 +16,7 @@ export class Uploader { }; } - uploadFiles(files = []) { + uploadFiles(files = [], extraHeaders) { const formData = this.filesToFormData(files); return new Observable((observer) => { @@ -52,6 +52,18 @@ export class Uploader { xhr.setRequestHeader(this.options.requestHeader.key, this.options.requestHeader.value); } + if (this.options.requestHeaders && Array.isArray(this.options.requestHeaders)) { + this.options.requestHeaders.forEach(({ key, value }) => { + xhr.setRequestHeader(key, value); + }); + } + + if (extraHeaders && Array.isArray(extraHeaders)) { + extraHeaders.forEach(({ key, value }) => { + xhr.setRequestHeader(key, value); + }); + } + xhr.send(formData); }); }