From a5aeba00708f634dab98db69749be8b270225815 Mon Sep 17 00:00:00 2001 From: Bipul Adhikari Date: Thu, 10 Oct 2024 17:59:08 +0545 Subject: [PATCH] Add support for S3 file uploads Signed-off-by: Bipul Adhikari --- locales/en/plugin__odf-console.json | 27 ++ package.json | 5 + packages/odf/components/mcg/ObjectBucket.tsx | 3 +- .../bucket-details/BucketDetails.tsx | 24 +- .../bucket-overview/BucketOverview.tsx | 37 ++- .../objects-list/ObjectListWithSidebar.tsx | 57 ++++ .../s3-browser/objects-list/ObjectsList.tsx | 1 - .../upload-objects/UploadSidebar.tsx | 171 +++++++++++ .../s3-browser/upload-objects/index.ts | 3 + .../s3-browser/upload-objects/types.ts | 18 ++ .../upload-component/FileUploadComponent.tsx | 165 +++++++++++ .../upload-component/fileUploadComponent.scss | 25 ++ .../upload-objects/upload-component/index.ts | 1 + .../upload-component/uploads.ts | 76 +++++ .../upload-status/UploadStatusBasedAlert.tsx | 56 ++++ .../upload-status/UploadStatusItem.tsx | 66 +++++ .../upload-status/UploadStatusList.tsx | 60 ++++ .../upload-objects/upload-status/index.ts | 1 + .../upload-status/uploadStatusItem.scss | 12 + .../upload-objects/uploadSidebar.scss | 3 + .../s3-browser/upload-objects/utils.ts | 115 ++++++++ .../storage-consumers/list-filter.ts | 4 +- .../abort-uploads/AbortUploadsModal.tsx | 60 ++++ packages/shared/src/s3/commands.ts | 55 ++-- packages/shared/src/status/Status.tsx | 1 + tsconfig.json | 2 +- yarn.lock | 272 +++++++++++++++++- 27 files changed, 1271 insertions(+), 49 deletions(-) create mode 100644 packages/odf/components/s3-browser/objects-list/ObjectListWithSidebar.tsx create mode 100644 packages/odf/components/s3-browser/upload-objects/UploadSidebar.tsx create mode 100644 packages/odf/components/s3-browser/upload-objects/index.ts create mode 100644 packages/odf/components/s3-browser/upload-objects/types.ts create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-component/FileUploadComponent.tsx create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-component/fileUploadComponent.scss create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-component/index.ts create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-component/uploads.ts create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusBasedAlert.tsx create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusItem.tsx create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusList.tsx create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-status/index.ts create mode 100644 packages/odf/components/s3-browser/upload-objects/upload-status/uploadStatusItem.scss create mode 100644 packages/odf/components/s3-browser/upload-objects/uploadSidebar.scss create mode 100644 packages/odf/components/s3-browser/upload-objects/utils.ts create mode 100644 packages/odf/modals/s3-browser/abort-uploads/AbortUploadsModal.tsx diff --git a/locales/en/plugin__odf-console.json b/locales/en/plugin__odf-console.json index 0a682b371..2ce9dca30 100644 --- a/locales/en/plugin__odf-console.json +++ b/locales/en/plugin__odf-console.json @@ -1176,6 +1176,28 @@ "You do not have any objects in the bucket": "You do not have any objects in the bucket", "many": "many", "{{ fromCount }} - {{ toCount }} of {{ totalCount }}": "{{ fromCount }} - {{ toCount }} of {{ totalCount }}", + "Add objects": "Add objects", + "Transfer files to cloud storage, where each file (object) is stored with a unique identifier and metadata. By default, objects are private. To configure permissions or properties for objects in an S3 bucket, users can use the AWS Command Line Interface (CLI), AWS Management Console, or SDKs. To make objects publicly accessible or apply more specific permissions, users can set bucket policies, use access control lists (ACLs), or define IAM roles based on their requirements.": "Transfer files to cloud storage, where each file (object) is stored with a unique identifier and metadata. By default, objects are private. To configure permissions or properties for objects in an S3 bucket, users can use the AWS Command Line Interface (CLI), AWS Management Console, or SDKs. To make objects publicly accessible or apply more specific permissions, users can set bucket policies, use access control lists (ACLs), or define IAM roles based on their requirements.", + "Drag and drop files/folders here.": "Drag and drop files/folders here.", + "Standard uploads have a size limit of up to 5TB in S3. For objects, multipart upload will upload the object in parts, which are assembled in the bucket.": "Standard uploads have a size limit of up to 5TB in S3. For objects, multipart upload will upload the object in parts, which are assembled in the bucket.", + "Upload": "Upload", + "Uploading files to the bucket is complete": "Uploading files to the bucket is complete", + "Uploading files to the bucket is in progress": "Uploading files to the bucket is in progress", + "View uploads": "View uploads", + "Dismiss": "Dismiss", + "{{completedUploads}} of {{totalUploads}} have been uploaded": "{{completedUploads}} of {{totalUploads}} have been uploaded", + "Uploads": "Uploads", + "{{uploadedFiles}} of {{totalFiles}} files uploaded": "{{uploadedFiles}} of {{totalFiles}} files uploaded", + "Complete": "Complete", + "Uploading": "Uploading", + "Ongoing": "Ongoing", + "Succeeded: {{uploadedFiles}}": "Succeeded: {{uploadedFiles}}", + "Failed files: {{failedFiles}}": "Failed files: {{failedFiles}}", + "Completion time: {{totalTimeElapsed}}": "Completion time: {{totalTimeElapsed}}", + "Total Remaining: {{totalRemaining}}": "Total Remaining: {{totalRemaining}}", + "Estimated time remaining: {{timeRemaining}}": "Estimated time remaining: {{timeRemaining}}", + "Transfer rate: {{uploadSpeed}}": "Transfer rate: {{uploadSpeed}}", + "Standard uploads have a size limit of up to 5 TB in S3.": "Standard uploads have a size limit of up to 5 TB in S3.", "<0>The amount of storage allocated to the client cluster for usage.<1>Due to simultaneous usage by multiple client clusters, actual available storage may vary affecting your allocated storage quota.": "<0>The amount of storage allocated to the client cluster for usage.<1>Due to simultaneous usage by multiple client clusters, actual available storage may vary affecting your allocated storage quota.", "No storage clients found.": "No storage clients found.", "You do not have any storage clients connected to this Data Foundation provider cluster.": "You do not have any storage clients connected to this Data Foundation provider cluster.", @@ -1338,6 +1360,11 @@ "and": "and", "GiB RAM": "GiB RAM", "Configure Performance": "Configure Performance", + "Cancel upload": "Cancel upload", + "Cancel all ongoing uploads?": "Cancel all ongoing uploads?", + "Yes, cancel": "Yes, cancel", + "No, continue uploads": "No, continue uploads", + "Are you sure you want to cancel the ongoing uploads? Any files currently being uploaded will be stopped, and partially uploaded files will not be saved.": "Are you sure you want to cancel the ongoing uploads? Any files currently being uploaded will be stopped, and partially uploaded files will not be saved.", "This name is already in use. Try using a different name for your folder.": "This name is already in use. Try using a different name for your folder.", "The forward slash (\"/\") cannot be used.": "The forward slash (\"/\") cannot be used.", "All characters are allowed except for the forward slash (\"/\").": "All characters are allowed except for the forward slash (\"/\").", diff --git a/package.json b/package.json index 5e0df9eac..75a507746 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,7 @@ }, "dependencies": { "@aws-sdk/client-s3": "3.667.0", + "@aws-sdk/lib-storage": "3.501.0", "@aws-sdk/s3-request-presigner": "3.614.0", "@openshift-console/dynamic-plugin-sdk": "1.3.0", "@openshift-console/dynamic-plugin-sdk-internal": "1.0.0", @@ -99,6 +100,7 @@ "react-dnd": "^15.1.1", "react-dnd-html5-backend": "^15.1.2", "react-dom": "^17.0.1", + "react-dropzone": "^14.2.9", "react-helmet": "^6.1.0", "react-hook-form": "^7.42.1", "react-i18next": "^11.11.4", @@ -107,6 +109,8 @@ "react-router-dom": "5.3.x", "react-router-dom-v5-compat": "^6.11.2", "react-tagsinput": "^3.19.0", + "react-virtualized-auto-sizer": "^1.0.24", + "react-window": "^1.8.10", "redux": "4.0.1", "resolve-url-loader": "^5.0.0", "sass": "^1.55.0", @@ -142,6 +146,7 @@ "@types/react-helmet": "^6.1.1", "@types/react-router": "^5.1.20", "@types/react-router-dom": "5.3.x", + "@types/react-window": "^1.8.8", "@typescript-eslint/eslint-plugin": "^5.42", "@typescript-eslint/parser": "^5.42", "comment-json": "4.x", diff --git a/packages/odf/components/mcg/ObjectBucket.tsx b/packages/odf/components/mcg/ObjectBucket.tsx index 7578ed569..cd8c8ed33 100644 --- a/packages/odf/components/mcg/ObjectBucket.tsx +++ b/packages/odf/components/mcg/ObjectBucket.tsx @@ -12,6 +12,7 @@ import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; import { referenceForModel } from '@odf/shared/utils'; import { EventStreamWrapped, YAMLEditorWrapped } from '@odf/shared/utils/Tabs'; import { + K8sResourceCommon, ListPageBody, ListPageFilter, ResourceLink as ResourceLinkWithKind, @@ -211,7 +212,7 @@ export const ObjectBucketListPage: React.FC = ( }; type OBDetailsProps = { - obj: K8sResourceKind; + obj: K8sResourceCommon; ownerLabel?: string; }; diff --git a/packages/odf/components/s3-browser/bucket-details/BucketDetails.tsx b/packages/odf/components/s3-browser/bucket-details/BucketDetails.tsx index 46c761e03..abf9ce95b 100644 --- a/packages/odf/components/s3-browser/bucket-details/BucketDetails.tsx +++ b/packages/odf/components/s3-browser/bucket-details/BucketDetails.tsx @@ -16,11 +16,14 @@ import { BUCKET_VERSIONING_CACHE_KEY_SUFFIX, } from '@odf/core/constants'; import { DASH } from '@odf/shared'; +import { LoadingBox } from '@odf/shared/generic/status-box'; import { SectionHeading } from '@odf/shared/heading/page-heading'; import { BucketPolicy } from '@odf/shared/s3'; -import { K8sResourceKind } from '@odf/shared/types'; import { useCustomTranslation } from '@odf/shared/useCustomTranslationHook'; -import { GreenCheckCircleIcon } from '@openshift-console/dynamic-plugin-sdk'; +import { + GreenCheckCircleIcon, + K8sResourceCommon, +} from '@openshift-console/dynamic-plugin-sdk'; import { TFunction } from 'i18next'; import { useParams } from 'react-router-dom-v5-compat'; import useSWR from 'swr'; @@ -166,16 +169,25 @@ const BucketDetailsOverview: React.FC<{}> = ({}) => { }; type BucketDetailsProps = { - obj: K8sResourceKind; + obj: { + refresh: boolean; + resource?: K8sResourceCommon; + }; }; -export const BucketDetails: React.FC = ({ obj }) => { +export const BucketDetails: React.FC = ({ + obj: { resource, refresh }, +}) => { const { t } = useCustomTranslation(); - return ( + return refresh ? ( <> - {obj && } + {resource && ( + + )} + ) : ( + ); }; diff --git a/packages/odf/components/s3-browser/bucket-overview/BucketOverview.tsx b/packages/odf/components/s3-browser/bucket-overview/BucketOverview.tsx index 1871d9196..528f7e2a8 100644 --- a/packages/odf/components/s3-browser/bucket-overview/BucketOverview.tsx +++ b/packages/odf/components/s3-browser/bucket-overview/BucketOverview.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { BucketDetails } from '@odf/core/components/s3-browser/bucket-details/BucketDetails'; -import { LoadingBox } from '@odf/shared/generic/status-box'; import PageHeading from '@odf/shared/heading/page-heading'; import { useRefresh } from '@odf/shared/hooks'; import { ModalKeys, defaultModalMap } from '@odf/shared/modals/types'; @@ -16,6 +15,7 @@ import { useK8sWatchResource, HorizontalNav, useModal, + K8sResourceCommon, } from '@openshift-console/dynamic-plugin-sdk'; import { LaunchModal } from '@openshift-console/dynamic-plugin-sdk/lib/app/modal-support/ModalProvider'; import { TFunction } from 'i18next'; @@ -27,9 +27,20 @@ import { PREFIX, BUCKETS_BASE_ROUTE } from '../../../constants'; import { NooBaaObjectBucketModel } from '../../../models'; import { getBreadcrumbs } from '../../../utils'; import { NoobaaS3Provider } from '../noobaa-context'; -import { CustomActionsToggle, ObjectsList } from '../objects-list'; +import { CustomActionsToggle } from '../objects-list'; +import { ObjectListWithSidebar } from '../objects-list/ObjectListWithSidebar'; import { PageTitle } from './PageTitle'; +type CustomYAMLEditorProps = { + obj: { + resource: K8sResourceCommon; + }; +}; + +const CustomYAMLEditor: React.FC = ({ + obj: { resource }, +}) => ; + const getBucketActionsItems = ( t: TFunction, launcher: LaunchModal, @@ -118,7 +129,7 @@ const BucketOverview: React.FC<{}> = () => { { href: '', name: t('Objects'), - component: ObjectsList, + component: ObjectListWithSidebar, }, ...(!foldersPath ? [ @@ -134,7 +145,7 @@ const BucketOverview: React.FC<{}> = () => { { href: 'yaml', name: t('YAML'), - component: YAMLEditorWrapped, + component: CustomYAMLEditor, }, ] : []), @@ -187,14 +198,16 @@ const BucketOverview: React.FC<{}> = () => { actions={actions} className="pf-v5-u-mt-md" /> - {fresh ? ( - - ) : ( - - )} + ); }; diff --git a/packages/odf/components/s3-browser/objects-list/ObjectListWithSidebar.tsx b/packages/odf/components/s3-browser/objects-list/ObjectListWithSidebar.tsx new file mode 100644 index 000000000..06f70b96b --- /dev/null +++ b/packages/odf/components/s3-browser/objects-list/ObjectListWithSidebar.tsx @@ -0,0 +1,57 @@ +import * as React from 'react'; +import { LoadingBox } from '@odf/shared/generic/status-box'; +import { useParams } from 'react-router-dom-v5-compat'; +import { NoobaaS3Context } from '../noobaa-context'; +import UploadSidebar from '../upload-objects'; +import { UploadProgress } from '../upload-objects'; +import { FileUploadComponent } from '../upload-objects'; +import { ObjectsList } from './ObjectsList'; + +type ObjectListWithSidebarProps = { + obj: { refresh: boolean; triggerRefresh: () => void }; +}; + +export const ObjectListWithSidebar: React.FC = ({ + obj: { refresh, triggerRefresh }, +}) => { + const [isExpanded, setExpanded] = React.useState(false); + const [uploadProgress, setUploadProgress] = React.useState( + {} + ); + const [completionTime, setCompletionTime] = React.useState(); + + const abortAll = React.useCallback(() => { + Object.values(uploadProgress).forEach((upload) => upload?.abort?.()); + }, [uploadProgress]); + + const { bucketName } = useParams(); + + const { noobaaS3 } = React.useContext(NoobaaS3Context); + + const closeSidebar = () => setExpanded(false); + const showSidebar = () => setExpanded(true); + + return ( + + + {refresh ? : } + + } + /> + ); +}; diff --git a/packages/odf/components/s3-browser/objects-list/ObjectsList.tsx b/packages/odf/components/s3-browser/objects-list/ObjectsList.tsx index 738bc2d3f..624efb4ce 100644 --- a/packages/odf/components/s3-browser/objects-list/ObjectsList.tsx +++ b/packages/odf/components/s3-browser/objects-list/ObjectsList.tsx @@ -444,7 +444,6 @@ export const ObjectsList: React.FC<{}> = () => { deleteResponse={deleteResponse} foldersPath={foldersPath} /> - {/* ToDo: add upload objects option */} { if (!!continuationTokens.next && loadedWOError) diff --git a/packages/odf/components/s3-browser/upload-objects/UploadSidebar.tsx b/packages/odf/components/s3-browser/upload-objects/UploadSidebar.tsx new file mode 100644 index 000000000..ac2c4e6e2 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/UploadSidebar.tsx @@ -0,0 +1,171 @@ +import * as React from 'react'; +import { Status, useCustomTranslation } from '@odf/shared'; +import { ResourceStatus } from '@openshift-console/dynamic-plugin-sdk'; +import { Trans } from 'react-i18next'; +import { + Alert, + AlertVariant, + Drawer, + DrawerActions, + DrawerCloseButton, + DrawerContent, + DrawerContentBody, + DrawerHead, + DrawerPanelContent, + Flex, + FlexItem, + Title, +} from '@patternfly/react-core'; +import { UploadProgress } from './types'; +import UploadStatusList from './upload-status'; +import { + getCompletedAndTotalUploadCount, + getFailedFiles, + getTotalRemainingFilesAndSize, + getTotalTimeElapsed, + getTotalTimeRemaining, + getUploadSpeed, +} from './utils'; +import './uploadSidebar.scss'; + +type PanelContentProps = { + uploadProgress: UploadProgress; + onClose: () => void; + completionTime: number; +}; + +const PanelContent: React.FC = ({ + uploadProgress, + onClose, + completionTime, +}) => { + const { t } = useCustomTranslation(); + const [uploadedFiles, totalFiles] = + getCompletedAndTotalUploadCount(uploadProgress); + const failedFiles = getFailedFiles(uploadProgress); + const uploadSpeed = getUploadSpeed(uploadProgress); + const totalRemaining = getTotalRemainingFilesAndSize(uploadProgress); + const timeRemaining = getTotalTimeRemaining(uploadProgress); + const totalTimeElapsed = getTotalTimeElapsed(uploadProgress, completionTime); + const isComplete = uploadedFiles + failedFiles === totalFiles; + + return ( + +
+ + + + {t('Uploads')} + + + + <span> + {t('{{uploadedFiles}} of {{totalFiles}} files uploaded', { + uploadedFiles, + totalFiles, + })} +   + {isComplete ? ( + <ResourceStatus> + <Status status={t('Complete')} title={t('Completed')} /> + </ResourceStatus> + ) : ( + <ResourceStatus> + <Status status={t('Uploading')} title={t('Ongoing')} /> + </ResourceStatus> + )} + </span> + + + {isComplete ? ( + <> + + {t('Succeeded: {{uploadedFiles}}', { uploadedFiles })} + + + {t('Failed files: {{failedFiles}}', { + failedFiles: totalFiles - uploadedFiles, + })} + + + {t('Completion time: {{totalTimeElapsed}}', { + totalTimeElapsed, + })} + + + ) : ( + <> + + {t('Total Remaining: {{totalRemaining}}', { totalRemaining })} + + + {t('Estimated time remaining: {{timeRemaining}}', { + timeRemaining, + })} + + + {t('Transfer rate: {{uploadSpeed}}', { uploadSpeed })} + + + )} + + + + + +
+ + + + Standard uploads have a size limit of up to 5 TB in S3. + + + + +
+ ); +}; + +export type UploadSidebarProps = { + isExpanded: boolean; + closeSidebar: () => void; + setDrawerReference?: (drawerRef: React.Ref) => void; + uploadProgress: UploadProgress; + mainContent: React.ReactNode; + completionTime: number; +}; + +export const UploadSidebar: React.FC = ({ + isExpanded, + closeSidebar, + uploadProgress, + mainContent: drawerContentBody, + completionTime, +}) => { + return ( + + + } + > + {drawerContentBody} + + + ); +}; diff --git a/packages/odf/components/s3-browser/upload-objects/index.ts b/packages/odf/components/s3-browser/upload-objects/index.ts new file mode 100644 index 000000000..6e883eda5 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/index.ts @@ -0,0 +1,3 @@ +export { UploadSidebar as default } from './UploadSidebar'; +export { default as FileUploadComponent } from './upload-component'; +export * from './types'; diff --git a/packages/odf/components/s3-browser/upload-objects/types.ts b/packages/odf/components/s3-browser/upload-objects/types.ts new file mode 100644 index 000000000..9704bc60d --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/types.ts @@ -0,0 +1,18 @@ +export enum UploadStatus { + INIT_STATE, + UPLOAD_COMPLETE, + UPLOAD_START, + UPLOAD_FAILED, +} + +export type UploadProgress = { + [name: string]: { + total: number; + loaded: number; + name: string; + abort?: () => void; + uploadState?: UploadStatus; + filePath?: string; + startTime: number; + }; +}; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-component/FileUploadComponent.tsx b/packages/odf/components/s3-browser/upload-objects/upload-component/FileUploadComponent.tsx new file mode 100644 index 000000000..7b3b1a90d --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-component/FileUploadComponent.tsx @@ -0,0 +1,165 @@ +import * as React from 'react'; +import { PREFIX } from '@odf/core/constants'; +import { getPrefix } from '@odf/core/utils'; +import { FieldLevelHelp, useCustomTranslation } from '@odf/shared'; +import { S3Commands } from '@odf/shared/s3'; +import UploadIcon from '@patternfly/react-icons/dist/esm/icons/upload-icon'; +import { useDropzone } from 'react-dropzone'; +import { Trans } from 'react-i18next'; +import { useSearchParams } from 'react-router-dom-v5-compat'; +import { Icon, FlexItem, Flex, Title, Button } from '@patternfly/react-core'; +import { UploadProgress, UploadStatus } from '../types'; +import { UploadStatusBasedAlert } from '../upload-status/UploadStatusBasedAlert'; +import { getCompletedTotalFailedCount } from '../utils'; +import { convertFileToUploadProgress, uploadFile } from './uploads'; +import './fileUploadComponent.scss'; + +type FileUploadComponentProps = { + client: S3Commands; + bucketName: string; + uploadProgress: UploadProgress; + setUploadProgress: React.Dispatch>; + showSidebar: () => void; + abortAll: () => void; + setCompletionTime: React.Dispatch>; + triggerRefresh: () => void; +}; + +export const FileUploadComponent: React.FC = ({ + client, + bucketName, + uploadProgress, + setUploadProgress, + showSidebar, + abortAll, + setCompletionTime, + triggerRefresh, +}) => { + const { t } = useCustomTranslation(); + const [uploadStatus, setUploadStatus] = React.useState< + | UploadStatus.INIT_STATE + | UploadStatus.UPLOAD_COMPLETE + | UploadStatus.UPLOAD_START + >(UploadStatus.INIT_STATE); + + const [searchParams] = useSearchParams(); + const foldersPath = searchParams.get(PREFIX) || ''; + + const processFiles = React.useCallback( + async (uploadObjects: File[], setProgress) => { + try { + const completionTime = await uploadFile( + uploadObjects, + client, + bucketName, + setProgress, + foldersPath + ); + setCompletionTime(completionTime); + } catch (e) { + // eslint-disable-next-line no-console + console.error('Error uploading file', e); + } + }, + [bucketName, client, foldersPath, setCompletionTime] + ); + + const onDrop = React.useCallback( + async (acceptedFiles: File[]) => { + const intialUploadObjects = acceptedFiles.reduce((acc, curr) => { + const name = getPrefix( + curr.webkitRelativePath || curr.name, + foldersPath + ); + acc[name] = convertFileToUploadProgress(curr); + return acc; + }, {} as UploadProgress); + setUploadProgress(intialUploadObjects); + setUploadStatus(UploadStatus.UPLOAD_START); + await processFiles(acceptedFiles, setUploadProgress); + setUploadStatus(UploadStatus.UPLOAD_COMPLETE); + triggerRefresh(); + }, + [setUploadProgress, processFiles, triggerRefresh, foldersPath] + ); + + const [completedUploads, totalUploads, failedUploads] = + getCompletedTotalFailedCount(uploadProgress); + + const { getRootProps, getInputProps } = useDropzone({ + onDrop, + useFsAccessApi: false, + }); + + const closeAlert = () => { + setUploadStatus(UploadStatus.INIT_STATE); + }; + + return ( + <> +
+ {(uploadStatus === UploadStatus.UPLOAD_START || + uploadStatus === UploadStatus.UPLOAD_COMPLETE) && ( + + )} + + {t('Add objects')} + <FieldLevelHelp> + <Trans t={t}> + Transfer files to cloud storage, where each file (object) is + stored with a unique identifier and metadata. By default, objects + are private. To configure permissions or properties for objects in + an S3 bucket, users can use the AWS Command Line Interface (CLI), + AWS Management Console, or SDKs. To make objects publicly + accessible or apply more specific permissions, users can set + bucket policies, use access control lists (ACLs), or define IAM + roles based on their requirements. + </Trans> + </FieldLevelHelp> + +
+ + + + + + + + + + + + + + {t('Drag and drop files/folders here.')} + + + + + + + + Standard uploads have a size limit of up to 5TB in S3. For + objects, multipart upload will upload the object in parts, + which are assembled in the bucket. + + + + + + + + + +
+
+ + ); +}; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-component/fileUploadComponent.scss b/packages/odf/components/s3-browser/upload-objects/upload-component/fileUploadComponent.scss new file mode 100644 index 000000000..169461a95 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-component/fileUploadComponent.scss @@ -0,0 +1,25 @@ +.odf-upload{ + width: 95%; + margin: var(--pf-v5-global--spacer--sm); + padding: var(--pf-v5-global--spacer--xs); +} + +.odf-upload__title { + border: 1px dotted var(--pf-v5-global--palette--black-500); + padding: var(--pf-v5-global--spacer--md); + margin-top: var(--pf-v5-global--spacer--xs); +} + +.odf-upload-title__button{ + width: 75%; +} + + +.odf-upload-title__input { + margin: 0 auto; + align-self: center; +} + +.odf-upload__helper-text { + color: var(--pf-v5-global--palette--black-500); +} diff --git a/packages/odf/components/s3-browser/upload-objects/upload-component/index.ts b/packages/odf/components/s3-browser/upload-objects/upload-component/index.ts new file mode 100644 index 000000000..859a915ea --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-component/index.ts @@ -0,0 +1 @@ +export { FileUploadComponent as default } from './FileUploadComponent'; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-component/uploads.ts b/packages/odf/components/s3-browser/upload-objects/upload-component/uploads.ts new file mode 100644 index 000000000..4f09077e1 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-component/uploads.ts @@ -0,0 +1,76 @@ +import * as React from 'react'; +import { Upload } from '@aws-sdk/lib-storage'; +import { getPrefix } from '@odf/core/utils'; +import { S3Commands } from '@odf/shared/s3'; +import * as _ from 'lodash-es'; +import { UploadProgress, UploadStatus } from '../types'; + +export const uploadFile = async ( + files: File[], + client: S3Commands, + bucketName: string, + setUploadProgress: React.Dispatch>, + folderPath: string +) => { + const performUploadPromise = ( + file: File + ): ReturnType => { + const key = getPrefix(file.webkitRelativePath || file.name, folderPath); + const uploader = client.getUploader(file, key, bucketName); + uploader.on('httpUploadProgress', (progress) => { + const isComplete = progress.total === progress.loaded; + setUploadProgress((prev) => ({ + ...prev, + [progress.Key]: { + startTime: prev[progress.Key].startTime ?? Date.now(), + name: progress.Key, + total: progress.total, + loaded: progress.loaded, + abort: () => { + uploader.abort(); + setUploadProgress((previous) => { + const clonedPrev = _.cloneDeep(previous); + clonedPrev[progress.Key].uploadState = UploadStatus.UPLOAD_FAILED; + return clonedPrev; + }); + }, + uploadState: isComplete + ? UploadStatus.UPLOAD_COMPLETE + : UploadStatus.UPLOAD_START, + }, + })); + }); + return uploader.done(); + }; + const allUploadPromise = files.map((file) => performUploadPromise(file)); + const settledPromises = await Promise.allSettled(allUploadPromise); + settledPromises.forEach((promise, i) => { + const hasFailed = promise.status === 'rejected'; + if (hasFailed) { + const key = getPrefix( + files[i].webkitRelativePath || files[i].name, + folderPath + ); + setUploadProgress((prev) => ({ + ...prev, + [key]: { + ...prev[key], + uploadState: UploadStatus.UPLOAD_FAILED, + }, + })); + } + }); + return Date.now(); +}; + +export const convertFileToUploadProgress = ( + file: File +): UploadProgress[keyof UploadProgress] => ({ + total: file.size, + loaded: 0, + uploadState: UploadStatus.INIT_STATE, + abort: null, + name: file.name, + filePath: file.webkitRelativePath, + startTime: undefined, +}); diff --git a/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusBasedAlert.tsx b/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusBasedAlert.tsx new file mode 100644 index 000000000..a94d4a4c0 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusBasedAlert.tsx @@ -0,0 +1,56 @@ +import * as React from 'react'; +import { useCustomTranslation } from '@odf/shared'; +import { Alert, AlertActionLink, AlertVariant } from '@patternfly/react-core'; +import { InProgressIcon } from '@patternfly/react-icons'; +import { AbortUploadsModal } from '../../../../modals/s3-browser/abort-uploads/AbortUploadsModal'; + +type UploadStatusBasedAlertProps = { + closeAlert: () => void; + abortAll: () => void; + showSidebar: () => void; + failedUploads: number; + totalUploads: number; + completedUploads: number; +}; + +export const UploadStatusBasedAlert: React.FC = ({ + showSidebar, + abortAll, + completedUploads, + failedUploads, + totalUploads, + closeAlert, +}) => { + const { t } = useCustomTranslation(); + const showSuccess = totalUploads === completedUploads + failedUploads; + return ( + } + title={ + showSuccess + ? t('Uploading files to the bucket is complete') + : t('Uploading files to the bucket is in progress') + } + isInline + actionLinks={ + <> + + {t('View uploads')} + + {showSuccess && ( + + {t('Dismiss')} + + )} + {!showSuccess && } + + } + > + {t('{{completedUploads}} of {{totalUploads}} have been uploaded', { + completedUploads, + totalUploads, + })} + + ); +}; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusItem.tsx b/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusItem.tsx new file mode 100644 index 000000000..f49946da2 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusItem.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { + Button, + ButtonVariant, + Flex, + FlexItem, + Icon, + Progress, + ProgressMeasureLocation, +} from '@patternfly/react-core'; +import { CloseIcon, FileIcon } from '@patternfly/react-icons'; +import './uploadStatusItem.scss'; + +type UploadStatusItemProps = { + fileName: string; + fileSize: string; + progress: number; + onAbort?: () => void; + failed: boolean; + variant: Progress['props']['variant']; +}; + +const FileTitle: React.FC<{ + name: string; + size: string; + onAbort?: (fileName: string) => void; +}> = ({ name, size }) => ( +
+ {name} +   + {size} +
+); +export const UploadStatusItem: React.FC = ({ + fileName, + fileSize, + progress, + onAbort, + variant, + failed, +}) => { + return ( + + + + + + + + } + /> + + + {progress !== 100 && !failed && ( + + )} + + + ); +}; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusList.tsx b/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusList.tsx new file mode 100644 index 000000000..ce8149ee0 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-status/UploadStatusList.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { humanizeBinaryBytes } from '@odf/shared/utils'; +import AutoSizer from 'react-virtualized-auto-sizer'; +import { FixedSizeList as List } from 'react-window'; +import { Progress, ProgressVariant } from '@patternfly/react-core'; +import { UploadProgress, UploadStatus } from '../types'; +import { UploadStatusItem } from './UploadStatusItem'; + +type UploadStatusListProps = { + progress: UploadProgress[keyof UploadProgress][]; + currentWidth?: number; +}; + +const getProgressVariant = ( + state: UploadStatus +): Progress['props']['variant'] => { + switch (state) { + case UploadStatus.UPLOAD_FAILED: + return ProgressVariant.danger; + case UploadStatus.UPLOAD_COMPLETE: + return ProgressVariant.success; + default: + return undefined; + } +}; + +export const UploadStatusList: React.FC = ({ + progress, +}) => { + const Row = ({ index, style }) => { + const item = progress[index]; + return ( +
+ +
+ ); + }; + + return ( + + {({ height, width }) => ( + + {Row} + + )} + + ); +}; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-status/index.ts b/packages/odf/components/s3-browser/upload-objects/upload-status/index.ts new file mode 100644 index 000000000..4676acb0d --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-status/index.ts @@ -0,0 +1 @@ +export { UploadStatusList as default } from './UploadStatusList'; diff --git a/packages/odf/components/s3-browser/upload-objects/upload-status/uploadStatusItem.scss b/packages/odf/components/s3-browser/upload-objects/upload-status/uploadStatusItem.scss new file mode 100644 index 000000000..28821cca7 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/upload-status/uploadStatusItem.scss @@ -0,0 +1,12 @@ +.odf-upload-status-item{ + &__file-title--overflow { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 200px; + display: block; + } + &__file-title--color { + color: var(--pf-v5-global--palette--black-500); + } +} diff --git a/packages/odf/components/s3-browser/upload-objects/uploadSidebar.scss b/packages/odf/components/s3-browser/upload-objects/uploadSidebar.scss new file mode 100644 index 000000000..31fdfba83 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/uploadSidebar.scss @@ -0,0 +1,3 @@ +.odf-upload-sidebar__drawer-head .pf-v5-c-drawer__body { + height: unset; +} diff --git a/packages/odf/components/s3-browser/upload-objects/utils.ts b/packages/odf/components/s3-browser/upload-objects/utils.ts new file mode 100644 index 000000000..cb3489a72 --- /dev/null +++ b/packages/odf/components/s3-browser/upload-objects/utils.ts @@ -0,0 +1,115 @@ +import { + humanizeBinaryBytes, + humanizeDecimalBytesPerSec, + humanizeMinutes, + humanizeSeconds, +} from '@odf/shared/utils'; +import * as _ from 'lodash-es'; +import { UploadProgress, UploadStatus } from './types'; + +export const getCompletedAndTotalUploadCount = (objects: UploadProgress) => { + const totalObjects = Object.keys(objects).length; + const totalUploaded = Object.values(objects).filter( + (obj) => obj.uploadState === UploadStatus.UPLOAD_COMPLETE + ).length; + return [totalUploaded, totalObjects]; +}; + +export const getCompletedTotalFailedCount = ( + uploadProgress: UploadProgress +) => { + const progressItems = Object.values(uploadProgress); + const [completedUploads, failedUploads] = progressItems.reduce( + (acc, curr) => { + const isCompleted = curr.uploadState === UploadStatus.UPLOAD_COMPLETE; + if (isCompleted) { + acc = [acc[0] + 1, acc[1]]; + return acc; + } + const isFailed = curr.uploadState === UploadStatus.UPLOAD_FAILED; + if (isFailed) { + acc = [acc[0], acc[1] + 1]; + return acc; + } + return acc; + }, + [0, 0] + ); + return [completedUploads, progressItems.length, failedUploads]; +}; + +export const getFailedFiles = (objects: UploadProgress) => + Object.values(objects).filter( + (obj) => obj.uploadState === UploadStatus.UPLOAD_FAILED + ).length; + +export const getUploadSpeed = (objects: UploadProgress): string => { + if (_.isEmpty(objects)) return ''; + const files = Object.values(objects); + const uploadingFiles = files.filter( + (item) => + item.uploadState === UploadStatus.UPLOAD_START || + item.uploadState === UploadStatus.INIT_STATE + ); + const totalTimes = uploadingFiles.map((file) => Date.now() - file?.startTime); + const totalUploaded = uploadingFiles.map((file) => file.loaded ?? 0); + const uploadSpeeds = totalUploaded.map((size, i) => size / totalTimes[i]); + const speed = _.sum(uploadSpeeds) / uploadSpeeds.length; + return humanizeDecimalBytesPerSec(speed * 1000).string; +}; + +export const getTotalRemainingFilesAndSize = ( + objects: UploadProgress +): string => { + if (_.isEmpty(objects)) return ''; + const files = Object.values(objects); + const uploadingFiles = files.filter( + (item) => + item.uploadState === UploadStatus.UPLOAD_START || + item.uploadState === UploadStatus.INIT_STATE + ); + const filesCount = uploadingFiles.length; + const filesSize = uploadingFiles.reduce( + (acc, curr) => (acc += curr.total - curr.loaded), + 0 + ); + return `${filesCount} files (${humanizeBinaryBytes(filesSize).string})`; +}; + +export const getTotalTimeRemaining = (objects: UploadProgress): string => { + const files = Object.values(objects); + const uploadingFiles = files.filter( + (item) => + item.uploadState === UploadStatus.UPLOAD_START || + item.uploadState === UploadStatus.INIT_STATE + ); + const totalTimes = uploadingFiles.map((file) => Date.now() - file?.startTime); + const totalUploaded = uploadingFiles.map((file) => file.loaded ?? 0); + const uploadSpeeds = totalUploaded.map((size, i) => size / totalTimes[i]); + const speed = _.sum(uploadSpeeds) / uploadSpeeds.length; + const totalRemainingBytes = uploadingFiles.reduce( + (acc, curr) => (acc += curr.total - curr.loaded), + 0 + ); + const timeRemaining = totalRemainingBytes / speed; + if (timeRemaining < 1000) { + return humanizeSeconds(timeRemaining, 'ms').string; + } + return humanizeMinutes(timeRemaining / 1000).string; +}; + +export const getTotalTimeElapsed = ( + objects: UploadProgress, + completionTime: number +): string => { + const earliestTime = Object.values(objects).reduce((acc, curr) => { + if (curr.startTime < acc) { + return curr.startTime; + } else return acc; + }, Number.MAX_SAFE_INTEGER); + const fromNow = completionTime - earliestTime; + const minutes = humanizeMinutes(fromNow / 1000); + if (minutes.value > 1) { + return minutes.string; + } else return humanizeSeconds(fromNow / 1000, 'seconds').string; +}; diff --git a/packages/odf/components/storage-consumers/list-filter.ts b/packages/odf/components/storage-consumers/list-filter.ts index 320cd6bc8..5398c0664 100644 --- a/packages/odf/components/storage-consumers/list-filter.ts +++ b/packages/odf/components/storage-consumers/list-filter.ts @@ -7,8 +7,8 @@ const heartbeatPhases = ['Connected', 'Disconnected']; export const getHeartbeatPhase = (client: StorageConsumerKind): string => { const heartbeat = client?.status?.lastHeartbeat; - const timeElasped = getTimeDifferenceInSeconds(heartbeat); - if (timeElasped <= 300) { + const timeElapsed = getTimeDifferenceInSeconds(heartbeat); + if (timeElapsed <= 300) { return heartbeatPhases[0]; } return heartbeatPhases[1]; diff --git a/packages/odf/modals/s3-browser/abort-uploads/AbortUploadsModal.tsx b/packages/odf/modals/s3-browser/abort-uploads/AbortUploadsModal.tsx new file mode 100644 index 000000000..f64767fd2 --- /dev/null +++ b/packages/odf/modals/s3-browser/abort-uploads/AbortUploadsModal.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { ButtonBar, useCustomTranslation } from '@odf/shared'; +import { Trans } from 'react-i18next'; +import { + Button, + ButtonVariant, + Modal, + ModalVariant, +} from '@patternfly/react-core'; + +type AbortUploadsModalProps = { + abortAll: () => Promise; +}; + +export const AbortUploadsModal: React.FC = ({ + abortAll, +}) => { + const { t } = useCustomTranslation(); + const [isModalOpen, setModalOpen] = React.useState(false); + + return ( + <> + + + + + + + , + ]} + > + + Are you sure you want to cancel the ongoing uploads? Any files + currently being uploaded will be stopped, and partially uploaded files + will not be saved. + + + + ); +}; diff --git a/packages/shared/src/s3/commands.ts b/packages/shared/src/s3/commands.ts index 862c97688..17e6fea6e 100644 --- a/packages/shared/src/s3/commands.ts +++ b/packages/shared/src/s3/commands.ts @@ -12,6 +12,7 @@ import { GetBucketAclCommand, GetBucketPolicyCommand, } from '@aws-sdk/client-s3'; +import { Upload } from '@aws-sdk/lib-storage'; import { getSignedUrl } from '@aws-sdk/s3-request-presigner'; import { CreateBucket, @@ -28,11 +29,9 @@ import { GetBucketPolicy, } from './types'; -export class S3Commands { - private s3Client: S3Client; - +export class S3Commands extends S3Client { constructor(endpoint: string, accessKeyId: string, secretAccessKey: string) { - this.s3Client = new S3Client({ + super({ // "region" is a required parameter for the SDK, using "none" as a workaround region: 'none', endpoint, @@ -46,39 +45,53 @@ export class S3Commands { // Bucket command members (alphabetical order) createBucket: CreateBucket = (input) => - this.s3Client.send(new CreateBucketCommand(input)); + this.send(new CreateBucketCommand(input)); getBucketAcl: GetBucketAcl = (input) => - this.s3Client.send(new GetBucketAclCommand(input)); + this.send(new GetBucketAclCommand(input)); getBucketEncryption: GetBucketEncryption = (input) => - this.s3Client.send(new GetBucketEncryptionCommand(input)); + this.send(new GetBucketEncryptionCommand(input)); getBucketPolicy: GetBucketPolicy = (input) => - this.s3Client.send(new GetBucketPolicyCommand(input)); + this.send(new GetBucketPolicyCommand(input)); getBucketTagging: GetBucketTagging = (input) => - this.s3Client.send(new GetBucketTaggingCommand(input)); + this.send(new GetBucketTaggingCommand(input)); getBucketVersioning: GetBucketVersioning = (input) => - this.s3Client.send(new GetBucketVersioningCommand(input)); + this.send(new GetBucketVersioningCommand(input)); listBuckets: ListBuckets = (input) => - this.s3Client.send(new ListBucketsCommand(input)); + this.send(new ListBucketsCommand(input)); putBucketTags: PutBucketTags = (input) => - this.s3Client.send(new PutBucketTaggingCommand(input)); - - // Object command members (alphabetical order) - deleteObjects: DeleteObjects = (input) => - this.s3Client.send(new DeleteObjectsCommand(input)); + this.send(new PutBucketTaggingCommand(input)); - getObject: GetObject = (input) => - this.s3Client.send(new GetObjectCommand(input)); + // Object command members + listObjects: ListObjectsV2 = (input) => + this.send(new ListObjectsV2Command(input)); getSignedUrl: GetSignedUrl = (input, expiresIn) => - getSignedUrl(this.s3Client, new GetObjectCommand(input), { expiresIn }); + getSignedUrl(this, new GetObjectCommand(input), { expiresIn }); - listObjects: ListObjectsV2 = (input) => - this.s3Client.send(new ListObjectsV2Command(input)); + getObject: GetObject = (input) => this.send(new GetObjectCommand(input)); + + deleteObjects: DeleteObjects = (input) => + this.send(new DeleteObjectsCommand(input)); + + getUploader = (file: File, key: string, bucketName: string): Upload => { + const uploader = new Upload({ + client: this, + params: { + Bucket: bucketName, + Key: key, + Body: file, + ...(file.type ? { ContentType: file.type } : {}), + }, + partSize: 5 * 1024 * 1024, + queueSize: 4, + }); + return uploader; + }; } diff --git a/packages/shared/src/status/Status.tsx b/packages/shared/src/status/Status.tsx index 78bcb5373..459cc5d18 100644 --- a/packages/shared/src/status/Status.tsx +++ b/packages/shared/src/status/Status.tsx @@ -55,6 +55,7 @@ export const Status: React.FC = ({ case 'ContainerCreating': case 'UpgradePending': + case 'Uploading': return ; case 'In Progress': diff --git a/tsconfig.json b/tsconfig.json index dee8af211..a6eb1c399 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,7 +3,7 @@ "baseUrl": ".", "module": "esnext", "moduleResolution": "node", - "target": "es2017", + "target": "es2020", "jsx": "react", "allowJs": true, "checkJs": true, diff --git a/yarn.lock b/yarn.lock index 363e098b2..48ff8c599 100644 --- a/yarn.lock +++ b/yarn.lock @@ -399,6 +399,19 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@aws-sdk/lib-storage@3.501.0": + version "3.501.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/lib-storage/-/lib-storage-3.501.0.tgz#733bc376ccddb371fca30a9ee9ee9a7a81603694" + integrity sha512-XZREd1O0S8AjM3RS85T2QCVJzXk+BSAGNOFvGP8t2al2Ti35O4+AvSHT75rmOGAZAsthtL2o9bt0h1VFnaIP+g== + dependencies: + "@smithy/abort-controller" "^2.1.1" + "@smithy/middleware-endpoint" "^2.4.1" + "@smithy/smithy-client" "^2.3.1" + buffer "5.6.0" + events "3.3.0" + stream-browserify "3.0.0" + tslib "^2.5.0" + "@aws-sdk/middleware-bucket-endpoint@3.667.0": version "3.667.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.667.0.tgz#bd0a0a24f71d3709debf6e48f4e503547398e7eb" @@ -1136,6 +1149,13 @@ core-js-pure "^3.15.0" regenerator-runtime "^0.13.4" +"@babel/runtime@^7.0.0": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" + integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1": version "7.14.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.14.5.tgz#665450911c6031af38f81db530f387ec04cd9a98" @@ -2072,6 +2092,14 @@ dependencies: "@sinonjs/commons" "^1.7.0" +"@smithy/abort-controller@^2.1.1", "@smithy/abort-controller@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-2.2.0.tgz#18983401a5e2154b5c94057730024a7d14cbcd35" + integrity sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/abort-controller@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@smithy/abort-controller/-/abort-controller-3.1.1.tgz#291210611ff6afecfc198d0ca72d5771d8461d16" @@ -2186,6 +2214,17 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/fetch-http-handler@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-2.5.0.tgz#0b8e1562807fdf91fe7dd5cde620d7a03ddc10ac" + integrity sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw== + dependencies: + "@smithy/protocol-http" "^3.3.0" + "@smithy/querystring-builder" "^2.2.0" + "@smithy/types" "^2.12.0" + "@smithy/util-base64" "^2.3.0" + tslib "^2.6.2" + "@smithy/fetch-http-handler@^3.2.4": version "3.2.4" resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-3.2.4.tgz#c754de7e0ff2541b73ac9ba7cc955940114b3d62" @@ -2277,6 +2316,19 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/middleware-endpoint@^2.4.1", "@smithy/middleware-endpoint@^2.5.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz#1333c58304aff4d843e8ef4b85c8cb88975dd5ad" + integrity sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ== + dependencies: + "@smithy/middleware-serde" "^2.3.0" + "@smithy/node-config-provider" "^2.3.0" + "@smithy/shared-ini-file-loader" "^2.4.0" + "@smithy/types" "^2.12.0" + "@smithy/url-parser" "^2.2.0" + "@smithy/util-middleware" "^2.2.0" + tslib "^2.6.2" + "@smithy/middleware-endpoint@^3.0.5", "@smithy/middleware-endpoint@^3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-3.1.0.tgz#9b8a496d87a68ec43f3f1a0139868d6765a88119" @@ -2318,6 +2370,14 @@ tslib "^2.6.2" uuid "^9.0.1" +"@smithy/middleware-serde@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-2.3.0.tgz#a7615ba646a88b6f695f2d55de13d8158181dd13" + integrity sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/middleware-serde@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-3.0.3.tgz#74d974460f74d99f38c861e6862984543a880a66" @@ -2334,6 +2394,14 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/middleware-stack@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-2.2.0.tgz#3fb49eae6313f16f6f30fdaf28e11a7321f34d9f" + integrity sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/middleware-stack@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-3.0.3.tgz#91845c7e61e6f137fa912b623b6def719a4f6ce7" @@ -2350,6 +2418,16 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/node-config-provider@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-2.3.0.tgz#9fac0c94a14c5b5b8b8fa37f20c310a844ab9922" + integrity sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg== + dependencies: + "@smithy/property-provider" "^2.2.0" + "@smithy/shared-ini-file-loader" "^2.4.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/node-config-provider@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-3.1.4.tgz#05647bed666aa8036a1ad72323c1942e5d421be1" @@ -2370,6 +2448,17 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/node-http-handler@^2.5.0": + version "2.5.0" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-2.5.0.tgz#7b5e0565dd23d340380489bd5fe4316d2bed32de" + integrity sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA== + dependencies: + "@smithy/abort-controller" "^2.2.0" + "@smithy/protocol-http" "^3.3.0" + "@smithy/querystring-builder" "^2.2.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/node-http-handler@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-3.1.4.tgz#be4195e45639e690d522cd5f11513ea822ff9d5f" @@ -2392,6 +2481,14 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/property-provider@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-2.2.0.tgz#37e3525a3fa3e11749f86a4f89f0fd7765a6edb0" + integrity sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/property-provider@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-3.1.3.tgz#afd57ea82a3f6c79fbda95e3cb85c0ee0a79f39a" @@ -2408,6 +2505,14 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/protocol-http@^3.3.0": + version "3.3.0" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-3.3.0.tgz#a37df7b4bb4960cdda560ce49acfd64c455e4090" + integrity sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/protocol-http@^4.0.3", "@smithy/protocol-http@^4.1.0": version "4.1.0" resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-4.1.0.tgz#23519d8f45bf4f33960ea5415847bc2b620a010b" @@ -2424,6 +2529,15 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/querystring-builder@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-2.2.0.tgz#22937e19fcd0aaa1a3e614ef8cb6f8e86756a4ef" + integrity sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A== + dependencies: + "@smithy/types" "^2.12.0" + "@smithy/util-uri-escape" "^2.2.0" + tslib "^2.6.2" + "@smithy/querystring-builder@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-3.0.3.tgz#6b0e566f885bb84938d077c69e8f8555f686af13" @@ -2442,6 +2556,14 @@ "@smithy/util-uri-escape" "^3.0.0" tslib "^2.6.2" +"@smithy/querystring-parser@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-2.2.0.tgz#24a5633f4b3806ff2888d4c2f4169e105fdffd79" + integrity sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/querystring-parser@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-3.0.3.tgz#272a6b83f88dfcbbec8283d72a6bde850cc00091" @@ -2465,6 +2587,14 @@ dependencies: "@smithy/types" "^3.5.0" +"@smithy/shared-ini-file-loader@^2.4.0": + version "2.4.0" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.4.0.tgz#1636d6eb9bff41e36ac9c60364a37fd2ffcb9947" + integrity sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/shared-ini-file-loader@^3.1.4": version "3.1.4" resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-3.1.4.tgz#7dceaf5a5307a2ee347ace8aba17312a1a3ede15" @@ -2508,6 +2638,18 @@ "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" +"@smithy/smithy-client@^2.3.1": + version "2.5.1" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.5.1.tgz#0fd2efff09dc65500d260e590f7541f8a387eae3" + integrity sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ== + dependencies: + "@smithy/middleware-endpoint" "^2.5.1" + "@smithy/middleware-stack" "^2.2.0" + "@smithy/protocol-http" "^3.3.0" + "@smithy/types" "^2.12.0" + "@smithy/util-stream" "^2.2.0" + tslib "^2.6.2" + "@smithy/smithy-client@^3.1.7": version "3.2.0" resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-3.2.0.tgz#6db94024e4bdaefa079ac68dbea23dafbea230c8" @@ -2532,6 +2674,13 @@ "@smithy/util-stream" "^3.1.9" tslib "^2.6.2" +"@smithy/types@^2.12.0": + version "2.12.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-2.12.0.tgz#c44845f8ba07e5e8c88eda5aed7e6a0c462da041" + integrity sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw== + dependencies: + tslib "^2.6.2" + "@smithy/types@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.3.0.tgz#fae037c733d09bc758946a01a3de0ef6e210b16b" @@ -2546,6 +2695,15 @@ dependencies: tslib "^2.6.2" +"@smithy/url-parser@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-2.2.0.tgz#6fcda6116391a4f61fef5580eb540e128359b3c0" + integrity sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ== + dependencies: + "@smithy/querystring-parser" "^2.2.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/url-parser@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-3.0.3.tgz#e8a060d9810b24b1870385fc2b02485b8a6c5955" @@ -2564,6 +2722,15 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/util-base64@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-2.3.0.tgz#312dbb4d73fb94249c7261aee52de4195c2dd8e2" + integrity sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw== + dependencies: + "@smithy/util-buffer-from" "^2.2.0" + "@smithy/util-utf8" "^2.3.0" + tslib "^2.6.2" + "@smithy/util-base64@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-3.0.0.tgz#f7a9a82adf34e27a72d0719395713edf0e493017" @@ -2643,6 +2810,13 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/util-hex-encoding@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-2.2.0.tgz#87edb7c88c2f422cfca4bb21f1394ae9602c5085" + integrity sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ== + dependencies: + tslib "^2.6.2" + "@smithy/util-hex-encoding@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-3.0.0.tgz#32938b33d5bf2a15796cd3f178a55b4155c535e6" @@ -2650,6 +2824,14 @@ dependencies: tslib "^2.6.2" +"@smithy/util-middleware@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-2.2.0.tgz#80cfad40f6cca9ffe42a5899b5cb6abd53a50006" + integrity sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw== + dependencies: + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@smithy/util-middleware@^3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-3.0.3.tgz#07bf9602682f5a6c55bc2f0384303f85fc68c87e" @@ -2675,6 +2857,20 @@ "@smithy/types" "^3.5.0" tslib "^2.6.2" +"@smithy/util-stream@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-2.2.0.tgz#b1279e417992a0f74afa78d7501658f174ed7370" + integrity sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA== + dependencies: + "@smithy/fetch-http-handler" "^2.5.0" + "@smithy/node-http-handler" "^2.5.0" + "@smithy/types" "^2.12.0" + "@smithy/util-base64" "^2.3.0" + "@smithy/util-buffer-from" "^2.2.0" + "@smithy/util-hex-encoding" "^2.2.0" + "@smithy/util-utf8" "^2.3.0" + tslib "^2.6.2" + "@smithy/util-stream@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-3.1.3.tgz#699ee2397cc1d474e46d2034039d5263812dca64" @@ -2703,6 +2899,13 @@ "@smithy/util-utf8" "^3.0.0" tslib "^2.6.2" +"@smithy/util-uri-escape@^2.2.0": + version "2.2.0" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-2.2.0.tgz#56f5764051a33b67bc93fdd2a869f971b0635406" + integrity sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA== + dependencies: + tslib "^2.6.2" + "@smithy/util-uri-escape@^3.0.0": version "3.0.0" resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-3.0.0.tgz#e43358a78bf45d50bb736770077f0f09195b6f54" @@ -2710,7 +2913,7 @@ dependencies: tslib "^2.6.2" -"@smithy/util-utf8@^2.0.0": +"@smithy/util-utf8@^2.0.0", "@smithy/util-utf8@^2.3.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" integrity sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A== @@ -3454,6 +3657,13 @@ "@types/history" "^4.7.11" "@types/react" "*" +"@types/react-window@^1.8.8": + version "1.8.8" + resolved "https://registry.yarnpkg.com/@types/react-window/-/react-window-1.8.8.tgz#c20645414d142364fbe735818e1c1e0a145696e3" + integrity sha512-8Ls660bHR1AUA2kuRvVG9D/4XpRC6wjAaPT9dil7Ckc76eP9TKWZwwmgfq8Q1LANX3QNDnoU4Zp48A3w+zK69Q== + dependencies: + "@types/react" "*" + "@types/react@*": version "17.0.11" resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.11.tgz#67fcd0ddbf5a0b083a0f94e926c7d63f3b836451" @@ -4385,7 +4595,7 @@ balanced-match@^2.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-2.0.0.tgz#dc70f920d78db8b858535795867bf48f820633d9" integrity sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA== -base64-js@^1.3.1: +base64-js@^1.0.2, base64-js@^1.3.1: version "1.5.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== @@ -4571,6 +4781,14 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer@5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.6.0.tgz#a31749dc7d81d84db08abf937b6b8c4033f62786" + integrity sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + buffer@^5.6.0: version "5.7.1" resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.7.1.tgz#ba62e7c13133053582197160851a8f648e99eed0" @@ -6846,7 +7064,7 @@ eventemitter3@^4.0.0: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -events@^3.2.0: +events@3.3.0, events@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== @@ -8066,7 +8284,7 @@ identity-obj-proxy@^3.0.0: dependencies: harmony-reflect "^1.4.6" -ieee754@^1.1.13, ieee754@^1.2.1: +ieee754@^1.1.13, ieee754@^1.1.4, ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -8145,7 +8363,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.3, inherits@~2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -9714,6 +9932,11 @@ memfs@^3.4.1, memfs@^3.4.3: dependencies: fs-monkey "^1.0.4" +"memoize-one@>=3.1.1 <6": + version "5.2.1" + resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" + integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== + meow@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/meow/-/meow-9.0.0.tgz#cd9510bc5cac9dee7d03c73ee1f9ad959f4ea364" @@ -11244,6 +11467,15 @@ react-dropzone@^14.2.3: file-selector "^0.6.0" prop-types "^15.8.1" +react-dropzone@^14.2.9: + version "14.2.9" + resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.9.tgz#193a33f9035e29fc91abf24e50de5d66cfa7c8c0" + integrity sha512-jRZsMC7h48WONsOLHcmhyn3cRWJoIPQjPApvt/sJVfnYaB3Qltn025AoRTTJaj4WdmmgmLl6tUQg1s0wOhpodQ== + dependencies: + attr-accept "^2.2.2" + file-selector "^0.6.0" + prop-types "^15.8.1" + react-error-boundary@^3.1.0: version "3.1.4" resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-3.1.4.tgz#255db92b23197108757a888b01e5b729919abde0" @@ -11395,6 +11627,19 @@ react-tagsinput@^3.19.0: resolved "https://registry.yarnpkg.com/react-tagsinput/-/react-tagsinput-3.19.0.tgz#6e3b45595f2d295d4657bf194491988f948caabf" integrity sha512-ni+/qnZrYrvLg83LtTFHErKy1KQHL0fi0Y6C5jgC1dNUePE9cS/OlQ4XH6JRSjv9GGoeVE0R/ujSBaS1uzCRYQ== +react-virtualized-auto-sizer@^1.0.24: + version "1.0.24" + resolved "https://registry.yarnpkg.com/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.24.tgz#3ebdc92f4b05ad65693b3cc8e7d8dd54924c0227" + integrity sha512-3kCn7N9NEb3FlvJrSHWGQ4iVl+ydQObq2fHMn12i5wbtm74zHOPhz/i64OL3c1S1vi9i2GXtZqNqUJTQ+BnNfg== + +react-window@^1.8.10: + version "1.8.10" + resolved "https://registry.yarnpkg.com/react-window/-/react-window-1.8.10.tgz#9e6b08548316814b443f7002b1cf8fd3a1bdde03" + integrity sha512-Y0Cx+dnU6NLa5/EvoHukUD0BklJ8qITCtVEPY1C/nL8wwoZ0b5aEw8Ff1dOVHw7fCzMt55XfJDd8S8W8LCaUCg== + dependencies: + "@babel/runtime" "^7.0.0" + memoize-one ">=3.1.1 <6" + react@^17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" @@ -11461,6 +11706,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.5, readable string_decoder "~1.1.1" util-deprecate "~1.0.1" +readable-stream@^3.5.0: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -12262,6 +12516,14 @@ statuses@2.0.1: resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= +stream-browserify@3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-3.0.0.tgz#22b0a2850cdf6503e73085da1fc7b7d0c2122f2f" + integrity sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA== + dependencies: + inherits "~2.0.4" + readable-stream "^3.5.0" + stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d"