From 038c91e652a5598693358819299c8407c5378e80 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 16:10:11 +0530 Subject: [PATCH 1/7] Move --- web/apps/photos/src/pages/gallery.tsx | 12 +-------- web/apps/photos/src/services/export/index.ts | 10 +++---- web/apps/photos/src/utils/collection.ts | 22 +-------------- .../new/photos/components/gallery/reducer.ts | 22 ++++++++++++++- .../new/photos/services/collection/index.ts | 27 +++++++++++++++++++ 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index fb1631019a..7f5742d69d 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -137,7 +137,6 @@ import { import { checkSubscriptionPurchase } from "utils/billing"; import { COLLECTION_OPS_TYPE, - constructCollectionNameMap, getSelectedCollection, handleCollectionOps, } from "utils/collection"; @@ -339,6 +338,7 @@ export default function Gallery() { const archivedCollectionIDs = state.archivedCollectionIDs; const defaultHiddenCollectionIDs = state.defaultHiddenCollectionIDs; const hiddenFileIDs = state.hiddenFileIDs; + const collectionNameMap = state.allCollectionNameByID; const collectionSummaries = state.collectionSummaries; const hiddenCollectionSummaries = state.hiddenCollectionSummaries; @@ -750,16 +750,6 @@ export default function Gallery() { return constructFileToCollectionMap(files); }, [files]); - const collectionNameMap = useMemo(() => { - if (!collections || !hiddenCollections) { - return new Map(); - } - return constructCollectionNameMap([ - ...collections, - ...hiddenCollections, - ]); - }, [collections, hiddenCollections]); - const showSessionExpiredMessage = () => { setDialogMessage(getSessionExpiredMessage(logout)); }; diff --git a/web/apps/photos/src/services/export/index.ts b/web/apps/photos/src/services/export/index.ts index ced1dd3cb4..e31e5d0494 100644 --- a/web/apps/photos/src/services/export/index.ts +++ b/web/apps/photos/src/services/export/index.ts @@ -9,6 +9,10 @@ import { } from "@/media/file-metadata"; import { FileType } from "@/media/file-type"; import { decodeLivePhoto } from "@/media/live-photo"; +import { + createCollectionNameByID, + getCollectionUserFacingName, +} from "@/new/photos/services/collection"; import downloadManager from "@/new/photos/services/download"; import { updateExifIfNeededAndPossible } from "@/new/photos/services/exif-update"; import { @@ -34,10 +38,6 @@ import { ExportUIUpdaters, FileExportNames, } from "types/export"; -import { - constructCollectionNameMap, - getCollectionUserFacingName, -} from "utils/collection"; import { getAllLocalCollections } from "../collectionService"; import { migrateExport } from "./migration"; @@ -330,7 +330,7 @@ class ExportService { convertCollectionIDExportNameObjectToMap( exportRecord.collectionExportNames, ); - const collectionIDNameMap = constructCollectionNameMap(collections); + const collectionIDNameMap = createCollectionNameByID(collections); const renamedCollections = getRenamedExportedCollections( collections, diff --git a/web/apps/photos/src/utils/collection.ts b/web/apps/photos/src/utils/collection.ts index 661ec371f0..92d49a2eae 100644 --- a/web/apps/photos/src/utils/collection.ts +++ b/web/apps/photos/src/utils/collection.ts @@ -11,8 +11,8 @@ import { import { EnteFile } from "@/media/file"; import { ItemVisibility } from "@/media/file-metadata"; import { + DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME, findDefaultHiddenCollectionIDs, - isDefaultHiddenCollection, isHiddenCollection, isIncomingShare, } from "@/new/photos/services/collection"; @@ -391,26 +391,6 @@ export function getHiddenCollections(collections: Collection[]): Collection[] { return collections.filter((collection) => isHiddenCollection(collection)); } -export function constructCollectionNameMap( - collections: Collection[], -): Map { - return new Map( - (collections ?? []).map((collection) => [ - collection.id, - getCollectionUserFacingName(collection), - ]), - ); -} - -const DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME = "Hidden"; - -export const getCollectionUserFacingName = (collection: Collection) => { - if (isDefaultHiddenCollection(collection)) { - return DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME; - } - return collection.name; -}; - export const getOrCreateAlbum = async ( albumName: string, existingCollections: Collection[], diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 4a0ccd3917..ffea09257d 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -5,7 +5,10 @@ import { } from "@/media/collection"; import type { EnteFile } from "@/media/file"; import { mergeMetadata } from "@/media/file"; -import { isHiddenCollection } from "@/new/photos/services/collection"; +import { + createCollectionNameByID, + isHiddenCollection, +} from "@/new/photos/services/collection"; import { splitByPredicate } from "@/utils/array"; import { ensure } from "@/utils/ensure"; import type { User } from "@ente/shared/user/types"; @@ -109,6 +112,13 @@ export interface GalleryState { * File IDs of all the files that the user has marked as a favorite. */ favoriteFileIDs: Set; + /** + * User visible collection names indexed by collection IDs for fast lookup. + * + * This map will contain entries for all (both normal and hidden) + * collections. + */ + allCollectionNameByID: Map; /*--< Derived UI state >--*/ @@ -183,6 +193,7 @@ const initialGalleryState: GalleryState = { defaultHiddenCollectionIDs: new Set(), hiddenFileIDs: new Set(), favoriteFileIDs: new Set(), + allCollectionNameByID: new Map(), collectionSummaries: new Map(), hiddenCollectionSummaries: new Map(), filteredData: [], @@ -222,6 +233,9 @@ const galleryReducer: React.Reducer = ( collections, action.files, ), + allCollectionNameByID: createCollectionNameByID( + action.allCollections, + ), collectionSummaries: deriveCollectionSummaries( action.user, collections, @@ -255,6 +269,9 @@ const galleryReducer: React.Reducer = ( action.collections, state.files, ), + allCollectionNameByID: createCollectionNameByID( + action.collections.concat(state.hiddenCollections), + ), collectionSummaries: deriveCollectionSummaries( ensure(state.user), action.collections, @@ -280,6 +297,9 @@ const galleryReducer: React.Reducer = ( action.collections, state.files, ), + allCollectionNameByID: createCollectionNameByID( + action.collections.concat(action.hiddenCollections), + ), collectionSummaries: deriveCollectionSummaries( ensure(state.user), action.collections, diff --git a/web/packages/new/photos/services/collection/index.ts b/web/packages/new/photos/services/collection/index.ts index 3df8bfdca8..2f87512f6f 100644 --- a/web/packages/new/photos/services/collection/index.ts +++ b/web/packages/new/photos/services/collection/index.ts @@ -41,3 +41,30 @@ export const isHiddenCollection = (collection: Collection) => // TODO: Need to audit the types // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition collection.magicMetadata?.data.visibility === ItemVisibility.hidden; + +// TODO: Does this need localizations? +export const DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME = "Hidden"; + +/** + * Return the "user facing" name of the given collection. + * + * Usually this is the same as the collection name, but it might be a different + * string for special collections like default hidden collections. + */ +export const getCollectionUserFacingName = (collection: Collection) => { + if (isDefaultHiddenCollection(collection)) { + return DEFAULT_HIDDEN_COLLECTION_USER_FACING_NAME; + } + return collection.name; +}; + +/** + * Return a map of the (user-facing) collection name, indexed by collection ID. + */ +export const createCollectionNameByID = (allCollections: Collection[]) => + new Map( + allCollections.map((collection) => [ + collection.id, + getCollectionUserFacingName(collection), + ]), + ); From 4da2f32e71f8c075ed952bbf23e9638c1f2b38fc Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 16:22:29 +0530 Subject: [PATCH 2/7] Move --- web/apps/photos/src/pages/deduplicate.tsx | 5 +++-- web/apps/photos/src/pages/gallery.tsx | 12 ++---------- web/apps/photos/src/utils/file/index.ts | 11 ----------- .../new/photos/components/gallery/reducer.ts | 10 ++++++++++ web/packages/new/photos/services/file.ts | 13 +++++++++++++ 5 files changed, 28 insertions(+), 23 deletions(-) diff --git a/web/apps/photos/src/pages/deduplicate.tsx b/web/apps/photos/src/pages/deduplicate.tsx index 242b41ceb9..67c594ccce 100644 --- a/web/apps/photos/src/pages/deduplicate.tsx +++ b/web/apps/photos/src/pages/deduplicate.tsx @@ -1,6 +1,7 @@ import { stashRedirect } from "@/accounts/services/redirect"; import { ActivityIndicator } from "@/base/components/mui/ActivityIndicator"; import { ALL_SECTION } from "@/new/photos/services/collection"; +import { createFileCollectionIDs } from "@/new/photos/services/file"; import { getLocalFiles } from "@/new/photos/services/files"; import { AppContext } from "@/new/photos/types/context"; import { VerticallyCentered } from "@ente/shared/components/Container"; @@ -28,7 +29,7 @@ import { DefaultDeduplicateContext, } from "types/deduplicate"; import { SelectedState } from "types/gallery"; -import { constructFileToCollectionMap, getSelectedFiles } from "utils/file"; +import { getSelectedFiles } from "utils/file"; export const DeduplicateContext = createContext( DefaultDeduplicateContext, @@ -114,7 +115,7 @@ export default function Deduplicate() { }, [duplicates]); const fileToCollectionsMap = useMemoSingleThreaded(() => { - return constructFileToCollectionMap(duplicateFiles); + return createFileCollectionIDs(duplicateFiles); }, [duplicateFiles]); const deleteFileHelper = async () => { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 7f5742d69d..2970f7cbe7 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -140,12 +140,7 @@ import { getSelectedCollection, handleCollectionOps, } from "utils/collection"; -import { - FILE_OPS_TYPE, - constructFileToCollectionMap, - getSelectedFiles, - handleFileOps, -} from "utils/file"; +import { FILE_OPS_TYPE, getSelectedFiles, handleFileOps } from "utils/file"; import { getSessionExpiredMessage } from "utils/ui"; import { getLocalFamilyData } from "utils/user/family"; @@ -339,6 +334,7 @@ export default function Gallery() { const defaultHiddenCollectionIDs = state.defaultHiddenCollectionIDs; const hiddenFileIDs = state.hiddenFileIDs; const collectionNameMap = state.allCollectionNameByID; + const fileToCollectionsMap = state.fileCollectionIDs; const collectionSummaries = state.collectionSummaries; const hiddenCollectionSummaries = state.hiddenCollectionSummaries; @@ -746,10 +742,6 @@ export default function Gallery() { }; }, [selectAll, clearSelection]); - const fileToCollectionsMap = useMemoSingleThreaded(() => { - return constructFileToCollectionMap(files); - }, [files]); - const showSessionExpiredMessage = () => { setDialogMessage(getSessionExpiredMessage(logout)); }; diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 4b7af55897..4adef118ea 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -517,17 +517,6 @@ export function getIDBasedSortedFiles(files: EnteFile[]) { return files.sort((a, b) => a.id - b.id); } -export function constructFileToCollectionMap(files: EnteFile[]) { - const fileToCollectionsMap = new Map(); - (files ?? []).forEach((file) => { - if (!fileToCollectionsMap.get(file.id)) { - fileToCollectionsMap.set(file.id, []); - } - fileToCollectionsMap.get(file.id).push(file.collectionID); - }); - return fileToCollectionsMap; -} - export const shouldShowAvatar = (file: EnteFile, user: User) => { if (!file || !user) { return false; diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index ffea09257d..94aaee2875 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -29,6 +29,7 @@ import type { CollectionSummaryType, } from "../../services/collection/ui"; import { + createFileCollectionIDs, getLatestVersionFiles, groupFilesByCollectionID, } from "../../services/file"; @@ -119,6 +120,10 @@ export interface GalleryState { * collections. */ allCollectionNameByID: Map; + /** + * A list of collection IDs to which a file belongs, indexed by file ID. + */ + fileCollectionIDs: Map; /*--< Derived UI state >--*/ @@ -194,6 +199,7 @@ const initialGalleryState: GalleryState = { hiddenFileIDs: new Set(), favoriteFileIDs: new Set(), allCollectionNameByID: new Map(), + fileCollectionIDs: new Map(), collectionSummaries: new Map(), hiddenCollectionSummaries: new Map(), filteredData: [], @@ -236,6 +242,7 @@ const galleryReducer: React.Reducer = ( allCollectionNameByID: createCollectionNameByID( action.allCollections, ), + fileCollectionIDs: createFileCollectionIDs(action.files), collectionSummaries: deriveCollectionSummaries( action.user, collections, @@ -323,6 +330,7 @@ const galleryReducer: React.Reducer = ( state.collections, files, ), + fileCollectionIDs: createFileCollectionIDs(action.files), collectionSummaries: deriveCollectionSummaries( ensure(state.user), state.collections, @@ -345,6 +353,7 @@ const galleryReducer: React.Reducer = ( state.collections, files, ), + fileCollectionIDs: createFileCollectionIDs(action.files), collectionSummaries: deriveCollectionSummaries( ensure(state.user), state.collections, @@ -363,6 +372,7 @@ const galleryReducer: React.Reducer = ( state.collections, files, ), + fileCollectionIDs: createFileCollectionIDs(files), // TODO: Consider batching this instead of doing it per file // upload to speed up uploads. Perf test first though. collectionSummaries: deriveCollectionSummaries( diff --git a/web/packages/new/photos/services/file.ts b/web/packages/new/photos/services/file.ts index e522bb3d34..d887b30a1c 100644 --- a/web/packages/new/photos/services/file.ts +++ b/web/packages/new/photos/services/file.ts @@ -14,6 +14,19 @@ export const groupFilesByCollectionID = (files: EnteFile[]) => return result; }, new Map()); +/** + * Construct a map from file IDs to the list of collections (IDs) to which the + * file belongs. + */ +export const createFileCollectionIDs = (files: EnteFile[]) => + files.reduce((result, file) => { + const id = file.id; + let fs = result.get(id); + if (!fs) result.set(id, (fs = [])); + fs.push(file.collectionID); + return result; + }, new Map()); + export function getLatestVersionFiles(files: EnteFile[]) { const latestVersionFiles = new Map(); files.forEach((file) => { From 489e80df4b812a31e57c217d3ca1c5f2152d8ef8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 16:42:24 +0530 Subject: [PATCH 3/7] Sketch --- .../new/photos/components/gallery/reducer.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 94aaee2875..9da9fbaa74 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -151,6 +151,15 @@ export interface GalleryState { * The list of people to show. */ people: Person[] | undefined; + /** + * List of files that match the selected search option. + * + * The search dropdown shows a list of options ("suggestions") that match + * the user's search term. If the user selects from one of these options, + * then we run a search to find all files that match that suggestion, and + * set this value to the result. + */ + searchResults: EnteFile[]; } export type GalleryAction = @@ -184,7 +193,8 @@ export type GalleryAction = | { type: "uploadFile"; file: EnteFile } | { type: "resetHiddenFiles"; hiddenFiles: EnteFile[] } | { type: "fetchHiddenFiles"; hiddenFiles: EnteFile[] } - | { type: "setTrashedFiles"; trashedFiles: EnteFile[] }; + | { type: "setTrashedFiles"; trashedFiles: EnteFile[] } + | { type: "searchResults"; searchResults: EnteFile[] }; const initialGalleryState: GalleryState = { user: undefined, @@ -205,6 +215,7 @@ const initialGalleryState: GalleryState = { filteredData: [], activePerson: undefined, people: [], + searchResults: [], }; const galleryReducer: React.Reducer = ( @@ -429,6 +440,11 @@ const galleryReducer: React.Reducer = ( state.archivedCollectionIDs, ), }; + case "searchResults": + return { + ...state, + searchResults: action.searchResults, + }; } }; From 82e72b8d8e8441b8146837b1fd503566133c6033 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 17:08:17 +0530 Subject: [PATCH 4/7] Sketch --- .../new/photos/components/gallery/reducer.ts | 26 ++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 9da9fbaa74..444e90cce7 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -151,15 +151,28 @@ export interface GalleryState { * The list of people to show. */ people: Person[] | undefined; + /** + * `true` if we are in "search mode". + * + * We will always be in search mode if we are showing search results, but we + * also may be in search mode earlier on smaller screens, where the search + * input is only shown on entering search mode. See: [Note: "Search mode"]. + * + * That is, {@link isInSearchMode} may be true even when + * {@link searchResults} is undefined. + */ + isInSearchMode: boolean; /** * List of files that match the selected search option. * + * This will be set only if we are showing search results. + * * The search dropdown shows a list of options ("suggestions") that match * the user's search term. If the user selects from one of these options, * then we run a search to find all files that match that suggestion, and * set this value to the result. */ - searchResults: EnteFile[]; + searchResults: EnteFile[] | undefined; } export type GalleryAction = @@ -194,7 +207,9 @@ export type GalleryAction = | { type: "resetHiddenFiles"; hiddenFiles: EnteFile[] } | { type: "fetchHiddenFiles"; hiddenFiles: EnteFile[] } | { type: "setTrashedFiles"; trashedFiles: EnteFile[] } - | { type: "searchResults"; searchResults: EnteFile[] }; + | { type: "searchResults"; searchResults: EnteFile[] } + | { type: "enterSearchMode" } + | { type: "exitSearch" }; const initialGalleryState: GalleryState = { user: undefined, @@ -215,7 +230,8 @@ const initialGalleryState: GalleryState = { filteredData: [], activePerson: undefined, people: [], - searchResults: [], + isInSearchMode: false, + searchResults: undefined, }; const galleryReducer: React.Reducer = ( @@ -440,11 +456,15 @@ const galleryReducer: React.Reducer = ( state.archivedCollectionIDs, ), }; + case "enterSearchMode": + return { ...state, isInSearchMode: true }; case "searchResults": return { ...state, searchResults: action.searchResults, }; + case "exitSearch": + return { ...state, searchResults: undefined }; } }; From 84ab342004ced61c11db02ab3f9d47fe78758890 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 17:22:02 +0530 Subject: [PATCH 5/7] Move --- web/apps/photos/src/pages/gallery.tsx | 19 +++++++++++-------- .../new/photos/components/gallery/reducer.ts | 6 +++++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 2970f7cbe7..039bc56ade 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -277,9 +277,6 @@ export default function Gallery() { const closeAuthenticateUserModal = () => setAuthenticateUserModalView(false); - // True if we're in "search mode". See: [Note: "search mode"]. - const [isInSearchMode, setIsInSearchMode] = useState(false); - // The option selected by the user selected from the search bar dropdown. const [selectedSearchOption, setSelectedSearchOption] = useState< SearchOption | undefined @@ -337,6 +334,7 @@ export default function Gallery() { const fileToCollectionsMap = state.fileCollectionIDs; const collectionSummaries = state.collectionSummaries; const hiddenCollectionSummaries = state.hiddenCollectionSummaries; + const isInSearchMode = state.isInSearchMode; if (process.env.NEXT_PUBLIC_ENTE_WIP_CL) { console.log("render", { collections, hiddenCollections, files }); @@ -981,7 +979,7 @@ export default function Gallery() { ) => { const type = searchOption?.suggestion.type; if (type == "collection" || type == "person") { - setIsInSearchMode(false); + dispatch({ type: "exitSearch" }); setSelectedSearchOption(undefined); if (type == "collection") { setBarMode("albums"); @@ -990,9 +988,12 @@ export default function Gallery() { setBarMode("people"); setActivePersonID(searchOption.suggestion.person.id); } - } else { - setIsInSearchMode(!!searchOption); + } else if (searchOption) { + dispatch({ type: "enterSearchMode" }); setSelectedSearchOption(searchOption); + } else { + dispatch({ type: "exitSearch" }); + setSelectedSearchOption(undefined); } setIsClipSearchResult(type == "clip"); }; @@ -1016,10 +1017,12 @@ export default function Gallery() { const handleShowCollection = (collectionID: number) => { setBarMode("albums"); setActiveCollectionID(collectionID); - setIsInSearchMode(false); + // TODO: type: "showAlbum" + // setIsInSearchMode(false); + dispatch({ type: "exitSearch" }); }; - const handleShowSearchInput = () => setIsInSearchMode(true); + const handleShowSearchInput = () => dispatch({ type: "enterSearchMode" }); const openHiddenSection: GalleryContextType["openHiddenSection"] = ( callback, diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 444e90cce7..cf423eb23b 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -464,7 +464,11 @@ const galleryReducer: React.Reducer = ( searchResults: action.searchResults, }; case "exitSearch": - return { ...state, searchResults: undefined }; + return { + ...state, + isInSearchMode: false, + searchResults: undefined, + }; } }; From e2bffffec6e24cea89464d5711351f2915464e86 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 18:56:56 +0530 Subject: [PATCH 6/7] Intermediate --- web/apps/photos/src/components/PhotoFrame.tsx | 2 +- .../pages/gallery/SelectedFileOptions.tsx | 2 +- web/apps/photos/src/pages/gallery.tsx | 93 +++++++++---------- web/apps/photos/src/utils/photoFrame/index.ts | 2 +- .../new/photos/components/gallery/BarImpl.tsx | 6 +- .../new/photos/components/gallery/reducer.ts | 89 ++++++++++++++++++ 6 files changed, 139 insertions(+), 55 deletions(-) diff --git a/web/apps/photos/src/components/PhotoFrame.tsx b/web/apps/photos/src/components/PhotoFrame.tsx index a74cde8d02..5b906194fc 100644 --- a/web/apps/photos/src/components/PhotoFrame.tsx +++ b/web/apps/photos/src/components/PhotoFrame.tsx @@ -2,7 +2,7 @@ import log from "@/base/log"; import type { LivePhotoSourceURL, SourceURLs } from "@/media/file"; import { EnteFile } from "@/media/file"; import { FileType } from "@/media/file-type"; -import type { GalleryBarMode } from "@/new/photos/components/gallery/BarImpl"; +import type { GalleryBarMode } from "@/new/photos/components/gallery/reducer"; import { TRASH_SECTION } from "@/new/photos/services/collection"; import DownloadManager from "@/new/photos/services/download"; import { PHOTOS_PAGES } from "@ente/shared/constants/pages"; diff --git a/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx b/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx index 453c6d8684..e454ccabb9 100644 --- a/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx +++ b/web/apps/photos/src/components/pages/gallery/SelectedFileOptions.tsx @@ -1,7 +1,7 @@ import { SelectionBar } from "@/base/components/Navbar"; import type { Collection } from "@/media/collection"; import type { CollectionSelectorAttributes } from "@/new/photos/components/CollectionSelector"; -import type { GalleryBarMode } from "@/new/photos/components/gallery/BarImpl"; +import type { GalleryBarMode } from "@/new/photos/components/gallery/reducer"; import { ALL_SECTION, ARCHIVE_SECTION, diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 039bc56ade..0f65f3c406 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -19,10 +19,10 @@ import { PeopleEmptyState, SearchResultsHeader, } from "@/new/photos/components/gallery"; -import type { GalleryBarMode } from "@/new/photos/components/gallery/BarImpl"; import { uniqueFilesByID, useGalleryReducer, + type GalleryBarMode, } from "@/new/photos/components/gallery/reducer"; import { usePeopleStateSnapshot } from "@/new/photos/components/utils/ml"; import { shouldShowWhatsNew } from "@/new/photos/services/changelog"; @@ -246,8 +246,6 @@ export default function Gallery() { const [userIDToEmailMap, setUserIDToEmailMap] = useState>(null); const [emailList, setEmailList] = useState(null); - const [activeCollectionID, setActiveCollectionID] = - useState(undefined); const [fixCreationTimeView, setFixCreationTimeView] = useState(false); const [fixCreationTimeAttributes, setFixCreationTimeAttributes] = useState(null); @@ -282,12 +280,6 @@ export default function Gallery() { SearchOption | undefined >(); - // If visible, what should the (sticky) gallery bar show. - const [barMode, setBarMode] = useState("albums"); - - // The ID of the currently selected person in the gallery bar (if any). - const [activePersonID, setActivePersonID] = useState(); - const peopleState = usePeopleStateSnapshot(); const [isClipSearchResult, setIsClipSearchResult] = @@ -334,6 +326,9 @@ export default function Gallery() { const fileToCollectionsMap = state.fileCollectionIDs; const collectionSummaries = state.collectionSummaries; const hiddenCollectionSummaries = state.hiddenCollectionSummaries; + const barMode = state.barMode ?? "albums"; + const activeCollectionID = state.activeCollectionID; + const activePersonID = state.activePersonID; const isInSearchMode = state.isInSearchMode; if (process.env.NEXT_PUBLIC_ENTE_WIP_CL) { @@ -373,7 +368,7 @@ export default function Gallery() { } await downloadManager.init(token); setupSelectAllKeyBoardShortcutHandler(); - setActiveCollectionID(ALL_SECTION); + dispatch({ type: "showAll" }); setIsFirstLoad(isFirstLogin()); if (justSignedUp()) { setPlanModalView(true); @@ -979,15 +974,18 @@ export default function Gallery() { ) => { const type = searchOption?.suggestion.type; if (type == "collection" || type == "person") { - dispatch({ type: "exitSearch" }); - setSelectedSearchOption(undefined); if (type == "collection") { - setBarMode("albums"); - setActiveCollectionID(searchOption.suggestion.collectionID); + dispatch({ + type: "showNormalOrHiddenCollectionSummary", + collectionSummaryID: searchOption.suggestion.collectionID, + }); } else { - setBarMode("people"); - setActivePersonID(searchOption.suggestion.person.id); + dispatch({ + type: "showPerson", + personID: searchOption.suggestion.person.id, + }); } + setSelectedSearchOption(undefined); } else if (searchOption) { dispatch({ type: "enterSearchMode" }); setSelectedSearchOption(searchOption); @@ -1014,40 +1012,34 @@ export default function Gallery() { setExportModalView(false); }; - const handleShowCollection = (collectionID: number) => { - setBarMode("albums"); - setActiveCollectionID(collectionID); - // TODO: type: "showAlbum" - // setIsInSearchMode(false); - dispatch({ type: "exitSearch" }); - }; + const handleSetActiveCollectionID = ( + collectionSummaryID: number | undefined, + ) => + dispatch({ + type: "showNormalOrHiddenCollectionSummary", + collectionSummaryID, + }); - const handleShowSearchInput = () => dispatch({ type: "enterSearchMode" }); + const handleChangeBarMode = (mode: GalleryBarMode) => + mode == "people" + ? dispatch({ type: "showPeople" }) + : dispatch({ + type: "showAll", + }); const openHiddenSection: GalleryContextType["openHiddenSection"] = ( callback, ) => { authenticateUser(() => { - setBarMode("hidden-albums"); - setActiveCollectionID(HIDDEN_ITEMS_SECTION); + dispatch({ type: "showHidden" }); callback?.(); }); }; - const exitHiddenSection = () => { - setBarMode("albums"); - setActiveCollectionID(ALL_SECTION); - }; - - const handleSelectPerson = (person: Person | undefined) => { - setActivePersonID(person?.id); - setBarMode("people"); - }; - - const handleSelectFileInfoPerson = (personID: string) => { - setActivePersonID(personID); - setBarMode("people"); - }; + const handleSelectPerson = (person: Person | undefined) => + person + ? dispatch({ type: "showPerson", personID: person.id }) + : dispatch({ type: "showPeople" }); const handleOpenCollectionSelector = useCallback( (attributes: CollectionSelectorAttributes) => { @@ -1076,8 +1068,12 @@ export default function Gallery() { value={{ ...defaultGalleryContext, showPlanSelectorModal, - setActiveCollectionID, - onShowCollection: handleShowCollection, + setActiveCollectionID: handleSetActiveCollectionID, + onShowCollection: (id) => + dispatch({ + type: "showNormalOrHiddenCollectionSummary", + collectionSummaryID: id, + }), syncWithRemote, setBlockingLoad, photoListHeader, @@ -1155,7 +1151,7 @@ export default function Gallery() { > {barMode == "hidden-albums" ? ( dispatch({ type: "showAll" })} /> ) : ( + dispatch({ type: "enterSearchMode" }), onSelectSearchOption: handleSelectSearchOption, onSelectPerson: handleSelectPerson, }} @@ -1175,11 +1172,11 @@ export default function Gallery() { {...{ shouldHide: isInSearchMode, mode: barMode, - onChangeMode: setBarMode, + onChangeMode: handleChangeBarMode, collectionSummaries, activeCollection, activeCollectionID, - setActiveCollectionID, + setActiveCollectionID: handleSetActiveCollectionID, hiddenCollectionSummaries, showPeopleSectionButton, people: galleryPeopleState?.people ?? [], @@ -1269,7 +1266,9 @@ export default function Gallery() { setFilesDownloadProgressAttributesCreator } selectable={true} - onSelectPerson={handleSelectFileInfoPerson} + onSelectPerson={(personID) => { + dispatch({ type: "showPerson", personID }); + }} /> )} {selected.count > 0 && diff --git a/web/apps/photos/src/utils/photoFrame/index.ts b/web/apps/photos/src/utils/photoFrame/index.ts index 1c52664b45..651848f1d9 100644 --- a/web/apps/photos/src/utils/photoFrame/index.ts +++ b/web/apps/photos/src/utils/photoFrame/index.ts @@ -3,7 +3,7 @@ import type { LivePhotoSourceURL, SourceURLs } from "@/media/file"; import { EnteFile } from "@/media/file"; 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 { GalleryBarMode } from "@/new/photos/components/gallery/reducer"; import { ensure } from "@/utils/ensure"; import { SetSelectedState } from "types/gallery"; diff --git a/web/packages/new/photos/components/gallery/BarImpl.tsx b/web/packages/new/photos/components/gallery/BarImpl.tsx index 1b2b2d4dc1..7cd754c240 100644 --- a/web/packages/new/photos/components/gallery/BarImpl.tsx +++ b/web/packages/new/photos/components/gallery/BarImpl.tsx @@ -42,11 +42,7 @@ import { type ListChildComponentProps, areEqual, } from "react-window"; - -/** - * Specifies what the bar is displaying currently. - */ -export type GalleryBarMode = "albums" | "hidden-albums" | "people"; +import type { GalleryBarMode } from "./reducer"; export interface GalleryBarImplProps { /** diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index cf423eb23b..a48d4634c7 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -42,6 +42,25 @@ import { import type { Person } from "../../services/ml/people"; import type { FamilyData } from "../../services/user"; +/** + * Specifies what the bar at the top of the gallery is displaying currently. + */ +export type GalleryBarMode = "albums" | "hidden-albums" | "people"; + +/** + * Specifies what the gallery is currently displaying. + * + * TODO: An experiment at consolidating state. + */ +export type GalleryFocus = + | { + type: "albums" | "hidden-albums"; + activeCollectionID: number; + activeCollection: Collection | undefined; + activeCollectionSummary: CollectionSummary; + } + | { type: "people"; activePersonID: string; activePerson: Person }; + /** * Derived UI state backing the gallery. * @@ -139,6 +158,18 @@ export interface GalleryState { /*--< Transient UI state >--*/ + /** + * If visible, what should the (sticky) gallery bar show. + */ + barMode: GalleryBarMode | undefined; + /** + * The section / area, and the item within it, that the gallery is currently + * showing. + */ + focus: GalleryFocus | undefined; + activeCollectionID: number | undefined; + activePersonID: string | undefined; + filteredData: EnteFile[]; /** * The currently selected person, if any. @@ -207,6 +238,14 @@ export type GalleryAction = | { type: "resetHiddenFiles"; hiddenFiles: EnteFile[] } | { type: "fetchHiddenFiles"; hiddenFiles: EnteFile[] } | { type: "setTrashedFiles"; trashedFiles: EnteFile[] } + | { type: "showAll" } + | { type: "showHidden" } + | { + type: "showNormalOrHiddenCollectionSummary"; + collectionSummaryID: number | undefined; + } + | { type: "showPeople" } + | { type: "showPerson"; personID: string } | { type: "searchResults"; searchResults: EnteFile[] } | { type: "enterSearchMode" } | { type: "exitSearch" }; @@ -227,6 +266,10 @@ const initialGalleryState: GalleryState = { fileCollectionIDs: new Map(), collectionSummaries: new Map(), hiddenCollectionSummaries: new Map(), + barMode: undefined, + focus: undefined, + activeCollectionID: undefined, + activePersonID: undefined, filteredData: [], activePerson: undefined, people: [], @@ -456,6 +499,52 @@ const galleryReducer: React.Reducer = ( state.archivedCollectionIDs, ), }; + case "showAll": + return { + ...state, + barMode: "albums", + activeCollectionID: ALL_SECTION, + isInSearchMode: false, + searchResults: undefined, + }; + case "showHidden": + return { + ...state, + barMode: "hidden-albums", + activeCollectionID: HIDDEN_ITEMS_SECTION, + isInSearchMode: false, + searchResults: undefined, + }; + case "showNormalOrHiddenCollectionSummary": + return { + ...state, + barMode: + action.collectionSummaryID !== undefined && + state.hiddenCollectionSummaries.has( + action.collectionSummaryID, + ) + ? "hidden-albums" + : "albums", + activeCollectionID: action.collectionSummaryID ?? ALL_SECTION, + isInSearchMode: false, + searchResults: undefined, + }; + case "showPeople": + return { + ...state, + barMode: "people", + activePersonID: undefined, + isInSearchMode: false, + searchResults: undefined, + }; + case "showPerson": + return { + ...state, + barMode: "people", + activePersonID: action.personID, + isInSearchMode: false, + searchResults: undefined, + }; case "enterSearchMode": return { ...state, isInSearchMode: true }; case "searchResults": From bd90f21618bd0bceb3b3bb895c77920597c80f44 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 21 Oct 2024 19:12:31 +0530 Subject: [PATCH 7/7] Fix --- web/apps/photos/src/pages/deduplicate.tsx | 2 +- web/apps/photos/src/pages/gallery.tsx | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/web/apps/photos/src/pages/deduplicate.tsx b/web/apps/photos/src/pages/deduplicate.tsx index 67c594ccce..c9ce265619 100644 --- a/web/apps/photos/src/pages/deduplicate.tsx +++ b/web/apps/photos/src/pages/deduplicate.tsx @@ -115,7 +115,7 @@ export default function Deduplicate() { }, [duplicates]); const fileToCollectionsMap = useMemoSingleThreaded(() => { - return createFileCollectionIDs(duplicateFiles); + return createFileCollectionIDs(duplicateFiles ?? []); }, [duplicateFiles]); const deleteFileHelper = async () => { diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index 0f65f3c406..56c5aedf84 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -1023,9 +1023,7 @@ export default function Gallery() { const handleChangeBarMode = (mode: GalleryBarMode) => mode == "people" ? dispatch({ type: "showPeople" }) - : dispatch({ - type: "showAll", - }); + : dispatch({ type: "showAll" }); const openHiddenSection: GalleryContextType["openHiddenSection"] = ( callback,