diff --git a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx index 30cd66efc7..d919461527 100644 --- a/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx +++ b/src/app/drive/components/NameCollisionDialog/NameCollisionContainer.tsx @@ -7,6 +7,7 @@ import { IRoot } from 'app/store/slices/storage/types'; import workspacesSelectors from 'app/store/slices/workspaces/workspaces.selectors'; import { fileVersionsSelectors } from 'app/store/slices/fileVersions'; import { NameCollisionContext, resolveCollision } from './nameCollision.actions'; +import { findExistingItemFor, findPendingGroupIndex, getRemainingGroups } from './nameCollision.utils'; const NameCollisionContainer: FC = () => { const dispatch = useAppDispatch(); @@ -17,32 +18,82 @@ const NameCollisionContainer: FC = () => { const operationType = collisionDialogInfo?.operation; const newItems = useMemo(() => collisionGroups.flatMap((g) => g.duplicatedItems), [collisionGroups]); const existingItems = useMemo(() => collisionGroups.flatMap((g) => g.existingItems), [collisionGroups]); + const remainingItemsCount = newItems.length; const selectedWorkspace = useAppSelector(workspacesSelectors.getSelectedWorkspace); const limits = useAppSelector(fileVersionsSelectors.getLimits); const maxUploadFileSize = useAppSelector(fileVersionsSelectors.getMaxFileSizeLimit); const isVersioningEnabled = limits?.versioning?.enabled ?? false; + const versioningMaxFileSize = limits?.versioning?.maxFileSize ?? 0; - const context: NameCollisionContext = { dispatch, selectedWorkspace, maxUploadFileSize, isVersioningEnabled }; + const context: NameCollisionContext = { + dispatch, + selectedWorkspace, + maxUploadFileSize, + isVersioningEnabled, + versioningMaxFileSize, + }; const closeDialog = () => { dispatch(uiActions.setIsNameCollisionDialogOpen({ open: false, info: undefined })); }; - const triggerSelectedOptionsOnSubmit = async ({ operationType, operation }: OnSubmitPressed) => { - for (const group of collisionGroups) { - await resolveCollision( - { - operationType, - operation, - items: group.duplicatedItems, - existingItems: group.existingItems, - destinationUuid: group.destinationUuid, - }, - context, + const triggerSelectedOptionsOnSubmit = async ({ operationType, operation, applyToAll }: OnSubmitPressed) => { + if (applyToAll) { + closeDialog(); + await Promise.all( + collisionGroups.map((group) => + resolveCollision( + { + operationType, + operation, + items: group.duplicatedItems, + existingItems: group.existingItems, + destinationUuid: group.destinationUuid, + }, + context, + ), + ), + ); + return; + } + + const groupIndex = findPendingGroupIndex(collisionGroups); + const hasPendingGroup = groupIndex !== -1; + if (!hasPendingGroup) { + closeDialog(); + return; + } + + const group = collisionGroups[groupIndex]; + const itemToUpload = group.duplicatedItems[0]; + const collidingExistingItem = findExistingItemFor(itemToUpload, group.existingItems); + const isReplacing = operation === 'replace'; + const replacedExistingItem = isReplacing ? collidingExistingItem : undefined; + + await resolveCollision( + { + operationType, + operation, + items: [itemToUpload], + existingItems: group.existingItems, + destinationUuid: group.destinationUuid, + }, + context, + ); + + const remainingGroups = getRemainingGroups(collisionGroups, groupIndex, replacedExistingItem); + const hasRemainingGroups = remainingGroups.length > 0; + if (hasRemainingGroups) { + dispatch( + uiActions.setIsNameCollisionDialogOpen({ + open: true, + info: { groups: remainingGroups, operation: operationType }, + }), ); + } else { + closeDialog(); } - closeDialog(); }; if (!collisionDialogInfo) return null; @@ -56,6 +107,7 @@ const NameCollisionContainer: FC = () => { onSubmitButtonPressed={triggerSelectedOptionsOnSubmit} onCloseDialog={closeDialog} operationType={operationType as 'move' | 'upload'} + remainingItemsCount={remainingItemsCount} /> ); }; diff --git a/src/app/drive/components/NameCollisionDialog/index.tsx b/src/app/drive/components/NameCollisionDialog/index.tsx index 7db0c245bb..f6419a0d9d 100644 --- a/src/app/drive/components/NameCollisionDialog/index.tsx +++ b/src/app/drive/components/NameCollisionDialog/index.tsx @@ -1,7 +1,7 @@ import { FC, useEffect, useMemo, useState } from 'react'; import { Label, Radio, RadioGroup } from '@headlessui/react'; -import { Button, Modal } from '@internxt/ui'; +import { Button, Checkbox, Modal } from '@internxt/ui'; import { DriveItemData } from 'app/drive/types'; import { useTranslationContext } from 'app/i18n/provider/TranslationProvider'; import { IRoot } from 'app/store/slices/storage/types'; @@ -13,9 +13,10 @@ export const OPERATION_TYPE = { export type OnSubmitPressed = { operationType: 'move' | 'upload'; - operation: 'keep' | 'replace'; + operation: 'keep' | 'replace' | 'skip'; itemsToUpload: (File | IRoot | DriveItemData)[]; itemsToReplace: (DriveItemData | IRoot)[]; + applyToAll: boolean; }; export interface NameCollisionDialogProps { @@ -23,9 +24,16 @@ export interface NameCollisionDialogProps { isOpen: boolean; driveItems: (DriveItemData | IRoot)[]; newItems: (File | IRoot)[]; + remainingItemsCount: number; onCloseDialog(): void; onCancelButtonPressed(): void; - onSubmitButtonPressed({ operationType, operation, itemsToUpload, itemsToReplace }: OnSubmitPressed): Promise; + onSubmitButtonPressed({ + operationType, + operation, + itemsToUpload, + itemsToReplace, + applyToAll, + }: OnSubmitPressed): Promise; } const NameCollisionDialog: FC = ({ @@ -33,35 +41,41 @@ const NameCollisionDialog: FC = ({ operationType, newItems, driveItems, + remainingItemsCount, onCancelButtonPressed, onCloseDialog, onSubmitButtonPressed, }: NameCollisionDialogProps) => { const { translate } = useTranslationContext(); - const options = [ - { - operation: 'replace' as const, - name: translate('modals.renameModal.replaceItem'), - }, - { - operation: 'keep' as const, - name: translate('modals.renameModal.keepBoth'), - }, - ]; + const options = useMemo( + () => [ + { + operation: 'replace' as const, + name: translate('modals.alreadyExistsModal.replaceItem'), + }, + { + operation: 'keep' as const, + name: translate('modals.alreadyExistsModal.keepBoth'), + }, + { + operation: 'skip' as const, + name: translate('modals.alreadyExistsModal.skipItem'), + }, + ], + [translate], + ); const [isLoading, setIsLoading] = useState(false); const [selectedOption, setSelectedOption] = useState(options[0]); + const [applyToAll, setApplyToAll] = useState(false); + + const title = translate('modals.alreadyExistsModal.title'); + const description = translate('modals.alreadyExistsModal.description', { itemName: newItems?.[0]?.name }); - const title = - newItems?.length > 1 ? translate('modals.renameModal.titleMultipleItems') : translate('modals.renameModal.title'); - const description = - newItems?.length > 1 - ? translate('modals.renameModal.multipleDescription') - : translate('modals.renameModal.description', { itemName: newItems?.[0]?.name }); const primaryButtonText = useMemo( () => operationType === OPERATION_TYPE.MOVE - ? translate('modals.renameModal.move') - : translate('modals.renameModal.upload'), + ? translate('modals.alreadyExistsModal.move') + : translate('modals.alreadyExistsModal.upload'), [operationType], ); @@ -69,13 +83,15 @@ const NameCollisionDialog: FC = ({ if (isOpen) { setIsLoading(false); setSelectedOption(options[0]); + setApplyToAll(false); } - }, [isOpen]); + }, [isOpen, options]); const onClose = (): void => { onCloseDialog(); onCancelButtonPressed(); setSelectedOption(options[0]); + setApplyToAll(false); setIsLoading(false); }; @@ -88,9 +104,10 @@ const NameCollisionDialog: FC = ({ operation: selectedOption.operation, itemsToUpload: newItems, itemsToReplace: driveItems, + applyToAll, }); - onClose(); + setIsLoading(false); }; return ( @@ -100,7 +117,7 @@ const NameCollisionDialog: FC = ({

{description}

- +
{options.map((option) => ( @@ -109,12 +126,14 @@ const NameCollisionDialog: FC = ({ className={`flex h-5 w-5 flex-col items-center justify-center rounded-full ${ option.operation === selectedOption.operation ? 'bg-primary active:bg-primary-dark' - : 'border border-gray-40 bg-white group-hover:border-gray-50' + : 'border border-gray-40 bg-white dark:bg-transparent group-hover:border-gray-50' }`} >
@@ -128,6 +147,17 @@ const NameCollisionDialog: FC = ({
+ {remainingItemsCount > 1 && ( +
+ setApplyToAll((prev) => !prev)} /> + +
+ )}