diff --git a/apps/photos/public/locales/nl/translation.json b/apps/photos/public/locales/nl/translation.json index 1f0fda828..43b365956 100644 --- a/apps/photos/public/locales/nl/translation.json +++ b/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" } diff --git a/apps/photos/src/components/Collections/CollectionSelector/index.tsx b/apps/photos/src/components/Collections/CollectionSelector/index.tsx index 43911abca..bbb590071 100644 --- a/apps/photos/src/components/Collections/CollectionSelector/index.tsx +++ b/apps/photos/src/components/Collections/CollectionSelector/index.tsx @@ -140,8 +140,8 @@ function CollectionSelector({ ? t('UNHIDE_TO_COLLECTION') : t('SELECT_COLLECTION')} - - + + diff --git a/apps/photos/src/components/PhotoFrame.tsx b/apps/photos/src/components/PhotoFrame.tsx index 77953d230..a45ea1510 100644 --- a/apps/photos/src/components/PhotoFrame.tsx +++ b/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, diff --git a/apps/photos/src/components/PhotoViewer/index.tsx b/apps/photos/src/components/PhotoViewer/index.tsx index 0aa4f3197..eac140946 100644 --- a/apps/photos/src/components/PhotoViewer/index.tsx +++ b/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) { diff --git a/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx b/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx index 98d6d48cc..718636804 100644 --- a/apps/photos/src/components/Upload/UploadProgress/inProgressSection.tsx +++ b/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 ( }> - {uploadStage === UPLOAD_STAGES.EXTRACTING_METADATA - ? t('INPROGRESS_METADATA_EXTRACTION') - : t('INPROGRESS_UPLOADS')} + {hasLivePhotos && ( diff --git a/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx b/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx index ca5e0e276..6149458b0 100644 --- a/apps/photos/src/components/Upload/UploadProgress/resultSection.tsx +++ b/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 ( }> - {props.sectionTitle} + {props.sectionInfo && ( diff --git a/apps/photos/src/components/pages/gallery/PreviewCard.tsx b/apps/photos/src/components/pages/gallery/PreviewCard.tsx index 8bc49d0ae..6d24ac055 100644 --- a/apps/photos/src/components/pages/gallery/PreviewCard.tsx +++ b/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 = () => ( ); + + if (deduplicateContext.isOnDeduplicatePage) { + return ( + + {renderFn()} + + ); + } else { + return renderFn(); + } } diff --git a/apps/photos/src/components/pages/sharedAlbum/SelectedFileOptions.tsx b/apps/photos/src/components/pages/sharedAlbum/SelectedFileOptions.tsx new file mode 100644 index 000000000..ed839cd4e --- /dev/null +++ b/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 ( + + + + + + + {formatNumber(count)} {t('SELECTED')}{' '} + {ownCount !== count && + `(${formatNumber(ownCount)} ${t('YOURS')})`} + + + + + + + + + + + ); +}; + +export default SelectedFileOptions; diff --git a/apps/photos/src/pages/shared-albums/index.tsx b/apps/photos/src/pages/shared-albums/index.tsx index 112f273e7..0efc1451e 100644 --- a/apps/photos/src/pages/shared-albums/index.tsx +++ b/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(null); @@ -87,6 +94,12 @@ export default function PublicCollectionGallery() { const [blockingLoad, setBlockingLoad] = useState(false); const [shouldDisableDropzone, setShouldDisableDropzone] = useState(false); + const [selected, setSelected] = useState({ + 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 ( 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 && ( + + )} ); diff --git a/apps/photos/src/services/deduplicationService.ts b/apps/photos/src/services/deduplicationService.ts index 9cbb11e97..4daeccd51 100644 --- a/apps/photos/src/services/deduplicationService.ts +++ b/apps/photos/src/services/deduplicationService.ts @@ -26,7 +26,11 @@ export async function getDuplicates( collectionNameMap: Map ) { try { - const dupes = await fetchDuplicateFileIDs(); + const ascDupes = await fetchDuplicateFileIDs(); + + const descSortedDupes = ascDupes.sort((firstDupe, secondDupe) => { + return secondDupe.size - firstDupe.size; + }); const fileMap = new Map(); 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)) {