diff --git a/web/apps/photos/src/pages/gallery.tsx b/web/apps/photos/src/pages/gallery.tsx
index 868e8e3a0b..59966ed4f3 100644
--- a/web/apps/photos/src/pages/gallery.tsx
+++ b/web/apps/photos/src/pages/gallery.tsx
@@ -20,6 +20,9 @@ import {
SearchResultsHeader,
} from "@/new/photos/components/gallery";
import {
+ deriveAlbumishFilteredFiles,
+ derivePeopleFilteredFiles,
+ deriveTrashFilteredFiles,
uniqueFilesByID,
useGalleryReducer,
type GalleryBarMode,
@@ -28,9 +31,7 @@ import { usePeopleStateSnapshot } from "@/new/photos/components/utils/ml";
import { shouldShowWhatsNew } from "@/new/photos/services/changelog";
import {
ALL_SECTION,
- ARCHIVE_SECTION,
DUMMY_UNCATEGORIZED_COLLECTION,
- HIDDEN_ITEMS_SECTION,
TRASH_SECTION,
isHiddenCollection,
} from "@/new/photos/services/collection";
@@ -41,7 +42,6 @@ import {
getLocalTrashedFiles,
sortFiles,
} from "@/new/photos/services/files";
-import { isArchivedFile } from "@/new/photos/services/magic-metadata";
import type { Person } from "@/new/photos/services/ml/people";
import {
filterSearchableFiles,
@@ -110,7 +110,6 @@ import {
useCallback,
useContext,
useEffect,
- useMemo,
useRef,
useState,
} from "react";
@@ -305,12 +304,10 @@ export default function Gallery() {
const user = state.user;
const familyData = state.familyData;
const collections = state.collections;
- const hiddenCollections = state.hiddenCollections;
const files = state.files;
const hiddenFiles = state.hiddenFiles;
const trashedFiles = state.trashedFiles;
const archivedCollectionIDs = state.archivedCollectionIDs;
- const defaultHiddenCollectionIDs = state.defaultHiddenCollectionIDs;
const hiddenFileIDs = state.hiddenFileIDs;
const collectionNameMap = state.allCollectionNameByID;
const fileToCollectionsMap = state.fileCollectionIDs;
@@ -318,14 +315,20 @@ export default function Gallery() {
const hiddenCollectionSummaries = state.hiddenCollectionSummaries;
const tempDeletedFileIDs = state.tempDeletedFileIDs;
const tempHiddenFileIDs = state.tempHiddenFileIDs;
- const barMode = state.barMode ?? "albums";
- const activeCollectionID = state.activeCollectionID;
- const activePersonID = state.activePersonID;
+ const barMode = state.view?.type ?? "albums";
+ const activeCollectionID =
+ state.view?.type == "people"
+ ? undefined
+ : state.view?.activeCollectionSummaryID;
+ const activeCollection =
+ state.view?.type == "people" ? undefined : state.view?.activeCollection;
+ const activePerson =
+ state.view?.type == "people" ? state.view.activePerson : undefined;
+ const activePersonID = activePerson?.id;
const isInSearchMode = state.isInSearchMode;
+ const filteredFiles = state.filteredFiles;
- if (process.env.NEXT_PUBLIC_ENTE_WIP_CL) {
- console.log("render", { collections, hiddenCollections, files });
- }
+ if (process.env.NEXT_PUBLIC_ENTE_WIP_CL) console.log("render", { state });
const router = useRouter();
@@ -478,15 +481,6 @@ export default function Gallery() {
}
}, [isInSearchMode, selectedSearchOption]);
- const activeCollection = useMemo(() => {
- if (!collections || !hiddenCollections) {
- return null;
- }
- return [...collections, ...hiddenCollections].find(
- (collection) => collection.id === activeCollectionID,
- );
- }, [collections, activeCollectionID]);
-
// TODO: Make this a normal useEffect.
useMemoSingleThreaded(async () => {
if (
@@ -497,147 +491,28 @@ export default function Gallery() {
!archivedCollectionIDs
) {
dispatch({
- type: "set",
- filteredData: [],
- galleryPeopleState: undefined,
+ type: "setFilteredFiles",
+ filteredFiles: [],
});
return;
}
- let filteredFiles: EnteFile[] = [];
- let galleryPeopleState:
- | { activePerson: Person | undefined; people: Person[] }
- | undefined;
+ let filteredFiles: EnteFile[];
if (selectedSearchOption) {
filteredFiles = await filterSearchableFiles(
selectedSearchOption.suggestion,
);
- } else if (barMode == "people") {
- let filteredPeople = peopleState?.people ?? [];
- let filteredVisiblePeople = peopleState?.visiblePeople ?? [];
- if (tempDeletedFileIDs?.size ?? tempHiddenFileIDs?.size) {
- // Prune the in-memory temp updates from the actual state to
- // obtain the UI state. Kept inside an preflight check to so
- // that the common path remains fast.
- const filterTemp = (ps: Person[]) =>
- ps
- .map((p) => ({
- ...p,
- fileIDs: p.fileIDs.filter(
- (id) =>
- !tempDeletedFileIDs?.has(id) &&
- !tempHiddenFileIDs?.has(id),
- ),
- }))
- .filter((p) => p.fileIDs.length > 0);
- filteredPeople = filterTemp(filteredPeople);
- filteredVisiblePeople = filterTemp(filteredVisiblePeople);
- }
- const findByID = (ps: Person[]) =>
- ps.find((p) => p.id == activePersonID);
- let activePerson = findByID(filteredVisiblePeople);
- if (!activePerson) {
- // This might be one of the normally hidden small clusters.
- activePerson = findByID(filteredPeople);
- if (activePerson) {
- // Temporarily add this person's entry to the list of people
- // surfaced in the people section.
- filteredVisiblePeople.push(activePerson);
- } else {
- // We don't have an "All" pseudo-album in people mode, so
- // default to the first person in the list.
- activePerson = filteredVisiblePeople[0];
- }
- }
- const pfSet = new Set(activePerson?.fileIDs ?? []);
- filteredFiles = uniqueFilesByID(
- files.filter(({ id }) => {
- if (!pfSet.has(id)) return false;
- return true;
- }),
- );
- galleryPeopleState = {
- activePerson,
- people: filteredVisiblePeople,
- };
+ } else if (state.view?.type == "people") {
+ filteredFiles = derivePeopleFilteredFiles(state, state.view);
} else if (activeCollectionID === TRASH_SECTION) {
- filteredFiles = uniqueFilesByID([
- ...trashedFiles,
- ...files.filter((file) => tempDeletedFileIDs?.has(file.id)),
- ]);
+ filteredFiles = deriveTrashFilteredFiles(state);
} else {
- const baseFiles = barMode == "hidden-albums" ? hiddenFiles : files;
- filteredFiles = uniqueFilesByID(
- baseFiles.filter((item) => {
- if (tempDeletedFileIDs?.has(item.id)) {
- return false;
- }
-
- if (
- barMode != "hidden-albums" &&
- tempHiddenFileIDs?.has(item.id)
- ) {
- return false;
- }
-
- // archived collections files can only be seen in their respective collection
- if (archivedCollectionIDs.has(item.collectionID)) {
- if (activeCollectionID === item.collectionID) {
- return true;
- } else {
- return false;
- }
- }
-
- // HIDDEN ITEMS SECTION - show all individual hidden files
- if (
- activeCollectionID === HIDDEN_ITEMS_SECTION &&
- defaultHiddenCollectionIDs.has(item.collectionID)
- ) {
- return true;
- }
-
- // Archived files can only be seen in archive section or their respective collection
- if (isArchivedFile(item)) {
- if (
- activeCollectionID === ARCHIVE_SECTION ||
- activeCollectionID === item.collectionID
- ) {
- return true;
- } else {
- return false;
- }
- }
-
- // ALL SECTION - show all files
- if (activeCollectionID === ALL_SECTION) {
- // show all files except the ones in hidden collections
- if (hiddenFileIDs.has(item.id)) {
- return false;
- } else {
- return true;
- }
- }
-
- // COLLECTION SECTION - show files in the active collection
- if (activeCollectionID === item.collectionID) {
- return true;
- } else {
- return false;
- }
- }),
- );
- const sortAsc =
- activeCollection?.pubMagicMetadata?.data?.asc ?? false;
- if (sortAsc) {
- filteredFiles = sortFiles(filteredFiles, true);
- }
+ filteredFiles = deriveAlbumishFilteredFiles(state);
}
dispatch({
- type: "set",
- filteredData: filteredFiles,
- galleryPeopleState,
+ type: "setFilteredFiles",
+ filteredFiles,
});
}, [
barMode,
@@ -654,8 +529,6 @@ export default function Gallery() {
activePersonID,
]);
- const { filteredData, ...galleryPeopleState } = state;
-
const selectAll = (e: KeyboardEvent) => {
// ignore ctrl/cmd + a if the user is typing in a text field
if (
@@ -675,7 +548,7 @@ export default function Gallery() {
exportModalView ||
authenticateUserModalView ||
isPhotoSwipeOpen ||
- !filteredData?.length ||
+ !filteredFiles?.length ||
!user
) {
return;
@@ -686,10 +559,10 @@ export default function Gallery() {
count: 0,
collectionID: activeCollectionID,
context:
- barMode == "people" && galleryPeopleState?.activePerson?.id
+ barMode == "people" && activePersonID
? {
mode: "people" as const,
- personID: galleryPeopleState.activePerson.id,
+ personID: activePersonID,
}
: {
mode: barMode as "albums" | "hidden-albums",
@@ -697,7 +570,7 @@ export default function Gallery() {
},
};
- filteredData.forEach((item) => {
+ filteredFiles.forEach((item) => {
if (item.ownerID === user.id) {
selected.ownCount++;
}
@@ -871,7 +744,7 @@ export default function Gallery() {
startLoading();
try {
setOpenCollectionSelector(false);
- const selectedFiles = getSelectedFiles(selected, filteredData);
+ const selectedFiles = getSelectedFiles(selected, filteredFiles);
const toProcessFiles =
ops === COLLECTION_OPS_TYPE.REMOVE
? selectedFiles
@@ -907,7 +780,7 @@ export default function Gallery() {
// passing files here instead of filteredData for hide ops because we want to move all files copies to hidden collection
const selectedFiles = getSelectedFiles(
selected,
- ops === FILE_OPS_TYPE.HIDE ? files : filteredData,
+ ops === FILE_OPS_TYPE.HIDE ? files : filteredFiles,
);
const toProcessFiles =
ops === FILE_OPS_TYPE.DOWNLOAD
@@ -1021,7 +894,7 @@ export default function Gallery() {
const handleChangeBarMode = (mode: GalleryBarMode) =>
mode == "people"
? dispatch({ type: "showPeople" })
- : dispatch({ type: "showAll" });
+ : dispatch({ type: "showAlbums" });
const openHiddenSection: GalleryContextType["openHiddenSection"] = (
callback,
@@ -1050,7 +923,7 @@ export default function Gallery() {
[],
);
- if (!user || !filteredData) {
+ if (!user || !filteredFiles) {
// Don't render until we get the logged in user and dispatch "mount".
return
;
}
@@ -1147,7 +1020,7 @@ export default function Gallery() {
>
{barMode == "hidden-albums" ? (
dispatch({ type: "showAll" })}
+ onBack={() => dispatch({ type: "showAlbums" })}
/>
) : (
) : (
;
-
/**
* Variant of {@link tempDeletedFileIDs} for files that have just been
* hidden.
*/
tempHiddenFileIDs: Set;
- /*--< Transient UI state >--*/
+ /*--< State that underlies transient UI state >--*/
/**
- * If visible, what should the (sticky) gallery bar show.
+ * The currently selected collection summary, if any.
+ *
+ * When present, this is used to derive the
+ * {@link activeCollectionSummaryID} property of the {@link view}.
+ *
+ * UI code should use the {@link view}, this property is meant as the
+ * underlying primitive state. In particular, this does not get reset when
+ * we switch sections, which allows us to come back to the same active
+ * collection (if possible) on switching back.
*/
- barMode: GalleryBarMode | undefined;
- /**
- * The section / area, and the item within it, that the gallery is currently
- * showing.
- */
- focus: GalleryFocus | undefined;
- activeCollectionID: number | undefined;
+ selectedCollectionSummaryID: number | undefined;
/**
* The currently selected person, if any.
*
* When present, it is used to derive the {@link activePerson} property of
- * the {@link focus}.
- */
- activePersonID: string | undefined;
-
- filteredData: EnteFile[];
- /**
- * The currently selected person, if any.
+ * the {@link view}.
*
- * Whenever this is present, it is guaranteed to be one of the items from
- * within {@link people}.
+ * UI code should use the {@link view}, this property is meant as the
+ * underlying primitive state. In particular, this does not get reset when
+ * we switch sections, which allows us to come back to the same person (if
+ * possible) on switching back.
*/
- activePerson: Person | undefined;
+ selectedPersonID: string | undefined;
/**
- * The list of people to show.
+ * 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.
*/
- people: Person[] | undefined;
+ searchResults: EnteFile[] | undefined;
+
+ /*--< Transient UI state >--*/
+
+ /**
+ * The view, and the item within it, that the gallery is currently showing.
+ *
+ * This can be temporarily overridden when we display search results.
+ */
+ view: GalleryView | undefined;
/**
* `true` if we are in "search mode".
*
@@ -246,16 +268,9 @@ export interface GalleryState {
*/
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.
+ * The files to show, uniqued and sorted appropriately.
*/
- searchResults: EnteFile[] | undefined;
+ filteredFiles: EnteFile[] | undefined;
}
export type GalleryAction =
@@ -268,13 +283,6 @@ export type GalleryAction =
hiddenFiles: EnteFile[];
trashedFiles: EnteFile[];
}
- | {
- type: "set";
- filteredData: EnteFile[];
- galleryPeopleState:
- | { activePerson: Person | undefined; people: Person[] }
- | undefined;
- }
| {
type: "setNormalCollections";
collections: Collection[];
@@ -297,6 +305,7 @@ export type GalleryAction =
| { type: "clearTempHidden" }
| { type: "showAll" }
| { type: "showHidden" }
+ | { type: "showAlbums" }
| {
type: "showNormalOrHiddenCollectionSummary";
collectionSummaryID: number | undefined;
@@ -305,7 +314,8 @@ export type GalleryAction =
| { type: "showPerson"; personID: string }
| { type: "searchResults"; searchResults: EnteFile[] }
| { type: "enterSearchMode" }
- | { type: "exitSearch" };
+ | { type: "exitSearch" }
+ | { type: "setFilteredFiles"; filteredFiles: EnteFile[] };
const initialGalleryState: GalleryState = {
user: undefined,
@@ -326,15 +336,12 @@ const initialGalleryState: GalleryState = {
hiddenCollectionSummaries: new Map(),
tempDeletedFileIDs: new Set(),
tempHiddenFileIDs: new Set(),
- barMode: undefined,
- focus: undefined,
- activeCollectionID: undefined,
- activePersonID: undefined,
- filteredData: [],
- activePerson: undefined,
- people: [],
- isInSearchMode: false,
+ selectedCollectionSummaryID: undefined,
+ selectedPersonID: undefined,
searchResults: undefined,
+ view: undefined,
+ filteredFiles: undefined,
+ isInSearchMode: false,
};
const galleryReducer: React.Reducer = (
@@ -383,15 +390,13 @@ const galleryReducer: React.Reducer = (
hiddenCollections,
action.hiddenFiles,
),
+ view: {
+ type: "albums",
+ activeCollectionSummaryID: ALL_SECTION,
+ activeCollection: undefined,
+ },
};
}
- case "set":
- return {
- ...state,
- filteredData: action.filteredData,
- activePerson: action.galleryPeopleState?.activePerson,
- people: action.galleryPeopleState?.people,
- };
case "setNormalCollections": {
const archivedCollectionIDs = deriveArchivedCollectionIDs(
action.collections,
@@ -584,49 +589,93 @@ const galleryReducer: React.Reducer = (
case "showAll":
return {
...state,
- barMode: "albums",
- activeCollectionID: ALL_SECTION,
- isInSearchMode: false,
searchResults: undefined,
+ selectedCollectionSummaryID: undefined,
+ view: {
+ type: "albums",
+ activeCollectionSummaryID: ALL_SECTION,
+ activeCollection: undefined,
+ },
+ isInSearchMode: false,
};
case "showHidden":
return {
...state,
- barMode: "hidden-albums",
- activeCollectionID: HIDDEN_ITEMS_SECTION,
- isInSearchMode: false,
searchResults: undefined,
+ selectedCollectionSummaryID: undefined,
+ view: {
+ type: "hidden-albums",
+ activeCollectionSummaryID: HIDDEN_ITEMS_SECTION,
+ activeCollection: undefined,
+ },
+ isInSearchMode: false,
};
+ case "showAlbums": {
+ const { view, selectedCollectionSummaryID } =
+ deriveAlbumsViewAndSelectedID(
+ state.collections,
+ state.collectionSummaries,
+ state.selectedCollectionSummaryID,
+ );
+ return {
+ ...state,
+ searchResults: undefined,
+ selectedCollectionSummaryID,
+ view,
+ isInSearchMode: false,
+ };
+ }
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,
+ selectedCollectionSummaryID: action.collectionSummaryID,
+ view: {
+ type:
+ action.collectionSummaryID !== undefined &&
+ state.hiddenCollectionSummaries.has(
+ action.collectionSummaryID,
+ )
+ ? "hidden-albums"
+ : "albums",
+ activeCollectionSummaryID:
+ action.collectionSummaryID ?? ALL_SECTION,
+ activeCollection: state.collections
+ .concat(state.hiddenCollections)
+ .find(({ id }) => id === action.collectionSummaryID),
+ },
+ isInSearchMode: false,
};
- case "showPeople":
+ case "showPeople": {
+ const view = derivePeopleView(
+ state.peopleState,
+ state.tempDeletedFileIDs,
+ state.tempHiddenFileIDs,
+ state.selectedPersonID,
+ );
return {
...state,
- barMode: "people",
- activePersonID: undefined,
- isInSearchMode: false,
searchResults: undefined,
+ selectedPersonID: view.activePerson?.id,
+ view,
+ isInSearchMode: false,
};
- case "showPerson":
+ }
+ case "showPerson": {
+ const view = derivePeopleView(
+ state.peopleState,
+ state.tempDeletedFileIDs,
+ state.tempHiddenFileIDs,
+ action.personID,
+ );
return {
...state,
- barMode: "people",
- activePersonID: action.personID,
- isInSearchMode: false,
searchResults: undefined,
+ selectedPersonID: view.activePerson?.id,
+ view,
+ isInSearchMode: false,
};
+ }
case "enterSearchMode":
return { ...state, isInSearchMode: true };
case "searchResults":
@@ -640,6 +689,8 @@ const galleryReducer: React.Reducer = (
isInSearchMode: false,
searchResults: undefined,
};
+ case "setFilteredFiles":
+ return { ...state, filteredFiles: action.filteredFiles };
}
};
@@ -944,3 +995,222 @@ const findAllSectionVisibleFiles = (
!archivedCollectionIDs.has(file.collectionID),
),
);
+
+/**
+ * Helper function to derive the {@link GalleryView} from its dependencies when
+ * we are switching to (or back to) the "albums" view.
+ */
+const deriveAlbumsViewAndSelectedID = (
+ collections: GalleryState["collections"],
+ collectionSummaries: GalleryState["collectionSummaries"],
+ selectedCollectionSummaryID: GalleryState["selectedCollectionSummaryID"],
+) => {
+ // Make sure that the last selected ID is still valid by searching for it.
+ const activeCollectionSummaryID = selectedCollectionSummaryID
+ ? collectionSummaries.get(selectedCollectionSummaryID)?.id
+ : undefined;
+ const activeCollection = activeCollectionSummaryID
+ ? collections.find(({ id }) => id == activeCollectionSummaryID)
+ : undefined;
+ return {
+ selectedCollectionSummaryID: activeCollectionSummaryID,
+ view: {
+ type: "albums" as const,
+ activeCollectionSummaryID: activeCollectionSummaryID ?? ALL_SECTION,
+ activeCollection,
+ },
+ };
+};
+
+/**
+ * Helper function to derive the {@link GalleryView} from its dependencies when
+ * we are switching to (or back to) the "people" view.
+ */
+const derivePeopleView = (
+ peopleState: GalleryState["peopleState"],
+ tempDeletedFileIDs: GalleryState["tempDeletedFileIDs"],
+ tempHiddenFileIDs: GalleryState["tempHiddenFileIDs"],
+ selectedPersonID: GalleryState["selectedPersonID"],
+): Extract => {
+ let people = peopleState?.people ?? [];
+ let visiblePeople = peopleState?.visiblePeople ?? [];
+ if (tempDeletedFileIDs.size + tempHiddenFileIDs.size > 0) {
+ // Prune the in-memory temp updates from the actual state to
+ // obtain the UI state. Kept inside an preflight check to so
+ // that the common path remains fast.
+ const filterTemp = (ps: Person[]) =>
+ ps
+ .map((p) => ({
+ ...p,
+ fileIDs: p.fileIDs.filter(
+ (id) =>
+ !tempDeletedFileIDs.has(id) &&
+ !tempHiddenFileIDs.has(id),
+ ),
+ }))
+ .filter((p) => p.fileIDs.length > 0);
+ people = filterTemp(people);
+ visiblePeople = filterTemp(visiblePeople);
+ }
+ const findByID = (ps: Person[]) => ps.find((p) => p.id == selectedPersonID);
+ let activePerson = findByID(visiblePeople);
+ if (!activePerson) {
+ // This might be one of the normally hidden small clusters.
+ activePerson = findByID(people);
+ if (activePerson) {
+ // Temporarily add this person's entry to the list of people
+ // surfaced in the people section.
+ visiblePeople.push(activePerson);
+ } else {
+ // We don't have an "All" pseudo-album in people mode, so default to
+ // the first person in the list (if any).
+ activePerson = visiblePeople[0];
+ }
+ }
+
+ return { type: "people", visiblePeople, activePerson };
+};
+
+/**
+ * Helper function to compute the sorted list of files to show when we're
+ * showing either "albums" or "hidden-albums".
+ */
+export const deriveAlbumishFilteredFiles = (state: GalleryState) => {
+ const view = state.view;
+ if (view?.type == "albums") {
+ return deriveAlbumsFilteredFiles(state, view);
+ } else if (view?.type == "hidden-albums") {
+ return deriveHiddenAlbumsFilteredFiles(state, view);
+ } else {
+ // TODO: Setup the types so that we don't come here.
+ throw new Error("Not implemented");
+ }
+};
+
+/**
+ * Helper function to compute the sorted list of files to show when we're
+ * in the "albums" view.
+ */
+export const deriveAlbumsFilteredFiles = (
+ state: GalleryState,
+ view: Extract,
+) => {
+ const {
+ files,
+ archivedCollectionIDs,
+ hiddenFileIDs,
+ tempDeletedFileIDs,
+ tempHiddenFileIDs,
+ } = state;
+ const activeCollectionSummaryID = view.activeCollectionSummaryID;
+
+ const filteredFiles = files.filter((file) => {
+ if (tempDeletedFileIDs.has(file.id)) return false;
+ if (hiddenFileIDs.has(file.id)) return false;
+ if (tempHiddenFileIDs.has(file.id)) return false;
+
+ // Files in archived collections can only be seen in their respective
+ // collection.
+ if (archivedCollectionIDs.has(file.collectionID)) {
+ return activeCollectionSummaryID === file.collectionID;
+ }
+
+ // Archived files can only be seen in the archive section, or in their
+ // respective collection.
+ if (isArchivedFile(file)) {
+ return (
+ activeCollectionSummaryID === ARCHIVE_SECTION ||
+ activeCollectionSummaryID === file.collectionID
+ );
+ }
+
+ // Show all remaining (non-hidden, non-archived) files in "All".
+ if (activeCollectionSummaryID === ALL_SECTION) {
+ return true;
+ }
+
+ // Show files that belong to the active collection.
+ return activeCollectionSummaryID === file.collectionID;
+ });
+
+ return sortAndUniqueFilteredFiles(filteredFiles, view.activeCollection);
+};
+
+/**
+ * Helper function to compute the sorted list of files to show when we're
+ * showing the "Trash".
+ */
+export const deriveTrashFilteredFiles = ({
+ files,
+ trashedFiles,
+ tempDeletedFileIDs,
+}: GalleryState) =>
+ uniqueFilesByID([
+ ...trashedFiles,
+ ...files.filter((file) => tempDeletedFileIDs.has(file.id)),
+ ]);
+
+/**
+ * Helper function to compute the sorted list of files to show when we're
+ * in the "hidden-albums" view.
+ */
+export const deriveHiddenAlbumsFilteredFiles = (
+ state: GalleryState,
+ view: Extract,
+) => {
+ const { hiddenFiles, defaultHiddenCollectionIDs, tempDeletedFileIDs } =
+ state;
+ const activeCollectionSummaryID = view.activeCollectionSummaryID;
+
+ const filteredFiles = hiddenFiles.filter((file) => {
+ if (tempDeletedFileIDs.has(file.id)) return false;
+
+ // "Hidden" shows all standalone hidden files.
+ if (
+ activeCollectionSummaryID === HIDDEN_ITEMS_SECTION &&
+ defaultHiddenCollectionIDs.has(file.collectionID)
+ ) {
+ return true;
+ }
+
+ // Show files that belong to the active collection.
+ return activeCollectionSummaryID === file.collectionID;
+ });
+
+ return sortAndUniqueFilteredFiles(filteredFiles, view.activeCollection);
+};
+
+/**
+ * Prepare the list of files for being shown in the gallery.
+ *
+ * This functions uniques the given collection files so that there is only one
+ * entry per file ID. Then it sorts them if the active collection prefers them
+ * to be sorted oldest first (by default, lists of collection files are sorted
+ * newest first, and we assume that {@link files} are already sorted that way).
+ */
+const sortAndUniqueFilteredFiles = (
+ files: EnteFile[],
+ activeCollection: Collection | undefined,
+) => {
+ const uniqueFiles = uniqueFilesByID(files);
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
+ const sortAsc = activeCollection?.pubMagicMetadata?.data?.asc ?? false;
+ return sortAsc ? sortFiles(uniqueFiles, true) : uniqueFiles;
+};
+
+/**
+ * Helper function to compute the sorted list of files to show when we're
+ * in the "people" view.
+ */
+export const derivePeopleFilteredFiles = (
+ { files }: GalleryState,
+ view: Extract,
+) => {
+ const pfSet = new Set(view.activePerson?.fileIDs ?? []);
+ return uniqueFilesByID(
+ files.filter(({ id }) => {
+ if (!pfSet.has(id)) return false;
+ return true;
+ }),
+ );
+};