Custom File download location (#1565)

This commit is contained in:
Abhinav Kumar 2024-01-24 13:59:25 +05:30 committed by GitHub
commit f5a0393c7c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 514 additions and 332 deletions

View file

@ -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),
}}
/>
))}
</>
);
};

View file

@ -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;
}

View file

@ -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
);
}
};

View file

@ -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}
/>
</>
);
}

View file

@ -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),
}}
/>
))}
</>
);
};

View file

@ -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>
);

View file

@ -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>

View file

@ -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"

View file

@ -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}>

View file

@ -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 &&

View file

@ -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,15 +507,6 @@ export default function PublicCollectionGallery() {
}
}
const downloadFilesHelper = async () => {
try {
const selectedFiles = getSelectedFiles(selected, publicFiles);
await downloadFiles(selectedFiles);
} catch (e) {
logError(e, 'failed to download selected files');
}
};
const clearSelection = () => {
if (!selected?.count) {
return;
@ -470,6 +514,23 @@ export default function PublicCollectionGallery() {
setSelected({ ownCount: 0, count: 0, collectionID: 0 });
};
const downloadFilesHelper = async () => {
try {
const selectedFiles = getSelectedFiles(selected, publicFiles);
const setFilesDownloadProgressAttributes =
setFilesDownloadProgressAttributesCreator(
`${selectedFiles.length} ${t('FILES')}`
);
await downloadSelectedFiles(
selectedFiles,
setFilesDownloadProgressAttributes
);
clearSelection();
} catch (e) {
logError(e, 'failed to download selected files');
}
};
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>

View file

@ -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;

View file

@ -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(

View file

@ -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;