From 0893dd3478cf770355c50ae5f05d34695bc4a147 Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 23 Jan 2025 19:00:07 +0100 Subject: [PATCH 01/28] Refactor upload folder logic to use async queue for improved concurrency and error handling --- .../storage.thunks/uploadFolderThunk.ts | 125 ++++++++++-------- 1 file changed, 68 insertions(+), 57 deletions(-) diff --git a/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts b/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts index f170682c3..04ce176db 100644 --- a/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts @@ -18,6 +18,7 @@ import { StorageState } from '../storage.model'; import { deleteItemsThunk } from './deleteItemsThunk'; import { uploadItemsParallelThunk } from './uploadItemsThunk'; import { IRoot } from '../types'; +import { queue, QueueObject } from 'async'; interface UploadFolderThunkPayload { root: IRoot; @@ -273,85 +274,95 @@ export const uploadMultipleFolderThunkNoCheck = createAsyncThunk< // checking why is not aborting correctly the folder upload for (const { root, currentFolderId, options: payloadOptions, taskId, abortController } of payloadWithTaskId) { + console.time('multiFolder-upload'); const options = { withNotification: true, ...payloadOptions }; let alreadyUploaded = 0; let rootFolderItem: DriveFolderData | undefined; - let rootFolderData: DriveFolderData | undefined; - const levels = [root]; const itemsUnderRoot = countItemsUnderRoot(root); const uploadFolderAbortController = abortController; - try { - root.folderId = currentFolderId; + const uploadFolderAsync = async (level: IRoot) => { + const createdFolder = await dispatch( + storageThunks.createFolderThunk({ + parentFolderId: level.folderId as string, + folderName: level.name, + options: { relatedTaskId: taskId, showErrors: false }, + }), + ).unwrap(); + + if (!rootFolderItem) { + rootFolderItem = createdFolder; + } - while (levels.length > 0) { - if (uploadFolderAbortController.signal.aborted) break; - const level: IRoot = levels.shift() as IRoot; - const createdFolder = await dispatch( - storageThunks.createFolderThunk({ - parentFolderId: level.folderId as string, - folderName: level.name, - options: { relatedTaskId: taskId, showErrors: false }, - }), - ).unwrap(); + tasksService.updateTask({ + taskId, + merge: { + stop: () => stopUploadTask(uploadFolderAbortController, dispatch, taskId, rootFolderItem), + }, + }); + if (level.childrenFiles.length > 0 || level.childrenFolders.length > 0) { // Added wait in order to allow enough time for the server to create the folder await wait(500); + } - if (!rootFolderItem) { - rootFolderItem = createdFolder; - } - - tasksService.updateTask({ - taskId, - merge: { - stop: () => stopUploadTask(uploadFolderAbortController, dispatch, taskId, rootFolderItem), - }, - }); - if (!rootFolderData) { - rootFolderData = createdFolder; - } - - if (level.childrenFiles) { - if (uploadFolderAbortController.signal.aborted) break; + if (level.childrenFiles) { + //if (uploadFolderAbortController.signal.aborted) break; + + await dispatch( + uploadItemsParallelThunk({ + files: level.childrenFiles, + parentFolderId: createdFolder.uuid, + options: { + relatedTaskId: taskId, + showNotifications: false, + showErrors: false, + abortController: uploadFolderAbortController, + disableDuplicatedNamesCheck: true, + }, + filesProgress: { filesUploaded: alreadyUploaded, totalFilesToUpload: itemsUnderRoot }, + }), + ) + .unwrap() + .then(() => { + alreadyUploaded += level.childrenFiles.length; + alreadyUploaded += 1; + }); - await dispatch( - uploadItemsParallelThunk({ - files: level.childrenFiles, - parentFolderId: createdFolder.uuid, - options: { - relatedTaskId: taskId, - showNotifications: false, - showErrors: false, - abortController: uploadFolderAbortController, - disableDuplicatedNamesCheck: true, - }, - filesProgress: { filesUploaded: alreadyUploaded, totalFilesToUpload: itemsUnderRoot }, - }), - ) - .unwrap() - .then(() => { - alreadyUploaded += level.childrenFiles.length; - alreadyUploaded += 1; - }); + //if (uploadFolderAbortController.signal.aborted) break; + } - if (uploadFolderAbortController.signal.aborted) break; - } + for (const child of level.childrenFolders) { + await uploadFolderQueue.pushAsync({ ...child, folderId: createdFolder.uuid }); + } + }; + + const uploadFolderQueue: QueueObject = queue((task, callback) => { + uploadFolderAsync(task) + .then(() => { + callback(); + }) + .catch((e) => { + callback(e); + }); + }, 6); - const childrenFolders = [] as IRoot[]; - for (const child of level.childrenFolders) { - childrenFolders.push({ ...child, folderId: createdFolder.uuid }); - } + try { + root.folderId = currentFolderId; + await uploadFolderQueue.pushAsync(root); - levels.push(...childrenFolders); + while (uploadFolderQueue.running() > 0 || uploadFolderQueue.length() > 0) { + await uploadFolderQueue.drain(); } + console.timeEnd('multiFolder-upload'); + tasksService.updateTask({ taskId: taskId, merge: { - itemUUID: { rootFolderUUID: rootFolderData?.uuid }, + itemUUID: { rootFolderUUID: rootFolderItem?.uuid }, status: TaskStatus.Success, }, }); From f681b5114f6a437289165e25e83c5725a49a6aaf Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 28 Jan 2025 15:23:30 +0100 Subject: [PATCH 02/28] Removed upload folder functionality from async thunk --- .../DriveExplorer/DriveExplorer.tsx | 21 +- .../storage/folderUtils/createFolder.ts | 99 ++++++++ .../storage/folderUtils/uploadFolders.ts | 235 ++++++++++++++++++ 3 files changed, 347 insertions(+), 8 deletions(-) create mode 100644 src/app/store/slices/storage/folderUtils/createFolder.ts create mode 100644 src/app/store/slices/storage/folderUtils/uploadFolders.ts diff --git a/src/app/drive/components/DriveExplorer/DriveExplorer.tsx b/src/app/drive/components/DriveExplorer/DriveExplorer.tsx index 2f6cca4d0..398771b21 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorer.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorer.tsx @@ -16,10 +16,10 @@ import DriveExplorerList from './DriveExplorerList/DriveExplorerList'; import { UserSettings } from '@internxt/sdk/dist/shared/types/userSettings'; import { useHotkeys } from 'react-hotkeys-hook'; -import moveItemsToTrash from 'use_cases/trash/move-items-to-trash'; +import moveItemsToTrash from '../../../../use_cases/trash/move-items-to-trash'; import { Role } from '@internxt/sdk/dist/drive/share/types'; -import workspacesSelectors from 'app/store/slices/workspaces/workspaces.selectors'; +import workspacesSelectors from '../../../store/slices/workspaces/workspaces.selectors'; import { t } from 'i18next'; import BannerWrapper from '../../../banners/BannerWrapper'; import deviceService from '../../../core/services/device.service'; @@ -68,9 +68,11 @@ import WarningMessageWrapper from '../WarningMessage/WarningMessageWrapper'; import './DriveExplorer.scss'; import { DriveTopBarItems } from './DriveTopBarItems'; import DriveTopBarActions from './components/DriveTopBarActions'; -import { getAncestorsAndSetNamePath } from 'app/store/slices/storage/storage.thunks/goToFolderThunk'; +import { getAncestorsAndSetNamePath } from '../../../store/slices/storage/storage.thunks/goToFolderThunk'; import { IRoot } from '../../../store/slices/storage/types'; -import { useTrashPagination } from 'app/drive/hooks/trash/useTrashPagination'; +import { useTrashPagination } from '../../../drive/hooks/trash/useTrashPagination'; +import { uploadMultipleFolder } from '../../../store/slices/storage/folderUtils/uploadFolders'; +import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; export const UPLOAD_ITEMS_LIMIT = 3000; @@ -99,6 +101,7 @@ interface DriveExplorerProps { namePath: FolderPath[]; dispatch: AppDispatch; workspace: Workspace; + selectedWorkspace: WorkspaceData | null; planLimit: number; planUsage: number; isOver: boolean; @@ -143,11 +146,11 @@ const DriveExplorer = (props: DriveExplorerProps): JSX.Element => { user, getTrashPaginated, roles, + selectedWorkspace, } = props; const [isOpen, setIsOpen] = useState(false); const dispatch = useAppDispatch(); const { translate } = useTranslationContext(); - const selectedWorkspace = useAppSelector(workspacesSelectors.getSelectedWorkspace); const menuItemsRef = useRef(null); const menuContextItemsRef = useRef(null); @@ -903,9 +906,9 @@ const uploadItems = async (props: DriveExplorerProps, rootList: IRoot[], files: onSuccess: onDragAndDropEnd, }, })); - dispatch(storageThunks.uploadMultipleFolderThunkNoCheck(folderDataToUpload)).then(() => { - dispatch(fetchSortedFolderContentThunk(currentFolderId)); - }); + + await uploadMultipleFolder(folderDataToUpload, props.selectedWorkspace, { dispatch }); + dispatch(fetchSortedFolderContentThunk(currentFolderId)); } } } else { @@ -951,6 +954,7 @@ const dropTargetCollect: DropTargetCollector< export default connect((state: RootState) => { const currentFolderId: string = storageSelectors.currentFolderId(state); + const selectedWorkspace = workspacesSelectors.getSelectedWorkspace(state); const hasMoreFolders = state.storage.hasMoreDriveFolders[currentFolderId] ?? true; const hasMoreFiles = state.storage.hasMoreDriveFiles[currentFolderId] ?? true; @@ -967,6 +971,7 @@ export default connect((state: RootState) => { viewMode: state.storage.viewMode, namePath: state.storage.namePath, workspace: state.session.workspace, + selectedWorkspace: selectedWorkspace, planLimit: planSelectors.planLimitToShow(state), planUsage: planSelectors.planUsageToShow(state), folderOnTrashLength: state.storage.folderOnTrashLength, diff --git a/src/app/store/slices/storage/folderUtils/createFolder.ts b/src/app/store/slices/storage/folderUtils/createFolder.ts new file mode 100644 index 000000000..b4567ed23 --- /dev/null +++ b/src/app/store/slices/storage/folderUtils/createFolder.ts @@ -0,0 +1,99 @@ +import { DriveFolderData, DriveItemData } from '../../../../drive/types'; +import tasksService from '../../../../tasks/services/tasks.service'; +import { CreateFolderTask, TaskProgress, TaskStatus, TaskType } from '../../../../tasks/types'; +import workspacesService from '../../../../core/services/workspace.service'; +import folderService from '../../../../drive/services/folder.service'; +import { storageActions } from '..'; +import errorService from '../../../../core/services/error.service'; +import { CreateFolderResponse } from '@internxt/sdk/dist/drive/storage/types'; +import { RequestCanceler } from '@internxt/sdk/dist/shared/http/types'; +import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; +import { RootState } from '../../../../store'; +import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; + +interface CreateFolderOptions { + relatedTaskId: string; + showErrors: boolean; +} + +interface CreateFolderPayload { + parentFolderId: string; + folderName: string; + options?: Partial; + uuid?: string; +} + +export const createFolder = async ( + { folderName, parentFolderId, options }: CreateFolderPayload, + currentFolderId: string, + selectedWorkspace: WorkspaceData | null, + { dispatch }: { dispatch: ThunkDispatch }, +) => { + options = Object.assign({ showErrors: true }, options || {}); + const workspaceId = selectedWorkspace?.workspace?.id; + let createdFolderPromise: Promise; + let requestCanceler: RequestCanceler; + + try { + if (workspaceId) { + [createdFolderPromise, requestCanceler] = workspacesService.createFolder({ + workspaceId, + parentFolderUuid: parentFolderId, + plainName: folderName, + }); + } else { + [createdFolderPromise, requestCanceler] = folderService.createFolderByUuid(parentFolderId, folderName); + } + + const taskId = tasksService.create({ + relatedTaskId: options.relatedTaskId, + action: TaskType.CreateFolder, + folderName: folderName, + parentFolderId: parentFolderId, + showNotification: false, + cancellable: false, + stop: async () => requestCanceler.cancel(), + }); + + const createdFolder = await createdFolderPromise; + + const createdFolderNormalized: DriveFolderData = { + ...createdFolder, + name: folderName, + parent_id: createdFolder.parentId, + user_id: createdFolder.userId, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: createdFolder.plainName, + deleted: false, + createdAt: new Date(createdFolder.createdAt || '').toISOString(), + updatedAt: new Date(createdFolder.updatedAt || '').toISOString(), + }; + + tasksService.updateTask({ + taskId: taskId, + merge: { + status: TaskStatus.Success, + progress: TaskProgress.Max, + }, + }); + + if (currentFolderId === parentFolderId) { + dispatch( + storageActions.pushItems({ + folderIds: [currentFolderId], + items: createdFolderNormalized as DriveItemData, + }), + ); + } + + return createdFolderNormalized; + } catch (err: unknown) { + const castedError = errorService.castError(err); + throw castedError; + } +}; diff --git a/src/app/store/slices/storage/folderUtils/uploadFolders.ts b/src/app/store/slices/storage/folderUtils/uploadFolders.ts new file mode 100644 index 000000000..b89b51dc4 --- /dev/null +++ b/src/app/store/slices/storage/folderUtils/uploadFolders.ts @@ -0,0 +1,235 @@ +import { TaskStatus, TaskType, UploadFolderTask } from '../../../../tasks/types'; +import { DriveFolderData, DriveItemData } from '../../../../drive/types'; +import { IRoot } from '../types'; +import tasksService from '../../../../tasks/services/tasks.service'; +import errorService from '../../../../core/services/error.service'; +import { planThunks } from '../../plan'; +import { queue, QueueObject } from 'async'; +import { t } from 'i18next'; +import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; +import { RootState } from '../../../../store'; +import { deleteItemsThunk } from '../storage.thunks/deleteItemsThunk'; +import { SdkFactory } from './../../../../core/factory/sdk'; +import { uploadItemsParallelThunk } from '../storage.thunks/uploadItemsThunk'; +import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; +import { createFolder } from './createFolder'; + +const MAX_CONCURRENT_UPLOADS = 6; + +interface UploadFolderThunkPayload { + root: IRoot; + currentFolderId: string; + options?: { + taskId?: string; + withNotification?: boolean; + onSuccess?: () => void; + }; +} + +const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]) => { + return foldersPayload.map(({ root, currentFolderId, options: payloadOptions }) => { + const options = { withNotification: true, ...payloadOptions }; + + const uploadFolderAbortController = new AbortController(); + + let taskId = options?.taskId; + + if (taskId) { + tasksService.updateTask({ + taskId, + merge: { + status: TaskStatus.InProcess, + progress: 0, + }, + }); + } else { + taskId = tasksService.create({ + action: TaskType.UploadFolder, + folderName: root.name, + item: root, + parentFolderId: currentFolderId, + showNotification: !!options.withNotification, + cancellable: true, + }); + } + + return { root, currentFolderId, options: payloadOptions, taskId, abortController: uploadFolderAbortController }; + }); +}; + +const stopUploadTask = async ( + uploadFolderAbortController: AbortController, + dispatch: ThunkDispatch, + relatedTaskId?: string, + rootFolderItem?: DriveFolderData, +) => { + uploadFolderAbortController.abort(); + const relatedTasks = tasksService.getTasks({ relatedTaskId }); + const promises: Promise[] = []; + + // Cancels related tasks + promises.push( + ...(relatedTasks.map((task) => task.stop?.()).filter((promise) => promise !== undefined) as Promise[]), + ); + // Deletes the root folder + if (rootFolderItem) { + promises.push(dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); + const storageClient = SdkFactory.getInstance().createStorageClient(); + promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); + } + await Promise.all(promises); +}; + +function countItemsUnderRoot(root: IRoot): number { + let count = 0; + + const queueOfFolders: Array = [root]; + + while (queueOfFolders.length > 0) { + const folder = queueOfFolders.shift() as IRoot; + + count += folder.childrenFiles.length; + + if (folder.childrenFolders) { + count += folder.childrenFolders.length; + queueOfFolders.push(...folder.childrenFolders); + } + } + + return count; +} + +const wait = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +export const uploadMultipleFolder = async ( + payload: UploadFolderThunkPayload[], + selectedWorkspace: WorkspaceData | null, + { dispatch }: { dispatch: ThunkDispatch }, +) => { + const payloadWithTaskId = generateTaskIdForFolders(payload); + + const memberId = selectedWorkspace?.workspaceUser?.memberId; + + for (const { root, currentFolderId, options: payloadOptions, taskId, abortController } of payloadWithTaskId) { + console.time('multiFolder-upload'); + const options = { withNotification: true, ...payloadOptions }; + + let alreadyUploaded = 0; + + let rootFolderItem: DriveFolderData | undefined; + const itemsUnderRoot = countItemsUnderRoot(root); + const uploadFolderAbortController = abortController; + + const uploadFolderAsync = async (level: IRoot) => { + if (uploadFolderAbortController.signal.aborted) return; + + const createdFolder = await createFolder( + { + parentFolderId: level.folderId as string, + folderName: level.name, + options: { relatedTaskId: taskId, showErrors: false }, + }, + currentFolderId, + selectedWorkspace, + { dispatch }, + ); + + if (!rootFolderItem) { + rootFolderItem = createdFolder; + } + + tasksService.updateTask({ + taskId, + merge: { + stop: () => stopUploadTask(uploadFolderAbortController, dispatch, taskId, rootFolderItem), + }, + }); + + if (level.childrenFiles.length > 0 || level.childrenFolders.length > 0) { + // Added wait in order to allow enough time for the server to create the folder + await wait(500); + } + + if (level.childrenFiles) { + if (uploadFolderAbortController.signal.aborted) return; + await dispatch( + uploadItemsParallelThunk({ + files: level.childrenFiles, + parentFolderId: createdFolder.uuid, + options: { + relatedTaskId: taskId, + showNotifications: false, + showErrors: false, + abortController: uploadFolderAbortController, + disableDuplicatedNamesCheck: true, + }, + filesProgress: { filesUploaded: alreadyUploaded, totalFilesToUpload: itemsUnderRoot }, + }), + ) + .unwrap() + .then(() => { + alreadyUploaded += level.childrenFiles.length + 1; + }); + } + + for (const child of level.childrenFolders) { + if (uploadFolderAbortController.signal.aborted) return; + await uploadFolderQueue.pushAsync({ ...child, folderId: createdFolder.uuid }); + } + }; + + const uploadFolderQueue: QueueObject = queue((task, callback) => { + uploadFolderAsync(task) + .then(() => { + callback(); + }) + .catch((e) => { + callback(e); + }); + }, MAX_CONCURRENT_UPLOADS); + + try { + root.folderId = currentFolderId; + await uploadFolderQueue.pushAsync(root); + + while (uploadFolderQueue.running() > 0 || uploadFolderQueue.length() > 0) { + await uploadFolderQueue.drain(); + } + + console.timeEnd('multiFolder-upload'); + + tasksService.updateTask({ + taskId: taskId, + merge: { + itemUUID: { rootFolderUUID: rootFolderItem?.uuid }, + status: TaskStatus.Success, + }, + }); + + options.onSuccess?.(); + + setTimeout(() => { + dispatch(planThunks.fetchUsageThunk()); + if (memberId) dispatch(planThunks.fetchBusinessLimitUsageThunk()); + }, 1000); + } catch (err: unknown) { + const castedError = errorService.castError(err); + const updatedTask = tasksService.findTask(taskId); + + if (updatedTask?.status !== TaskStatus.Cancelled && taskId === updatedTask?.id) { + tasksService.updateTask({ + taskId: taskId, + merge: { + status: TaskStatus.Error, + subtitle: t('tasks.subtitles.upload-failed') as string, + }, + }); + // Log the error or report it but don't re-throw it to allow the next folder to be processed + errorService.reportError(castedError); + continue; + } + } + } +}; From 66a590cd70e7d2b16df90facd2cffcf645ecd634 Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 29 Jan 2025 16:27:42 +0100 Subject: [PATCH 03/28] Add @svgr/rollup for SVG handling in Vitest configuration --- package.json | 1 + vitest.config.js | 6 + yarn.lock | 1114 +++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 1109 insertions(+), 12 deletions(-) diff --git a/package.json b/package.json index efc591002..a5ad3886f 100644 --- a/package.json +++ b/package.json @@ -125,6 +125,7 @@ "@internxt/eslint-config-internxt": "^1.0.3", "@internxt/prettier-config": "^1.0.1", "@playwright/test": "^1.44.1", + "@svgr/rollup": "^8.1.0", "@testing-library/dom": "^7.26.0", "@testing-library/jest-dom": "^6.6.2", "@testing-library/react": "^16.0.1", diff --git a/vitest.config.js b/vitest.config.js index 4457f918a..83f11a1ad 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -3,6 +3,7 @@ import replace from '@rollup/plugin-replace'; import react from '@vitejs/plugin-react'; import path from 'path'; import { defineConfig } from 'vitest/config'; +import svgr from '@svgr/rollup'; export default defineConfig({ plugins: [ @@ -11,6 +12,10 @@ export default defineConfig({ preventAssignment: true, 'process.browser': true, }), + svgr({ + svgrOptions: { native: true }, + include: '**/*.svg', + }), ], resolve: { alias: { @@ -18,6 +23,7 @@ export default defineConfig({ app: path.resolve(__dirname, './src/app'), crypto: 'crypto-browserify', // Resolve `crypto` to `crypto-browserify` stream: 'stream-browserify', + path: 'path-browserify', }, }, test: { diff --git a/yarn.lock b/yarn.lock index 5ccdd6db3..3c08e507c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -49,7 +49,7 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0": +"@babel/code-frame@^7.25.9", "@babel/code-frame@^7.26.0", "@babel/code-frame@^7.26.2": version "7.26.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.26.2.tgz#4b5fab97d33338eff916235055f0ebc21e573a85" integrity sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ== @@ -68,6 +68,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.2.tgz#278b6b13664557de95b8f35b90d96785850bb56e" integrity sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg== +"@babel/compat-data@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.26.5.tgz#df93ac37f4417854130e21d72c66ff3d4b897fc7" + integrity sha512-XvcZi1KWf88RVbF9wn8MN6tYFloU5qX8KjuF3E1PVBmJ9eypXfs4GRiJwLuTZL0iSnJUKn1BFPa5BPZZJyFzPg== + "@babel/core@^7.1.0", "@babel/core@^7.11.1", "@babel/core@^7.12.3", "@babel/core@^7.16.0", "@babel/core@^7.7.2", "@babel/core@^7.8.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.23.0.tgz#f8259ae0e52a123eb40f552551e647b506a94d83" @@ -89,6 +94,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.21.3": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.7.tgz#0439347a183b97534d52811144d763a17f9d2b24" + integrity sha512-SRijHmF0PSPgLIBYlWnG0hyeJLwXE2CgpsXaMOrtt2yp9/86ALw6oUlj9KYuZ0JN07T4eBMVIW4li/9S1j2BGA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helpers" "^7.26.7" + "@babel/parser" "^7.26.7" + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.26.7" + "@babel/types" "^7.26.7" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/core@^7.23.9", "@babel/core@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.26.0.tgz#d78b6023cc8f3114ccf049eb219613f74a747b40" @@ -140,6 +166,17 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^3.0.2" +"@babel/generator@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.26.5.tgz#e44d4ab3176bbcaf78a5725da5f1dc28802a9458" + integrity sha512-2caSP6fN9I7HOe6nqhtft7V4g7/V/gfDsC3Ag4W7kEzzvRGKqiv0pu0HogPiZ3KaVSoNDhUws6IJjDjpfmYIXw== + dependencies: + "@babel/parser" "^7.26.5" + "@babel/types" "^7.26.5" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^3.0.2" + "@babel/helper-annotate-as-pure@^7.18.6", "@babel/helper-annotate-as-pure@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.25.9.tgz#d8eac4d2dc0d7b6e11fa6e535332e0d3184f06b4" @@ -183,6 +220,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz#75d92bb8d8d51301c0d49e52a65c9a7fe94514d8" + integrity sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + browserslist "^4.24.0" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.22.11", "@babel/helper-create-class-features-plugin@^7.22.15", "@babel/helper-create-class-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.15.tgz#97a61b385e57fe458496fad19f8e63b63c867de4" @@ -198,7 +246,7 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.21.0": +"@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz#7644147706bb90ff613297d49ed5266bde729f83" integrity sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ== @@ -220,6 +268,15 @@ regexpu-core "^5.3.1" semver "^6.3.1" +"@babel/helper-create-regexp-features-plugin@^7.25.9": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.26.3.tgz#5169756ecbe1d95f7866b90bb555b022595302a0" + integrity sha512-G7ZRb40uUgdKOQqPLjfD12ZmGA54PzqDFUv2BKImnC9QIfGhIHKvVML0oN8IUiDq4iRqpq74ABpvOaerfWdong== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + regexpu-core "^6.2.0" + semver "^6.3.1" + "@babel/helper-define-polyfill-provider@^0.4.3": version "0.4.3" resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.3.tgz#a71c10f7146d809f4a256c373f462d9bba8cf6ba" @@ -231,6 +288,17 @@ lodash.debounce "^4.0.8" resolve "^1.14.2" +"@babel/helper-define-polyfill-provider@^0.6.2", "@babel/helper-define-polyfill-provider@^0.6.3": + version "0.6.3" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.3.tgz#f4f2792fae2ef382074bc2d713522cf24e6ddb21" + integrity sha512-HK7Bi+Hj6H+VTHA3ZvBis7V/6hu9QuTrnMXNybfUf2iiuU/N97I8VjB+KbhFF8Rld/Lx5MzoCwPCpPjfK+n8Cg== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + "@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" @@ -292,7 +360,7 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.20" -"@babel/helper-module-transforms@^7.26.0": +"@babel/helper-module-transforms@^7.25.9", "@babel/helper-module-transforms@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz#8ce54ec9d592695e58d84cd884b7b5c6a2fdeeae" integrity sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw== @@ -325,6 +393,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz#9cbdd63a9443a2c92a725cca7ebca12cc8dd9f46" integrity sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw== +"@babel/helper-plugin-utils@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz#18580d00c9934117ad719392c4f6585c9333cc35" + integrity sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg== + "@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" @@ -334,6 +407,15 @@ "@babel/helper-environment-visitor" "^7.22.20" "@babel/helper-wrap-function" "^7.22.20" +"@babel/helper-remap-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.9.tgz#e53956ab3d5b9fb88be04b3e2f31b523afd34b92" + integrity sha512-IZtukuUeBbhgOcaW2s06OXTzVNJR0ybm4W5xC1opWFFJMZbwRj5LCk+ByYH7WdZPZTt8KnFwA8pvjN2yqcPlgw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-wrap-function" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/helper-replace-supers@^7.22.20", "@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" @@ -420,6 +502,15 @@ "@babel/template" "^7.22.15" "@babel/types" "^7.22.19" +"@babel/helper-wrap-function@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.25.9.tgz#d99dfd595312e6c894bd7d237470025c85eea9d0" + integrity sha512-ETzz9UTjQSTmw39GboatdymDq4XIQbR8ySgVrylRhPOFpsd+JrKHIuF0de7GCWmem+T4uC5z7EZguod7Wj4A4g== + dependencies: + "@babel/template" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/helpers@^7.23.0": version "7.23.1" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.1.tgz#44e981e8ce2b9e99f8f0b703f3326a4636c16d15" @@ -437,6 +528,14 @@ "@babel/template" "^7.25.9" "@babel/types" "^7.26.0" +"@babel/helpers@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.26.7.tgz#fd1d2a7c431b6e39290277aacfd8367857c576a4" + integrity sha512-8NHiL98vsi0mbPQmYAGWwfcFaOy4j2HY49fXJCfuDcdE7fMIsH9a7GdaeXpIBsbT7307WU8KCMp5pUVDNL4f9A== + dependencies: + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.7" + "@babel/highlight@^7.10.4", "@babel/highlight@^7.22.13": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54" @@ -458,6 +557,28 @@ dependencies: "@babel/types" "^7.26.0" +"@babel/parser@^7.26.5", "@babel/parser@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.26.7.tgz#e114cd099e5f7d17b05368678da0fb9f69b3385c" + integrity sha512-kEvgGGgEjRUutvdVvZhbn/BxVt+5VSpwXz1j3WYXQbXDo8KzFOPNG2GQbdAiNq8g6wn1yKk7C/qrke03a84V+w== + dependencies: + "@babel/types" "^7.26.7" + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.9.tgz#cc2e53ebf0a0340777fff5ed521943e253b4d8fe" + integrity sha512-ZkRyVkThtxQ/J6nv3JFYv1RYY+JT5BvU0y3k5bWrmuG4woXypRa4PXmm9RhOwodRkYFWqC0C0cqcJ4OqR7kW+g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + +"@babel/plugin-bugfix-safari-class-field-initializer-scope@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.9.tgz#af9e4fb63ccb8abcb92375b2fcfe36b60c774d30" + integrity sha512-MrGRLZxLD/Zjj0gdU15dfs+HH/OXvnw/U4jJD8vpcP2CJQapPEv1IWwjc/qMg7ItBlPwSv1hRBbb7LeuANdcnw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.22.15.tgz#02dc8a03f613ed5fdc29fb2f728397c78146c962" @@ -465,6 +586,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.9.tgz#e8dc26fcd616e6c5bf2bd0d5a2c151d4f92a9137" + integrity sha512-2qUwwfAFpJLZqxd02YW9btUCZHl+RFvdDkNfZwaIJrvB8Tesjsk8pEQkTvGwZXLqXUx/2oyY3ySRhm6HOXuCug== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.15.tgz#2aeb91d337d4e1a1e7ce85b76a37f5301781200f" @@ -474,6 +602,23 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-transform-optional-chaining" "^7.22.15" +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.25.9.tgz#807a667f9158acac6f6164b4beb85ad9ebc9e1d1" + integrity sha512-6xWgLZTJXwilVjlnV7ospI3xi+sl8lN8rXXbBD6vYn3UYDlGsag8wrZkKcSI8G6KgqKP7vNFaDgeDnfAABq61g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.9.tgz#de7093f1e7deaf68eadd7cc6b07f2ab82543269e" + integrity sha512-aLnMXYPnzwwqhYSCyXfKkIkYgJ8zv9RK+roo9DkTXz38ynIhd9XCbN08s3MGvqL2MYGVUGdRQLL/JqBIeJhJBg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-proposal-class-properties@^7.16.0": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz#b110f59741895f7ec21a6fff696ec46265c446a3" @@ -604,6 +749,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-import-assertions@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.26.0.tgz#620412405058efa56e4a564903b79355020f445f" + integrity sha512-QCWT5Hh830hK5EQa7XzuqIkQU9tT/whqbDz7kuaZMHFl1inRRg7JnuAEOQ0Ur0QUl0NufCk1msK2BeY79Aj/eg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-import-attributes@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb" @@ -611,6 +763,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-import-attributes@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.26.0.tgz#3b1412847699eea739b4f2602c74ce36f6b0b0f7" + integrity sha512-e2dttdsJ1ZTpi3B9UYGLw41hifAubg19AtCu/2I/F1QNVclOBr1dYpTdmdyZ84Xiz43BS/tCUkMAZNLv12Pi+A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-import-meta@^7.10.4", "@babel/plugin-syntax-import-meta@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" @@ -632,6 +791,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.25.9.tgz#a34313a178ea56f1951599b929c1ceacee719290" + integrity sha512-ld6oezHQMZsZfp6pWtbjaNDF2tiiCYYDqQszHt5VV437lewP9aSi2Of99CK0D0XB21k7FLgnLcmQKyKzynfeAA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-logical-assignment-operators@^7.10.4", "@babel/plugin-syntax-logical-assignment-operators@^7.8.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" @@ -695,6 +861,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-syntax-typescript@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.9.tgz#67dda2b74da43727cf21d46cf9afef23f4365399" + integrity sha512-hjMgRy5hb8uJJjUcdWunWVcoi9bGpJp8p5Ol1229PoN6aytsLwNMgmdftO23wnCLMfVmTwZDWMPNq/D1SY60JQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-unicode-sets-regex@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" @@ -710,6 +883,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-arrow-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.25.9.tgz#7821d4410bee5daaadbb4cdd9a6649704e176845" + integrity sha512-6jmooXYIwn9ca5/RylZADJ+EnSxVUS5sjeJ9UPk6RWRzXCmOJCy6dqItPJFpw2cuCangPK4OYr5uhGKcmrm5Qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-async-generator-functions@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.15.tgz#3b153af4a6b779f340d5b80d3f634f55820aefa3" @@ -720,6 +900,15 @@ "@babel/helper-remap-async-to-generator" "^7.22.9" "@babel/plugin-syntax-async-generators" "^7.8.4" +"@babel/plugin-transform-async-generator-functions@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.9.tgz#1b18530b077d18a407c494eb3d1d72da505283a2" + integrity sha512-RXV6QAzTBbhDMO9fWwOmwwTuYaiPbggWQ9INdZqAYeSHyG7FzQ+nOZaUUjNwKv9pV3aE4WFqFm1Hnbci5tBCAw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-transform-async-to-generator@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775" @@ -729,6 +918,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-remap-async-to-generator" "^7.22.5" +"@babel/plugin-transform-async-to-generator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.25.9.tgz#c80008dacae51482793e5a9c08b39a5be7e12d71" + integrity sha512-NT7Ejn7Z/LjUH0Gv5KsBCxh7BH3fbLTV0ptHvpeMvrt3cPThHfJfst9Wrb7S8EvJ7vRTFI7z+VAvFVEQn/m5zQ== + dependencies: + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-remap-async-to-generator" "^7.25.9" + "@babel/plugin-transform-block-scoped-functions@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024" @@ -736,6 +934,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-block-scoped-functions@^7.26.5": + version "7.26.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.26.5.tgz#3dc4405d31ad1cbe45293aa57205a6e3b009d53e" + integrity sha512-chuTSY+hq09+/f5lMj8ZSYgCFpppV2CbYrhNFJ1BFoXpiWPnnAb7R0MqrafCpN8E1+YRrtM1MXZHJdIx8B6rMQ== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/plugin-transform-block-scoping@^7.22.15": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.23.0.tgz#8744d02c6c264d82e1a4bc5d2d501fd8aff6f022" @@ -743,6 +948,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-block-scoping@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.9.tgz#c33665e46b06759c93687ca0f84395b80c0473a1" + integrity sha512-1F05O7AYjymAtqbsFETboN1NvBdcnzMerO+zlMyJBEz6WkMdejvGWw9p05iTSjC85RLlBseHHQpYaM4gzJkBGg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-class-properties@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77" @@ -751,6 +963,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-class-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.25.9.tgz#a8ce84fedb9ad512549984101fa84080a9f5f51f" + integrity sha512-bbMAII8GRSkcd0h0b4X+36GksxuheLFjP65ul9w6C3KgAamI3JqErNgSrosX6ZPj+Mpim5VvEbawXxJCyEUV3Q== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-class-static-block@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.11.tgz#dc8cc6e498f55692ac6b4b89e56d87cec766c974" @@ -760,6 +980,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-transform-class-static-block@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.26.0.tgz#6c8da219f4eb15cae9834ec4348ff8e9e09664a0" + integrity sha512-6J2APTs7BDDm+UMqP1useWqhcRAXo0WIoVj26N7kPFB6S73Lgvyka4KTZYIxtgYXiN5HTyRObA72N2iu628iTQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-classes@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.15.tgz#aaf4753aee262a232bbc95451b4bdf9599c65a0b" @@ -775,6 +1003,18 @@ "@babel/helper-split-export-declaration" "^7.22.6" globals "^11.1.0" +"@babel/plugin-transform-classes@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.9.tgz#7152457f7880b593a63ade8a861e6e26a4469f52" + integrity sha512-mD8APIXmseE7oZvZgGABDyM34GUmK45Um2TXiBUt7PnuAxrgoSVf123qUzPxEr/+/BHrRn5NMZCdE2m/1F8DGg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/traverse" "^7.25.9" + globals "^11.1.0" + "@babel/plugin-transform-computed-properties@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869" @@ -783,6 +1023,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/template" "^7.22.5" +"@babel/plugin-transform-computed-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.25.9.tgz#db36492c78460e534b8852b1d5befe3c923ef10b" + integrity sha512-HnBegGqXZR12xbcTHlJ9HGxw1OniltT26J5YpfruGqtUHlz/xKf/G2ak9e+t0rVqrjXa9WOhvYPz1ERfMj23AA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/template" "^7.25.9" + "@babel/plugin-transform-destructuring@^7.22.15": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.23.0.tgz#6447aa686be48b32eaf65a73e0e2c0bd010a266c" @@ -790,6 +1038,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-destructuring@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.25.9.tgz#966ea2595c498224340883602d3cfd7a0c79cea1" + integrity sha512-WkCGb/3ZxXepmMiX101nnGiU+1CAdut8oHyEOHxkKuS1qKpU2SMXE2uSvfz8PBuLd49V6LEsbtyPhWC7fnkgvQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-dotall-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165" @@ -798,6 +1053,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-dotall-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.25.9.tgz#bad7945dd07734ca52fe3ad4e872b40ed09bb09a" + integrity sha512-t7ZQ7g5trIgSRYhI9pIJtRl64KHotutUJsh4Eze5l7olJv+mRSg4/MmbZ0tv1eeqRbdvo/+trvJD/Oc5DmW2cA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-duplicate-keys@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285" @@ -805,6 +1068,21 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-duplicate-keys@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.25.9.tgz#8850ddf57dce2aebb4394bb434a7598031059e6d" + integrity sha512-LZxhJ6dvBb/f3x8xwWIuyiAHy56nrRG3PeYTpBkkzkYRRQ6tJLu68lEF5VIqMUZiAV7a8+Tb78nEoMCMcqjXBw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + +"@babel/plugin-transform-duplicate-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.9.tgz#6f7259b4de127721a08f1e5165b852fcaa696d31" + integrity sha512-0UfuJS0EsXbRvKnwcLjFtJy/Sxc5J5jhLHnFhy7u4zih97Hz6tJkLU+O+FMMrNZrosUPxDi6sYxJ/EA8jDiAog== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-dynamic-import@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.11.tgz#2c7722d2a5c01839eaf31518c6ff96d408e447aa" @@ -813,6 +1091,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-dynamic-import" "^7.8.3" +"@babel/plugin-transform-dynamic-import@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.25.9.tgz#23e917de63ed23c6600c5dd06d94669dce79f7b8" + integrity sha512-GCggjexbmSLaFhqsojeugBpeaRIgWNTcgKVq/0qIteFEqY2A+b9QidYadrWlnbWQUrW5fn+mCvf3tr7OeBFTyg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-exponentiation-operator@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a" @@ -821,6 +1106,13 @@ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-exponentiation-operator@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.26.3.tgz#e29f01b6de302c7c2c794277a48f04a9ca7f03bc" + integrity sha512-7CAHcQ58z2chuXPWblnn1K6rLDnDWieghSOEmqQsrBenH0P9InCUtOJYD89pvngljmZlJcz3fcmgYsXFNGa1ZQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-export-namespace-from@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.11.tgz#b3c84c8f19880b6c7440108f8929caf6056db26c" @@ -829,6 +1121,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-export-namespace-from" "^7.8.3" +"@babel/plugin-transform-export-namespace-from@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.25.9.tgz#90745fe55053394f554e40584cda81f2c8a402a2" + integrity sha512-2NsEz+CxzJIVOPx2o9UsW1rXLqtChtLoVnwYHHiB04wS5sgn7mrV45fWMBX0Kk+ub9uXytVYfNP2HjbVbCB3Ww== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-flow-strip-types@^7.16.0": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.22.5.tgz#0bb17110c7bf5b35a60754b2f00c58302381dee2" @@ -844,6 +1143,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-for-of@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.25.9.tgz#4bdc7d42a213397905d89f02350c5267866d5755" + integrity sha512-LqHxduHoaGELJl2uhImHwRQudhCM50pT46rIBNvtT/Oql3nqiS3wOwP+5ten7NpYSXrrVLgtZU3DZmPtWZo16A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-function-name@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143" @@ -853,6 +1160,15 @@ "@babel/helper-function-name" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-function-name@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.9.tgz#939d956e68a606661005bfd550c4fc2ef95f7b97" + integrity sha512-8lP+Yxjv14Vc5MuWBpJsoUCd3hD6V9DgBon2FVYL4jJgbnVQ9fTgYmonchzZJOVNgzEgbxp4OwAf6xz6M/14XA== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-transform-json-strings@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.11.tgz#689a34e1eed1928a40954e37f74509f48af67835" @@ -861,6 +1177,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-json-strings" "^7.8.3" +"@babel/plugin-transform-json-strings@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.25.9.tgz#c86db407cb827cded902a90c707d2781aaa89660" + integrity sha512-xoTMk0WXceiiIvsaquQQUaLLXSW1KJ159KP87VilruQm0LNNGxWzahxSS6T6i4Zg3ezp4vA4zuwiNUR53qmQAw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920" @@ -868,6 +1191,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.9.tgz#1a1c6b4d4aa59bc4cad5b6b3a223a0abd685c9de" + integrity sha512-9N7+2lFziW8W9pBl2TzaNht3+pgMIRP74zizeCSrtnSKVdUl8mAjjOP2OOVQAfZ881P2cNjDj1uAMEdeD50nuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-logical-assignment-operators@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.11.tgz#24c522a61688bde045b7d9bc3c2597a4d948fc9c" @@ -876,6 +1206,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" +"@babel/plugin-transform-logical-assignment-operators@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.25.9.tgz#b19441a8c39a2fda0902900b306ea05ae1055db7" + integrity sha512-wI4wRAzGko551Y8eVf6iOY9EouIDTtPb0ByZx+ktDGHwv6bHFimrgJM/2T021txPZ2s4c7bqvHbd+vXG6K948Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-member-expression-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def" @@ -883,6 +1220,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-member-expression-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.25.9.tgz#63dff19763ea64a31f5e6c20957e6a25e41ed5de" + integrity sha512-PYazBVfofCQkkMzh2P6IdIUaCEWni3iYEerAsRWuVd8+jlM1S9S9cz1dF9hIzyoZ8IA3+OwVYIp9v9e+GbgZhA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-modules-amd@^7.22.5": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.23.0.tgz#05b2bc43373faa6d30ca89214731f76f966f3b88" @@ -891,6 +1235,14 @@ "@babel/helper-module-transforms" "^7.23.0" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-modules-amd@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.25.9.tgz#49ba478f2295101544abd794486cd3088dddb6c5" + integrity sha512-g5T11tnI36jVClQlMlt4qKDLlWnG5pP9CSM4GhdRciTNMRgkfpo5cR6b4rGIOYPgRRuFAvwjPQ/Yk+ql4dyhbw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-modules-commonjs@^7.22.15", "@babel/plugin-transform-modules-commonjs@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.23.0.tgz#b3dba4757133b2762c00f4f94590cf6d52602481" @@ -900,6 +1252,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-simple-access" "^7.22.5" +"@babel/plugin-transform-modules-commonjs@^7.25.9", "@babel/plugin-transform-modules-commonjs@^7.26.3": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.26.3.tgz#8f011d44b20d02c3de44d8850d971d8497f981fb" + integrity sha512-MgR55l4q9KddUDITEzEFYn5ZsGDXMSsU9E+kh7fjRXTIC3RHqfCo8RPRbyReYJh44HQ/yomFkqbOFohXvDCiIQ== + dependencies: + "@babel/helper-module-transforms" "^7.26.0" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-modules-systemjs@^7.22.11": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.23.0.tgz#77591e126f3ff4132a40595a6cccd00a6b60d160" @@ -910,6 +1270,16 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-validator-identifier" "^7.22.20" +"@babel/plugin-transform-modules-systemjs@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.9.tgz#8bd1b43836269e3d33307151a114bcf3ba6793f8" + integrity sha512-hyss7iIlH/zLHaehT+xwiymtPOpsiwIIRlCAOwBB04ta5Tt+lNItADdlXw3jAWZ96VJ2jlhl/c+PNIQPKNfvcA== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/traverse" "^7.25.9" + "@babel/plugin-transform-modules-umd@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98" @@ -918,6 +1288,14 @@ "@babel/helper-module-transforms" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-modules-umd@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.25.9.tgz#6710079cdd7c694db36529a1e8411e49fcbf14c9" + integrity sha512-bS9MVObUgE7ww36HEfwe6g9WakQ0KF07mQF74uuXdkoziUPfKyu/nIm663kz//e5O1nPInPFx36z7WJmJ4yNEw== + dependencies: + "@babel/helper-module-transforms" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f" @@ -926,6 +1304,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-named-capturing-groups-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.25.9.tgz#454990ae6cc22fd2a0fa60b3a2c6f63a38064e6a" + integrity sha512-oqB6WHdKTGl3q/ItQhpLSnWWOpjUJLsOCLVyeFgeTktkBSCiurvPOsyt93gibI9CmuKvTUEtWmG5VhZD+5T/KA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-new-target@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d" @@ -933,6 +1319,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-new-target@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.25.9.tgz#42e61711294b105c248336dcb04b77054ea8becd" + integrity sha512-U/3p8X1yCSoKyUj2eOBIx3FOn6pElFOKvAAGf8HTtItuPyB+ZeOqfn+mvTtg9ZlOAjsPdK3ayQEjqHjU/yLeVQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-nullish-coalescing-operator@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.11.tgz#debef6c8ba795f5ac67cd861a81b744c5d38d9fc" @@ -941,6 +1334,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" +"@babel/plugin-transform-nullish-coalescing-operator@^7.26.6": + version "7.26.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.26.6.tgz#fbf6b3c92cb509e7b319ee46e3da89c5bedd31fe" + integrity sha512-CKW8Vu+uUZneQCPtXmSBUC6NCAUdya26hWCElAWh5mVSlSRsmiCPUUDKb3Z0szng1hiAJa098Hkhg9o4SE35Qw== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/plugin-transform-numeric-separator@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.11.tgz#498d77dc45a6c6db74bb829c02a01c1d719cbfbd" @@ -949,6 +1349,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-numeric-separator" "^7.10.4" +"@babel/plugin-transform-numeric-separator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.25.9.tgz#bfed75866261a8b643468b0ccfd275f2033214a1" + integrity sha512-TlprrJ1GBZ3r6s96Yq8gEQv82s8/5HnCVHtEJScUj90thHQbwe+E5MLhi2bbNHBEJuzrvltXSru+BUxHDoog7Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-object-rest-spread@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.15.tgz#21a95db166be59b91cde48775310c0df6e1da56f" @@ -960,6 +1367,15 @@ "@babel/plugin-syntax-object-rest-spread" "^7.8.3" "@babel/plugin-transform-parameters" "^7.22.15" +"@babel/plugin-transform-object-rest-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.25.9.tgz#0203725025074164808bcf1a2cfa90c652c99f18" + integrity sha512-fSaXafEE9CVHPweLYw4J0emp1t8zYTXyzN3UuG+lylqkvYd7RMrsOQ8TYx5RF231be0vqtFC6jnx3UmpJmKBYg== + dependencies: + "@babel/helper-compilation-targets" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/plugin-transform-object-super@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c" @@ -968,6 +1384,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-replace-supers" "^7.22.5" +"@babel/plugin-transform-object-super@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.25.9.tgz#385d5de135162933beb4a3d227a2b7e52bb4cf03" + integrity sha512-Kj/Gh+Rw2RNLbCK1VAWj2U48yxxqL2x0k10nPtSdRa0O2xnHXalD0s+o1A6a0W43gJ00ANo38jxkQreckOzv5A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-replace-supers" "^7.25.9" + "@babel/plugin-transform-optional-catch-binding@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.11.tgz#461cc4f578a127bb055527b3e77404cad38c08e0" @@ -976,6 +1400,13 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" +"@babel/plugin-transform-optional-catch-binding@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.25.9.tgz#10e70d96d52bb1f10c5caaac59ac545ea2ba7ff3" + integrity sha512-qM/6m6hQZzDcZF3onzIhZeDHDO43bkNNlOX0i8n3lR6zLbu0GN2d8qfM/IERJZYauhAHSLHy39NF0Ctdvcid7g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-optional-chaining@^7.22.15": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.23.0.tgz#73ff5fc1cf98f542f09f29c0631647d8ad0be158" @@ -985,6 +1416,14 @@ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" "@babel/plugin-syntax-optional-chaining" "^7.8.3" +"@babel/plugin-transform-optional-chaining@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.25.9.tgz#e142eb899d26ef715435f201ab6e139541eee7dd" + integrity sha512-6AvV0FsLULbpnXeBjrY4dmWF8F7gf8QnvTEoO/wX/5xm/xE1Xo8oPuD3MPS+KS9f9XBEAWN7X1aWr4z9HdOr7A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-parameters@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.15.tgz#719ca82a01d177af358df64a514d64c2e3edb114" @@ -992,6 +1431,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-parameters@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.25.9.tgz#b856842205b3e77e18b7a7a1b94958069c7ba257" + integrity sha512-wzz6MKwpnshBAiRmn4jR8LYz/g8Ksg0o80XmwZDlordjwEk9SxBzTWC7F5ef1jhbrbOW2DJ5J6ayRukrJmnr0g== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-private-methods@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722" @@ -1000,6 +1446,14 @@ "@babel/helper-create-class-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-private-methods@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.25.9.tgz#847f4139263577526455d7d3223cd8bda51e3b57" + integrity sha512-D/JUozNpQLAPUVusvqMxyvjzllRaF8/nSrP1s2YGQT/W4LHK4xxsMcHjhOGTS01mp9Hda8nswb+FblLdJornQw== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-private-property-in-object@^7.22.11": version "7.22.11" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.11.tgz#ad45c4fc440e9cb84c718ed0906d96cf40f9a4e1" @@ -1010,6 +1464,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-private-property-in-object" "^7.14.5" +"@babel/plugin-transform-private-property-in-object@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.25.9.tgz#9c8b73e64e6cc3cbb2743633885a7dd2c385fe33" + integrity sha512-Evf3kcMqzXA3xfYJmZ9Pg1OvKdtqsDMSWBDzZOPLvHiTt36E75jLDQo5w1gtRU95Q4E5PDttrTf25Fw8d/uWLw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-property-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766" @@ -1017,6 +1480,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-property-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.25.9.tgz#d72d588bd88b0dec8b62e36f6fda91cedfe28e3f" + integrity sha512-IvIUeV5KrS/VPavfSM/Iu+RE6llrHrYIKY1yfCzyO/lMXHQ+p7uGhonmGVisv6tSBSVgWzMBohTcvkC9vQcQFA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-react-constant-elements@^7.12.1": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.22.5.tgz#6dfa7c1c37f7d7279e417ceddf5a04abb8bb9c29" @@ -1024,6 +1494,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-react-constant-elements@^7.21.3": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-constant-elements/-/plugin-transform-react-constant-elements-7.25.9.tgz#08a1de35a301929b60fdf2788a54b46cd8ecd0ef" + integrity sha512-Ncw2JFsJVuvfRsa2lSHiC55kETQVLSnsYGQ1JDDwkUeWGTL/8Tom8aLTnlqgoeuopWrbbGndrc9AlLYrIosrow== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-react-display-name@^7.16.0", "@babel/plugin-transform-react-display-name@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b" @@ -1031,6 +1508,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-react-display-name@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.25.9.tgz#4b79746b59efa1f38c8695065a92a9f5afb24f7d" + integrity sha512-KJfMlYIUxQB1CJfO3e0+h0ZHWOTLCPP115Awhaz8U0Zpq36Gl/cXlpoyMRnUWlhNUBAzldnCiAZNvCDj7CrKxQ== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-react-jsx-development@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87" @@ -1038,6 +1522,13 @@ dependencies: "@babel/plugin-transform-react-jsx" "^7.22.5" +"@babel/plugin-transform-react-jsx-development@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.25.9.tgz#8fd220a77dd139c07e25225a903b8be8c829e0d7" + integrity sha512-9mj6rm7XVYs4mdLIpbZnHOYdpW42uoiBCTVowg7sP1thUOiANgMb4UtpRivR0pp5iL+ocvUv7X4mZgFRpJEzGw== + dependencies: + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx-self@^7.25.9": version "7.25.9" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.25.9.tgz#c0b6cae9c1b73967f7f9eb2fca9536ba2fad2858" @@ -1063,6 +1554,17 @@ "@babel/plugin-syntax-jsx" "^7.22.5" "@babel/types" "^7.22.15" +"@babel/plugin-transform-react-jsx@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.9.tgz#06367940d8325b36edff5e2b9cbe782947ca4166" + integrity sha512-s5XwpQYCqGerXl+Pu6VDL3x0j2d82eiV77UJ8a2mDHAW7j9SWRqQ2y1fNo1Z74CdcYipl5Z41zvjj4Nfzq36rw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-module-imports" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/types" "^7.25.9" + "@babel/plugin-transform-react-pure-annotations@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0" @@ -1071,6 +1573,14 @@ "@babel/helper-annotate-as-pure" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-react-pure-annotations@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.25.9.tgz#ea1c11b2f9dbb8e2d97025f43a3b5bc47e18ae62" + integrity sha512-KQ/Takk3T8Qzj5TppkS1be588lkbTp5uj7w6a0LeQaTMSckU/wK0oJ/pih+T690tkgI5jfmg2TqDJvd41Sj1Cg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-regenerator@^7.22.10": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca" @@ -1079,6 +1589,22 @@ "@babel/helper-plugin-utils" "^7.22.5" regenerator-transform "^0.15.2" +"@babel/plugin-transform-regenerator@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.25.9.tgz#03a8a4670d6cebae95305ac6defac81ece77740b" + integrity sha512-vwDcDNsgMPDGP0nMqzahDWE5/MLcX8sv96+wfX7as7LoF/kr97Bo/7fI00lXY4wUXYfVmwIIyG80fGZ1uvt2qg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-regexp-modifiers@^7.26.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.26.0.tgz#2f5837a5b5cd3842a919d8147e9903cc7455b850" + integrity sha512-vN6saax7lrA2yA/Pak3sCxuD6F5InBjn9IcrIKQPjpsLvuHYLVroTxjdlVRHjjBWxKOqIwpTXDkOssYT4BFdRw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-reserved-words@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb" @@ -1086,6 +1612,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-reserved-words@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.25.9.tgz#0398aed2f1f10ba3f78a93db219b27ef417fb9ce" + integrity sha512-7DL7DKYjn5Su++4RXu8puKZm2XBPHyjWLUidaPEkCUBbE7IPcsrkRHggAOOKydH1dASWdcUBxrkOGNxUv5P3Jg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-runtime@^7.12.1", "@babel/plugin-transform-runtime@^7.16.4": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.22.15.tgz#3a625c4c05a39e932d7d34f5d4895cdd0172fdc9" @@ -1105,6 +1638,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-shorthand-properties@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.25.9.tgz#bb785e6091f99f826a95f9894fc16fde61c163f2" + integrity sha512-MUv6t0FhO5qHnS/W8XCbHmiRWOphNufpE1IVxhK5kuN3Td9FT1x4rx4K42s3RYdMXCXpfWkGSbCSd0Z64xA7Ng== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-spread@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b" @@ -1113,6 +1653,14 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5" +"@babel/plugin-transform-spread@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.25.9.tgz#24a35153931b4ba3d13cec4a7748c21ab5514ef9" + integrity sha512-oNknIB0TbURU5pqJFVbOOFspVlrpVwo2H1+HUIsVDvp5VauGGDP1ZEvO8Nn5xyMEs3dakajOxlmkNW7kNgSm6A== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-transform-sticky-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa" @@ -1120,6 +1668,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-sticky-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.25.9.tgz#c7f02b944e986a417817b20ba2c504dfc1453d32" + integrity sha512-WqBUSgeVwucYDP9U/xNRQam7xV8W5Zf+6Eo7T2SRVUFlhRiMNFdFz58u0KZmCVVqs2i7SHgpRnAhzRNmKfi2uA== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-template-literals@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff" @@ -1127,6 +1682,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-template-literals@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.25.9.tgz#6dbd4a24e8fad024df76d1fac6a03cf413f60fe1" + integrity sha512-o97AE4syN71M/lxrCtQByzphAdlYluKPDBzDVzMmfCobUjjhAryZV0AIpRPrxN0eAkxXO6ZLEScmt+PNhj2OTw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-typeof-symbol@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34" @@ -1134,6 +1696,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-typeof-symbol@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.26.7.tgz#d0e33acd9223744c1e857dbd6fa17bd0a3786937" + integrity sha512-jfoTXXZTgGg36BmhqT3cAYK5qkmqvJpvNrPhaK/52Vgjhw4Rq29s9UqpWWV0D6yuRmgiFH/BUVlkl96zJWqnaw== + dependencies: + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/plugin-transform-typescript@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.22.15.tgz#15adef906451d86349eb4b8764865c960eb54127" @@ -1144,6 +1713,17 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-typescript" "^7.22.5" +"@babel/plugin-transform-typescript@^7.25.9": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.7.tgz#64339515ea3eff610160f62499c3ef437d0ac83d" + integrity sha512-5cJurntg+AT+cgelGP9Bt788DKiAw9gIMSMU2NJrLAilnj0m8WZWUNZPSLOmadYsujHutpgElO+50foX+ib/Wg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.25.9" + "@babel/helper-create-class-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-skip-transparent-expression-wrappers" "^7.25.9" + "@babel/plugin-syntax-typescript" "^7.25.9" + "@babel/plugin-transform-unicode-escapes@^7.22.10": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9" @@ -1151,6 +1731,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-escapes@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.25.9.tgz#a75ef3947ce15363fccaa38e2dd9bc70b2788b82" + integrity sha512-s5EDrE6bW97LtxOcGj1Khcx5AaXwiMmi4toFWRDP9/y0Woo6pXC+iyPu/KuhKtfSrNFd7jJB+/fkOtZy6aIC6Q== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-unicode-property-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81" @@ -1159,6 +1746,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-property-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.25.9.tgz#a901e96f2c1d071b0d1bb5dc0d3c880ce8f53dd3" + integrity sha512-Jt2d8Ga+QwRluxRQ307Vlxa6dMrYEMZCgGxoPR8V52rxPyldHu3hdlHspxaqYmE7oID5+kB+UKUB/eWS+DkkWg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-unicode-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183" @@ -1167,6 +1762,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.25.9.tgz#5eae747fe39eacf13a8bd006a4fb0b5d1fa5e9b1" + integrity sha512-yoxstj7Rg9dlNn9UQxzk4fcNivwv4nUYz7fYXBaKxvw/lnmPuOm/ikoELygbYq68Bls3D/D+NBPHiLwZdZZ4HA== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/plugin-transform-unicode-sets-regex@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91" @@ -1175,6 +1778,14 @@ "@babel/helper-create-regexp-features-plugin" "^7.22.5" "@babel/helper-plugin-utils" "^7.22.5" +"@babel/plugin-transform-unicode-sets-regex@^7.25.9": + version "7.25.9" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.25.9.tgz#65114c17b4ffc20fa5b163c63c70c0d25621fabe" + integrity sha512-8BYqO3GeVNHtx69fdPshN3fnzUNLrWdHhk/icSwigksJGczKSizZ+Z6SBCxTs723Fr5VSNorTIK7a+R2tISvwQ== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.25.9" + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/preset-env@^7.11.0", "@babel/preset-env@^7.12.1", "@babel/preset-env@^7.16.4": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.20.tgz#de9e9b57e1127ce0a2f580831717f7fb677ceedb" @@ -1261,6 +1872,81 @@ core-js-compat "^3.31.0" semver "^6.3.1" +"@babel/preset-env@^7.20.2": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.26.7.tgz#24d38e211f4570b8d806337035cc3ae798e0c36d" + integrity sha512-Ycg2tnXwixaXOVb29rana8HNPgLVBof8qqtNQ9LE22IoyZboQbGSxI6ZySMdW3K5nAe6gu35IaJefUJflhUFTQ== + dependencies: + "@babel/compat-data" "^7.26.5" + "@babel/helper-compilation-targets" "^7.26.5" + "@babel/helper-plugin-utils" "^7.26.5" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.25.9" + "@babel/plugin-bugfix-safari-class-field-initializer-scope" "^7.25.9" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.25.9" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.25.9" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.25.9" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-import-assertions" "^7.26.0" + "@babel/plugin-syntax-import-attributes" "^7.26.0" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.25.9" + "@babel/plugin-transform-async-generator-functions" "^7.25.9" + "@babel/plugin-transform-async-to-generator" "^7.25.9" + "@babel/plugin-transform-block-scoped-functions" "^7.26.5" + "@babel/plugin-transform-block-scoping" "^7.25.9" + "@babel/plugin-transform-class-properties" "^7.25.9" + "@babel/plugin-transform-class-static-block" "^7.26.0" + "@babel/plugin-transform-classes" "^7.25.9" + "@babel/plugin-transform-computed-properties" "^7.25.9" + "@babel/plugin-transform-destructuring" "^7.25.9" + "@babel/plugin-transform-dotall-regex" "^7.25.9" + "@babel/plugin-transform-duplicate-keys" "^7.25.9" + "@babel/plugin-transform-duplicate-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-dynamic-import" "^7.25.9" + "@babel/plugin-transform-exponentiation-operator" "^7.26.3" + "@babel/plugin-transform-export-namespace-from" "^7.25.9" + "@babel/plugin-transform-for-of" "^7.25.9" + "@babel/plugin-transform-function-name" "^7.25.9" + "@babel/plugin-transform-json-strings" "^7.25.9" + "@babel/plugin-transform-literals" "^7.25.9" + "@babel/plugin-transform-logical-assignment-operators" "^7.25.9" + "@babel/plugin-transform-member-expression-literals" "^7.25.9" + "@babel/plugin-transform-modules-amd" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.26.3" + "@babel/plugin-transform-modules-systemjs" "^7.25.9" + "@babel/plugin-transform-modules-umd" "^7.25.9" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.25.9" + "@babel/plugin-transform-new-target" "^7.25.9" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.26.6" + "@babel/plugin-transform-numeric-separator" "^7.25.9" + "@babel/plugin-transform-object-rest-spread" "^7.25.9" + "@babel/plugin-transform-object-super" "^7.25.9" + "@babel/plugin-transform-optional-catch-binding" "^7.25.9" + "@babel/plugin-transform-optional-chaining" "^7.25.9" + "@babel/plugin-transform-parameters" "^7.25.9" + "@babel/plugin-transform-private-methods" "^7.25.9" + "@babel/plugin-transform-private-property-in-object" "^7.25.9" + "@babel/plugin-transform-property-literals" "^7.25.9" + "@babel/plugin-transform-regenerator" "^7.25.9" + "@babel/plugin-transform-regexp-modifiers" "^7.26.0" + "@babel/plugin-transform-reserved-words" "^7.25.9" + "@babel/plugin-transform-shorthand-properties" "^7.25.9" + "@babel/plugin-transform-spread" "^7.25.9" + "@babel/plugin-transform-sticky-regex" "^7.25.9" + "@babel/plugin-transform-template-literals" "^7.25.9" + "@babel/plugin-transform-typeof-symbol" "^7.26.7" + "@babel/plugin-transform-unicode-escapes" "^7.25.9" + "@babel/plugin-transform-unicode-property-regex" "^7.25.9" + "@babel/plugin-transform-unicode-regex" "^7.25.9" + "@babel/plugin-transform-unicode-sets-regex" "^7.25.9" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.6" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.38.1" + semver "^6.3.1" + "@babel/preset-modules@0.1.6-no-external-plugins": version "0.1.6-no-external-plugins" resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" @@ -1282,6 +1968,18 @@ "@babel/plugin-transform-react-jsx-development" "^7.22.5" "@babel/plugin-transform-react-pure-annotations" "^7.22.5" +"@babel/preset-react@^7.18.6": + version "7.26.3" + resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.26.3.tgz#7c5e028d623b4683c1f83a0bd4713b9100560caa" + integrity sha512-Nl03d6T9ky516DGK2YMxrTqvnpUW63TnJMOMonj+Zae0JiPC5BC9xPMSL6L8fiSpA5vP88qfygavVQvnLp+6Cw== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-transform-react-display-name" "^7.25.9" + "@babel/plugin-transform-react-jsx" "^7.25.9" + "@babel/plugin-transform-react-jsx-development" "^7.25.9" + "@babel/plugin-transform-react-pure-annotations" "^7.25.9" + "@babel/preset-typescript@^7.16.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.23.0.tgz#cc6602d13e7e5b2087c811912b87cf937a9129d9" @@ -1293,6 +1991,17 @@ "@babel/plugin-transform-modules-commonjs" "^7.23.0" "@babel/plugin-transform-typescript" "^7.22.15" +"@babel/preset-typescript@^7.21.0": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/preset-typescript/-/preset-typescript-7.26.0.tgz#4a570f1b8d104a242d923957ffa1eaff142a106d" + integrity sha512-NMk1IGZ5I/oHhoXEElcm+xUnL/szL6xflkFZmoEU9xj1qSJXpiS7rsspYo92B4DRCDvZn2erT5LdsCeXAKNCkg== + dependencies: + "@babel/helper-plugin-utils" "^7.25.9" + "@babel/helper-validator-option" "^7.25.9" + "@babel/plugin-syntax-jsx" "^7.25.9" + "@babel/plugin-transform-modules-commonjs" "^7.25.9" + "@babel/plugin-transform-typescript" "^7.25.9" + "@babel/regjsgen@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" @@ -1367,6 +2076,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.26.7.tgz#99a0a136f6a75e7fb8b0a1ace421e0b25994b8bb" + integrity sha512-1x1sgeyRLC3r5fQOM0/xtQKsYjyxmFjaOrLJNtZ81inNjyJHGIolTULPiSc/2qe1/qfpFLisLQYFnnZl7QoedA== + dependencies: + "@babel/code-frame" "^7.26.2" + "@babel/generator" "^7.26.5" + "@babel/parser" "^7.26.7" + "@babel/template" "^7.25.9" + "@babel/types" "^7.26.7" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.12.6", "@babel/types@^7.20.7", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" @@ -1376,6 +2098,14 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.21.3", "@babel/types@^7.26.5", "@babel/types@^7.26.7": + version "7.26.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.7.tgz#5e2b89c0768e874d4d061961f3a5a153d71dc17a" + integrity sha512-t8kDRGrKXyp6+tjUh7hw2RLyclsW4TRoRvRHtSyAX9Bb5ldlFh+90YAYY6awRXrlB4G5G2izNeGySpATlFzmOg== + dependencies: + "@babel/helper-string-parser" "^7.25.9" + "@babel/helper-validator-identifier" "^7.25.9" + "@babel/types@^7.25.4", "@babel/types@^7.25.9", "@babel/types@^7.26.0": version "7.26.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.26.0.tgz#deabd08d6b753bc8e0f198f8709fb575e31774ff" @@ -3162,6 +3892,15 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rollup/pluginutils@^5.0.2": + version "5.1.4" + resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz#bb94f1f9eaaac944da237767cdfee6c5b2262d4a" + integrity sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ== + dependencies: + "@types/estree" "^1.0.0" + estree-walker "^2.0.2" + picomatch "^4.0.2" + "@rollup/rollup-android-arm-eabi@4.27.4": version "4.27.4" resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz#e3c9cc13f144ba033df4d2c3130a214dc8e3473e" @@ -3374,46 +4113,100 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" +"@svgr/babel-plugin-add-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz#4001f5d5dd87fa13303e36ee106e3ff3a7eb8b22" + integrity sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g== + "@svgr/babel-plugin-add-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906" integrity sha512-ZFf2gs/8/6B8PnSofI0inYXr2SDNTDScPXhN7k5EqD4aZ3gi6u+rbmZHVB8IM3wDyx8ntKACZbtXSm7oZGRqVg== +"@svgr/babel-plugin-remove-jsx-attribute@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz#69177f7937233caca3a1afb051906698f2f59186" + integrity sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA== + "@svgr/babel-plugin-remove-jsx-attribute@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-5.4.0.tgz#6b2c770c95c874654fd5e1d5ef475b78a0a962ef" integrity sha512-yaS4o2PgUtwLFGTKbsiAy6D0o3ugcUhWK0Z45umJ66EPWunAz9fuFw2gJuje6wqQvQWOTJvIahUwndOXb7QCPg== +"@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz#c2c48104cfd7dcd557f373b70a56e9e3bdae1d44" + integrity sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA== + "@svgr/babel-plugin-remove-jsx-empty-expression@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-5.0.1.tgz#25621a8915ed7ad70da6cea3d0a6dbc2ea933efd" integrity sha512-LA72+88A11ND/yFIMzyuLRSMJ+tRKeYKeQ+mR3DcAZ5I4h5CPWN9AHyUzJbWSYp/u2u0xhmgOe0+E41+GjEueA== +"@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz#8fbb6b2e91fa26ac5d4aa25c6b6e4f20f9c0ae27" + integrity sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ== + "@svgr/babel-plugin-replace-jsx-attribute-value@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-5.0.1.tgz#0b221fc57f9fcd10e91fe219e2cd0dd03145a897" integrity sha512-PoiE6ZD2Eiy5mK+fjHqwGOS+IXX0wq/YDtNyIgOrc6ejFnxN4b13pRpiIPbtPwHEc+NT2KCjteAcq33/F1Y9KQ== +"@svgr/babel-plugin-svg-dynamic-title@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz#1d5ba1d281363fc0f2f29a60d6d936f9bbc657b0" + integrity sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og== + "@svgr/babel-plugin-svg-dynamic-title@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-5.4.0.tgz#139b546dd0c3186b6e5db4fefc26cb0baea729d7" integrity sha512-zSOZH8PdZOpuG1ZVx/cLVePB2ibo3WPpqo7gFIjLV9a0QsuQAzJiwwqmuEdTaW2pegyBE17Uu15mOgOcgabQZg== +"@svgr/babel-plugin-svg-em-dimensions@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz#35e08df300ea8b1d41cb8f62309c241b0369e501" + integrity sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g== + "@svgr/babel-plugin-svg-em-dimensions@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-5.4.0.tgz#6543f69526632a133ce5cabab965deeaea2234a0" integrity sha512-cPzDbDA5oT/sPXDCUYoVXEmm3VIoAWAPT6mSPTJNbQaBNUuEKVKyGH93oDY4e42PYHRW67N5alJx/eEol20abw== +"@svgr/babel-plugin-transform-react-native-svg@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz#90a8b63998b688b284f255c6a5248abd5b28d754" + integrity sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q== + "@svgr/babel-plugin-transform-react-native-svg@^5.4.0": version "5.4.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-5.4.0.tgz#00bf9a7a73f1cad3948cdab1f8dfb774750f8c80" integrity sha512-3eYP/SaopZ41GHwXma7Rmxcv9uRslRDTY1estspeB1w1ueZWd/tPlMfEOoccYpEMZU3jD4OU7YitnXcF5hLW2Q== +"@svgr/babel-plugin-transform-svg-component@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz#013b4bfca88779711f0ed2739f3f7efcefcf4f7e" + integrity sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw== + "@svgr/babel-plugin-transform-svg-component@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-5.5.0.tgz#583a5e2a193e214da2f3afeb0b9e8d3250126b4a" integrity sha512-q4jSH1UUvbrsOtlo/tKcgSeiCHRSBdXoIoqX1pgcKK/aU3JD27wmMKwGtpB8qRYUYoyXvfGxUVKchLuR5pB3rQ== +"@svgr/babel-preset@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-8.1.0.tgz#0e87119aecdf1c424840b9d4565b7137cabf9ece" + integrity sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug== + dependencies: + "@svgr/babel-plugin-add-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-attribute" "8.0.0" + "@svgr/babel-plugin-remove-jsx-empty-expression" "8.0.0" + "@svgr/babel-plugin-replace-jsx-attribute-value" "8.0.0" + "@svgr/babel-plugin-svg-dynamic-title" "8.0.0" + "@svgr/babel-plugin-svg-em-dimensions" "8.0.0" + "@svgr/babel-plugin-transform-react-native-svg" "8.1.0" + "@svgr/babel-plugin-transform-svg-component" "8.0.0" + "@svgr/babel-preset@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/babel-preset/-/babel-preset-5.5.0.tgz#8af54f3e0a8add7b1e2b0fcd5a882c55393df327" @@ -3428,6 +4221,17 @@ "@svgr/babel-plugin-transform-react-native-svg" "^5.4.0" "@svgr/babel-plugin-transform-svg-component" "^5.5.0" +"@svgr/core@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/core/-/core-8.1.0.tgz#41146f9b40b1a10beaf5cc4f361a16a3c1885e88" + integrity sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + camelcase "^6.2.0" + cosmiconfig "^8.1.3" + snake-case "^3.0.4" + "@svgr/core@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/core/-/core-5.5.0.tgz#82e826b8715d71083120fe8f2492ec7d7874a579" @@ -3437,6 +4241,14 @@ camelcase "^6.2.0" cosmiconfig "^7.0.0" +"@svgr/hast-util-to-babel-ast@8.0.0": + version "8.0.0" + resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz#6952fd9ce0f470e1aded293b792a2705faf4ffd4" + integrity sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q== + dependencies: + "@babel/types" "^7.21.3" + entities "^4.4.0" + "@svgr/hast-util-to-babel-ast@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-5.5.0.tgz#5ee52a9c2533f73e63f8f22b779f93cd432a5461" @@ -3444,6 +4256,16 @@ dependencies: "@babel/types" "^7.12.6" +"@svgr/plugin-jsx@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz#96969f04a24b58b174ee4cd974c60475acbd6928" + integrity sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA== + dependencies: + "@babel/core" "^7.21.3" + "@svgr/babel-preset" "8.1.0" + "@svgr/hast-util-to-babel-ast" "8.0.0" + svg-parser "^2.0.4" + "@svgr/plugin-jsx@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/plugin-jsx/-/plugin-jsx-5.5.0.tgz#1aa8cd798a1db7173ac043466d7b52236b369000" @@ -3454,6 +4276,15 @@ "@svgr/hast-util-to-babel-ast" "^5.5.0" svg-parser "^2.0.2" +"@svgr/plugin-svgo@8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-8.1.0.tgz#b115b7b967b564f89ac58feae89b88c3decd0f00" + integrity sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA== + dependencies: + cosmiconfig "^8.1.3" + deepmerge "^4.3.1" + svgo "^3.0.2" + "@svgr/plugin-svgo@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/plugin-svgo/-/plugin-svgo-5.5.0.tgz#02da55d85320549324e201c7b2e53bf431fcc246" @@ -3463,6 +4294,21 @@ deepmerge "^4.2.2" svgo "^1.2.2" +"@svgr/rollup@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@svgr/rollup/-/rollup-8.1.0.tgz#2c8e09655336cda4b7843799a5d2a5860300b030" + integrity sha512-0XR1poYvPQoPpmfDYLEqUGu5ePAQ4pdgN3VFsZBNAeze7qubVpsIY1o1R6PZpKep/DKu33GSm2NhwpCLkMs2Cw== + dependencies: + "@babel/core" "^7.21.3" + "@babel/plugin-transform-react-constant-elements" "^7.21.3" + "@babel/preset-env" "^7.20.2" + "@babel/preset-react" "^7.18.6" + "@babel/preset-typescript" "^7.21.0" + "@rollup/pluginutils" "^5.0.2" + "@svgr/core" "8.1.0" + "@svgr/plugin-jsx" "8.1.0" + "@svgr/plugin-svgo" "8.1.0" + "@svgr/webpack@^5.5.0": version "5.5.0" resolved "https://registry.yarnpkg.com/@svgr/webpack/-/webpack-5.5.0.tgz#aae858ee579f5fa8ce6c3166ef56c6a1b381b640" @@ -5012,6 +5858,15 @@ babel-plugin-named-asset-import@^0.3.8: resolved "https://registry.yarnpkg.com/babel-plugin-named-asset-import/-/babel-plugin-named-asset-import-0.3.8.tgz#6b7fa43c59229685368683c28bc9734f24524cc2" integrity sha512-WXiAc++qo7XcJ1ZnTYGtLxmBCVbddAml3CEXgWaBzNzLNoxtQ8AiGEFDMOhot9XjTCQbvP5E77Fj9Gk924f00Q== +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.12" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.12.tgz#ca55bbec8ab0edeeef3d7b8ffd75322e210879a9" + integrity sha512-CPWT6BwvhrTO2d8QVorhTCQw9Y43zOu7G9HigcfxvepOU6b8o3tcWad6oVgZIsZCTt42FFv97aA7ZJsbM4+8og== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.3" + semver "^6.3.1" + babel-plugin-polyfill-corejs2@^0.4.5: version "0.4.6" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.6.tgz#b2df0251d8e99f229a8e60fc4efa9a68b41c8313" @@ -5021,6 +5876,14 @@ babel-plugin-polyfill-corejs2@^0.4.5: "@babel/helper-define-polyfill-provider" "^0.4.3" semver "^6.3.1" +babel-plugin-polyfill-corejs3@^0.10.6: + version "0.10.6" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz#2deda57caef50f59c525aeb4964d3b2f867710c7" + integrity sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + core-js-compat "^3.38.0" + babel-plugin-polyfill-corejs3@^0.8.3: version "0.8.5" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.5.tgz#a75fa1b0c3fc5bd6837f9ec465c0f48031b8cab1" @@ -5036,6 +5899,13 @@ babel-plugin-polyfill-regenerator@^0.5.2: dependencies: "@babel/helper-define-polyfill-provider" "^0.4.3" +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.3" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.3.tgz#abeb1f3f1c762eace37587f42548b08b57789bc8" + integrity sha512-LiWSbl4CRSIa5x/JAU6jZiG9eit9w6mz+yVMFwDE83LAWvt0AfGBoZ7HS/mkhrKuh2ZlzfVZYKoLjXdqw6Yt7Q== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.3" + babel-plugin-syntax-jsx@^6.18.0: version "6.18.0" resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" @@ -5330,6 +6200,16 @@ browserslist@^4.24.0: node-releases "^2.0.18" update-browserslist-db "^1.1.1" +browserslist@^4.24.3: + version "4.24.4" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.24.4.tgz#c6b2865a3f08bcb860a0e827389003b9fe686e4b" + integrity sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A== + dependencies: + caniuse-lite "^1.0.30001688" + electron-to-chromium "^1.5.73" + node-releases "^2.0.19" + update-browserslist-db "^1.1.1" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -5470,6 +6350,11 @@ caniuse-lite@^1.0.30001669: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001684.tgz#0eca437bab7d5f03452ff0ef9de8299be6b08e16" integrity sha512-G1LRwLIQjBQoyq0ZJGqGIJUXzJ8irpbjHLpVRXDvBEScFJ9b17sgK6vlx0GAJFE21okD7zXl08rRRUfq6HdoEQ== +caniuse-lite@^1.0.30001688: + version "1.0.30001696" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001696.tgz#00c30a2fc11e3c98c25e5125418752af3ae2f49f" + integrity sha512-pDCPkvzfa39ehJtJ+OwGT/2yvT2SbjfHhiIW2LWOAcMQ7BzwxT/XuyUp4OTOd0XFWA6BKw0JalnBHgSi5DGJBQ== + canvas@^2.11.2: version "2.11.2" resolved "https://registry.yarnpkg.com/canvas/-/canvas-2.11.2.tgz#553d87b1e0228c7ac0fc72887c3adbac4abbd860" @@ -5909,6 +6794,13 @@ core-js-compat@^3.31.0, core-js-compat@^3.32.2: dependencies: browserslist "^4.22.1" +core-js-compat@^3.38.0, core-js-compat@^3.38.1: + version "3.40.0" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.40.0.tgz#7485912a5a4a4315c2fdb2cbdc623e6881c88b38" + integrity sha512-0XEDpr5y5mijvw8Lbc6E5AkjrHfp7eEoPlu36SWeAbcL8fn1G1ANe8DBlo2XoNN89oVpxWwOjYIPVzR4ZvsKCQ== + dependencies: + browserslist "^4.24.3" + core-js-pure@^3.23.3, core-js-pure@^3.30.2: version "3.33.0" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.33.0.tgz#938a28754b4d82017a7a8cbd2727b1abecc63591" @@ -5954,6 +6846,16 @@ cosmiconfig@^7, cosmiconfig@^7.0.0, cosmiconfig@^7.0.1: path-type "^4.0.0" yaml "^1.10.0" +cosmiconfig@^8.1.3: + version "8.3.6" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" + integrity sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA== + dependencies: + import-fresh "^3.3.0" + js-yaml "^4.1.0" + parse-json "^5.2.0" + path-type "^4.0.0" + cosmiconfig@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-9.0.0.tgz#34c3fc58287b915f3ae905ab6dc3de258b55ad9d" @@ -6029,7 +6931,7 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.1, cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" -crypto-browserify@^3.12.0, "crypto@npm:crypto-browserify": +crypto-browserify@^3.12.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== @@ -6056,6 +6958,23 @@ crypto-random-string@^2.0.0: resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== +"crypto@npm:crypto-browserify": + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + css-blank-pseudo@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/css-blank-pseudo/-/css-blank-pseudo-3.0.3.tgz#36523b01c12a25d812df343a32c322d2a2324561" @@ -6137,6 +7056,17 @@ css-select@^4.1.3: domutils "^2.8.0" nth-check "^2.0.1" +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" @@ -6153,6 +7083,14 @@ css-tree@^1.1.2, css-tree@^1.1.3: mdn-data "2.0.14" source-map "^0.6.1" +css-tree@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.3.1.tgz#10264ce1e5442e8572fc82fbe490644ff54b5c20" + integrity sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw== + dependencies: + mdn-data "2.0.30" + source-map-js "^1.0.1" + css-tree@^3.0.0, css-tree@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-3.0.1.tgz#bea6deaea60bb5bcf416adfb1ecf607a8d9471f6" @@ -6161,12 +7099,20 @@ css-tree@^3.0.0, css-tree@^3.0.1: mdn-data "2.12.1" source-map-js "^1.0.1" +css-tree@~2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-2.2.1.tgz#36115d382d60afd271e377f9c5f67d02bd48c032" + integrity sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA== + dependencies: + mdn-data "2.0.28" + source-map-js "^1.0.1" + css-what@^3.2.1: version "3.4.2" resolved "https://registry.yarnpkg.com/css-what/-/css-what-3.4.2.tgz#ea7026fcb01777edbde52124e21f327e7ae950e4" integrity sha512-ACUm3L0/jiZTqfzRM3Hi9Q8eZqd6IK37mMWPLz9PJxkLWllYeRf+EHUSHYEtFop2Eqytaq1FizFVh7XfBnXCDQ== -css-what@^6.0.1: +css-what@^6.0.1, css-what@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== @@ -6242,6 +7188,13 @@ csso@^4.0.2, csso@^4.2.0: dependencies: css-tree "^1.1.2" +csso@^5.0.5: + version "5.0.5" + resolved "https://registry.yarnpkg.com/csso/-/csso-5.0.5.tgz#f9b7fe6cc6ac0b7d90781bb16d5e9874303e2ca6" + integrity sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ== + dependencies: + css-tree "~2.2.0" + cssom@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10" @@ -6348,7 +7301,7 @@ deep-is@^0.1.3, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== -deepmerge@^4.2.2: +deepmerge@^4.2.2, deepmerge@^4.3.1: version "4.3.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a" integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A== @@ -6583,12 +7536,21 @@ dom-serializer@^1.0.1: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== -domelementtype@^2.0.1, domelementtype@^2.2.0: +domelementtype@^2.0.1, domelementtype@^2.2.0, domelementtype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== @@ -6607,6 +7569,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.1: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + domutils@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/domutils/-/domutils-1.7.0.tgz#56ea341e834e06e6748af7a1cb25da67ea9f8c2a" @@ -6624,6 +7593,15 @@ domutils@^2.5.2, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.2.2.tgz#edbfe2b668b0c1d97c24baf0f1062b132221bc78" + integrity sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -6686,6 +7664,11 @@ electron-to-chromium@^1.5.41: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.65.tgz#e2b9d84d31e187a847e3ccdcfb415ddd4a3d1ea7" integrity sha512-PWVzBjghx7/wop6n22vS2MLU8tKGd4Q91aCEGhG/TYmW6PP5OcSXcdnxTe1NNt0T66N8D6jxh4kC8UsdzOGaIw== +electron-to-chromium@^1.5.73: + version "1.5.88" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.88.tgz#cdb6e2dda85e6521e8d7d3035ba391c8848e073a" + integrity sha512-K3C2qf1o+bGzbilTDCTBhTQcMS9KW60yTAaTeeXsfvQuTDDwlokLam/AdqlqcSy9u4UainDgsHV23ksXAOgamw== + elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -6783,6 +7766,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0, entities@^4.4.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + env-paths@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/env-paths/-/env-paths-2.2.1.tgz#420399d416ce1fbe9bc0a07c62fa68d67fd0f8f2" @@ -7332,6 +8320,11 @@ estree-walker@^1.0.1: resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-1.0.1.tgz#31bc5d612c96b704106b477e6dd5d8aa138cb700" integrity sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg== +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + estree-walker@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-3.0.3.tgz#67c3e549ec402a487b4fc193d1953a524752340d" @@ -9501,7 +10494,7 @@ jsesc@^2.5.1: resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== -jsesc@^3.0.2: +jsesc@^3.0.2, jsesc@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-3.0.2.tgz#bb8b09a6597ba426425f2e4a07245c3d00b9343e" integrity sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g== @@ -10046,6 +11039,16 @@ mdn-data@2.0.14: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== +mdn-data@2.0.28: + version "2.0.28" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.28.tgz#5ec48e7bef120654539069e1ae4ddc81ca490eba" + integrity sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g== + +mdn-data@2.0.30: + version "2.0.30" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" + integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== + mdn-data@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" @@ -10380,6 +11383,11 @@ node-releases@^2.0.18: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== +node-releases@^2.0.19: + version "2.0.19" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.19.tgz#9e445a52950951ec4d177d843af370b411caf314" + integrity sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw== + nopt@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" @@ -10887,6 +11895,11 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.2.3, picomatc resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== +picomatch@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-4.0.2.tgz#77c742931e8f3b8820946c76cd0c1f13730d1dab" + integrity sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg== + pidtree@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.6.0.tgz#90ad7b6d42d5841e69e0a2419ef38f8883aa057c" @@ -12263,6 +13276,13 @@ regenerate-unicode-properties@^10.1.0: dependencies: regenerate "^1.4.2" +regenerate-unicode-properties@^10.2.0: + version "10.2.0" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz#626e39df8c372338ea9b8028d1f99dc3fd9c3db0" + integrity sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA== + dependencies: + regenerate "^1.4.2" + regenerate@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" @@ -12316,6 +13336,30 @@ regexpu-core@^5.3.1: unicode-match-property-ecmascript "^2.0.0" unicode-match-property-value-ecmascript "^2.1.0" +regexpu-core@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-6.2.0.tgz#0e5190d79e542bf294955dccabae04d3c7d53826" + integrity sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA== + dependencies: + regenerate "^1.4.2" + regenerate-unicode-properties "^10.2.0" + regjsgen "^0.8.0" + regjsparser "^0.12.0" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsgen@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.8.0.tgz#df23ff26e0c5b300a6470cad160a9d090c3a37ab" + integrity sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q== + +regjsparser@^0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.12.0.tgz#0e846df6c6530586429377de56e0475583b088dc" + integrity sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ== + dependencies: + jsesc "~3.0.2" + regjsparser@^0.9.1: version "0.9.1" resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" @@ -12911,6 +13955,14 @@ slice-ansi@^5.0.0: ansi-styles "^6.0.0" is-fullwidth-code-point "^4.0.0" +snake-case@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/snake-case/-/snake-case-3.0.4.tgz#4f2bbd568e9935abdfd593f34c691dadb49c452c" + integrity sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg== + dependencies: + dot-case "^3.0.4" + tslib "^2.0.3" + socket.io-client@4.8.1: version "4.8.1" resolved "https://registry.yarnpkg.com/socket.io-client/-/socket.io-client-4.8.1.tgz#1941eca135a5490b94281d0323fe2a35f6f291cb" @@ -13138,7 +14190,16 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13221,7 +14282,14 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13423,7 +14491,7 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== -svg-parser@^2.0.2: +svg-parser@^2.0.2, svg-parser@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5" integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ== @@ -13465,6 +14533,19 @@ svgo@^2.7.0: picocolors "^1.0.0" stable "^0.1.8" +svgo@^3.0.2: + version "3.3.2" + resolved "https://registry.yarnpkg.com/svgo/-/svgo-3.3.2.tgz#ad58002652dffbb5986fc9716afe52d869ecbda8" + integrity sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw== + dependencies: + "@trysound/sax" "0.2.0" + commander "^7.2.0" + css-select "^5.1.0" + css-tree "^2.3.1" + css-what "^6.1.0" + csso "^5.0.5" + picocolors "^1.0.0" + symbol-tree@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" @@ -14861,7 +15942,7 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -14879,6 +15960,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From ea0acd43bef61774b78185490e62d82aa8a8a565 Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 29 Jan 2025 16:28:28 +0100 Subject: [PATCH 04/28] Add process polyfill to globalThis for testing environment --- src/setupTests.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/setupTests.ts b/src/setupTests.ts index 79709281b..048517be6 100644 --- a/src/setupTests.ts +++ b/src/setupTests.ts @@ -1,2 +1,5 @@ // src/setupTests.ts import '@testing-library/jest-dom/vitest'; + +import process from 'process'; +globalThis.process = process; From cf522b1fd84a3022609553296d1a858f3c6aca71 Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 29 Jan 2025 16:29:00 +0100 Subject: [PATCH 05/28] Add unit tests for createFolder function in folderUtils --- .../storage/folderUtils/createFolder.test.ts | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/app/store/slices/storage/folderUtils/createFolder.test.ts diff --git a/src/app/store/slices/storage/folderUtils/createFolder.test.ts b/src/app/store/slices/storage/folderUtils/createFolder.test.ts new file mode 100644 index 000000000..7aa17ee40 --- /dev/null +++ b/src/app/store/slices/storage/folderUtils/createFolder.test.ts @@ -0,0 +1,84 @@ +import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { createFolder } from './createFolder'; +import { CreateFolderResponse, EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types'; +import folderService from '../../../../drive/services/folder.service'; +import tasksService from '../../../../tasks/services/tasks.service'; +import errorService from '../../../../core/services/error.service'; +import AppError from '../../../../core/types'; +import { DriveFolderData } from '../../../../drive/types'; + +//StorageActions mock +vi.mock('..', () => ({ + default: { + pushItems: vi.fn(), + }, + storageActions: vi.fn(), + storageSelectors: vi.fn(), +})); + +describe('checkCreateFolder', () => { + const parentFolderId = 'parent-folder-id'; + const mockDispatch = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should create folder via folderService', async () => { + const currentFolderId = 'currentFolderId'; + const mockFolder: CreateFolderResponse = { + id: 0, + name: 'Folder1', + parentId: 0, + plainName: 'Folder1', + bucket: 'bucket', + createdAt: new Date(), + updatedAt: new Date(), + creationTime: new Date(), + deleted: false, + deletedAt: null, + encryptVersion: EncryptionVersion.Aes03, + modificationTime: new Date(), + parentUuid: parentFolderId, + removed: false, + removedAt: null, + userId: 0, + uuid: 'uuid', + }; + + vi.spyOn(folderService, 'createFolderByUuid').mockReturnValue([Promise.resolve(mockFolder), { cancel: vi.fn() }]); + vi.spyOn(tasksService, 'create').mockReturnValue('task-id'); + vi.spyOn(tasksService, 'updateTask').mockReturnValue(); + vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); + + const result = await createFolder( + { + folderName: 'Folder1', + parentFolderId, + options: { showErrors: true }, + }, + currentFolderId, + null, + { dispatch: mockDispatch }, + ); + + const mockFolderNormalized: DriveFolderData = { + ...mockFolder, + name: 'Folder1', + parent_id: mockFolder.parentId, + user_id: mockFolder.userId, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: mockFolder.plainName, + deleted: false, + createdAt: new Date(mockFolder.createdAt || '').toISOString(), + updatedAt: new Date(mockFolder.updatedAt || '').toISOString(), + }; + + expect(mockFolderNormalized).toStrictEqual(result); + }); +}); From 54029a55d42a335d16b43168b6ff2bda4e1c1aa9 Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 29 Jan 2025 17:27:28 +0100 Subject: [PATCH 06/28] Add storage thunk mocks for improved consistency --- .../storage/folderUtils/createFolder.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/app/store/slices/storage/folderUtils/createFolder.test.ts b/src/app/store/slices/storage/folderUtils/createFolder.test.ts index 7aa17ee40..a60dd7d44 100644 --- a/src/app/store/slices/storage/folderUtils/createFolder.test.ts +++ b/src/app/store/slices/storage/folderUtils/createFolder.test.ts @@ -16,6 +16,29 @@ vi.mock('..', () => ({ storageSelectors: vi.fn(), })); +vi.mock('../storage.thunks', () => ({ + default: { + initializeThunk: vi.fn(), + resetNamePathThunk: vi.fn(), + uploadItemsThunk: vi.fn(), + downloadItemsThunk: vi.fn(), + downloadFileThunk: vi.fn(), + downloadFolderThunk: vi.fn(), + fetchPaginatedFolderContentThunk: vi.fn(), + deleteItemsThunk: vi.fn(), + goToFolderThunk: vi.fn(), + uploadFolderThunk: vi.fn(), + uploadMultipleFolderThunkNoCheck: vi.fn(), + updateItemMetadataThunk: vi.fn(), + fetchRecentsThunk: vi.fn(), + createFolderThunk: vi.fn(), + moveItemsThunk: vi.fn(), + fetchDeletedThunk: vi.fn(), + renameItemsThunk: vi.fn(), + uploadSharedItemsThunk: vi.fn(), + }, +})); + describe('checkCreateFolder', () => { const parentFolderId = 'parent-folder-id'; const mockDispatch = vi.fn(); From 2f9fc347c0da734600cbac07e7c1c7d37876daee Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 30 Jan 2025 16:59:18 +0100 Subject: [PATCH 07/28] Enhance createFolder functionality with improved type definitions and mock implementations --- .../storage/folderUtils/createFolder.test.ts | 22 ++++++++++++++++--- .../storage/folderUtils/createFolder.ts | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/src/app/store/slices/storage/folderUtils/createFolder.test.ts b/src/app/store/slices/storage/folderUtils/createFolder.test.ts index a60dd7d44..49ba7b1dc 100644 --- a/src/app/store/slices/storage/folderUtils/createFolder.test.ts +++ b/src/app/store/slices/storage/folderUtils/createFolder.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { createFolder } from './createFolder'; import { CreateFolderResponse, EncryptionVersion } from '@internxt/sdk/dist/drive/storage/types'; import folderService from '../../../../drive/services/folder.service'; @@ -28,7 +28,6 @@ vi.mock('../storage.thunks', () => ({ deleteItemsThunk: vi.fn(), goToFolderThunk: vi.fn(), uploadFolderThunk: vi.fn(), - uploadMultipleFolderThunkNoCheck: vi.fn(), updateItemMetadataThunk: vi.fn(), fetchRecentsThunk: vi.fn(), createFolderThunk: vi.fn(), @@ -39,6 +38,23 @@ vi.mock('../storage.thunks', () => ({ }, })); +vi.mock('../../../../drive/services/folder.service', () => ({ + default: { + createFolder: vi.fn(), + createFolderByUuid: vi.fn(), + updateMetaData: vi.fn(), + moveFolder: vi.fn(), + moveFolderByUuid: vi.fn(), + fetchFolderTree: vi.fn(), + downloadFolderAsZip: vi.fn(), + addAllFoldersToZip: vi.fn(), + addAllFilesToZip: vi.fn(), + downloadSharedFolderAsZip: vi.fn(), + }, + createFilesIterator: vi.fn(), + createFoldersIterator: vi.fn(), +})); + describe('checkCreateFolder', () => { const parentFolderId = 'parent-folder-id'; const mockDispatch = vi.fn(); @@ -69,7 +85,7 @@ describe('checkCreateFolder', () => { uuid: 'uuid', }; - vi.spyOn(folderService, 'createFolderByUuid').mockReturnValue([Promise.resolve(mockFolder), { cancel: vi.fn() }]); + (folderService.createFolderByUuid as Mock).mockReturnValue([Promise.resolve(mockFolder), { cancel: vi.fn() }]); vi.spyOn(tasksService, 'create').mockReturnValue('task-id'); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); diff --git a/src/app/store/slices/storage/folderUtils/createFolder.ts b/src/app/store/slices/storage/folderUtils/createFolder.ts index b4567ed23..1c491f52e 100644 --- a/src/app/store/slices/storage/folderUtils/createFolder.ts +++ b/src/app/store/slices/storage/folderUtils/createFolder.ts @@ -28,7 +28,7 @@ export const createFolder = async ( currentFolderId: string, selectedWorkspace: WorkspaceData | null, { dispatch }: { dispatch: ThunkDispatch }, -) => { +): Promise => { options = Object.assign({ showErrors: true }, options || {}); const workspaceId = selectedWorkspace?.workspace?.id; let createdFolderPromise: Promise; From b50cf2f83d291844d5d5a57a6b70f63e02424787 Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 30 Jan 2025 17:01:22 +0100 Subject: [PATCH 08/28] Removed unused async thunk to use new uploadMultipleFolder function --- .../NameCollisionContainer.tsx | 11 +- .../slices/storage/storage.thunks/index.ts | 7 +- .../storage.thunks/uploadFolderThunk.ts | 164 ------------------ 3 files changed, 9 insertions(+), 173 deletions(-) diff --git a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx index 2526f3b36..eccde2971 100644 --- a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx +++ b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx @@ -11,6 +11,8 @@ import { fetchSortedFolderContentThunk } from '../../../store/slices/storage/sto import { uiActions } from '../../../store/slices/ui'; import { DriveItemData } from '../../types'; import { IRoot } from '../../../store/slices/storage/types'; +import { uploadMultipleFolder } from '../../../store/slices/storage/folderUtils/uploadFolders'; +import workspacesSelectors from '../../../store/slices/workspaces/workspaces.selectors'; type NameCollisionContainerProps = { currentFolderId: string; @@ -36,6 +38,7 @@ const NameCollisionContainer: FC = ({ const [driveRepeatedFolder, setDriveRepeatedFolder] = useState([]); const isOpen = useAppSelector((state: RootState) => state.ui.isNameCollisionDialogOpen); + const selectedWorkspace = useAppSelector(workspacesSelectors.getSelectedWorkspace); const isMoveDialog = useMemo(() => !!moveDestinationFolderId, [moveDestinationFolderId]); const folderId = useMemo( () => moveDestinationFolderId ?? currentFolderId, @@ -131,13 +134,15 @@ const NameCollisionContainer: FC = ({ itemsToUpload.forEach((itemToUpload) => { if ((itemToUpload as IRoot).fullPathEdited) { - dispatch( - storageThunks.uploadMultipleFolderThunkNoCheck([ + uploadMultipleFolder( + [ { root: { ...(itemToUpload as IRoot) }, currentFolderId: folderId, }, - ]), + ], + selectedWorkspace, + { dispatch }, ).then(() => { dispatch(fetchSortedFolderContentThunk(folderId)); }); diff --git a/src/app/store/slices/storage/storage.thunks/index.ts b/src/app/store/slices/storage/storage.thunks/index.ts index b1ce1c2c6..18024e81e 100644 --- a/src/app/store/slices/storage/storage.thunks/index.ts +++ b/src/app/store/slices/storage/storage.thunks/index.ts @@ -15,11 +15,7 @@ import { moveItemsThunk, moveItemsThunkExtraReducers } from './moveItemsThunk'; import { renameItemsThunk, renameItemsThunkExtraReducers } from './renameItemsThunk'; import { resetNamePathThunk, resetNamePathThunkExtraReducers } from './resetNamePathThunk'; import { updateItemMetadataThunk, updateItemMetadataThunkExtraReducers } from './updateItemMetadataThunk'; -import { - uploadFolderThunk, - uploadFolderThunkExtraReducers, - uploadMultipleFolderThunkNoCheck, -} from './uploadFolderThunk'; +import { uploadFolderThunk, uploadFolderThunkExtraReducers } from './uploadFolderThunk'; import { uploadItemsThunk, uploadItemsThunkExtraReducers, uploadSharedItemsThunk } from './uploadItemsThunk'; const storageThunks = { @@ -33,7 +29,6 @@ const storageThunks = { deleteItemsThunk, goToFolderThunk, uploadFolderThunk, - uploadMultipleFolderThunkNoCheck, updateItemMetadataThunk, fetchRecentsThunk, createFolderThunk, diff --git a/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts b/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts index 04ce176db..77aa2b0ec 100644 --- a/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/uploadFolderThunk.ts @@ -18,7 +18,6 @@ import { StorageState } from '../storage.model'; import { deleteItemsThunk } from './deleteItemsThunk'; import { uploadItemsParallelThunk } from './uploadItemsThunk'; import { IRoot } from '../types'; -import { queue, QueueObject } from 'async'; interface UploadFolderThunkPayload { root: IRoot; @@ -230,169 +229,6 @@ export const uploadFolderThunk = createAsyncThunk { - return foldersPayload.map(({ root, currentFolderId, options: payloadOptions }) => { - const options = { withNotification: true, ...payloadOptions }; - - const uploadFolderAbortController = new AbortController(); - - let taskId = options?.taskId; - - if (taskId) { - tasksService.updateTask({ - taskId, - merge: { - status: TaskStatus.InProcess, - progress: 0, - }, - }); - } else { - taskId = tasksService.create({ - action: TaskType.UploadFolder, - folderName: root.name, - item: root, - parentFolderId: currentFolderId, - showNotification: !!options.withNotification, - cancellable: true, - }); - } - - return { root, currentFolderId, options: payloadOptions, taskId, abortController: uploadFolderAbortController }; - }); -}; - -export const uploadMultipleFolderThunkNoCheck = createAsyncThunk< - void, - UploadFolderThunkPayload[], - { state: RootState } ->('storage/createFolderStructure', async (payload, { dispatch, getState }) => { - const state = getState(); - const payloadWithTaskId = generateTaskIdForFolders(payload); - - const selectedWorkspace = workspacesSelectors.getSelectedWorkspace(state); - const memberId = selectedWorkspace?.workspaceUser?.memberId; - - // checking why is not aborting correctly the folder upload - for (const { root, currentFolderId, options: payloadOptions, taskId, abortController } of payloadWithTaskId) { - console.time('multiFolder-upload'); - const options = { withNotification: true, ...payloadOptions }; - - let alreadyUploaded = 0; - - let rootFolderItem: DriveFolderData | undefined; - const itemsUnderRoot = countItemsUnderRoot(root); - const uploadFolderAbortController = abortController; - - const uploadFolderAsync = async (level: IRoot) => { - const createdFolder = await dispatch( - storageThunks.createFolderThunk({ - parentFolderId: level.folderId as string, - folderName: level.name, - options: { relatedTaskId: taskId, showErrors: false }, - }), - ).unwrap(); - - if (!rootFolderItem) { - rootFolderItem = createdFolder; - } - - tasksService.updateTask({ - taskId, - merge: { - stop: () => stopUploadTask(uploadFolderAbortController, dispatch, taskId, rootFolderItem), - }, - }); - - if (level.childrenFiles.length > 0 || level.childrenFolders.length > 0) { - // Added wait in order to allow enough time for the server to create the folder - await wait(500); - } - - if (level.childrenFiles) { - //if (uploadFolderAbortController.signal.aborted) break; - - await dispatch( - uploadItemsParallelThunk({ - files: level.childrenFiles, - parentFolderId: createdFolder.uuid, - options: { - relatedTaskId: taskId, - showNotifications: false, - showErrors: false, - abortController: uploadFolderAbortController, - disableDuplicatedNamesCheck: true, - }, - filesProgress: { filesUploaded: alreadyUploaded, totalFilesToUpload: itemsUnderRoot }, - }), - ) - .unwrap() - .then(() => { - alreadyUploaded += level.childrenFiles.length; - alreadyUploaded += 1; - }); - - //if (uploadFolderAbortController.signal.aborted) break; - } - - for (const child of level.childrenFolders) { - await uploadFolderQueue.pushAsync({ ...child, folderId: createdFolder.uuid }); - } - }; - - const uploadFolderQueue: QueueObject = queue((task, callback) => { - uploadFolderAsync(task) - .then(() => { - callback(); - }) - .catch((e) => { - callback(e); - }); - }, 6); - - try { - root.folderId = currentFolderId; - await uploadFolderQueue.pushAsync(root); - - while (uploadFolderQueue.running() > 0 || uploadFolderQueue.length() > 0) { - await uploadFolderQueue.drain(); - } - - console.timeEnd('multiFolder-upload'); - - tasksService.updateTask({ - taskId: taskId, - merge: { - itemUUID: { rootFolderUUID: rootFolderItem?.uuid }, - status: TaskStatus.Success, - }, - }); - - options.onSuccess?.(); - - setTimeout(() => { - dispatch(planThunks.fetchUsageThunk()); - if (memberId) dispatch(planThunks.fetchBusinessLimitUsageThunk()); - }, 1000); - } catch (err: unknown) { - const castedError = errorService.castError(err); - const updatedTask = tasksService.findTask(taskId); - - if (updatedTask?.status !== TaskStatus.Cancelled && taskId === updatedTask?.id) { - tasksService.updateTask({ - taskId: taskId, - merge: { - status: TaskStatus.Error, - subtitle: t('tasks.subtitles.upload-failed') as string, - }, - }); - // Log the error or report it but don't re-throw it to allow the next folder to be processed - errorService.reportError(castedError); - continue; - } - } - } -}); - function countItemsUnderRoot(root: IRoot): number { let count = 0; From c0282d99ccb1527179e6434b211979367a5e1f22 Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 30 Jan 2025 17:02:08 +0100 Subject: [PATCH 09/28] Add unit tests for uploadMultipleFolder functionality --- .../storage/folderUtils/uploadFolders.test.ts | 198 ++++++++++++++++++ .../storage/folderUtils/uploadFolders.ts | 2 +- 2 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/app/store/slices/storage/folderUtils/uploadFolders.test.ts diff --git a/src/app/store/slices/storage/folderUtils/uploadFolders.test.ts b/src/app/store/slices/storage/folderUtils/uploadFolders.test.ts new file mode 100644 index 000000000..9cae87777 --- /dev/null +++ b/src/app/store/slices/storage/folderUtils/uploadFolders.test.ts @@ -0,0 +1,198 @@ +import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; +import tasksService from '../../../../tasks/services/tasks.service'; +import errorService from '../../../../core/services/error.service'; +import AppError from '../../../../core/types'; +import { DriveFolderData } from '../../../../drive/types'; +import { uploadMultipleFolder } from './uploadFolders'; +import { createFolder } from './createFolder'; + +vi.mock('../storage.thunks', () => ({ + default: { + initializeThunk: vi.fn(), + resetNamePathThunk: vi.fn(), + uploadItemsThunk: vi.fn(), + downloadItemsThunk: vi.fn(), + downloadFileThunk: vi.fn(), + downloadFolderThunk: vi.fn(), + fetchPaginatedFolderContentThunk: vi.fn(), + deleteItemsThunk: vi.fn(), + goToFolderThunk: vi.fn(), + uploadFolderThunk: vi.fn(), + updateItemMetadataThunk: vi.fn(), + fetchRecentsThunk: vi.fn(), + createFolderThunk: vi.fn(), + moveItemsThunk: vi.fn(), + fetchDeletedThunk: vi.fn(), + renameItemsThunk: vi.fn(), + uploadSharedItemsThunk: vi.fn(), + }, + storageExtraReducers: vi.fn(), +})); + +vi.mock('../../plan', () => ({ + default: { + initializeThunk: vi.fn(), + fetchLimitThunk: vi.fn(), + fetchUsageThunk: vi.fn(), + fetchSubscriptionThunk: vi.fn(), + fetchBusinessLimitUsageThunk: vi.fn(), + }, + planThunks: vi.fn(), +})); + +vi.mock('app/drive/services/download.service/downloadFolder', () => ({ + default: { + fetchFileBlob: vi.fn(), + downloadFileFromBlob: vi.fn(), + downloadFile: vi.fn(), + downloadFolder: vi.fn(), + downloadBackup: vi.fn(), + }, +})); + +vi.mock('./createFolder', () => ({ + createFolder: vi.fn(), +})); + +describe('checkUploadFolders', () => { + const mockDispatch = vi.fn(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should upload folder using an async queue', async () => { + const mockFolder: DriveFolderData = { + id: 0, + uuid: 'uuid', + name: 'Folder1', + bucket: 'bucket', + parentId: 0, + parent_id: 0, + parentUuid: 'parentUuid', + userId: 0, + user_id: 0, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: 'Folder1', + deleted: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + const createFolderSpy = (createFolder as Mock).mockResolvedValueOnce(mockFolder); + + vi.spyOn(tasksService, 'create').mockReturnValue('task-id'); + vi.spyOn(tasksService, 'updateTask').mockReturnValue(); + vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); + + await uploadMultipleFolder( + [ + { + currentFolderId: 'currentFolderId', + root: { + folderId: mockFolder.uuid, + childrenFiles: [], + childrenFolders: [], + name: mockFolder.name, + fullPathEdited: 'path1', + }, + options: { + taskId: 'task-id', + }, + }, + ], + null, + { dispatch: mockDispatch }, + ); + + expect(createFolderSpy).toHaveBeenCalledOnce(); + }); + + it('should upload multiple folders using an async queue', async () => { + const mockParentFolder: DriveFolderData = { + id: 1, + uuid: 'uuid1', + name: 'Folder1', + bucket: 'bucket', + parentId: 0, + parent_id: 0, + parentUuid: 'parentUuid', + userId: 0, + user_id: 0, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: 'Folder1', + deleted: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const mockChildFolder: DriveFolderData = { + id: 2, + uuid: 'uuid2', + name: 'Folder2', + bucket: 'bucket', + parentId: 1, + parent_id: 1, + parentUuid: 'uuid1', + userId: 0, + user_id: 0, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: 'Folder2', + deleted: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + + const createFolderSpy = (createFolder as Mock) + .mockResolvedValueOnce(mockParentFolder) + .mockResolvedValueOnce(mockChildFolder); + + vi.spyOn(tasksService, 'create').mockReturnValue('task-id'); + vi.spyOn(tasksService, 'updateTask').mockReturnValue(); + vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); + + await uploadMultipleFolder( + [ + { + currentFolderId: 'currentFolderId', + root: { + folderId: mockParentFolder.parentUuid, + childrenFiles: [], + childrenFolders: [ + { + folderId: mockParentFolder.uuid, + childrenFiles: [], + childrenFolders: [], + name: mockChildFolder.name, + fullPathEdited: 'path2', + }, + ], + name: mockParentFolder.name, + fullPathEdited: 'path1', + }, + options: { + taskId: 'task-id', + }, + }, + ], + null, + { dispatch: mockDispatch }, + ); + + expect(createFolderSpy).toHaveBeenCalledTimes(2); + }); +}); diff --git a/src/app/store/slices/storage/folderUtils/uploadFolders.ts b/src/app/store/slices/storage/folderUtils/uploadFolders.ts index b89b51dc4..fc0abab91 100644 --- a/src/app/store/slices/storage/folderUtils/uploadFolders.ts +++ b/src/app/store/slices/storage/folderUtils/uploadFolders.ts @@ -152,7 +152,7 @@ export const uploadMultipleFolder = async ( await wait(500); } - if (level.childrenFiles) { + if (level.childrenFiles.length > 0) { if (uploadFolderAbortController.signal.aborted) return; await dispatch( uploadItemsParallelThunk({ From ec5304f1c16883b3ebb5add2157aa0748bc01a61 Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 31 Jan 2025 09:41:21 +0100 Subject: [PATCH 10/28] Remove timing logs from uploadMultipleFolder function --- src/app/store/slices/storage/folderUtils/uploadFolders.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/app/store/slices/storage/folderUtils/uploadFolders.ts b/src/app/store/slices/storage/folderUtils/uploadFolders.ts index fc0abab91..b99233dc8 100644 --- a/src/app/store/slices/storage/folderUtils/uploadFolders.ts +++ b/src/app/store/slices/storage/folderUtils/uploadFolders.ts @@ -113,7 +113,6 @@ export const uploadMultipleFolder = async ( const memberId = selectedWorkspace?.workspaceUser?.memberId; for (const { root, currentFolderId, options: payloadOptions, taskId, abortController } of payloadWithTaskId) { - console.time('multiFolder-upload'); const options = { withNotification: true, ...payloadOptions }; let alreadyUploaded = 0; @@ -198,8 +197,6 @@ export const uploadMultipleFolder = async ( await uploadFolderQueue.drain(); } - console.timeEnd('multiFolder-upload'); - tasksService.updateTask({ taskId: taskId, merge: { From f249d7d20351e67b7179e2bbca66af1a46e62b51 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 3 Feb 2025 17:35:01 +0100 Subject: [PATCH 11/28] Refactor upload functionality to use uploadFoldersWithManager and update related tests --- .../DriveExplorer/DriveExplorer.tsx | 8 +- .../NameCollisionContainer.tsx | 10 +- src/app/network/UploadFolderManager.ts | 333 ++++++++++++++++++ .../UploadFoldersManager.test.ts} | 40 +-- .../storage/folderUtils/uploadFolders.ts | 232 ------------ 5 files changed, 364 insertions(+), 259 deletions(-) create mode 100644 src/app/network/UploadFolderManager.ts rename src/app/{store/slices/storage/folderUtils/uploadFolders.test.ts => network/UploadFoldersManager.test.ts} (85%) delete mode 100644 src/app/store/slices/storage/folderUtils/uploadFolders.ts diff --git a/src/app/drive/components/DriveExplorer/DriveExplorer.tsx b/src/app/drive/components/DriveExplorer/DriveExplorer.tsx index 398771b21..2657f4486 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorer.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorer.tsx @@ -71,8 +71,8 @@ import DriveTopBarActions from './components/DriveTopBarActions'; import { getAncestorsAndSetNamePath } from '../../../store/slices/storage/storage.thunks/goToFolderThunk'; import { IRoot } from '../../../store/slices/storage/types'; import { useTrashPagination } from '../../../drive/hooks/trash/useTrashPagination'; -import { uploadMultipleFolder } from '../../../store/slices/storage/folderUtils/uploadFolders'; import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; +import { uploadFoldersWithManager } from '../../../network/UploadFolderManager'; export const UPLOAD_ITEMS_LIMIT = 3000; @@ -907,7 +907,11 @@ const uploadItems = async (props: DriveExplorerProps, rootList: IRoot[], files: }, })); - await uploadMultipleFolder(folderDataToUpload, props.selectedWorkspace, { dispatch }); + await uploadFoldersWithManager({ + payload: folderDataToUpload, + selectedWorkspace: props.selectedWorkspace, + dispatch, + }); dispatch(fetchSortedFolderContentThunk(currentFolderId)); } } diff --git a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx index eccde2971..c9ae19281 100644 --- a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx +++ b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx @@ -11,8 +11,8 @@ import { fetchSortedFolderContentThunk } from '../../../store/slices/storage/sto import { uiActions } from '../../../store/slices/ui'; import { DriveItemData } from '../../types'; import { IRoot } from '../../../store/slices/storage/types'; -import { uploadMultipleFolder } from '../../../store/slices/storage/folderUtils/uploadFolders'; import workspacesSelectors from '../../../store/slices/workspaces/workspaces.selectors'; +import { uploadFoldersWithManager } from '../../../network/UploadFolderManager'; type NameCollisionContainerProps = { currentFolderId: string; @@ -134,16 +134,16 @@ const NameCollisionContainer: FC = ({ itemsToUpload.forEach((itemToUpload) => { if ((itemToUpload as IRoot).fullPathEdited) { - uploadMultipleFolder( - [ + uploadFoldersWithManager({ + payload: [ { root: { ...(itemToUpload as IRoot) }, currentFolderId: folderId, }, ], selectedWorkspace, - { dispatch }, - ).then(() => { + dispatch, + }).then(() => { dispatch(fetchSortedFolderContentThunk(folderId)); }); } else { diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts new file mode 100644 index 000000000..470e99960 --- /dev/null +++ b/src/app/network/UploadFolderManager.ts @@ -0,0 +1,333 @@ +import { TaskStatus, TaskType, UploadFolderTask } from '../tasks/types'; +import { DriveFolderData, DriveItemData } from '../drive/types'; +import { IRoot } from '../store/slices/storage/types'; +import tasksService from '../tasks/services/tasks.service'; +import errorService from '../core/services/error.service'; +import { queue, QueueObject } from 'async'; +import { t } from 'i18next'; +import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; +import { RootState } from '../store'; +import { SdkFactory } from '../core/factory/sdk'; +import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; +import { planThunks } from '../store/slices/plan'; +import { uploadItemsParallelThunk } from '../store/slices/storage/storage.thunks/uploadItemsThunk'; +import { createFolder } from '../store/slices/storage/folderUtils/createFolder'; +import { deleteItemsThunk } from '../store/slices/storage/storage.thunks/deleteItemsThunk'; + +const MAX_CONCURRENT_UPLOADS = 6; + +interface UploadFolderThunkPayload { + root: IRoot; + currentFolderId: string; + options?: { + taskId?: string; + withNotification?: boolean; + onSuccess?: () => void; + }; +} + +interface TaskFolder { + root: IRoot; + currentFolderId: string; + options: + | { + taskId?: string; + withNotification?: boolean; + onSuccess?: () => void; + } + | undefined; + taskId: string; + abortController: AbortController; +} + +interface TaskInfo { + rootFolderItem?: DriveFolderData; + progress: { + itemsUploaded: number; + totalItems: number; + }; +} + +const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): TaskFolder[] => { + return foldersPayload.map(({ root, currentFolderId, options: payloadOptions }) => { + const options = { withNotification: true, ...payloadOptions }; + + const uploadFolderAbortController = new AbortController(); + + let taskId = options?.taskId; + + if (taskId) { + tasksService.updateTask({ + taskId, + merge: { + status: TaskStatus.InProcess, + progress: 0, + }, + }); + } else { + taskId = tasksService.create({ + action: TaskType.UploadFolder, + folderName: root.name, + item: root, + parentFolderId: currentFolderId, + showNotification: !!options.withNotification, + cancellable: true, + }); + } + + return { root, currentFolderId, options: payloadOptions, taskId, abortController: uploadFolderAbortController }; + }); +}; + +const stopUploadTask = async ( + uploadFolderAbortController: AbortController, + dispatch: ThunkDispatch, + relatedTaskId?: string, + rootFolderItem?: DriveFolderData, +) => { + uploadFolderAbortController.abort(); + const relatedTasks = tasksService.getTasks({ relatedTaskId }); + const promises: Promise[] = []; + + // Cancels related tasks + promises.push( + ...(relatedTasks.map((task) => task.stop?.()).filter((promise) => promise !== undefined) as Promise[]), + ); + // Deletes the root folder + if (rootFolderItem) { + promises.push(dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); + const storageClient = SdkFactory.getInstance().createStorageClient(); + promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); + } + await Promise.all(promises); +}; + +function countItemsUnderRoot(root: IRoot): number { + let count = 1; + + const queueOfFolders: Array = [root]; + + while (queueOfFolders.length > 0) { + const folder = queueOfFolders.shift() as IRoot; + count += folder.childrenFiles.length; + + if (folder.childrenFolders) { + count += folder.childrenFolders.length; + queueOfFolders.push(...folder.childrenFolders); + } + } + + return count; +} + +const wait = (ms: number): Promise => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; + +export const uploadFoldersWithManager = ({ + payload, + selectedWorkspace, + dispatch, +}: { + payload: UploadFolderThunkPayload[]; + selectedWorkspace: WorkspaceData | null; + dispatch: ThunkDispatch; +}): Promise => { + const uploadFoldersManager = new UploadFoldersManager(payload, selectedWorkspace, dispatch); + return uploadFoldersManager.run(); +}; + +class UploadFoldersManager { + private payload: UploadFolderThunkPayload[]; + private selectedWorkspace: WorkspaceData | null; + private dispatch: ThunkDispatch; + private abortController?: AbortController; + + private tasksInfo: Record = {}; + + constructor( + payload: UploadFolderThunkPayload[], + selectedWorkspace: WorkspaceData | null, + dispatch: ThunkDispatch, + ) { + this.payload = payload; + this.selectedWorkspace = selectedWorkspace; + this.dispatch = dispatch; + } + + private uploadFoldersQueue: QueueObject = queue( + (task, next: (err: Error | null, res?: DriveFolderData) => void) => { + if (this.abortController?.signal.aborted) return; + + this.manageMemoryUsage(); + + this.uploadFolderAsync(task) + .then((uploadedFolder: DriveFolderData | undefined) => { + next(null, uploadedFolder); + }) + .catch((e) => { + next(e); + }); + }, + MAX_CONCURRENT_UPLOADS, + ); + + private uploadFolderAsync = async (taskFolder: TaskFolder) => { + const { root: level, currentFolderId, taskId, abortController } = taskFolder; + + if (abortController.signal.aborted) return; + + const createdFolder = await createFolder( + { + parentFolderId: level.folderId as string, + folderName: level.name, + options: { relatedTaskId: taskId, showErrors: false }, + }, + currentFolderId, + this.selectedWorkspace, + { dispatch: this.dispatch }, + ); + + if (!this.tasksInfo[taskId].rootFolderItem) { + this.tasksInfo[taskId].rootFolderItem = createdFolder; + } + + this.tasksInfo[taskId].progress.itemsUploaded += 1; + + tasksService.updateTask({ + taskId, + merge: { + progress: this.tasksInfo[taskId].progress.itemsUploaded / this.tasksInfo[taskId].progress.totalItems, + stop: () => stopUploadTask(abortController, this.dispatch, taskId, this.tasksInfo[taskId].rootFolderItem), + }, + }); + + if (level.childrenFiles.length > 0 || level.childrenFolders.length > 0) { + // Added wait in order to allow enough time for the server to create the folder + await wait(500); + } + + if (level.childrenFiles.length > 0) { + if (abortController.signal.aborted) return; + await this.dispatch( + uploadItemsParallelThunk({ + files: level.childrenFiles, + parentFolderId: createdFolder.uuid, + options: { + relatedTaskId: taskId, + showNotifications: false, + showErrors: false, + abortController: abortController, + disableDuplicatedNamesCheck: true, + }, + filesProgress: { + filesUploaded: this.tasksInfo[taskId].progress.itemsUploaded, + totalFilesToUpload: this.tasksInfo[taskId].progress.totalItems, + }, + }), + ) + .unwrap() + .then(() => { + this.tasksInfo[taskId].progress.itemsUploaded += level.childrenFiles.length; + }); + } + + for (const child of level.childrenFolders) { + if (abortController.signal.aborted) return; + await this.uploadFoldersQueue.pushAsync({ + root: { ...child, folderId: createdFolder.uuid }, + currentFolderId: taskFolder.currentFolderId, + options: taskFolder.options, + abortController: taskFolder.abortController, + taskId: taskFolder.taskId, + }); + } + + return createdFolder; + }; + + private manageMemoryUsage() { + if (window?.performance?.memory) { + const memory = window.performance.memory; + + if (memory && memory?.jsHeapSizeLimit !== null && memory.usedJSHeapSize !== null) { + const memoryUsagePercentage = memory.usedJSHeapSize / memory.jsHeapSizeLimit; + + const shouldIncreaseConcurrency = memoryUsagePercentage < 0.7; + if (shouldIncreaseConcurrency) { + const newConcurrency = Math.min(this.uploadFoldersQueue.concurrency + 1, 6); + if (newConcurrency !== this.uploadFoldersQueue.concurrency) { + console.warn(`Memory usage under 70%. Increasing folder upload concurrency to ${newConcurrency}`); + this.uploadFoldersQueue.concurrency = newConcurrency; + } + } + + const shouldReduceConcurrency = memoryUsagePercentage >= 0.8 && this.uploadFoldersQueue.concurrency > 1; + if (shouldReduceConcurrency) { + console.warn('Memory usage reached 80%. Reducing folder upload concurrency.'); + this.uploadFoldersQueue.concurrency = 1; + } + } + } else { + console.warn('Memory usage control is not available'); + } + } + + async run(): Promise { + const payloadWithTaskId = generateTaskIdForFolders(this.payload); + + const memberId = this.selectedWorkspace?.workspaceUser?.memberId; + + for (const taskFolder of payloadWithTaskId) { + const { root, currentFolderId, options: payloadOptions, taskId } = taskFolder; + const options = { withNotification: true, ...payloadOptions }; + + this.tasksInfo[taskId] = { + progress: { + itemsUploaded: 0, + totalItems: countItemsUnderRoot(root), + }, + }; + + try { + root.folderId = currentFolderId; + await this.uploadFoldersQueue.pushAsync(taskFolder); + + while (this.uploadFoldersQueue.running() > 0 || this.uploadFoldersQueue.length() > 0) { + await this.uploadFoldersQueue.drain(); + } + + tasksService.updateTask({ + taskId: taskId, + merge: { + itemUUID: { rootFolderUUID: this.tasksInfo[taskId].rootFolderItem?.uuid }, + status: TaskStatus.Success, + }, + }); + + options.onSuccess?.(); + + setTimeout(() => { + this.dispatch(planThunks.fetchUsageThunk()); + if (memberId) this.dispatch(planThunks.fetchBusinessLimitUsageThunk()); + }, 1000); + } catch (err: unknown) { + const castedError = errorService.castError(err); + const updatedTask = tasksService.findTask(taskId); + + if (updatedTask?.status !== TaskStatus.Cancelled && taskId === updatedTask?.id) { + tasksService.updateTask({ + taskId: taskId, + merge: { + status: TaskStatus.Error, + subtitle: t('tasks.subtitles.upload-failed') as string, + }, + }); + // Log the error or report it but don't re-throw it to allow the next folder to be processed + errorService.reportError(castedError); + continue; + } + } + } + } +} diff --git a/src/app/store/slices/storage/folderUtils/uploadFolders.test.ts b/src/app/network/UploadFoldersManager.test.ts similarity index 85% rename from src/app/store/slices/storage/folderUtils/uploadFolders.test.ts rename to src/app/network/UploadFoldersManager.test.ts index 9cae87777..17448d359 100644 --- a/src/app/store/slices/storage/folderUtils/uploadFolders.test.ts +++ b/src/app/network/UploadFoldersManager.test.ts @@ -1,12 +1,12 @@ +import errorService from 'app/core/services/error.service'; +import AppError from 'app/core/types'; +import { DriveFolderData } from 'app/drive/types'; +import { createFolder } from 'app/store/slices/storage/folderUtils/createFolder'; +import tasksService from 'app/tasks/services/tasks.service'; import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; -import tasksService from '../../../../tasks/services/tasks.service'; -import errorService from '../../../../core/services/error.service'; -import AppError from '../../../../core/types'; -import { DriveFolderData } from '../../../../drive/types'; -import { uploadMultipleFolder } from './uploadFolders'; -import { createFolder } from './createFolder'; - -vi.mock('../storage.thunks', () => ({ +import { uploadFoldersWithManager } from './UploadFolderManager'; + +vi.mock('app/store/slices/storage/storage.thunks', () => ({ default: { initializeThunk: vi.fn(), resetNamePathThunk: vi.fn(), @@ -29,7 +29,7 @@ vi.mock('../storage.thunks', () => ({ storageExtraReducers: vi.fn(), })); -vi.mock('../../plan', () => ({ +vi.mock('app/store/slices/plan', () => ({ default: { initializeThunk: vi.fn(), fetchLimitThunk: vi.fn(), @@ -50,7 +50,7 @@ vi.mock('app/drive/services/download.service/downloadFolder', () => ({ }, })); -vi.mock('./createFolder', () => ({ +vi.mock('app/store/slices/storage/folderUtils/createFolder', () => ({ createFolder: vi.fn(), })); @@ -90,8 +90,8 @@ describe('checkUploadFolders', () => { vi.spyOn(tasksService, 'updateTask').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); - await uploadMultipleFolder( - [ + await uploadFoldersWithManager({ + payload: [ { currentFolderId: 'currentFolderId', root: { @@ -106,9 +106,9 @@ describe('checkUploadFolders', () => { }, }, ], - null, - { dispatch: mockDispatch }, - ); + selectedWorkspace: null, + dispatch: mockDispatch, + }); expect(createFolderSpy).toHaveBeenCalledOnce(); }); @@ -165,8 +165,8 @@ describe('checkUploadFolders', () => { vi.spyOn(tasksService, 'updateTask').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); - await uploadMultipleFolder( - [ + await uploadFoldersWithManager({ + payload: [ { currentFolderId: 'currentFolderId', root: { @@ -189,9 +189,9 @@ describe('checkUploadFolders', () => { }, }, ], - null, - { dispatch: mockDispatch }, - ); + selectedWorkspace: null, + dispatch: mockDispatch, + }); expect(createFolderSpy).toHaveBeenCalledTimes(2); }); diff --git a/src/app/store/slices/storage/folderUtils/uploadFolders.ts b/src/app/store/slices/storage/folderUtils/uploadFolders.ts deleted file mode 100644 index b99233dc8..000000000 --- a/src/app/store/slices/storage/folderUtils/uploadFolders.ts +++ /dev/null @@ -1,232 +0,0 @@ -import { TaskStatus, TaskType, UploadFolderTask } from '../../../../tasks/types'; -import { DriveFolderData, DriveItemData } from '../../../../drive/types'; -import { IRoot } from '../types'; -import tasksService from '../../../../tasks/services/tasks.service'; -import errorService from '../../../../core/services/error.service'; -import { planThunks } from '../../plan'; -import { queue, QueueObject } from 'async'; -import { t } from 'i18next'; -import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; -import { RootState } from '../../../../store'; -import { deleteItemsThunk } from '../storage.thunks/deleteItemsThunk'; -import { SdkFactory } from './../../../../core/factory/sdk'; -import { uploadItemsParallelThunk } from '../storage.thunks/uploadItemsThunk'; -import { WorkspaceData } from '@internxt/sdk/dist/workspaces'; -import { createFolder } from './createFolder'; - -const MAX_CONCURRENT_UPLOADS = 6; - -interface UploadFolderThunkPayload { - root: IRoot; - currentFolderId: string; - options?: { - taskId?: string; - withNotification?: boolean; - onSuccess?: () => void; - }; -} - -const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]) => { - return foldersPayload.map(({ root, currentFolderId, options: payloadOptions }) => { - const options = { withNotification: true, ...payloadOptions }; - - const uploadFolderAbortController = new AbortController(); - - let taskId = options?.taskId; - - if (taskId) { - tasksService.updateTask({ - taskId, - merge: { - status: TaskStatus.InProcess, - progress: 0, - }, - }); - } else { - taskId = tasksService.create({ - action: TaskType.UploadFolder, - folderName: root.name, - item: root, - parentFolderId: currentFolderId, - showNotification: !!options.withNotification, - cancellable: true, - }); - } - - return { root, currentFolderId, options: payloadOptions, taskId, abortController: uploadFolderAbortController }; - }); -}; - -const stopUploadTask = async ( - uploadFolderAbortController: AbortController, - dispatch: ThunkDispatch, - relatedTaskId?: string, - rootFolderItem?: DriveFolderData, -) => { - uploadFolderAbortController.abort(); - const relatedTasks = tasksService.getTasks({ relatedTaskId }); - const promises: Promise[] = []; - - // Cancels related tasks - promises.push( - ...(relatedTasks.map((task) => task.stop?.()).filter((promise) => promise !== undefined) as Promise[]), - ); - // Deletes the root folder - if (rootFolderItem) { - promises.push(dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); - const storageClient = SdkFactory.getInstance().createStorageClient(); - promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); - } - await Promise.all(promises); -}; - -function countItemsUnderRoot(root: IRoot): number { - let count = 0; - - const queueOfFolders: Array = [root]; - - while (queueOfFolders.length > 0) { - const folder = queueOfFolders.shift() as IRoot; - - count += folder.childrenFiles.length; - - if (folder.childrenFolders) { - count += folder.childrenFolders.length; - queueOfFolders.push(...folder.childrenFolders); - } - } - - return count; -} - -const wait = (ms: number): Promise => { - return new Promise((resolve) => setTimeout(resolve, ms)); -}; - -export const uploadMultipleFolder = async ( - payload: UploadFolderThunkPayload[], - selectedWorkspace: WorkspaceData | null, - { dispatch }: { dispatch: ThunkDispatch }, -) => { - const payloadWithTaskId = generateTaskIdForFolders(payload); - - const memberId = selectedWorkspace?.workspaceUser?.memberId; - - for (const { root, currentFolderId, options: payloadOptions, taskId, abortController } of payloadWithTaskId) { - const options = { withNotification: true, ...payloadOptions }; - - let alreadyUploaded = 0; - - let rootFolderItem: DriveFolderData | undefined; - const itemsUnderRoot = countItemsUnderRoot(root); - const uploadFolderAbortController = abortController; - - const uploadFolderAsync = async (level: IRoot) => { - if (uploadFolderAbortController.signal.aborted) return; - - const createdFolder = await createFolder( - { - parentFolderId: level.folderId as string, - folderName: level.name, - options: { relatedTaskId: taskId, showErrors: false }, - }, - currentFolderId, - selectedWorkspace, - { dispatch }, - ); - - if (!rootFolderItem) { - rootFolderItem = createdFolder; - } - - tasksService.updateTask({ - taskId, - merge: { - stop: () => stopUploadTask(uploadFolderAbortController, dispatch, taskId, rootFolderItem), - }, - }); - - if (level.childrenFiles.length > 0 || level.childrenFolders.length > 0) { - // Added wait in order to allow enough time for the server to create the folder - await wait(500); - } - - if (level.childrenFiles.length > 0) { - if (uploadFolderAbortController.signal.aborted) return; - await dispatch( - uploadItemsParallelThunk({ - files: level.childrenFiles, - parentFolderId: createdFolder.uuid, - options: { - relatedTaskId: taskId, - showNotifications: false, - showErrors: false, - abortController: uploadFolderAbortController, - disableDuplicatedNamesCheck: true, - }, - filesProgress: { filesUploaded: alreadyUploaded, totalFilesToUpload: itemsUnderRoot }, - }), - ) - .unwrap() - .then(() => { - alreadyUploaded += level.childrenFiles.length + 1; - }); - } - - for (const child of level.childrenFolders) { - if (uploadFolderAbortController.signal.aborted) return; - await uploadFolderQueue.pushAsync({ ...child, folderId: createdFolder.uuid }); - } - }; - - const uploadFolderQueue: QueueObject = queue((task, callback) => { - uploadFolderAsync(task) - .then(() => { - callback(); - }) - .catch((e) => { - callback(e); - }); - }, MAX_CONCURRENT_UPLOADS); - - try { - root.folderId = currentFolderId; - await uploadFolderQueue.pushAsync(root); - - while (uploadFolderQueue.running() > 0 || uploadFolderQueue.length() > 0) { - await uploadFolderQueue.drain(); - } - - tasksService.updateTask({ - taskId: taskId, - merge: { - itemUUID: { rootFolderUUID: rootFolderItem?.uuid }, - status: TaskStatus.Success, - }, - }); - - options.onSuccess?.(); - - setTimeout(() => { - dispatch(planThunks.fetchUsageThunk()); - if (memberId) dispatch(planThunks.fetchBusinessLimitUsageThunk()); - }, 1000); - } catch (err: unknown) { - const castedError = errorService.castError(err); - const updatedTask = tasksService.findTask(taskId); - - if (updatedTask?.status !== TaskStatus.Cancelled && taskId === updatedTask?.id) { - tasksService.updateTask({ - taskId: taskId, - merge: { - status: TaskStatus.Error, - subtitle: t('tasks.subtitles.upload-failed') as string, - }, - }); - // Log the error or report it but don't re-throw it to allow the next folder to be processed - errorService.reportError(castedError); - continue; - } - } - } -}; From 6676963c1355db62347a2fd262187bd287065562 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 3 Feb 2025 17:39:27 +0100 Subject: [PATCH 12/28] Improve memory usage checks in UploadManager to use optional chaining and prevent unnecessary concurrency updates --- src/app/network/UploadManager.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/network/UploadManager.ts b/src/app/network/UploadManager.ts index 14da73c1b..2b6498e5b 100644 --- a/src/app/network/UploadManager.ts +++ b/src/app/network/UploadManager.ts @@ -373,7 +373,7 @@ class UploadManager { } private manageMemoryUsage() { - if (window.performance && (window.performance as any).memory) { + if (window?.performance?.memory) { const memory = window.performance.memory; if (memory && memory?.jsHeapSizeLimit !== null && memory.usedJSHeapSize !== null) { @@ -386,8 +386,10 @@ class UploadManager { this.uploadQueue.concurrency + 1, this.filesGroups[FileSizeType.Small].concurrency, ); - console.warn(`Memory usage under 70%. Increasing upload concurrency to ${newConcurrency}`); - this.uploadQueue.concurrency = newConcurrency; + if (newConcurrency !== this.uploadQueue.concurrency) { + console.warn(`Memory usage under 70%. Increasing upload concurrency to ${newConcurrency}`); + this.uploadQueue.concurrency = newConcurrency; + } } const shouldReduceConcurrency = memoryUsagePercentage >= 0.8 && this.uploadQueue.concurrency > 1; From 11398a7c85a67185dfbd059f6a101dcc528952b9 Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 4 Feb 2025 11:56:53 +0100 Subject: [PATCH 13/28] Refactor countItemsUnderRoot function to use arrow function syntax --- src/app/network/UploadFolderManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 470e99960..92cda6b6f 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -102,7 +102,7 @@ const stopUploadTask = async ( await Promise.all(promises); }; -function countItemsUnderRoot(root: IRoot): number { +const countItemsUnderRoot = (root: IRoot): number => { let count = 1; const queueOfFolders: Array = [root]; @@ -118,7 +118,7 @@ function countItemsUnderRoot(root: IRoot): number { } return count; -} +}; const wait = (ms: number): Promise => { return new Promise((resolve) => setTimeout(resolve, ms)); From 18225d873f0529205c21cd36bb85da2c352b5d0a Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 4 Feb 2025 11:59:02 +0100 Subject: [PATCH 14/28] Refactor TaskFolder interface to make options property optional and simplify its structure --- src/app/network/UploadFolderManager.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 92cda6b6f..0ebc9cd97 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -29,13 +29,10 @@ interface UploadFolderThunkPayload { interface TaskFolder { root: IRoot; currentFolderId: string; - options: - | { - taskId?: string; - withNotification?: boolean; - onSuccess?: () => void; - } - | undefined; + options?: { + withNotification?: boolean; + onSuccess?: () => void; + }; taskId: string; abortController: AbortController; } From 5ec4461674aed3ab26a104729e5b998fb6b47cd1 Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 4 Feb 2025 12:10:29 +0100 Subject: [PATCH 15/28] Refactor UploadFoldersManager to use readonly for class properties and methods --- src/app/network/UploadFolderManager.ts | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 0ebc9cd97..7c6ab4faa 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -135,10 +135,10 @@ export const uploadFoldersWithManager = ({ }; class UploadFoldersManager { - private payload: UploadFolderThunkPayload[]; - private selectedWorkspace: WorkspaceData | null; - private dispatch: ThunkDispatch; - private abortController?: AbortController; + private readonly payload: UploadFolderThunkPayload[]; + private readonly selectedWorkspace: WorkspaceData | null; + private readonly dispatch: ThunkDispatch; + private readonly abortController?: AbortController; private tasksInfo: Record = {}; @@ -152,7 +152,7 @@ class UploadFoldersManager { this.dispatch = dispatch; } - private uploadFoldersQueue: QueueObject = queue( + private readonly uploadFoldersQueue: QueueObject = queue( (task, next: (err: Error | null, res?: DriveFolderData) => void) => { if (this.abortController?.signal.aborted) return; @@ -169,7 +169,7 @@ class UploadFoldersManager { MAX_CONCURRENT_UPLOADS, ); - private uploadFolderAsync = async (taskFolder: TaskFolder) => { + private readonly uploadFolderAsync = async (taskFolder: TaskFolder) => { const { root: level, currentFolderId, taskId, abortController } = taskFolder; if (abortController.signal.aborted) return; @@ -243,7 +243,7 @@ class UploadFoldersManager { return createdFolder; }; - private manageMemoryUsage() { + private readonly manageMemoryUsage = () => { if (window?.performance?.memory) { const memory = window.performance.memory; @@ -268,9 +268,9 @@ class UploadFoldersManager { } else { console.warn('Memory usage control is not available'); } - } + }; - async run(): Promise { + public readonly run = async (): Promise => { const payloadWithTaskId = generateTaskIdForFolders(this.payload); const memberId = this.selectedWorkspace?.workspaceUser?.memberId; @@ -326,5 +326,5 @@ class UploadFoldersManager { } } } - } + }; } From 449bda2c52c5f3884a6d49f8ac720600e55c1802 Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 4 Feb 2025 18:03:48 +0100 Subject: [PATCH 16/28] Refactor UploadFoldersManager to replace async push with synchronous push for upload queue --- src/app/network/UploadFolderManager.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 7c6ab4faa..ddacd6758 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -217,10 +217,6 @@ class UploadFoldersManager { abortController: abortController, disableDuplicatedNamesCheck: true, }, - filesProgress: { - filesUploaded: this.tasksInfo[taskId].progress.itemsUploaded, - totalFilesToUpload: this.tasksInfo[taskId].progress.totalItems, - }, }), ) .unwrap() @@ -231,7 +227,8 @@ class UploadFoldersManager { for (const child of level.childrenFolders) { if (abortController.signal.aborted) return; - await this.uploadFoldersQueue.pushAsync({ + + this.uploadFoldersQueue.push({ root: { ...child, folderId: createdFolder.uuid }, currentFolderId: taskFolder.currentFolderId, options: taskFolder.options, @@ -286,9 +283,17 @@ class UploadFoldersManager { }, }; + tasksService.updateTask({ + taskId, + merge: { + status: TaskStatus.InProcess, + progress: 0, + }, + }); + try { root.folderId = currentFolderId; - await this.uploadFoldersQueue.pushAsync(taskFolder); + this.uploadFoldersQueue.push(taskFolder); while (this.uploadFoldersQueue.running() > 0 || this.uploadFoldersQueue.length() > 0) { await this.uploadFoldersQueue.drain(); From 30d7ca831dc02fbc29f342afd6d4f1443a422260 Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 4 Feb 2025 18:04:49 +0100 Subject: [PATCH 17/28] Refactor stopUploadTask method to improve task cancellation and error handling --- src/app/network/UploadFolderManager.ts | 52 ++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index ddacd6758..12fa347c4 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -76,29 +76,6 @@ const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): T }); }; -const stopUploadTask = async ( - uploadFolderAbortController: AbortController, - dispatch: ThunkDispatch, - relatedTaskId?: string, - rootFolderItem?: DriveFolderData, -) => { - uploadFolderAbortController.abort(); - const relatedTasks = tasksService.getTasks({ relatedTaskId }); - const promises: Promise[] = []; - - // Cancels related tasks - promises.push( - ...(relatedTasks.map((task) => task.stop?.()).filter((promise) => promise !== undefined) as Promise[]), - ); - // Deletes the root folder - if (rootFolderItem) { - promises.push(dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); - const storageClient = SdkFactory.getInstance().createStorageClient(); - promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); - } - await Promise.all(promises); -}; - const countItemsUnderRoot = (root: IRoot): number => { let count = 1; @@ -267,6 +244,35 @@ class UploadFoldersManager { } }; + private readonly stopUploadTask = async (taskId: string, uploadFolderAbortController: AbortController) => { + uploadFolderAbortController.abort(); + const relatedTasks = tasksService.getTasks({ relatedTaskId: taskId }); + const promises: Promise[] = []; + + // Cancels related tasks + promises.push( + ...(relatedTasks.map((task) => task.stop?.()).filter((promise) => promise !== undefined) as Promise[]), + ); + // Deletes the root folder + const rootFolderItem = this.tasksInfo[taskId].rootFolderItem; + if (rootFolderItem) { + promises.push(this.dispatch(deleteItemsThunk([rootFolderItem as DriveItemData])).unwrap()); + const storageClient = SdkFactory.getInstance().createStorageClient(); + promises.push(storageClient.deleteFolder(rootFolderItem.id) as Promise); + } + await Promise.allSettled(promises); + }; + + private readonly killQueueAndNotifyError = (taskId: string) => { + this.uploadFoldersQueue.kill(); + tasksService.updateTask({ + taskId: taskId, + merge: { + status: TaskStatus.Error, + }, + }); + }; + public readonly run = async (): Promise => { const payloadWithTaskId = generateTaskIdForFolders(this.payload); From e23857feee336be0e4ce8317c3587bf5d6a65d23 Mon Sep 17 00:00:00 2001 From: larryrider Date: Tue, 4 Feb 2025 18:07:49 +0100 Subject: [PATCH 18/28] Enhance error handling in UploadFoldersManager by task listeners for improved task cancellation and queue management --- src/app/network/UploadFolderManager.ts | 59 ++++++++++++++++++++------ 1 file changed, 47 insertions(+), 12 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 12fa347c4..35602632d 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -1,4 +1,4 @@ -import { TaskStatus, TaskType, UploadFolderTask } from '../tasks/types'; +import { TaskData, TaskEvent, TaskStatus, TaskType, UploadFolderTask } from '../tasks/types'; import { DriveFolderData, DriveItemData } from '../drive/types'; import { IRoot } from '../store/slices/storage/types'; import tasksService from '../tasks/services/tasks.service'; @@ -151,16 +151,24 @@ class UploadFoldersManager { if (abortController.signal.aborted) return; - const createdFolder = await createFolder( - { - parentFolderId: level.folderId as string, - folderName: level.name, - options: { relatedTaskId: taskId, showErrors: false }, - }, - currentFolderId, - this.selectedWorkspace, - { dispatch: this.dispatch }, - ); + let createdFolder: DriveFolderData; + + try { + createdFolder = await createFolder( + { + parentFolderId: level.folderId as string, + folderName: level.name, + options: { relatedTaskId: taskId, showErrors: false }, + }, + currentFolderId, + this.selectedWorkspace, + { dispatch: this.dispatch }, + ); + } catch (error) { + this.stopUploadTask(taskId, abortController); + this.killQueueAndNotifyError(taskId); + return; + } if (!this.tasksInfo[taskId].rootFolderItem) { this.tasksInfo[taskId].rootFolderItem = createdFolder; @@ -172,7 +180,7 @@ class UploadFoldersManager { taskId, merge: { progress: this.tasksInfo[taskId].progress.itemsUploaded / this.tasksInfo[taskId].progress.totalItems, - stop: () => stopUploadTask(abortController, this.dispatch, taskId, this.tasksInfo[taskId].rootFolderItem), + stop: () => this.stopUploadTask(taskId, abortController), }, }); @@ -199,6 +207,11 @@ class UploadFoldersManager { .unwrap() .then(() => { this.tasksInfo[taskId].progress.itemsUploaded += level.childrenFiles.length; + }) + .catch(() => { + this.stopUploadTask(taskId, abortController); + this.killQueueAndNotifyError(taskId); + return; }); } @@ -297,10 +310,29 @@ class UploadFoldersManager { }, }); + const cancelQueueListener = (task?: TaskData) => { + const isCurrentTask = task && task.id === taskId; + if (isCurrentTask && task.status === TaskStatus.Cancelled) { + this.uploadFoldersQueue.kill(); + } + }; + + const updateQueueListener = (task?: TaskData) => { + const isCurrentTask = task && task.id === taskId; + if (isCurrentTask && task.status === TaskStatus.InProcess) { + this.uploadFoldersQueue.resume(); + } else if (isCurrentTask && task.status === TaskStatus.Paused) { + this.uploadFoldersQueue.pause(); + } + }; + try { root.folderId = currentFolderId; this.uploadFoldersQueue.push(taskFolder); + tasksService.addListener({ event: TaskEvent.TaskCancelled, listener: cancelQueueListener }); + tasksService.addListener({ event: TaskEvent.TaskUpdated, listener: updateQueueListener }); + while (this.uploadFoldersQueue.running() > 0 || this.uploadFoldersQueue.length() > 0) { await this.uploadFoldersQueue.drain(); } @@ -335,6 +367,9 @@ class UploadFoldersManager { errorService.reportError(castedError); continue; } + } finally { + tasksService.removeListener({ event: TaskEvent.TaskCancelled, listener: cancelQueueListener }); + tasksService.removeListener({ event: TaskEvent.TaskUpdated, listener: updateQueueListener }); } } }; From 25a8e2a556c11e67f4ea1417ebb964be89411242 Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 5 Feb 2025 10:06:20 +0100 Subject: [PATCH 19/28] Added abort upload test --- src/app/network/UploadFolderManager.ts | 4 +- src/app/network/UploadFoldersManager.test.ts | 112 ++++++++++++++++++- 2 files changed, 108 insertions(+), 8 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 35602632d..674f550db 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -26,7 +26,7 @@ interface UploadFolderThunkPayload { }; } -interface TaskFolder { +export interface TaskFolder { root: IRoot; currentFolderId: string; options?: { @@ -111,7 +111,7 @@ export const uploadFoldersWithManager = ({ return uploadFoldersManager.run(); }; -class UploadFoldersManager { +export class UploadFoldersManager { private readonly payload: UploadFolderThunkPayload[]; private readonly selectedWorkspace: WorkspaceData | null; private readonly dispatch: ThunkDispatch; diff --git a/src/app/network/UploadFoldersManager.test.ts b/src/app/network/UploadFoldersManager.test.ts index 17448d359..ab06994fb 100644 --- a/src/app/network/UploadFoldersManager.test.ts +++ b/src/app/network/UploadFoldersManager.test.ts @@ -4,7 +4,7 @@ import { DriveFolderData } from 'app/drive/types'; import { createFolder } from 'app/store/slices/storage/folderUtils/createFolder'; import tasksService from 'app/tasks/services/tasks.service'; import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; -import { uploadFoldersWithManager } from './UploadFolderManager'; +import { TaskFolder, UploadFoldersManager, uploadFoldersWithManager } from './UploadFolderManager'; vi.mock('app/store/slices/storage/storage.thunks', () => ({ default: { @@ -37,7 +37,13 @@ vi.mock('app/store/slices/plan', () => ({ fetchSubscriptionThunk: vi.fn(), fetchBusinessLimitUsageThunk: vi.fn(), }, - planThunks: vi.fn(), + planThunks: { + initializeThunk: vi.fn(), + fetchLimitThunk: vi.fn(), + fetchUsageThunk: vi.fn(), + fetchSubscriptionThunk: vi.fn(), + fetchBusinessLimitUsageThunk: vi.fn(), + }, })); vi.mock('app/drive/services/download.service/downloadFolder', () => ({ @@ -83,10 +89,11 @@ describe('checkUploadFolders', () => { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; + const taskId = 'task-id'; const createFolderSpy = (createFolder as Mock).mockResolvedValueOnce(mockFolder); - vi.spyOn(tasksService, 'create').mockReturnValue('task-id'); + vi.spyOn(tasksService, 'create').mockReturnValue(taskId); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); @@ -102,7 +109,7 @@ describe('checkUploadFolders', () => { fullPathEdited: 'path1', }, options: { - taskId: 'task-id', + taskId, }, }, ], @@ -156,12 +163,13 @@ describe('checkUploadFolders', () => { createdAt: new Date().toISOString(), updatedAt: new Date().toISOString(), }; + const taskId = 'task-id'; const createFolderSpy = (createFolder as Mock) .mockResolvedValueOnce(mockParentFolder) .mockResolvedValueOnce(mockChildFolder); - vi.spyOn(tasksService, 'create').mockReturnValue('task-id'); + vi.spyOn(tasksService, 'create').mockReturnValue(taskId); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); @@ -185,7 +193,7 @@ describe('checkUploadFolders', () => { fullPathEdited: 'path1', }, options: { - taskId: 'task-id', + taskId, }, }, ], @@ -195,4 +203,96 @@ describe('checkUploadFolders', () => { expect(createFolderSpy).toHaveBeenCalledTimes(2); }); + + it('should abort the upload if abortController is called', async () => { + const mockParentFolder: DriveFolderData = { + id: 1, + uuid: 'uuid1', + name: 'Folder1', + bucket: 'bucket', + parentId: 0, + parent_id: 0, + parentUuid: 'parentUuid', + userId: 0, + user_id: 0, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: 'Folder1', + deleted: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const mockChildFolder: DriveFolderData = { + id: 2, + uuid: 'uuid2', + name: 'Folder2', + bucket: 'bucket', + parentId: 1, + parent_id: 1, + parentUuid: 'uuid1', + userId: 0, + user_id: 0, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: 'Folder2', + deleted: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const payload = []; + const selectedWorkspace = null; + const taskId = 'task-id'; + + const manager = new UploadFoldersManager(payload, selectedWorkspace, mockDispatch); + const abortController = new AbortController(); + + const taskFolder: TaskFolder = { + currentFolderId: 'currentFolderId', + root: { + folderId: mockParentFolder.parentUuid, + childrenFiles: [], + childrenFolders: [ + { + folderId: mockParentFolder.uuid, + childrenFiles: [], + childrenFolders: [], + name: mockChildFolder.name, + fullPathEdited: 'path2', + }, + ], + name: mockParentFolder.name, + fullPathEdited: 'path1', + }, + taskId, + abortController, + }; + + const createFolderSpy = (createFolder as Mock) + .mockResolvedValueOnce(mockParentFolder) + .mockResolvedValueOnce(mockChildFolder); + + manager['tasksInfo'][taskId] = { + progress: { + itemsUploaded: 0, + totalItems: 2, + }, + rootFolderItem: mockParentFolder, + }; + + abortController.abort(); + + const uploadPromise = manager['uploadFolderAsync'](taskFolder); + + await expect(uploadPromise).resolves.toBeUndefined(); + expect(abortController.signal.aborted).toBe(true); + expect(createFolderSpy).not.toHaveBeenCalled(); + }); }); From 99154a3097aa4a704460c6558a36d0c9bc123f9e Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 5 Feb 2025 16:55:02 +0100 Subject: [PATCH 20/28] Implement retry logic for folder creation with a maximum of 2 attempts --- src/app/network/UploadFolderManager.ts | 38 +++++++++++++++++--------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 674f550db..6bf2cf61a 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -3,7 +3,7 @@ import { DriveFolderData, DriveItemData } from '../drive/types'; import { IRoot } from '../store/slices/storage/types'; import tasksService from '../tasks/services/tasks.service'; import errorService from '../core/services/error.service'; -import { queue, QueueObject } from 'async'; +import { queue, QueueObject, retry } from 'async'; import { t } from 'i18next'; import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; import { RootState } from '../store'; @@ -15,6 +15,7 @@ import { createFolder } from '../store/slices/storage/folderUtils/createFolder'; import { deleteItemsThunk } from '../store/slices/storage/storage.thunks/deleteItemsThunk'; const MAX_CONCURRENT_UPLOADS = 6; +const MAX_UPLOAD_ATTEMPTS = 2; interface UploadFolderThunkPayload { root: IRoot; @@ -151,25 +152,36 @@ export class UploadFoldersManager { if (abortController.signal.aborted) return; - let createdFolder: DriveFolderData; + let createdFolder: DriveFolderData | undefined; try { - createdFolder = await createFolder( - { - parentFolderId: level.folderId as string, - folderName: level.name, - options: { relatedTaskId: taskId, showErrors: false }, - }, - currentFolderId, - this.selectedWorkspace, - { dispatch: this.dispatch }, - ); + let uploadAttempts = 0; + const createFolderFunction = async () => { + uploadAttempts++; + createdFolder = await createFolder( + { + parentFolderId: level.folderId as string, + folderName: level.name, + options: { relatedTaskId: taskId, showErrors: false }, + }, + currentFolderId, + this.selectedWorkspace, + { dispatch: this.dispatch }, + ); + }; + await retry({ times: MAX_UPLOAD_ATTEMPTS, interval: 600 }, createFolderFunction); } catch (error) { this.stopUploadTask(taskId, abortController); this.killQueueAndNotifyError(taskId); return; } + if (!createdFolder) { + this.stopUploadTask(taskId, abortController); + this.killQueueAndNotifyError(taskId); + return; + } + if (!this.tasksInfo[taskId].rootFolderItem) { this.tasksInfo[taskId].rootFolderItem = createdFolder; } @@ -186,7 +198,7 @@ export class UploadFoldersManager { if (level.childrenFiles.length > 0 || level.childrenFolders.length > 0) { // Added wait in order to allow enough time for the server to create the folder - await wait(500); + await wait(600); } if (level.childrenFiles.length > 0) { From 4e4a1f1e20b179aa60350c70ccd663afa6a6742b Mon Sep 17 00:00:00 2001 From: larryrider Date: Wed, 5 Feb 2025 17:00:00 +0100 Subject: [PATCH 21/28] Increase max upload attempts from 1 to 2 to enable retrying file uploads --- src/app/network/UploadManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/network/UploadManager.ts b/src/app/network/UploadManager.ts index 2b6498e5b..e1f4c9ee6 100644 --- a/src/app/network/UploadManager.ts +++ b/src/app/network/UploadManager.ts @@ -14,7 +14,7 @@ import { FileToUpload } from '../drive/services/file.service/types'; const TWENTY_MEGABYTES = 20 * 1024 * 1024; const USE_MULTIPART_THRESHOLD_BYTES = 50 * 1024 * 1024; -const MAX_UPLOAD_ATTEMPTS = 1; +const MAX_UPLOAD_ATTEMPTS = 2; enum FileSizeType { Big = 'big', From 48e898b7f94a286ab118f942bd77eecfaf6288fd Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 6 Feb 2025 10:11:19 +0100 Subject: [PATCH 22/28] Add onFileUploadCallback to fix upload files progress and enhance folder creation retry logic --- src/app/network/UploadFolderManager.ts | 16 +++++++++++----- src/app/network/UploadManager.ts | 10 ++++++++++ .../storage/storage.thunks/uploadItemsThunk.ts | 6 ++++-- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 6bf2cf61a..68220b58a 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -155,9 +155,7 @@ export class UploadFoldersManager { let createdFolder: DriveFolderData | undefined; try { - let uploadAttempts = 0; const createFolderFunction = async () => { - uploadAttempts++; createdFolder = await createFolder( { parentFolderId: level.folderId as string, @@ -213,13 +211,21 @@ export class UploadFoldersManager { showErrors: false, abortController: abortController, disableDuplicatedNamesCheck: true, + disableExistenceCheck: true, + }, + onFileUploadCallback: () => { + this.tasksInfo[taskId].progress.itemsUploaded += 1; + tasksService.updateTask({ + taskId, + merge: { + progress: this.tasksInfo[taskId].progress.itemsUploaded / this.tasksInfo[taskId].progress.totalItems, + stop: () => this.stopUploadTask(taskId, abortController), + }, + }); }, }), ) .unwrap() - .then(() => { - this.tasksInfo[taskId].progress.itemsUploaded += level.childrenFiles.length; - }) .catch(() => { this.stopUploadTask(taskId, abortController); this.killQueueAndNotifyError(taskId); diff --git a/src/app/network/UploadManager.ts b/src/app/network/UploadManager.ts index e1f4c9ee6..49c2270cc 100644 --- a/src/app/network/UploadManager.ts +++ b/src/app/network/UploadManager.ts @@ -63,6 +63,7 @@ export const uploadFileWithManager = ( abortController?: AbortController, options?: Options, relatedTaskProgress?: { filesUploaded: number; totalFilesToUpload: number }, + onFileUploadCallback?: (driveFileData: DriveFileData) => void, ): Promise => { const uploadManager = new UploadManager( files, @@ -71,6 +72,7 @@ export const uploadFileWithManager = ( abortController, options, relatedTaskProgress, + onFileUploadCallback, ); return uploadManager.run(); }; @@ -84,6 +86,7 @@ class UploadManager { private options?: Options; private relatedTaskProgress?: { filesUploaded: number; totalFilesToUpload: number }; private maxSpaceOccupiedCallback: () => void; + private onFileUploadCallback?: (driveFileData: DriveFileData) => void; private uploadRepository?: PersistUploadRepository; private filesUploadedList: (DriveFileData & { taskId: string })[] = []; private filesGroups: Record< @@ -110,6 +113,7 @@ class UploadManager { concurrency: 6, }, }; + private uploadQueue: QueueObject = queue( (fileData, next: (err: Error | null, res?: DriveFileData) => void) => { if (this.abortController?.signal.aborted ?? fileData.abortController?.signal.aborted) return; @@ -241,6 +245,10 @@ class UploadManager { uploadProgress: this.uploadsProgress[uploadId] ?? 0, }, }); + + if (this.onFileUploadCallback) { + this.onFileUploadCallback(driveFileDataWithNameParsed); + } next(null, driveFileDataWithNameParsed); }) .catch((error) => { @@ -277,6 +285,7 @@ class UploadManager { abortController?: AbortController, options?: Options, relatedTaskProgress?: { filesUploaded: number; totalFilesToUpload: number }, + onFileUploadCallback?: (driveFileData: DriveFileData) => void, ) { this.items = items; this.abortController = abortController; @@ -284,6 +293,7 @@ class UploadManager { this.relatedTaskProgress = relatedTaskProgress; this.maxSpaceOccupiedCallback = maxSpaceOccupiedCallback; this.uploadRepository = uploadRepository; + this.onFileUploadCallback = onFileUploadCallback; } private handleUploadErrors({ diff --git a/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts b/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts index cd26d5f39..1ed2ad1a5 100644 --- a/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts @@ -43,6 +43,7 @@ interface UploadItemsPayload { parentFolderId: string; options?: Partial; filesProgress?: { filesUploaded: number; totalFilesToUpload: number }; + onFileUploadCallback?: (driveFileData: DriveFileData) => void; } const DEFAULT_OPTIONS: Partial = { @@ -394,7 +395,7 @@ export const uploadSharedItemsThunk = createAsyncThunk( 'storage/uploadItems', async ( - { files, parentFolderId, options: payloadOptions, filesProgress }: UploadItemsPayload, + { files, parentFolderId, options: payloadOptions, filesProgress, onFileUploadCallback }: UploadItemsPayload, { getState, dispatch }, ) => { const state = getState(); @@ -423,6 +424,7 @@ export const uploadItemsParallelThunk = createAsyncThunk Date: Thu, 6 Feb 2025 10:17:17 +0100 Subject: [PATCH 23/28] Add disableExistenceCheck option to prepareFilesToUpload for conditional file existence validation --- .../storage/fileUtils/prepareFilesToUpload.ts | 20 ++++++++++++------- .../storage.thunks/uploadItemsThunk.ts | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app/store/slices/storage/fileUtils/prepareFilesToUpload.ts b/src/app/store/slices/storage/fileUtils/prepareFilesToUpload.ts index f0f74ffb1..26d725618 100644 --- a/src/app/store/slices/storage/fileUtils/prepareFilesToUpload.ts +++ b/src/app/store/slices/storage/fileUtils/prepareFilesToUpload.ts @@ -10,11 +10,13 @@ export const prepareFilesToUpload = async ({ parentFolderId, disableDuplicatedNamesCheck = false, fileType, + disableExistenceCheck = false, }: { files: File[]; parentFolderId: string; disableDuplicatedNamesCheck?: boolean; fileType?: string; + disableExistenceCheck?: boolean; }): Promise<{ filesToUpload: FileToUpload[]; zeroLengthFilesNumber: number }> => { let filesToUpload: FileToUpload[] = []; let zeroLengthFilesNumber = 0; @@ -38,13 +40,17 @@ export const prepareFilesToUpload = async ({ }; const processFilesBatch = async (filesBatch: File[]) => { - const { duplicatedFilesResponse, filesWithoutDuplicates, filesWithDuplicates } = await checkDuplicatedFiles( - filesBatch, - parentFolderId, - ); - - await processFiles(filesWithoutDuplicates as File[], true); - await processFiles(filesWithDuplicates as File[], disableDuplicatedNamesCheck, duplicatedFilesResponse); + if (disableExistenceCheck) { + await processFiles(filesBatch, true); + } else { + const { duplicatedFilesResponse, filesWithoutDuplicates, filesWithDuplicates } = await checkDuplicatedFiles( + filesBatch, + parentFolderId, + ); + + await processFiles(filesWithoutDuplicates as File[], true); + await processFiles(filesWithDuplicates as File[], disableDuplicatedNamesCheck, duplicatedFilesResponse); + } }; for (let i = 0; i < files.length; i += BATCH_SIZE) { diff --git a/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts b/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts index 1ed2ad1a5..8e46a717f 100644 --- a/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts @@ -34,6 +34,7 @@ interface UploadItemsThunkOptions { onSuccess: () => void; isRetriedUpload?: boolean; disableDuplicatedNamesCheck?: boolean; + disableExistenceCheck?: boolean; } interface UploadItemsPayload { From 1f85322c3c4fc087651213d8ac106bd1371eeef8 Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 6 Feb 2025 15:57:35 +0100 Subject: [PATCH 24/28] Refactor create folder to fix upload attempts --- src/app/network/UploadFolderManager.ts | 29 ++++++++++++++------------ 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 68220b58a..11c952ea0 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -3,7 +3,7 @@ import { DriveFolderData, DriveItemData } from '../drive/types'; import { IRoot } from '../store/slices/storage/types'; import tasksService from '../tasks/services/tasks.service'; import errorService from '../core/services/error.service'; -import { queue, QueueObject, retry } from 'async'; +import { queue, QueueObject } from 'async'; import { t } from 'i18next'; import { AnyAction, ThunkDispatch } from '@reduxjs/toolkit'; import { RootState } from '../store'; @@ -14,9 +14,6 @@ import { uploadItemsParallelThunk } from '../store/slices/storage/storage.thunks import { createFolder } from '../store/slices/storage/folderUtils/createFolder'; import { deleteItemsThunk } from '../store/slices/storage/storage.thunks/deleteItemsThunk'; -const MAX_CONCURRENT_UPLOADS = 6; -const MAX_UPLOAD_ATTEMPTS = 2; - interface UploadFolderThunkPayload { root: IRoot; currentFolderId: string; @@ -113,6 +110,9 @@ export const uploadFoldersWithManager = ({ }; export class UploadFoldersManager { + private static readonly MAX_CONCURRENT_UPLOADS = 6; + private static readonly MAX_UPLOAD_ATTEMPTS = 2; + private readonly payload: UploadFolderThunkPayload[]; private readonly selectedWorkspace: WorkspaceData | null; private readonly dispatch: ThunkDispatch; @@ -144,7 +144,7 @@ export class UploadFoldersManager { next(e); }); }, - MAX_CONCURRENT_UPLOADS, + UploadFoldersManager.MAX_CONCURRENT_UPLOADS, ); private readonly uploadFolderAsync = async (taskFolder: TaskFolder) => { @@ -154,8 +154,10 @@ export class UploadFoldersManager { let createdFolder: DriveFolderData | undefined; - try { - const createFolderFunction = async () => { + let uploadAttempts = 0; + while (!createdFolder && uploadAttempts < UploadFoldersManager.MAX_UPLOAD_ATTEMPTS) { + uploadAttempts++; + try { createdFolder = await createFolder( { parentFolderId: level.folderId as string, @@ -166,12 +168,13 @@ export class UploadFoldersManager { this.selectedWorkspace, { dispatch: this.dispatch }, ); - }; - await retry({ times: MAX_UPLOAD_ATTEMPTS, interval: 600 }, createFolderFunction); - } catch (error) { - this.stopUploadTask(taskId, abortController); - this.killQueueAndNotifyError(taskId); - return; + } catch { + if (uploadAttempts >= UploadFoldersManager.MAX_UPLOAD_ATTEMPTS) { + this.stopUploadTask(taskId, abortController); + this.killQueueAndNotifyError(taskId); + return; + } + } } if (!createdFolder) { From d8aab893b637e0ffcaaff3756fa367b1752ed8bc Mon Sep 17 00:00:00 2001 From: larryrider Date: Thu, 6 Feb 2025 17:14:31 +0100 Subject: [PATCH 25/28] Refactor upload folder queue handling to await taskFolder push for improved synchronization --- src/app/network/UploadFolderManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 11c952ea0..2bac0a92f 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -347,12 +347,12 @@ export class UploadFoldersManager { } }; + tasksService.addListener({ event: TaskEvent.TaskCancelled, listener: cancelQueueListener }); + tasksService.addListener({ event: TaskEvent.TaskUpdated, listener: updateQueueListener }); + try { root.folderId = currentFolderId; - this.uploadFoldersQueue.push(taskFolder); - - tasksService.addListener({ event: TaskEvent.TaskCancelled, listener: cancelQueueListener }); - tasksService.addListener({ event: TaskEvent.TaskUpdated, listener: updateQueueListener }); + await this.uploadFoldersQueue.pushAsync(taskFolder); while (this.uploadFoldersQueue.running() > 0 || this.uploadFoldersQueue.length() > 0) { await this.uploadFoldersQueue.drain(); From 833e87e180531f3e59942577159ee1db44cea49e Mon Sep 17 00:00:00 2001 From: larryrider Date: Fri, 7 Feb 2025 13:53:15 +0100 Subject: [PATCH 26/28] Enhance abort listener for upload tasks --- src/app/network/UploadFolderManager.ts | 3 ++- src/app/network/UploadManager.ts | 24 +++++++++++++++++------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 2bac0a92f..374f9771b 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -255,7 +255,7 @@ export class UploadFoldersManager { if (window?.performance?.memory) { const memory = window.performance.memory; - if (memory && memory?.jsHeapSizeLimit !== null && memory.usedJSHeapSize !== null) { + if (memory.jsHeapSizeLimit != null && memory.usedJSHeapSize != null) { const memoryUsagePercentage = memory.usedJSHeapSize / memory.jsHeapSizeLimit; const shouldIncreaseConcurrency = memoryUsagePercentage < 0.7; @@ -303,6 +303,7 @@ export class UploadFoldersManager { taskId: taskId, merge: { status: TaskStatus.Error, + subtitle: t('tasks.subtitles.upload-failed') as string, }, }); }; diff --git a/src/app/network/UploadManager.ts b/src/app/network/UploadManager.ts index 49c2270cc..17a02ab20 100644 --- a/src/app/network/UploadManager.ts +++ b/src/app/network/UploadManager.ts @@ -162,6 +162,8 @@ class UploadManager { isRetriedUpload: !!this.options?.isRetriedUpload, }; + let abortListener: (task: TaskData) => void; + uploadFile( fileData.userEmail, { @@ -189,15 +191,17 @@ class UploadManager { isTeam: false, abortController: this.abortController ?? fileData.abortController, ownerUserAuthenticationData: this.options?.ownerUserAuthenticationData, - abortCallback: (abort?: () => void) => + abortCallback: (abort?: () => void) => { + abortListener = (task) => { + if (task.id === taskId) { + abort?.(); + } + }; tasksService.addListener({ event: TaskEvent.TaskCancelled, - listener: (task) => { - if (task.id === taskId) { - abort?.(); - } - }, - }), + listener: abortListener, + }); + }, }, continueUploadOptions, ) @@ -270,6 +274,12 @@ class UploadManager { uploadId, }); } + }) + .finally(() => { + tasksService.removeListener({ + event: TaskEvent.TaskCancelled, + listener: abortListener, + }); }); }; From 1d84786b57373380a8e52cd2903d156dc0d34ee0 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 10 Feb 2025 17:23:11 +0100 Subject: [PATCH 27/28] Refactor upload logic to use uploadFoldersWithManager for improved folder upload handling --- .../hooks/useDriveItemDragAndDrop.tsx | 17 ++++++++++++----- .../NameCollisionContainer.tsx | 16 ++++++++++------ 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx index 78677f861..70d45033f 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx @@ -11,6 +11,8 @@ import { handleRepeatedUploadingFolders, } from '../../../../../store/slices/storage/storage.thunks/renameItemsThunk'; import { DriveItemData } from '../../../../types'; +import { uploadFoldersWithManager } from '../../../../../network/UploadFolderManager'; +import workspacesSelectors from '../../../../../store/slices/workspaces/workspaces.selectors'; interface DragSourceCollectorProps { isDraggingThisItem: boolean; @@ -49,6 +51,7 @@ export const useDriveItemDrop = (item: DriveItemData): DriveItemDrop => { const isSomeItemSelected = useAppSelector(storageSelectors.isSomeItemSelected); const { selectedItems } = useAppSelector((state) => state.storage); const namePath = useAppSelector((state) => state.storage.namePath); + const selectedWorkspace = useAppSelector(workspacesSelectors.getSelectedWorkspace); const [{ isDraggingOverThisItem, canDrop }, connectDropTarget] = useDrop< DriveItemData | DriveItemData[], unknown, @@ -111,11 +114,15 @@ export const useDriveItemDrop = (item: DriveItemData): DriveItemDrop => { } if (rootList.length) { // Directory tree - for (const root of rootList) { - const currentFolderId = item.uuid; - - await dispatch(storageThunks.uploadFolderThunk({ root, currentFolderId })); - } + const folderDataToUpload = rootList.map((root) => ({ + root, + currentFolderId: item.uuid, + })); + await uploadFoldersWithManager({ + payload: folderDataToUpload, + selectedWorkspace, + dispatch, + }); } }); } diff --git a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx index c9ae19281..273f3ac01 100644 --- a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx +++ b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx @@ -165,12 +165,16 @@ const NameCollisionContainer: FC = ({ const keepAndUploadItem = async (itemsToUpload: (IRoot | File)[]) => { itemsToUpload.forEach((itemToUpload) => { if ((itemToUpload as IRoot).fullPathEdited) { - dispatch( - storageThunks.uploadFolderThunk({ - root: { ...(itemToUpload as IRoot) }, - currentFolderId: folderId, - }), - ).then(() => { + uploadFoldersWithManager({ + payload: [ + { + root: { ...(itemToUpload as IRoot) }, + currentFolderId: folderId, + }, + ], + selectedWorkspace, + dispatch, + }).then(() => { dispatch(fetchSortedFolderContentThunk(folderId)); }); } else { From df0877b5161b273ec974bb1e33965764a24d1e73 Mon Sep 17 00:00:00 2001 From: larryrider Date: Mon, 10 Feb 2025 17:24:26 +0100 Subject: [PATCH 28/28] Refactor folder upload handling to include renaming and duplication checks --- src/app/network/UploadFolderManager.ts | 48 ++++++++-- src/app/network/UploadFoldersManager.test.ts | 99 ++++++++++++++++++++ 2 files changed, 138 insertions(+), 9 deletions(-) diff --git a/src/app/network/UploadFolderManager.ts b/src/app/network/UploadFolderManager.ts index 374f9771b..1c1043bf5 100644 --- a/src/app/network/UploadFolderManager.ts +++ b/src/app/network/UploadFolderManager.ts @@ -13,8 +13,10 @@ import { planThunks } from '../store/slices/plan'; import { uploadItemsParallelThunk } from '../store/slices/storage/storage.thunks/uploadItemsThunk'; import { createFolder } from '../store/slices/storage/folderUtils/createFolder'; import { deleteItemsThunk } from '../store/slices/storage/storage.thunks/deleteItemsThunk'; +import { checkFolderDuplicated } from '../store/slices/storage/folderUtils/checkFolderDuplicated'; +import { getUniqueFolderName } from '../store/slices/storage/folderUtils/getUniqueFolderName'; -interface UploadFolderThunkPayload { +interface UploadFolderPayload { root: IRoot; currentFolderId: string; options?: { @@ -43,9 +45,13 @@ interface TaskInfo { }; } -const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): TaskFolder[] => { - return foldersPayload.map(({ root, currentFolderId, options: payloadOptions }) => { +const generateTaskIdForFolders = async (foldersPayload: UploadFolderPayload[]): Promise => { + const taskFolders: TaskFolder[] = []; + + for (const folder of foldersPayload) { + const { root: originalRoot, currentFolderId, options: payloadOptions } = folder; const options = { withNotification: true, ...payloadOptions }; + const root = await handleFoldersRename(originalRoot, currentFolderId); const uploadFolderAbortController = new AbortController(); @@ -55,6 +61,7 @@ const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): T tasksService.updateTask({ taskId, merge: { + folderName: root.name, status: TaskStatus.InProcess, progress: 0, }, @@ -70,8 +77,15 @@ const generateTaskIdForFolders = (foldersPayload: UploadFolderThunkPayload[]): T }); } - return { root, currentFolderId, options: payloadOptions, taskId, abortController: uploadFolderAbortController }; - }); + taskFolders.push({ + root, + currentFolderId, + options: payloadOptions, + taskId, + abortController: uploadFolderAbortController, + }); + } + return taskFolders; }; const countItemsUnderRoot = (root: IRoot): number => { @@ -92,6 +106,22 @@ const countItemsUnderRoot = (root: IRoot): number => { return count; }; +const handleFoldersRename = async (root: IRoot, currentFolderId: string) => { + const { duplicatedFoldersResponse } = await checkFolderDuplicated([root], currentFolderId); + + let finalFilename = root.name; + if (duplicatedFoldersResponse.length > 0) { + finalFilename = await getUniqueFolderName( + root.name, + duplicatedFoldersResponse as DriveFolderData[], + currentFolderId, + ); + } + + const folder: IRoot = { ...root, name: finalFilename }; + return folder; +}; + const wait = (ms: number): Promise => { return new Promise((resolve) => setTimeout(resolve, ms)); }; @@ -101,7 +131,7 @@ export const uploadFoldersWithManager = ({ selectedWorkspace, dispatch, }: { - payload: UploadFolderThunkPayload[]; + payload: UploadFolderPayload[]; selectedWorkspace: WorkspaceData | null; dispatch: ThunkDispatch; }): Promise => { @@ -113,7 +143,7 @@ export class UploadFoldersManager { private static readonly MAX_CONCURRENT_UPLOADS = 6; private static readonly MAX_UPLOAD_ATTEMPTS = 2; - private readonly payload: UploadFolderThunkPayload[]; + private readonly payload: UploadFolderPayload[]; private readonly selectedWorkspace: WorkspaceData | null; private readonly dispatch: ThunkDispatch; private readonly abortController?: AbortController; @@ -121,7 +151,7 @@ export class UploadFoldersManager { private tasksInfo: Record = {}; constructor( - payload: UploadFolderThunkPayload[], + payload: UploadFolderPayload[], selectedWorkspace: WorkspaceData | null, dispatch: ThunkDispatch, ) { @@ -309,7 +339,7 @@ export class UploadFoldersManager { }; public readonly run = async (): Promise => { - const payloadWithTaskId = generateTaskIdForFolders(this.payload); + const payloadWithTaskId = await generateTaskIdForFolders(this.payload); const memberId = this.selectedWorkspace?.workspaceUser?.memberId; diff --git a/src/app/network/UploadFoldersManager.test.ts b/src/app/network/UploadFoldersManager.test.ts index ab06994fb..1ac7d6b69 100644 --- a/src/app/network/UploadFoldersManager.test.ts +++ b/src/app/network/UploadFoldersManager.test.ts @@ -2,6 +2,8 @@ import errorService from 'app/core/services/error.service'; import AppError from 'app/core/types'; import { DriveFolderData } from 'app/drive/types'; import { createFolder } from 'app/store/slices/storage/folderUtils/createFolder'; +import { checkFolderDuplicated } from 'app/store/slices/storage/folderUtils/checkFolderDuplicated'; +import { getUniqueFolderName } from 'app/store/slices/storage/folderUtils/getUniqueFolderName'; import tasksService from 'app/tasks/services/tasks.service'; import { beforeEach, describe, expect, it, Mock, vi } from 'vitest'; import { TaskFolder, UploadFoldersManager, uploadFoldersWithManager } from './UploadFolderManager'; @@ -60,6 +62,14 @@ vi.mock('app/store/slices/storage/folderUtils/createFolder', () => ({ createFolder: vi.fn(), })); +vi.mock('app/store/slices/storage/folderUtils/checkFolderDuplicated', () => ({ + checkFolderDuplicated: vi.fn(), +})); + +vi.mock('app/store/slices/storage/folderUtils/getUniqueFolderName', () => ({ + getUniqueFolderName: vi.fn(), +})); + describe('checkUploadFolders', () => { const mockDispatch = vi.fn(); @@ -93,8 +103,78 @@ describe('checkUploadFolders', () => { const createFolderSpy = (createFolder as Mock).mockResolvedValueOnce(mockFolder); + (checkFolderDuplicated as Mock).mockResolvedValueOnce({ + duplicatedFoldersResponse: [] as DriveFolderData[], + foldersWithDuplicates: [] as DriveFolderData[], + foldersWithoutDuplicates: [mockFolder], + }); + vi.spyOn(tasksService, 'create').mockReturnValue(taskId); + vi.spyOn(tasksService, 'updateTask').mockReturnValue(); + vi.spyOn(tasksService, 'addListener').mockReturnValue(); + vi.spyOn(tasksService, 'removeListener').mockReturnValue(); + vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); + + await uploadFoldersWithManager({ + payload: [ + { + currentFolderId: 'currentFolderId', + root: { + folderId: mockFolder.uuid, + childrenFiles: [], + childrenFolders: [], + name: mockFolder.name, + fullPathEdited: 'path1', + }, + options: { + taskId, + }, + }, + ], + selectedWorkspace: null, + dispatch: mockDispatch, + }); + + expect(createFolderSpy).toHaveBeenCalledOnce(); + }); + + it('should rename folder before upload using an async queue', async () => { + const mockFolder: DriveFolderData = { + id: 0, + uuid: 'uuid', + name: 'Folder1', + bucket: 'bucket', + parentId: 0, + parent_id: 0, + parentUuid: 'parentUuid', + userId: 0, + user_id: 0, + icon: null, + iconId: null, + icon_id: null, + isFolder: true, + color: null, + encrypt_version: null, + plain_name: 'Folder1', + deleted: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + }; + const taskId = 'task-id'; + + const createFolderSpy = (createFolder as Mock).mockResolvedValueOnce(mockFolder); + + (checkFolderDuplicated as Mock).mockResolvedValueOnce({ + duplicatedFoldersResponse: [mockFolder] as DriveFolderData[], + foldersWithDuplicates: [mockFolder] as DriveFolderData[], + foldersWithoutDuplicates: [], + }); + + const renameFolderSpy = (getUniqueFolderName as Mock).mockResolvedValueOnce('renamed-folder1'); + vi.spyOn(tasksService, 'create').mockReturnValue(taskId); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); + vi.spyOn(tasksService, 'addListener').mockReturnValue(); + vi.spyOn(tasksService, 'removeListener').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); await uploadFoldersWithManager({ @@ -118,6 +198,7 @@ describe('checkUploadFolders', () => { }); expect(createFolderSpy).toHaveBeenCalledOnce(); + expect(renameFolderSpy).toHaveBeenCalledOnce(); }); it('should upload multiple folders using an async queue', async () => { @@ -169,6 +250,14 @@ describe('checkUploadFolders', () => { .mockResolvedValueOnce(mockParentFolder) .mockResolvedValueOnce(mockChildFolder); + (checkFolderDuplicated as Mock).mockResolvedValueOnce({ + duplicatedFoldersResponse: [] as DriveFolderData[], + foldersWithDuplicates: [] as DriveFolderData[], + foldersWithoutDuplicates: [mockParentFolder], + }); + + const renameFolderSpy = (getUniqueFolderName as Mock).mockResolvedValueOnce(''); + vi.spyOn(tasksService, 'create').mockReturnValue(taskId); vi.spyOn(tasksService, 'updateTask').mockReturnValue(); vi.spyOn(errorService, 'castError').mockResolvedValue(new AppError('error')); @@ -202,6 +291,7 @@ describe('checkUploadFolders', () => { }); expect(createFolderSpy).toHaveBeenCalledTimes(2); + expect(renameFolderSpy).not.toHaveBeenCalled(); }); it('should abort the upload if abortController is called', async () => { @@ -279,6 +369,14 @@ describe('checkUploadFolders', () => { .mockResolvedValueOnce(mockParentFolder) .mockResolvedValueOnce(mockChildFolder); + (checkFolderDuplicated as Mock).mockResolvedValueOnce({ + duplicatedFoldersResponse: [] as DriveFolderData[], + foldersWithDuplicates: [] as DriveFolderData[], + foldersWithoutDuplicates: [mockParentFolder], + }); + + const renameFolderSpy = (getUniqueFolderName as Mock).mockResolvedValueOnce(''); + manager['tasksInfo'][taskId] = { progress: { itemsUploaded: 0, @@ -294,5 +392,6 @@ describe('checkUploadFolders', () => { await expect(uploadPromise).resolves.toBeUndefined(); expect(abortController.signal.aborted).toBe(true); expect(createFolderSpy).not.toHaveBeenCalled(); + expect(renameFolderSpy).not.toHaveBeenCalled(); }); });