소스 검색

Merge branch 'main' into fix-1554

Abhinav Kumar 1 년 전
부모
커밋
be547a6cf5

+ 4 - 4
apps/photos/public/locales/nl/translation.json

@@ -85,9 +85,9 @@
     "ZOOM_IN_OUT": "In/uitzoomen",
     "PREVIOUS": "Vorige (←)",
     "NEXT": "Volgende (→)",
-    "TITLE_PHOTOS": "",
-    "TITLE_ALBUMS": "",
-    "TITLE_AUTH": "",
+    "TITLE_PHOTOS": "Ente Foto's",
+    "TITLE_ALBUMS": "Ente Foto's",
+    "TITLE_AUTH": "Ente Auth",
     "UPLOAD_FIRST_PHOTO": "Je eerste foto uploaden",
     "IMPORT_YOUR_FOLDERS": "Importeer uw mappen",
     "UPLOAD_DROPZONE_MESSAGE": "Sleep om een back-up van je bestanden te maken",
@@ -622,7 +622,7 @@
     "PHOTO_EDITOR": "Fotobewerker",
     "FASTER_UPLOAD": "Snellere uploads",
     "FASTER_UPLOAD_DESCRIPTION": "Uploaden door nabije servers",
-    "MAGIC_SEARCH_STATUS": "",
+    "MAGIC_SEARCH_STATUS": "Magische Zoekfunctie Status",
     "INDEXED_ITEMS": "Geïndexeerde bestanden",
     "CACHE_DIRECTORY": "Cache map"
 }

+ 2 - 2
apps/photos/src/components/Collections/CollectionSelector/index.tsx

