[web] Use reducer for gallery - Part 6/x (#3810)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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;
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user