[web] Collection summary refactoring (#6390)

This commit is contained in:
Manav Rathi
2025-06-27 19:00:08 +05:30
committed by GitHub
12 changed files with 380 additions and 343 deletions

View File

@@ -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")
}
>

View File

@@ -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,

View File

@@ -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}

View File

@@ -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 (
<>

View File

@@ -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 />

View File

@@ -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,

View File

@@ -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;
};

View File

@@ -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))

View File

@@ -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_>
);

View File

@@ -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,
},
};
};

View File

@@ -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");

View File

@@ -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.