diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx index b22c4d3a1c..456b678a7f 100644 --- a/web/apps/photos/src/pages/gallery.tsx +++ b/web/apps/photos/src/pages/gallery.tsx @@ -63,7 +63,6 @@ import { PHOTOS_PAGES as PAGES } from "@ente/shared/constants/pages"; import { getRecoveryKey } from "@ente/shared/crypto/helpers"; import { CustomError } from "@ente/shared/error"; import { useFileInput } from "@ente/shared/hooks/useFileInput"; -import useMemoSingleThreaded from "@ente/shared/hooks/useMemoSingleThreaded"; import { LS_KEYS, getData } from "@ente/shared/storage/localStorage"; import { getToken, @@ -459,15 +458,17 @@ export default function Gallery() { } }, [isInSearchMode, state.searchSuggestion, state.searchResults]); - // TODO: Make this a normal useEffect. - useMemoSingleThreaded(async () => { - if (state.searchSuggestion) { - const searchResults = await filterSearchableFiles( - state.searchSuggestion, + useEffect(() => { + const pendingSearchSuggestion = state.pendingSearchSuggestions.at(-1); + if (!state.isRecomputingSearchResults && pendingSearchSuggestion) { + dispatch({ type: "updatingSearchResults" }); + filterSearchableFiles(pendingSearchSuggestion).then( + (searchResults) => { + dispatch({ type: "setSearchResults", searchResults }); + }, ); - dispatch({ type: "setSearchResults", searchResults }); } - }, [state.searchSuggestion]); + }, [state.isRecomputingSearchResults, state.pendingSearchSuggestions]); const selectAll = (e: KeyboardEvent) => { // ignore ctrl/cmd + a if the user is typing in a text field diff --git a/web/packages/new/photos/components/gallery/reducer.ts b/web/packages/new/photos/components/gallery/reducer.ts index 75e2b3acb1..2730d6b6b1 100644 --- a/web/packages/new/photos/components/gallery/reducer.ts +++ b/web/packages/new/photos/components/gallery/reducer.ts @@ -262,6 +262,19 @@ export interface GalleryState { * set this value to the result. */ searchResults: EnteFile[] | undefined; + /** + * `true` an external effect is currently (asynchronously) updating search + * results. + */ + isRecomputingSearchResults: boolean; + /** + * {@link SearchSuggestion}s that have been enqueued while we were + * recomputing the current search results. + * + * We only need the last element of this array (if any), the rest are + * discarded when we get around to processing these. + */ + pendingSearchSuggestions: SearchSuggestion[]; /*--< Transient UI state >--*/ @@ -331,6 +344,7 @@ export type GalleryAction = | { type: "showPeople" } | { type: "showPerson"; personID: string } | { type: "enterSearchMode"; searchSuggestion?: SearchSuggestion } + | { type: "updatingSearchResults" } | { type: "setSearchResults"; searchResults: EnteFile[] } | { type: "exitSearch" }; @@ -358,6 +372,8 @@ const initialGalleryState: GalleryState = { extraVisiblePerson: undefined, searchSuggestion: undefined, searchResults: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view: undefined, filteredFiles: [], isInSearchMode: false, @@ -691,6 +707,8 @@ const galleryReducer: React.Reducer = ( extraVisiblePerson: undefined, searchSuggestion: undefined, searchResults: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view: { type: "albums", activeCollectionSummaryID: ALL_SECTION, @@ -706,6 +724,8 @@ const galleryReducer: React.Reducer = ( extraVisiblePerson: undefined, searchSuggestion: undefined, searchResults: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view: { type: "hidden-albums", activeCollectionSummaryID: HIDDEN_ITEMS_SECTION, @@ -727,6 +747,8 @@ const galleryReducer: React.Reducer = ( extraVisiblePerson: undefined, searchSuggestion: undefined, searchResults: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view, isInSearchMode: false, }); @@ -739,6 +761,8 @@ const galleryReducer: React.Reducer = ( extraVisiblePerson: undefined, searchResults: undefined, searchSuggestion: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view: { type: action.collectionSummaryID !== undefined && @@ -770,6 +794,8 @@ const galleryReducer: React.Reducer = ( extraVisiblePerson, searchResults: undefined, searchSuggestion: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view, isInSearchMode: false, }); @@ -789,22 +815,41 @@ const galleryReducer: React.Reducer = ( extraVisiblePerson, searchResults: undefined, searchSuggestion: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], view, isInSearchMode: false, }); } - case "enterSearchMode": + case "enterSearchMode": { + const pendingSearchSuggestions = action.searchSuggestion + ? [...state.pendingSearchSuggestions, action.searchSuggestion] + : state.pendingSearchSuggestions; + return stateByUpdatingFilteredFiles({ ...state, isInSearchMode: true, searchSuggestion: action.searchSuggestion, + pendingSearchSuggestions, + }); + } + + case "updatingSearchResults": + return stateByUpdatingFilteredFiles({ + ...state, + isRecomputingSearchResults: true, + pendingSearchSuggestions: [], }); case "setSearchResults": + // Discard stale updates + if (!state.isRecomputingSearchResults) return state; + return stateByUpdatingFilteredFiles({ ...state, searchResults: action.searchResults, + isRecomputingSearchResults: false, }); case "exitSearch": @@ -812,6 +857,8 @@ const galleryReducer: React.Reducer = ( ...state, searchResults: undefined, searchSuggestion: undefined, + isRecomputingSearchResults: false, + pendingSearchSuggestions: [], isInSearchMode: false, }); }