From 4c7c2bf071d3f617ccf73649a18c11bb85ac0c11 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Thu, 19 Sep 2024 19:48:14 +0200 Subject: [PATCH 01/24] feat: pagination for backups view --- .../BackupsAsFoldersList.tsx | 244 +++++++++++------- .../hooks/useDriveItemDragAndDrop.tsx | 14 +- .../DriveExplorerList/DriveExplorerList.tsx | 2 +- src/app/drive/services/new-storage.service.ts | 33 ++- .../BreadcrumbsItem/BreadcrumbsItem.tsx | 12 +- .../storage.thunks/fetchDialogContentThunk.ts | 9 +- .../storage.thunks/fetchFolderContentThunk.ts | 36 --- .../storage.thunks/renameItemsThunk.ts | 13 +- .../storage.thunks/uploadFolderThunk.ts | 9 +- .../storage.thunks/uploadItemsThunk.ts | 9 +- 10 files changed, 212 insertions(+), 169 deletions(-) diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index 294eb0ebf..6c893a2ae 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -3,7 +3,6 @@ import folderEmptyImage from 'assets/icons/light/folder-open.svg'; import _ from 'lodash'; import { useEffect, useState } from 'react'; import { useDispatch } from 'react-redux'; -import { SdkFactory } from '../../../core/factory/sdk'; import dateService from '../../../core/services/date.service'; import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; import DriveListItemSkeleton from '../../../drive/components/DriveListItemSkeleton/DriveListItemSkeleton'; @@ -19,6 +18,10 @@ import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/d import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; import { uiActions } from '../../../store/slices/ui'; import { backupsActions } from 'app/store/slices/backups'; +import newStorageService from 'app/drive/services/new-storage.service'; +import { skinSkeleton } from 'app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList'; + +const DEFAULT_LIMIT = 50; export default function BackupsAsFoldersList({ className = '', @@ -31,31 +34,80 @@ export default function BackupsAsFoldersList({ }): JSX.Element { const dispatch = useDispatch(); const { translate } = useTranslationContext(); - - const [isLoading, setIsloading] = useState(true); + const [isLoading, setIsLoading] = useState(true); const [currentItems, setCurrentItems] = useState([]); const [selectedItems, setSelectedItems] = useState([]); + const [hasMoreItems, setHasMoreItems] = useState(true); + const [offset, setOffset] = useState(0); const Skeleton = Array(10) .fill(0) .map((n, i) => ); + useEffect(() => { + refreshFolderContent(); + }, [folderId]); + async function refreshFolderContent() { - setIsloading(true); + setIsLoading(true); + setOffset(0); setSelectedItems([]); - const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); - const [responsePromise] = storageClient.getFolderContentByUuid(folderId); - const response = await responsePromise; + setCurrentItems([]); + + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid: folderId, + limit: DEFAULT_LIMIT, + offset: 0, + }); + + const response = await folderContentPromise; const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + setCurrentItems(items); - setIsloading(false); + + if (items.length >= DEFAULT_LIMIT) { + setHasMoreItems(true); + setOffset(items.length); + } else { + setHasMoreItems(false); + } + + setIsLoading(false); } - useEffect(() => { - refreshFolderContent(); - }, [folderId]); + const getPaginatedBackupList = async () => { + if (!hasMoreItems) return; + + setIsLoading(true); + + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid: folderId, + limit: DEFAULT_LIMIT, + offset, + }); + + const folderContentResponse = await folderContentPromise; + const files = folderContentResponse.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); + const folders = folderContentResponse.children.map((folder) => ({ + ...folder, + isFolder: true, + name: folder.plainName, + })); + const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + + setCurrentItems((prevItems) => [...prevItems, ...items]); + + if (items.length >= DEFAULT_LIMIT) { + setHasMoreItems(true); + setOffset((prevOffset) => prevOffset + items.length); + } else { + setHasMoreItems(false); + } + + setIsLoading(false); + }; const onDownloadSelectedItems = () => { dispatch(downloadItemsThunk(selectedItems)); @@ -76,7 +128,7 @@ export default function BackupsAsFoldersList({ const onClick = (item: DriveItemData) => { if (item.isFolder) { if (!isLoading) { - setIsloading(true); + setIsLoading(true); onFolderPush(item as DriveFolderData); dispatch(backupsActions.setCurrentFolder(item)); } @@ -100,92 +152,90 @@ export default function BackupsAsFoldersList({ return (
- {isLoading ? ( - Skeleton - ) : ( - - header={[ - { - label: translate('drive.list.columns.name'), - width: 'flex-1 min-w-activity truncate cursor-pointer', - name: 'name', - orderable: true, - defaultDirection: 'ASC', - }, - { - label: translate('drive.list.columns.modified'), - width: 'w-date', - name: 'updatedAt', - orderable: true, - defaultDirection: 'ASC', - }, - { - label: translate('drive.list.columns.size'), - width: 'cursor-pointer items-center w-size', - name: 'size', - orderable: true, - defaultDirection: 'ASC', - }, - ]} - items={currentItems} - isLoading={isLoading} - itemComposition={[ - (item) => { - const displayName = item.type === 'folder' ? item.name : `${item.plainName}.${item.type}`; - const Icon = iconService.getItemIcon(item.isFolder, item.type); - - return ( -
-
- -
-
- onClick(item)}> - {displayName} - -
+ + header={[ + { + label: translate('drive.list.columns.name'), + width: 'flex-1 min-w-activity truncate cursor-pointer', + name: 'name', + orderable: true, + defaultDirection: 'ASC', + }, + { + label: translate('drive.list.columns.modified'), + width: 'w-date', + name: 'updatedAt', + orderable: true, + defaultDirection: 'ASC', + }, + { + label: translate('drive.list.columns.size'), + width: 'cursor-pointer items-center w-size', + name: 'size', + orderable: true, + defaultDirection: 'ASC', + }, + ]} + items={currentItems} + isLoading={isLoading} + itemComposition={[ + (item) => { + const displayName = item.type === 'folder' ? item.name : `${item.plainName}.${item.type}`; + const Icon = iconService.getItemIcon(item.isFolder, item.type); + + return ( +
+
+ +
+
+ onClick(item)}> + {displayName} +
- ); - }, - (item) => { - return
{dateService.format(item.createdAt, 'DD MMMM YYYY. HH:mm')}
; - }, - (item) => { - const size = 'size' in item ? sizeService.bytesToString(item.size) : ''; - return
{size}
; - }, - ]} - onClick={(item) => { - const unselectedDevices = selectedItems.map((deviceSelected) => ({ - device: deviceSelected, - isSelected: false, - })); - onItemSelected([...unselectedDevices, { device: item, isSelected: true }]); - }} - onDoubleClick={onClick} - skinSkeleton={Skeleton} - emptyState={ - } - title="This folder is empty" - subtitle="Use Internxt Desktop to upload your data" - /> - } - menu={contextMenuSelectedBackupItems({ - onDownloadSelectedItems, - onDeleteSelectedItems, - })} - selectedItems={selectedItems} - keyboardShortcuts={['unselectAll', 'selectAll', 'multiselect']} - onSelectedItemsChanged={(changes) => { - const selectedDevicesParsed = changes.map((change) => ({ - device: change.props, - isSelected: change.value, - })); - onItemSelected(selectedDevicesParsed); - }} - /> - )} +
+ ); + }, + (item) => { + return
{dateService.format(item.createdAt, 'DD MMMM YYYY. HH:mm')}
; + }, + (item) => { + const size = 'size' in item ? sizeService.bytesToString(item.size) : ''; + return
{size}
; + }, + ]} + onClick={(item) => { + const unselectedDevices = selectedItems.map((deviceSelected) => ({ + device: deviceSelected, + isSelected: false, + })); + onItemSelected([...unselectedDevices, { device: item, isSelected: true }]); + }} + onNextPage={getPaginatedBackupList} + hasMoreItems={hasMoreItems} + onDoubleClick={onClick} + skinSkeleton={skinSkeleton} + emptyState={ + } + title="This folder is empty" + subtitle="Use Internxt Desktop to upload your data" + /> + } + menu={contextMenuSelectedBackupItems({ + onDownloadSelectedItems, + onDeleteSelectedItems, + })} + selectedItems={selectedItems} + keyboardShortcuts={['unselectAll', 'selectAll', 'multiselect']} + onSelectedItemsChanged={(changes) => { + const selectedDevicesParsed = changes.map((change) => ({ + device: change.props, + isSelected: change.value, + })); + onItemSelected(selectedDevicesParsed); + }} + />
); diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx index 0fc0d0cb1..6a0b3bb6f 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerItem/hooks/useDriveItemDragAndDrop.tsx @@ -1,6 +1,5 @@ import { ConnectDragSource, ConnectDropTarget, useDrag, useDrop } from 'react-dnd'; import { NativeTypes } from 'react-dnd-html5-backend'; -import { SdkFactory } from '../../../../../core/factory/sdk'; import { transformDraggedItems } from '../../../../../core/services/drag-and-drop.service'; import { DragAndDropType } from '../../../../../core/types'; import { useAppDispatch, useAppSelector } from '../../../../../store/hooks'; @@ -12,6 +11,7 @@ import { handleRepeatedUploadingFolders, } from '../../../../../store/slices/storage/storage.thunks/renameItemsThunk'; import { DriveItemData } from '../../../../types'; +import newStorageService from 'app/drive/services/new-storage.service'; interface DragSourceCollectorProps { isDraggingThisItem: boolean; @@ -88,15 +88,13 @@ export const useDriveItemDrop = (item: DriveItemData): DriveItemDrop => { return i.isFolder; }); - const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); - dispatch(storageActions.setMoveDestinationFolderId(item.uuid)); - const [folderContentPromise] = storageClient.getFolderContentByUuid( - item.uuid, - false, - workspacesCredentials?.tokenHeader, - ); + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid: item.uuid, + workspacesToken: workspacesCredentials?.tokenHeader, + }); + const { children: foldersInDestinationFolder, files: filesInDestinationFolder } = await folderContentPromise; const foldersInDestinationFolderParsed = foldersInDestinationFolder.map((folder) => ({ ...folder, diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx index 633556b06..fde6166db 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx @@ -569,7 +569,7 @@ export default connect((state: RootState) => ({ state.ui.isPreferencesDialogOpen, }))(DriveExplorerList); -const skinSkeleton = [ +export const skinSkeleton = [
diff --git a/src/app/drive/services/new-storage.service.ts b/src/app/drive/services/new-storage.service.ts index 8b335d367..c0b09c300 100644 --- a/src/app/drive/services/new-storage.service.ts +++ b/src/app/drive/services/new-storage.service.ts @@ -1,5 +1,12 @@ -import { DriveFileData, FolderAncestor, FolderMeta, FolderTreeResponse } from '@internxt/sdk/dist/drive/storage/types'; +import { + DriveFileData, + FetchFolderContentResponse, + FolderAncestor, + FolderMeta, + FolderTreeResponse, +} from '@internxt/sdk/dist/drive/storage/types'; import { SdkFactory } from '../../core/factory/sdk'; +import { RequestCanceler } from '@internxt/sdk/dist/shared/http/types'; export async function searchItemsByName(name: string): Promise { const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); @@ -23,11 +30,35 @@ export async function getFolderTree(uuid: string): Promise { return storageClient.getFolderTree(uuid); } +export function getFolderContentByUuid({ + folderUuid, + limit, + offset, + trash, + workspacesToken, +}: { + folderUuid: string; + limit?: number; + offset?: number; + trash?: boolean; + workspacesToken?: string; +}): [Promise, RequestCanceler] { + const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); + return storageClient.getFolderContentByUuid({ + folderUuid, + limit, + offset, + trash, + workspacesToken, + }); +} + const newStorageService = { searchItemsByName, getFolderAncestors, getFolderMeta, getFolderTree, + getFolderContentByUuid, }; export default newStorageService; diff --git a/src/app/shared/components/Breadcrumbs/BreadcrumbsItem/BreadcrumbsItem.tsx b/src/app/shared/components/Breadcrumbs/BreadcrumbsItem/BreadcrumbsItem.tsx index 279a20767..ca66b25b1 100644 --- a/src/app/shared/components/Breadcrumbs/BreadcrumbsItem/BreadcrumbsItem.tsx +++ b/src/app/shared/components/Breadcrumbs/BreadcrumbsItem/BreadcrumbsItem.tsx @@ -7,7 +7,6 @@ import storageSelectors from 'app/store/slices/storage/storage.selectors'; import storageThunks from 'app/store/slices/storage/storage.thunks'; import { DropTargetMonitor, useDrop } from 'react-dnd'; import { NativeTypes } from 'react-dnd-html5-backend'; -import { SdkFactory } from '../../../../core/factory/sdk'; import { storageActions } from '../../../../store/slices/storage'; import { handleRepeatedUploadingFiles, @@ -15,6 +14,7 @@ import { } from '../../../../store/slices/storage/storage.thunks/renameItemsThunk'; import { uiActions } from '../../../../store/slices/ui'; import { BreadcrumbItemData, BreadcrumbsMenuProps } from '../types'; +import newStorageService from 'app/drive/services/new-storage.service'; interface BreadcrumbsItemProps { item: BreadcrumbItemData; totalBreadcrumbsLength: number; @@ -53,13 +53,11 @@ const BreadcrumbsItem = (props: BreadcrumbsItemProps): JSX.Element => { }); dispatch(storageActions.setMoveDestinationFolderId(props.item.uuid)); - const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); - const [folderContentPromise] = storageClient.getFolderContentByUuid( - props.item.uuid, - false, - workspacesCredentials?.tokenHeader, - ); + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid: props.item.uuid, + workspacesToken: workspacesCredentials?.tokenHeader, + }); const { children: foldersInDestinationFolder, files: filesInDestinationFolder } = await folderContentPromise; diff --git a/src/app/store/slices/storage/storage.thunks/fetchDialogContentThunk.ts b/src/app/store/slices/storage/storage.thunks/fetchDialogContentThunk.ts index 589caa208..184d27e5c 100644 --- a/src/app/store/slices/storage/storage.thunks/fetchDialogContentThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/fetchDialogContentThunk.ts @@ -4,12 +4,12 @@ import _ from 'lodash'; import { t } from 'i18next'; import { storageActions } from '..'; import { RootState } from '../../..'; -import { SdkFactory } from '../../../../core/factory/sdk'; import databaseService, { DatabaseCollection } from '../../../../database/services/database.service'; import { DriveFolderData, DriveItemData } from '../../../../drive/types'; import notificationsService, { ToastType } from '../../../../notifications/services/notifications.service'; import workspacesSelectors from '../../workspaces/workspaces.selectors'; import { StorageState } from '../storage.model'; +import newStorageService from 'app/drive/services/new-storage.service'; export const fetchDialogContentThunk = createAsyncThunk( 'storage/fetchDialogContentThunk', @@ -17,9 +17,10 @@ export const fetchDialogContentThunk = createAsyncThunk( DatabaseCollection.MoveDialogLevels, folderId, diff --git a/src/app/store/slices/storage/storage.thunks/fetchFolderContentThunk.ts b/src/app/store/slices/storage/storage.thunks/fetchFolderContentThunk.ts index 4956a2ebe..9c67f944d 100644 --- a/src/app/store/slices/storage/storage.thunks/fetchFolderContentThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/fetchFolderContentThunk.ts @@ -117,42 +117,6 @@ export const fetchPaginatedFolderContentThunk = createAsyncThunk( -// 'storage/fetchFolderContent', -// async (folderId, { dispatch }) => { -// const storageClient = SdkFactory.getInstance().createStorageClient(); -// const [responsePromise] = storageClient.getFolderContent(folderId); -// const databaseContent = await databaseService.get(DatabaseCollection.Levels, folderId); - -// dispatch(storageActions.resetOrder()); - -// if (databaseContent) { -// dispatch( -// storageActions.setItems({ -// folderId, -// items: databaseContent, -// }), -// ); -// } else { -// await responsePromise; -// } - -// responsePromise.then((response) => { -// const folders = response.children.map((folder) => ({ ...folder, isFolder: true })); -// const items = _.concat(folders as DriveItemData[], response.files as DriveItemData[]); -// const parsedItems = items.map((item) => ({ ...item, plainName: item?.plain_name })); - -// dispatch( -// storageActions.setItems({ -// folderId, -// items: parsedItems, -// }), -// ); -// databaseService.put(DatabaseCollection.Levels, folderId, parsedItems); -// }); -// }, -// ); - export const fetchFolderContentThunkExtraReducers = (builder: ActionReducerMapBuilder): void => { builder .addCase(fetchPaginatedFolderContentThunk.pending, (state, action) => { diff --git a/src/app/store/slices/storage/storage.thunks/renameItemsThunk.ts b/src/app/store/slices/storage/storage.thunks/renameItemsThunk.ts index 10ca14ca3..8e801112d 100644 --- a/src/app/store/slices/storage/storage.thunks/renameItemsThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/renameItemsThunk.ts @@ -9,13 +9,13 @@ import { t } from 'i18next'; import storageThunks from '.'; import { storageActions } from '..'; import { RootState } from '../../..'; -import { SdkFactory } from '../../../../core/factory/sdk'; import errorService from '../../../../core/services/error.service'; import { uiActions } from '../../ui'; import workspacesSelectors from '../../workspaces/workspaces.selectors'; import { StorageState } from '../storage.model'; import storageSelectors from '../storage.selectors'; import renameFolderIfNeeded, { IRoot } from './uploadFolderThunk'; +import newStorageService from 'app/drive/services/new-storage.service'; const checkRepeatedNameFiles = (destinationFolderFiles: DriveItemData[], files: (DriveItemData | File)[]) => { const repeatedFilesInDrive: DriveItemData[] = []; @@ -118,13 +118,10 @@ export const renameItemsThunk = createAsyncThunk { - const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); - const [parentFolderContentPromise] = storageClient.getFolderContentByUuid(currentFolderId, false, tokenHeader); +const handleFoldersRename = async (root: IRoot, currentFolderId: string, workspaceTokenHeader?: string) => { + const [parentFolderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid: currentFolderId, + workspacesToken: workspaceTokenHeader, + }); const parentFolderContent = await parentFolderContentPromise; const [, , finalFilename] = renameFolderIfNeeded(parentFolderContent.children, root.name); const fileContent: IRoot = { ...root, name: finalFilename }; diff --git a/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts b/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts index b74729bf4..095bbae63 100644 --- a/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts +++ b/src/app/store/slices/storage/storage.thunks/uploadItemsThunk.ts @@ -14,7 +14,6 @@ import { t } from 'i18next'; import { storageActions } from '..'; import { RootState } from '../../..'; -import { SdkFactory } from '../../../../core/factory/sdk'; import errorService from '../../../../core/services/error.service'; import workspacesService from '../../../../core/services/workspace.service'; import { uploadFileWithManager } from '../../../../network/UploadManager'; @@ -24,6 +23,7 @@ import { planThunks } from '../../plan'; import { uiActions } from '../../ui'; import workspacesSelectors from '../../workspaces/workspaces.selectors'; import { StorageState } from '../storage.model'; +import newStorageService from 'app/drive/services/new-storage.service'; interface UploadItemsThunkOptions { relatedTaskId: string; @@ -112,12 +112,13 @@ const prepareFilesToUpload = async ({ workspaceToken?: string; }): Promise<{ filesToUpload: FileToUpload[]; zeroLengthFilesNumber: number }> => { const filesToUpload: FileToUpload[] = []; - const storageClient = SdkFactory.getNewApiInstance().createNewStorageClient(); - let parentFolderContent; if (!disableDuplicatedNamesCheck) { - const [parentFolderContentPromise] = storageClient.getFolderContentByUuid(parentFolderId, false, workspaceToken); + const [parentFolderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid: parentFolderId, + workspacesToken: workspaceToken, + }); parentFolderContent = await parentFolderContentPromise; } From bb3fddf2b16810098094c3c65c3893c6f70785db Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 07:43:30 +0200 Subject: [PATCH 02/24] refactor(backups): move all logic to the parent --- .../BackupsAsFoldersList.tsx | 162 ++-------------- .../backups/views/BackupsView/BackupsView.tsx | 180 ++++++++++++++++-- .../Containers/BreadcrumbsBackupsView.tsx | 4 +- 3 files changed, 184 insertions(+), 162 deletions(-) diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index 6c893a2ae..b16b29039 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -1,153 +1,36 @@ -import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; import folderEmptyImage from 'assets/icons/light/folder-open.svg'; -import _ from 'lodash'; -import { useEffect, useState } from 'react'; -import { useDispatch } from 'react-redux'; import dateService from '../../../core/services/date.service'; -import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; -import DriveListItemSkeleton from '../../../drive/components/DriveListItemSkeleton/DriveListItemSkeleton'; -import { deleteFile } from '../../../drive/services/file.service'; -import { deleteBackupDeviceAsFolder } from '../../../drive/services/folder.service'; import iconService from '../../../drive/services/icon.service'; import sizeService from '../../../drive/services/size.service'; -import { DriveItemData, DriveFolderData as DriveWebFolderData } from '../../../drive/types'; +import { DriveItemData } from '../../../drive/types'; import { useTranslationContext } from '../../../i18n/provider/TranslationProvider'; import Empty from '../../../shared/components/Empty/Empty'; import List from '../../../shared/components/List'; -import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/deleteItemsThunk'; -import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; -import { uiActions } from '../../../store/slices/ui'; -import { backupsActions } from 'app/store/slices/backups'; -import newStorageService from 'app/drive/services/new-storage.service'; import { skinSkeleton } from 'app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList'; - -const DEFAULT_LIMIT = 50; +import { ListItemMenu } from 'app/shared/components/List/ListItem'; export default function BackupsAsFoldersList({ className = '', - folderId, - onFolderPush, + contextMenu, + currentItems, + isLoading, + selectedItems, + hasMoreItems, + getPaginatedBackupList, + onItemClicked, + onItemSelected, }: { className?: string; - folderId: string; - onFolderPush: (folder: DriveFolderData) => void; + contextMenu: ListItemMenu; + currentItems: DriveItemData[]; + selectedItems: DriveItemData[]; + isLoading: boolean; + hasMoreItems: boolean; + getPaginatedBackupList: () => void; + onItemClicked: (item: DriveItemData) => void; + onItemSelected: (changes: { device: DriveItemData; isSelected: boolean }[]) => void; }): JSX.Element { - const dispatch = useDispatch(); const { translate } = useTranslationContext(); - const [isLoading, setIsLoading] = useState(true); - const [currentItems, setCurrentItems] = useState([]); - const [selectedItems, setSelectedItems] = useState([]); - const [hasMoreItems, setHasMoreItems] = useState(true); - const [offset, setOffset] = useState(0); - - const Skeleton = Array(10) - .fill(0) - .map((n, i) => ); - - useEffect(() => { - refreshFolderContent(); - }, [folderId]); - - async function refreshFolderContent() { - setIsLoading(true); - setOffset(0); - setSelectedItems([]); - setCurrentItems([]); - - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ - folderUuid: folderId, - limit: DEFAULT_LIMIT, - offset: 0, - }); - - const response = await folderContentPromise; - const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); - const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); - const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - - setCurrentItems(items); - - if (items.length >= DEFAULT_LIMIT) { - setHasMoreItems(true); - setOffset(items.length); - } else { - setHasMoreItems(false); - } - - setIsLoading(false); - } - - const getPaginatedBackupList = async () => { - if (!hasMoreItems) return; - - setIsLoading(true); - - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ - folderUuid: folderId, - limit: DEFAULT_LIMIT, - offset, - }); - - const folderContentResponse = await folderContentPromise; - const files = folderContentResponse.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); - const folders = folderContentResponse.children.map((folder) => ({ - ...folder, - isFolder: true, - name: folder.plainName, - })); - const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - - setCurrentItems((prevItems) => [...prevItems, ...items]); - - if (items.length >= DEFAULT_LIMIT) { - setHasMoreItems(true); - setOffset((prevOffset) => prevOffset + items.length); - } else { - setHasMoreItems(false); - } - - setIsLoading(false); - }; - - const onDownloadSelectedItems = () => { - dispatch(downloadItemsThunk(selectedItems)); - }; - - async function onDeleteSelectedItems() { - for (const item of selectedItems) { - if (item.isFolder) { - await deleteBackupDeviceAsFolder(item as DriveWebFolderData); - } else { - await deleteFile(item); - } - setCurrentItems((items) => items.filter((i) => !(i.id === item.id && i.isFolder === item.isFolder))); - } - dispatch(deleteItemsThunk(selectedItems)); - } - - const onClick = (item: DriveItemData) => { - if (item.isFolder) { - if (!isLoading) { - setIsLoading(true); - onFolderPush(item as DriveFolderData); - dispatch(backupsActions.setCurrentFolder(item)); - } - } else { - dispatch(uiActions.setIsFileViewerOpen(true)); - dispatch(uiActions.setFileViewerItem(item)); - } - }; - - const onItemSelected = (changes: { device: DriveItemData; isSelected: boolean }[]) => { - let updatedSelectedItems = selectedItems; - for (const change of changes) { - updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); - if (change.isSelected) { - updatedSelectedItems = [...updatedSelectedItems, change.device]; - } - } - setSelectedItems(updatedSelectedItems); - }; return (
@@ -189,7 +72,7 @@ export default function BackupsAsFoldersList({
- onClick(item)}> + onItemClicked(item)}> {displayName}
@@ -213,7 +96,7 @@ export default function BackupsAsFoldersList({ }} onNextPage={getPaginatedBackupList} hasMoreItems={hasMoreItems} - onDoubleClick={onClick} + onDoubleClick={onItemClicked} skinSkeleton={skinSkeleton} emptyState={ } - menu={contextMenuSelectedBackupItems({ - onDownloadSelectedItems, - onDeleteSelectedItems, - })} + menu={contextMenu} selectedItems={selectedItems} keyboardShortcuts={['unselectAll', 'selectAll', 'multiselect']} onSelectedItemsChanged={(changes) => { diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index fb79e8d69..e82c008ed 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -1,8 +1,8 @@ import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; -import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; -import BreadcrumbsBackupsView from 'app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView'; -import { useAppDispatch, useAppSelector } from 'app/store/hooks'; -import { backupsActions, backupsThunks } from 'app/store/slices/backups'; +import { useTranslationContext } from '../../../i18n/provider/TranslationProvider'; +import BreadcrumbsBackupsView from '../../../shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView'; +import { useAppDispatch, useAppSelector } from '../../../store/hooks'; +import { backupsActions, backupsThunks } from '../../../store/slices/backups'; import { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import DeleteBackupDialog from '../../../drive/components/DeleteBackupDialog/DeleteBackupDialog'; @@ -14,6 +14,15 @@ import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/d import BackupsAsFoldersList from '../../components/BackupsAsFoldersList/BackupsAsFoldersList'; import DeviceList from '../../components/DeviceList/DeviceList'; import { Device } from '../../types'; +import newStorageService from 'app/drive/services/new-storage.service'; +import _ from 'lodash'; +import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; +import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; +import { deleteFile } from '../../../drive/services/file.service'; +import { ListItemMenu } from '../../../shared/components/List/ListItem'; +import { uiActions } from '../../../store/slices/ui'; + +const DEFAULT_LIMIT = 50; export default function BackupsView(): JSX.Element { const { translate } = useTranslationContext(); @@ -25,6 +34,41 @@ export default function BackupsView(): JSX.Element { const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); const [selectedDevices, setSelectedDevices] = useState<(Device | DriveFolderData)[]>([]); const [backupsAsFoldersPath, setBackupsAsFoldersPath] = useState([]); + const [folderUuid, setFolderUuid] = useState(); + const [isLoading, setIsLoading] = useState(true); + const [currentItems, setCurrentItems] = useState([]); + const [selectedItems, setSelectedItems] = useState([]); + const [hasMoreItems, setHasMoreItems] = useState(true); + const [offset, setOffset] = useState(0); + + useEffect(() => { + dispatch(backupsActions.setCurrentDevice(null)); + setBackupsAsFoldersPath([]); + setFolderUuid(undefined); + dispatch(backupsThunks.fetchDevicesThunk()); + }, []); + + useEffect(() => { + if (currentDevice && !('mac' in currentDevice)) { + setBackupsAsFoldersPath([currentDevice]); + setFolderUuid(currentDevice.uuid); + } + }, [currentDevice]); + + useEffect(() => { + refreshFolderContent(); + }, [folderUuid]); + + function goToFolder(folderId: number, folderUuid?: string) { + setBackupsAsFoldersPath((current) => { + const index = current.findIndex((i) => i.id === folderId); + return current.slice(0, index + 1); + }); + + if (folderUuid) { + setFolderUuid(folderUuid); + } + } const onDeviceClicked = (target: Device | DriveFolderData) => { setSelectedDevices([]); @@ -68,23 +112,115 @@ export default function BackupsView(): JSX.Element { setSelectedDevices([]); }; - useEffect(() => { - dispatch(backupsActions.setCurrentDevice(null)); - setBackupsAsFoldersPath([]); - dispatch(backupsThunks.fetchDevicesThunk()); - }, []); + const onDownloadSelectedItems = () => { + dispatch(downloadItemsThunk(selectedItems)); + }; - useEffect(() => { - if (currentDevice && !('mac' in currentDevice)) setBackupsAsFoldersPath([currentDevice]); - }, [currentDevice]); + async function onDeleteSelectedItems() { + for (const item of selectedItems) { + if (item.isFolder) { + await deleteBackupDeviceAsFolder(item as DriveWebFolderData); + } else { + await deleteFile(item); + } + setCurrentItems((items) => items.filter((i) => !(i.id === item.id && i.isFolder === item.isFolder))); + } + dispatch(deleteItemsThunk(selectedItems)); + } - function goToFolder(folderId: number) { - setBackupsAsFoldersPath((current) => { - const index = current.findIndex((i) => i.id === folderId); - return current.slice(0, index + 1); + const contextMenu: ListItemMenu = contextMenuSelectedBackupItems({ + onDownloadSelectedItems, + onDeleteSelectedItems, + }); + + const onItemClicked = (item: DriveItemData) => { + if (item.isFolder) { + if (!isLoading) { + setIsLoading(true); + setBackupsAsFoldersPath((current) => [...current, item]); + dispatch(backupsActions.setCurrentFolder(item)); + setFolderUuid(item.uuid); + } + } else { + dispatch(uiActions.setIsFileViewerOpen(true)); + dispatch(uiActions.setFileViewerItem(item)); + } + }; + + const onItemSelected = (changes: { device: DriveItemData; isSelected: boolean }[]) => { + let updatedSelectedItems = selectedItems; + for (const change of changes) { + updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); + if (change.isSelected) { + updatedSelectedItems = [...updatedSelectedItems, change.device]; + } + } + setSelectedItems(updatedSelectedItems); + }; + + async function refreshFolderContent() { + if (!folderUuid) return; + + setIsLoading(true); + setOffset(0); + setSelectedItems([]); + setCurrentItems([]); + + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid, + limit: DEFAULT_LIMIT, + offset: 0, }); + + const response = await folderContentPromise; + const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); + const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); + const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + + setCurrentItems(items); + + if (items.length >= DEFAULT_LIMIT) { + setHasMoreItems(true); + setOffset(items.length); + } else { + setHasMoreItems(false); + } + + setIsLoading(false); } + const getPaginatedBackupList = async () => { + if (!folderUuid || !hasMoreItems) return; + + setIsLoading(true); + + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid, + limit: DEFAULT_LIMIT, + offset, + }); + + const folderContentResponse = await folderContentPromise; + const files = folderContentResponse.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); + const folders = folderContentResponse.children.map((folder) => ({ + ...folder, + isFolder: true, + name: folder.plainName, + })); + const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + + setCurrentItems((prevItems) => [...prevItems, ...items]); + + if (items.length >= DEFAULT_LIMIT) { + setHasMoreItems(true); + setOffset((prevOffset) => prevOffset + items.length); + } else { + setHasMoreItems(false); + } + + setIsLoading(false); + }; + let body; if (!currentDevice) { @@ -101,8 +237,14 @@ export default function BackupsView(): JSX.Element { } else if (backupsAsFoldersPath.length) { body = ( setBackupsAsFoldersPath((current) => [...current, folder])} - folderId={backupsAsFoldersPath[backupsAsFoldersPath.length - 1].uuid} + contextMenu={contextMenu} + currentItems={currentItems} + selectedItems={selectedItems} + hasMoreItems={hasMoreItems} + isLoading={isLoading} + getPaginatedBackupList={getPaginatedBackupList} + onItemClicked={onItemClicked} + onItemSelected={onItemSelected} /> ); } @@ -137,7 +279,7 @@ export default function BackupsView(): JSX.Element { goToFolder={goToFolder} /> ) : ( -

{translate('backups.your-devices')}

+

{translate('backups.your-devices')}

)}
diff --git a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx index 6fc941789..eae10e943 100644 --- a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx +++ b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx @@ -11,7 +11,7 @@ import { BreadcrumbItemData } from '../types'; interface BreadcrumbsBackupsViewProps { setSelectedDevices: Dispatch>; backupsAsFoldersPath: DriveFolderData[]; - goToFolder: (folderId: number) => void; + goToFolder: (folderId: number, folderUuid?: string) => void; } const BreadcrumbsBackupsView = ({ @@ -53,7 +53,7 @@ const BreadcrumbsBackupsView = ({ active: true, onClick: () => { dispatch(backupsActions.setCurrentFolder(item)); - goToFolder(item.id); + goToFolder(item.id, item.uuid); }, }; items.push({ From a72201b089fb846c2f0dc75b5cfbe68514d71823 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 08:58:55 +0200 Subject: [PATCH 03/24] fix: code issues and remove unused components --- .../components/BackupList/BackupList.tsx | 114 ------------------ .../components/BackupList/BackupListItem.tsx | 94 --------------- .../BackupsAsFoldersList.tsx | 4 +- .../DriveItemContextMenu.tsx | 6 +- 4 files changed, 5 insertions(+), 213 deletions(-) delete mode 100644 src/app/backups/components/BackupList/BackupList.tsx delete mode 100644 src/app/backups/components/BackupList/BackupListItem.tsx diff --git a/src/app/backups/components/BackupList/BackupList.tsx b/src/app/backups/components/BackupList/BackupList.tsx deleted file mode 100644 index e669cb6b9..000000000 --- a/src/app/backups/components/BackupList/BackupList.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useState } from 'react'; -import { DeviceBackup } from '../../types'; -import BackupListItem from './BackupListItem'; -import { useAppDispatch } from '../../../store/hooks'; -import { backupsThunks } from '../../../store/slices/backups'; -import DriveListItemSkeleton from '../../../drive/components/DriveListItemSkeleton/DriveListItemSkeleton'; -import List from '../../../shared/components/List'; -import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; -import { useTranslationContext } from '../../../i18n/provider/TranslationProvider'; - -interface Props { - items: DeviceBackup[]; - isLoading: boolean; -} - -const BackupList = (props: Props): JSX.Element => { - const dispatch = useAppDispatch(); - const { translate } = useTranslationContext(); - const { isLoading } = props; - - const [selectedBackups, setSelectedBackups] = useState([]); - - const onDownloadBackupClicked = () => { - selectedBackups.forEach((backup) => dispatch(backupsThunks.downloadBackupThunk(backup))); - }; - const onDeleteBackupClicked = () => { - selectedBackups.forEach((backup) => dispatch(backupsThunks.deleteBackupThunk(backup))); - }; - - const onBackupSelected = (changes: { device: DeviceBackup; isSelected: boolean }[]) => { - let updatedSelectedItems = selectedBackups; - for (const change of changes) { - updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); - if (change.isSelected) { - updatedSelectedItems = [...updatedSelectedItems, change.device]; - } - } - setSelectedBackups(updatedSelectedItems); - }; - - const getLoadingSkeleton = () => { - return Array(10) - .fill(0) - .map((n, i) => ); - }; - - return ( -
-
- {isLoading ? ( - getLoadingSkeleton() - ) : ( -
- - header={[ - { - label: translate('drive.list.columns.name'), - width: 'flex grow cursor-pointer items-center pl-6', - name: 'name', - orderable: true, - defaultDirection: 'ASC', - }, - { - label: translate('drive.list.columns.modified'), - width: 'hidden w-3/12 lg:flex pl-4', - name: 'updatedAt', - orderable: true, - defaultDirection: 'ASC', - }, - { - label: translate('drive.list.columns.size'), - width: 'flex w-1/12 cursor-pointer items-center', - name: 'size', - orderable: true, - defaultDirection: 'ASC', - }, - ]} - items={props.items} - isLoading={isLoading} - itemComposition={[ - (props) => ( - - ), - ]} - skinSkeleton={getLoadingSkeleton()} - menu={contextMenuSelectedBackupItems({ - onDeleteSelectedItems: onDeleteBackupClicked, - onDownloadSelectedItems: onDownloadBackupClicked, - })} - selectedItems={selectedBackups} - keyboardShortcuts={['unselectAll', 'selectAll', 'multiselect']} - onSelectedItemsChanged={(changes) => { - const selectedDevicesParsed = changes.map((change) => ({ - device: change.props, - isSelected: change.value, - })); - onBackupSelected(selectedDevicesParsed); - }} - disableItemCompositionStyles={true} - /> -
- )} -
-
- ); -}; - -export default BackupList; diff --git a/src/app/backups/components/BackupList/BackupListItem.tsx b/src/app/backups/components/BackupList/BackupListItem.tsx deleted file mode 100644 index a6c34ed9c..000000000 --- a/src/app/backups/components/BackupList/BackupListItem.tsx +++ /dev/null @@ -1,94 +0,0 @@ -import { Dropdown } from 'react-bootstrap'; - -import { DeviceBackup } from '../../types'; -import UilEllipsisH from '@iconscout/react-unicons/icons/uil-ellipsis-h'; -import dateService from '../../../core/services/date.service'; -import { ReactComponent as BackupIcon } from '../../../../assets/icons/light/folder-backup.svg'; -import sizeService from '../../../drive/services/size.service'; -import { useAppDispatch } from '../../../store/hooks'; -import { uiActions } from '../../../store/slices/ui'; -import BackupDropdownActions from '../BackupDropdownActions/BackupDropdownActions'; - -export default function BackupsListItem({ - backup, - onDownloadBackupClicked, - onDeleteBackupClicked, - dataTest, -}: { - backup: DeviceBackup; - onDownloadBackupClicked: (backup: DeviceBackup) => void; - onDeleteBackupClicked: (backup: DeviceBackup) => void; - dataTest?: string; -}): JSX.Element { - const dispatch = useAppDispatch(); - const isUploaded = !!backup.fileId; - const onDownload = () => isUploaded && onDownloadBackupClicked(backup); - const onDeleteButtonClicked = () => onDeleteBackupClicked(backup); - - const onInfoButtonClicked = (e: React.MouseEvent) => { - const infoMenuFeatures = [ - { - label: 'Device path', - value: backup.path, - }, - { - label: 'Size', - value: sizeService.bytesToString(backup.size || 0, false), - }, - { - label: 'Modified', - value: dateService.format(backup.updatedAt, 'DD MMMM YYYY'), - }, - { - label: 'Created', - value: dateService.format(backup.createdAt, 'DD MMMM YYYY'), - }, - ]; - - dispatch( - uiActions.setFileInfoItem({ - id: `backup-item-${backup.id}`, - icon: BackupIcon, - title: backup.name, - features: infoMenuFeatures, - }), - ); - dispatch(uiActions.setIsDriveItemInfoMenuOpen(true)); - - e.stopPropagation(); - }; - - return ( -
-
- -
-

{backup.name}

-
-
- {backup.lastBackupAt ? dateService.format(backup.lastBackupAt, 'DD MMMM YYYY. HH:mm') : 'Not uploaded yet'} -
-
{backup.size ? sizeService.bytesToString(backup.size, false) : ''}
-
- - - - - - - - -
-
- ); -} diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index b16b29039..01f63cc56 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -72,9 +72,9 @@ export default function BackupsAsFoldersList({
- onItemClicked(item)}> +
); diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx index 04e0f9dc3..ea95927a5 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx @@ -348,15 +348,15 @@ const contextMenuSelectedBackupItems = ({ onDeleteSelectedItems, }: { onDownloadSelectedItems: () => void; - onDeleteSelectedItems: () => void; + onDeleteSelectedItems: () => Promise; }): ListItemMenu => [ getDownloadMenuItem(onDownloadSelectedItems), { name: '', action: () => false, separator: true }, { name: t('drive.dropdown.delete'), icon: Trash, - action: () => { - onDeleteSelectedItems(); + action: async () => { + await onDeleteSelectedItems(); }, disabled: () => { return false; From bf56e433f690cd8f308ecb54e5fc278db0baba4f Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:22:46 +0200 Subject: [PATCH 04/24] chore(deps): update SDK version --- package.json | 2 +- yarn.lock | 29 ++++++----------------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 276009c9c..6c11754c4 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "@iconscout/react-unicons": "^1.1.6", "@internxt/inxt-js": "=1.2.21", "@internxt/lib": "^1.2.0", - "@internxt/sdk": "^1.5.15", + "@internxt/sdk": "^1.5.17", "@phosphor-icons/react": "^2.1.7", "@popperjs/core": "^2.11.6", "@reduxjs/toolkit": "^1.6.0", diff --git a/yarn.lock b/yarn.lock index 8eb9a8e9c..41ad02e70 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1612,10 +1612,10 @@ resolved "https://npm.pkg.github.com/download/@internxt/prettier-config/1.0.2/5bd220b8de76734448db5475b3e0c01f9d22c19b#5bd220b8de76734448db5475b3e0c01f9d22c19b" integrity sha512-t4HiqvCbC7XgQepwWlIaFJe3iwW7HCf6xOSU9nKTV0tiGqOPz7xMtIgLEloQrDA34Cx4PkOYBXrvFPV6RxSFAA== -"@internxt/sdk@^1.5.15": - version "1.5.15" - resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.5.15/8b8febc6f5d37f66db525f66d85fbfdba36b7357#8b8febc6f5d37f66db525f66d85fbfdba36b7357" - integrity sha512-LO2VpJsZKmPzAkz7Lu4JVubjpv27XwwunHKT3XTZ4BntGS7JxAVljOQ0LtY9GN5pdV3Id5YzQwB6SAzdm03z3A== +"@internxt/sdk@^1.5.17": + version "1.5.17" + resolved "https://npm.pkg.github.com/download/@internxt/sdk/1.5.17/a08d06a5bbb603cecb0ba71f6114701ff91e9ab2#a08d06a5bbb603cecb0ba71f6114701ff91e9ab2" + integrity sha512-s9uw1l2EIbHNR8qZ8mCYYIkqU6mXharS9SK3e67NuLuzmWDJYd4Tei/wpVu4DGrSH4AvVGoIIeSFQAshLnI2Zg== dependencies: axios "^0.24.0" query-string "^7.1.0" @@ -1970,7 +1970,7 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@phosphor-icons/react@^2.0.10": +"@phosphor-icons/react@^2.1.7": version "2.1.7" resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7" integrity sha512-g2e2eVAn1XG2a+LI09QU3IORLhnFNAFkNbo2iwbX6NOKSLOwvEMmTa7CgOzEbgNWR47z8i8kwjdvYZ5fkGx1mQ== @@ -4811,7 +4811,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-browserify@^3.12.0, "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== @@ -4838,23 +4838,6 @@ 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" From 5bfc486c5c32df7619ec352c08a31afe2fea6973 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 11:59:05 +0200 Subject: [PATCH 05/24] fix: setting the correct offset --- src/app/backups/views/BackupsView/BackupsView.tsx | 6 +++--- .../DriveExplorerList/DriveItemContextMenu.tsx | 6 ++---- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index e82c008ed..a689d6ff3 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -130,7 +130,7 @@ export default function BackupsView(): JSX.Element { const contextMenu: ListItemMenu = contextMenuSelectedBackupItems({ onDownloadSelectedItems, - onDeleteSelectedItems, + onDeleteSelectedItems: onDeleteSelectedItems, }); const onItemClicked = (item: DriveItemData) => { @@ -181,7 +181,7 @@ export default function BackupsView(): JSX.Element { if (items.length >= DEFAULT_LIMIT) { setHasMoreItems(true); - setOffset(items.length); + setOffset(DEFAULT_LIMIT); } else { setHasMoreItems(false); } @@ -213,7 +213,7 @@ export default function BackupsView(): JSX.Element { if (items.length >= DEFAULT_LIMIT) { setHasMoreItems(true); - setOffset((prevOffset) => prevOffset + items.length); + setOffset((prevOffset) => prevOffset + DEFAULT_LIMIT); } else { setHasMoreItems(false); } diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx index ea95927a5..266ccb1d3 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx @@ -348,16 +348,14 @@ const contextMenuSelectedBackupItems = ({ onDeleteSelectedItems, }: { onDownloadSelectedItems: () => void; - onDeleteSelectedItems: () => Promise; + onDeleteSelectedItems: () => void; }): ListItemMenu => [ getDownloadMenuItem(onDownloadSelectedItems), { name: '', action: () => false, separator: true }, { name: t('drive.dropdown.delete'), icon: Trash, - action: async () => { - await onDeleteSelectedItems(); - }, + action: onDeleteSelectedItems, disabled: () => { return false; }, From a06f37136b7b5632fec7efa20768035ad546afbd Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:13:05 +0200 Subject: [PATCH 06/24] fix: selecting all folder items while loading more items --- .../BackupsAsFoldersList.tsx | 10 ++---- .../backups/views/BackupsView/BackupsView.tsx | 33 ++++++++++++++++++- 2 files changed, 35 insertions(+), 8 deletions(-) diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index 01f63cc56..2b75ec503 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -19,6 +19,7 @@ export default function BackupsAsFoldersList({ getPaginatedBackupList, onItemClicked, onItemSelected, + onSelectedItemsChanged, }: { className?: string; contextMenu: ListItemMenu; @@ -29,6 +30,7 @@ export default function BackupsAsFoldersList({ getPaginatedBackupList: () => void; onItemClicked: (item: DriveItemData) => void; onItemSelected: (changes: { device: DriveItemData; isSelected: boolean }[]) => void; + onSelectedItemsChanged: (changes: { props: DriveItemData; value: boolean }[]) => void; }): JSX.Element { const { translate } = useTranslationContext(); @@ -108,13 +110,7 @@ export default function BackupsAsFoldersList({ menu={contextMenu} selectedItems={selectedItems} keyboardShortcuts={['unselectAll', 'selectAll', 'multiselect']} - onSelectedItemsChanged={(changes) => { - const selectedDevicesParsed = changes.map((change) => ({ - device: change.props, - isSelected: change.value, - })); - onItemSelected(selectedDevicesParsed); - }} + onSelectedItemsChanged={onSelectedItemsChanged} />
diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index a689d6ff3..b9221ae3a 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -40,6 +40,7 @@ export default function BackupsView(): JSX.Element { const [selectedItems, setSelectedItems] = useState([]); const [hasMoreItems, setHasMoreItems] = useState(true); const [offset, setOffset] = useState(0); + const [areAllItemsSelected, setAreAllItemsSelected] = useState(false); useEffect(() => { dispatch(backupsActions.setCurrentDevice(null)); @@ -57,8 +58,26 @@ export default function BackupsView(): JSX.Element { useEffect(() => { refreshFolderContent(); + setAreAllItemsSelected(false); }, [folderUuid]); + useEffect(() => { + const areAllItemsSelected = selectedItems.length === currentItems.length; + const itemsLengthIsNotZero = currentItems.length !== 0; + + if (areAllItemsSelected && itemsLengthIsNotZero) { + setAreAllItemsSelected(true); + } else { + setAreAllItemsSelected(false); + } + }, [selectedItems]); + + useEffect(() => { + if (areAllItemsSelected) { + setSelectedItems(currentItems); + } + }, [currentItems.length]); + function goToFolder(folderId: number, folderUuid?: string) { setBackupsAsFoldersPath((current) => { const index = current.findIndex((i) => i.id === folderId); @@ -147,8 +166,17 @@ export default function BackupsView(): JSX.Element { } }; + const onSelectedItemsChanged = (changes: { props: DriveItemData; value: boolean }[]) => { + const selectedDevicesParsed = changes.map((change) => ({ + device: change.props, + isSelected: change.value, + })); + onItemSelected(selectedDevicesParsed); + }; + const onItemSelected = (changes: { device: DriveItemData; isSelected: boolean }[]) => { let updatedSelectedItems = selectedItems; + for (const change of changes) { updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); if (change.isSelected) { @@ -209,7 +237,9 @@ export default function BackupsView(): JSX.Element { })); const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - setCurrentItems((prevItems) => [...prevItems, ...items]); + const totalCurrentItems = _.concat(currentItems, items); + + setCurrentItems(totalCurrentItems); if (items.length >= DEFAULT_LIMIT) { setHasMoreItems(true); @@ -245,6 +275,7 @@ export default function BackupsView(): JSX.Element { getPaginatedBackupList={getPaginatedBackupList} onItemClicked={onItemClicked} onItemSelected={onItemSelected} + onSelectedItemsChanged={onSelectedItemsChanged} /> ); } From 791b3d31bcd3a84f817875624834ea3f12c41544 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 13:55:02 +0200 Subject: [PATCH 07/24] fix: header checkbox behavior --- .../backups/views/BackupsView/BackupsView.tsx | 31 ++++++------------- .../DriveExplorerList/DriveExplorerList.tsx | 24 +------------- .../Containers/BreadcrumbsBackupsView.tsx | 18 +++-------- src/app/shared/components/List/index.tsx | 4 +-- 4 files changed, 16 insertions(+), 61 deletions(-) diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index b9221ae3a..4f02cca1c 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -40,7 +40,6 @@ export default function BackupsView(): JSX.Element { const [selectedItems, setSelectedItems] = useState([]); const [hasMoreItems, setHasMoreItems] = useState(true); const [offset, setOffset] = useState(0); - const [areAllItemsSelected, setAreAllItemsSelected] = useState(false); useEffect(() => { dispatch(backupsActions.setCurrentDevice(null)); @@ -58,26 +57,8 @@ export default function BackupsView(): JSX.Element { useEffect(() => { refreshFolderContent(); - setAreAllItemsSelected(false); }, [folderUuid]); - useEffect(() => { - const areAllItemsSelected = selectedItems.length === currentItems.length; - const itemsLengthIsNotZero = currentItems.length !== 0; - - if (areAllItemsSelected && itemsLengthIsNotZero) { - setAreAllItemsSelected(true); - } else { - setAreAllItemsSelected(false); - } - }, [selectedItems]); - - useEffect(() => { - if (areAllItemsSelected) { - setSelectedItems(currentItems); - } - }, [currentItems.length]); - function goToFolder(folderId: number, folderUuid?: string) { setBackupsAsFoldersPath((current) => { const index = current.findIndex((i) => i.id === folderId); @@ -89,6 +70,12 @@ export default function BackupsView(): JSX.Element { } } + const goToRootFolder = () => { + setSelectedDevices([]); + setFolderUuid(undefined); + dispatch(backupsActions.setCurrentDevice(null)); + }; + const onDeviceClicked = (target: Device | DriveFolderData) => { setSelectedDevices([]); dispatch(backupsActions.setCurrentDevice(target)); @@ -187,13 +174,13 @@ export default function BackupsView(): JSX.Element { }; async function refreshFolderContent() { - if (!folderUuid) return; - setIsLoading(true); setOffset(0); setSelectedItems([]); setCurrentItems([]); + if (!folderUuid) return; + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ folderUuid, limit: DEFAULT_LIMIT, @@ -305,9 +292,9 @@ export default function BackupsView(): JSX.Element {
{currentDevice ? ( ) : (

{translate('backups.your-devices')}

diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx index fde6166db..fdc83e81c 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx @@ -1,7 +1,7 @@ import { useAppSelector } from 'app/store/hooks'; import storageSelectors from 'app/store/slices/storage/storage.selectors'; import { fetchSortedFolderContentThunk } from 'app/store/slices/storage/storage.thunks/fetchSortedFolderContentThunk'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { memo, useCallback, useState } from 'react'; import { connect, useSelector } from 'react-redux'; import { ListShareLinksItem, Role } from '@internxt/sdk/dist/drive/share/types'; @@ -107,7 +107,6 @@ const resetDriveOrder = ({ const DriveExplorerList: React.FC = memo((props) => { const { dispatch, isLoading, order, hasMoreItems, onEndOfScroll, forceLoading, roles } = props; const selectedWorkspace = useSelector(workspacesSelectors.getSelectedWorkspace); - const [isAllSelectedEnabled, setIsAllSelectedEnabled] = useState(false); const [editNameItem, setEditNameItem] = useState(null); const workspaceSelected = useSelector(workspacesSelectors.getSelectedWorkspace); @@ -123,27 +122,6 @@ const DriveExplorerList: React.FC = memo((props) => { const { translate } = useTranslationContext(); - useEffect(() => { - setIsAllSelectedEnabled(false); - }, [props.folderId]); - - useEffect(() => { - const isAllItemsSelected = props.selectedItems.length === props.items.length; - const itemsLengthIsNotZero = props.items.length !== 0; - - if (isAllItemsSelected && itemsLengthIsNotZero) { - setIsAllSelectedEnabled(true); - } else { - setIsAllSelectedEnabled(false); - } - }, [props.selectedItems]); - - useEffect(() => { - if (isAllSelectedEnabled) { - dispatch(storageActions.selectItems(props.items)); - } - }, [props.items.length]); - const onSelectedItemsChanged = (changes: { props: DriveItemData; value: boolean }[]) => { let updatedSelectedItems = props.selectedItems; diff --git a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx index eae10e943..2774a8dea 100644 --- a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx +++ b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx @@ -1,32 +1,21 @@ import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; -import { Device } from 'app/backups/types'; import Breadcrumbs from 'app/shared/components/Breadcrumbs/Breadcrumbs'; import { useAppDispatch, useAppSelector } from 'app/store/hooks'; import { backupsActions } from 'app/store/slices/backups'; import { t } from 'i18next'; -import { Dispatch, SetStateAction } from 'react'; import BreadcrumbsMenuBackups from '../BreadcrumbsMenu/BreadcrumbsMenuBackups'; import { BreadcrumbItemData } from '../types'; interface BreadcrumbsBackupsViewProps { - setSelectedDevices: Dispatch>; backupsAsFoldersPath: DriveFolderData[]; goToFolder: (folderId: number, folderUuid?: string) => void; + goToRootFolder: () => void; } -const BreadcrumbsBackupsView = ({ - setSelectedDevices, - backupsAsFoldersPath, - goToFolder, -}: BreadcrumbsBackupsViewProps) => { +const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, goToRootFolder }: BreadcrumbsBackupsViewProps) => { const currentDevice = useAppSelector((state) => state.backups.currentDevice); const dispatch = useAppDispatch(); - const goBack = () => { - setSelectedDevices([]); - dispatch(backupsActions.setCurrentDevice(null)); - }; - const breadcrumbBackupsViewItems = (): BreadcrumbItemData[] => { const items: BreadcrumbItemData[] = []; @@ -36,7 +25,7 @@ const BreadcrumbsBackupsView = ({ icon: null, isFirstPath: true, active: true, - onClick: () => goBack(), + onClick: () => goToRootFolder(), }); if (currentDevice && 'mac' in currentDevice) { @@ -53,6 +42,7 @@ const BreadcrumbsBackupsView = ({ active: true, onClick: () => { dispatch(backupsActions.setCurrentFolder(item)); + console.log(item); goToFolder(item.id, item.uuid); }, }; diff --git a/src/app/shared/components/List/index.tsx b/src/app/shared/components/List/index.tsx index 83c917c74..2b95a8762 100644 --- a/src/app/shared/components/List/index.tsx +++ b/src/app/shared/components/List/index.tsx @@ -204,9 +204,9 @@ ListProps): JSX.Element { } function onTopSelectionCheckboxClick() { - const atLeastOneItemSelected = selectedItems.length !== 0; + const areAllItemsSelected = selectedItems.length === items.length; - if (atLeastOneItemSelected) { + if (areAllItemsSelected) { unselectAllItems(); } else { selectAllItems(); From 039e11fa78f6e0a75b72b35088358eef086847a8 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 15:44:03 +0200 Subject: [PATCH 08/24] fix(backups): navigating between files in the file viewer --- .../backups/views/BackupsView/BackupsView.tsx | 26 ++++++++++++++----- .../FileViewer/FileViewerWrapper.tsx | 14 ++++++---- .../Containers/BreadcrumbsBackupsView.tsx | 6 ++--- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 4f02cca1c..6f9c223e0 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -20,7 +20,8 @@ import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveE import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; import { deleteFile } from '../../../drive/services/file.service'; import { ListItemMenu } from '../../../shared/components/List/ListItem'; -import { uiActions } from '../../../store/slices/ui'; +import FileViewerWrapper from 'app/drive/components/FileViewer/FileViewerWrapper'; +import { PreviewFileItem } from 'app/share/types'; const DEFAULT_LIMIT = 50; @@ -40,6 +41,8 @@ export default function BackupsView(): JSX.Element { const [selectedItems, setSelectedItems] = useState([]); const [hasMoreItems, setHasMoreItems] = useState(true); const [offset, setOffset] = useState(0); + const [itemToPreview, setItemToPreview] = useState(); + const [isFileViewerOpen, setIsFileViewerOpen] = useState(false); useEffect(() => { dispatch(backupsActions.setCurrentDevice(null)); @@ -70,7 +73,7 @@ export default function BackupsView(): JSX.Element { } } - const goToRootFolder = () => { + const goToFolderRoot = () => { setSelectedDevices([]); setFolderUuid(undefined); dispatch(backupsActions.setCurrentDevice(null)); @@ -148,8 +151,8 @@ export default function BackupsView(): JSX.Element { setFolderUuid(item.uuid); } } else { - dispatch(uiActions.setIsFileViewerOpen(true)); - dispatch(uiActions.setFileViewerItem(item)); + setItemToPreview(item); + setIsFileViewerOpen(false); } }; @@ -174,13 +177,13 @@ export default function BackupsView(): JSX.Element { }; async function refreshFolderContent() { + if (!folderUuid) return; + setIsLoading(true); setOffset(0); setSelectedItems([]); setCurrentItems([]); - if (!folderUuid) return; - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ folderUuid, limit: DEFAULT_LIMIT, @@ -289,12 +292,21 @@ export default function BackupsView(): JSX.Element { secondaryAction={translate('modals.deleteBackupModal.secondaryAction')} primaryActionColor="danger" /> + {itemToPreview && ( + setIsFileViewerOpen(false)} + showPreview={isFileViewerOpen} + folderItems={currentItems} + onShowStopSharingDialog={() => setIsDeleteModalOpen(true)} + /> + )}
{currentDevice ? ( ) : (

{translate('backups.your-devices')}

diff --git a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx index 646ad1d09..2e11623aa 100644 --- a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx +++ b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx @@ -5,7 +5,6 @@ import { DriveFileData, DriveItemData } from '../../types'; import { Thumbnail } from '@internxt/sdk/dist/drive/storage/types'; import { getAppConfig } from 'app/core/services/config.service'; import localStorageService from 'app/core/services/local-storage.service'; -import navigationService from 'app/core/services/navigation.service'; import { ListItemMenu } from 'app/shared/components/List/ListItem'; import { useCallback, useEffect, useMemo, useState } from 'react'; import errorService from '../../../core/services/error.service'; @@ -43,6 +42,7 @@ interface FileViewerWrapperProps { file: PreviewFileItem; onClose: () => void; showPreview: boolean; + folderItems?: DriveItemData[]; onShowStopSharingDialog?: () => void; sharedKeyboardShortcuts?: { removeItemFromKeyboard?: (item: DriveItemData) => void; @@ -54,6 +54,7 @@ const FileViewerWrapper = ({ file, onClose, showPreview, + folderItems, onShowStopSharingDialog, sharedKeyboardShortcuts, }: FileViewerWrapperProps): JSX.Element => { @@ -157,8 +158,11 @@ const FileViewerWrapper = ({ } return []; }, [folderFiles]); - const fileIndex = sortFolderFiles?.findIndex((item) => item.id === currentFile?.id); - const totalFolderIndex = sortFolderFiles?.length; + + const folderItemsFiltered = folderItems && folderItems.filter((item) => !item.isFolder || item.type !== 'folder'); + const currentFolder = folderItemsFiltered ?? sortFolderFiles; + const fileIndex = currentFolder?.findIndex((item) => item.id === currentFile?.id); + const totalFolderIndex = currentFolder?.length; //Switch to the next or previous file in the folder function changeFile(direction: 'next' | 'prev') { @@ -166,9 +170,9 @@ const FileViewerWrapper = ({ setIsDownloadStarted(false); setUpdateProgress(0); if (direction === 'next') { - setCurrentFile?.(sortFolderFiles[fileIndex + 1]); + setCurrentFile?.(currentFolder[fileIndex + 1]); } else { - setCurrentFile?.(sortFolderFiles[fileIndex - 1]); + setCurrentFile?.(currentFolder[fileIndex - 1]); } } diff --git a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx index 2774a8dea..6b1e8d6d5 100644 --- a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx +++ b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx @@ -9,10 +9,10 @@ import { BreadcrumbItemData } from '../types'; interface BreadcrumbsBackupsViewProps { backupsAsFoldersPath: DriveFolderData[]; goToFolder: (folderId: number, folderUuid?: string) => void; - goToRootFolder: () => void; + gotoFolderRoot: () => void; } -const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, goToRootFolder }: BreadcrumbsBackupsViewProps) => { +const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, gotoFolderRoot }: BreadcrumbsBackupsViewProps) => { const currentDevice = useAppSelector((state) => state.backups.currentDevice); const dispatch = useAppDispatch(); @@ -25,7 +25,7 @@ const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, goToRootFold icon: null, isFirstPath: true, active: true, - onClick: () => goToRootFolder(), + onClick: () => gotoFolderRoot(), }); if (currentDevice && 'mac' in currentDevice) { From 433ac0bf768ab7afe264b8ad4483cacd69bdaa74 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:01:21 +0200 Subject: [PATCH 09/24] fix(backups): context menu options and close behavior for file viewer comp --- .../backups/views/BackupsView/BackupsView.tsx | 15 +++++++++++-- .../FileViewer/FileViewerWrapper.tsx | 22 +++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 6f9c223e0..8d7b4012d 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -135,8 +135,18 @@ export default function BackupsView(): JSX.Element { setCurrentItems((items) => items.filter((i) => !(i.id === item.id && i.isFolder === item.isFolder))); } dispatch(deleteItemsThunk(selectedItems)); + + if (isFileViewerOpen) { + setIsFileViewerOpen(false); + setItemToPreview(undefined); + } } + const onCloseFileViewer = () => { + setIsFileViewerOpen(false); + setItemToPreview(undefined); + }; + const contextMenu: ListItemMenu = contextMenuSelectedBackupItems({ onDownloadSelectedItems, onDeleteSelectedItems: onDeleteSelectedItems, @@ -152,7 +162,7 @@ export default function BackupsView(): JSX.Element { } } else { setItemToPreview(item); - setIsFileViewerOpen(false); + setIsFileViewerOpen(true); } }; @@ -295,10 +305,11 @@ export default function BackupsView(): JSX.Element { {itemToPreview && ( setIsFileViewerOpen(false)} + onClose={onCloseFileViewer} showPreview={isFileViewerOpen} folderItems={currentItems} onShowStopSharingDialog={() => setIsDeleteModalOpen(true)} + contextMenu={contextMenu} /> )}
diff --git a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx index 2e11623aa..cca25fa8c 100644 --- a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx +++ b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx @@ -40,9 +40,10 @@ const SPECIAL_MIME_TYPES = ['heic']; interface FileViewerWrapperProps { file: PreviewFileItem; - onClose: () => void; showPreview: boolean; + onClose: () => void; folderItems?: DriveItemData[]; + contextMenu?: ListItemMenu; onShowStopSharingDialog?: () => void; sharedKeyboardShortcuts?: { removeItemFromKeyboard?: (item: DriveItemData) => void; @@ -55,6 +56,7 @@ const FileViewerWrapper = ({ onClose, showPreview, folderItems, + contextMenu, onShowStopSharingDialog, sharedKeyboardShortcuts, }: FileViewerWrapperProps): JSX.Element => { @@ -123,14 +125,16 @@ const FileViewerWrapper = ({ return currentUserRole === UserRoles.Reader; }, [currentUserRole]); - const topActionsMenu = topDropdownBarActionsMenu({ - currentFile, - user, - onClose, - onShowStopSharingDialog, - driveItemActions, - isCurrentUserViewer, - }); + const topActionsMenu = + contextMenu ?? + topDropdownBarActionsMenu({ + currentFile, + user, + onClose, + onShowStopSharingDialog, + driveItemActions, + isCurrentUserViewer, + }); const { removeItemFromKeyboard, renameItemFromKeyboard } = useFileViewerKeyboardShortcuts({ sharedKeyboardShortcuts, From 818bb49d5430c909edc6e37ffdf4555c0f7f629a Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Fri, 20 Sep 2024 16:20:19 +0200 Subject: [PATCH 10/24] fix(breadcrumbs): function name --- src/app/backups/views/BackupsView/BackupsView.tsx | 2 +- .../Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 8d7b4012d..268580222 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -317,7 +317,7 @@ export default function BackupsView(): JSX.Element { ) : (

{translate('backups.your-devices')}

diff --git a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx index 6b1e8d6d5..c119b31ff 100644 --- a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx +++ b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx @@ -9,10 +9,10 @@ import { BreadcrumbItemData } from '../types'; interface BreadcrumbsBackupsViewProps { backupsAsFoldersPath: DriveFolderData[]; goToFolder: (folderId: number, folderUuid?: string) => void; - gotoFolderRoot: () => void; + goToFolderRoot: () => void; } -const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, gotoFolderRoot }: BreadcrumbsBackupsViewProps) => { +const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, goToFolderRoot }: BreadcrumbsBackupsViewProps) => { const currentDevice = useAppSelector((state) => state.backups.currentDevice); const dispatch = useAppDispatch(); @@ -25,7 +25,7 @@ const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, gotoFolderRo icon: null, isFirstPath: true, active: true, - onClick: () => gotoFolderRoot(), + onClick: () => goToFolderRoot(), }); if (currentDevice && 'mac' in currentDevice) { From 22d06d7a5e6bfc3d185fd4f511464e97e174866b Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Mon, 23 Sep 2024 17:10:36 +0200 Subject: [PATCH 11/24] feat: extract Skeleton to a shared component --- .../BackupsAsFoldersList.tsx | 2 +- .../backups/views/BackupsView/BackupsView.tsx | 91 +++---------------- .../DriveExplorerList/DriveExplorerList.tsx | 11 +-- src/app/shared/Skeleton/index.tsx | 9 ++ .../Containers/BreadcrumbsBackupsView.tsx | 1 - src/hooks/backups/usePagination.ts | 86 ++++++++++++++++++ 6 files changed, 111 insertions(+), 89 deletions(-) create mode 100644 src/app/shared/Skeleton/index.tsx create mode 100644 src/hooks/backups/usePagination.ts diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index 2b75ec503..633a6e8d5 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -6,8 +6,8 @@ import { DriveItemData } from '../../../drive/types'; import { useTranslationContext } from '../../../i18n/provider/TranslationProvider'; import Empty from '../../../shared/components/Empty/Empty'; import List from '../../../shared/components/List'; -import { skinSkeleton } from 'app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList'; import { ListItemMenu } from 'app/shared/components/List/ListItem'; +import { skinSkeleton } from 'app/shared/Skeleton'; export default function BackupsAsFoldersList({ className = '', diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 268580222..2d5e34505 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -14,14 +14,13 @@ import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/d import BackupsAsFoldersList from '../../components/BackupsAsFoldersList/BackupsAsFoldersList'; import DeviceList from '../../components/DeviceList/DeviceList'; import { Device } from '../../types'; -import newStorageService from 'app/drive/services/new-storage.service'; -import _ from 'lodash'; import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; import { deleteFile } from '../../../drive/services/file.service'; import { ListItemMenu } from '../../../shared/components/List/ListItem'; import FileViewerWrapper from 'app/drive/components/FileViewer/FileViewerWrapper'; import { PreviewFileItem } from 'app/share/types'; +import { useBackupsPagination } from 'hooks/backups/usePagination'; const DEFAULT_LIMIT = 50; @@ -36,11 +35,16 @@ export default function BackupsView(): JSX.Element { const [selectedDevices, setSelectedDevices] = useState<(Device | DriveFolderData)[]>([]); const [backupsAsFoldersPath, setBackupsAsFoldersPath] = useState([]); const [folderUuid, setFolderUuid] = useState(); - const [isLoading, setIsLoading] = useState(true); - const [currentItems, setCurrentItems] = useState([]); const [selectedItems, setSelectedItems] = useState([]); - const [hasMoreItems, setHasMoreItems] = useState(true); - const [offset, setOffset] = useState(0); + + const { currentItems, isLoading, hasMoreItems, getMorePaginatedItems, getFolderContent, setIsLoading } = + useBackupsPagination(folderUuid); + + useEffect(() => { + setSelectedItems([]); + getFolderContent(); + }, [folderUuid]); + const [itemToPreview, setItemToPreview] = useState(); const [isFileViewerOpen, setIsFileViewerOpen] = useState(false); @@ -58,10 +62,6 @@ export default function BackupsView(): JSX.Element { } }, [currentDevice]); - useEffect(() => { - refreshFolderContent(); - }, [folderUuid]); - function goToFolder(folderId: number, folderUuid?: string) { setBackupsAsFoldersPath((current) => { const index = current.findIndex((i) => i.id === folderId); @@ -132,7 +132,6 @@ export default function BackupsView(): JSX.Element { } else { await deleteFile(item); } - setCurrentItems((items) => items.filter((i) => !(i.id === item.id && i.isFolder === item.isFolder))); } dispatch(deleteItemsThunk(selectedItems)); @@ -140,6 +139,9 @@ export default function BackupsView(): JSX.Element { setIsFileViewerOpen(false); setItemToPreview(undefined); } + + setSelectedItems([]); + await getFolderContent(); } const onCloseFileViewer = () => { @@ -186,71 +188,6 @@ export default function BackupsView(): JSX.Element { setSelectedItems(updatedSelectedItems); }; - async function refreshFolderContent() { - if (!folderUuid) return; - - setIsLoading(true); - setOffset(0); - setSelectedItems([]); - setCurrentItems([]); - - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ - folderUuid, - limit: DEFAULT_LIMIT, - offset: 0, - }); - - const response = await folderContentPromise; - const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); - const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); - const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - - setCurrentItems(items); - - if (items.length >= DEFAULT_LIMIT) { - setHasMoreItems(true); - setOffset(DEFAULT_LIMIT); - } else { - setHasMoreItems(false); - } - - setIsLoading(false); - } - - const getPaginatedBackupList = async () => { - if (!folderUuid || !hasMoreItems) return; - - setIsLoading(true); - - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ - folderUuid, - limit: DEFAULT_LIMIT, - offset, - }); - - const folderContentResponse = await folderContentPromise; - const files = folderContentResponse.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); - const folders = folderContentResponse.children.map((folder) => ({ - ...folder, - isFolder: true, - name: folder.plainName, - })); - const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - - const totalCurrentItems = _.concat(currentItems, items); - - setCurrentItems(totalCurrentItems); - - if (items.length >= DEFAULT_LIMIT) { - setHasMoreItems(true); - setOffset((prevOffset) => prevOffset + DEFAULT_LIMIT); - } else { - setHasMoreItems(false); - } - - setIsLoading(false); - }; - let body; if (!currentDevice) { @@ -272,7 +209,7 @@ export default function BackupsView(): JSX.Element { selectedItems={selectedItems} hasMoreItems={hasMoreItems} isLoading={isLoading} - getPaginatedBackupList={getPaginatedBackupList} + getPaginatedBackupList={getMorePaginatedItems} onItemClicked={onItemClicked} onItemSelected={onItemSelected} onSelectedItemsChanged={onSelectedItemsChanged} diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx index fdc83e81c..b35532c98 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveExplorerList.tsx @@ -35,6 +35,7 @@ import { contextMenuWorkspaceFile, contextMenuWorkspaceFolder, } from './DriveItemContextMenu'; +import { skinSkeleton } from 'app/shared/Skeleton'; interface DriveExplorerListProps { folderId: string; @@ -546,13 +547,3 @@ export default connect((state: RootState) => ({ state.ui.isItemDetailsDialogOpen || state.ui.isPreferencesDialogOpen, }))(DriveExplorerList); - -export const skinSkeleton = [ -
-
-
-
, -
, -
, -
, -]; diff --git a/src/app/shared/Skeleton/index.tsx b/src/app/shared/Skeleton/index.tsx new file mode 100644 index 000000000..80fc5b908 --- /dev/null +++ b/src/app/shared/Skeleton/index.tsx @@ -0,0 +1,9 @@ +export const skinSkeleton = [ +
+
+
+
, +
, +
, +
, +]; diff --git a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx index c119b31ff..a7cfaae0d 100644 --- a/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx +++ b/src/app/shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView.tsx @@ -42,7 +42,6 @@ const BreadcrumbsBackupsView = ({ backupsAsFoldersPath, goToFolder, goToFolderRo active: true, onClick: () => { dispatch(backupsActions.setCurrentFolder(item)); - console.log(item); goToFolder(item.id, item.uuid); }, }; diff --git a/src/hooks/backups/usePagination.ts b/src/hooks/backups/usePagination.ts new file mode 100644 index 000000000..17539d8bf --- /dev/null +++ b/src/hooks/backups/usePagination.ts @@ -0,0 +1,86 @@ +import newStorageService from 'app/drive/services/new-storage.service'; +import { DriveItemData } from 'app/drive/types'; +import _ from 'lodash'; +import { useState } from 'react'; + +const DEFAULT_LIMIT = 50; + +export const useBackupsPagination = (folderUuid: string | undefined) => { + const [isLoading, setIsLoading] = useState(true); + const [currentItems, setCurrentItems] = useState([]); + const [hasMoreItems, setHasMoreItems] = useState(true); + const [offset, setOffset] = useState(0); + + const getFolderContent = async () => { + setIsLoading(true); + setCurrentItems([]); + setOffset(0); + + if (!folderUuid) return; + + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid, + limit: DEFAULT_LIMIT, + offset: 0, + }); + + const response = await folderContentPromise; + const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); + const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); + const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + + setCurrentItems(items); + + if (items.length >= DEFAULT_LIMIT) { + setHasMoreItems(true); + setOffset(DEFAULT_LIMIT); + } else { + setHasMoreItems(false); + } + + setIsLoading(false); + }; + + const getMorePaginatedItems = async () => { + if (!folderUuid || !hasMoreItems) return; + + setIsLoading(true); + + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid, + limit: DEFAULT_LIMIT, + offset, + }); + + const folderContentResponse = await folderContentPromise; + const files = folderContentResponse.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); + const folders = folderContentResponse.children.map((folder) => ({ + ...folder, + isFolder: true, + name: folder.plainName, + })); + const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + + const totalCurrentItems = _.concat(currentItems, items); + + setCurrentItems(totalCurrentItems); + + if (items.length >= DEFAULT_LIMIT) { + setHasMoreItems(true); + setOffset((prevOffset) => prevOffset + DEFAULT_LIMIT); + } else { + setHasMoreItems(false); + } + + setIsLoading(false); + }; + + return { + currentItems, + isLoading, + hasMoreItems, + getFolderContent, + getMorePaginatedItems, + setIsLoading, + }; +}; From 06fc41c29467b255630f85fb10c1e82e0da1c01f Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 09:00:02 +0200 Subject: [PATCH 12/24] feat: adding key to the shared Skin Skeleton --- src/app/drive/components/FileViewer/FileViewerWrapper.tsx | 4 +++- src/app/shared/Skeleton/index.tsx | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx index cca25fa8c..ef09446bc 100644 --- a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx +++ b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx @@ -163,7 +163,9 @@ const FileViewerWrapper = ({ return []; }, [folderFiles]); - const folderItemsFiltered = folderItems && folderItems.filter((item) => !item.isFolder || item.type !== 'folder'); + const folderItemsFiltered = folderItems + ? folderItems.filter((item) => !item.isFolder || item.type !== 'folder') + : undefined; const currentFolder = folderItemsFiltered ?? sortFolderFiles; const fileIndex = currentFolder?.findIndex((item) => item.id === currentFile?.id); const totalFolderIndex = currentFolder?.length; diff --git a/src/app/shared/Skeleton/index.tsx b/src/app/shared/Skeleton/index.tsx index 80fc5b908..f364c15b1 100644 --- a/src/app/shared/Skeleton/index.tsx +++ b/src/app/shared/Skeleton/index.tsx @@ -1,5 +1,5 @@ export const skinSkeleton = [ -
+
, From 763da2087bd0141eb75bb324f6cd26f6f6f23045 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 11:16:15 +0200 Subject: [PATCH 13/24] feat: extracting logic to custom hooks --- .../BackupsAsFoldersList.tsx | 4 +- .../backups/views/BackupsView/BackupsView.tsx | 195 ++++-------------- src/hooks/backups/useBackupDeviceActions.ts | 105 ++++++++++ src/hooks/backups/useBackupListActions.ts | 73 +++++++ ...ePagination.ts => useBackupsPagination.ts} | 22 +- 5 files changed, 238 insertions(+), 161 deletions(-) create mode 100644 src/hooks/backups/useBackupDeviceActions.ts create mode 100644 src/hooks/backups/useBackupListActions.ts rename src/hooks/backups/{usePagination.ts => useBackupsPagination.ts} (85%) diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index 633a6e8d5..2c3e40ed0 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -73,8 +73,8 @@ export default function BackupsAsFoldersList({
-
-
diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 2d5e34505..0e2e7cf92 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -1,28 +1,25 @@ -import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; +import { useState } from 'react'; import { useTranslationContext } from '../../../i18n/provider/TranslationProvider'; import BreadcrumbsBackupsView from '../../../shared/components/Breadcrumbs/Containers/BreadcrumbsBackupsView'; import { useAppDispatch, useAppSelector } from '../../../store/hooks'; -import { backupsActions, backupsThunks } from '../../../store/slices/backups'; -import { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import DeleteBackupDialog from '../../../drive/components/DeleteBackupDialog/DeleteBackupDialog'; import WarningMessageWrapper from '../../../drive/components/WarningMessage/WarningMessageWrapper'; -import { deleteBackupDeviceAsFolder } from '../../../drive/services/folder.service'; -import { DriveItemData, DriveFolderData as DriveWebFolderData } from '../../../drive/types'; import Dialog from '../../../shared/components/Dialog/Dialog'; -import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/deleteItemsThunk'; import BackupsAsFoldersList from '../../components/BackupsAsFoldersList/BackupsAsFoldersList'; import DeviceList from '../../components/DeviceList/DeviceList'; -import { Device } from '../../types'; -import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; +import FileViewerWrapper from '../../../drive/components/FileViewer/FileViewerWrapper'; +import { useBackupsPagination } from 'hooks/backups/useBackupsPagination'; import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; +import { deleteBackupDeviceAsFolder } from '../../../drive/services/folder.service'; import { deleteFile } from '../../../drive/services/file.service'; +import { deleteItemsThunk } from '../../../store/slices/storage/storage.thunks/deleteItemsThunk'; import { ListItemMenu } from '../../../shared/components/List/ListItem'; -import FileViewerWrapper from 'app/drive/components/FileViewer/FileViewerWrapper'; -import { PreviewFileItem } from 'app/share/types'; -import { useBackupsPagination } from 'hooks/backups/usePagination'; - -const DEFAULT_LIMIT = 50; +import { DriveFolderData as DriveWebFolderData, DriveItemData } from '../../../drive/types'; +import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; +import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; +import { useBackupListActions } from 'hooks/backups/useBackupListActions'; +import { useBackupDeviceActions } from 'hooks/backups/useBackupDeviceActions'; export default function BackupsView(): JSX.Element { const { translate } = useTranslationContext(); @@ -30,96 +27,35 @@ export default function BackupsView(): JSX.Element { const isLoadingDevices = useAppSelector((state) => state.backups.isLoadingDevices); const devices = useAppSelector((state) => state.backups.devices); const currentDevice = useAppSelector((state) => state.backups.currentDevice); - - const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); - const [selectedDevices, setSelectedDevices] = useState<(Device | DriveFolderData)[]>([]); - const [backupsAsFoldersPath, setBackupsAsFoldersPath] = useState([]); - const [folderUuid, setFolderUuid] = useState(); - const [selectedItems, setSelectedItems] = useState([]); - - const { currentItems, isLoading, hasMoreItems, getMorePaginatedItems, getFolderContent, setIsLoading } = - useBackupsPagination(folderUuid); - - useEffect(() => { - setSelectedItems([]); - getFolderContent(); - }, [folderUuid]); - - const [itemToPreview, setItemToPreview] = useState(); - const [isFileViewerOpen, setIsFileViewerOpen] = useState(false); - - useEffect(() => { - dispatch(backupsActions.setCurrentDevice(null)); - setBackupsAsFoldersPath([]); - setFolderUuid(undefined); - dispatch(backupsThunks.fetchDevicesThunk()); - }, []); - - useEffect(() => { - if (currentDevice && !('mac' in currentDevice)) { - setBackupsAsFoldersPath([currentDevice]); - setFolderUuid(currentDevice.uuid); - } - }, [currentDevice]); - - function goToFolder(folderId: number, folderUuid?: string) { - setBackupsAsFoldersPath((current) => { - const index = current.findIndex((i) => i.id === folderId); - return current.slice(0, index + 1); - }); - - if (folderUuid) { - setFolderUuid(folderUuid); - } - } - - const goToFolderRoot = () => { - setSelectedDevices([]); - setFolderUuid(undefined); - dispatch(backupsActions.setCurrentDevice(null)); - }; - - const onDeviceClicked = (target: Device | DriveFolderData) => { - setSelectedDevices([]); - dispatch(backupsActions.setCurrentDevice(target)); - if ('mac' in target) { - dispatch(backupsThunks.fetchDeviceBackupsThunk(target.mac)); - } - }; - - const onOpenDeleteModal = (targets: (Device | DriveFolderData)[]) => { - setSelectedDevices((values) => [...values, ...targets]); - setIsDeleteModalOpen(true); - }; - - const onDevicesSelected = (changes: { device: Device | DriveFolderData; isSelected: boolean }[]) => { - let updatedSelectedItems = selectedDevices; - for (const change of changes) { - updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); - if (change.isSelected) { - updatedSelectedItems = [...updatedSelectedItems, change.device]; - } - } - setSelectedDevices(updatedSelectedItems); - }; - - const onConfirmDelete = async () => { - for (const selectedDevice of selectedDevices) { - if (selectedDevice && 'mac' in selectedDevice) { - dispatch(backupsThunks.deleteDeviceThunk(selectedDevice)); - } else { - await dispatch(deleteItemsThunk([selectedDevice as DriveItemData])).unwrap(); - await deleteBackupDeviceAsFolder(selectedDevice as DriveWebFolderData); - dispatch(backupsThunks.fetchDevicesThunk()); - } - } - onCloseDeleteModal(); - }; - - const onCloseDeleteModal = () => { - setIsDeleteModalOpen(false); - setSelectedDevices([]); - }; + const [foldersInBreadcrumbs, setFoldersInBreadcrumbs] = useState([]); + + const { + folderUuid, + isFileViewerOpen, + itemToPreview, + selectedItems, + onFolderUuidChanges, + clearSelectedItems, + onCloseFileViewer, + onItemSelected, + onItemClicked, + onSelectedItemsChanged, + } = useBackupListActions(setFoldersInBreadcrumbs, dispatch); + + const { + selectedDevices, + isDeleteModalOpen, + goToFolder, + goToFolderRoot, + onConfirmDelete, + onDeviceClicked, + onDevicesSelected, + onOpenDeleteModal, + onCloseDeleteModal, + } = useBackupDeviceActions(onFolderUuidChanges, setFoldersInBreadcrumbs, dispatch); + + const { currentItems, areFetchingItems, hasMoreItems, getMorePaginatedItems, getFolderContent } = + useBackupsPagination(folderUuid, clearSelectedItems); const onDownloadSelectedItems = () => { dispatch(downloadItemsThunk(selectedItems)); @@ -136,58 +72,18 @@ export default function BackupsView(): JSX.Element { dispatch(deleteItemsThunk(selectedItems)); if (isFileViewerOpen) { - setIsFileViewerOpen(false); - setItemToPreview(undefined); + onCloseFileViewer(); } - setSelectedItems([]); + onSelectedItemsChanged([]); await getFolderContent(); } - const onCloseFileViewer = () => { - setIsFileViewerOpen(false); - setItemToPreview(undefined); - }; - const contextMenu: ListItemMenu = contextMenuSelectedBackupItems({ onDownloadSelectedItems, onDeleteSelectedItems: onDeleteSelectedItems, }); - const onItemClicked = (item: DriveItemData) => { - if (item.isFolder) { - if (!isLoading) { - setIsLoading(true); - setBackupsAsFoldersPath((current) => [...current, item]); - dispatch(backupsActions.setCurrentFolder(item)); - setFolderUuid(item.uuid); - } - } else { - setItemToPreview(item); - setIsFileViewerOpen(true); - } - }; - - const onSelectedItemsChanged = (changes: { props: DriveItemData; value: boolean }[]) => { - const selectedDevicesParsed = changes.map((change) => ({ - device: change.props, - isSelected: change.value, - })); - onItemSelected(selectedDevicesParsed); - }; - - const onItemSelected = (changes: { device: DriveItemData; isSelected: boolean }[]) => { - let updatedSelectedItems = selectedItems; - - for (const change of changes) { - updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); - if (change.isSelected) { - updatedSelectedItems = [...updatedSelectedItems, change.device]; - } - } - setSelectedItems(updatedSelectedItems); - }; - let body; if (!currentDevice) { @@ -201,14 +97,14 @@ export default function BackupsView(): JSX.Element { selectedItems={selectedDevices} /> ); - } else if (backupsAsFoldersPath.length) { + } else if (foldersInBreadcrumbs.length) { body = ( {translate('sideNav.backups')} - Internxt Drive - + setIsDeleteModalOpen(true)} contextMenu={contextMenu} /> )}
{currentDevice ? ( diff --git a/src/hooks/backups/useBackupDeviceActions.ts b/src/hooks/backups/useBackupDeviceActions.ts new file mode 100644 index 000000000..723de6095 --- /dev/null +++ b/src/hooks/backups/useBackupDeviceActions.ts @@ -0,0 +1,105 @@ +import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; +import { Device } from 'app/backups/types'; +import { deleteBackupDeviceAsFolder } from 'app/drive/services/folder.service'; +import { DriveItemData, DriveFolderData as DriveWebFolderData } from 'app/drive/types'; +import { AppDispatch } from 'app/store'; +import { useAppSelector } from 'app/store/hooks'; +import { backupsActions, backupsThunks } from 'app/store/slices/backups'; +import { deleteItemsThunk } from 'app/store/slices/storage/storage.thunks/deleteItemsThunk'; +import { Dispatch, SetStateAction, useEffect, useState } from 'react'; + +export const useBackupDeviceActions = ( + onFolderUuidChanges: (folderUuid?: string) => void, + onBreadcrumbFolderChanges: Dispatch>, + dispatch: AppDispatch, +) => { + const currentDevice = useAppSelector((state) => state.backups.currentDevice); + + const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false); + const [selectedDevices, setSelectedDevices] = useState<(Device | DriveFolderData)[]>([]); + + useEffect(() => { + dispatch(backupsActions.setCurrentDevice(null)); + onBreadcrumbFolderChanges([]); + onFolderUuidChanges(undefined); + dispatch(backupsThunks.fetchDevicesThunk()); + }, []); + + useEffect(() => { + if (currentDevice && !('mac' in currentDevice)) { + onBreadcrumbFolderChanges([currentDevice]); + onFolderUuidChanges(currentDevice.uuid); + } + }, [currentDevice]); + + function goToFolder(folderId: number, folderUuid?: string) { + onBreadcrumbFolderChanges((current) => { + const index = current.findIndex((i) => i.id === folderId); + return current.slice(0, index + 1); + }); + + if (folderUuid) { + onFolderUuidChanges(folderUuid); + } + } + + const goToFolderRoot = () => { + setSelectedDevices([]); + onFolderUuidChanges(undefined); + dispatch(backupsActions.setCurrentDevice(null)); + }; + + const onDeviceClicked = (target: Device | DriveFolderData) => { + setSelectedDevices([]); + dispatch(backupsActions.setCurrentDevice(target)); + if ('mac' in target) { + dispatch(backupsThunks.fetchDeviceBackupsThunk(target.mac)); + } + }; + + const onOpenDeleteModal = (targets: (Device | DriveFolderData)[]) => { + setSelectedDevices((values) => [...values, ...targets]); + setIsDeleteModalOpen(true); + }; + + const onDevicesSelected = (changes: { device: Device | DriveFolderData; isSelected: boolean }[]) => { + let updatedSelectedItems = selectedDevices; + for (const change of changes) { + updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); + if (change.isSelected) { + updatedSelectedItems = [...updatedSelectedItems, change.device]; + } + } + setSelectedDevices(updatedSelectedItems); + }; + + const onConfirmDelete = async () => { + for (const selectedDevice of selectedDevices) { + if (selectedDevice && 'mac' in selectedDevice) { + dispatch(backupsThunks.deleteDeviceThunk(selectedDevice)); + } else { + await dispatch(deleteItemsThunk([selectedDevice as DriveItemData])).unwrap(); + await deleteBackupDeviceAsFolder(selectedDevice as DriveWebFolderData); + dispatch(backupsThunks.fetchDevicesThunk()); + } + } + onCloseDeleteModal(); + }; + + const onCloseDeleteModal = () => { + setIsDeleteModalOpen(false); + setSelectedDevices([]); + }; + + return { + isDeleteModalOpen, + selectedDevices, + goToFolder, + goToFolderRoot, + onDeviceClicked, + onOpenDeleteModal, + onDevicesSelected, + onConfirmDelete, + onCloseDeleteModal, + }; +}; diff --git a/src/hooks/backups/useBackupListActions.ts b/src/hooks/backups/useBackupListActions.ts new file mode 100644 index 000000000..84d5c3ad8 --- /dev/null +++ b/src/hooks/backups/useBackupListActions.ts @@ -0,0 +1,73 @@ +import { Dispatch, SetStateAction, useState } from 'react'; +import { DriveItemData } from 'app/drive/types'; +import { AppDispatch } from 'app/store'; +import { backupsActions } from 'app/store/slices/backups'; +import { PreviewFileItem } from 'app/share/types'; +import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; + +export const useBackupListActions = ( + onBreadcrumbFolderChanges: Dispatch>, + dispatch: AppDispatch, +) => { + const [folderUuid, setFolderUuid] = useState(); + const [selectedItems, setSelectedItems] = useState([]); + const [itemToPreview, setItemToPreview] = useState(); + const [isFileViewerOpen, setIsFileViewerOpen] = useState(false); + + const onFolderUuidChanges = (folderUuid?: string) => { + setFolderUuid(folderUuid); + }; + + const onCloseFileViewer = () => { + setIsFileViewerOpen(false); + setItemToPreview(undefined); + }; + + const onItemClicked = (item: DriveItemData) => { + if (item.isFolder) { + onBreadcrumbFolderChanges((current) => [...current, item]); + dispatch(backupsActions.setCurrentFolder(item)); + setFolderUuid(item.uuid); + } else { + setItemToPreview(item); + setIsFileViewerOpen(true); + } + }; + + const onSelectedItemsChanged = (changes: { props: DriveItemData; value: boolean }[]) => { + const selectedDevicesParsed = changes.map((change) => ({ + device: change.props, + isSelected: change.value, + })); + onItemSelected(selectedDevicesParsed); + }; + + const onItemSelected = (changes: { device: DriveItemData; isSelected: boolean }[]) => { + let updatedSelectedItems = selectedItems; + + for (const change of changes) { + updatedSelectedItems = updatedSelectedItems.filter((item) => item.id !== change.device.id); + if (change.isSelected) { + updatedSelectedItems = [...updatedSelectedItems, change.device]; + } + } + setSelectedItems(updatedSelectedItems); + }; + + const clearSelectedItems = () => { + setSelectedItems([]); + }; + + return { + folderUuid, + selectedItems, + itemToPreview, + isFileViewerOpen, + onFolderUuidChanges, + onCloseFileViewer, + onItemClicked, + onSelectedItemsChanged, + onItemSelected, + clearSelectedItems, + }; +}; diff --git a/src/hooks/backups/usePagination.ts b/src/hooks/backups/useBackupsPagination.ts similarity index 85% rename from src/hooks/backups/usePagination.ts rename to src/hooks/backups/useBackupsPagination.ts index 17539d8bf..33f838ed0 100644 --- a/src/hooks/backups/usePagination.ts +++ b/src/hooks/backups/useBackupsPagination.ts @@ -1,19 +1,24 @@ import newStorageService from 'app/drive/services/new-storage.service'; import { DriveItemData } from 'app/drive/types'; import _ from 'lodash'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; const DEFAULT_LIMIT = 50; -export const useBackupsPagination = (folderUuid: string | undefined) => { - const [isLoading, setIsLoading] = useState(true); +export const useBackupsPagination = (folderUuid: string | undefined, clearSelectedItems: () => void) => { + const [areFetchingItems, setAreFetchingItems] = useState(true); const [currentItems, setCurrentItems] = useState([]); const [hasMoreItems, setHasMoreItems] = useState(true); const [offset, setOffset] = useState(0); + useEffect(() => { + getFolderContent(); + }, [folderUuid]); + const getFolderContent = async () => { - setIsLoading(true); + setAreFetchingItems(true); setCurrentItems([]); + clearSelectedItems(); setOffset(0); if (!folderUuid) return; @@ -38,13 +43,13 @@ export const useBackupsPagination = (folderUuid: string | undefined) => { setHasMoreItems(false); } - setIsLoading(false); + setAreFetchingItems(false); }; const getMorePaginatedItems = async () => { if (!folderUuid || !hasMoreItems) return; - setIsLoading(true); + setAreFetchingItems(true); const [folderContentPromise] = newStorageService.getFolderContentByUuid({ folderUuid, @@ -72,15 +77,14 @@ export const useBackupsPagination = (folderUuid: string | undefined) => { setHasMoreItems(false); } - setIsLoading(false); + setAreFetchingItems(false); }; return { currentItems, - isLoading, + areFetchingItems, hasMoreItems, getFolderContent, getMorePaginatedItems, - setIsLoading, }; }; From 37d6595c556291b7fca75ba56565d965c379350f Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:53:36 +0200 Subject: [PATCH 14/24] chore(deps): uppdate testing-library/react version --- jest.config.js | 1 - package.json | 2 +- .../backups/hooks}/useBackupDeviceActions.ts | 0 .../backups/hooks}/useBackupListActions.ts | 0 .../hooks/useBackupsPagination.test.ts | 117 ++++++++++++++++++ .../backups/hooks}/useBackupsPagination.ts | 6 +- .../backups/views/BackupsView/BackupsView.tsx | 6 +- test/unit/services/keys.service.test.ts | 4 +- test/unit/services/pgp.service.test.ts | 4 + yarn.lock | 11 +- 10 files changed, 136 insertions(+), 15 deletions(-) rename src/{hooks/backups => app/backups/hooks}/useBackupDeviceActions.ts (100%) rename src/{hooks/backups => app/backups/hooks}/useBackupListActions.ts (100%) create mode 100644 src/app/backups/hooks/useBackupsPagination.test.ts rename src/{hooks/backups => app/backups/hooks}/useBackupsPagination.ts (95%) diff --git a/jest.config.js b/jest.config.js index 4a42cb24e..35620df8a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,5 +1,4 @@ /*eslint-disable*/ module.exports = { preset: 'ts-jest', - testEnvironment: 'node', }; diff --git a/package.json b/package.json index 6c11754c4..5da2a581b 100644 --- a/package.json +++ b/package.json @@ -130,7 +130,7 @@ "@playwright/test": "^1.44.1", "@testing-library/dom": "^7.26.0", "@testing-library/jest-dom": "^6.1.3", - "@testing-library/react": "^11.1.0", + "@testing-library/react": "^16.0.1", "@testing-library/user-event": "^12.1.10", "@types/analytics-node": "^3.1.3", "@types/async": "^3.2.7", diff --git a/src/hooks/backups/useBackupDeviceActions.ts b/src/app/backups/hooks/useBackupDeviceActions.ts similarity index 100% rename from src/hooks/backups/useBackupDeviceActions.ts rename to src/app/backups/hooks/useBackupDeviceActions.ts diff --git a/src/hooks/backups/useBackupListActions.ts b/src/app/backups/hooks/useBackupListActions.ts similarity index 100% rename from src/hooks/backups/useBackupListActions.ts rename to src/app/backups/hooks/useBackupListActions.ts diff --git a/src/app/backups/hooks/useBackupsPagination.test.ts b/src/app/backups/hooks/useBackupsPagination.test.ts new file mode 100644 index 000000000..8c772a83f --- /dev/null +++ b/src/app/backups/hooks/useBackupsPagination.test.ts @@ -0,0 +1,117 @@ +/** + * @jest-environment jsdom + */ + +import { renderHook, waitFor } from '@testing-library/react'; +import { useBackupsPagination } from './useBackupsPagination'; +import newStorageService from '../../drive/services/new-storage.service'; +import { act } from 'react-dom/test-utils'; +import _ from 'lodash'; + +jest.mock('../../drive/services/new-storage.service', () => ({ + getFolderContentByUuid: jest.fn(), +})); + +describe('useBackupsPagination', () => { + const clearSelectedItems = jest.fn(); + + const FOLDER_CONTENT_1: any = { + files: Array.from({ length: 30 }, (_, i) => ({ plainName: `file-${i + 1}.txt` })), + children: Array.from({ length: 25 }, (_, i) => ({ plainName: `folder-${i + 1}` })), + }; + + const FOLDER_CONTENT_2: any = { + files: Array.from({ length: 20 }, (_, i) => ({ plainName: `file-${i + 31}.txt` })), + children: Array.from({ length: 0 }, () => []), + }; + + const FOLDER_CONTENT_1_LENGTH = _.concat(FOLDER_CONTENT_1.files, FOLDER_CONTENT_1.children).length; + const FOLDER_CONTENT_2_LENGTH = _.concat(FOLDER_CONTENT_2.files, FOLDER_CONTENT_2.children).length; + const TOTAL_LENGTH = FOLDER_CONTENT_1_LENGTH + FOLDER_CONTENT_2_LENGTH; + + it('Should load the first items and contains more items to fetch paginated', async () => { + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + Promise.resolve(FOLDER_CONTENT_1), + { + cancel: jest.fn(), + }, + ]); + + const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); + + await waitFor(() => { + expect(result.current.areFetchingItems).toBe(true); + }); + + expect(newStorageService.getFolderContentByUuid as jest.Mock).toHaveBeenCalledWith({ + folderUuid: 'some-folder-uuid', + limit: 50, + offset: 0, + }); + + await waitFor(() => { + expect(result.current.currentItems.length).toEqual(FOLDER_CONTENT_1_LENGTH); + }); + + await waitFor(() => { + expect(result.current.hasMoreItems).toBe(true); + expect(result.current.areFetchingItems).toBe(false); + }); + }); + + it('Should fetch more items if getMorePaginatedItems is called', async () => { + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + Promise.resolve(FOLDER_CONTENT_1), + { + cancel: jest.fn(), + }, + ]); + + const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); + + await act(async () => { + await result.current.getFolderContent(); + }); + + expect(result.current.currentItems.length).toBe(FOLDER_CONTENT_1.files.length + FOLDER_CONTENT_1.children.length); + + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + Promise.resolve(FOLDER_CONTENT_2), + { + cancel: jest.fn(), + }, + ]); + + await act(async () => { + await result.current.getMorePaginatedItems(); + }); + + await waitFor(() => { + expect(result.current.currentItems.length).toEqual(TOTAL_LENGTH); + }); + }); + + // it('no debería cargar más elementos si no hay más elementos por cargar', async () => { + // const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); + + // expect(result.current.hasMoreItems).toBe(false); + + // await act(async () => { + // await result.current.getMorePaginatedItems(); + // }); + + // expect(result.current.currentItems.length).toBe(3); + // }); + + // it('debería limpiar los elementos seleccionados cuando se cambia folderUuid', async () => { + // const { rerender } = renderHook(({ folderUuid }) => useBackupsPagination(folderUuid, clearSelectedItems), { + // initialProps: { folderUuid: 'folder-1' }, + // }); + + // expect(clearSelectedItems).toHaveBeenCalledTimes(1); + + // rerender({ folderUuid: 'folder-2' }); + + // expect(clearSelectedItems).toHaveBeenCalledTimes(2); + // }); +}); diff --git a/src/hooks/backups/useBackupsPagination.ts b/src/app/backups/hooks/useBackupsPagination.ts similarity index 95% rename from src/hooks/backups/useBackupsPagination.ts rename to src/app/backups/hooks/useBackupsPagination.ts index 33f838ed0..e34649fe9 100644 --- a/src/hooks/backups/useBackupsPagination.ts +++ b/src/app/backups/hooks/useBackupsPagination.ts @@ -1,7 +1,7 @@ -import newStorageService from 'app/drive/services/new-storage.service'; -import { DriveItemData } from 'app/drive/types'; -import _ from 'lodash'; import { useEffect, useState } from 'react'; +import _ from 'lodash'; +import newStorageService from '../../drive/services/new-storage.service'; +import { DriveItemData } from '../../drive/types'; const DEFAULT_LIMIT = 50; diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index 0e2e7cf92..fa7377563 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -9,7 +9,6 @@ import Dialog from '../../../shared/components/Dialog/Dialog'; import BackupsAsFoldersList from '../../components/BackupsAsFoldersList/BackupsAsFoldersList'; import DeviceList from '../../components/DeviceList/DeviceList'; import FileViewerWrapper from '../../../drive/components/FileViewer/FileViewerWrapper'; -import { useBackupsPagination } from 'hooks/backups/useBackupsPagination'; import { downloadItemsThunk } from '../../../store/slices/storage/storage.thunks/downloadItemsThunk'; import { deleteBackupDeviceAsFolder } from '../../../drive/services/folder.service'; import { deleteFile } from '../../../drive/services/file.service'; @@ -18,8 +17,9 @@ import { ListItemMenu } from '../../../shared/components/List/ListItem'; import { DriveFolderData as DriveWebFolderData, DriveItemData } from '../../../drive/types'; import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu'; -import { useBackupListActions } from 'hooks/backups/useBackupListActions'; -import { useBackupDeviceActions } from 'hooks/backups/useBackupDeviceActions'; +import { useBackupListActions } from 'app/backups/hooks/useBackupListActions'; +import { useBackupDeviceActions } from 'app/backups/hooks/useBackupDeviceActions'; +import { useBackupsPagination } from 'app/backups/hooks/useBackupsPagination'; export default function BackupsView(): JSX.Element { const { translate } = useTranslationContext(); diff --git a/test/unit/services/keys.service.test.ts b/test/unit/services/keys.service.test.ts index 44455f5c8..c73ea1fe4 100644 --- a/test/unit/services/keys.service.test.ts +++ b/test/unit/services/keys.service.test.ts @@ -1,4 +1,6 @@ -import { aes } from '@internxt/lib'; +/** + * @jest-environment node + */ import { isValid } from '../../../src/app/crypto/services/utilspgp'; import { generateNewKeys } from '../../../src/app/crypto/services/pgp.service'; diff --git a/test/unit/services/pgp.service.test.ts b/test/unit/services/pgp.service.test.ts index ecbd16045..4b30b9697 100644 --- a/test/unit/services/pgp.service.test.ts +++ b/test/unit/services/pgp.service.test.ts @@ -1,3 +1,7 @@ +/** + * @jest-environment node + */ + import { generateNewKeys, encryptMessageWithPublicKey, diff --git a/yarn.lock b/yarn.lock index 41ad02e70..531dd945c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2350,7 +2350,7 @@ dependencies: tslib "^2.4.0" -"@testing-library/dom@^7.26.0", "@testing-library/dom@^7.28.1": +"@testing-library/dom@^7.26.0": version "7.31.2" resolved "https://registry.yarnpkg.com/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ== @@ -2378,13 +2378,12 @@ lodash "^4.17.15" redent "^3.0.0" -"@testing-library/react@^11.1.0": - version "11.2.7" - resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818" - integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA== +"@testing-library/react@^16.0.1": + version "16.0.1" + resolved "https://registry.yarnpkg.com/@testing-library/react/-/react-16.0.1.tgz#29c0ee878d672703f5e7579f239005e4e0faa875" + integrity sha512-dSmwJVtJXmku+iocRhWOUFbrERC76TX2Mnf0ATODz8brzAZrMBbzLwQixlBSanZxR6LddK3eiwpSFZgDET1URg== dependencies: "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^7.28.1" "@testing-library/user-event@^12.1.10": version "12.8.3" From c5e2e3325fb307d97dd07fc3c0ec347107d72309 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:24:07 +0200 Subject: [PATCH 15/24] tests: fixing useBackupPagination test --- .../hooks/useBackupsPagination.test.ts | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/app/backups/hooks/useBackupsPagination.test.ts b/src/app/backups/hooks/useBackupsPagination.test.ts index 8c772a83f..1657c7ffd 100644 --- a/src/app/backups/hooks/useBackupsPagination.test.ts +++ b/src/app/backups/hooks/useBackupsPagination.test.ts @@ -29,6 +29,10 @@ describe('useBackupsPagination', () => { const FOLDER_CONTENT_2_LENGTH = _.concat(FOLDER_CONTENT_2.files, FOLDER_CONTENT_2.children).length; const TOTAL_LENGTH = FOLDER_CONTENT_1_LENGTH + FOLDER_CONTENT_2_LENGTH; + beforeEach(() => { + jest.clearAllMocks(); + }); + it('Should load the first items and contains more items to fetch paginated', async () => { jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ Promise.resolve(FOLDER_CONTENT_1), @@ -73,7 +77,7 @@ describe('useBackupsPagination', () => { await result.current.getFolderContent(); }); - expect(result.current.currentItems.length).toBe(FOLDER_CONTENT_1.files.length + FOLDER_CONTENT_1.children.length); + expect(result.current.currentItems.length).toBe(FOLDER_CONTENT_1_LENGTH); jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ Promise.resolve(FOLDER_CONTENT_2), @@ -91,27 +95,32 @@ describe('useBackupsPagination', () => { }); }); - // it('no debería cargar más elementos si no hay más elementos por cargar', async () => { - // const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); - - // expect(result.current.hasMoreItems).toBe(false); + it('Should not fetch more items as there are less than 50 items in total', async () => { + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + Promise.resolve(FOLDER_CONTENT_2), + { + cancel: jest.fn(), + }, + ]); - // await act(async () => { - // await result.current.getMorePaginatedItems(); - // }); + const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); - // expect(result.current.currentItems.length).toBe(3); - // }); + await waitFor(() => { + expect(result.current.hasMoreItems).toBe(false); + }); + }); - // it('debería limpiar los elementos seleccionados cuando se cambia folderUuid', async () => { - // const { rerender } = renderHook(({ folderUuid }) => useBackupsPagination(folderUuid, clearSelectedItems), { - // initialProps: { folderUuid: 'folder-1' }, - // }); + it('should clean all items when navigating to a subfolder', async () => { + const { rerender } = renderHook(({ folderUuid }) => useBackupsPagination(folderUuid, clearSelectedItems), { + initialProps: FOLDER_CONTENT_2, + }); - // expect(clearSelectedItems).toHaveBeenCalledTimes(1); + expect(clearSelectedItems).toHaveBeenCalledTimes(1); - // rerender({ folderUuid: 'folder-2' }); + await act(async () => { + rerender({ folderUuid: 'folder-2' }); + }); - // expect(clearSelectedItems).toHaveBeenCalledTimes(2); - // }); + expect(clearSelectedItems).toHaveBeenCalledTimes(2); + }); }); From 54a5211f657783a300e6502ca26608d3a94a6710 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:26:54 +0200 Subject: [PATCH 16/24] tests: changes in useBackupPagination --- .../hooks/useBackupsPagination.test.ts | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/app/backups/hooks/useBackupsPagination.test.ts b/src/app/backups/hooks/useBackupsPagination.test.ts index 1657c7ffd..3cd1a0364 100644 --- a/src/app/backups/hooks/useBackupsPagination.test.ts +++ b/src/app/backups/hooks/useBackupsPagination.test.ts @@ -33,6 +33,21 @@ describe('useBackupsPagination', () => { jest.clearAllMocks(); }); + it('Should not fetch more items as there are less than 50 items in total', async () => { + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + Promise.resolve(FOLDER_CONTENT_2), + { + cancel: jest.fn(), + }, + ]); + + const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); + + await waitFor(() => { + expect(result.current.hasMoreItems).toBe(false); + }); + }); + it('Should load the first items and contains more items to fetch paginated', async () => { jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ Promise.resolve(FOLDER_CONTENT_1), @@ -63,7 +78,7 @@ describe('useBackupsPagination', () => { }); }); - it('Should fetch more items if getMorePaginatedItems is called', async () => { + it('Should fetch more items if there are more than 50 items', async () => { jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ Promise.resolve(FOLDER_CONTENT_1), { @@ -77,7 +92,7 @@ describe('useBackupsPagination', () => { await result.current.getFolderContent(); }); - expect(result.current.currentItems.length).toBe(FOLDER_CONTENT_1_LENGTH); + expect(result.current.currentItems.length > 50).toBeTruthy(); jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ Promise.resolve(FOLDER_CONTENT_2), @@ -91,22 +106,7 @@ describe('useBackupsPagination', () => { }); await waitFor(() => { - expect(result.current.currentItems.length).toEqual(TOTAL_LENGTH); - }); - }); - - it('Should not fetch more items as there are less than 50 items in total', async () => { - jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ - Promise.resolve(FOLDER_CONTENT_2), - { - cancel: jest.fn(), - }, - ]); - - const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); - - await waitFor(() => { - expect(result.current.hasMoreItems).toBe(false); + expect(result.current.currentItems.length < 50).toBeFalsy(); }); }); From fc6157875d1d5e853450eea3847a172cf11ffa45 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:39:47 +0200 Subject: [PATCH 17/24] fix: keys for skin skeleton and removing unused variables --- .../BackupsAsFoldersList/BackupsAsFoldersList.tsx | 4 ++-- src/app/backups/components/DeviceList/DeviceList.tsx | 9 ++------- src/app/backups/hooks/useBackupListActions.ts | 1 + src/app/backups/hooks/useBackupsPagination.test.ts | 1 - src/app/shared/Skeleton/index.tsx | 6 +++--- 5 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index 2c3e40ed0..a9d53fb29 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -73,8 +73,8 @@ export default function BackupsAsFoldersList({
-
-
diff --git a/src/app/backups/components/DeviceList/DeviceList.tsx b/src/app/backups/components/DeviceList/DeviceList.tsx index f205818ab..a39b2f3a1 100644 --- a/src/app/backups/components/DeviceList/DeviceList.tsx +++ b/src/app/backups/components/DeviceList/DeviceList.tsx @@ -1,6 +1,5 @@ import desktopService from '../../../core/services/desktop.service'; import { Device } from '../../types'; -import DriveListItemSkeleton from '../../../drive/components/DriveListItemSkeleton/DriveListItemSkeleton'; import folderEmptyImage from 'assets/icons/light/folder-backup.svg'; import { DownloadSimple } from '@phosphor-icons/react'; @@ -16,6 +15,7 @@ import UilDesktop from '@iconscout/react-unicons/icons/uil-desktop'; import dateService from '../../../core/services/date.service'; import sizeService from '../../../drive/services/size.service'; import { DriveFolderData } from '@internxt/sdk/dist/drive/storage/types'; +import { skinSkeleton } from 'app/shared/Skeleton'; interface Props { items: (Device | DriveFolderData)[]; @@ -35,11 +35,6 @@ const DeviceList = (props: Props): JSX.Element => { const download = await desktopService.getDownloadAppUrl(); return download; }; - const getLoadingSkeleton = () => { - return Array(20) - .fill(0) - .map((n, i) => ); - }; return (
@@ -116,7 +111,7 @@ const DeviceList = (props: Props): JSX.Element => { onDeviceSelected([...unselectedDevices, { device: item, isSelected: true }]); }} onDoubleClick={onDeviceClicked} - skinSkeleton={getLoadingSkeleton()} + skinSkeleton={skinSkeleton} emptyState={ } diff --git a/src/app/backups/hooks/useBackupListActions.ts b/src/app/backups/hooks/useBackupListActions.ts index 84d5c3ad8..0a36ef424 100644 --- a/src/app/backups/hooks/useBackupListActions.ts +++ b/src/app/backups/hooks/useBackupListActions.ts @@ -25,6 +25,7 @@ export const useBackupListActions = ( const onItemClicked = (item: DriveItemData) => { if (item.isFolder) { + clearSelectedItems(); onBreadcrumbFolderChanges((current) => [...current, item]); dispatch(backupsActions.setCurrentFolder(item)); setFolderUuid(item.uuid); diff --git a/src/app/backups/hooks/useBackupsPagination.test.ts b/src/app/backups/hooks/useBackupsPagination.test.ts index 3cd1a0364..6f8ba1195 100644 --- a/src/app/backups/hooks/useBackupsPagination.test.ts +++ b/src/app/backups/hooks/useBackupsPagination.test.ts @@ -27,7 +27,6 @@ describe('useBackupsPagination', () => { const FOLDER_CONTENT_1_LENGTH = _.concat(FOLDER_CONTENT_1.files, FOLDER_CONTENT_1.children).length; const FOLDER_CONTENT_2_LENGTH = _.concat(FOLDER_CONTENT_2.files, FOLDER_CONTENT_2.children).length; - const TOTAL_LENGTH = FOLDER_CONTENT_1_LENGTH + FOLDER_CONTENT_2_LENGTH; beforeEach(() => { jest.clearAllMocks(); diff --git a/src/app/shared/Skeleton/index.tsx b/src/app/shared/Skeleton/index.tsx index f364c15b1..76110e6c7 100644 --- a/src/app/shared/Skeleton/index.tsx +++ b/src/app/shared/Skeleton/index.tsx @@ -3,7 +3,7 @@ export const skinSkeleton = [
, -
, -
, -
, +
, +
, +
, ]; From d8f45d74c48fb674ecffa5564f4065c93b9236cc Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:42:04 +0200 Subject: [PATCH 18/24] fix: remove unused constant --- src/app/backups/hooks/useBackupsPagination.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/backups/hooks/useBackupsPagination.test.ts b/src/app/backups/hooks/useBackupsPagination.test.ts index 6f8ba1195..ca024e526 100644 --- a/src/app/backups/hooks/useBackupsPagination.test.ts +++ b/src/app/backups/hooks/useBackupsPagination.test.ts @@ -26,7 +26,6 @@ describe('useBackupsPagination', () => { }; const FOLDER_CONTENT_1_LENGTH = _.concat(FOLDER_CONTENT_1.files, FOLDER_CONTENT_1.children).length; - const FOLDER_CONTENT_2_LENGTH = _.concat(FOLDER_CONTENT_2.files, FOLDER_CONTENT_2.children).length; beforeEach(() => { jest.clearAllMocks(); From 6d519712300088df362dea5e3a862c2bf55eb1d9 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Tue, 24 Sep 2024 18:28:50 +0200 Subject: [PATCH 19/24] fix: on item name clicked --- .../BackupsAsFoldersList/BackupsAsFoldersList.tsx | 12 +++++++++--- src/app/backups/hooks/useBackupListActions.ts | 1 - 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx index a9d53fb29..92744b7dc 100644 --- a/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx +++ b/src/app/backups/components/BackupsAsFoldersList/BackupsAsFoldersList.tsx @@ -69,12 +69,18 @@ export default function BackupsAsFoldersList({ const Icon = iconService.getItemIcon(item.isFolder, item.type); return ( -
+
-
-
diff --git a/src/app/backups/hooks/useBackupListActions.ts b/src/app/backups/hooks/useBackupListActions.ts index 0a36ef424..84d5c3ad8 100644 --- a/src/app/backups/hooks/useBackupListActions.ts +++ b/src/app/backups/hooks/useBackupListActions.ts @@ -25,7 +25,6 @@ export const useBackupListActions = ( const onItemClicked = (item: DriveItemData) => { if (item.isFolder) { - clearSelectedItems(); onBreadcrumbFolderChanges((current) => [...current, item]); dispatch(backupsActions.setCurrentFolder(item)); setFolderUuid(item.uuid); From d339fd1d76cc28f2cbdbe7569064b7c0c18a69e2 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Wed, 25 Sep 2024 09:09:23 +0200 Subject: [PATCH 20/24] fix: grammatical issues --- src/app/drive/components/ShareDialog/ShareDialog.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index 211da12d8..2f02157e4 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -98,7 +98,7 @@ type ShareDialogProps = { onCloseDialog?: () => void; }; -const isAdvanchedShareItem = (item: DriveItemData | AdvancedSharedItem): item is AdvancedSharedItem => { +const isAdvancedShareItem = (item: DriveItemData | AdvancedSharedItem): item is AdvancedSharedItem => { return item['encryptionKey']; }; @@ -251,7 +251,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { const itemId = itemToShare?.item.uuid ?? ''; const isItemNotSharedYet = - !isAdvanchedShareItem(itemToShare?.item) && !itemToShare.item.sharings?.length && !sharingMeta; + !isAdvancedShareItem(itemToShare?.item) && !itemToShare.item.sharings?.length && !sharingMeta; if (!isItemNotSharedYet) { try { @@ -343,7 +343,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { }; trackPublicShared(trackingPublicSharedProperties); - const encryptionKey = isAdvanchedShareItem(itemToShare.item) ? itemToShare?.item?.encryptionKey : undefined; + const encryptionKey = isAdvancedShareItem(itemToShare.item) ? itemToShare?.item?.encryptionKey : undefined; const sharingInfo = await shareService.getPublicShareLink( itemToShare?.item.uuid, itemToShare.item.isFolder ? 'folder' : 'file', From 0542c02f0e3d1821bbce90c4fbf2b061e8ebd42e Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:35:26 +0200 Subject: [PATCH 21/24] fix: remove deleted items from the list --- .../hooks/useBackupsPagination.test.ts | 97 ++++++++++--- src/app/backups/hooks/useBackupsPagination.ts | 130 +++++++++--------- .../backups/views/BackupsView/BackupsView.tsx | 61 ++++++-- .../FileViewer/FileViewerWrapper.tsx | 4 +- src/app/i18n/locales/de.json | 4 +- src/app/i18n/locales/en.json | 4 +- src/app/i18n/locales/es.json | 4 +- src/app/i18n/locales/fr.json | 4 +- src/app/i18n/locales/it.json | 4 +- src/app/i18n/locales/ru.json | 4 +- src/app/i18n/locales/tw.json | 4 +- src/app/i18n/locales/zh.json | 4 +- 12 files changed, 219 insertions(+), 105 deletions(-) diff --git a/src/app/backups/hooks/useBackupsPagination.test.ts b/src/app/backups/hooks/useBackupsPagination.test.ts index ca024e526..6394c9393 100644 --- a/src/app/backups/hooks/useBackupsPagination.test.ts +++ b/src/app/backups/hooks/useBackupsPagination.test.ts @@ -7,25 +7,45 @@ import { useBackupsPagination } from './useBackupsPagination'; import newStorageService from '../../drive/services/new-storage.service'; import { act } from 'react-dom/test-utils'; import _ from 'lodash'; - +import { FetchFolderContentResponse } from '@internxt/sdk/dist/drive/storage/types'; +import { DriveItemData } from '../../drive/types'; +import notificationsService, { ToastType } from '../../notifications/services/notifications.service'; + +jest.mock('../../notifications/services/notifications.service', () => ({ + show: jest.fn(), + ToastType: { + Error: 'ERROR', + }, +})); jest.mock('../../drive/services/new-storage.service', () => ({ getFolderContentByUuid: jest.fn(), })); +jest.mock('../../core/services/error.service', () => ({ + reportError: jest.fn(), +})); + describe('useBackupsPagination', () => { const clearSelectedItems = jest.fn(); - const FOLDER_CONTENT_1: any = { + const FOLDER_CONTENT_1 = { files: Array.from({ length: 30 }, (_, i) => ({ plainName: `file-${i + 1}.txt` })), children: Array.from({ length: 25 }, (_, i) => ({ plainName: `folder-${i + 1}` })), - }; + } as unknown as FetchFolderContentResponse; - const FOLDER_CONTENT_2: any = { + const FOLDER_CONTENT_2 = { files: Array.from({ length: 20 }, (_, i) => ({ plainName: `file-${i + 31}.txt` })), - children: Array.from({ length: 0 }, () => []), - }; + children: Array.from({ length: 0 }, (_, i) => []), + } as unknown as FetchFolderContentResponse; + + const LIMIT_OF_ITEMS_FETCHED = 50; - const FOLDER_CONTENT_1_LENGTH = _.concat(FOLDER_CONTENT_1.files, FOLDER_CONTENT_1.children).length; + const FOLDER_CONTENT_1_LENGTH = _.concat( + FOLDER_CONTENT_1.files as DriveItemData[], + FOLDER_CONTENT_1.children as DriveItemData[], + ).length; + const FOLDER_CONTENT_2_LENGTH = FOLDER_CONTENT_2.files.length; + const TOTAL_LENGTH = FOLDER_CONTENT_1_LENGTH + FOLDER_CONTENT_2_LENGTH; beforeEach(() => { jest.clearAllMocks(); @@ -77,7 +97,7 @@ describe('useBackupsPagination', () => { }); it('Should fetch more items if there are more than 50 items', async () => { - jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValueOnce([ Promise.resolve(FOLDER_CONTENT_1), { cancel: jest.fn(), @@ -87,12 +107,14 @@ describe('useBackupsPagination', () => { const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); await act(async () => { - await result.current.getFolderContent(); + await result.current.getFolderContent(true); }); - expect(result.current.currentItems.length > 50).toBeTruthy(); + await waitFor(() => { + expect(result.current.currentItems.length > LIMIT_OF_ITEMS_FETCHED).toBeTruthy(); + }); - jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValue([ + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValueOnce([ Promise.resolve(FOLDER_CONTENT_2), { cancel: jest.fn(), @@ -104,21 +126,64 @@ describe('useBackupsPagination', () => { }); await waitFor(() => { - expect(result.current.currentItems.length < 50).toBeFalsy(); + expect(result.current.currentItems.length === TOTAL_LENGTH).toBeTruthy(); }); }); - it('should clean all items when navigating to a subfolder', async () => { - const { rerender } = renderHook(({ folderUuid }) => useBackupsPagination(folderUuid, clearSelectedItems), { - initialProps: FOLDER_CONTENT_2, + it('should run the clean function when navigating to a subfolder', async () => { + const { rerender } = renderHook((folderUuid) => useBackupsPagination(folderUuid, clearSelectedItems), { + initialProps: 'folder-1', }); expect(clearSelectedItems).toHaveBeenCalledTimes(1); await act(async () => { - rerender({ folderUuid: 'folder-2' }); + rerender('folder-2'); }); expect(clearSelectedItems).toHaveBeenCalledTimes(2); }); + + it('should update current items list by removing an item', async () => { + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValueOnce([ + Promise.resolve(FOLDER_CONTENT_1), + { + cancel: jest.fn(), + }, + ]); + + const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); + + const updatedItems = result.current.currentItems.filter((item) => item.id !== 2); + + act(() => { + result.current.updateCurrentItemsList(updatedItems); + }); + + await waitFor(() => { + expect(result.current.currentItems).toEqual(updatedItems); + expect(result.current.currentItems).not.toContainEqual({ id: 2 }); + }); + }); + + it('should show a notification when there is an error fetching items', async () => { + jest.spyOn(newStorageService, 'getFolderContentByUuid').mockReturnValueOnce([ + Promise.reject(new Error('Error fetching items')), + { + cancel: jest.fn(), + }, + ]); + + const { result } = renderHook(() => useBackupsPagination('some-folder-uuid', clearSelectedItems)); + + await act(async () => { + await result.current.getFolderContent(true); + }); + + await waitFor(() => { + expect(notificationsService.show).toHaveBeenCalledWith({ + type: ToastType.Error, + }); + }); + }); }); diff --git a/src/app/backups/hooks/useBackupsPagination.ts b/src/app/backups/hooks/useBackupsPagination.ts index e34649fe9..3a24fd112 100644 --- a/src/app/backups/hooks/useBackupsPagination.ts +++ b/src/app/backups/hooks/useBackupsPagination.ts @@ -1,7 +1,10 @@ -import { useEffect, useState } from 'react'; +import { useEffect, useState, useCallback } from 'react'; import _ from 'lodash'; +import { t } from 'i18next'; import newStorageService from '../../drive/services/new-storage.service'; import { DriveItemData } from '../../drive/types'; +import errorService from '../../core/services/error.service'; +import notificationsService, { ToastType } from '../../notifications/services/notifications.service'; const DEFAULT_LIMIT = 50; @@ -12,79 +15,80 @@ export const useBackupsPagination = (folderUuid: string | undefined, clearSelect const [offset, setOffset] = useState(0); useEffect(() => { - getFolderContent(); - }, [folderUuid]); - - const getFolderContent = async () => { - setAreFetchingItems(true); - setCurrentItems([]); - clearSelectedItems(); - setOffset(0); - - if (!folderUuid) return; - - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ - folderUuid, - limit: DEFAULT_LIMIT, - offset: 0, - }); - - const response = await folderContentPromise; - const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); - const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); - const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - - setCurrentItems(items); - - if (items.length >= DEFAULT_LIMIT) { - setHasMoreItems(true); - setOffset(DEFAULT_LIMIT); - } else { - setHasMoreItems(false); + if (folderUuid) { + clearSelectedItems(); + setCurrentItems([]); + setOffset(0); + fetchItems(true); } + }, [folderUuid]); - setAreFetchingItems(false); - }; - - const getMorePaginatedItems = async () => { - if (!folderUuid || !hasMoreItems) return; - - setAreFetchingItems(true); - - const [folderContentPromise] = newStorageService.getFolderContentByUuid({ - folderUuid, - limit: DEFAULT_LIMIT, - offset, + const handleError = useCallback((error: Error) => { + errorService.reportError(error.message); + notificationsService.show({ + type: ToastType.Error, + text: t('notificationMessages.errorWhileFetchingMoreItems'), }); - - const folderContentResponse = await folderContentPromise; - const files = folderContentResponse.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); - const folders = folderContentResponse.children.map((folder) => ({ - ...folder, - isFolder: true, - name: folder.plainName, - })); - const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); - - const totalCurrentItems = _.concat(currentItems, items); - - setCurrentItems(totalCurrentItems); - - if (items.length >= DEFAULT_LIMIT) { - setHasMoreItems(true); - setOffset((prevOffset) => prevOffset + DEFAULT_LIMIT); - } else { - setHasMoreItems(false); + setHasMoreItems(false); + }, []); + + const fetchItems = useCallback( + async (isFirstLoad: boolean) => { + if (!folderUuid) return; + + setAreFetchingItems(true); + + const currentOffset = isFirstLoad ? 0 : offset; + + try { + const [folderContentPromise] = newStorageService.getFolderContentByUuid({ + folderUuid, + limit: DEFAULT_LIMIT, + offset: currentOffset, + }); + + const response = await folderContentPromise; + const files = response.files.map((file) => ({ ...file, isFolder: false, name: file.plainName })); + const folders = response.children.map((folder) => ({ ...folder, isFolder: true, name: folder.plainName })); + const items = _.concat(folders as DriveItemData[], files as DriveItemData[]); + const thereAreMoreItems = items.length >= DEFAULT_LIMIT; + + setCurrentItems((prevItems) => { + const totalItems = isFirstLoad ? items : _.concat(prevItems, items); + return totalItems; + }); + + if (thereAreMoreItems) { + setHasMoreItems(true); + setOffset((prevOffset) => prevOffset + DEFAULT_LIMIT); + } else { + setHasMoreItems(false); + } + } catch (err) { + handleError(err as Error); + } finally { + setAreFetchingItems(false); + } + }, + [folderUuid, offset, handleError], + ); + + const getMorePaginatedItems = useCallback(async () => { + if (hasMoreItems && !areFetchingItems) { + await fetchItems(false); } + }, [hasMoreItems, areFetchingItems, fetchItems]); - setAreFetchingItems(false); + const updateCurrentItemsList = (newItemsList: DriveItemData[]) => { + setCurrentItems(newItemsList); }; return { currentItems, areFetchingItems, hasMoreItems, - getFolderContent, + getFolderContent: fetchItems, getMorePaginatedItems, + updateCurrentItemsList, }; }; diff --git a/src/app/backups/views/BackupsView/BackupsView.tsx b/src/app/backups/views/BackupsView/BackupsView.tsx index fa7377563..57b5c3691 100644 --- a/src/app/backups/views/BackupsView/BackupsView.tsx +++ b/src/app/backups/views/BackupsView/BackupsView.tsx @@ -20,6 +20,8 @@ import { contextMenuSelectedBackupItems } from '../../../drive/components/DriveE import { useBackupListActions } from 'app/backups/hooks/useBackupListActions'; import { useBackupDeviceActions } from 'app/backups/hooks/useBackupDeviceActions'; import { useBackupsPagination } from 'app/backups/hooks/useBackupsPagination'; +import errorService from 'app/core/services/error.service'; +import notificationsService, { ToastType } from 'app/notifications/services/notifications.service'; export default function BackupsView(): JSX.Element { const { translate } = useTranslationContext(); @@ -54,36 +56,65 @@ export default function BackupsView(): JSX.Element { onCloseDeleteModal, } = useBackupDeviceActions(onFolderUuidChanges, setFoldersInBreadcrumbs, dispatch); - const { currentItems, areFetchingItems, hasMoreItems, getMorePaginatedItems, getFolderContent } = + const { currentItems, areFetchingItems, hasMoreItems, getMorePaginatedItems, updateCurrentItemsList } = useBackupsPagination(folderUuid, clearSelectedItems); const onDownloadSelectedItems = () => { dispatch(downloadItemsThunk(selectedItems)); }; - async function onDeleteSelectedItems() { - for (const item of selectedItems) { - if (item.isFolder) { - await deleteBackupDeviceAsFolder(item as DriveWebFolderData); - } else { - await deleteFile(item); - } + const onDownloadFileFormFileViewer = () => { + if (itemToPreview && isFileViewerOpen) dispatch(downloadItemsThunk([itemToPreview as DriveItemData])); + }; + + const onDeleteSelectedItems = async () => { + const selectedItemsIDs = new Set(selectedItems.map((item) => item.id)); + const filteredCurrentItems = currentItems.filter((item) => !selectedItemsIDs.has(item.id)); + try { + const deletePromises = selectedItems.map((item) => + item.isFolder ? deleteBackupDeviceAsFolder(item as DriveWebFolderData) : deleteFile(item), + ); + await Promise.all(deletePromises); + dispatch(deleteItemsThunk(selectedItems)); + clearSelectedItems(); + updateCurrentItemsList(filteredCurrentItems); + } catch (error) { + errorService.reportError(error); + notificationsService.show({ + text: translate('notificationMessages.errorDeletingItems'), + type: ToastType.Error, + }); } - dispatch(deleteItemsThunk(selectedItems)); + }; - if (isFileViewerOpen) { + const onDeleteFileItemFromFilePreview = async () => { + try { + if (isFileViewerOpen && itemToPreview) { + await deleteFile(itemToPreview as DriveItemData); + dispatch(deleteItemsThunk([itemToPreview as DriveItemData])); + } + clearSelectedItems(); + updateCurrentItemsList([itemToPreview] as DriveItemData[]); onCloseFileViewer(); + } catch (error) { + errorService.reportError(error); + notificationsService.show({ + text: translate('notificationMessages.errorDeletingItems'), + type: ToastType.Error, + }); } - - onSelectedItemsChanged([]); - await getFolderContent(); - } + }; const contextMenu: ListItemMenu = contextMenuSelectedBackupItems({ onDownloadSelectedItems, onDeleteSelectedItems: onDeleteSelectedItems, }); + const contextMenuForFileViewer: ListItemMenu = contextMenuSelectedBackupItems({ + onDownloadSelectedItems: onDownloadFileFormFileViewer, + onDeleteSelectedItems: onDeleteFileItemFromFilePreview, + }); + let body; if (!currentDevice) { @@ -141,7 +172,7 @@ export default function BackupsView(): JSX.Element { onClose={onCloseFileViewer} showPreview={isFileViewerOpen} folderItems={currentItems} - contextMenu={contextMenu} + contextMenu={contextMenuForFileViewer} /> )}
diff --git a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx index ef09446bc..147409ea4 100644 --- a/src/app/drive/components/FileViewer/FileViewerWrapper.tsx +++ b/src/app/drive/components/FileViewer/FileViewerWrapper.tsx @@ -163,9 +163,7 @@ const FileViewerWrapper = ({ return []; }, [folderFiles]); - const folderItemsFiltered = folderItems - ? folderItems.filter((item) => !item.isFolder || item.type !== 'folder') - : undefined; + const folderItemsFiltered = folderItems?.filter((item) => !item.isFolder || item.type !== 'folder'); const currentFolder = folderItemsFiltered ?? sortFolderFiles; const fileIndex = currentFolder?.findIndex((item) => item.id === currentFile?.id); const totalFolderIndex = currentFolder?.length; diff --git a/src/app/i18n/locales/de.json b/src/app/i18n/locales/de.json index ee358800f..b59bfcba4 100644 --- a/src/app/i18n/locales/de.json +++ b/src/app/i18n/locales/de.json @@ -1241,7 +1241,9 @@ "errorCreatingSubscription": "Beim Erstellen Ihres Abonnements ist ein Fehler aufgetreten", "errorPurchaseBusinessPlan": "Sie haben bereits einen Geschäftstarif.", "couponIsNotValidForUserError": "Der Gutschein kann nur von neuen Kunden eingelöst werden.", - "errorApplyingCoupon": "Beim Anwenden des Coupons ist ein Fehler aufgetreten" + "errorApplyingCoupon": "Beim Anwenden des Coupons ist ein Fehler aufgetreten", + "errorWhileFetchingMoreItems": "Beim Abrufen weiterer Elemente ist ein Fehler aufgetreten", + "errorDeletingItems": "Beim Löschen dieses Elements ist ein Fehler aufgetreten" }, "actions": { "undo": "Rückgängig machen", diff --git a/src/app/i18n/locales/en.json b/src/app/i18n/locales/en.json index 9279d84f1..85913b031 100644 --- a/src/app/i18n/locales/en.json +++ b/src/app/i18n/locales/en.json @@ -1297,7 +1297,9 @@ "alreadyAppliedCoupon": "The coupon has been already applied", "errorPurchaseBusinessPlan": "You already have a business plan.", "couponIsNotValidForUserError": "Coupon can only be redeemed by new customers", - "errorApplyingCoupon": "Something went wrong while applying the coupon" + "errorApplyingCoupon": "Something went wrong while applying the coupon", + "errorWhileFetchingMoreItems": "Something went wrong while fetching more items", + "errorDeletingItems": "Something went wrong while deleting this item" }, "actions": { "undo": "Undo", diff --git a/src/app/i18n/locales/es.json b/src/app/i18n/locales/es.json index 5f5584099..18f55a0a9 100644 --- a/src/app/i18n/locales/es.json +++ b/src/app/i18n/locales/es.json @@ -1261,7 +1261,9 @@ "alreadyAppliedCoupon": "El cupón ya se ha aplicado", "errorPurchaseBusinessPlan": "Ya tienes un plan para empresas", "couponIsNotValidForUserError": "El cupón solo puede ser canjeado por nuevos clientes.", - "errorApplyingCoupon": "Ocurrió un error al aplicar el cupón" + "errorApplyingCoupon": "Ocurrió un error al aplicar el cupón", + "errorWhileFetchingMoreItems": "Algo salió mal al obtener más elementos", + "errorDeletingItems": "Algo salió mal al eliminar este elemento" }, "success": { "passwordChanged": "Contraseña actualizada correctamente", diff --git a/src/app/i18n/locales/fr.json b/src/app/i18n/locales/fr.json index 95957ad6a..28c364e86 100644 --- a/src/app/i18n/locales/fr.json +++ b/src/app/i18n/locales/fr.json @@ -1225,7 +1225,9 @@ "errorCancelSubscription": "Quelque chose s'est mal passé lors de l'annulation de votre abonnement", "errorPurchaseBusinessPlan": "Vous avez déjà un plan d'affaires.", "couponIsNotValidForUserError": "Le coupon ne peut être utilisé que par de nouveaux clients.", - "errorApplyingCoupon": "Une erreur s'est produite lors de l'application du coupon" + "errorApplyingCoupon": "Une erreur s'est produite lors de l'application du coupon", + "errorWhileFetchingMoreItems": "Une erreur est survenue lors de la récupération d'éléments supplémentaires", + "errorDeletingItems": "Une erreur est survenue lors de la suppression de cet élément" }, "actions": { "undo": "Annuler", diff --git a/src/app/i18n/locales/it.json b/src/app/i18n/locales/it.json index 43796cb43..3e650905e 100644 --- a/src/app/i18n/locales/it.json +++ b/src/app/i18n/locales/it.json @@ -1285,7 +1285,9 @@ "errorCreatingSubscription": "Si è verificato un errore durante la creazione del tuo abbonamento", "errorPurchaseBusinessPlan": "Hai già un piano aziendale.", "couponIsNotValidForUserError": "Il coupon può essere riscattato solo da nuovi clienti.", - "errorApplyingCoupon": "Si è verificato un errore durante l'applicazione del coupon" + "errorApplyingCoupon": "Si è verificato un errore durante l'applicazione del coupon", + "errorWhileFetchingMoreItems": "Qualcosa è andato storto durante il recupero di ulteriori elementi", + "errorDeletingItems": "Qualcosa è andato storto durante l'eliminazione di questo elemento" }, "actions": { "undo": "Annulla", diff --git a/src/app/i18n/locales/ru.json b/src/app/i18n/locales/ru.json index 0e385e0d1..8fafd728e 100644 --- a/src/app/i18n/locales/ru.json +++ b/src/app/i18n/locales/ru.json @@ -1265,7 +1265,9 @@ "errorCreatingSubscription": "Что-то пошло не так при создании вашей подписки", "errorPurchaseBusinessPlan": "У вас уже есть бизнес-тариф.", "couponIsNotValidForUserError": "Купон может быть использован только новыми клиентами.", - "errorApplyingCoupon": "Произошла ошибка при применении купона" + "errorApplyingCoupon": "Произошла ошибка при применении купона", + "errorWhileFetchingMoreItems": "Произошла ошибка при получении дополнительных элементов", + "errorDeletingItems": "Произошла ошибка при удалении этого элемента" }, "actions": { "undo": "Отменить", diff --git a/src/app/i18n/locales/tw.json b/src/app/i18n/locales/tw.json index 3ad3daeed..2aee36aea 100644 --- a/src/app/i18n/locales/tw.json +++ b/src/app/i18n/locales/tw.json @@ -1259,7 +1259,9 @@ "errorCreatingSubscription": "建立您的訂閱時出現問題", "errorPurchaseBusinessPlan": "您已經有一個商業計劃", "couponIsNotValidForUserError": "優惠券只能由新客戶兌換。", - "errorApplyingCoupon": "應用優惠券時出錯" + "errorApplyingCoupon": "應用優惠券時出錯", + "errorWhileFetchingMoreItems": "獲取更多項目時出現錯誤", + "errorDeletingItems": "刪除此項目時出現錯誤" }, "actions": { "undo": "撤銷", diff --git a/src/app/i18n/locales/zh.json b/src/app/i18n/locales/zh.json index c0d051b4a..c9f85ad97 100644 --- a/src/app/i18n/locales/zh.json +++ b/src/app/i18n/locales/zh.json @@ -1296,7 +1296,9 @@ "errorCreatingSubscription": "创建您的订阅时出现问题", "errorPurchaseBusinessPlan": "您已经有一个商业计划", "couponIsNotValidForUserError": "优惠券只能由新客户兑换。", - "errorApplyingCoupon": "应用优惠券时出错" + "errorApplyingCoupon": "应用优惠券时出错", + "errorWhileFetchingMoreItems": "获取更多项目时出现错误", + "errorDeletingItems": "删除此项目时出现错误" }, "actions": { "undo": "撤销", From 7433a90eca7e6591ee756e398ae614610a6b221f Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:44:27 +0200 Subject: [PATCH 22/24] fix: sonarcloud issues --- .../DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx index 266ccb1d3..72d9f3b07 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx @@ -348,7 +348,7 @@ const contextMenuSelectedBackupItems = ({ onDeleteSelectedItems, }: { onDownloadSelectedItems: () => void; - onDeleteSelectedItems: () => void; + onDeleteSelectedItems: () => Promise; }): ListItemMenu => [ getDownloadMenuItem(onDownloadSelectedItems), { name: '', action: () => false, separator: true }, From d6d18d7e60e166d1221f326bfc82aa4de6603ff8 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:58:31 +0200 Subject: [PATCH 23/24] fix: allow void and Promises for action function (context menu) --- .../DriveItemContextMenu.tsx | 50 +++++++++---------- src/app/shared/components/List/ListItem.tsx | 2 +- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx index 72d9f3b07..9b1656dbc 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorerList/DriveItemContextMenu.tsx @@ -145,12 +145,12 @@ const contextMenuSelectedItems = ({ }): ListItemMenu => [ { name: `${selectedItems.length} ${t('contextMenu.itemsSelected')}`, - action: () => ({}), + action: () => {}, disabled: () => { return true; }, }, - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveItemMenuItem(moveItems), getDownloadMenuItem(downloadItems), { name: '', action: () => false, separator: true }, @@ -178,13 +178,13 @@ const contextMenuDriveNotSharedLink = ({ }): ListItemMenu => [ shareLinkMenuItem(shareLink), getCopyLinkMenuItem(getLink), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, openPreview && getOpenPreviewMenuItem(openPreview), showDetailsMenuItem(showDetails), getRenameMenuItem(renameItem), getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; @@ -207,12 +207,12 @@ const contextMenuDriveFolderNotSharedLink = ({ }): ListItemMenu => [ shareLinkMenuItem(shareLink), getCopyLinkMenuItem(getLink), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, showDetailsMenuItem(showDetails), getRenameMenuItem(renameItem), getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; @@ -236,13 +236,13 @@ const contextMenuDriveItemShared = ({ moveToTrash: (item: DriveItemData | (ListShareLinksItem & { code: string })) => void; }): ListItemMenu => [ ...[manageLinkAccessMenuItem(openShareAccessSettings), getCopyLinkMenuItem(copyLink)], - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, openPreview && getOpenPreviewMenuItem(openPreview), showDetailsMenuItem(showDetails), getRenameMenuItem(renameItem), getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; @@ -264,12 +264,12 @@ const contextMenuDriveFolderShared = ({ moveToTrash: (item: DriveItemData | (ListShareLinksItem & { code: string })) => void; }): ListItemMenu => [ ...[manageLinkAccessMenuItem(openShareAccessSettings), getCopyLinkMenuItem(copyLink)], - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, showDetailsMenuItem(showDetails), getRenameMenuItem(renameItem), getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; @@ -281,7 +281,7 @@ const contextMenuMultipleSharedView = ({ moveToTrash: (item: ListShareLinksItem) => void; }): ListItemMenu => [ getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; @@ -296,7 +296,7 @@ const contextMenuTrashItems = ({ }): ListItemMenu => [ openPreview && getOpenPreviewMenuItem(openPreview), getRestoreMenuItem(restoreItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getDeletePermanentlyMenuItem(deletePermanently), ]; @@ -308,7 +308,7 @@ const contextMenuTrashFolder = ({ deletePermanently: (item: DriveItemData) => void; }): ListItemMenu => [ getRestoreMenuItem(restoreItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getDeletePermanentlyMenuItem(deletePermanently), ]; @@ -320,7 +320,7 @@ const contextMenuMultipleSelectedTrashItems = ({ deletePermanently: (item: DriveItemData) => void; }): ListItemMenu => [ getRestoreMenuItem(restoreItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getDeletePermanentlyMenuItem(deletePermanently), ]; @@ -337,9 +337,7 @@ const contextMenuBackupItems = ({ action: () => { onDeviceDeleted(selectedDevices); }, - disabled: () => { - return false; - }, + disabled: () => false, }, ]; @@ -351,7 +349,7 @@ const contextMenuSelectedBackupItems = ({ onDeleteSelectedItems: () => Promise; }): ListItemMenu => [ getDownloadMenuItem(onDownloadSelectedItems), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, { name: t('drive.dropdown.delete'), icon: Trash, @@ -397,7 +395,7 @@ const contextMenuDriveItemSharedAFS = ({ renameItem && getRenameMenuItem(renameItem), moveItem && getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - moveToTrash && { name: '', action: () => false, separator: true }, + moveToTrash && { name: '', action: () => {}, separator: true }, moveToTrash && getMoveToTrashMenuItem(moveToTrash), ]; @@ -419,12 +417,12 @@ const contextMenuDriveFolderSharedAFS = ({ moveToTrash?: (item: any) => void; }): ListItemMenu => [ openShareAccessSettings && manageLinkAccessMenuItem(openShareAccessSettings), - openShareAccessSettings && { name: '', action: () => false, separator: true }, + openShareAccessSettings && { name: '', action: () => {}, separator: true }, showDetailsMenuItem(showDetails), renameItem && getRenameMenuItem(renameItem), moveItem && getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - moveToTrash && { name: '', action: () => false, separator: true }, + moveToTrash && { name: '', action: () => {}, separator: true }, moveToTrash && getMoveToTrashMenuItem(moveToTrash), ]; @@ -436,7 +434,7 @@ const contextMenuMultipleSharedViewAFS = ({ moveToTrash?: (item: AdvancedSharedItem) => void; }): ListItemMenu => [ getDownloadMenuItem(downloadItem), - moveToTrash && { name: '', action: () => false, separator: true }, + moveToTrash && { name: '', action: () => {}, separator: true }, moveToTrash && getMoveToTrashMenuItem(moveToTrash), ]; @@ -462,12 +460,12 @@ const contextMenuWorkspaceFolder = ({ shareLinkMenuItem(shareLink), getCopyLinkMenuItem(getLink), shareWithTeamMenuItem(shareWithTeam), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, showDetailsMenuItem(showDetails), getRenameMenuItem(renameItem), getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; @@ -495,13 +493,13 @@ const contextMenuWorkspaceFile = ({ shareLinkMenuItem(shareLink), getCopyLinkMenuItem(getLink), shareWithTeamMenuItem(shareWithTeam), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, openPreview && getOpenPreviewMenuItem(openPreview), showDetailsMenuItem(showDetails), getRenameMenuItem(renameItem), getMoveItemMenuItem(moveItem), getDownloadMenuItem(downloadItem), - { name: '', action: () => false, separator: true }, + { name: '', action: () => {}, separator: true }, getMoveToTrashMenuItem(moveToTrash), ]; diff --git a/src/app/shared/components/List/ListItem.tsx b/src/app/shared/components/List/ListItem.tsx index 1ba284e77..475c0a475 100644 --- a/src/app/shared/components/List/ListItem.tsx +++ b/src/app/shared/components/List/ListItem.tsx @@ -13,7 +13,7 @@ export type ListItemMenu = Array< keyboardShortcutIcon?: React.ForwardRefExoticComponent<{ size?: number | string }>; keyboardShortcutText?: string; }; - action: (target: T) => void; + action: (target: T) => void | Promise; disabled?: (target: T) => boolean; } | undefined From 73bc121fb024201090171ced7516a076fc788194 Mon Sep 17 00:00:00 2001 From: Xavier Abad <77491413+masterprog-cmd@users.noreply.github.com> Date: Wed, 25 Sep 2024 13:46:55 +0200 Subject: [PATCH 24/24] fix: selected items when stop sharing item for shared view --- .../components/DriveExplorer/DriveExplorer.tsx | 6 ++++-- .../components/ShareDialog/ShareDialog.tsx | 2 ++ .../share/views/SharedLinksView/SharedView.tsx | 18 ++++++++++++------ 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/app/drive/components/DriveExplorer/DriveExplorer.tsx b/src/app/drive/components/DriveExplorer/DriveExplorer.tsx index 5bdd2cb28..e18b825d8 100644 --- a/src/app/drive/components/DriveExplorer/DriveExplorer.tsx +++ b/src/app/drive/components/DriveExplorer/DriveExplorer.tsx @@ -523,8 +523,10 @@ const DriveExplorer = (props: DriveExplorerProps): JSX.Element => { ); const handleOnShareItem = useCallback(() => { - resetPaginationState(); - dispatch(fetchSortedFolderContentThunk(currentFolderId)); + setTimeout(() => { + resetPaginationState(); + dispatch(fetchSortedFolderContentThunk(currentFolderId)); + }, 500); }, [currentFolderId]); const onSuccessEditingName = useCallback(() => { diff --git a/src/app/drive/components/ShareDialog/ShareDialog.tsx b/src/app/drive/components/ShareDialog/ShareDialog.tsx index 2f02157e4..f14a35cf3 100644 --- a/src/app/drive/components/ShareDialog/ShareDialog.tsx +++ b/src/app/drive/components/ShareDialog/ShareDialog.tsx @@ -95,6 +95,7 @@ type ShareDialogProps = { user: UserSettings; isDriveItem?: boolean; onShareItem?: () => void; + onStopSharingItem?: () => void; onCloseDialog?: () => void; }; @@ -463,6 +464,7 @@ const ShareDialog = (props: ShareDialogProps): JSX.Element => { }), ); props.onShareItem?.(); + props.onStopSharingItem?.(); setShowStopSharingConfirmation(false); onClose(); setIsLoading(false); diff --git a/src/app/share/views/SharedLinksView/SharedView.tsx b/src/app/share/views/SharedLinksView/SharedView.tsx index 9cf0480dc..9c5dd5a38 100644 --- a/src/app/share/views/SharedLinksView/SharedView.tsx +++ b/src/app/share/views/SharedLinksView/SharedView.tsx @@ -293,6 +293,7 @@ function SharedView({ : translate('shared-links.toast.link-deleted'); notificationsService.show({ text: stringLinksDeleted, type: ToastType.Success }); closeConfirmDelete(); + actionDispatch(setSelectedItems([])); actionDispatch(setIsLoading(false)); } }; @@ -313,6 +314,8 @@ function SharedView({ })); await moveItemsToTrash(itemsToTrash, () => removeItemsFromList(itemsToTrash)); + + actionDispatch(setSelectedItems([])); }; const renameItem = (shareItem: AdvancedSharedItem | DriveItemData) => { @@ -330,14 +333,14 @@ function SharedView({ const mnemonic = selectedWorkspace?.workspaceUser.key ?? (await decryptMnemonic(shareItem.encryptionKey ? shareItem.encryptionKey : clickedShareItemEncryptionKey)); - handleOpemItemPreview(true, { ...previewItem, mnemonic }); + handleOpenItemPreview(true, { ...previewItem, mnemonic }); } catch (err) { const error = errorService.castError(err); errorService.reportError(error); } }; - const handleOpemItemPreview = (openItemPreview: boolean, item?: PreviewFileItem) => { + const handleOpenItemPreview = (openItemPreview: boolean, item?: PreviewFileItem) => { actionDispatch(setItemToView(item)); actionDispatch(setIsFileViewerOpen(openItemPreview)); }; @@ -465,7 +468,7 @@ function SharedView({ await moveSelectedItemsToTrash(items); if (isFileViewerOpen) { - handleOpemItemPreview(false); + handleOpenItemPreview(false); } }; @@ -536,7 +539,7 @@ function SharedView({ onOpenStopSharingDialog={onOpenStopSharingDialog} onRenameSelectedItem={renameItem} onOpenItemPreview={(item: PreviewFileItem) => { - handleOpemItemPreview(true, item); + handleOpenItemPreview(true, item); }} />
@@ -558,7 +561,7 @@ function SharedView({ handleOpemItemPreview(false)} + onClose={() => handleOpenItemPreview(false)} onShowStopSharingDialog={onOpenStopSharingDialog} sharedKeyboardShortcuts={{ renameItemFromKeyboard: !isCurrentUserViewer(currentUserRole) ? renameItem : undefined, @@ -574,7 +577,10 @@ function SharedView({ moveItemsToTrash={moveItemsToTrashOnStopSharing} /> - + actionDispatch(setSelectedItems([]))} + /> {isShowInvitationsOpen && } 0}