From 1258e09f252c81295fbaee0da58194886424f0a6 Mon Sep 17 00:00:00 2001 From: Anton Arnautov <43254280+arnautov-anton@users.noreply.github.com> Date: Fri, 8 Sep 2023 15:57:46 +0200 Subject: [PATCH] perf(package-size): remove react-file-utils package (#2088) * Move react-file-utils files to chat * Update import paths * Remove react-file-utils from packages * Refactor - reuse UploadButton, move icons --- package.json | 1 - rollup.config.js | 1 - src/components/Attachment/FileAttachment.tsx | 2 +- .../MessageInput/AttachmentPreviewList.tsx | 2 +- .../MessageInput/DropzoneProvider.tsx | 2 +- .../MessageInput/EditMessageForm.tsx | 2 +- .../MessageInput/MessageInputFlat.tsx | 2 +- .../MessageInput/MessageInputSmall.tsx | 2 +- .../MessageInput/UploadsPreview.tsx | 2 +- .../MessageInput/hooks/useAttachments.ts | 2 +- .../hooks/useMessageInputState.ts | 2 +- .../MessageInput/hooks/usePasteHandler.ts | 6 +- src/components/MessageInput/hooks/utils.ts | 2 +- .../ReactFileUtilities/FileIcon/FileIcon.tsx | 50 ++ .../FileIcon/FileIconSet/v1.tsx | 185 ++++++ .../FileIcon/FileIconSet/v2.tsx | 576 ++++++++++++++++++ .../ReactFileUtilities/FileIcon/iconMap.ts | 169 +++++ .../ReactFileUtilities/FileIcon/index.ts | 1 + .../ReactFileUtilities/FileIcon/mimeTypes.ts | 163 +++++ .../ReactFileUtilities/FilePreviewer.tsx | 68 +++ .../ReactFileUtilities/FileUploadButton.tsx | 46 ++ .../ReactFileUtilities/IconButton.tsx | 20 + .../ReactFileUtilities/ImageDropzone.tsx | 87 +++ .../ReactFileUtilities/ImagePreviewer.tsx | 80 +++ .../ReactFileUtilities/ImageUploadButton.tsx | 37 ++ .../ReactFileUtilities/LoadingIndicator.tsx | 27 + .../ReactFileUtilities/Thumbnail.tsx | 39 ++ .../ThumbnailPlaceholder.tsx | 21 + .../ReactFileUtilities/UploadButton.tsx | 18 + .../icons/AttachmentIcon.tsx | 11 + .../ReactFileUtilities/icons/CloseIcon.tsx | 27 + .../icons/FilePlaceholderIcon.tsx | 20 + .../ReactFileUtilities/icons/PictureIcon.tsx | 13 + .../ReactFileUtilities/icons/RetryIcon.tsx | 11 + .../ReactFileUtilities/icons/index.ts | 5 + src/components/ReactFileUtilities/index.ts | 13 + src/components/ReactFileUtilities/types.ts | 34 ++ src/components/ReactFileUtilities/utils.ts | 94 +++ yarn.lock | 25 +- 39 files changed, 1831 insertions(+), 37 deletions(-) create mode 100644 src/components/ReactFileUtilities/FileIcon/FileIcon.tsx create mode 100644 src/components/ReactFileUtilities/FileIcon/FileIconSet/v1.tsx create mode 100644 src/components/ReactFileUtilities/FileIcon/FileIconSet/v2.tsx create mode 100644 src/components/ReactFileUtilities/FileIcon/iconMap.ts create mode 100644 src/components/ReactFileUtilities/FileIcon/index.ts create mode 100644 src/components/ReactFileUtilities/FileIcon/mimeTypes.ts create mode 100644 src/components/ReactFileUtilities/FilePreviewer.tsx create mode 100644 src/components/ReactFileUtilities/FileUploadButton.tsx create mode 100644 src/components/ReactFileUtilities/IconButton.tsx create mode 100644 src/components/ReactFileUtilities/ImageDropzone.tsx create mode 100644 src/components/ReactFileUtilities/ImagePreviewer.tsx create mode 100644 src/components/ReactFileUtilities/ImageUploadButton.tsx create mode 100644 src/components/ReactFileUtilities/LoadingIndicator.tsx create mode 100644 src/components/ReactFileUtilities/Thumbnail.tsx create mode 100644 src/components/ReactFileUtilities/ThumbnailPlaceholder.tsx create mode 100644 src/components/ReactFileUtilities/UploadButton.tsx create mode 100644 src/components/ReactFileUtilities/icons/AttachmentIcon.tsx create mode 100644 src/components/ReactFileUtilities/icons/CloseIcon.tsx create mode 100644 src/components/ReactFileUtilities/icons/FilePlaceholderIcon.tsx create mode 100644 src/components/ReactFileUtilities/icons/PictureIcon.tsx create mode 100644 src/components/ReactFileUtilities/icons/RetryIcon.tsx create mode 100644 src/components/ReactFileUtilities/icons/index.ts create mode 100644 src/components/ReactFileUtilities/index.ts create mode 100644 src/components/ReactFileUtilities/types.ts create mode 100644 src/components/ReactFileUtilities/utils.ts diff --git a/package.json b/package.json index e82112c59..d91d4933d 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "prop-types": "^15.7.2", "react-dropzone": "^14.2.3", "react-fast-compare": "^3.2.2", - "react-file-utils": "^1.2.0", "react-image-gallery": "1.2.12", "react-is": "^18.1.0", "react-markdown": "^8.0.7", diff --git a/rollup.config.js b/rollup.config.js index 4f2671e83..824dfcd74 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -49,7 +49,6 @@ const externalDependencies = [ 'pretty-bytes', 'prop-types', 'react-fast-compare', - /react-file-utils/, 'react-images', 'react-image-gallery', 'react-is', diff --git a/src/components/Attachment/FileAttachment.tsx b/src/components/Attachment/FileAttachment.tsx index e263c2c23..d3e30140a 100644 --- a/src/components/Attachment/FileAttachment.tsx +++ b/src/components/Attachment/FileAttachment.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { FileIcon } from 'react-file-utils'; +import { FileIcon } from '../ReactFileUtilities'; import type { Attachment } from 'stream-chat'; import { DownloadButton } from './DownloadButton'; diff --git a/src/components/MessageInput/AttachmentPreviewList.tsx b/src/components/MessageInput/AttachmentPreviewList.tsx index ebad12482..c96a0cfb9 100644 --- a/src/components/MessageInput/AttachmentPreviewList.tsx +++ b/src/components/MessageInput/AttachmentPreviewList.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import { FileIcon } from 'react-file-utils'; +import { FileIcon } from '../ReactFileUtilities'; import { useMessageInputContext } from '../../context/MessageInputContext'; import { useFileState } from './hooks/useFileState'; diff --git a/src/components/MessageInput/DropzoneProvider.tsx b/src/components/MessageInput/DropzoneProvider.tsx index 5fea494bc..236cdf44e 100644 --- a/src/components/MessageInput/DropzoneProvider.tsx +++ b/src/components/MessageInput/DropzoneProvider.tsx @@ -1,5 +1,5 @@ import React, { PropsWithChildren } from 'react'; -import { ImageDropzone } from 'react-file-utils'; +import { ImageDropzone } from '../ReactFileUtilities'; import { useCooldownTimer } from './hooks/useCooldownTimer'; import { useCreateMessageInputContext } from './hooks/useCreateMessageInputContext'; diff --git a/src/components/MessageInput/EditMessageForm.tsx b/src/components/MessageInput/EditMessageForm.tsx index 6b538daff..cb23fd929 100644 --- a/src/components/MessageInput/EditMessageForm.tsx +++ b/src/components/MessageInput/EditMessageForm.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { FileUploadButton, ImageDropzone } from 'react-file-utils'; +import { FileUploadButton, ImageDropzone } from '../ReactFileUtilities'; import { EmojiPicker } from './EmojiPicker'; import { diff --git a/src/components/MessageInput/MessageInputFlat.tsx b/src/components/MessageInput/MessageInputFlat.tsx index b05e2c33f..443fc6fce 100644 --- a/src/components/MessageInput/MessageInputFlat.tsx +++ b/src/components/MessageInput/MessageInputFlat.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useMemo, useState } from 'react'; -import { FileUploadButton, ImageDropzone, UploadButton } from 'react-file-utils'; +import { FileUploadButton, ImageDropzone, UploadButton } from '../ReactFileUtilities'; import type { Event } from 'stream-chat'; import clsx from 'clsx'; import { usePopper } from 'react-popper'; diff --git a/src/components/MessageInput/MessageInputSmall.tsx b/src/components/MessageInput/MessageInputSmall.tsx index 67df91633..0e88024db 100644 --- a/src/components/MessageInput/MessageInputSmall.tsx +++ b/src/components/MessageInput/MessageInputSmall.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import { FileUploadButton, ImageDropzone } from 'react-file-utils'; +import { FileUploadButton, ImageDropzone } from '../ReactFileUtilities'; import type { Event } from 'stream-chat'; import { EmojiPicker } from './EmojiPicker'; diff --git a/src/components/MessageInput/UploadsPreview.tsx b/src/components/MessageInput/UploadsPreview.tsx index 008937c63..86fce5acb 100644 --- a/src/components/MessageInput/UploadsPreview.tsx +++ b/src/components/MessageInput/UploadsPreview.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { FilePreviewer, ImagePreviewer } from 'react-file-utils'; +import { FilePreviewer, ImagePreviewer } from '../ReactFileUtilities'; import { useChannelStateContext } from '../../context/ChannelStateContext'; import { useMessageInputContext } from '../../context/MessageInputContext'; diff --git a/src/components/MessageInput/hooks/useAttachments.ts b/src/components/MessageInput/hooks/useAttachments.ts index f7dae59cd..f4db818ba 100644 --- a/src/components/MessageInput/hooks/useAttachments.ts +++ b/src/components/MessageInput/hooks/useAttachments.ts @@ -6,7 +6,7 @@ import { useFileUploads } from './useFileUploads'; import { useChannelStateContext } from '../../../context/ChannelStateContext'; -import type { FileLike } from 'react-file-utils'; +import type { FileLike } from '../../ReactFileUtilities'; import type { MessageInputProps } from '../MessageInput'; import type { MessageInputReducerAction, MessageInputState } from './useMessageInputState'; diff --git a/src/components/MessageInput/hooks/useMessageInputState.ts b/src/components/MessageInput/hooks/useMessageInputState.ts index c2e92033c..3508dfa1d 100644 --- a/src/components/MessageInput/hooks/useMessageInputState.ts +++ b/src/components/MessageInput/hooks/useMessageInputState.ts @@ -11,7 +11,7 @@ import { useSubmitHandler } from './useSubmitHandler'; import { usePasteHandler } from './usePasteHandler'; import type { EmojiData, NimbleEmojiIndex } from 'emoji-mart'; -import type { FileLike } from 'react-file-utils'; +import type { FileLike } from '../../ReactFileUtilities'; import type { Attachment, Message, UserResponse } from 'stream-chat'; import type { MessageInputProps } from '../MessageInput'; diff --git a/src/components/MessageInput/hooks/usePasteHandler.ts b/src/components/MessageInput/hooks/usePasteHandler.ts index b94f4d7ec..4c6fded2e 100644 --- a/src/components/MessageInput/hooks/usePasteHandler.ts +++ b/src/components/MessageInput/hooks/usePasteHandler.ts @@ -1,5 +1,9 @@ import { useCallback } from 'react'; -import { dataTransferItemsHaveFiles, dataTransferItemsToFiles, FileLike } from 'react-file-utils'; +import { + dataTransferItemsHaveFiles, + dataTransferItemsToFiles, + FileLike, +} from '../../ReactFileUtilities'; export const usePasteHandler = ( uploadNewFiles: (files: FileList | FileLike[] | File[]) => void, diff --git a/src/components/MessageInput/hooks/utils.ts b/src/components/MessageInput/hooks/utils.ts index f350b4248..ac340c8aa 100644 --- a/src/components/MessageInput/hooks/utils.ts +++ b/src/components/MessageInput/hooks/utils.ts @@ -1,4 +1,4 @@ -import type { ImageUpload } from 'react-file-utils'; +import type { ImageUpload } from '../../ReactFileUtilities'; import type { AppSettingsAPIResponse, FileUploadConfig, UserResponse } from 'stream-chat'; import type { ChannelActionContextValue } from '../../../context/ChannelActionContext'; diff --git a/src/components/ReactFileUtilities/FileIcon/FileIcon.tsx b/src/components/ReactFileUtilities/FileIcon/FileIcon.tsx new file mode 100644 index 000000000..6d47ce894 --- /dev/null +++ b/src/components/ReactFileUtilities/FileIcon/FileIcon.tsx @@ -0,0 +1,50 @@ +import React from 'react'; + +import { iconMap, IconType, IconVersion } from './iconMap'; + +export type FileIconProps = { + big?: boolean; + className?: string; + filename?: string; + mimeType?: string; + size?: number; // big icon on sent attachment + sizeSmall?: number; // small icon on file upload preview + type?: IconType; + version?: IconVersion; +}; + +export function mimeTypeToIcon( + type: IconType = 'standard', + version: IconVersion = '1', + mimeType?: string, +) { + const theMap = iconMap[version]?.[type] || iconMap[version]['standard']; + + if (!mimeType) return theMap.fallback; + + const icon = theMap[mimeType]; + if (icon) return icon; + + if (mimeType.startsWith('audio/')) return theMap['audio/']; + if (mimeType.startsWith('video/')) return theMap['video/']; + if (mimeType.startsWith('image/')) return theMap['image/']; + if (mimeType.startsWith('text/')) return theMap['text/']; + + return theMap.fallback; +} + +export const FileIcon = (props: FileIconProps) => { + const { + big = false, + mimeType, + size = 50, + sizeSmall = 20, + type = 'standard', + version = '1', + ...rest + } = props; + + const Icon = mimeTypeToIcon(type, version, mimeType); + + return ; +}; diff --git a/src/components/ReactFileUtilities/FileIcon/FileIconSet/v1.tsx b/src/components/ReactFileUtilities/FileIcon/FileIconSet/v1.tsx new file mode 100644 index 000000000..d0b4fb52a --- /dev/null +++ b/src/components/ReactFileUtilities/FileIcon/FileIconSet/v1.tsx @@ -0,0 +1,185 @@ +/** + * Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com + * License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) + */ +import React, { ComponentPropsWithoutRef } from 'react'; + +export type IconPropsV1 = { size?: number } & ComponentPropsWithoutRef<'svg'>; + +const DEFAULT_SIZE = 20; + +export const FilePdfIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileWordIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FilePowerPointIcon = ({ size = DEFAULT_SIZE, ...props }) => ( + + + +); + +export const FileExcelIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileArchiveIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileCodeIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileAudioIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileVideoIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileImageIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileAltIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); + +export const FileFallbackIcon = ({ size = DEFAULT_SIZE, ...props }: IconPropsV1) => ( + + + +); diff --git a/src/components/ReactFileUtilities/FileIcon/FileIconSet/v2.tsx b/src/components/ReactFileUtilities/FileIcon/FileIconSet/v2.tsx new file mode 100644 index 000000000..3fb7a1d87 --- /dev/null +++ b/src/components/ReactFileUtilities/FileIcon/FileIconSet/v2.tsx @@ -0,0 +1,576 @@ +import React, { ComponentPropsWithoutRef } from 'react'; + +export type IconTypeV2 = 'standard' | 'alt'; + +export type IconPropsV2 = { + mimeType?: string; + size?: number; + type?: IconTypeV2; +} & ComponentPropsWithoutRef<'svg'>; + +const DEFAULT_SIZE = 40; + +export const FilePdfIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + +); + +export const FileWordIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + + +); + +export const FileWordIconAlt = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + + + + + + + +); + +export const FilePowerPointIcon = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + +); + +export const FilePowerPointIconAlt = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + + + + + +); + +export const FileExcelIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + +); + +export const FileExcelIconAlt = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + + + + + +); + +export const FileArchiveIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + +); + +export const FileArchiveIconAlt = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + + + + + +); + +export const FileCodeIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + +); + +export const FileCodeIconAlt = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + + + + + +); + +export const FileAudioIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + +); + +export const FileAudioIconAlt = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + + + + + +); + +export const FileVideoIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + + + + + +); + +export const FileVideoIconAlt = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + + + + + +); + +export const FileFallbackIcon = ({ + className = '', + size = DEFAULT_SIZE, + ...props +}: IconPropsV2) => ( + + + + + + + + + + + + + +); + +// v1 icon without possibility to specify size via props +export const FileImageIcon = ({ className = '', size = DEFAULT_SIZE, ...props }: IconPropsV2) => ( + + + +); diff --git a/src/components/ReactFileUtilities/FileIcon/iconMap.ts b/src/components/ReactFileUtilities/FileIcon/iconMap.ts new file mode 100644 index 000000000..187faaf6c --- /dev/null +++ b/src/components/ReactFileUtilities/FileIcon/iconMap.ts @@ -0,0 +1,169 @@ +import type { ComponentType } from 'react'; + +import * as v1 from './FileIconSet/v1'; +import type { IconPropsV1 } from './FileIconSet/v1'; +import * as v2 from './FileIconSet/v2'; +import type { IconPropsV2 } from './FileIconSet/v2'; +import { + archiveFileTypes, + codeFileTypes, + excelMimeTypes, + GeneralType, + powerpointMimeTypes, + SupportedMimeType, + wordMimeTypes, +} from './mimeTypes'; + +type MimeTypeMappedComponent = + | 'FilePdfIcon' + | 'FileWordIcon' + | 'FileExcelIcon' + | 'FilePowerPointIcon' + | 'FileArchiveIcon' + | 'FileCodeIcon'; + +type GeneralContentTypeComponent = + | 'FileImageIcon' + | 'FileAudioIcon' + | 'FileVideoIcon' + | 'FileAltIcon'; + +type IconComponents = { + FileAltIcon: ComponentType; + FileArchiveIcon: ComponentType; + FileAudioIcon: ComponentType; + FileCodeIcon: ComponentType; + FileExcelIcon: ComponentType; + FileFallbackIcon: ComponentType; + FileImageIcon: ComponentType; + FilePdfIcon: ComponentType; + FilePowerPointIcon: ComponentType; + FileVideoIcon: ComponentType; + FileWordIcon: ComponentType; +}; + +type MimeTypeToIconMap = { + [key: string]: ComponentType; +}; + +function generateMimeTypeToIconMap({ + FileArchiveIcon, + FileCodeIcon, + FileExcelIcon, + FilePdfIcon, + FilePowerPointIcon, + FileWordIcon, +}: Pick, MimeTypeMappedComponent>) { + const mimeTypeToIconMap: MimeTypeToIconMap = { + 'application/pdf': FilePdfIcon, + }; + + for (const type of wordMimeTypes) { + mimeTypeToIconMap[type] = FileWordIcon; + } + + for (const type of excelMimeTypes) { + mimeTypeToIconMap[type] = FileExcelIcon; + } + + for (const type of powerpointMimeTypes) { + mimeTypeToIconMap[type] = FilePowerPointIcon; + } + + for (const type of archiveFileTypes) { + mimeTypeToIconMap[type] = FileArchiveIcon; + } + + for (const type of codeFileTypes) { + mimeTypeToIconMap[type] = FileCodeIcon; + } + return mimeTypeToIconMap; +} + +function generateGeneralTypeToIconMap({ + FileAltIcon, + FileAudioIcon, + FileImageIcon, + FileVideoIcon, +}: Pick, GeneralContentTypeComponent>) { + return { + 'audio/': FileAudioIcon, + 'image/': FileImageIcon, + 'text/': FileAltIcon, + 'video/': FileVideoIcon, + }; +} + +export type IconType = 'standard' | 'alt'; +export type IconVersion = '1' | '2'; + +type IconProps = { + '1': IconPropsV1; + '2': IconPropsV2; +}; + +type IconMap = { + [v in IconVersion]: { + standard: Record>; + alt?: Record>; + }; +}; + +export const iconMap: IconMap = { + '1': { + alt: {}, + standard: { + ...generateMimeTypeToIconMap({ + FileArchiveIcon: v1.FileArchiveIcon, + FileCodeIcon: v1.FileCodeIcon, + FileExcelIcon: v1.FileExcelIcon, + FilePdfIcon: v1.FilePdfIcon, + FilePowerPointIcon: v1.FilePowerPointIcon, + FileWordIcon: v1.FileWordIcon, + }), + ...generateGeneralTypeToIconMap({ + FileAltIcon: v1.FileAltIcon, + FileAudioIcon: v1.FileAudioIcon, + FileImageIcon: v1.FileImageIcon, + FileVideoIcon: v1.FileVideoIcon, + }), + fallback: v1.FileFallbackIcon, + }, + }, + '2': { + alt: { + ...generateMimeTypeToIconMap({ + FileArchiveIcon: v2.FileArchiveIconAlt, + FileCodeIcon: v2.FileCodeIconAlt, + FileExcelIcon: v2.FileExcelIconAlt, + FilePdfIcon: v2.FilePdfIcon, + FilePowerPointIcon: v2.FilePowerPointIconAlt, + FileWordIcon: v2.FileWordIconAlt, + }), + ...generateGeneralTypeToIconMap({ + FileAltIcon: v2.FileFallbackIcon, + FileAudioIcon: v2.FileAudioIconAlt, + FileImageIcon: v2.FileImageIcon, + FileVideoIcon: v2.FileVideoIconAlt, + }), + fallback: v2.FileFallbackIcon, + }, + standard: { + ...generateMimeTypeToIconMap({ + FileArchiveIcon: v2.FileArchiveIcon, + FileCodeIcon: v2.FileCodeIcon, + FileExcelIcon: v2.FileExcelIcon, + FilePdfIcon: v2.FilePdfIcon, + FilePowerPointIcon: v2.FilePowerPointIcon, + FileWordIcon: v2.FileWordIcon, + }), + ...generateGeneralTypeToIconMap({ + FileAltIcon: v2.FileFallbackIcon, + FileAudioIcon: v2.FileAudioIcon, + FileImageIcon: v2.FileImageIcon, + FileVideoIcon: v2.FileVideoIcon, + }), + fallback: v2.FileFallbackIcon, + }, + }, +}; diff --git a/src/components/ReactFileUtilities/FileIcon/index.ts b/src/components/ReactFileUtilities/FileIcon/index.ts new file mode 100644 index 000000000..f72cd523d --- /dev/null +++ b/src/components/ReactFileUtilities/FileIcon/index.ts @@ -0,0 +1 @@ +export { FileIcon } from './FileIcon'; diff --git a/src/components/ReactFileUtilities/FileIcon/mimeTypes.ts b/src/components/ReactFileUtilities/FileIcon/mimeTypes.ts new file mode 100644 index 000000000..765636fb3 --- /dev/null +++ b/src/components/ReactFileUtilities/FileIcon/mimeTypes.ts @@ -0,0 +1,163 @@ +export type GeneralType = 'audio/' | 'video/' | 'image/' | 'text/'; + +export type SupportedMimeType = + | typeof wordMimeTypes[number] + | typeof excelMimeTypes[number] + | typeof powerpointMimeTypes[number] + | typeof archiveFileTypes[number] + | typeof codeFileTypes[number]; + +export const wordMimeTypes = [ + // Microsoft Word + // .doc .dot + 'application/msword', + // .doc .dot + 'application/msword-template', + // .docx + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + // .dotx (no test) + 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + // .docm + 'application/vnd.ms-word.document.macroEnabled.12', + // .dotm (no test) + 'application/vnd.ms-word.template.macroEnabled.12', + + // LibreOffice/OpenOffice Writer + // .odt + 'application/vnd.oasis.opendocument.text', + // .ott + 'application/vnd.oasis.opendocument.text-template', + // .fodt + 'application/vnd.oasis.opendocument.text-flat-xml', + // .uot + // NOTE: firefox doesn't know mimetype so maybe ignore +]; + +export const excelMimeTypes = [ + // .csv + 'text/csv', + // TODO: maybe more data files + + // Microsoft Excel + // .xls .xlt .xla (no test for .xla) + 'application/vnd.ms-excel', + // .xlsx + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + // .xltx (no test) + 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + // .xlsm + 'application/vnd.ms-excel.sheet.macroEnabled.12', + // .xltm (no test) + 'application/vnd.ms-excel.template.macroEnabled.12', + // .xlam (no test) + 'application/vnd.ms-excel.addin.macroEnabled.12', + // .xlsb (no test) + 'application/vnd.ms-excel.addin.macroEnabled.12', + + // LibreOffice/OpenOffice Calc + // .ods + 'application/vnd.oasis.opendocument.spreadsheet', + // .ots + 'application/vnd.oasis.opendocument.spreadsheet-template', + // .fods + 'application/vnd.oasis.opendocument.spreadsheet-flat-xml', + // .uos + // NOTE: firefox doesn't know mimetype so maybe ignore +]; + +export const powerpointMimeTypes = [ + // Microsoft Word + // .ppt .pot .pps .ppa (no test for .ppa) + 'application/vnd.ms-powerpoint', + // .pptx + 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + // .potx (no test) + 'application/vnd.openxmlformats-officedocument.presentationml.template', + // .ppsx + 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + // .ppam + 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + // .pptm + 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + // .potm + 'application/vnd.ms-powerpoint.template.macroEnabled.12', + // .ppsm + 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + + // LibreOffice/OpenOffice Writer + // .odp + 'application/vnd.oasis.opendocument.presentation', + // .otp + 'application/vnd.oasis.opendocument.presentation-template', + // .fodp + 'application/vnd.oasis.opendocument.presentation-flat-xml', + // .uop + // NOTE: firefox doesn't know mimetype so maybe ignore +]; + +export const archiveFileTypes = [ + // .zip + 'application/zip', + // .z7 + 'application/x-7z-compressed', + // .ar + 'application/x-archive', + // .tar + 'application/x-tar', + // .tar.gz + 'application/gzip', + // .tar.Z + 'application/x-compress', + // .tar.bz2 + 'application/x-bzip', + // .tar.lz + 'application/x-lzip', + // .tar.lz4 + 'application/x-lz4', + // .tar.lzma + 'application/x-lzma', + // .tar.lzo (no test) + 'application/x-lzop', + // .tar.xz + 'application/x-xz', + // .war + 'application/x-webarchive', + // .rar + 'application/vnd.rar', +]; + +export const codeFileTypes = [ + // .html .htm + 'text/html', + // .css + 'text/css', + // .js + 'application/x-javascript', + 'text/javascript', + // .json + 'application/json', + // .py + 'text/x-python', + // .go + 'text/x-go', + // .c + 'text/x-csrc', + // .cpp + 'text/x-c++src', + // .rb + 'application/x-ruby', + // .rust + 'text/rust', + // .java + 'text/x-java', + // .php + 'application/x-php', + // .cs + 'text/x-csharp', + // .scala + 'text/x-scala', + // .erl + 'text/x-erlang', + // .sh + 'application/x-shellscript', +]; diff --git a/src/components/ReactFileUtilities/FilePreviewer.tsx b/src/components/ReactFileUtilities/FilePreviewer.tsx new file mode 100644 index 000000000..75f75759b --- /dev/null +++ b/src/components/ReactFileUtilities/FilePreviewer.tsx @@ -0,0 +1,68 @@ +import React from 'react'; + +import { FileIcon } from './FileIcon'; +import { LoadingIndicator } from './LoadingIndicator'; + +import type { FileUpload } from './types'; +import type { FileIconProps } from './FileIcon/FileIcon'; + +export type FilePreviewerProps = { + fileIconProps?: FileIconProps; + handleFiles?: (files: FileList) => void; + handleRemove?: (id: string) => void; + handleRetry?: (id: string) => void; + uploads?: FileUpload[]; +}; + +/** + * Component that displays files which are being uploaded + */ +export const FilePreviewer = ({ + fileIconProps = {}, + uploads, + handleRemove, + handleRetry, +}: FilePreviewerProps) => ( +
+
    + {uploads?.map((upload) => ( +
  1. + + + {upload.file.name} + {upload.state === 'failed' && ( + <> +
    handleRetry?.(upload.id)} + > + failed +
    +
    handleRetry?.(upload.id)}> + retry +
    + + )} +
    + + handleRemove(upload.id))} + > + ✘ + + {upload.state === 'uploading' && ( +
    + +
    + )} +
  2. + ))} +