@@ -140,8 +140,8 @@ function CollectionSelector({
                     ? t('UNHIDE_TO_COLLECTION')
                     : t('SELECT_COLLECTION')}
             </DialogTitleWithCloseButton>
-            <DialogContent>
-                <FlexWrapper flexWrap="wrap" gap={0.5}>
+            <DialogContent sx={{ '&&&': { padding: 0 } }}>
+                <FlexWrapper flexWrap="wrap" gap={'4px'} padding={'16px'}>
                     <AddCollectionButton
                         showNextModal={attributes.showNextModal}
                     />

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

@@ -12,7 +12,6 @@ import PhotoViewer from 'components/PhotoViewer';
 import { TRASH_SECTION } from 'constants/collection';
 import { updateFileMsrcProps, updateFileSrcProps } from 'utils/photoFrame';
 import { SelectedState } from 'types/gallery';
-import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
 import { useRouter } from 'next/router';
 import { logError } from '@ente/shared/sentry';
 import { addLogLine } from '@ente/shared/logging';
@@ -89,9 +88,6 @@ const PhotoFrame = ({
         [k: number]: boolean;
     }>({});
     const galleryContext = useContext(GalleryContext);
-    const publicCollectionGalleryContext = useContext(
-        PublicCollectionGalleryContext
-    );
     const [rangeStart, setRangeStart] = useState(null);
     const [currentHover, setCurrentHover] = useState(null);
     const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false);
@@ -315,9 +311,7 @@ const PhotoFrame = ({
             file={item}
             updateURL={updateURL(index)}
             onClick={onThumbnailClick(index)}
-            selectable={
-                !publicCollectionGalleryContext?.accessedThroughSharedURL
-            }
+            selectable={enableDownload}
             onSelect={handleSelect(
                 item.id,
                 item.ownerID === galleryContext.user?.id,

+ 25 - 0
apps/photos/src/components/PhotoViewer/index.tsx

@@ -192,6 +192,12 @@ function PhotoViewer(props: Iprops) {
                 case 'L':
                     onFavClick(photoSwipe?.currItem as EnteFile);
                     break;
+                case 'ArrowLeft':
+                    handleArrowClick(event, 'left');
+                    break;
+                case 'ArrowRight':
+                    handleArrowClick(event, 'right');
+                    break;
                 default:
                     break;
             }
@@ -352,6 +358,7 @@ function PhotoViewer(props: Iprops) {
             maxSpreadZoom: 5,
             index: currentIndex,
             showHideOpacity: true,
+            arrowKeys: false,
             getDoubleTapZoom(isMouseClick, item) {
                 if (isMouseClick) {
                     return 2.5;
@@ -505,6 +512,24 @@ function PhotoViewer(props: Iprops) {
         appContext.setDialogMessage(getTrashFileMessage(() => trashFile(file)));
     };
 
+    const handleArrowClick = (
+        e: KeyboardEvent,
+        direction: 'left' | 'right'
+    ) => {
+        // ignore arrow clicks if the user is typing in a text field
+        if (
+            e.target instanceof HTMLInputElement ||
+            e.target instanceof HTMLTextAreaElement
+        ) {
+            return;
+        }
+        if (direction === 'left') {
+            photoSwipe.prev();
+        } else {
+            photoSwipe.next();
+        }
+    };
+
     const updateItems = (items: EnteFile[]) => {
         try {
             if (photoSwipe) {

+ 9 - 3
apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx

@@ -12,6 +12,7 @@ import UploadProgressContext from 'contexts/uploadProgress';
 import { t } from 'i18next';
 
 import { UPLOAD_STAGES } from 'constants/upload';
+import { CaptionedText } from 'components/CaptionedText';
 
 export const InProgressSection = () => {
     const { inProgressUploads, hasLivePhotos, uploadFileNames, uploadStage } =
@@ -44,9 +45,14 @@ export const InProgressSection = () => {
     return (
         <UploadProgressSection>
             <UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
-                {uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA
-                    ? t('INPROGRESS_METADATA_EXTRACTION')
-                    : t('INPROGRESS_UPLOADS')}
+                <CaptionedText
+                    mainText={
+                        uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA
+                            ? t('INPROGRESS_METADATA_EXTRACTION')
+                            : t('INPROGRESS_UPLOADS')
+                    }
+                    subText={String(inProgressUploads?.length ?? 0)}
+                />
             </UploadProgressSectionTitle>
             <UploadProgressSectionContent>
                 {hasLivePhotos && (

+ 6 - 3
apps/photos/src/components/Upload/UploadProgress/resultSection.tsx

@@ -1,6 +1,5 @@
-import React, { useContext } from 'react';
+import { useContext } from 'react';
 import ItemList from 'components/ItemList';
-import { Typography } from '@mui/material';
 import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
 import { ResultItemContainer } from './styledComponents';
 import { UPLOAD_RESULT } from 'constants/upload';
@@ -11,6 +10,7 @@ import {
     UploadProgressSectionTitle,
 } from './section';
 import UploadProgressContext from 'contexts/uploadProgress';
+import { CaptionedText } from 'components/CaptionedText';
 
 export interface ResultSectionProps {
     uploadResult: UPLOAD_RESULT;
@@ -46,7 +46,10 @@ export const ResultSection = (props: ResultSectionProps) => {
     return (
         <UploadProgressSection>
             <UploadProgressSectionTitle expandIcon={<ExpandMoreIcon />}>
-                <Typography> {props.sectionTitle}</Typography>
+                <CaptionedText
+                    mainText={props.sectionTitle}
+                    subText={String(fileList?.length ?? 0)}
+                />
             </UploadProgressSectionTitle>
             <UploadProgressSectionContent>
                 {props.sectionInfo && (

+ 20 - 2
apps/photos/src/components/pages/gallery/PreviewCard.tsx

@@ -1,6 +1,6 @@
 import React, { useContext, useEffect, useRef, useState } from 'react';
 import { EnteFile } from 'types/file';
-import { styled } from '@mui/material';
+import { Tooltip, styled } from '@mui/material';
 import PlayCircleOutlineOutlinedIcon from '@mui/icons-material/PlayCircleOutlineOutlined';
 import DownloadManager from 'services/download';
 import useLongPress from '@ente/shared/hooks/useLongPress';
@@ -298,7 +298,7 @@ export default function PreviewCard(props: IProps) {
         }
     };
 
-    return (
+    const renderFn = () => (
         <Cont
             key={`thumb-${file.id}}`}
             onClick={handleClick}
@@ -360,4 +360,22 @@ export default function PreviewCard(props: IProps) {
             )}
         </Cont>
     );
+
+    if (deduplicateContext.isOnDeduplicatePage) {
+        return (
+            <Tooltip
+                placement="bottom-start"
+                enterDelay={300}
+                enterNextDelay={100}
+                title={`${
+                    file.metadata.title
+                } - ${deduplicateContext.collectionNameMap.get(
+                    file.collectionID
+                )}`}>
+                {renderFn()}
+            </Tooltip>
+        );
+    } else {
+        return renderFn();
+    }
 }

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

@@ -0,0 +1,49 @@
+import { useContext } from 'react';
+import { FluidContainer } from '@ente/shared/components/Container';
+import { SelectionBar } from '@ente/shared/components/Navbar/SelectionBar';
+import { AppContext } from 'pages/_app';
+import { Box, IconButton, Stack, Tooltip } from '@mui/material';
+import CloseIcon from '@mui/icons-material/Close';
+import DownloadIcon from '@mui/icons-material/Download';
+import { t } from 'i18next';
+import { formatNumber } from 'utils/number/format';
+
+interface Props {
+    count: number;
+    ownCount: number;
+    clearSelection: () => void;
+    downloadFilesHelper: () => void;
+}
+
+const SelectedFileOptions = ({
+    downloadFilesHelper,
+    count,
+    ownCount,
+    clearSelection,
+}: Props) => {
+    const { isMobile } = useContext(AppContext);
+
+    return (
+        <SelectionBar isMobile={isMobile}>
+            <FluidContainer>
+                <IconButton onClick={clearSelection}>
+                    <CloseIcon />
+                </IconButton>
+                <Box ml={1.5}>
+                    {formatNumber(count)} {t('SELECTED')}{' '}
+                    {ownCount !== count &&
+                        `(${formatNumber(ownCount)} ${t('YOURS')})`}
+                </Box>
+            </FluidContainer>
+            <Stack spacing={2} direction="row" mr={2}>
+                <Tooltip title={t('DOWNLOAD')}>
+                    <IconButton onClick={downloadFilesHelper}>
+                        <DownloadIcon />
+                    </IconButton>
+                </Tooltip>
+            </Stack>
+        </SelectionBar>
+    );
+};
+
+export default SelectedFileOptions;

+ 41 - 4
apps/photos/src/pages/shared-albums/index.tsx

@@ -16,7 +16,13 @@ import {
 } from 'services/publicCollectionService';
 import { Collection } from 'types/collection';
 import { EnteFile } from 'types/file';
-import { downloadFile, mergeMetadata, sortFiles } from 'utils/file';
+import {
+    downloadFile,
+    downloadFiles,
+    getSelectedFiles,
+    mergeMetadata,
+    sortFiles,
+} from 'utils/file';
 import { AppContext } from 'pages/_app';
 import { PublicCollectionGalleryContext } from 'utils/publicCollectionGallery';
 import { CustomError, parseSharingErrorCodes } from '@ente/shared/error';
@@ -52,7 +58,7 @@ import UploadButton from 'components/Upload/UploadButton';
 import bs58 from 'bs58';
 import AddPhotoAlternateOutlined from '@mui/icons-material/AddPhotoAlternateOutlined';
 import ComlinkCryptoWorker from '@ente/shared/crypto';
-import { UploadTypeSelectorIntent } from 'types/gallery';
+import { SelectedState, 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';
@@ -60,6 +66,7 @@ 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 SelectedFileOptions from 'components/pages/sharedAlbum/SelectedFileOptions';
 
 export default function PublicCollectionGallery() {
     const token = useRef<string>(null);
@@ -87,6 +94,12 @@ export default function PublicCollectionGallery() {
     const [blockingLoad, setBlockingLoad] = useState(false);
     const [shouldDisableDropzone, setShouldDisableDropzone] = useState(false);
 
+    const [selected, setSelected] = useState<SelectedState>({
+        ownCount: 0,
+        count: 0,
+        collectionID: 0,
+    });
+
     const {
         getRootProps: getDragAndDropRootProps,
         getInputProps: getDragAndDropInputProps,
@@ -441,6 +454,22 @@ 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;
+        }
+        setSelected({ ownCount: 0, count: 0, collectionID: 0 });
+    };
+
     return (
         <PublicCollectionGalleryContext.Provider
             value={{
@@ -468,8 +497,8 @@ export default function PublicCollectionGallery() {
                     page={PAGES.SHARED_ALBUMS}
                     files={publicFiles}
                     syncWithRemote={syncWithRemote}
-                    setSelected={() => null}
-                    selected={{ count: 0, collectionID: null, ownCount: 0 }}
+                    setSelected={setSelected}
+                    selected={selected}
                     activeCollectionID={ALL_SECTION}
                     enableDownload={downloadEnabled}
                     fileToCollectionsMap={null}
@@ -498,6 +527,14 @@ export default function PublicCollectionGallery() {
                         UploadTypeSelectorIntent.collectPhotos
                     }
                 />
+                {selected.count > 0 && (
+                    <SelectedFileOptions
+                        downloadFilesHelper={downloadFilesHelper}
+                        clearSelection={clearSelection}
+                        count={selected.count}
+                        ownCount={selected.ownCount}
+                    />
+                )}
             </FullScreenDropZone>
         </PublicCollectionGalleryContext.Provider>
     );

+ 6 - 2
apps/photos/src/services/deduplicationService.ts

@@ -26,7 +26,11 @@ export async function getDuplicates(
     collectionNameMap: Map<number, string>
 ) {
     try {
-        const dupes = await fetchDuplicateFileIDs();
+        const ascDupes = await fetchDuplicateFileIDs();
+
+        const descSortedDupes = ascDupes.sort((firstDupe, secondDupe) => {
+            return secondDupe.size - firstDupe.size;
+        });
 
         const fileMap = new Map<number, EnteFile>();
         for (const file of files) {
@@ -35,7 +39,7 @@ export async function getDuplicates(
 
         let result: Duplicate[] = [];
 
-        for (const dupe of dupes) {
+        for (const dupe of descSortedDupes) {
             let duplicateFiles: EnteFile[] = [];
             for (const fileID of dupe.fileIDs) {
                 if (fileMap.has(fileID)) {