[web] Collection summary refactoring (#6390)
This commit is contained in:
@@ -25,7 +25,6 @@ import {
|
||||
import { SingleInputDialog } from "ente-base/components/SingleInputDialog";
|
||||
import { useModalVisibility } from "ente-base/components/utils/modal";
|
||||
import { useBaseContext } from "ente-base/context";
|
||||
import { isArchivedCollection } from "ente-gallery/services/magic-metadata";
|
||||
import { CollectionOrder, type Collection } from "ente-media/collection";
|
||||
import { ItemVisibility } from "ente-media/file-metadata";
|
||||
import type { RemotePullOpts } from "ente-new/photos/components/gallery";
|
||||
@@ -84,24 +83,14 @@ export const CollectionHeader: React.FC<CollectionHeaderProps> = (props) => {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const { name, type, fileCount } = collectionSummary;
|
||||
const { name, type, attributes, fileCount } = collectionSummary;
|
||||
|
||||
const EndIcon = ({ type }: { type: CollectionSummaryType }) => {
|
||||
switch (type) {
|
||||
case "favorites":
|
||||
return <FavoriteRoundedIcon />;
|
||||
case "archived":
|
||||
return <ArchiveOutlinedIcon />;
|
||||
case "incomingShareViewer":
|
||||
case "incomingShareCollaborator":
|
||||
return <PeopleIcon />;
|
||||
case "outgoingShare":
|
||||
return <PeopleIcon />;
|
||||
case "sharedOnlyViaLink":
|
||||
return <LinkIcon />;
|
||||
default:
|
||||
return <></>;
|
||||
}
|
||||
const EndIcon = () => {
|
||||
if (attributes.has("archived")) return <ArchiveOutlinedIcon />;
|
||||
if (attributes.has("sharedOnlyViaLink")) return <LinkIcon />;
|
||||
if (attributes.has("shared")) return <PeopleIcon />;
|
||||
if (attributes.has("userFavorites")) return <FavoriteRoundedIcon />;
|
||||
return <></>;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -110,7 +99,7 @@ export const CollectionHeader: React.FC<CollectionHeaderProps> = (props) => {
|
||||
<GalleryItemsSummary
|
||||
name={name}
|
||||
fileCount={fileCount}
|
||||
endIcon={<EndIcon type={type} />}
|
||||
endIcon={<EndIcon />}
|
||||
/>
|
||||
{shouldShowOptions(type) && (
|
||||
<CollectionHeaderOptions {...props} />
|
||||
@@ -121,7 +110,7 @@ export const CollectionHeader: React.FC<CollectionHeaderProps> = (props) => {
|
||||
};
|
||||
|
||||
const shouldShowOptions = (type: CollectionSummaryType) =>
|
||||
type != "all" && type != "archive";
|
||||
type != "all" && type != "archiveItems";
|
||||
|
||||
const CollectionHeaderOptions: React.FC<CollectionHeaderProps> = ({
|
||||
activeCollection,
|
||||
@@ -320,7 +309,7 @@ const CollectionHeaderOptions: React.FC<CollectionHeaderProps> = ({
|
||||
];
|
||||
break;
|
||||
|
||||
case "favorites":
|
||||
case "userFavorites":
|
||||
menuOptions = [
|
||||
<DownloadOption
|
||||
key="download"
|
||||
@@ -365,10 +354,9 @@ const CollectionHeaderOptions: React.FC<CollectionHeaderProps> = ({
|
||||
];
|
||||
break;
|
||||
|
||||
case "incomingShareViewer":
|
||||
case "incomingShareCollaborator":
|
||||
case "sharedIncoming":
|
||||
menuOptions = [
|
||||
isArchivedCollection(activeCollection) ? (
|
||||
collectionSummary.attributes.has("archived") ? (
|
||||
<OverflowMenuOption
|
||||
key="unarchive"
|
||||
onClick={unarchiveAlbum}
|
||||
@@ -437,7 +425,7 @@ const CollectionHeaderOptions: React.FC<CollectionHeaderProps> = ({
|
||||
),
|
||||
...(!isHiddenCollection(activeCollection)
|
||||
? [
|
||||
isArchivedCollection(activeCollection) ? (
|
||||
collectionSummary.attributes.has("archived") ? (
|
||||
<OverflowMenuOption
|
||||
key="unarchive"
|
||||
onClick={unarchiveAlbum}
|
||||
@@ -501,7 +489,7 @@ const CollectionHeaderOptions: React.FC<CollectionHeaderProps> = ({
|
||||
return (
|
||||
<Box sx={{ display: "inline-flex", gap: "16px" }}>
|
||||
<QuickOptions
|
||||
collectionSummaryType={collectionSummaryType}
|
||||
collectionSummary={collectionSummary}
|
||||
isDownloadInProgress={isActiveCollectionDownloadInProgress}
|
||||
onEmptyTrashClick={confirmEmptyTrash}
|
||||
onDownloadClick={downloadCollection}
|
||||
@@ -539,7 +527,7 @@ interface OptionProps {
|
||||
}
|
||||
|
||||
interface QuickOptionsProps {
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
collectionSummary: CollectionSummary;
|
||||
isDownloadInProgress: () => boolean;
|
||||
onEmptyTrashClick: () => void;
|
||||
onDownloadClick: () => void;
|
||||
@@ -550,32 +538,32 @@ const QuickOptions: React.FC<QuickOptionsProps> = ({
|
||||
onEmptyTrashClick,
|
||||
onDownloadClick,
|
||||
onShareClick,
|
||||
collectionSummaryType: type,
|
||||
collectionSummary,
|
||||
isDownloadInProgress,
|
||||
}) => (
|
||||
<Stack direction="row" sx={{ alignItems: "center", gap: "16px" }}>
|
||||
{showEmptyTrashQuickOption(type) && (
|
||||
{showEmptyTrashQuickOption(collectionSummary) && (
|
||||
<EmptyTrashQuickOption onClick={onEmptyTrashClick} />
|
||||
)}
|
||||
{showDownloadQuickOption(type) &&
|
||||
{showDownloadQuickOption(collectionSummary) &&
|
||||
(isDownloadInProgress() ? (
|
||||
<ActivityIndicator size="20px" sx={{ m: "12px" }} />
|
||||
) : (
|
||||
<DownloadQuickOption
|
||||
collectionSummary={collectionSummary}
|
||||
onClick={onDownloadClick}
|
||||
collectionSummaryType={type}
|
||||
/>
|
||||
))}
|
||||
{showShareQuickOption(type) && (
|
||||
{showShareQuickOption(collectionSummary) && (
|
||||
<ShareQuickOption
|
||||
collectionSummary={collectionSummary}
|
||||
onClick={onShareClick}
|
||||
collectionSummaryType={type}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
|
||||
const showEmptyTrashQuickOption = (type: CollectionSummaryType) =>
|
||||
const showEmptyTrashQuickOption = ({ type }: CollectionSummary) =>
|
||||
type == "trash";
|
||||
|
||||
const EmptyTrashQuickOption: React.FC<OptionProps> = ({ onClick }) => (
|
||||
@@ -586,34 +574,29 @@ const EmptyTrashQuickOption: React.FC<OptionProps> = ({ onClick }) => (
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const showDownloadQuickOption = (type: CollectionSummaryType) =>
|
||||
const showDownloadQuickOption = ({ type, attributes }: CollectionSummary) =>
|
||||
type == "album" ||
|
||||
type == "folder" ||
|
||||
type == "favorites" ||
|
||||
type == "uncategorized" ||
|
||||
type == "hiddenItems" ||
|
||||
type == "incomingShareViewer" ||
|
||||
type == "incomingShareCollaborator" ||
|
||||
type == "outgoingShare" ||
|
||||
type == "sharedOnlyViaLink" ||
|
||||
type == "archived" ||
|
||||
type == "pinned";
|
||||
attributes.has("favorites") ||
|
||||
attributes.has("shared");
|
||||
|
||||
type DownloadQuickOptionProps = OptionProps & {
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
collectionSummary: CollectionSummary;
|
||||
};
|
||||
|
||||
const DownloadQuickOption: React.FC<DownloadQuickOptionProps> = ({
|
||||
collectionSummary: { type },
|
||||
onClick,
|
||||
collectionSummaryType,
|
||||
}) => (
|
||||
<Tooltip
|
||||
title={
|
||||
collectionSummaryType == "favorites"
|
||||
type == "userFavorites"
|
||||
? t("download_favorites")
|
||||
: collectionSummaryType == "uncategorized"
|
||||
: type == "uncategorized"
|
||||
? t("download_uncategorized")
|
||||
: collectionSummaryType == "hiddenItems"
|
||||
: type == "hiddenItems"
|
||||
? t("download_hidden_items")
|
||||
: t("download_album")
|
||||
}
|
||||
@@ -624,36 +607,29 @@ const DownloadQuickOption: React.FC<DownloadQuickOptionProps> = ({
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const showShareQuickOption = (type: CollectionSummaryType) =>
|
||||
const showShareQuickOption = ({ type, attributes }: CollectionSummary) =>
|
||||
type == "album" ||
|
||||
type == "folder" ||
|
||||
type == "favorites" ||
|
||||
type == "outgoingShare" ||
|
||||
type == "sharedOnlyViaLink" ||
|
||||
type == "archived" ||
|
||||
type == "incomingShareViewer" ||
|
||||
type == "incomingShareCollaborator" ||
|
||||
type == "pinned";
|
||||
attributes.has("favorites") ||
|
||||
attributes.has("shared");
|
||||
|
||||
interface ShareQuickOptionProps {
|
||||
collectionSummary: CollectionSummary;
|
||||
onClick: () => void;
|
||||
collectionSummaryType: CollectionSummaryType;
|
||||
}
|
||||
|
||||
const ShareQuickOption: React.FC<ShareQuickOptionProps> = ({
|
||||
collectionSummary: { attributes },
|
||||
onClick,
|
||||
collectionSummaryType,
|
||||
}) => (
|
||||
<Tooltip
|
||||
title={
|
||||
collectionSummaryType == "incomingShareViewer" ||
|
||||
collectionSummaryType == "incomingShareCollaborator"
|
||||
? t("sharing_details")
|
||||
: collectionSummaryType == "outgoingShare" ||
|
||||
collectionSummaryType == "sharedOnlyViaLink"
|
||||
? t("modify_sharing")
|
||||
: collectionSummaryType == "favorites"
|
||||
? t("share_favorites")
|
||||
attributes.has("userFavorites")
|
||||
? t("share_favorites")
|
||||
: attributes.has("sharedIncoming")
|
||||
? t("sharing_details")
|
||||
: attributes.has("shared")
|
||||
? t("modify_sharing")
|
||||
: t("share_album")
|
||||
}
|
||||
>
|
||||
|
||||
@@ -66,10 +66,7 @@ import {
|
||||
type CreatePublicURLAttributes,
|
||||
type UpdatePublicURLAttributes,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import type {
|
||||
CollectionSummary,
|
||||
CollectionSummaryType,
|
||||
} from "ente-new/photos/services/collection-summary";
|
||||
import type { CollectionSummary } from "ente-new/photos/services/collection-summary";
|
||||
import { usePhotosAppContext } from "ente-new/photos/types/context";
|
||||
import { CustomError, parseSharingErrorCodes } from "ente-shared/error";
|
||||
import { wait } from "ente-utils/promise";
|
||||
@@ -150,7 +147,7 @@ export const CollectionShare: React.FC<CollectionShareProps> = ({
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const { type } = collectionSummary;
|
||||
const isSharedIncoming = collectionSummary.type == "sharedIncoming";
|
||||
|
||||
return (
|
||||
<SidebarDrawer anchor="right" {...{ open, onClose }}>
|
||||
@@ -159,17 +156,22 @@ export const CollectionShare: React.FC<CollectionShareProps> = ({
|
||||
onClose={onClose}
|
||||
onRootClose={onClose}
|
||||
title={
|
||||
type == "incomingShareCollaborator" ||
|
||||
type == "incomingShareViewer"
|
||||
isSharedIncoming
|
||||
? t("sharing_details")
|
||||
: t("share_album")
|
||||
}
|
||||
caption={collection.name}
|
||||
/>
|
||||
<Stack sx={{ py: "20px", px: "8px", gap: "24px" }}>
|
||||
{type == "incomingShareCollaborator" ||
|
||||
type == "incomingShareViewer" ? (
|
||||
<SharingDetails {...{ user, collection, type }} />
|
||||
{isSharedIncoming ? (
|
||||
<SharingDetails
|
||||
{...{
|
||||
user,
|
||||
collection,
|
||||
collectionSummary,
|
||||
emailByUserID,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<EmailShare
|
||||
@@ -199,15 +201,16 @@ export const CollectionShare: React.FC<CollectionShareProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
type SharingDetailsProps = { type: CollectionSummaryType } & Pick<
|
||||
type SharingDetailsProps = Pick<
|
||||
CollectionShareProps,
|
||||
"user" | "collection"
|
||||
"user" | "collection" | "emailByUserID" | "collectionSummary"
|
||||
>;
|
||||
|
||||
const SharingDetails: React.FC<SharingDetailsProps> = ({
|
||||
user,
|
||||
collection,
|
||||
type,
|
||||
collectionSummary,
|
||||
emailByUserID,
|
||||
}) => {
|
||||
const isOwner = user.id == collection.owner?.id;
|
||||
|
||||
@@ -232,12 +235,17 @@ const SharingDetails: React.FC<SharingDetailsProps> = ({
|
||||
</RowButtonGroupTitle>
|
||||
<RowButtonGroup>
|
||||
<RowLabel
|
||||
startIcon={<Avatar email={ownerEmail} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={ownerEmail}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
label={isOwner ? t("you") : ownerEmail}
|
||||
/>
|
||||
</RowButtonGroup>
|
||||
</Stack>
|
||||
{type == "incomingShareCollaborator" &&
|
||||
{collectionSummary.attributes.has("sharedIncomingCollaborator") &&
|
||||
collaborators.length > 0 && (
|
||||
<Stack>
|
||||
<RowButtonGroupTitle icon={<ModeEditIcon />}>
|
||||
@@ -247,7 +255,12 @@ const SharingDetails: React.FC<SharingDetailsProps> = ({
|
||||
{collaborators.map((email, index) => (
|
||||
<React.Fragment key={email}>
|
||||
<RowLabel
|
||||
startIcon={<Avatar email={email} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={email}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
label={userOrEmail(email)}
|
||||
/>
|
||||
{index != collaborators.length - 1 && (
|
||||
@@ -267,7 +280,12 @@ const SharingDetails: React.FC<SharingDetailsProps> = ({
|
||||
{viewers.map((email, index) => (
|
||||
<React.Fragment key={email}>
|
||||
<RowLabel
|
||||
startIcon={<Avatar email={email} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={email}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
label={userOrEmail(email)}
|
||||
/>
|
||||
{index != viewers.length - 1 && (
|
||||
@@ -390,6 +408,7 @@ const EmailShare: React.FC<EmailShareProps> = ({
|
||||
onRootClose,
|
||||
user,
|
||||
collection,
|
||||
emailByUserID,
|
||||
shareSuggestionEmails,
|
||||
onRemotePull,
|
||||
}}
|
||||
@@ -401,6 +420,7 @@ const EmailShare: React.FC<EmailShareProps> = ({
|
||||
onRootClose,
|
||||
user,
|
||||
collection,
|
||||
emailByUserID,
|
||||
shareSuggestionEmails,
|
||||
participantCount,
|
||||
wrap,
|
||||
@@ -474,7 +494,11 @@ type AddParticipantProps = ModalVisibilityProps & {
|
||||
role: CollectionNewParticipantRole;
|
||||
} & Pick<
|
||||
CollectionShareProps,
|
||||
"user" | "collection" | "shareSuggestionEmails" | "onRemotePull"
|
||||
| "user"
|
||||
| "collection"
|
||||
| "emailByUserID"
|
||||
| "shareSuggestionEmails"
|
||||
| "onRemotePull"
|
||||
>;
|
||||
|
||||
const AddParticipant: React.FC<AddParticipantProps> = ({
|
||||
@@ -483,6 +507,7 @@ const AddParticipant: React.FC<AddParticipantProps> = ({
|
||||
onRootClose,
|
||||
user,
|
||||
collection,
|
||||
emailByUserID,
|
||||
shareSuggestionEmails,
|
||||
role,
|
||||
onRemotePull,
|
||||
@@ -561,6 +586,7 @@ const AddParticipant: React.FC<AddParticipantProps> = ({
|
||||
caption={collection.name}
|
||||
>
|
||||
<AddParticipantForm
|
||||
{...{ user, emailByUserID }}
|
||||
existingEmails={eligibleEmails}
|
||||
submitButtonTitle={title}
|
||||
onSubmit={collectionShare}
|
||||
@@ -569,7 +595,7 @@ const AddParticipant: React.FC<AddParticipantProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
interface AddParticipantFormProps {
|
||||
type AddParticipantFormProps = {
|
||||
/**
|
||||
* Title for the submit button.
|
||||
*/
|
||||
@@ -591,9 +617,11 @@ interface AddParticipantFormProps {
|
||||
emailOrEmails: string | string[],
|
||||
setEmailFieldError: (message: string) => void,
|
||||
) => Promise<void>;
|
||||
}
|
||||
} & Pick<CollectionShareProps, "user" | "emailByUserID">;
|
||||
|
||||
const AddParticipantForm: React.FC<AddParticipantFormProps> = ({
|
||||
user,
|
||||
emailByUserID,
|
||||
existingEmails,
|
||||
submitButtonTitle,
|
||||
onSubmit,
|
||||
@@ -669,7 +697,12 @@ const AddParticipantForm: React.FC<AddParticipantFormProps> = ({
|
||||
);
|
||||
}}
|
||||
label={email}
|
||||
startIcon={<Avatar email={email} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={email}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
endIcon={
|
||||
formik.values.selectedEmails.includes(
|
||||
email,
|
||||
@@ -706,7 +739,11 @@ type ManageEmailShareProps = ModalVisibilityProps & {
|
||||
wrap: (f: () => Promise<void>) => () => void;
|
||||
} & Pick<
|
||||
CollectionShareProps,
|
||||
"user" | "collection" | "shareSuggestionEmails" | "onRemotePull"
|
||||
| "user"
|
||||
| "collection"
|
||||
| "emailByUserID"
|
||||
| "shareSuggestionEmails"
|
||||
| "onRemotePull"
|
||||
>;
|
||||
|
||||
const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
@@ -715,6 +752,7 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
onRootClose,
|
||||
user,
|
||||
collection,
|
||||
emailByUserID,
|
||||
shareSuggestionEmails,
|
||||
participantCount,
|
||||
wrap,
|
||||
@@ -783,7 +821,12 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
</RowButtonGroupTitle>
|
||||
<RowButtonGroup>
|
||||
<RowLabel
|
||||
startIcon={<Avatar email={ownerEmail} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={ownerEmail}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
label={isOwner ? t("you") : ownerEmail}
|
||||
/>
|
||||
</RowButtonGroup>
|
||||
@@ -801,7 +844,12 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
openManageParticipant(item)
|
||||
}
|
||||
label={item}
|
||||
startIcon={<Avatar email={item} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={item}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
endIcon={<ChevronRightIcon />}
|
||||
/>
|
||||
<RowButtonDivider />
|
||||
@@ -832,7 +880,12 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
openManageParticipant(item)
|
||||
}
|
||||
label={item}
|
||||
startIcon={<Avatar email={item} />}
|
||||
startIcon={
|
||||
<Avatar
|
||||
email={item}
|
||||
{...{ user, emailByUserID }}
|
||||
/>
|
||||
}
|
||||
endIcon={<ChevronRightIcon />}
|
||||
/>
|
||||
<RowButtonDivider />
|
||||
@@ -856,6 +909,7 @@ const ManageEmailShare: React.FC<ManageEmailShareProps> = ({
|
||||
{...{
|
||||
user,
|
||||
collection,
|
||||
emailByUserID,
|
||||
shareSuggestionEmails,
|
||||
onRootClose,
|
||||
onRemotePull,
|
||||
|
||||
@@ -12,11 +12,9 @@ import {
|
||||
} from "ente-new/photos/components/gallery/BarImpl";
|
||||
import { PeopleHeader } from "ente-new/photos/components/gallery/PeopleHeader";
|
||||
import {
|
||||
areOnlySystemCollections,
|
||||
collectionsSortBy,
|
||||
isSystemCollection,
|
||||
haveOnlySystemCollections,
|
||||
PseudoCollectionID,
|
||||
shouldShowOnCollectionBar,
|
||||
type CollectionsSortBy,
|
||||
type CollectionSummaries,
|
||||
} from "ente-new/photos/services/collection-summary";
|
||||
@@ -38,7 +36,6 @@ import {
|
||||
type GalleryBarAndListHeaderProps = Omit<
|
||||
GalleryBarImplProps,
|
||||
| "collectionSummaries"
|
||||
| "hiddenCollectionSummaries"
|
||||
| "onSelectCollectionID"
|
||||
| "collectionsSortBy"
|
||||
| "onChangeCollectionsSortBy"
|
||||
@@ -48,10 +45,9 @@ type GalleryBarAndListHeaderProps = Omit<
|
||||
* When `true`, the bar is be hidden altogether.
|
||||
*/
|
||||
shouldHide: boolean;
|
||||
collectionSummaries: CollectionSummaries;
|
||||
barCollectionSummaries: CollectionSummaries;
|
||||
activeCollection: Collection;
|
||||
setActiveCollectionID: (collectionID: number) => void;
|
||||
hiddenCollectionSummaries: CollectionSummaries;
|
||||
setPhotoListHeader: (value: TimeStampListItem) => void;
|
||||
filesDownloadProgressAttributesList: FilesDownloadProgressAttributes[];
|
||||
} & Pick<
|
||||
@@ -88,12 +84,11 @@ export const GalleryBarAndListHeader: React.FC<
|
||||
mode,
|
||||
onChangeMode,
|
||||
user,
|
||||
collectionSummaries,
|
||||
barCollectionSummaries: toShowCollectionSummaries,
|
||||
activeCollection,
|
||||
activeCollectionID,
|
||||
setActiveCollectionID,
|
||||
setBlockingLoad,
|
||||
hiddenCollectionSummaries,
|
||||
people,
|
||||
activePerson,
|
||||
emailByUserID,
|
||||
@@ -114,18 +109,10 @@ export const GalleryBarAndListHeader: React.FC<
|
||||
const [collectionsSortBy, setCollectionsSortBy] =
|
||||
useCollectionsSortByLocalState("updation-time-desc");
|
||||
|
||||
const toShowCollectionSummaries = useMemo(
|
||||
() =>
|
||||
mode == "hidden-albums"
|
||||
? hiddenCollectionSummaries
|
||||
: collectionSummaries,
|
||||
[mode, hiddenCollectionSummaries, collectionSummaries],
|
||||
);
|
||||
|
||||
const shouldBeHidden = useMemo(
|
||||
() =>
|
||||
shouldHide ||
|
||||
(areOnlySystemCollections(toShowCollectionSummaries) &&
|
||||
(haveOnlySystemCollections(toShowCollectionSummaries) &&
|
||||
activeCollectionID === PseudoCollectionID.all),
|
||||
[shouldHide, toShowCollectionSummaries, activeCollectionID],
|
||||
);
|
||||
@@ -214,15 +201,15 @@ export const GalleryBarAndListHeader: React.FC<
|
||||
onSelectCollectionID={setActiveCollectionID}
|
||||
onChangeCollectionsSortBy={setCollectionsSortBy}
|
||||
onShowAllAlbums={showAllAlbums}
|
||||
collectionSummaries={sortedCollectionSummaries.filter((x) =>
|
||||
shouldShowOnCollectionBar(x.type),
|
||||
collectionSummaries={sortedCollectionSummaries.filter(
|
||||
(cs) => !cs.attributes.has("hideFromCollectionBar"),
|
||||
)}
|
||||
/>
|
||||
|
||||
<AllAlbums
|
||||
{...allAlbumsVisibilityProps}
|
||||
collectionSummaries={sortedCollectionSummaries.filter(
|
||||
(x) => !isSystemCollection(x.type),
|
||||
(cs) => !cs.attributes.has("system"),
|
||||
)}
|
||||
onSelectCollectionID={setActiveCollectionID}
|
||||
onChangeCollectionsSortBy={setCollectionsSortBy}
|
||||
|
||||
@@ -124,12 +124,12 @@ import { SubscriptionCard } from "./SubscriptionCard";
|
||||
|
||||
type SidebarProps = ModalVisibilityProps & {
|
||||
/**
|
||||
* The latest set of collections, sections and pseudo-collections.
|
||||
* Information about non-hidden collections and pseudo-collections.
|
||||
*
|
||||
* These are used to obtain data about the archive, hidden and trash
|
||||
* "section" entries shown within the shortcut section of the sidebar.
|
||||
*/
|
||||
collectionSummaries: CollectionSummaries;
|
||||
normalCollectionSummaries: CollectionSummaries;
|
||||
/**
|
||||
* The ID of the collection summary that should be shown when the user
|
||||
* activates the "Uncategorized" section shortcut.
|
||||
@@ -171,7 +171,7 @@ type SidebarProps = ModalVisibilityProps & {
|
||||
export const Sidebar: React.FC<SidebarProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
collectionSummaries,
|
||||
normalCollectionSummaries,
|
||||
uncategorizedCollectionSummaryID,
|
||||
onShowPlanSelector,
|
||||
onShowCollectionSummary,
|
||||
@@ -186,7 +186,7 @@ export const Sidebar: React.FC<SidebarProps> = ({
|
||||
<ShortcutSection
|
||||
onCloseSidebar={onClose}
|
||||
{...{
|
||||
collectionSummaries,
|
||||
normalCollectionSummaries,
|
||||
uncategorizedCollectionSummaryID,
|
||||
onShowCollectionSummary,
|
||||
onShowHiddenSection,
|
||||
@@ -458,7 +458,7 @@ const ManageMemberSubscription: React.FC<ManageMemberSubscriptionProps> = ({
|
||||
type ShortcutSectionProps = SectionProps &
|
||||
Pick<
|
||||
SidebarProps,
|
||||
| "collectionSummaries"
|
||||
| "normalCollectionSummaries"
|
||||
| "uncategorizedCollectionSummaryID"
|
||||
| "onShowCollectionSummary"
|
||||
| "onShowHiddenSection"
|
||||
@@ -466,7 +466,7 @@ type ShortcutSectionProps = SectionProps &
|
||||
|
||||
const ShortcutSection: React.FC<ShortcutSectionProps> = ({
|
||||
onCloseSidebar,
|
||||
collectionSummaries,
|
||||
normalCollectionSummaries,
|
||||
uncategorizedCollectionSummaryID,
|
||||
onShowCollectionSummary,
|
||||
onShowHiddenSection,
|
||||
@@ -489,8 +489,8 @@ const ShortcutSection: React.FC<ShortcutSectionProps> = ({
|
||||
const openHiddenSection = () =>
|
||||
void onShowHiddenSection().then(onCloseSidebar);
|
||||
|
||||
const summaryCaption = (collectionSummaryID: number) =>
|
||||
collectionSummaries.get(collectionSummaryID)?.fileCount.toString();
|
||||
const summaryCaption = (summaryID: number) =>
|
||||
normalCollectionSummaries.get(summaryID)?.fileCount.toString();
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -17,7 +17,10 @@ import { useBaseContext } from "ente-base/context";
|
||||
import type { Collection } from "ente-media/collection";
|
||||
import type { CollectionSelectorAttributes } from "ente-new/photos/components/CollectionSelector";
|
||||
import type { GalleryBarMode } from "ente-new/photos/components/gallery/reducer";
|
||||
import { PseudoCollectionID } from "ente-new/photos/services/collection-summary";
|
||||
import {
|
||||
PseudoCollectionID,
|
||||
type CollectionSummary,
|
||||
} from "ente-new/photos/services/collection-summary";
|
||||
import { t } from "i18next";
|
||||
import { type CollectionOp } from "utils/collection";
|
||||
import { type FileOp } from "utils/file";
|
||||
@@ -40,8 +43,6 @@ interface Props {
|
||||
clearSelection: () => void;
|
||||
barMode?: GalleryBarMode;
|
||||
activeCollectionID: number;
|
||||
isFavoriteCollection: boolean;
|
||||
isUncategorizedCollection: boolean;
|
||||
/**
|
||||
* TODO: Need to implement delete-equivalent from shared albums.
|
||||
*
|
||||
@@ -63,7 +64,7 @@ interface Props {
|
||||
* Also note that that user cannot delete files that are not owned by the
|
||||
* user, even if they are in an album owned by the user.
|
||||
*/
|
||||
isIncomingSharedCollection: boolean;
|
||||
activeCollectionSummary: CollectionSummary | undefined;
|
||||
isInSearchMode: boolean;
|
||||
selectedCollection: Collection;
|
||||
isInHiddenSection: boolean;
|
||||
@@ -80,9 +81,7 @@ const SelectedFileOptions = ({
|
||||
clearSelection,
|
||||
barMode,
|
||||
activeCollectionID,
|
||||
isFavoriteCollection,
|
||||
isUncategorizedCollection,
|
||||
isIncomingSharedCollection,
|
||||
activeCollectionSummary,
|
||||
isInSearchMode,
|
||||
isInHiddenSection,
|
||||
}: Props) => {
|
||||
@@ -90,6 +89,15 @@ const SelectedFileOptions = ({
|
||||
|
||||
const peopleMode = barMode == "people";
|
||||
|
||||
const isFavoriteCollection =
|
||||
!!activeCollectionSummary?.attributes.has("userFavorites");
|
||||
|
||||
const isUncategorizedCollection =
|
||||
activeCollectionSummary?.type == "uncategorized";
|
||||
|
||||
const isSharedIncomingCollection =
|
||||
!!activeCollectionSummary?.attributes.has("sharedIncoming");
|
||||
|
||||
const addToCollection = () =>
|
||||
onOpenCollectionSelector({
|
||||
action: "add",
|
||||
@@ -279,7 +287,7 @@ const SelectedFileOptions = ({
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : isIncomingSharedCollection ? (
|
||||
) : isSharedIncomingCollection ? (
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
|
||||
@@ -69,7 +69,7 @@ import { usePeopleStateSnapshot } from "ente-new/photos/components/utils/use-sna
|
||||
import { shouldShowWhatsNew } from "ente-new/photos/services/changelog";
|
||||
import { createAlbum } from "ente-new/photos/services/collection";
|
||||
import {
|
||||
areOnlySystemCollections,
|
||||
haveOnlySystemCollections,
|
||||
PseudoCollectionID,
|
||||
} from "ente-new/photos/services/collection-summary";
|
||||
import exportService from "ente-new/photos/services/export";
|
||||
@@ -108,7 +108,7 @@ import {
|
||||
import { PromiseQueue } from "ente-utils/promise";
|
||||
import { t } from "i18next";
|
||||
import { useRouter, type NextRouter } from "next/router";
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { FileWithPath } from "react-dropzone";
|
||||
import { Trans } from "react-i18next";
|
||||
import {
|
||||
@@ -249,10 +249,27 @@ const Page: React.FC = () => {
|
||||
: state.view?.activeCollectionSummaryID;
|
||||
const activeCollection =
|
||||
state.view?.type == "people" ? undefined : state.view?.activeCollection;
|
||||
const activeCollectionSummary =
|
||||
state.view?.type == "people"
|
||||
? undefined
|
||||
: state.view?.activeCollectionSummary;
|
||||
const activePerson =
|
||||
state.view?.type == "people" ? state.view.activePerson : undefined;
|
||||
const activePersonID = activePerson?.id;
|
||||
|
||||
// TODO: Move into reducer
|
||||
const barCollectionSummaries = useMemo(
|
||||
() =>
|
||||
barMode == "hidden-albums"
|
||||
? state.hiddenCollectionSummaries
|
||||
: state.normalCollectionSummaries,
|
||||
[
|
||||
barMode,
|
||||
state.hiddenCollectionSummaries,
|
||||
state.normalCollectionSummaries,
|
||||
],
|
||||
);
|
||||
|
||||
if (process.env.NEXT_PUBLIC_ENTE_TRACE) console.log("render", state);
|
||||
|
||||
const router = useRouter();
|
||||
@@ -917,20 +934,7 @@ const Page: React.FC = () => {
|
||||
selected.collectionID,
|
||||
state.collections,
|
||||
)}
|
||||
isFavoriteCollection={
|
||||
normalCollectionSummaries.get(activeCollectionID)
|
||||
?.type == "favorites"
|
||||
}
|
||||
isUncategorizedCollection={
|
||||
normalCollectionSummaries.get(activeCollectionID)
|
||||
?.type == "uncategorized"
|
||||
}
|
||||
isIncomingSharedCollection={
|
||||
normalCollectionSummaries.get(activeCollectionID)
|
||||
?.type == "incomingShareCollaborator" ||
|
||||
normalCollectionSummaries.get(activeCollectionID)
|
||||
?.type == "incomingShareViewer"
|
||||
}
|
||||
activeCollectionSummary={activeCollectionSummary}
|
||||
isInSearchMode={isInSearchMode}
|
||||
isInHiddenSection={barMode == "hidden-albums"}
|
||||
/>
|
||||
@@ -967,8 +971,7 @@ const Page: React.FC = () => {
|
||||
}}
|
||||
mode={barMode}
|
||||
shouldHide={isInSearchMode}
|
||||
collectionSummaries={normalCollectionSummaries}
|
||||
hiddenCollectionSummaries={state.hiddenCollectionSummaries}
|
||||
barCollectionSummaries={barCollectionSummaries}
|
||||
emailByUserID={state.emailByUserID}
|
||||
shareSuggestionEmails={state.shareSuggestionEmails}
|
||||
people={
|
||||
@@ -1003,14 +1006,14 @@ const Page: React.FC = () => {
|
||||
onRemoteFilesPull={remoteFilesPull}
|
||||
onUploadFile={(file) => dispatch({ type: "uploadFile", file })}
|
||||
onShowPlanSelector={showPlanSelector}
|
||||
isFirstUpload={areOnlySystemCollections(
|
||||
isFirstUpload={haveOnlySystemCollections(
|
||||
normalCollectionSummaries,
|
||||
)}
|
||||
showSessionExpiredMessage={showSessionExpiredDialog}
|
||||
/>
|
||||
<Sidebar
|
||||
{...sidebarVisibilityProps}
|
||||
collectionSummaries={normalCollectionSummaries}
|
||||
normalCollectionSummaries={normalCollectionSummaries}
|
||||
uncategorizedCollectionSummaryID={
|
||||
state.uncategorizedCollectionSummaryID
|
||||
}
|
||||
@@ -1051,12 +1054,9 @@ const Page: React.FC = () => {
|
||||
setSelected={setSelected}
|
||||
activeCollectionID={activeCollectionID}
|
||||
activePersonID={activePerson?.id}
|
||||
isInIncomingSharedCollection={
|
||||
normalCollectionSummaries.get(activeCollectionID)
|
||||
?.type == "incomingShareCollaborator" ||
|
||||
normalCollectionSummaries.get(activeCollectionID)
|
||||
?.type == "incomingShareViewer"
|
||||
}
|
||||
isInIncomingSharedCollection={activeCollectionSummary?.attributes.has(
|
||||
"sharedIncoming",
|
||||
)}
|
||||
isInHiddenSection={barMode == "hidden-albums"}
|
||||
{...{
|
||||
favoriteFileIDs,
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
// TODO: Review this file
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
import type { Collection } from "ente-media/collection";
|
||||
import { ItemVisibility } from "ente-media/file-metadata";
|
||||
|
||||
export const isArchivedCollection = (item: Collection) => {
|
||||
if (!item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (item.magicMetadata && item.magicMetadata.data) {
|
||||
return item.magicMetadata.data.visibility === ItemVisibility.archived;
|
||||
}
|
||||
|
||||
if (item.sharedMagicMetadata && item.sharedMagicMetadata.data) {
|
||||
return (
|
||||
item.sharedMagicMetadata.data.visibility === ItemVisibility.archived
|
||||
);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
@@ -122,17 +122,21 @@ export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
|
||||
}
|
||||
|
||||
const collections = [...collectionSummaries.values()]
|
||||
.filter(({ id, type }) => {
|
||||
if (id === attributes.relatedCollectionID) {
|
||||
.filter((cs) => {
|
||||
if (cs.id === attributes.relatedCollectionID) {
|
||||
return false;
|
||||
} else if (attributes.action == "add") {
|
||||
return canAddToCollection(type);
|
||||
return canAddToCollection(cs);
|
||||
} else if (attributes.action == "upload") {
|
||||
return canMoveToCollection(type) || type == "uncategorized";
|
||||
return (
|
||||
canMoveToCollection(cs) || cs.type == "uncategorized"
|
||||
);
|
||||
} else if (attributes.action == "restore") {
|
||||
return canMoveToCollection(type) || type == "uncategorized";
|
||||
return (
|
||||
canMoveToCollection(cs) || cs.type == "uncategorized"
|
||||
);
|
||||
} else {
|
||||
return canMoveToCollection(type);
|
||||
return canMoveToCollection(cs);
|
||||
}
|
||||
})
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
|
||||
@@ -23,7 +23,7 @@ import {
|
||||
import { FocusVisibleUnstyledButton } from "ente-new/photos/components/UnstyledButton";
|
||||
import type {
|
||||
CollectionSummary,
|
||||
CollectionSummaryType,
|
||||
CollectionSummaryAttribute,
|
||||
CollectionsSortBy,
|
||||
} from "ente-new/photos/services/collection-summary";
|
||||
import type { Person } from "ente-new/photos/services/ml/people";
|
||||
@@ -528,7 +528,7 @@ const CardText: React.FC<React.PropsWithChildren> = ({ children }) => (
|
||||
);
|
||||
|
||||
interface CollectionBarCardIconProps {
|
||||
attributes: Set<CollectionSummaryType>;
|
||||
attributes: Set<CollectionSummaryAttribute>;
|
||||
}
|
||||
|
||||
const CollectionBarCardIcon: React.FC<CollectionBarCardIconProps> = ({
|
||||
@@ -538,15 +538,17 @@ const CollectionBarCardIcon: React.FC<CollectionBarCardIconProps> = ({
|
||||
// will be true simultaneously even in the rarest of cases (a pinned and
|
||||
// shared album that is also archived), and there is enough space for 3.
|
||||
<CollectionBarCardIcon_>
|
||||
{attributes.has("favorites") && <FavoriteRoundedIcon />}
|
||||
{attributes.has("userFavorites") && <FavoriteRoundedIcon />}
|
||||
{attributes.has("pinned") && (
|
||||
// Need && to override the 20px set in the container.
|
||||
<PushPinIcon sx={{ "&&": { fontSize: "18px" } }} />
|
||||
)}
|
||||
{(attributes.has("outgoingShare") ||
|
||||
attributes.has("incomingShareViewer") ||
|
||||
attributes.has("incomingShareCollaborator")) && <PeopleIcon />}
|
||||
{attributes.has("sharedOnlyViaLink") && <LinkIcon />}
|
||||
{attributes.has("shared") &&
|
||||
(attributes.has("sharedOnlyViaLink") ? (
|
||||
<LinkIcon />
|
||||
) : (
|
||||
<PeopleIcon />
|
||||
))}
|
||||
{attributes.has("archived") && <ArchiveIcon sx={{ opacity: 0.48 }} />}
|
||||
</CollectionBarCardIcon_>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import type { User } from "ente-accounts/services/user";
|
||||
import { isArchivedCollection } from "ente-gallery/services/magic-metadata";
|
||||
import {
|
||||
groupFilesByCollectionID,
|
||||
sortFiles,
|
||||
@@ -18,6 +17,7 @@ import {
|
||||
import type { MagicMetadata } from "ente-media/magic-metadata";
|
||||
import {
|
||||
createCollectionNameByID,
|
||||
isArchivedCollection,
|
||||
isHiddenCollection,
|
||||
} from "ente-new/photos/services/collection";
|
||||
import { sortTrashItems, type TrashItem } from "ente-new/photos/services/trash";
|
||||
@@ -33,6 +33,7 @@ import {
|
||||
CollectionSummarySortPriority,
|
||||
PseudoCollectionID,
|
||||
type CollectionSummary,
|
||||
type CollectionSummaryAttribute,
|
||||
type CollectionSummaryType,
|
||||
} from "../../services/collection-summary";
|
||||
import type { PeopleState, Person } from "../../services/ml/people";
|
||||
@@ -68,6 +69,10 @@ export type GalleryView =
|
||||
* or {@link hiddenCollections}.
|
||||
*/
|
||||
activeCollection: Collection | undefined;
|
||||
/**
|
||||
* The currently active collection summary.
|
||||
*/
|
||||
activeCollectionSummary: CollectionSummary;
|
||||
}
|
||||
| {
|
||||
/**
|
||||
@@ -553,10 +558,28 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
collectionFiles,
|
||||
);
|
||||
|
||||
const normalCollectionSummaries = deriveNormalCollectionSummaries(
|
||||
normalCollections,
|
||||
action.user,
|
||||
trashItems,
|
||||
collectionFiles,
|
||||
hiddenFileIDs,
|
||||
archivedFileIDs,
|
||||
);
|
||||
|
||||
const hiddenCollectionSummaries = deriveHiddenCollectionSummaries(
|
||||
hiddenCollections,
|
||||
action.user,
|
||||
collectionFiles,
|
||||
);
|
||||
|
||||
const view = {
|
||||
type: "albums" as const,
|
||||
activeCollectionSummaryID: PseudoCollectionID.all,
|
||||
activeCollection: undefined,
|
||||
activeCollectionSummary: normalCollectionSummaries.get(
|
||||
PseudoCollectionID.all,
|
||||
)!,
|
||||
};
|
||||
|
||||
return stateByUpdatingFilteredFiles({
|
||||
@@ -592,19 +615,8 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
familyData,
|
||||
collections,
|
||||
),
|
||||
normalCollectionSummaries: deriveNormalCollectionSummaries(
|
||||
normalCollections,
|
||||
action.user,
|
||||
trashItems,
|
||||
collectionFiles,
|
||||
hiddenFileIDs,
|
||||
archivedFileIDs,
|
||||
),
|
||||
hiddenCollectionSummaries: deriveHiddenCollectionSummaries(
|
||||
hiddenCollections,
|
||||
action.user,
|
||||
collectionFiles,
|
||||
),
|
||||
normalCollectionSummaries,
|
||||
hiddenCollectionSummaries,
|
||||
uncategorizedCollectionSummaryID:
|
||||
deriveUncategorizedCollectionSummaryID(normalCollections),
|
||||
view,
|
||||
@@ -636,6 +648,7 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
hiddenFileIDs,
|
||||
archivedFileIDs,
|
||||
);
|
||||
|
||||
const hiddenCollectionSummaries = deriveHiddenCollectionSummaries(
|
||||
hiddenCollections,
|
||||
state.user!,
|
||||
@@ -928,6 +941,10 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
type: "albums",
|
||||
activeCollectionSummaryID: PseudoCollectionID.all,
|
||||
activeCollection: undefined,
|
||||
activeCollectionSummary:
|
||||
state.normalCollectionSummaries.get(
|
||||
PseudoCollectionID.all,
|
||||
)!,
|
||||
},
|
||||
isInSearchMode: false,
|
||||
});
|
||||
@@ -945,6 +962,10 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
type: "hidden-albums",
|
||||
activeCollectionSummaryID: PseudoCollectionID.hiddenItems,
|
||||
activeCollection: undefined,
|
||||
activeCollectionSummary:
|
||||
state.hiddenCollectionSummaries.get(
|
||||
PseudoCollectionID.hiddenItems,
|
||||
)!,
|
||||
},
|
||||
isInSearchMode: false,
|
||||
});
|
||||
@@ -971,31 +992,53 @@ const galleryReducer: React.Reducer<GalleryState, GalleryAction> = (
|
||||
});
|
||||
}
|
||||
|
||||
case "showCollectionSummary":
|
||||
case "showCollectionSummary": {
|
||||
const selectedCollectionSummaryID = action.collectionSummaryID;
|
||||
const activeCollection = state.collections.find(
|
||||
({ id }) => id == selectedCollectionSummaryID,
|
||||
);
|
||||
|
||||
let view: GalleryState["view"];
|
||||
if (
|
||||
selectedCollectionSummaryID !== undefined &&
|
||||
state.hiddenCollectionSummaries.has(selectedCollectionSummaryID)
|
||||
) {
|
||||
const activeCollectionSummaryID = selectedCollectionSummaryID;
|
||||
view = {
|
||||
type: "hidden-albums",
|
||||
activeCollectionSummaryID,
|
||||
activeCollection,
|
||||
activeCollectionSummary:
|
||||
state.hiddenCollectionSummaries.get(
|
||||
activeCollectionSummaryID,
|
||||
)!,
|
||||
};
|
||||
} else {
|
||||
const activeCollectionSummaryID =
|
||||
selectedCollectionSummaryID ?? PseudoCollectionID.all;
|
||||
view = {
|
||||
type: "albums",
|
||||
activeCollectionSummaryID,
|
||||
activeCollection,
|
||||
activeCollectionSummary:
|
||||
state.normalCollectionSummaries.get(
|
||||
activeCollectionSummaryID,
|
||||
)!,
|
||||
};
|
||||
}
|
||||
|
||||
return stateByUpdatingFilteredFiles({
|
||||
...state,
|
||||
selectedCollectionSummaryID: action.collectionSummaryID,
|
||||
selectedCollectionSummaryID,
|
||||
extraVisiblePerson: undefined,
|
||||
searchResults: undefined,
|
||||
searchSuggestion: undefined,
|
||||
isRecomputingSearchResults: false,
|
||||
pendingSearchSuggestions: [],
|
||||
view: {
|
||||
type:
|
||||
action.collectionSummaryID !== undefined &&
|
||||
state.hiddenCollectionSummaries.has(
|
||||
action.collectionSummaryID,
|
||||
)
|
||||
? "hidden-albums"
|
||||
: "albums",
|
||||
activeCollectionSummaryID:
|
||||
action.collectionSummaryID ?? PseudoCollectionID.all,
|
||||
activeCollection: state.collections.find(
|
||||
({ id }) => id == action.collectionSummaryID,
|
||||
),
|
||||
},
|
||||
view,
|
||||
isInSearchMode: false,
|
||||
});
|
||||
}
|
||||
|
||||
case "showPeople": {
|
||||
const { view, extraVisiblePerson } = derivePeopleView(
|
||||
@@ -1256,7 +1299,11 @@ const deriveNormalCollectionSummaries = (
|
||||
...pseudoCollectionOptionsForFiles([]),
|
||||
id,
|
||||
type: "uncategorized",
|
||||
attributes: new Set(["uncategorized"]),
|
||||
attributes: new Set([
|
||||
"uncategorized",
|
||||
"system",
|
||||
"hideFromCollectionBar",
|
||||
]),
|
||||
name: t("section_uncategorized"),
|
||||
sortPriority: CollectionSummarySortPriority.system,
|
||||
});
|
||||
@@ -1278,7 +1325,7 @@ const deriveNormalCollectionSummaries = (
|
||||
...pseudoCollectionOptionsForFiles(allSectionFiles),
|
||||
id: PseudoCollectionID.all,
|
||||
type: "all",
|
||||
attributes: new Set(["all"]),
|
||||
attributes: new Set(["all", "system"]),
|
||||
name: t("section_all"),
|
||||
sortPriority: CollectionSummarySortPriority.system,
|
||||
});
|
||||
@@ -1291,7 +1338,7 @@ const deriveNormalCollectionSummaries = (
|
||||
id: PseudoCollectionID.trash,
|
||||
name: t("section_trash"),
|
||||
type: "trash",
|
||||
attributes: new Set(["trash"]),
|
||||
attributes: new Set(["trash", "system", "hideFromCollectionBar"]),
|
||||
coverFile: undefined,
|
||||
sortPriority: CollectionSummarySortPriority.other,
|
||||
});
|
||||
@@ -1300,8 +1347,12 @@ const deriveNormalCollectionSummaries = (
|
||||
...pseudoCollectionOptionsForFiles(archiveItemsFiles),
|
||||
id: PseudoCollectionID.archiveItems,
|
||||
name: t("section_archive"),
|
||||
type: "archive",
|
||||
attributes: new Set(["archive"]),
|
||||
type: "archiveItems",
|
||||
attributes: new Set([
|
||||
"archiveItems",
|
||||
"system",
|
||||
"hideFromCollectionBar",
|
||||
]),
|
||||
coverFile: undefined,
|
||||
sortPriority: CollectionSummarySortPriority.other,
|
||||
});
|
||||
@@ -1345,7 +1396,7 @@ const deriveHiddenCollectionSummaries = (
|
||||
id: PseudoCollectionID.hiddenItems,
|
||||
name: t("hidden_items"),
|
||||
type: "hiddenItems",
|
||||
attributes: new Set(["hiddenItems"]),
|
||||
attributes: new Set(["hiddenItems", "system"]),
|
||||
sortPriority: CollectionSummarySortPriority.system,
|
||||
});
|
||||
|
||||
@@ -1377,26 +1428,42 @@ const createCollectionSummaries = (
|
||||
? collection.type
|
||||
: "album";
|
||||
|
||||
const attributes = new Set<CollectionSummaryAttribute>();
|
||||
|
||||
let type: CollectionSummaryType;
|
||||
let name = collection.name;
|
||||
let sortPriority: CollectionSummarySortPriority =
|
||||
CollectionSummarySortPriority.other;
|
||||
|
||||
if (collection.owner.id != user.id) {
|
||||
type =
|
||||
// This case needs to be the first; the rest assume that they're
|
||||
// dealing with collections owned by the user.
|
||||
type = "sharedIncoming";
|
||||
attributes.add("shared");
|
||||
attributes.add("sharedIncoming");
|
||||
attributes.add(
|
||||
collection.sharees.find((s) => s.id == user.id)?.role ==
|
||||
"COLLABORATOR"
|
||||
? "incomingShareCollaborator"
|
||||
: "incomingShareViewer";
|
||||
"COLLABORATOR"
|
||||
? "sharedIncomingCollaborator"
|
||||
: "sharedIncomingViewer",
|
||||
);
|
||||
} else if (collectionType == "uncategorized") {
|
||||
type = "uncategorized";
|
||||
name = t("section_uncategorized");
|
||||
attributes.add("system");
|
||||
attributes.add("hideFromCollectionBar");
|
||||
sortPriority = CollectionSummarySortPriority.system;
|
||||
} else if (collectionType == "favorites") {
|
||||
// [Note: User and shared favorites]
|
||||
//
|
||||
// "favorites" can be both the user's own favorites, or favorites of
|
||||
// other users shared with them. However, all of the latter will get
|
||||
// typed as "incomingShareViewer" or "incomingShareCollaborator" in
|
||||
// the first case above. So if a collection summary has type
|
||||
// "favorites", it is guaranteed to be the user's own favorites.
|
||||
// typed as "sharedIncoming" in the first case above.
|
||||
//
|
||||
// So if we get here and the collection summary has type
|
||||
// "favorites", it is guaranteed to be the user's own favorites. We
|
||||
// mark these with the type "userFavorites", which gives it special
|
||||
// treatment like custom icon etc.
|
||||
//
|
||||
// However, notice that the type of the _collection_ itself is not
|
||||
// changed, so whenever we're checking the type of the collection
|
||||
@@ -1408,70 +1475,37 @@ const createCollectionSummaries = (
|
||||
// classification of this collection summary is that it is the
|
||||
// user's "favorites", everything else is secondary and can be part
|
||||
// of the `attributes` computed below.
|
||||
type = collectionType;
|
||||
type = "userFavorites";
|
||||
name = t("favorites");
|
||||
sortPriority = CollectionSummarySortPriority.favorites;
|
||||
} else if (isOutgoingShare(collection, user)) {
|
||||
type = "outgoingShare";
|
||||
} else if (isSharedOnlyViaLink(collection)) {
|
||||
type = "sharedOnlyViaLink";
|
||||
} else if (isArchivedCollection(collection)) {
|
||||
type = "archived";
|
||||
} else if (isDefaultHiddenCollection(collection)) {
|
||||
type = "defaultHidden";
|
||||
} else if (
|
||||
collection.magicMetadata?.data.order == CollectionOrder.pinned
|
||||
) {
|
||||
type = "pinned";
|
||||
sortPriority = CollectionSummarySortPriority.pinned;
|
||||
attributes.add("system");
|
||||
attributes.add("hideFromCollectionBar");
|
||||
} else {
|
||||
type = collectionType;
|
||||
}
|
||||
|
||||
// This block of code duplicates the above. Such duplication is needed
|
||||
// until type is completely replaced by attributes.
|
||||
const attributes = new Set<CollectionSummaryType>();
|
||||
if (collection.owner.id != user.id) {
|
||||
attributes.add(
|
||||
collection.sharees.find((s) => s.id == user.id)?.role ==
|
||||
"COLLABORATOR"
|
||||
? "incomingShareCollaborator"
|
||||
: "incomingShareViewer",
|
||||
);
|
||||
attributes.add(type);
|
||||
attributes.add(collectionType);
|
||||
|
||||
if (collection.owner.id == user.id && collection.sharees.length) {
|
||||
attributes.add("shared");
|
||||
attributes.add("sharedOutgoing");
|
||||
}
|
||||
if (isOutgoingShare(collection, user)) {
|
||||
attributes.add("outgoingShare");
|
||||
}
|
||||
if (isSharedOnlyViaLink(collection)) {
|
||||
if (collection.publicURLs.length && !collection.sharees.length) {
|
||||
attributes.add("shared");
|
||||
attributes.add("sharedOnlyViaLink");
|
||||
}
|
||||
if (isArchivedCollection(collection)) {
|
||||
attributes.add("archived");
|
||||
}
|
||||
if (isDefaultHiddenCollection(collection)) {
|
||||
attributes.add("defaultHidden");
|
||||
}
|
||||
if (collection.magicMetadata?.data.order == CollectionOrder.pinned) {
|
||||
attributes.add("pinned");
|
||||
}
|
||||
switch (collectionType) {
|
||||
case "favorites":
|
||||
// We don't want to treat other folks' favorites specially like
|
||||
// the user's own favorites (giving it a special icon etc), so
|
||||
// only apply the favorites attribute if it is the user's own.
|
||||
if (collection.owner.id == user.id)
|
||||
attributes.add(collectionType);
|
||||
break;
|
||||
default:
|
||||
attributes.add(collectionType);
|
||||
break;
|
||||
sortPriority = CollectionSummarySortPriority.pinned;
|
||||
}
|
||||
|
||||
let name: string;
|
||||
if (type == "uncategorized") {
|
||||
name = t("section_uncategorized");
|
||||
} else if (type == "favorites") {
|
||||
name = t("favorites");
|
||||
} else if (collectionType == "favorites") {
|
||||
if (type == "sharedIncoming" && collectionType == "favorites") {
|
||||
// See: [Note: User and shared favorites] above.
|
||||
//
|
||||
// Use the first letter of the email of the user who shared this
|
||||
@@ -1484,8 +1518,6 @@ const createCollectionSummaries = (
|
||||
} else {
|
||||
name = t("shared_favorites");
|
||||
}
|
||||
} else {
|
||||
name = collection.name;
|
||||
}
|
||||
|
||||
const collectionFiles = filesByCollection.get(collection.id);
|
||||
@@ -1538,14 +1570,6 @@ const findCoverFiles = (
|
||||
return coverFiles;
|
||||
};
|
||||
|
||||
const isOutgoingShare = (collection: Collection, user: User) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
collection.owner.id === user.id && collection.sharees?.length > 0;
|
||||
|
||||
const isSharedOnlyViaLink = (collection: Collection) =>
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
collection.publicURLs?.length && !collection.sharees?.length;
|
||||
|
||||
/**
|
||||
* Compute the {@link GalleryView} from its dependencies when we are switching
|
||||
* to (or back to) the "albums" view, or the underlying collections might've
|
||||
@@ -1558,11 +1582,17 @@ const deriveAlbumsViewAndSelectedID = (
|
||||
selectedCollectionSummaryID: GalleryState["selectedCollectionSummaryID"],
|
||||
) => {
|
||||
// Make sure that the last selected ID is still valid by searching for it.
|
||||
const activeCollectionSummaryID = selectedCollectionSummaryID
|
||||
? collectionSummaries.get(selectedCollectionSummaryID)?.id
|
||||
const selectedCollectionSummary = selectedCollectionSummaryID
|
||||
? collectionSummaries.get(selectedCollectionSummaryID)
|
||||
: undefined;
|
||||
|
||||
const activeCollectionSummaryID =
|
||||
selectedCollectionSummary?.id ?? PseudoCollectionID.all;
|
||||
const activeCollectionSummary = collectionSummaries.get(
|
||||
activeCollectionSummaryID,
|
||||
)!;
|
||||
const activeCollection =
|
||||
activeCollectionSummaryID &&
|
||||
selectedCollectionSummary &&
|
||||
!hiddenCollectionIDs.has(activeCollectionSummaryID)
|
||||
? collections.find(({ id }) => id == activeCollectionSummaryID)
|
||||
: undefined;
|
||||
@@ -1570,9 +1600,9 @@ const deriveAlbumsViewAndSelectedID = (
|
||||
selectedCollectionSummaryID: activeCollectionSummaryID,
|
||||
view: {
|
||||
type: "albums" as const,
|
||||
activeCollectionSummaryID:
|
||||
activeCollectionSummaryID ?? PseudoCollectionID.all,
|
||||
activeCollectionSummaryID,
|
||||
activeCollection,
|
||||
activeCollectionSummary,
|
||||
},
|
||||
};
|
||||
};
|
||||
@@ -1588,11 +1618,17 @@ const deriveHiddenAlbumsViewAndSelectedID = (
|
||||
selectedCollectionSummaryID: GalleryState["selectedCollectionSummaryID"],
|
||||
) => {
|
||||
// Make sure that the last selected ID is still valid by searching for it.
|
||||
const activeCollectionSummaryID = selectedCollectionSummaryID
|
||||
? hiddenCollectionSummaries.get(selectedCollectionSummaryID)?.id
|
||||
const selectedCollectionSummary = selectedCollectionSummaryID
|
||||
? hiddenCollectionSummaries.get(selectedCollectionSummaryID)
|
||||
: undefined;
|
||||
|
||||
const activeCollectionSummaryID =
|
||||
selectedCollectionSummary?.id ?? PseudoCollectionID.hiddenItems;
|
||||
const activeCollectionSummary = hiddenCollectionSummaries.get(
|
||||
activeCollectionSummaryID,
|
||||
)!;
|
||||
const activeCollection =
|
||||
activeCollectionSummaryID &&
|
||||
selectedCollectionSummary &&
|
||||
hiddenCollectionIDs.has(activeCollectionSummaryID)
|
||||
? collections.find(({ id }) => id == activeCollectionSummaryID)
|
||||
: undefined;
|
||||
@@ -1600,9 +1636,9 @@ const deriveHiddenAlbumsViewAndSelectedID = (
|
||||
selectedCollectionSummaryID: activeCollectionSummaryID,
|
||||
view: {
|
||||
type: "hidden-albums" as const,
|
||||
activeCollectionSummaryID:
|
||||
activeCollectionSummaryID ?? PseudoCollectionID.hiddenItems,
|
||||
activeCollectionSummaryID,
|
||||
activeCollection,
|
||||
activeCollectionSummary,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -4,14 +4,22 @@ import type { EnteFile } from "ente-media/file";
|
||||
export type CollectionSummaryType =
|
||||
| CollectionType
|
||||
| "all"
|
||||
| "archive"
|
||||
| "trash"
|
||||
| "hiddenItems"
|
||||
| "defaultHidden"
|
||||
| "outgoingShare"
|
||||
| "incomingShareViewer"
|
||||
| "incomingShareCollaborator"
|
||||
| "archiveItems"
|
||||
| "trash"
|
||||
| "userFavorites"
|
||||
| "sharedIncoming";
|
||||
|
||||
export type CollectionSummaryAttribute =
|
||||
| CollectionSummaryType
|
||||
| "shared"
|
||||
| "sharedOutgoing"
|
||||
| "sharedIncomingViewer"
|
||||
| "sharedIncomingCollaborator"
|
||||
| "sharedOnlyViaLink"
|
||||
| "system"
|
||||
| "hideFromCollectionBar"
|
||||
| "archived"
|
||||
| "pinned";
|
||||
|
||||
@@ -117,7 +125,7 @@ export interface CollectionSummary {
|
||||
* ad-hoc "UI" attributes which make it easier and more efficient for the UI
|
||||
* elements to render the collection summary in the UI.
|
||||
*/
|
||||
attributes: Set<CollectionSummaryType>;
|
||||
attributes: Set<CollectionSummaryAttribute>;
|
||||
/**
|
||||
* The name of the collection or pseudo-collection surfaced in the UI.
|
||||
*/
|
||||
@@ -201,47 +209,15 @@ export const CollectionSummarySortPriority = {
|
||||
export type CollectionSummarySortPriority =
|
||||
(typeof CollectionSummarySortPriority)[keyof typeof CollectionSummarySortPriority];
|
||||
|
||||
const systemCSTypes = new Set<CollectionSummaryType>([
|
||||
"all",
|
||||
"archive",
|
||||
"trash",
|
||||
"uncategorized",
|
||||
"hiddenItems",
|
||||
"defaultHidden",
|
||||
]);
|
||||
|
||||
const addToDisabledCSTypes = new Set<CollectionSummaryType>([
|
||||
...systemCSTypes,
|
||||
"incomingShareViewer",
|
||||
]);
|
||||
|
||||
const moveToDisabledCSTypes = new Set<CollectionSummaryType>([
|
||||
...addToDisabledCSTypes,
|
||||
"incomingShareCollaborator",
|
||||
]);
|
||||
|
||||
const hideFromCollectionBarCSTypes = new Set<CollectionSummaryType>([
|
||||
"trash",
|
||||
"archive",
|
||||
"uncategorized",
|
||||
"defaultHidden",
|
||||
]);
|
||||
|
||||
export const isSystemCollection = (type: CollectionSummaryType) =>
|
||||
systemCSTypes.has(type);
|
||||
|
||||
export const areOnlySystemCollections = (
|
||||
export const haveOnlySystemCollections = (
|
||||
collectionSummaries: CollectionSummaries,
|
||||
) =>
|
||||
[...collectionSummaries.values()].every(({ type }) =>
|
||||
isSystemCollection(type),
|
||||
[...collectionSummaries.values()].every((cs) =>
|
||||
cs.attributes.has("system"),
|
||||
);
|
||||
|
||||
export const canAddToCollection = (type: CollectionSummaryType) =>
|
||||
!addToDisabledCSTypes.has(type);
|
||||
export const canAddToCollection = ({ attributes }: CollectionSummary) =>
|
||||
!attributes.has("system") && !attributes.has("sharedIncomingViewer");
|
||||
|
||||
export const canMoveToCollection = (type: CollectionSummaryType) =>
|
||||
!moveToDisabledCSTypes.has(type);
|
||||
|
||||
export const shouldShowOnCollectionBar = (type: CollectionSummaryType) =>
|
||||
!hideFromCollectionBarCSTypes.has(type);
|
||||
export const canMoveToCollection = ({ attributes }: CollectionSummary) =>
|
||||
!attributes.has("system") && !attributes.has("sharedIncoming");
|
||||
|
||||
@@ -696,7 +696,7 @@ export const deleteFromTrash = async (fileIDs: number[]) =>
|
||||
*
|
||||
* The move operation is not supported across ownership boundaries. The remove
|
||||
* operation is only supported across ownership boundaries, but the user should
|
||||
* have owner ship of either the file or collection (not both).
|
||||
* have ownership of either the file or collection (not both).
|
||||
*
|
||||
* In more detail, the above three scenarios can be described this way.
|
||||
*
|
||||
@@ -1184,9 +1184,25 @@ export const findDefaultHiddenCollectionIDs = (collections: Collection[]) =>
|
||||
.map((collection) => collection.id),
|
||||
);
|
||||
|
||||
/**
|
||||
* Return `true` if the given collection is hidden.
|
||||
*
|
||||
* Hidden collections are those that have their visibility set to hidden in the
|
||||
* collection's owner's private magic metadata.
|
||||
*/
|
||||
export const isHiddenCollection = (collection: Collection) =>
|
||||
collection.magicMetadata?.data.visibility == ItemVisibility.hidden;
|
||||
|
||||
/**
|
||||
* Return `true` if the given collection is archived.
|
||||
*
|
||||
* Archived collections are those that have their visibility set to hidden in the
|
||||
* collection's private magic metadata or per-sharee private metadata.
|
||||
*/
|
||||
export const isArchivedCollection = (collection: Collection) =>
|
||||
collection.magicMetadata?.data.visibility == ItemVisibility.archived ||
|
||||
collection.sharedMagicMetadata?.data.visibility == ItemVisibility.archived;
|
||||
|
||||
/**
|
||||
* Hide the provided {@link files} by moving them to the default hidden
|
||||
* collection.
|
||||
|
||||
Reference in New Issue
Block a user