+
+); diff --git a/src/components/ReactFileUtilities/FileUploadButton.tsx b/src/components/ReactFileUtilities/FileUploadButton.tsx new file mode 100644 index 000000000..4fab9bf34 --- /dev/null +++ b/src/components/ReactFileUtilities/FileUploadButton.tsx @@ -0,0 +1,46 @@ +import React, { PropsWithChildren } from 'react'; + +import { AttachmentIcon } from './icons'; +import { UploadButton } from './UploadButton'; + +export type FileUploadButtonProps = { + handleFiles: (files: FileList | File[]) => void; + accepts?: string | string[]; + disabled?: boolean; + multiple?: boolean; + resetOnChange?: boolean; +}; + +/** + * @deprecated will be removed in the next major release + */ +export const FileUploadButton = ({ + disabled = false, + multiple = false, + children = , + handleFiles, + accepts, + resetOnChange = true, +}: PropsWithChildren) => { + let className = 'rfu-file-upload-button'; + if (disabled) { + className = `${className} rfu-file-upload-button--disabled`; + } + + return ( +
+ +
+ ); +}; diff --git a/src/components/ReactFileUtilities/IconButton.tsx b/src/components/ReactFileUtilities/IconButton.tsx new file mode 100644 index 000000000..47d7f9f99 --- /dev/null +++ b/src/components/ReactFileUtilities/IconButton.tsx @@ -0,0 +1,20 @@ +import React, { MouseEventHandler, PropsWithChildren } from 'react'; + +export type IconButtonProps = { + onClick?: MouseEventHandler; +}; + +/** + * This is simply a button wrapper, adds a div with `role="button"` and a onClick + */ +export const IconButton = ({ children, onClick }: PropsWithChildren) => ( + +); diff --git a/src/components/ReactFileUtilities/ImageDropzone.tsx b/src/components/ReactFileUtilities/ImageDropzone.tsx new file mode 100644 index 000000000..ad3aad97f --- /dev/null +++ b/src/components/ReactFileUtilities/ImageDropzone.tsx @@ -0,0 +1,87 @@ +import clsx from 'clsx'; +import React, { PropsWithChildren, useCallback, useMemo } from 'react'; +import { useDropzone } from 'react-dropzone'; + +export type ImageDropzoneProps = { + /** + * Set accepted file types. See https://github.com/okonet/attr-accept for more information. Keep in mind that mime type determination is not reliable across platforms. CSV files, for example, are reported as text/plain under macOS but as application/vnd.ms-excel under Windows. In some cases there might not be a mime type set at all. + * + * One of type: `string, string[]` + */ + accept?: string | string[]; + /** Enable/disable the dropzone */ + disabled?: boolean; + handleFiles?: (files: FileList | File[]) => void; + maxNumberOfFiles?: number; + /** Allow drag 'n' drop (or selection from the file dialog) of multiple files */ + multiple?: boolean; +}; + +export const ImageDropzone = ({ + accept: acceptedFiles = [], + children, + disabled, + handleFiles, + maxNumberOfFiles, + multiple, +}: PropsWithChildren) => { + const handleDrop = useCallback( + (accepted: File[]) => { + if (!handleFiles) { + return; + } + + if (accepted && accepted.length) { + handleFiles(accepted); + } + }, + [handleFiles], + ); + + const accept = useMemo( + () => + (typeof acceptedFiles === 'string' ? acceptedFiles.split(',') : acceptedFiles).reduce< + Record> + >((mediaTypeMap, mediaType) => { + mediaTypeMap[mediaType] ??= []; + return mediaTypeMap; + }, {}), + [acceptedFiles], + ); + + const { getRootProps, isDragAccept, isDragReject } = useDropzone({ + accept, + disabled, + maxFiles: maxNumberOfFiles, + multiple, + noClick: true, + onDrop: handleDrop, + }); + + return ( +
+
+
+ + + +

Drag your files here to add to your post

+
+
+ {children} +
+ ); +}; diff --git a/src/components/ReactFileUtilities/ImagePreviewer.tsx b/src/components/ReactFileUtilities/ImagePreviewer.tsx new file mode 100644 index 000000000..74c925b61 --- /dev/null +++ b/src/components/ReactFileUtilities/ImagePreviewer.tsx @@ -0,0 +1,80 @@ +import React, { MouseEvent, useCallback } from 'react'; + +import { LoadingIndicator } from './LoadingIndicator'; +import { Thumbnail } from './Thumbnail'; +import { ThumbnailPlaceholder } from './ThumbnailPlaceholder'; +import { RetryIcon } from './icons'; + +import type { ImageUpload } from './types'; +import clsx from 'clsx'; + +type CustomMouseEvent = (id: string, event: MouseEvent) => void; + +export type ImagePreviewerProps = { + /** The list of image uploads that should be displayed */ + disabled?: boolean; + /** A callback to call with newly selected files. If this is not provided no + * `ThumbnailPlaceholder` will be displayed. + */ + handleFiles?: (files: File[]) => void; + /** A callback to call when the remove icon is clicked */ + handleRemove?: CustomMouseEvent; + /** A callback to call when the retry icon is clicked */ + handleRetry?: CustomMouseEvent; + imageUploads?: ImageUpload[]; + /** Allow drag 'n' drop (or selection from the file dialog) of multiple files */ + multiple?: boolean; +}; + +export const ImagePreviewer = ({ + disabled = false, + handleFiles, + handleRemove, + handleRetry, + imageUploads, + multiple = true, +}: ImagePreviewerProps) => { + const onClose: CustomMouseEvent = useCallback( + (id, event) => { + if (!id) return console.warn(`image.id of closed image was "null", this shouldn't happen`); + handleRemove?.(id, event); + }, + [handleRemove], + ); + + return ( +
+ {imageUploads?.map((image) => { + const url = image.url || image.previewUri; + return ( +
+ {image.state === 'failed' && ( + + )} + + {url && onClose(image.id, event)} image={url} />} + {image.state === 'uploading' && ( + + )} +
+ ); + })} + {handleFiles && !disabled && ( + + )} +
+ ); +}; diff --git a/src/components/ReactFileUtilities/ImageUploadButton.tsx b/src/components/ReactFileUtilities/ImageUploadButton.tsx new file mode 100644 index 000000000..900d62780 --- /dev/null +++ b/src/components/ReactFileUtilities/ImageUploadButton.tsx @@ -0,0 +1,37 @@ +import React, { PropsWithChildren } from 'react'; + +import { PictureIcon } from './icons'; +import { UploadButton } from './UploadButton'; + +export type ImageUploadButtonProps = { + handleFiles: (files: File[]) => void; + disabled?: boolean; + multiple?: boolean; + resetOnChange?: boolean; +}; + +/** + * @deprecated will be removed in the next major release + */ +export const ImageUploadButton = ({ + multiple = false, + disabled = false, + handleFiles, + children = , + resetOnChange = false, +}: PropsWithChildren) => ( +
+ +
+); diff --git a/src/components/ReactFileUtilities/LoadingIndicator.tsx b/src/components/ReactFileUtilities/LoadingIndicator.tsx new file mode 100644 index 000000000..a276a73d6 --- /dev/null +++ b/src/components/ReactFileUtilities/LoadingIndicator.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +export type LoadingIndicatorProps = { + backgroundColor?: string; + color?: string; + size?: number; + width?: number; +}; + +export const LoadingIndicator = ({ + backgroundColor, + color, + size = 20, + width = 2, +}: LoadingIndicatorProps) => ( +
+); diff --git a/src/components/ReactFileUtilities/Thumbnail.tsx b/src/components/ReactFileUtilities/Thumbnail.tsx new file mode 100644 index 000000000..0c8e56fd4 --- /dev/null +++ b/src/components/ReactFileUtilities/Thumbnail.tsx @@ -0,0 +1,39 @@ +import React, { MouseEventHandler, useCallback } from 'react'; + +import { IconButton } from './IconButton'; +import { CloseIcon, FilePlaceholderIcon } from './icons'; + +export type ThumbnailProps = { + image: string; + alt?: string; + handleClose?: MouseEventHandler; + id?: string; + size?: number; +}; + +export const Thumbnail = ({ alt, handleClose, image, size = 100 }: ThumbnailProps) => { + const onClose: MouseEventHandler = useCallback( + (event) => handleClose?.(event), + [handleClose], + ); + + return ( +
+
+ {handleClose ? ( + + + + ) : null} +
+ {image ? ( + {alt + ) : ( + + )} +
+ ); +}; diff --git a/src/components/ReactFileUtilities/ThumbnailPlaceholder.tsx b/src/components/ReactFileUtilities/ThumbnailPlaceholder.tsx new file mode 100644 index 000000000..926ffc220 --- /dev/null +++ b/src/components/ReactFileUtilities/ThumbnailPlaceholder.tsx @@ -0,0 +1,21 @@ +import React from 'react'; + +import { ImageUploadButton } from './ImageUploadButton'; + +export type ThumbnailPlaceholderProps = { + handleFiles: (files: File[]) => void; + multiple: boolean; +}; + +export const ThumbnailPlaceholder = ({ + handleFiles, + multiple = false, +}: ThumbnailPlaceholderProps) => ( + +
+ + + +
+
+); diff --git a/src/components/ReactFileUtilities/UploadButton.tsx b/src/components/ReactFileUtilities/UploadButton.tsx new file mode 100644 index 000000000..59c96ea49 --- /dev/null +++ b/src/components/ReactFileUtilities/UploadButton.tsx @@ -0,0 +1,18 @@ +import React, { ComponentProps } from 'react'; + +import { useHandleFileChangeWrapper } from './utils'; + +export type UploadButtonProps = { + onFileChange: (files: Array) => void; + resetOnChange?: boolean; +} & Omit, 'type' | 'onChange'>; + +export const UploadButton = ({ + onFileChange, + resetOnChange = true, + ...rest +}: UploadButtonProps) => { + const handleInputChange = useHandleFileChangeWrapper(resetOnChange, onFileChange); + + return ; +}; diff --git a/src/components/ReactFileUtilities/icons/AttachmentIcon.tsx b/src/components/ReactFileUtilities/icons/AttachmentIcon.tsx new file mode 100644 index 000000000..69f7e6be3 --- /dev/null +++ b/src/components/ReactFileUtilities/icons/AttachmentIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +/** + * An icon of a paperclip, which is used as the default icon for FileUploadButton + */ +export const AttachmentIcon = () => ( + + + + +); diff --git a/src/components/ReactFileUtilities/icons/CloseIcon.tsx b/src/components/ReactFileUtilities/icons/CloseIcon.tsx new file mode 100644 index 000000000..5c5e4e6dd --- /dev/null +++ b/src/components/ReactFileUtilities/icons/CloseIcon.tsx @@ -0,0 +1,27 @@ +import React from 'react'; + +export const CloseIcon = () => ( + + + + + + + + + + + + + + +); diff --git a/src/components/ReactFileUtilities/icons/FilePlaceholderIcon.tsx b/src/components/ReactFileUtilities/icons/FilePlaceholderIcon.tsx new file mode 100644 index 000000000..03bd41529 --- /dev/null +++ b/src/components/ReactFileUtilities/icons/FilePlaceholderIcon.tsx @@ -0,0 +1,20 @@ +import React, { ComponentPropsWithoutRef } from 'react'; + +export const FilePlaceholderIcon = (props: ComponentPropsWithoutRef<'svg'>) => ( + + + + + + +); diff --git a/src/components/ReactFileUtilities/icons/PictureIcon.tsx b/src/components/ReactFileUtilities/icons/PictureIcon.tsx new file mode 100644 index 000000000..5daeec847 --- /dev/null +++ b/src/components/ReactFileUtilities/icons/PictureIcon.tsx @@ -0,0 +1,13 @@ +import React from 'react'; + +/** + * An icon of a picture, which is used as the default icon for ImageUploadButton + */ +export const PictureIcon = () => ( + + + +); diff --git a/src/components/ReactFileUtilities/icons/RetryIcon.tsx b/src/components/ReactFileUtilities/icons/RetryIcon.tsx new file mode 100644 index 000000000..ba37d176a --- /dev/null +++ b/src/components/ReactFileUtilities/icons/RetryIcon.tsx @@ -0,0 +1,11 @@ +import React from 'react'; + +export const RetryIcon = () => ( + + + +); diff --git a/src/components/ReactFileUtilities/icons/index.ts b/src/components/ReactFileUtilities/icons/index.ts new file mode 100644 index 000000000..381e7af1c --- /dev/null +++ b/src/components/ReactFileUtilities/icons/index.ts @@ -0,0 +1,5 @@ +export * from './AttachmentIcon'; +export * from './CloseIcon'; +export * from './FilePlaceholderIcon'; +export * from './PictureIcon'; +export * from './RetryIcon'; diff --git a/src/components/ReactFileUtilities/index.ts b/src/components/ReactFileUtilities/index.ts new file mode 100644 index 000000000..fc8ff1c62 --- /dev/null +++ b/src/components/ReactFileUtilities/index.ts @@ -0,0 +1,13 @@ +export * from './FileIcon'; +export * from './FilePreviewer'; +export * from './FileUploadButton'; +export * from './ImageDropzone'; +export * from './ImagePreviewer'; +export * from './ImageUploadButton'; +export * from './LoadingIndicator'; +export * from './Thumbnail'; +export * from './ThumbnailPlaceholder'; +export * from './UploadButton'; +export * from './types'; +export * from './utils'; +export { AttachmentIcon, PictureIcon } from './icons'; diff --git a/src/components/ReactFileUtilities/types.ts b/src/components/ReactFileUtilities/types.ts new file mode 100644 index 000000000..3ea0ebe46 --- /dev/null +++ b/src/components/ReactFileUtilities/types.ts @@ -0,0 +1,34 @@ +export type UploadState = 'uploading' | 'finished' | 'failed'; + +export type FileLike = Blob | File; + +export type UploadInfo = { + id: string; + state: UploadState; + url?: string; +}; + +export type FileUpload = { + file: { + name: string; + lastModified?: number; + lastModifiedDate?: Date; + size?: number; + type?: string; + uri?: string; + }; +} & UploadInfo; + +export type ImageUpload = { + file: { + name: string; + height?: number; + lastModified?: number; + lastModifiedDate?: Date; + size?: number; + type?: string; + uri?: string; + width?: number; + }; + previewUri?: string; +} & UploadInfo; diff --git a/src/components/ReactFileUtilities/utils.ts b/src/components/ReactFileUtilities/utils.ts new file mode 100644 index 000000000..f1c5060e6 --- /dev/null +++ b/src/components/ReactFileUtilities/utils.ts @@ -0,0 +1,94 @@ +import type { FileLike } from './types'; + +export const useHandleFileChangeWrapper = ( + resetOnChange = false, + handler?: (files: Array) => void, +) => ({ currentTarget }: React.ChangeEvent) => { + const { files } = currentTarget; + + if (!files) return; + + try { + handler?.(Array.from(files)); + } catch (error) { + console.error(error); + } + + if (resetOnChange) currentTarget.value = ''; +}; + +export function dataTransferItemsHaveFiles(items?: DataTransferItem[]): boolean { + if (!items || !items.length) { + return false; + } + for (const item of items) { + if (item.kind === 'file' || item.type === 'text/html') { + return true; + } + } + return false; +} + +export async function dataTransferItemsToFiles(items?: DataTransferItem[]): Promise { + if (!items || !items.length) { + return []; + } + + // If there are files inside the DataTransferItem prefer those + const fileLikes = getFileLikes(items); + if (fileLikes.length) { + return fileLikes; + } + + // Otherwise extract images from html + const blobPromises = []; + for (const item of items) { + if (item.type === 'text/html') { + blobPromises.push( + new Promise((accept) => { + item.getAsString(async (s) => { + const imagePromises = extractImageSources(s).map((src) => + getImageSource(fileLikes, src), + ); + + await Promise.all(imagePromises); + accept(); + }); + }), + ); + } + } + await Promise.all(blobPromises); + return fileLikes; +} + +function getFileLikes(items: DataTransferItem[]) { + const fileLikes = []; + for (const item of items) { + if (item.kind === 'file') { + const file = item.getAsFile(); + if (file) { + fileLikes.push(file); + } + } + } + return fileLikes; +} + +async function getImageSource(fileLikes: FileLike[], src: string) { + let res; + try { + res = await fetch(src); + } catch (e) { + return; + } + const contentType = res.headers.get('Content-type') || 'application/octet-stream'; + const buf = await res.arrayBuffer(); + const blob = new Blob([buf], { type: contentType }); + fileLikes.push(blob); +} + +const extractImageSources = (s: string) => { + const imageTags = new DOMParser().parseFromString(s, 'text/html').getElementsByTagName('img'); + return Array.from(imageTags, (tag) => tag.src).filter((tag) => tag); +}; diff --git a/yarn.lock b/yarn.lock index 10301e2a7..5edba142b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6676,13 +6676,6 @@ file-loader@^6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -file-selector@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.5.0.tgz#21c7126dc9728b31a2742d91cab20d55e67e4fb4" - integrity sha512-s8KNnmIDTBoD0p9uJ9uD0XY38SCeBOtj0UMXyQSLg1Ypfrfj8+dAvwsLjYQkQ2GjhVtp2HrnF5cJzMhBjfD8HA== - dependencies: - tslib "^2.0.3" - file-selector@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/file-selector/-/file-selector-0.6.0.tgz#fa0a8d9007b829504db4d07dd4de0310b65287dc" @@ -12042,15 +12035,6 @@ react-dom@^18.1.0: loose-envify "^1.1.0" scheduler "^0.23.0" -react-dropzone@^12.0.5: - version "12.1.0" - resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-12.1.0.tgz#e097b37e9da6f9e324efc757b7434ebc6f3dc2cb" - integrity sha512-iBYHA1rbopIvtzokEX4QubO6qk5IF/x3BtKGu74rF2JkQDXnwC4uO/lHKpaw4PJIV6iIAYOlwLv2FpiGyqHNog== - dependencies: - attr-accept "^2.2.2" - file-selector "^0.5.0" - prop-types "^15.8.1" - react-dropzone@^14.2.3: version "14.2.3" resolved "https://registry.yarnpkg.com/react-dropzone/-/react-dropzone-14.2.3.tgz#0acab68308fda2d54d1273a1e626264e13d4e84b" @@ -12077,13 +12061,6 @@ react-fast-compare@^3.2.2: resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-3.2.2.tgz#929a97a532304ce9fee4bcae44234f1ce2c21d49" integrity sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ== -react-file-utils@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/react-file-utils/-/react-file-utils-1.2.0.tgz#4420f41b76bfdc19acfbccc092ebd3ca6d41e955" - integrity sha512-pu5mpNNwHZe7X+LG4CNtEwqf+J2sseE3y9dbkt1LLQpYs7TC4sy3XgRkQTzZuvLXsDyGpASbhDJCpB/KHCPbAA== - dependencies: - react-dropzone "^12.0.5" - react-image-gallery@1.2.12: version "1.2.12" resolved "https://registry.yarnpkg.com/react-image-gallery/-/react-image-gallery-1.2.12.tgz#b08a633cc336bab2a5afdb96941e023925043c6a" @@ -14077,7 +14054,7 @@ tslib@^1.8.1, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1, tslib@^2.0.3: +tslib@^2.0.1: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==