This commit is contained in:
Manav Rathi
2025-06-17 17:56:45 +05:30
parent cc54faf78a
commit 5877d64449
6 changed files with 96 additions and 75 deletions

View File

@@ -79,7 +79,7 @@ import {
useUserDetailsSnapshot,
} from "ente-new/photos/components/utils/use-snapshot";
import {
CollectionSummaryID,
PseudoCollectionID,
type CollectionSummaries,
} from "ente-new/photos/services/collection-summary";
import exportService from "ente-new/photos/services/export";
@@ -453,12 +453,12 @@ const ShortcutSection: React.FC<ShortcutSectionProps> = ({
};
const openTrashSection = () => {
galleryContext.setActiveCollectionID(CollectionSummaryID.trash);
galleryContext.setActiveCollectionID(PseudoCollectionID.trash);
onCloseSidebar();
};
const openArchiveSection = () => {
galleryContext.setActiveCollectionID(CollectionSummaryID.archiveItems);
galleryContext.setActiveCollectionID(PseudoCollectionID.archiveItems);
onCloseSidebar();
};
@@ -482,7 +482,7 @@ const ShortcutSection: React.FC<ShortcutSectionProps> = ({
<RowButton
startIcon={<ArchiveOutlinedIcon />}
label={t("section_archive")}
caption={summaryCaption(CollectionSummaryID.archiveItems)}
caption={summaryCaption(PseudoCollectionID.archiveItems)}
onClick={openArchiveSection}
/>
<RowButton
@@ -501,7 +501,7 @@ const ShortcutSection: React.FC<ShortcutSectionProps> = ({
<RowButton
startIcon={<DeleteOutlineIcon />}
label={t("section_trash")}
caption={summaryCaption(CollectionSummaryID.trash)}
caption={summaryCaption(PseudoCollectionID.trash)}
onClick={openTrashSection}
/>
</>

View File

@@ -922,9 +922,10 @@ const Page: React.FC = () => {
onClose={handleCloseCollectionSelector}
attributes={collectionSelectorAttributes}
collectionSummaries={normalCollectionSummaries}
collectionForCollectionID={(id) =>
collectionForCollectionSummaryID={(id) =>
// Null assert since the collection selector should only
// show selectable normalCollectionSummaries.
// show "selectable" normalCollectionSummaries. See:
// [Note: Picking from selectable collection summaries].
findCollectionCreatingUncategorizedIfNeeded(
normalCollections,
id,

View File

@@ -70,18 +70,33 @@ type CollectionSelectorProps = ModalVisibilityProps & {
attributes: CollectionSelectorAttributes | undefined;
/**
* The collections to list.
*
* The picker does not list all of the collection summaries, it filters
* these provided list down to values which make sense for the
* {@link attribute}'s {@link action}.
*
* See: [Note: Picking from selectable collection summaries].
*/
collectionSummaries: CollectionSummaries;
/**
* A function to map from a collection or pseudo-collection ID to a
* {@link Collection}.
* A function to map from a collection summary ID to a {@link Collection}.
*
* This is invoked when the user makes a selection, to convert the ID of the
* selected collection, which can be a pseudo-collection too, into a
* collection object that can be passed to the {@link callback} attribute of
* {@link CollectionSelectorAttributes}.
* selected collection summary into a collection object that can be passed
* as the {@link callback} property of {@link CollectionSelectorAttributes}.
*
* [Note: Picking from selectable collection summaries]
*
* In general, not all pseudo collections can be converted into a
* collection. For example, there is no underlying collection corresponding
* to the "All" pseudo collection. However, the implementation of
* {@link CollectionSelector} is such that it filters the provided
* {@link collectionSummaries} to only show those which, when selected, can
* be mapped to an (existing or on-demand created) collection.
*/
collectionForCollectionID: (collectionID: number) => Promise<Collection>;
collectionForCollectionSummaryID: (
collectionID: number,
) => Promise<Collection>;
};
/**
@@ -93,7 +108,7 @@ export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
onClose,
attributes,
collectionSummaries,
collectionForCollectionID,
collectionForCollectionSummaryID,
}) => {
// Make the dialog fullscreen if the screen is <= the dialog's max width.
const isFullScreen = useMediaQuery("(max-width: 490px)");
@@ -150,8 +165,8 @@ export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
const { action, onSelectCollection, onCancel, onCreateCollection } =
attributes;
const handleCollectionClick = async (collectionID: number) => {
onSelectCollection(await collectionForCollectionID(collectionID));
const handleCollectionSummaryClick = async (id: number) => {
onSelectCollection(await collectionForCollectionSummaryID(id));
onClose();
};
@@ -178,10 +193,10 @@ export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
{t("create_albums")}
</LargeTileCreateNewButton>
{filteredCollections.map((collectionSummary) => (
<CollectionButton
<CollectionSummaryButton
key={collectionSummary.id}
collectionSummary={collectionSummary}
onCollectionClick={handleCollectionClick}
onClick={handleCollectionSummaryClick}
/>
))}
</DialogContent_>
@@ -210,19 +225,19 @@ const titleForAction = (action: CollectionSelectorAction) => {
}
};
interface CollectionButtonProps {
interface CollectionSummaryButtonProps {
collectionSummary: CollectionSummary;
onCollectionClick: (collectionID: number) => void;
onClick: (collectionSummaryID: number) => void;
}
const CollectionButton: React.FC<CollectionButtonProps> = ({
const CollectionSummaryButton: React.FC<CollectionSummaryButtonProps> = ({
collectionSummary,
onCollectionClick,
onClick,
}) => (
<ItemCard
TileComponent={LargeTileButton}
coverFile={collectionSummary.coverFile}
onClick={() => onCollectionClick(collectionSummary.id)}
onClick={() => onClick(collectionSummary.id)}
>
<LargeTileTextOverlay>
<Typography>{collectionSummary.name}</Typography>

View File

@@ -16,7 +16,7 @@ import log from "ente-base/log";
import type { Collection } from "ente-media/collection";
import type { FamilyData } from "ente-new/photos/services/user-details";
import { createUncategorizedCollection } from "../../services/collection";
import { CollectionSummaryID } from "../../services/collection-summary";
import { PseudoCollectionID } from "../../services/collection-summary";
/**
* Ensure that the keys in local storage are not malformed by verifying that the
@@ -40,17 +40,17 @@ export const validateKey = async () => {
/**
* Return the {@link Collection} (from amongst {@link collections}) with the
* given {@link collectionID}. As a special case, if collection ID is the ID of
* the placeholder uncategorized collection, create a new uncategorized
* collection and then return it.
* given {@link collectionSummaryID}. As a special case, if the given
* {@link collectionSummaryID} is the ID of the placeholder uncategorized
* collection, create a new uncategorized collection and then return it.
*/
export const findCollectionCreatingUncategorizedIfNeeded = async (
collections: Collection[],
collectionID: number,
collectionSummaryID: number,
): Promise<Collection | undefined> =>
collectionID == CollectionSummaryID.uncategorizedPlaceholder
collectionSummaryID == PseudoCollectionID.uncategorizedPlaceholder
? createUncategorizedCollection()
: collections.find(({ id }) => id == collectionID);
: collections.find(({ id }) => id == collectionSummaryID);
export const constructUserIDToEmailMap = (
user: User,

View File

@@ -26,7 +26,7 @@ import {
TRASH_SECTION,
} from "../../services/collection";
import {
CollectionSummaryID,
PseudoCollectionID,
type CollectionSummary,
type CollectionSummaryType,
} from "../../services/collection-summary";
@@ -258,7 +258,7 @@ export interface GalleryState {
*
* This will be either the ID of the user's uncategorized collection, if one
* has already been created, otherwise it will be the predefined
* {@link CollectionSummaryID.uncategorizedPlaceholder}.
* {@link PseudoCollectionID.uncategorizedPlaceholder}.
*
* See: [Note: Uncategorized placeholder]
*/
@@ -486,7 +486,7 @@ const initialGalleryState: GalleryState = {
normalCollectionSummaries: new Map(),
hiddenCollectionSummaries: new Map(),
uncategorizedCollectionSummaryID:
CollectionSummaryID.uncategorizedPlaceholder,
PseudoCollectionID.uncategorizedPlaceholder,
tempDeletedFileIDs: new Set(),
tempHiddenFileIDs: new Set(),
pendingFavoriteUpdates: new Set(),
@@ -1368,7 +1368,7 @@ const deriveUncategorizedCollectionSummaryID = (
normalCollections: Collection[],
) =>
normalCollections.find(({ type }) => type == "uncategorized")?.id ??
CollectionSummaryID.uncategorizedPlaceholder;
PseudoCollectionID.uncategorizedPlaceholder;
const createCollectionSummaries = (
user: User,

View File

@@ -19,29 +19,32 @@ export type CollectionSummaryType =
* ID of the special {@link CollectionSummary} instances that are not backed by
* a real {@link Collection}.
*/
export const CollectionSummaryID = {
export const PseudoCollectionID = {
/**
* The "All" section.
*
* The default view when the user opens the gallery, showing the unique
* non-hidden and non-archived files in their collections.
* The "All" section is the default view when the user opens the gallery,
* showing them all of their unique non-hidden and non-archived files.
*/
all: 0,
/**
* The items shown in the "Archive" section.
* A pseudo-collection containing the individually archived files.
*
* It shows the files that the user has individually archived.
* The archive items is a pseudo-collection consisting of all the files
* which have been individually archived. It is what gets shown when the
* user navigates to the "Archive" section.
*/
archiveItems: -1,
/**
* Trash
* Trash.
*
* This shows files that are in the user's trash - files that have been
* deleted, but have not yet been deleted permanently.
* This pseudo-collection contains files that are in the user's trash -
* files that have been deleted, but have not yet been deleted permanently.
*/
trash: -2,
/**
* A placeholder for the uncategorized collection used till it is empty.
* A placeholder for the uncategorized collection until the real one comes
* into existence.
*
* [Note: Uncategorized placeholder]
*
@@ -55,11 +58,13 @@ export const CollectionSummaryID = {
*/
uncategorizedPlaceholder: -3,
/**
* The default collection shown in the "Hidden" section.
* A pseudo-collection containing the individually hidden files.
*
* It shows the files that the user has individually hidden; effectively, it
* works as a merged combination of all of the user's "default hidden"
* albums (See: Note: Multiple "default" hidden collections]).
* The "Hidden items" is the default pseudo-collection shown in the "Hidden"
* section. It consists of the files that the user has individually hidden.
*
* It is derived by merging all of the user's "default hidden" albums (See:
* Note: Multiple "default" hidden collections]).
*
* In addition to this "Hidden items" pseudo-collection, the "Hidden"
* section also shows other albums that were hidden.
@@ -74,56 +79,56 @@ export const HIDDEN_ITEMS_SECTION = -4;
export const ALL_SECTION = 0;
/**
* A massaged version of a collection (real or placeholder) or a pseudo
* "section" suitable for being directly shown in the UI.
* A massaged version of a collection or a pseudo-collection suitable for being
* directly shown in the UI.
*
* From one perspective, this can be thought of as a "CollectionOrSection":
* i.e., a group of files listed together in the UI, with the files coming from
* a real "collection" or some special "section". In the first case, the
* underlying listing will be backed by a {@link Collection}, while in the
* second case the files and other attributes comprising the listing will be
* determined by the special case-specific rules for that particular section.
* From one perspective, this can be thought of as a
* "CollectionOrPseudoCollection": a group of files listed together in the UI,
* with the files coming from a real "collection" or some special "section".
*
* Even when this is backed by a corresponding {@link Collection}, it adds some
* extra attributes that make it easier and more efficient for the UI elements
* to render this collection summary directly. From that perspective, this can
* be thought of as a "UICollection".
* - In the first case, the underlying listing will be backed by a
* {@link Collection},
*
* - In the second case the files and other attributes comprising the listing
* will be determined by the special case-specific rules for that particular
* pseudo-collection.
*
* However, even when this is backed by a corresponding "real"
* {@link Collection}, it adds some extra attributes that make it easier and
* more efficient for the UI elements to render this collection summary
* directly. So from that perspective, this can be also be thought of as a
* "UICollection".
*/
export interface CollectionSummary {
/**
* The ID of the underlying {@link Collection}, or one of the predefined
* {@link CollectionSummaryID}s for sections and other pseudo-collections.
* {@link PseudoCollectionID}s.
*/
id: number;
/**
* The primary "UI" type for the collection or section or pseudo-collection.
* The primary "UI" type for the collection or pseudo-collection.
*
* For newer code consider using {@link attributes} instead.
*/
type: CollectionSummaryType;
/**
* Various UI related attributes of the collection or section or
* pseudo-collection.
* Various UI related attributes of the collection or pseudo-collection.
*
* This is meant to replace {@link type} gradually. It defines various
* attributes about the underlying file listing that this collection summary
* stands for which the UI elements rendering the collection summary might
* want to know.
* ad-hoc "UI" attributes which make it easier and more efficient for the UI
* elements to render the collection summary in the UI.
*/
attributes: CollectionSummaryType[];
/**
* The name of the collection or section or pseudo-collection surfaced in
* the UI.
* The name of the collection or pseudo-collection surfaced in the UI.
*/
name: string;
/**
* The newest file in the collection or section or pseudo-collection (if it
* is not empty).
* The newest file in the collection or pseudo-collection (if any).
*/
latestFile: EnteFile | undefined;
/**
* The file to show as the cover for the collection or section or
* pseudo-collection.
* The file to show as the cover for the collection or pseudo-collection.
*
* This can be one of
* - A file explicitly chosen by the user.
@@ -133,13 +138,13 @@ export interface CollectionSummary {
coverFile: EnteFile | undefined;
/**
* The number of files in the underlying collection, or the number of files
* that belong to this section or pseudo-collection.
* computed to belong to the pseudo-collection.
*/
fileCount: number;
/**
* The time (epoch microseconds) when the collection was last updated. For
* sections or pseudo-collections this will (usually) be the updation time
* of the latest file that it contains.
* pseudo-collections this will (usually) be the updation time of the latest
* file that it contains.
*/
updationTime: number | undefined;
order?: number;