Browse Source

Custom File download location (#1565)

Abhinav Kumar 1 year ago
parent
commit
f5a0393c7c

+ 0 - 160
apps/photos/src/components/Collections/CollectionDownloadProgress.tsx

@@ -1,160 +0,0 @@
-import Notification from 'components/Notification';
-import { t } from 'i18next';
-import isElectron from 'is-electron';
-import { AppContext } from 'pages/_app';
-import { GalleryContext } from 'pages/gallery';
-import { useContext } from 'react';
-import ElectronAPIs from '@ente/shared/electron';
-
-export interface CollectionDownloadProgressAttributes {
-    success: number;
-    failed: number;
-    total: number;
-    collectionName: string;
-    collectionID: number;
-    isHidden: boolean;
-    downloadDirPath: string;
-    canceller: AbortController;
-}
-
-interface CollectionDownloadProgressProps {
-    attributesList: CollectionDownloadProgressAttributes[];
-    setAttributesList: (value: CollectionDownloadProgressAttributes[]) => void;
-}
-
-export const isCollectionDownloadCompleted = (
-    attributes: CollectionDownloadProgressAttributes
-) => {
-    return (
-        attributes &&
-        attributes.success + attributes.failed === attributes.total
-    );
-};
-
-export const isCollectionDownloadCompletedWithErrors = (
-    attributes: CollectionDownloadProgressAttributes
-) => {
-    return (
-        attributes &&
-        attributes.failed > 0 &&
-        isCollectionDownloadCompleted(attributes)
-    );
-};
-
-export const isCollectionDownloadCancelled = (
-    attributes: CollectionDownloadProgressAttributes
-) => {
-    return attributes && attributes.canceller?.signal?.aborted;
-};
-
-export const CollectionDownloadProgress: React.FC<CollectionDownloadProgressProps> =
-    ({ attributesList, setAttributesList }) => {
-        const appContext = useContext(AppContext);
-        const galleryContext = useContext(GalleryContext);
-
-        if (!attributesList) {
-            return <></>;
-        }
-
-        const onClose = (collectionID: number) => {
-            setAttributesList(
-                attributesList.filter(
-                    (attr) => attr.collectionID !== collectionID
-                )
-            );
-        };
-
-        const confirmCancelUpload = (
-            attributes: CollectionDownloadProgressAttributes
-        ) => {
-            appContext.setDialogMessage({
-                title: t('STOP_DOWNLOADS_HEADER'),
-                content: t('STOP_ALL_DOWNLOADS_MESSAGE'),
-                proceed: {
-                    text: t('YES_STOP_DOWNLOADS'),
-                    variant: 'critical',
-                    action: () => {
-                        attributes?.canceller.abort();
-                        onClose(attributes.collectionID);
-                    },
-                },
-                close: {
-                    text: t('NO'),
-                    variant: 'secondary',
-                    action: () => {},
-                },
-            });
-        };
-
-        const handleClose =
-            (attributes: CollectionDownloadProgressAttributes) => () => {
-                if (isCollectionDownloadCompleted(attributes)) {
-                    onClose(attributes.collectionID);
-                } else {
-                    confirmCancelUpload(attributes);
-                }
-            };
-
-        const handleOnClick = (collectionID: number) => () => {
-            const attributes = attributesList.find(
-                (attr) => attr.collectionID === collectionID
-            );
-            if (isElectron()) {
-                ElectronAPIs.openDirectory(attributes.downloadDirPath);
-            } else {
-                if (attributes.isHidden) {
-                    galleryContext.openHiddenSection(() => {
-                        galleryContext.setActiveCollectionID(
-                            attributes.collectionID
-                        );
-                    });
-                } else {
-                    galleryContext.setActiveCollectionID(
-                        attributes.collectionID
-                    );
-                }
-            }
-        };
-
-        return (
-            <>
-                {attributesList.map((attributes, index) => (
-                    <Notification
-                        key={attributes.collectionID}
-                        horizontal="left"
-                        sx={{ '&&': { bottom: `${index * 80 + 20}px` } }}
-                        open
-                        onClose={handleClose(attributes)}
-                        keepOpenOnClick
-                        attributes={{
-                            variant: isCollectionDownloadCompletedWithErrors(
-                                attributes
-                            )
-                                ? 'critical'
-                                : 'secondary',
-                            title: isCollectionDownloadCompletedWithErrors(
-                                attributes
-                            )
-                                ? t('DOWNLOAD_FAILED')
-                                : isCollectionDownloadCompleted(attributes)
-                                ? t(`DOWNLOAD_COMPLETE`)
-                                : t('DOWNLOADING_COLLECTION', {
-                                      name: attributes.collectionName,
-                                  }),
-                            caption: isCollectionDownloadCompleted(attributes)
-                                ? attributes.collectionName
-                                : t('DOWNLOAD_PROGRESS', {
-                                      progress: {
-                                          current:
-                                              attributes.success +
-                                              attributes.failed,
-                                          total: attributes.total,
-                                      },
-                                  }),
-                            onClick: handleOnClick(attributes.collectionID),
-                        }}
-                    />
-                ))}
-            </>
-        );
-    };

+ 2 - 5
apps/photos/src/components/Collections/CollectionInfoWithOptions.tsx

@@ -1,5 +1,4 @@
 import { CollectionInfo } from './CollectionInfo';
-import React from 'react';
 import { Collection, CollectionSummary } from 'types/collection';
 import CollectionOptions from 'components/Collections/CollectionOptions';
 import { SetCollectionNamerAttributes } from 'components/Collections/CollectionNamer';
@@ -11,16 +10,14 @@ import Favorite from '@mui/icons-material/FavoriteRounded';
 import ArchiveOutlined from '@mui/icons-material/ArchiveOutlined';
 import PeopleIcon from '@mui/icons-material/People';
 import LinkIcon from '@mui/icons-material/Link';
-import { SetCollectionDownloadProgressAttributes } from 'types/gallery';
+import { SetFilesDownloadProgressAttributesCreator } from 'types/gallery';
 
 interface Iprops {
     activeCollection: Collection;
     collectionSummary: CollectionSummary;
     setCollectionNamerAttributes: SetCollectionNamerAttributes;
     showCollectionShareModal: () => void;
-    setCollectionDownloadProgressAttributesCreator: (
-        collectionID: number
-    ) => SetCollectionDownloadProgressAttributes;
+    setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
     isActiveCollectionDownloadInProgress: () => boolean;
     setActiveCollectionID: (collectionID: number) => void;
 }

+ 15 - 13
apps/photos/src/components/Collections/CollectionOptions/index.tsx

@@ -33,13 +33,11 @@ import { Trans } from 'react-i18next';
 import { t } from 'i18next';
 import { Box } from '@mui/material';
 import CollectionSortOrderMenu from './CollectionSortOrderMenu';
-import { SetCollectionDownloadProgressAttributes } from 'types/gallery';
+import { SetFilesDownloadProgressAttributesCreator } from 'types/gallery';
 
 interface CollectionOptionsProps {
     setCollectionNamerAttributes: SetCollectionNamerAttributes;
-    setCollectionDownloadProgressAttributesCreator: (
-        collectionID: number
-    ) => SetCollectionDownloadProgressAttributes;
+    setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
     isActiveCollectionDownloadInProgress: () => boolean;
     activeCollection: Collection;
     collectionSummaryType: CollectionSummaryType;
@@ -76,7 +74,7 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
         setActiveCollectionID,
         setCollectionNamerAttributes,
         showCollectionShareModal,
-        setCollectionDownloadProgressAttributesCreator,
+        setFilesDownloadProgressAttributesCreator,
         isActiveCollectionDownloadInProgress,
     } = props;
 
@@ -219,21 +217,25 @@ const CollectionOptions = (props: CollectionOptionsProps) => {
             return;
         }
         if (collectionSummaryType === CollectionSummaryType.hiddenItems) {
-            const setCollectionDownloadProgressAttributes =
-                setCollectionDownloadProgressAttributesCreator(
-                    HIDDEN_ITEMS_SECTION
+            const setFilesDownloadProgressAttributes =
+                setFilesDownloadProgressAttributesCreator(
+                    activeCollection.name,
+                    HIDDEN_ITEMS_SECTION,
+                    true
                 );
             downloadDefaultHiddenCollectionHelper(
-                setCollectionDownloadProgressAttributes
+                setFilesDownloadProgressAttributes
             );
         } else {
-            const setCollectionDownloadProgressAttributes =
-                setCollectionDownloadProgressAttributesCreator(
-                    activeCollection.id
+            const setFilesDownloadProgressAttributes =
+                setFilesDownloadProgressAttributesCreator(
+                    activeCollection.name,
+                    activeCollection.id,
+                    isHiddenCollection(activeCollection)
                 );
             downloadCollectionHelper(
                 activeCollection.id,
-                setCollectionDownloadProgressAttributes
+                setFilesDownloadProgressAttributes
             );
         }
     };

+ 15 - 43
apps/photos/src/components/Collections/index.tsx

@@ -16,12 +16,11 @@ import { useLocalState } from '@ente/shared/hooks/useLocalState';
 import { sortCollectionSummaries } from 'services/collectionService';
 import { LS_KEYS } from '@ente/shared/storage/localStorage';
 import {
-    CollectionDownloadProgress,
-    CollectionDownloadProgressAttributes,
-    isCollectionDownloadCancelled,
-    isCollectionDownloadCompleted,
-} from './CollectionDownloadProgress';
-import { SetCollectionDownloadProgressAttributes } from 'types/gallery';
+    FilesDownloadProgressAttributes,
+    isFilesDownloadCancelled,
+    isFilesDownloadCompleted,
+} from '../FilesDownloadProgress';
+import { SetFilesDownloadProgressAttributesCreator } from 'types/gallery';
 
 interface Iprops {
     activeCollection: Collection;
@@ -33,6 +32,8 @@ interface Iprops {
     hiddenCollectionSummaries: CollectionSummaries;
     setCollectionNamerAttributes: SetCollectionNamerAttributes;
     setPhotoListHeader: (value: TimeStampListItem) => void;
+    filesDownloadProgressAttributesList: FilesDownloadProgressAttributes[];
+    setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
 }
 
 export default function Collections(props: Iprops) {
@@ -46,17 +47,14 @@ export default function Collections(props: Iprops) {
         hiddenCollectionSummaries,
         setCollectionNamerAttributes,
         setPhotoListHeader,
+        filesDownloadProgressAttributesList,
+        setFilesDownloadProgressAttributesCreator,
     } = props;
 
     const [allCollectionView, setAllCollectionView] = useState(false);
     const [collectionShareModalView, setCollectionShareModalView] =
         useState(false);
 
-    const [
-        collectionDownloadProgressAttributesList,
-        setCollectionDownloadProgressAttributesList,
-    ] = useState<CollectionDownloadProgressAttributes[]>([]);
-
     const [collectionListSortBy, setCollectionListSortBy] =
         useLocalState<COLLECTION_LIST_SORT_BY>(
             LS_KEYS.COLLECTION_SORT_BY,
@@ -86,38 +84,16 @@ export default function Collections(props: Iprops) {
         [collectionListSortBy, toShowCollectionSummaries]
     );
 
-    const setCollectionDownloadProgressAttributesCreator =
-        (collectionID: number): SetCollectionDownloadProgressAttributes =>
-        (value) => {
-            setCollectionDownloadProgressAttributesList((prev) => {
-                const attributes = prev?.find(
-                    (attr) => attr.collectionID === collectionID
-                );
-                const updatedAttributes =
-                    typeof value === 'function' ? value(attributes) : value;
-
-                const updatedAttributesList = attributes
-                    ? prev.map((attr) =>
-                          attr.collectionID === collectionID
-                              ? updatedAttributes
-                              : attr
-                      )
-                    : [...prev, updatedAttributes];
-
-                return updatedAttributesList;
-            });
-        };
-
     const isActiveCollectionDownloadInProgress = useCallback(() => {
-        const attributes = collectionDownloadProgressAttributesList.find(
+        const attributes = filesDownloadProgressAttributesList.find(
             (attr) => attr.collectionID === activeCollectionID
         );
         return (
             attributes &&
-            !isCollectionDownloadCancelled(attributes) &&
-            !isCollectionDownloadCompleted(attributes)
+            !isFilesDownloadCancelled(attributes) &&
+            !isFilesDownloadCompleted(attributes)
         );
-    }, [activeCollectionID, collectionDownloadProgressAttributesList]);
+    }, [activeCollectionID, filesDownloadProgressAttributesList]);
 
     useEffect(() => {
         if (isInSearchMode) {
@@ -134,8 +110,8 @@ export default function Collections(props: Iprops) {
                     showCollectionShareModal={() =>
                         setCollectionShareModalView(true)
                     }
-                    setCollectionDownloadProgressAttributesCreator={
-                        setCollectionDownloadProgressAttributesCreator
+                    setFilesDownloadProgressAttributesCreator={
+                        setFilesDownloadProgressAttributesCreator
                     }
                     isActiveCollectionDownloadInProgress={
                         isActiveCollectionDownloadInProgress
@@ -195,10 +171,6 @@ export default function Collections(props: Iprops) {
                 onClose={closeCollectionShare}
                 collection={activeCollection}
             />
-            <CollectionDownloadProgress
-                attributesList={collectionDownloadProgressAttributesList}
-                setAttributesList={setCollectionDownloadProgressAttributesList}
-            />
         </>
     );
 }

+ 159 - 0
apps/photos/src/components/FilesDownloadProgress.tsx

@@ -0,0 +1,159 @@
+import Notification from 'components/Notification';
+import { t } from 'i18next';
+import isElectron from 'is-electron';
+import { AppContext } from 'pages/_app';
+import { GalleryContext } from 'pages/gallery';
+import { useContext } from 'react';
+import ElectronAPIs from '@ente/shared/electron';
+
+export interface FilesDownloadProgressAttributes {
+    id: number;
+    success: number;
+    failed: number;
+    total: number;
+    folderName: string;
+    collectionID: number;
+    isHidden: boolean;
+    downloadDirPath: string;
+    canceller: AbortController;
+}
+
+interface FilesDownloadProgressProps {
+    attributesList: FilesDownloadProgressAttributes[];
+    setAttributesList: (value: FilesDownloadProgressAttributes[]) => void;
+}
+
+export const isFilesDownloadStarted = (
+    attributes: FilesDownloadProgressAttributes
+) => {
+    return attributes && attributes.total > 0;
+};
+
+export const isFilesDownloadCompleted = (
+    attributes: FilesDownloadProgressAttributes
+) => {
+    return (
+        attributes &&
+        attributes.success + attributes.failed === attributes.total
+    );
+};
+
+export const isFilesDownloadCompletedWithErrors = (
+    attributes: FilesDownloadProgressAttributes
+) => {
+    return (
+        attributes &&
+        attributes.failed > 0 &&
+        isFilesDownloadCompleted(attributes)
+    );
+};
+
+export const isFilesDownloadCancelled = (
+    attributes: FilesDownloadProgressAttributes
+) => {
+    return attributes && attributes.canceller?.signal?.aborted;
+};
+
+export const FilesDownloadProgress: React.FC<FilesDownloadProgressProps> = ({
+    attributesList,
+    setAttributesList,
+}) => {
+    const appContext = useContext(AppContext);
+    const galleryContext = useContext(GalleryContext);
+
+    if (!attributesList) {
+        return <></>;
+    }
+
+    const onClose = (id: number) => {
+        setAttributesList(attributesList.filter((attr) => attr.id !== id));
+    };
+
+    const confirmCancelUpload = (
+        attributes: FilesDownloadProgressAttributes
+    ) => {
+        appContext.setDialogMessage({
+            title: t('STOP_DOWNLOADS_HEADER'),
+            content: t('STOP_ALL_DOWNLOADS_MESSAGE'),
+            proceed: {
+                text: t('YES_STOP_DOWNLOADS'),
+                variant: 'critical',
+                action: () => {
+                    attributes?.canceller.abort();
+                    onClose(attributes.id);
+                },
+            },
+            close: {
+                text: t('NO'),
+                variant: 'secondary',
+                action: () => {},
+            },
+        });
+    };
+
+    const handleClose = (attributes: FilesDownloadProgressAttributes) => () => {
+        if (isFilesDownloadCompleted(attributes)) {
+            onClose(attributes.id);
+        } else {
+            confirmCancelUpload(attributes);
+        }
+    };
+
+    const handleOnClick = (id: number) => () => {
+        const attributes = attributesList.find((attr) => attr.id === id);
+        if (isElectron()) {
+            ElectronAPIs.openDirectory(attributes.downloadDirPath);
+        } else {
+            if (attributes.isHidden) {
+                galleryContext.openHiddenSection(() => {
+                    galleryContext.setActiveCollectionID(
+                        attributes.collectionID
+                    );
+                });
+            } else {
+                galleryContext.setActiveCollectionID(attributes.collectionID);
+            }
+        }
+    };
+
+    return (
+        <>
+            {attributesList.map((attributes, index) => (
+                <Notification
+                    key={attributes.id}
+                    horizontal="left"
+                    sx={{
+                        '&&': { bottom: `${index * 80 + 20}px` },
+                        zIndex: 1600,
+                    }}
+                    open={isFilesDownloadStarted(attributes)}
+                    onClose={handleClose(attributes)}
+                    keepOpenOnClick
+                    attributes={{
+                        variant: isFilesDownloadCompletedWithErrors(attributes)
+                            ? 'critical'
+                            : 'secondary',
+                        title: isFilesDownloadCompletedWithErrors(attributes)
+                            ? t('DOWNLOAD_FAILED')
+                            : isFilesDownloadCompleted(attributes)
+                            ? t(`DOWNLOAD_COMPLETE`)
+                            : t('DOWNLOADING_COLLECTION', {
+                                  name: attributes.folderName,
+                              }),
+                        caption: isFilesDownloadCompleted(attributes)
+                            ? attributes.folderName
+                            : t('DOWNLOAD_PROGRESS', {
+                                  progress: {
+                                      current:
+                                          attributes.success +
+                                          attributes.failed,
+                                      total: attributes.total,
+                                  },
+                              }),
+                        onClick: handleOnClick(attributes.id),
+                    }}
+                />
+            ))}
+        </>
+    );
+};

+ 9 - 1
apps/photos/src/components/PhotoFrame.tsx

@@ -11,7 +11,10 @@ import AutoSizer from 'react-virtualized-auto-sizer';
 import PhotoViewer from 'components/PhotoViewer';
 import { TRASH_SECTION } from 'constants/collection';
 import { updateFileMsrcProps, updateFileSrcProps } from 'utils/photoFrame';
-import { SelectedState } from 'types/gallery';
+import {
+    SelectedState,
+    SetFilesDownloadProgressAttributesCreator,
+} from 'types/gallery';
 import { useRouter } from 'next/router';
 import { logError } from '@ente/shared/sentry';
 import { addLogLine } from '@ente/shared/logging';
@@ -61,6 +64,7 @@ interface Props {
     showAppDownloadBanner?: boolean;
     setIsPhotoSwipeOpen?: (value: boolean) => void;
     isInHiddenSection?: boolean;
+    setFilesDownloadProgressAttributesCreator?: SetFilesDownloadProgressAttributesCreator;
 }
 
 const PhotoFrame = ({
@@ -80,6 +84,7 @@ const PhotoFrame = ({
     showAppDownloadBanner,
     setIsPhotoSwipeOpen,
     isInHiddenSection,
+    setFilesDownloadProgressAttributesCreator,
 }: Props) => {
     const [open, setOpen] = useState(false);
     const [currentIndex, setCurrentIndex] = useState<number>(0);
@@ -601,6 +606,9 @@ const PhotoFrame = ({
                 enableDownload={enableDownload}
                 fileToCollectionsMap={fileToCollectionsMap}
                 collectionNameMap={collectionNameMap}
+                setFilesDownloadProgressAttributesCreator={
+                    setFilesDownloadProgressAttributesCreator
+                }
             />
         </Container>
     );

+ 21 - 9
apps/photos/src/components/PhotoViewer/index.tsx

@@ -8,12 +8,12 @@ import {
 } from 'services/collectionService';
 import { EnteFile } from 'types/file';
 import {
-    downloadFile,
     copyFileToClipboard,
     getFileExtension,
     getFileFromURL,
     isSupportedRawFormat,
     isRawFile,
+    downloadSingleFile,
 } from 'utils/file';
 import { logError } from '@ente/shared/sentry';
 
@@ -58,6 +58,7 @@ import isElectron from 'is-electron';
 import ReplayIcon from '@mui/icons-material/Replay';
 import ImageEditorOverlay from './ImageEditorOverlay';
 import EditIcon from '@mui/icons-material/Edit';
+import { SetFilesDownloadProgressAttributesCreator } from 'types/gallery';
 
 interface PhotoswipeFullscreenAPI {
     enter: () => void;
@@ -92,6 +93,7 @@ interface Iprops {
     enableDownload: boolean;
     fileToCollectionsMap: Map<number, number[]>;
     collectionNameMap: Map<number, string>;
+    setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator;
 }
 
 function PhotoViewer(props: Iprops) {
@@ -265,7 +267,7 @@ function PhotoViewer(props: Iprops) {
             `download-btn-${item.id}`
         ) as HTMLButtonElement;
         const downloadFile = () => {
-            downloadFileHelper(photoSwipe.currItem);
+            downloadFileHelper(photoSwipe.currItem as unknown as EnteFile);
         };
 
         if (downloadLivePhotoBtn) {
@@ -624,15 +626,21 @@ function PhotoViewer(props: Iprops) {
         setShowImageEditorOverlay(false);
     };
 
-    const downloadFileHelper = async (file) => {
-        if (file && props.enableDownload) {
-            appContext.startLoading();
+    const downloadFileHelper = async (file: EnteFile) => {
+        if (
+            file &&
+            props.enableDownload &&
+            props.setFilesDownloadProgressAttributesCreator
+        ) {
             try {
-                await downloadFile(file);
+                const setSingleFileDownloadProgress =
+                    props.setFilesDownloadProgressAttributesCreator(
+                        file.metadata.title
+                    );
+                await downloadSingleFile(file, setSingleFileDownloadProgress);
             } catch (e) {
                 // do nothing
             }
-            appContext.finishLoading();
         }
     };
 
@@ -727,7 +735,9 @@ function PhotoViewer(props: Iprops) {
                         onClose={() =>
                             setConversionFailedNotificationOpen(false)
                         }
-                        onClick={() => downloadFileHelper(photoSwipe.currItem)}
+                        onClick={() =>
+                            downloadFileHelper(photoSwipe.currItem as EnteFile)
+                        }
                     />
 
                     <Box
@@ -771,7 +781,9 @@ function PhotoViewer(props: Iprops) {
                                     className="pswp__button pswp__button--custom"
                                     title={t('DOWNLOAD_OPTION')}
                                     onClick={() =>
-                                        downloadFileHelper(photoSwipe.currItem)
+                                        downloadFileHelper(
+                                            photoSwipe.currItem as EnteFile
+                                        )
                                     }>
                                     <DownloadIcon />
                                 </button>

+ 7 - 4
apps/photos/src/components/pages/gallery/PreviewCard.tsx

@@ -218,6 +218,12 @@ export default function PreviewCard(props: IProps) {
     const galleryContext = useContext(GalleryContext);
     const deduplicateContext = useContext(DeduplicateContext);
 
+    const longPressCallback = () => {
+        onSelect(!selected);
+    };
+
+    const longPress = useLongPress(longPressCallback, 500);
+
     const {
         file,
         onClick,
@@ -289,9 +295,6 @@ export default function PreviewCard(props: IProps) {
         }
     };
 
-    const longPressCallback = () => {
-        onSelect(!selected);
-    };
     const handleHover = () => {
         if (isRangeSelectActive) {
             onHover();
@@ -304,7 +307,7 @@ export default function PreviewCard(props: IProps) {
             onClick={handleClick}
             onMouseEnter={handleHover}
             disabled={!file?.msrc && !imgSrc}
-            {...(selectable ? useLongPress(longPressCallback, 500) : {})}>
+            {...(selectable ? longPress : {})}>
             {selectable && (
                 <Check
                     type="checkbox"

+ 0 - 4
apps/photos/src/components/pages/sharedAlbum/SelectedFileOptions.tsx

@@ -10,7 +10,6 @@ import { formatNumber } from 'utils/number/format';
 
 interface Props {
     count: number;
-    ownCount: number;
     clearSelection: () => void;
     downloadFilesHelper: () => void;
 }
@@ -18,7 +17,6 @@ interface Props {
 const SelectedFileOptions = ({
     downloadFilesHelper,
     count,
-    ownCount,
     clearSelection,
 }: Props) => {
     const { isMobile } = useContext(AppContext);
@@ -31,8 +29,6 @@ const SelectedFileOptions = ({
                 </IconButton>
                 <Box ml={1.5}>
                     {formatNumber(count)} {t('SELECTED')}{' '}
-                    {ownCount !== count &&
-                        `(${formatNumber(ownCount)} ${t('YOURS')})`}
                 </Box>
             </FluidContainer>
             <Stack spacing={2} direction="row" mr={2}>

+ 60 - 1
apps/photos/src/pages/gallery/index.tsx

@@ -97,6 +97,8 @@ import { EnteFile } from 'types/file';
 import {
     GalleryContextType,
     SelectedState,
+    SetFilesDownloadProgressAttributes,
+    SetFilesDownloadProgressAttributesCreator,
     UploadTypeSelectorIntent,
 } from 'types/gallery';
 import Collections from 'components/Collections';
@@ -130,6 +132,10 @@ import { ClipService } from 'services/clipService';
 import isElectron from 'is-electron';
 import downloadManager from 'services/download';
 import { APPS } from '@ente/shared/apps/constants';
+import {
+    FilesDownloadProgress,
+    FilesDownloadProgressAttributes,
+} from 'components/FilesDownloadProgress';
 import locationSearchService from 'services/locationSearchService';
 import ComlinkSearchWorker from 'utils/comlink/ComlinkSearchWorker';
 import useEffectSingleThreaded from '@ente/shared/hooks/useEffectSingleThreaded';
@@ -286,6 +292,11 @@ export default function Gallery() {
 
     const [isInHiddenSection, setIsInHiddenSection] = useState(false);
 
+    const [
+        filesDownloadProgressAttributesList,
+        setFilesDownloadProgressAttributesList,
+    ] = useState<FilesDownloadProgressAttributes[]>([]);
+
     const openHiddenSection: GalleryContextType['openHiddenSection'] = (
         callback
     ) => {
@@ -793,6 +804,40 @@ export default function Gallery() {
         return <div />;
     }
 
+    const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator =
+        (folderName, collectionID, isHidden) => {
+            const id = filesDownloadProgressAttributesList?.length ?? 0;
+            const updater: SetFilesDownloadProgressAttributes = (value) => {
+                setFilesDownloadProgressAttributesList((prev) => {
+                    const attributes = prev?.find((attr) => attr.id === id);
+                    const updatedAttributes =
+                        typeof value === 'function'
+                            ? value(attributes)
+                            : { ...attributes, ...value };
+                    console.log('value', attributes, updatedAttributes);
+                    const updatedAttributesList = attributes
+                        ? prev.map((attr) =>
+                              attr.id === id ? updatedAttributes : attr
+                          )
+                        : [...prev, updatedAttributes];
+
+                    return updatedAttributesList;
+                });
+            };
+            updater({
+                id,
+                folderName,
+                collectionID,
+                isHidden,
+                canceller: null,
+                total: 0,
+                success: 0,
+                failed: 0,
+                downloadDirPath: null,
+            });
+            return updater;
+        };
+
     const collectionOpsHelper =
         (ops: COLLECTION_OPS_TYPE) => async (collection: Collection) => {
             startLoading();
@@ -858,7 +903,8 @@ export default function Gallery() {
                     toProcessFiles,
                     setTempDeletedFileIds,
                     setTempHiddenFileIds,
-                    setFixCreationTimeAttributes
+                    setFixCreationTimeAttributes,
+                    setFilesDownloadProgressAttributesCreator
                 );
             }
             if (
@@ -1005,6 +1051,10 @@ export default function Gallery() {
                     attributes={collectionSelectorAttributes}
                     collections={collections}
                 />
+                <FilesDownloadProgress
+                    attributesList={filesDownloadProgressAttributesList}
+                    setAttributesList={setFilesDownloadProgressAttributesList}
+                />
                 <FixCreationTime
                     isOpen={fixCreationTimeView}
                     hide={() => setFixCreationTimeView(false)}
@@ -1034,6 +1084,12 @@ export default function Gallery() {
                     hiddenCollectionSummaries={hiddenCollectionSummaries}
                     setCollectionNamerAttributes={setCollectionNamerAttributes}
                     setPhotoListHeader={setPhotoListHeader}
+                    setFilesDownloadProgressAttributesCreator={
+                        setFilesDownloadProgressAttributesCreator
+                    }
+                    filesDownloadProgressAttributesList={
+                        filesDownloadProgressAttributesList
+                    }
                 />
 
                 <Uploader
@@ -1101,6 +1157,9 @@ export default function Gallery() {
                             files.length < 30 && !isInSearchMode
                         }
                         isInHiddenSection={isInHiddenSection}
+                        setFilesDownloadProgressAttributesCreator={
+                            setFilesDownloadProgressAttributesCreator
+                        }
                     />
                 )}
                 {selected.count > 0 &&

+ 90 - 23
apps/photos/src/pages/shared-albums/index.tsx

@@ -17,8 +17,7 @@ import {
 import { Collection } from 'types/collection';
 import { EnteFile } from 'types/file';
 import {
-    downloadFile,
-    downloadFiles,
+    downloadSelectedFiles,
     getSelectedFiles,
     mergeMetadata,
     sortFiles,
@@ -58,7 +57,12 @@ import UploadButton from 'components/Upload/UploadButton';
 import bs58 from 'bs58';
 import AddPhotoAlternateOutlined from '@mui/icons-material/AddPhotoAlternateOutlined';
 import ComlinkCryptoWorker from '@ente/shared/crypto';
-import { SelectedState, UploadTypeSelectorIntent } from 'types/gallery';
+import {
+    SelectedState,
+    SetFilesDownloadProgressAttributes,
+    SetFilesDownloadProgressAttributesCreator,
+    UploadTypeSelectorIntent,
+} from 'types/gallery';
 import FileDownloadOutlinedIcon from '@mui/icons-material/FileDownloadOutlined';
 import MoreHoriz from '@mui/icons-material/MoreHoriz';
 import OverflowMenu from '@ente/shared/components/OverflowMenu/menu';
@@ -66,6 +70,11 @@ import { OverflowMenuOption } from '@ente/shared/components/OverflowMenu/option'
 import { ENTE_WEBSITE_LINK } from '@ente/shared/constants/urls';
 import { APPS } from '@ente/shared/apps/constants';
 import downloadManager from 'services/download';
+import {
+    FilesDownloadProgress,
+    FilesDownloadProgressAttributes,
+} from 'components/FilesDownloadProgress';
+import { downloadCollectionFiles, isHiddenCollection } from 'utils/collection';
 import SelectedFileOptions from 'components/pages/sharedAlbum/SelectedFileOptions';
 
 export default function PublicCollectionGallery() {
@@ -93,7 +102,6 @@ export default function PublicCollectionGallery() {
     const [uploadTypeSelectorView, setUploadTypeSelectorView] = useState(false);
     const [blockingLoad, setBlockingLoad] = useState(false);
     const [shouldDisableDropzone, setShouldDisableDropzone] = useState(false);
-
     const [selected, setSelected] = useState<SelectedState>({
         ownCount: 0,
         count: 0,
@@ -124,6 +132,45 @@ export default function PublicCollectionGallery() {
         directory: true,
     });
 
+    const [
+        filesDownloadProgressAttributesList,
+        setFilesDownloadProgressAttributesList,
+    ] = useState<FilesDownloadProgressAttributes[]>([]);
+
+    const setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator =
+        (folderName, collectionID, isHidden) => {
+            const id = filesDownloadProgressAttributesList?.length ?? 0;
+            const updater: SetFilesDownloadProgressAttributes = (value) => {
+                setFilesDownloadProgressAttributesList((prev) => {
+                    const attributes = prev?.find((attr) => attr.id === id);
+                    const updatedAttributes =
+                        typeof value === 'function'
+                            ? value(attributes)
+                            : { ...attributes, ...value };
+                    console.log('value', attributes, updatedAttributes);
+                    const updatedAttributesList = attributes
+                        ? prev.map((attr) =>
+                              attr.id === id ? updatedAttributes : attr
+                          )
+                        : [...prev, updatedAttributes];
+
+                    return updatedAttributesList;
+                });
+            };
+            updater({
+                id,
+                folderName,
+                collectionID,
+                isHidden,
+                canceller: null,
+                total: 0,
+                success: 0,
+                failed: 0,
+                downloadDirPath: null,
+            });
+            return updater;
+        };
+
     const openUploader = () => {
         setUploadTypeSelectorView(true);
     };
@@ -230,18 +277,24 @@ export default function PublicCollectionGallery() {
     );
 
     const downloadAllFiles = async () => {
-        if (!downloadEnabled) {
-            return;
-        }
-        appContext.startLoading();
-        for (const file of publicFiles) {
-            try {
-                await downloadFile(file);
-            } catch (e) {
-                // do nothing
+        try {
+            if (!downloadEnabled) {
+                return;
             }
+            const setFilesDownloadProgressAttributes =
+                setFilesDownloadProgressAttributesCreator(
+                    publicCollection.name,
+                    publicCollection.id,
+                    isHiddenCollection(publicCollection)
+                );
+            await downloadCollectionFiles(
+                publicCollection.name,
+                publicFiles,
+                setFilesDownloadProgressAttributes
+            );
+        } catch (e) {
+            logError(e, 'failed to downloads shared album all files');
         }
-        appContext.finishLoading();
     };
 
     useEffect(() => {
@@ -454,22 +507,30 @@ export default function PublicCollectionGallery() {
         }
     }
 
+    const clearSelection = () => {
+        if (!selected?.count) {
+            return;
+        }
+        setSelected({ ownCount: 0, count: 0, collectionID: 0 });
+    };
+
     const downloadFilesHelper = async () => {
         try {
             const selectedFiles = getSelectedFiles(selected, publicFiles);
-            await downloadFiles(selectedFiles);
+            const setFilesDownloadProgressAttributes =
+                setFilesDownloadProgressAttributesCreator(
+                    `${selectedFiles.length} ${t('FILES')}`
+                );
+            await downloadSelectedFiles(
+                selectedFiles,
+                setFilesDownloadProgressAttributes
+            );
+            clearSelection();
         } catch (e) {
             logError(e, 'failed to download selected files');
         }
     };
 
-    const clearSelection = () => {
-        if (!selected?.count) {
-            return;
-        }
-        setSelected({ ownCount: 0, count: 0, collectionID: 0 });
-    };
-
     return (
         <PublicCollectionGalleryContext.Provider
             value={{
@@ -503,6 +564,9 @@ export default function PublicCollectionGallery() {
                     enableDownload={downloadEnabled}
                     fileToCollectionsMap={null}
                     collectionNameMap={null}
+                    setFilesDownloadProgressAttributesCreator={
+                        setFilesDownloadProgressAttributesCreator
+                    }
                 />
                 {blockingLoad && (
                     <LoadingOverlay>
@@ -527,12 +591,15 @@ export default function PublicCollectionGallery() {
                         UploadTypeSelectorIntent.collectPhotos
                     }
                 />
+                <FilesDownloadProgress
+                    attributesList={filesDownloadProgressAttributesList}
+                    setAttributesList={setFilesDownloadProgressAttributesList}
+                />
                 {selected.count > 0 && (
                     <SelectedFileOptions
                         downloadFilesHelper={downloadFilesHelper}
                         clearSelection={clearSelection}
                         count={selected.count}
-                        ownCount={selected.ownCount}
                     />
                 )}
             </FullScreenDropZone>

+ 14 - 4
apps/photos/src/types/gallery/index.ts

@@ -1,4 +1,4 @@
-import { CollectionDownloadProgressAttributes } from 'components/Collections/CollectionDownloadProgress';
+import { FilesDownloadProgressAttributes } from 'components/FilesDownloadProgress';
 import { CollectionSelectorAttributes } from 'components/Collections/CollectionSelector';
 import { TimeStampListItem } from 'components/PhotoList';
 import { Collection } from 'types/collection';
@@ -17,9 +17,19 @@ export type SetLoading = React.Dispatch<React.SetStateAction<boolean>>;
 export type SetCollectionSelectorAttributes = React.Dispatch<
     React.SetStateAction<CollectionSelectorAttributes>
 >;
-export type SetCollectionDownloadProgressAttributes = React.Dispatch<
-    React.SetStateAction<CollectionDownloadProgressAttributes>
->;
+export type SetFilesDownloadProgressAttributes = (
+    value:
+        | Partial<FilesDownloadProgressAttributes>
+        | ((
+              prev: FilesDownloadProgressAttributes
+          ) => FilesDownloadProgressAttributes)
+) => void;
+
+export type SetFilesDownloadProgressAttributesCreator = (
+    folderName: string,
+    collectionID?: number,
+    isHidden?: boolean
+) => SetFilesDownloadProgressAttributes;
 
 export type MergedSourceURL = {
     original: string;

+ 15 - 59
apps/photos/src/utils/collection/index.ts

@@ -12,7 +12,7 @@ import {
     updatePublicCollectionMagicMetadata,
     updateSharedCollectionMagicMetadata,
 } from 'services/collectionService';
-import { downloadFiles, downloadFilesDesktop } from 'utils/file';
+import { downloadFilesWithProgress } from 'utils/file';
 import { getAllLocalFiles, getLocalFiles } from 'services/fileService';
 import { EnteFile } from 'types/file';
 import { CustomError } from '@ente/shared/error';
@@ -34,7 +34,6 @@ import {
     SYSTEM_COLLECTION_TYPES,
     MOVE_TO_NOT_ALLOWED_COLLECTION,
     ADD_TO_NOT_ALLOWED_COLLECTION,
-    HIDDEN_ITEMS_SECTION,
     DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME,
 } from 'constants/collection';
 import { getUnixTimeInMicroSecondsWithDelta } from '@ente/shared/time';
@@ -44,14 +43,13 @@ import { getAlbumsURL } from '@ente/shared/network/api';
 import bs58 from 'bs58';
 import { t } from 'i18next';
 import isElectron from 'is-electron';
-import { SetCollectionDownloadProgressAttributes } from 'types/gallery';
+import { SetFilesDownloadProgressAttributes } from 'types/gallery';
 import ElectronAPIs from '@ente/shared/electron';
 import {
     getCollectionExportPath,
     getUniqueCollectionExportName,
 } from 'utils/export';
 import exportService from 'services/export';
-import { CollectionDownloadProgressAttributes } from 'components/Collections/CollectionDownloadProgress';
 import { addLogLine } from '@ente/shared/logging';
 
 export enum COLLECTION_OPS_TYPE {
@@ -101,7 +99,7 @@ export function getSelectedCollection(
 
 export async function downloadCollectionHelper(
     collectionID: number,
-    setCollectionDownloadProgressAttributes: SetCollectionDownloadProgressAttributes
+    setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes
 ) {
     try {
         const allFiles = await getAllLocalFiles();
@@ -117,10 +115,8 @@ export async function downloadCollectionHelper(
         }
         await downloadCollectionFiles(
             collection.name,
-            collection.id,
-            isHiddenCollection(collection),
             collectionFiles,
-            setCollectionDownloadProgressAttributes
+            setFilesDownloadProgressAttributes
         );
     } catch (e) {
         logError(e, 'download collection failed ');
@@ -128,7 +124,7 @@ export async function downloadCollectionHelper(
 }
 
 export async function downloadDefaultHiddenCollectionHelper(
-    setCollectionDownloadProgressAttributes: SetCollectionDownloadProgressAttributes
+    setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes
 ) {
     try {
         const hiddenCollections = await getLocalCollections('hidden');
@@ -140,78 +136,38 @@ export async function downloadDefaultHiddenCollectionHelper(
         );
         await downloadCollectionFiles(
             DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME,
-            HIDDEN_ITEMS_SECTION,
-            true,
             defaultHiddenCollectionFiles,
-            setCollectionDownloadProgressAttributes
+            setFilesDownloadProgressAttributes
         );
     } catch (e) {
         logError(e, 'download hidden files failed ');
     }
 }
 
-async function downloadCollectionFiles(
+export async function downloadCollectionFiles(
     collectionName: string,
-    collectionID: number,
-    isHidden: boolean,
     collectionFiles: EnteFile[],
-    setCollectionDownloadProgressAttributes: SetCollectionDownloadProgressAttributes
+    setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes
 ) {
     if (!collectionFiles.length) {
         return;
     }
-    const canceller = new AbortController();
-    const increaseSuccess = () => {
-        if (canceller.signal.aborted) return;
-        setCollectionDownloadProgressAttributes((prev) => ({
-            ...prev,
-            success: prev.success + 1,
-        }));
-    };
-    const increaseFailed = () => {
-        if (canceller.signal.aborted) return;
-        setCollectionDownloadProgressAttributes((prev) => ({
-            ...prev,
-            failed: prev.failed + 1,
-        }));
-    };
-    const isCancelled = () => canceller.signal.aborted;
-    const initialProgressAttributes: CollectionDownloadProgressAttributes = {
-        collectionName,
-        collectionID,
-        isHidden,
-        canceller,
-        total: collectionFiles.length,
-        success: 0,
-        failed: 0,
-        downloadDirPath: null,
-    };
+    let downloadDirPath: string;
     if (isElectron()) {
         const selectedDir = await ElectronAPIs.selectDirectory();
         if (!selectedDir) {
             return;
         }
-        const downloadDirPath = await createCollectionDownloadFolder(
+        downloadDirPath = await createCollectionDownloadFolder(
             selectedDir,
             collectionName
         );
-        setCollectionDownloadProgressAttributes({
-            ...initialProgressAttributes,
-            downloadDirPath,
-        });
-        await downloadFilesDesktop(
-            collectionFiles,
-            { increaseSuccess, increaseFailed, isCancelled },
-            downloadDirPath
-        );
-    } else {
-        setCollectionDownloadProgressAttributes(initialProgressAttributes);
-        await downloadFiles(collectionFiles, {
-            increaseSuccess,
-            increaseFailed,
-            isCancelled,
-        });
     }
+    await downloadFilesWithProgress(
+        collectionFiles,
+        downloadDirPath,
+        setFilesDownloadProgressAttributes
+    );
 }
 
 async function createCollectionDownloadFolder(

+ 106 - 5
apps/photos/src/utils/file/index.ts

@@ -1,4 +1,8 @@
-import { SelectedState } from 'types/gallery';
+import {
+    SelectedState,
+    SetFilesDownloadProgressAttributes,
+    SetFilesDownloadProgressAttributesCreator,
+} from 'types/gallery';
 import {
     EnteFile,
     EncryptedEnteFile,
@@ -52,6 +56,7 @@ import { getFileExportPath, getUniqueFileExportName } from 'utils/export';
 import imageProcessor from 'services/imageProcessor';
 import ElectronAPIs from '@ente/shared/electron';
 import { downloadUsingAnchor } from '@ente/shared/utils';
+import { t } from 'i18next';
 
 const WAIT_TIME_IMAGE_CONVERSION = 30 * 1000;
 
@@ -625,9 +630,96 @@ export function getUniqueFiles(files: EnteFile[]) {
     return uniqueFiles;
 }
 
+export async function downloadFilesWithProgress(
+    files: EnteFile[],
+    downloadDirPath: string,
+    setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes
+) {
+    if (!files.length) {
+        return;
+    }
+    const canceller = new AbortController();
+    const increaseSuccess = () => {
+        if (canceller.signal.aborted) return;
+        setFilesDownloadProgressAttributes((prev) => ({
+            ...prev,
+            success: prev.success + 1,
+        }));
+    };
+    const increaseFailed = () => {
+        if (canceller.signal.aborted) return;
+        setFilesDownloadProgressAttributes((prev) => ({
+            ...prev,
+            failed: prev.failed + 1,
+        }));
+    };
+    const isCancelled = () => canceller.signal.aborted;
+
+    setFilesDownloadProgressAttributes({
+        downloadDirPath,
+        success: 0,
+        failed: 0,
+        total: files.length,
+        canceller,
+    });
+
+    if (isElectron()) {
+        await downloadFilesDesktop(
+            files,
+            { increaseSuccess, increaseFailed, isCancelled },
+            downloadDirPath
+        );
+    } else {
+        await downloadFiles(files, {
+            increaseSuccess,
+            increaseFailed,
+            isCancelled,
+        });
+    }
+}
+
+export async function downloadSelectedFiles(
+    files: EnteFile[],
+    setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes
+) {
+    if (!files.length) {
+        return;
+    }
+    let downloadDirPath: string;
+    if (isElectron()) {
+        downloadDirPath = await ElectronAPIs.selectDirectory();
+        if (!downloadDirPath) {
+            return;
+        }
+    }
+    await downloadFilesWithProgress(
+        files,
+        downloadDirPath,
+        setFilesDownloadProgressAttributes
+    );
+}
+
+export async function downloadSingleFile(
+    file: EnteFile,
+    setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes
+) {
+    let downloadDirPath: string;
+    if (isElectron()) {
+        downloadDirPath = await ElectronAPIs.selectDirectory();
+        if (!downloadDirPath) {
+            return;
+        }
+    }
+    await downloadFilesWithProgress(
+        [file],
+        downloadDirPath,
+        setFilesDownloadProgressAttributes
+    );
+}
+
 export async function downloadFiles(
     files: EnteFile[],
-    progressBarUpdater?: {
+    progressBarUpdater: {
         increaseSuccess: () => void;
         increaseFailed: () => void;
         isCancelled: () => boolean;
@@ -869,7 +961,8 @@ export const handleFileOps = async (
                   files: EnteFile[];
               }
             | ((prev: { files: EnteFile[] }) => { files: EnteFile[] })
-    ) => void
+    ) => void,
+    setFilesDownloadProgressAttributesCreator: SetFilesDownloadProgressAttributesCreator
 ) => {
     switch (ops) {
         case FILE_OPS_TYPE.TRASH:
@@ -881,9 +974,17 @@ export const handleFileOps = async (
         case FILE_OPS_TYPE.HIDE:
             await hideFilesHelper(files, setTempHiddenFileIds);
             break;
-        case FILE_OPS_TYPE.DOWNLOAD:
-            await downloadFiles(files);
+        case FILE_OPS_TYPE.DOWNLOAD: {
+            const setSelectedFileDownloadProgressAttributes =
+                setFilesDownloadProgressAttributesCreator(
+                    `${files.length} ${t('FILES')}`
+                );
+            await downloadSelectedFiles(
+                files,
+                setSelectedFileDownloadProgressAttributes
+            );
             break;
+        }
         case FILE_OPS_TYPE.FIX_TIME:
             fixTimeHelper(files, setFixCreationTimeAttributes);
             break;