[web] Use reducer for gallery - Part 6/x (#3810)

This commit is contained in:
Manav Rathi
2024-10-23 10:09:36 +05:30
committed by GitHub
2 changed files with 399 additions and 253 deletions

View File

@@ -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 <div></div>;
}
@@ -1147,7 +1020,7 @@ export default function Gallery() {
>
{barMode == "hidden-albums" ? (
<HiddenSectionNavbarContents
onBack={() => dispatch({ type: "showAll" })}
onBack={() => dispatch({ type: "showAlbums" })}
/>
) : (
<NormalNavbarContents
@@ -1175,8 +1048,11 @@ export default function Gallery() {
setActiveCollectionID: handleSetActiveCollectionID,
hiddenCollectionSummaries,
showPeopleSectionButton,
people: galleryPeopleState?.people ?? [],
activePerson: galleryPeopleState?.activePerson,
people:
(state.view.type == "people"
? state.view.visiblePeople
: undefined) ?? [],
activePerson,
onSelectPerson: handleSelectPerson,
setCollectionNamerAttributes,
setPhotoListHeader,
@@ -1234,14 +1110,14 @@ export default function Gallery() {
) : !isInSearchMode &&
!isFirstLoad &&
barMode == "people" &&
!galleryPeopleState?.activePerson ? (
!activePerson ? (
<PeopleEmptyState />
) : (
<PhotoFrame
page={PAGES.GALLERY}
mode={barMode}
modePlus={isInSearchMode ? "search" : barMode}
files={filteredData}
files={filteredFiles}
syncWithRemote={syncWithRemote}
favItemIds={state.favoriteFileIDs}
setSelected={setSelected}
@@ -1251,7 +1127,7 @@ export default function Gallery() {
}
setIsPhotoSwipeOpen={setIsPhotoSwipeOpen}
activeCollectionID={activeCollectionID}
activePersonID={galleryPeopleState?.activePerson?.id}
activePersonID={activePerson?.id}
enableDownload={true}
fileToCollectionsMap={fileToCollectionsMap}
collectionNameMap={collectionNameMap}

View File

@@ -44,24 +44,33 @@ import type { FamilyData } from "../../services/user";
/**
* Specifies what the bar at the top of the gallery is displaying currently.
*
* TODO: Deprecated(?). Use GalleryView instead. Deprecated if it can be used in
* all cases where the bar mode was in use.
*/
export type GalleryBarMode = "albums" | "hidden-albums" | "people";
/**
* Specifies what the gallery is currently displaying.
*
* TODO: An experiment at consolidating state.
* This can be overridden by the display of search results.
*/
export type GalleryFocus =
export type GalleryView =
| {
/**
* We're either in the "Albums" section, or are displaying the hidden
* albums.
* We're either in the "Albums" or "Hidden albums" section.
*/
type: "albums" | "hidden-albums";
activeCollectionID: number;
activeCollectionSummaryID: number;
/**
* If the active collection ID is for a collection and not a
* pseudo-collection, this property will be set to the corresponding
* {@link Collection}.
*
* It is guaranteed that this will be one of the {@link collections}
* or {@link hiddenCollections}.
*/
activeCollection: Collection | undefined;
activeCollectionSummary: CollectionSummary;
}
| {
/**
@@ -74,14 +83,14 @@ export type GalleryFocus =
* Note that this can be different from the underlying list of people,
* and can temporarily include a person from outside that list.
*/
people: Person[];
visiblePeople: Person[];
/**
* The currently selected person in the gallery bar.
* The currently selected person in the gallery bar, if any.
*
* It is guaranteed that {@link activePerson} will be one of the
* objects from among {@link people}.
* It is guaranteed that when it is set, {@link activePerson} will be
* one of the objects from among {@link people}.
*/
activePerson: Person;
activePerson: Person | undefined;
};
/**
@@ -195,45 +204,58 @@ export interface GalleryState {
* local state also gets synced in a bit.
*/
tempDeletedFileIDs: Set<number>;
/**
* Variant of {@link tempDeletedFileIDs} for files that have just been
* hidden.
*/
tempHiddenFileIDs: Set<number>;
/*--< 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<number>(),
tempHiddenFileIDs: new Set<number>(),
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<GalleryState, GalleryAction> = (
@@ -383,15 +390,13 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
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<GalleryState, GalleryAction> = (
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<GalleryState, GalleryAction> = (
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<GalleryView, { type: "people" }> => {
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<GalleryView, { type: "albums" | "hidden-albums" }>,
) => {
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<GalleryView, { type: "albums" | "hidden-albums" }>,
) => {
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<GalleryView, { type: "people" }>,
) => {
const pfSet = new Set(view.activePerson?.fileIDs ?? []);
return uniqueFilesByID(
files.filter(({ id }) => {
if (!pfSet.has(id)) return false;
return true;
}),
);
};