diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index 911ea78a8e..59bb80b954 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -1,5 +1,6 @@ import log from "@/base/log"; import { FileType } from "@/media/file-type"; +import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl"; import DownloadManager from "@/new/photos/services/download"; import type { LivePhotoSourceURL, SourceURLs } from "@/new/photos/types/file"; import { EnteFile } from "@/new/photos/types/file"; @@ -48,6 +49,7 @@ interface Props { | PHOTOS_PAGES.GALLERY | PHOTOS_PAGES.DEDUPLICATE | PHOTOS_PAGES.SHARED_ALBUMS; + mode?: GalleryBarMode; files: EnteFile[]; duplicates?: Duplicate[]; syncWithRemote: () => Promise; @@ -58,7 +60,10 @@ interface Props { selected: SelectedState; tempDeletedFileIds?: Set; setTempDeletedFileIds?: (value: Set) => void; + /** This will be set if mode is not "people". */ activeCollectionID: number; + /** This will be set if mode is "people". */ + activePersonID?: string | undefined; enableDownload?: boolean; fileToCollectionsMap: Map; collectionNameMap: Map; @@ -72,6 +77,7 @@ interface Props { const PhotoFrame = ({ page, duplicates, + mode, files, syncWithRemote, favItemIds, @@ -80,6 +86,7 @@ const PhotoFrame = ({ tempDeletedFileIds, setTempDeletedFileIds, activeCollectionID, + activePersonID, enableDownload, fileToCollectionsMap, collectionNameMap, @@ -232,7 +239,9 @@ const PhotoFrame = ({ const handleSelect = handleSelectCreator( setSelected, + mode, activeCollectionID, + activePersonID, setRangeStart, ); @@ -287,8 +296,13 @@ const PhotoFrame = ({ index, )} selected={ - selected.collectionID === activeCollectionID && - selected[item.id] + (!mode + ? selected.collectionID === activeCollectionID + : mode == selected.context?.mode && + (selected.context.mode == "people" + ? selected.context.personID == activePersonID + : selected.context.collectionID == + activeCollectionID)) && selected[item.id] } selectOnClick={selected.count > 0} onHover={onHoverOver(index)} @@ -539,8 +553,10 @@ const PhotoFrame = ({ width={width} height={height} getThumbnail={getThumbnail} + mode={mode} displayFiles={displayFiles} activeCollectionID={activeCollectionID} + activePersonID={activePersonID} showAppDownloadBanner={showAppDownloadBanner} /> ) diff --git a/web/apps/photos/src/components/PhotoList/index.tsx b/web/apps/photos/src/components/PhotoList/index.tsx index a05996a60a..2b879faf9a 100644 --- a/web/apps/photos/src/components/PhotoList/index.tsx +++ b/web/apps/photos/src/components/PhotoList/index.tsx @@ -1,3 +1,4 @@ +import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl"; import { EnteFile } from "@/new/photos/types/file"; import { formattedByteSize } from "@/new/photos/utils/units"; import { FlexWrapper } from "@ente/shared/components/Container"; @@ -189,6 +190,7 @@ const NothingContainer = styled(ListItemContainer)` interface Props { height: number; width: number; + mode?: GalleryBarMode; displayFiles: EnteFile[]; showAppDownloadBanner: boolean; getThumbnail: ( @@ -197,6 +199,7 @@ interface Props { isScrolling?: boolean, ) => JSX.Element; activeCollectionID: number; + activePersonID?: string; } interface ItemData { @@ -253,10 +256,12 @@ const PhotoListRow = React.memo( export function PhotoList({ height, width, + mode, displayFiles, showAppDownloadBanner, getThumbnail, activeCollectionID, + activePersonID, }: Props) { const galleryContext = useContext(GalleryContext); const publicCollectionGalleryContext = useContext( @@ -768,7 +773,9 @@ export function PhotoList({ const handleSelect = handleSelectCreator( galleryContext.setSelectedFiles, + mode, activeCollectionID, + activePersonID, ); const onChangeSelectAllCheckBox = (date: string) => { diff --git a/web/apps/photos/src/pages/deduplicate.tsx b/web/apps/photos/src/pages/deduplicate.tsx index 352c27d5d8..52b1620959 100644 --- a/web/apps/photos/src/pages/deduplicate.tsx +++ b/web/apps/photos/src/pages/deduplicate.tsx @@ -49,6 +49,7 @@ export default function Deduplicate() { count: 0, collectionID: 0, ownCount: 0, + context: undefined, }); const closeDeduplication = function () { Router.push(PAGES.GALLERY); @@ -97,6 +98,7 @@ export default function Deduplicate() { count: count, ownCount: count, collectionID: ALL_SECTION, + context: undefined, }; for (const fileID of toSelectFileIDs) { selectedFiles[fileID] = true; @@ -157,7 +159,12 @@ export default function Deduplicate() { }; const clearSelection = function () { - setSelected({ count: 0, collectionID: 0, ownCount: 0 }); + setSelected({ + count: 0, + collectionID: 0, + ownCount: 0, + context: undefined, + }); }; if (!duplicates) { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index cd04293049..50fb314395 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -202,6 +202,7 @@ export default function Gallery() { ownCount: 0, count: 0, collectionID: 0, + context: { mode: "albums", collectionID: ALL_SECTION }, }); const [planModalView, setPlanModalView] = useState(false); const [blockingLoad, setBlockingLoad] = useState(false); @@ -661,6 +662,13 @@ export default function Gallery() { ownCount: 0, count: 0, collectionID: activeCollectionID, + context: + barMode == "people" && activePerson + ? { mode: "people" as const, personID: activePerson.id } + : { + mode: "albums" as const, + collectionID: ensure(activeCollectionID), + }, }; filteredData.forEach((item) => { @@ -677,7 +685,12 @@ export default function Gallery() { if (!selected?.count) { return; } - setSelected({ ownCount: 0, count: 0, collectionID: 0 }); + setSelected({ + ownCount: 0, + count: 0, + collectionID: 0, + context: undefined, + }); }; const keyboardShortcutHandlerRef = useRef({ @@ -1207,6 +1220,7 @@ export default function Gallery() { ) : ( { diff --git a/web/apps/photos/src/types/gallery/index.ts b/web/apps/photos/src/types/gallery/index.ts index 2c37533708..f83773699b 100644 --- a/web/apps/photos/src/types/gallery/index.ts +++ b/web/apps/photos/src/types/gallery/index.ts @@ -1,4 +1,5 @@ import type { Collection } from "@/media/collection"; +import { type SelectionContext } from "@/new/photos/components/Gallery"; import { EnteFile } from "@/new/photos/types/file"; import type { User } from "@ente/shared/user/types"; import { CollectionSelectorAttributes } from "components/Collections/CollectionSelector"; @@ -10,6 +11,12 @@ export type SelectedState = { ownCount: number; count: number; collectionID: number; + /** + * The context in which the selection was made. Only set by newer code if + * there is an active selection (older code continues to rely on the + * {@link collectionID} logic). + */ + context: SelectionContext | undefined; }; export type SetSelectedState = React.Dispatch< React.SetStateAction diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index fdf3c8855a..fef9b72b30 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -1,7 +1,10 @@ import log from "@/base/log"; import { FileType } from "@/media/file-type"; +import type { SelectionContext } from "@/new/photos/components/Gallery"; +import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl"; import type { LivePhotoSourceURL, SourceURLs } from "@/new/photos/types/file"; import { EnteFile } from "@/new/photos/types/file"; +import { ensure } from "@/utils/ensure"; import { SetSelectedState } from "types/gallery"; export async function playVideo(livePhotoVideo, livePhotoImage) { @@ -102,7 +105,9 @@ export async function updateFileSrcProps( export const handleSelectCreator = ( setSelected: SetSelectedState, + mode: GalleryBarMode | undefined, activeCollectionID: number, + activePersonID: string | undefined, setRangeStart?, ) => (id: number, isOwnFile: boolean, index?: number) => @@ -115,10 +120,84 @@ export const handleSelectCreator = } } setSelected((selected) => { - if (selected.collectionID !== activeCollectionID) { - selected = { ownCount: 0, count: 0, collectionID: 0 }; + if (!mode) { + // Retain older behavior for non-gallery call sites. + if (selected.collectionID !== activeCollectionID) { + selected = { + ownCount: 0, + count: 0, + collectionID: 0, + context: undefined, + }; + } + } else if (!selected.context) { + // Gallery will specify a mode, but a fresh selection starts off + // without a context, so fill it in with the current context. + selected = { + ...selected, + context: + mode == "people" + ? { mode, personID: ensure(activePersonID) } + : { + mode, + collectionID: ensure(activeCollectionID), + }, + }; + } else { + // Both mode and context are defined. + if (selected.context.mode != mode) { + // Clear selection if mode has changed. + selected = { + ownCount: 0, + count: 0, + collectionID: 0, + context: + mode == "people" + ? { mode, personID: ensure(activePersonID) } + : { + mode, + collectionID: ensure(activeCollectionID), + }, + }; + } else { + if (selected.context?.mode == "people") { + if (selected.context.personID != activePersonID) { + // Clear selection if person has changed. + selected = { + ownCount: 0, + count: 0, + collectionID: 0, + context: { + mode: selected.context?.mode, + personID: ensure(activePersonID), + }, + }; + } + } else { + if ( + selected.context.collectionID != activeCollectionID + ) { + // Clear selection if collection has changed. + selected = { + ownCount: 0, + count: 0, + collectionID: 0, + context: { + mode: selected.context?.mode, + collectionID: ensure(activeCollectionID), + }, + }; + } + } + } } + const newContext: SelectionContext | undefined = !mode + ? undefined + : mode == "people" + ? { mode, personID: ensure(activePersonID) } + : { mode, collectionID: ensure(activeCollectionID) }; + const handleCounterChange = (count: number) => { if (selected[id] === checked) { return count; @@ -146,6 +225,7 @@ export const handleSelectCreator = ...selected, [id]: checked, collectionID: activeCollectionID, + context: newContext, ...handleAllCounterChange(), }; }); diff --git a/web/packages/new/photos/components/Gallery/index.tsx b/web/packages/new/photos/components/Gallery/index.tsx index 485965b8d0..72d1ec8d4c 100644 --- a/web/packages/new/photos/components/Gallery/index.tsx +++ b/web/packages/new/photos/components/Gallery/index.tsx @@ -19,6 +19,16 @@ import React from "react"; import { SpaceBetweenFlex } from "../mui-custom"; import { GalleryItemsHeaderAdapter, GalleryItemsSummary } from "./ListHeader"; +/** + * The context in which a selection was made. + * + * This allows us to reset the selection if user moves to a different context + * and starts a new selection. + * */ +export type SelectionContext = + | { mode: "albums" | "hidden-albums"; collectionID: number } + | { mode: "people"; personID: string }; + interface SearchResultsHeaderProps { selectedOption: SearchOption; }