[web] Collection selector related cleanup - Part 2/2 (#3585)
Completes https://github.com/ente-io/ente/pull/3581
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import {
|
||||
AllCollectionTile,
|
||||
CollectionTileButton,
|
||||
ItemCard,
|
||||
LargeTileTextOverlay,
|
||||
} from "@/new/photos/components/ItemCards";
|
||||
} from "@/new/photos/components/Tiles";
|
||||
import type { CollectionSummary } from "@/new/photos/services/collection/ui";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import useWindowSize from "@ente/shared/hooks/useWindowSize";
|
||||
@@ -68,7 +68,7 @@ const AllCollectionRow = React.memo(
|
||||
<div style={style}>
|
||||
<FlexWrapper gap={"4px"} padding={"16px"}>
|
||||
{collectionRow.map((item: any) => (
|
||||
<AllCollectionCard
|
||||
<CollectionButton
|
||||
isScrolling={isScrolling}
|
||||
onCollectionClick={onCollectionClick}
|
||||
collectionSummary={item}
|
||||
@@ -160,13 +160,13 @@ interface AllCollectionCardProps {
|
||||
isScrolling?: boolean;
|
||||
}
|
||||
|
||||
const AllCollectionCard: React.FC<AllCollectionCardProps> = ({
|
||||
const CollectionButton: React.FC<AllCollectionCardProps> = ({
|
||||
onCollectionClick,
|
||||
collectionSummary,
|
||||
isScrolling,
|
||||
}) => (
|
||||
<ItemCard
|
||||
TileComponent={AllCollectionTile}
|
||||
TileComponent={CollectionTileButton}
|
||||
coverFile={collectionSummary.coverFile}
|
||||
onClick={() => onCollectionClick(collectionSummary.id)}
|
||||
isScrolling={isScrolling}
|
||||
|
||||
@@ -1,232 +0,0 @@
|
||||
import type { Collection } from "@/media/collection";
|
||||
import {
|
||||
AllCollectionTile,
|
||||
ItemCard,
|
||||
ItemTileOverlay,
|
||||
LargeTileTextOverlay,
|
||||
} from "@/new/photos/components/ItemCards";
|
||||
import {
|
||||
canAddToCollection,
|
||||
canMoveToCollection,
|
||||
CollectionSummaryOrder,
|
||||
type CollectionSummaries,
|
||||
type CollectionSummary,
|
||||
} from "@/new/photos/services/collection/ui";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import DialogTitleWithCloseButton from "@ente/shared/components/DialogBox/TitleWithCloseButton";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
styled,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export enum CollectionSelectorIntent {
|
||||
upload,
|
||||
add,
|
||||
move,
|
||||
restore,
|
||||
unhide,
|
||||
}
|
||||
|
||||
export interface CollectionSelectorAttributes {
|
||||
callback: (collection: Collection) => void;
|
||||
showNextModal: () => void;
|
||||
/**
|
||||
* The {@link intent} modifies the title of the dialog, and also filters
|
||||
* the list of collections the user can select from appropriately.
|
||||
*/
|
||||
intent: CollectionSelectorIntent;
|
||||
fromCollection?: number;
|
||||
onCancel?: () => void;
|
||||
}
|
||||
|
||||
interface CollectionSelectorProps {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
attributes: CollectionSelectorAttributes;
|
||||
collectionSummaries: CollectionSummaries;
|
||||
/**
|
||||
* A function to map from a collection ID to a {@link Collection}.
|
||||
*
|
||||
* This is invoked when the user makes a selection, to convert the ID of the
|
||||
* selected collection into a collection object that can be passed to the
|
||||
* {@link callback} attribute of {@link CollectionSelectorAttributes}.
|
||||
*/
|
||||
collectionForCollectionID: (collectionID: number) => Promise<Collection>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A dialog allowing the user to select one of their existing collections or
|
||||
* create a new one.
|
||||
*/
|
||||
export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
|
||||
attributes,
|
||||
collectionSummaries,
|
||||
collectionForCollectionID,
|
||||
...props
|
||||
}) => {
|
||||
// Make the dialog fullscreen if the screen is <= the dialog's max width.
|
||||
const isFullScreen = useMediaQuery("(max-width: 494px)");
|
||||
|
||||
const [collectionsToShow, setCollectionsToShow] = useState<
|
||||
CollectionSummary[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!attributes || !props.open) {
|
||||
return;
|
||||
}
|
||||
const main = async () => {
|
||||
const collectionsToShow = [...collectionSummaries.values()]
|
||||
?.filter(({ id, type }) => {
|
||||
if (id === attributes.fromCollection) {
|
||||
return false;
|
||||
} else if (
|
||||
attributes.intent === CollectionSelectorIntent.add
|
||||
) {
|
||||
return canAddToCollection(type);
|
||||
} else if (
|
||||
attributes.intent === CollectionSelectorIntent.upload
|
||||
) {
|
||||
return (
|
||||
canMoveToCollection(type) || type == "uncategorized"
|
||||
);
|
||||
} else if (
|
||||
attributes.intent === CollectionSelectorIntent.restore
|
||||
) {
|
||||
return (
|
||||
canMoveToCollection(type) || type == "uncategorized"
|
||||
);
|
||||
} else {
|
||||
return canMoveToCollection(type);
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
CollectionSummaryOrder.get(a.type) -
|
||||
CollectionSummaryOrder.get(b.type)
|
||||
);
|
||||
});
|
||||
if (collectionsToShow.length === 0) {
|
||||
props.onClose();
|
||||
attributes.showNextModal();
|
||||
}
|
||||
setCollectionsToShow(collectionsToShow);
|
||||
};
|
||||
main();
|
||||
}, [collectionSummaries, attributes, props.open]);
|
||||
|
||||
if (!collectionsToShow?.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const handleCollectionClick = async (collectionID: number) => {
|
||||
attributes.callback(await collectionForCollectionID(collectionID));
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
const onUserTriggeredClose = () => {
|
||||
attributes.onCancel?.();
|
||||
props.onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog_
|
||||
onClose={onUserTriggeredClose}
|
||||
open={props.open}
|
||||
fullScreen={isFullScreen}
|
||||
fullWidth
|
||||
>
|
||||
<DialogTitleWithCloseButton onClose={onUserTriggeredClose}>
|
||||
{attributes.intent === CollectionSelectorIntent.upload
|
||||
? t("upload_to_album")
|
||||
: attributes.intent === CollectionSelectorIntent.add
|
||||
? t("add_to_album")
|
||||
: attributes.intent === CollectionSelectorIntent.move
|
||||
? t("move_to_album")
|
||||
: attributes.intent === CollectionSelectorIntent.restore
|
||||
? t("restore_to_album")
|
||||
: attributes.intent ===
|
||||
CollectionSelectorIntent.unhide
|
||||
? t("unhide_to_album")
|
||||
: t("select_album")}
|
||||
</DialogTitleWithCloseButton>
|
||||
<DialogContent sx={{ "&&&": { padding: 0 } }}>
|
||||
<FlexWrapper flexWrap="wrap" gap={"4px"} padding={"16px"}>
|
||||
<AddCollectionButton
|
||||
showNextModal={attributes.showNextModal}
|
||||
/>
|
||||
{collectionsToShow.map((collectionSummary) => (
|
||||
<CollectionSelectorCard
|
||||
key={collectionSummary.id}
|
||||
collectionSummary={collectionSummary}
|
||||
onCollectionClick={handleCollectionClick}
|
||||
/>
|
||||
))}
|
||||
</FlexWrapper>
|
||||
</DialogContent>
|
||||
</Dialog_>
|
||||
);
|
||||
};
|
||||
|
||||
export const AllCollectionMobileBreakpoint = 559;
|
||||
|
||||
export const Dialog_ = styled(Dialog)(({ theme }) => ({
|
||||
"& .MuiPaper-root": {
|
||||
maxWidth: "494px",
|
||||
},
|
||||
"& .MuiDialogTitle-root": {
|
||||
padding: "16px",
|
||||
paddingRight: theme.spacing(1),
|
||||
},
|
||||
"& .MuiDialogContent-root": {
|
||||
padding: "16px",
|
||||
},
|
||||
}));
|
||||
|
||||
interface CollectionSelectorCardProps {
|
||||
collectionSummary: CollectionSummary;
|
||||
onCollectionClick: (collectionID: number) => void;
|
||||
}
|
||||
|
||||
const CollectionSelectorCard: React.FC<CollectionSelectorCardProps> = ({
|
||||
collectionSummary,
|
||||
onCollectionClick,
|
||||
}) => (
|
||||
<ItemCard
|
||||
TileComponent={AllCollectionTile}
|
||||
coverFile={collectionSummary.coverFile}
|
||||
onClick={() => onCollectionClick(collectionSummary.id)}
|
||||
>
|
||||
<LargeTileTextOverlay>
|
||||
<Typography>{collectionSummary.name}</Typography>
|
||||
</LargeTileTextOverlay>
|
||||
</ItemCard>
|
||||
);
|
||||
|
||||
interface AddCollectionButtonProps {
|
||||
showNextModal: () => void;
|
||||
}
|
||||
|
||||
const AddCollectionButton: React.FC<AddCollectionButtonProps> = ({
|
||||
showNextModal,
|
||||
}) => (
|
||||
<ItemCard TileComponent={AllCollectionTile} onClick={showNextModal}>
|
||||
<LargeTileTextOverlay>{t("create_albums")}</LargeTileTextOverlay>
|
||||
<ImageContainer>+</ImageContainer>
|
||||
</ItemCard>
|
||||
);
|
||||
|
||||
const ImageContainer = styled(ItemTileOverlay)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 42px;
|
||||
`;
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
} from "@/new/photos/components/Gallery/BarImpl";
|
||||
import { PeopleHeader } from "@/new/photos/components/Gallery/PeopleHeader";
|
||||
import {
|
||||
areOnlySystemCollections,
|
||||
collectionsSortBy,
|
||||
hasNonSystemCollections,
|
||||
isSystemCollection,
|
||||
shouldShowOnCollectionBar,
|
||||
type CollectionsSortBy,
|
||||
@@ -122,7 +122,7 @@ export const GalleryBarAndListHeader: React.FC<CollectionsProps> = ({
|
||||
const shouldBeHidden = useMemo(
|
||||
() =>
|
||||
shouldHide ||
|
||||
(!hasNonSystemCollections(toShowCollectionSummaries) &&
|
||||
(areOnlySystemCollections(toShowCollectionSummaries) &&
|
||||
activeCollectionID === ALL_SECTION),
|
||||
[shouldHide, toShowCollectionSummaries, activeCollectionID],
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { ItemCard, PreviewItemTile } from "@/new/photos/components/ItemCards";
|
||||
import { ItemCard, PreviewItemTile } from "@/new/photos/components/Tiles";
|
||||
import { EnteFile } from "@/new/photos/types/file";
|
||||
import { FlexWrapper } from "@ente/shared/components/Container";
|
||||
import DialogBoxV2 from "@ente/shared/components/DialogBoxV2";
|
||||
|
||||
@@ -3,6 +3,7 @@ import log from "@/base/log";
|
||||
import type { CollectionMapping, Electron, ZipItem } from "@/base/types/ipc";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import { CollectionMappingChoiceDialog } from "@/new/photos/components/CollectionMappingChoiceDialog";
|
||||
import type { CollectionSelectorAttributes } from "@/new/photos/components/CollectionSelector";
|
||||
import { exportMetadataDirectoryName } from "@/new/photos/services/export";
|
||||
import type {
|
||||
FileAndPath,
|
||||
@@ -13,7 +14,6 @@ import { firstNonEmpty } from "@/utils/array";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import { CustomError } from "@ente/shared/error";
|
||||
import DiscFullIcon from "@mui/icons-material/DiscFull";
|
||||
import { CollectionSelectorIntent } from "components/Collections/CollectionSelector";
|
||||
import UserNameInputDialog from "components/UserNameInputDialog";
|
||||
import { t } from "i18next";
|
||||
import isElectron from "is-electron";
|
||||
@@ -36,12 +36,7 @@ import type {
|
||||
} from "services/upload/uploadManager";
|
||||
import uploadManager from "services/upload/uploadManager";
|
||||
import watcher from "services/watch";
|
||||
import {
|
||||
SetCollectionSelectorAttributes,
|
||||
SetCollections,
|
||||
SetFiles,
|
||||
SetLoading,
|
||||
} from "types/gallery";
|
||||
import { SetCollections, SetFiles, SetLoading } from "types/gallery";
|
||||
import { NotificationAttributes } from "types/Notification";
|
||||
import { getOrCreateAlbum } from "utils/collection";
|
||||
import { PublicCollectionGalleryContext } from "utils/publicCollectionGallery";
|
||||
@@ -64,9 +59,17 @@ enum PICKED_UPLOAD_TYPE {
|
||||
|
||||
interface Props {
|
||||
syncWithRemote: (force?: boolean, silent?: boolean) => Promise<void>;
|
||||
closeCollectionSelector?: () => void;
|
||||
closeUploadTypeSelector: () => void;
|
||||
setCollectionSelectorAttributes?: SetCollectionSelectorAttributes;
|
||||
/**
|
||||
* Show the collection selector with the given {@link attributes}.
|
||||
*/
|
||||
onOpenCollectionSelector?: (
|
||||
attributes: CollectionSelectorAttributes,
|
||||
) => void;
|
||||
/**
|
||||
* Close the collection selector if it is open.
|
||||
*/
|
||||
onCloseCollectionSelector?: () => void;
|
||||
setCollectionNamerAttributes?: SetCollectionNamerAttributes;
|
||||
setLoading: SetLoading;
|
||||
setShouldDisableDropzone: (value: boolean) => void;
|
||||
@@ -460,17 +463,17 @@ export default function Uploader({
|
||||
showCollectionCreateModal(importSuggestion.rootFolderName);
|
||||
}
|
||||
|
||||
props.setCollectionSelectorAttributes({
|
||||
callback: uploadFilesToExistingCollection,
|
||||
props.onOpenCollectionSelector({
|
||||
action: "upload",
|
||||
onSelectCollection: uploadFilesToExistingCollection,
|
||||
onCreateCollection: showNextModal,
|
||||
onCancel: handleCollectionSelectorCancel,
|
||||
showNextModal,
|
||||
intent: CollectionSelectorIntent.upload,
|
||||
});
|
||||
})();
|
||||
}, [webFiles, desktopFiles, desktopFilePaths, desktopZipItems]);
|
||||
|
||||
const preCollectionCreationAction = async () => {
|
||||
props.closeCollectionSelector?.();
|
||||
props.onCloseCollectionSelector?.();
|
||||
props.setShouldDisableDropzone(!uploadManager.shouldAllowNewUpload());
|
||||
setUploadStage(UPLOAD_STAGES.START);
|
||||
setUploadProgressView(true);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { SelectionBar } from "@/base/components/Navbar";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import type { CollectionSelectorAttributes } from "@/new/photos/components/CollectionSelector";
|
||||
import type { GalleryBarMode } from "@/new/photos/components/Gallery/BarImpl";
|
||||
import { FluidContainer } from "@ente/shared/components/Container";
|
||||
import ClockIcon from "@mui/icons-material/AccessTime";
|
||||
@@ -15,11 +16,9 @@ import UnArchiveIcon from "@mui/icons-material/Unarchive";
|
||||
import VisibilityOffOutlined from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import VisibilityOutlined from "@mui/icons-material/VisibilityOutlined";
|
||||
import { Box, IconButton, Stack, Tooltip } from "@mui/material";
|
||||
import { CollectionSelectorIntent } from "components/Collections/CollectionSelector";
|
||||
import { t } from "i18next";
|
||||
import { AppContext } from "pages/_app";
|
||||
import { useContext } from "react";
|
||||
import { SetCollectionSelectorAttributes } from "types/gallery";
|
||||
import {
|
||||
ALL_SECTION,
|
||||
ARCHIVE_SECTION,
|
||||
@@ -36,7 +35,15 @@ interface Props {
|
||||
) => (...args: any[]) => void;
|
||||
handleFileOps: (opsType: FILE_OPS_TYPE) => (...args: any[]) => void;
|
||||
showCreateCollectionModal: (opsType: COLLECTION_OPS_TYPE) => () => void;
|
||||
setCollectionSelectorAttributes: SetCollectionSelectorAttributes;
|
||||
/**
|
||||
* Callback to open a dialog where the user can choose a collection.
|
||||
*
|
||||
* The reason for opening the dialog and other properties are passed as the
|
||||
* {@link attributes} argument.
|
||||
*/
|
||||
onOpenCollectionSelector: (
|
||||
attributes: CollectionSelectorAttributes,
|
||||
) => void;
|
||||
count: number;
|
||||
ownCount: number;
|
||||
clearSelection: () => void;
|
||||
@@ -52,7 +59,7 @@ interface Props {
|
||||
|
||||
const SelectedFileOptions = ({
|
||||
showCreateCollectionModal,
|
||||
setCollectionSelectorAttributes,
|
||||
onOpenCollectionSelector,
|
||||
handleCollectionOps,
|
||||
handleFileOps,
|
||||
selectedCollection,
|
||||
@@ -72,12 +79,14 @@ const SelectedFileOptions = ({
|
||||
const peopleMode = barMode == "people";
|
||||
|
||||
const addToCollection = () =>
|
||||
setCollectionSelectorAttributes({
|
||||
callback: handleCollectionOps(COLLECTION_OPS_TYPE.ADD),
|
||||
showNextModal: showCreateCollectionModal(COLLECTION_OPS_TYPE.ADD),
|
||||
intent: CollectionSelectorIntent.add,
|
||||
fromCollection:
|
||||
!isInSearchMode && !peopleMode ? activeCollectionID : undefined,
|
||||
onOpenCollectionSelector({
|
||||
action: "add",
|
||||
onSelectCollection: handleCollectionOps(COLLECTION_OPS_TYPE.ADD),
|
||||
onCreateCollection: showCreateCollectionModal(
|
||||
COLLECTION_OPS_TYPE.ADD,
|
||||
),
|
||||
ignoredCollectionID:
|
||||
isInSearchMode || peopleMode ? undefined : activeCollectionID,
|
||||
});
|
||||
|
||||
const trashHandler = () =>
|
||||
@@ -98,12 +107,14 @@ const SelectedFileOptions = ({
|
||||
});
|
||||
|
||||
const restoreHandler = () =>
|
||||
setCollectionSelectorAttributes({
|
||||
callback: handleCollectionOps(COLLECTION_OPS_TYPE.RESTORE),
|
||||
showNextModal: showCreateCollectionModal(
|
||||
onOpenCollectionSelector({
|
||||
action: "restore",
|
||||
onSelectCollection: handleCollectionOps(
|
||||
COLLECTION_OPS_TYPE.RESTORE,
|
||||
),
|
||||
onCreateCollection: showCreateCollectionModal(
|
||||
COLLECTION_OPS_TYPE.RESTORE,
|
||||
),
|
||||
intent: CollectionSelectorIntent.restore,
|
||||
});
|
||||
|
||||
const removeFromCollectionHandler = () => {
|
||||
@@ -141,22 +152,24 @@ const SelectedFileOptions = ({
|
||||
};
|
||||
|
||||
const moveToCollection = () => {
|
||||
setCollectionSelectorAttributes({
|
||||
callback: handleCollectionOps(COLLECTION_OPS_TYPE.MOVE),
|
||||
showNextModal: showCreateCollectionModal(COLLECTION_OPS_TYPE.MOVE),
|
||||
intent: CollectionSelectorIntent.move,
|
||||
fromCollection:
|
||||
!isInSearchMode && !peopleMode ? activeCollectionID : undefined,
|
||||
onOpenCollectionSelector({
|
||||
action: "move",
|
||||
onSelectCollection: handleCollectionOps(COLLECTION_OPS_TYPE.MOVE),
|
||||
onCreateCollection: showCreateCollectionModal(
|
||||
COLLECTION_OPS_TYPE.MOVE,
|
||||
),
|
||||
ignoredCollectionID:
|
||||
isInSearchMode || peopleMode ? undefined : activeCollectionID,
|
||||
});
|
||||
};
|
||||
|
||||
const unhideToCollection = () => {
|
||||
setCollectionSelectorAttributes({
|
||||
callback: handleCollectionOps(COLLECTION_OPS_TYPE.UNHIDE),
|
||||
showNextModal: showCreateCollectionModal(
|
||||
onOpenCollectionSelector({
|
||||
action: "unhide",
|
||||
onSelectCollection: handleCollectionOps(COLLECTION_OPS_TYPE.UNHIDE),
|
||||
onCreateCollection: showCreateCollectionModal(
|
||||
COLLECTION_OPS_TYPE.UNHIDE,
|
||||
),
|
||||
intent: CollectionSelectorIntent.unhide,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -3,6 +3,10 @@ import { NavbarBase } from "@/base/components/Navbar";
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import log from "@/base/log";
|
||||
import type { Collection } from "@/media/collection";
|
||||
import {
|
||||
CollectionSelector,
|
||||
type CollectionSelectorAttributes,
|
||||
} from "@/new/photos/components/CollectionSelector";
|
||||
import {
|
||||
PeopleEmptyState,
|
||||
SearchResultsHeader,
|
||||
@@ -16,7 +20,7 @@ import {
|
||||
import { WhatsNew } from "@/new/photos/components/WhatsNew";
|
||||
import { shouldShowWhatsNew } from "@/new/photos/services/changelog";
|
||||
import type { CollectionSummaries } from "@/new/photos/services/collection/ui";
|
||||
import { hasNonSystemCollections } from "@/new/photos/services/collection/ui";
|
||||
import { areOnlySystemCollections } from "@/new/photos/services/collection/ui";
|
||||
import downloadManager from "@/new/photos/services/download";
|
||||
import {
|
||||
getLocalFiles,
|
||||
@@ -67,10 +71,6 @@ import AuthenticateUserModal from "components/AuthenticateUserModal";
|
||||
import CollectionNamer, {
|
||||
CollectionNamerAttributes,
|
||||
} from "components/Collections/CollectionNamer";
|
||||
import {
|
||||
CollectionSelector,
|
||||
CollectionSelectorAttributes,
|
||||
} from "components/Collections/CollectionSelector";
|
||||
import { GalleryBarAndListHeader } from "components/Collections/GalleryBarAndListHeader";
|
||||
import ExportModal from "components/ExportModal";
|
||||
import {
|
||||
@@ -96,6 +96,7 @@ import { useRouter } from "next/router";
|
||||
import { AppContext } from "pages/_app";
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
@@ -212,9 +213,6 @@ export default function Gallery() {
|
||||
});
|
||||
const [planModalView, setPlanModalView] = useState(false);
|
||||
const [blockingLoad, setBlockingLoad] = useState(false);
|
||||
const [collectionSelectorAttributes, setCollectionSelectorAttributes] =
|
||||
useState<CollectionSelectorAttributes>(null);
|
||||
const [collectionSelectorView, setCollectionSelectorView] = useState(false);
|
||||
const [collectionNamerAttributes, setCollectionNamerAttributes] =
|
||||
useState<CollectionNamerAttributes>(null);
|
||||
const [collectionNamerView, setCollectionNamerView] = useState(false);
|
||||
@@ -350,6 +348,10 @@ export default function Gallery() {
|
||||
new Set<number>(),
|
||||
);
|
||||
|
||||
const [openCollectionSelector, setOpenCollectionSelector] = useState(false);
|
||||
const [collectionSelectorAttributes, setCollectionSelectorAttributes] =
|
||||
useState<CollectionSelectorAttributes | undefined>();
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
// Ensure that the keys in local storage are not malformed by verifying that
|
||||
@@ -474,10 +476,6 @@ export default function Gallery() {
|
||||
setEmailList(emailList);
|
||||
}, [user, collections, familyData]);
|
||||
|
||||
useEffect(() => {
|
||||
collectionSelectorAttributes && setCollectionSelectorView(true);
|
||||
}, [collectionSelectorAttributes]);
|
||||
|
||||
useEffect(() => {
|
||||
collectionNamerAttributes && setCollectionNamerView(true);
|
||||
}, [collectionNamerAttributes]);
|
||||
@@ -700,7 +698,7 @@ export default function Gallery() {
|
||||
if (
|
||||
sidebarView ||
|
||||
uploadTypeSelectorView ||
|
||||
collectionSelectorView ||
|
||||
openCollectionSelector ||
|
||||
collectionNamerView ||
|
||||
fixCreationTimeView ||
|
||||
planModalView ||
|
||||
@@ -943,7 +941,7 @@ export default function Gallery() {
|
||||
(ops: COLLECTION_OPS_TYPE) => async (collection: Collection) => {
|
||||
startLoading();
|
||||
try {
|
||||
setCollectionSelectorView(false);
|
||||
setOpenCollectionSelector(false);
|
||||
const selectedFiles = getSelectedFiles(selected, filteredData);
|
||||
const toProcessFiles =
|
||||
ops === COLLECTION_OPS_TYPE.REMOVE
|
||||
@@ -1067,10 +1065,6 @@ export default function Gallery() {
|
||||
setUploadTypeSelectorIntent(intent ?? "upload");
|
||||
};
|
||||
|
||||
const closeCollectionSelector = () => {
|
||||
setCollectionSelectorView(false);
|
||||
};
|
||||
|
||||
const openExportModal = () => {
|
||||
setExportModalView(true);
|
||||
};
|
||||
@@ -1112,6 +1106,19 @@ export default function Gallery() {
|
||||
setBarMode("people");
|
||||
};
|
||||
|
||||
const handleOpenCollectionSelector = useCallback(
|
||||
(attributes: CollectionSelectorAttributes) => {
|
||||
setCollectionSelectorAttributes(attributes);
|
||||
setOpenCollectionSelector(true);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleCloseCollectionSelector = useCallback(
|
||||
() => setOpenCollectionSelector(false),
|
||||
[],
|
||||
);
|
||||
|
||||
if (!collectionSummaries || !filteredData) {
|
||||
return <div></div>;
|
||||
}
|
||||
@@ -1173,10 +1180,10 @@ export default function Gallery() {
|
||||
attributes={collectionNamerAttributes}
|
||||
/>
|
||||
<CollectionSelector
|
||||
open={collectionSelectorView}
|
||||
onClose={closeCollectionSelector}
|
||||
collectionSummaries={collectionSummaries}
|
||||
open={openCollectionSelector}
|
||||
onClose={handleCloseCollectionSelector}
|
||||
attributes={collectionSelectorAttributes}
|
||||
collectionSummaries={collectionSummaries}
|
||||
collectionForCollectionID={(id) =>
|
||||
findCollectionCreatingUncategorizedIfNeeded(
|
||||
collections,
|
||||
@@ -1244,29 +1251,20 @@ export default function Gallery() {
|
||||
<Uploader
|
||||
activeCollection={activeCollection}
|
||||
syncWithRemote={syncWithRemote}
|
||||
showCollectionSelector={setCollectionSelectorView.bind(
|
||||
null,
|
||||
true,
|
||||
)}
|
||||
closeUploadTypeSelector={setUploadTypeSelectorView.bind(
|
||||
null,
|
||||
false,
|
||||
)}
|
||||
setCollectionSelectorAttributes={
|
||||
setCollectionSelectorAttributes
|
||||
}
|
||||
closeCollectionSelector={setCollectionSelectorView.bind(
|
||||
null,
|
||||
false,
|
||||
)}
|
||||
onOpenCollectionSelector={handleOpenCollectionSelector}
|
||||
onCloseCollectionSelector={handleCloseCollectionSelector}
|
||||
setLoading={setBlockingLoad}
|
||||
setCollectionNamerAttributes={setCollectionNamerAttributes}
|
||||
setShouldDisableDropzone={setShouldDisableDropzone}
|
||||
setFiles={setFiles}
|
||||
setCollections={setCollections}
|
||||
isFirstUpload={
|
||||
!hasNonSystemCollections(collectionSummaries)
|
||||
}
|
||||
isFirstUpload={areOnlySystemCollections(
|
||||
collectionSummaries,
|
||||
)}
|
||||
{...{
|
||||
dragAndDropFiles,
|
||||
openFileSelector,
|
||||
@@ -1336,8 +1334,8 @@ export default function Gallery() {
|
||||
showCreateCollectionModal={
|
||||
showCreateCollectionModal
|
||||
}
|
||||
setCollectionSelectorAttributes={
|
||||
setCollectionSelectorAttributes
|
||||
onOpenCollectionSelector={
|
||||
handleOpenCollectionSelector
|
||||
}
|
||||
count={selected.count}
|
||||
ownCount={selected.ownCount}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Collection } from "@/media/collection";
|
||||
import { type SelectionContext } from "@/new/photos/components/Gallery";
|
||||
import { EnteFile } from "@/new/photos/types/file";
|
||||
import type { User } from "@ente/shared/user/types";
|
||||
import { CollectionSelectorAttributes } from "components/Collections/CollectionSelector";
|
||||
import { FilesDownloadProgressAttributes } from "components/FilesDownloadProgress";
|
||||
import { TimeStampListItem } from "components/PhotoList";
|
||||
|
||||
@@ -24,9 +23,6 @@ export type SetSelectedState = React.Dispatch<
|
||||
export type SetFiles = React.Dispatch<React.SetStateAction<EnteFile[]>>;
|
||||
export type SetCollections = React.Dispatch<React.SetStateAction<Collection[]>>;
|
||||
export type SetLoading = React.Dispatch<React.SetStateAction<boolean>>;
|
||||
export type SetCollectionSelectorAttributes = React.Dispatch<
|
||||
React.SetStateAction<CollectionSelectorAttributes>
|
||||
>;
|
||||
export type SetFilesDownloadProgressAttributes = (
|
||||
value:
|
||||
| Partial<FilesDownloadProgressAttributes>
|
||||
|
||||
@@ -303,7 +303,6 @@
|
||||
"LARGER_THAN_AVAILABLE_STORAGE_INFO": "These files were not uploaded as they exceed the maximum size limit for your storage plan",
|
||||
"TOO_LARGE_INFO": "These files were not uploaded as they exceed our maximum file size limit",
|
||||
"THUMBNAIL_GENERATION_FAILED_INFO": "These files were uploaded, but unfortunately we could not generate the thumbnails for them.",
|
||||
"select_album": "Select album",
|
||||
"upload_to_album": "Upload to album",
|
||||
"add_to_album": "Add to album",
|
||||
"move_to_album": "Move to album",
|
||||
|
||||
@@ -12,9 +12,12 @@ import {
|
||||
import { t } from "i18next";
|
||||
import React from "react";
|
||||
import { SpaceBetweenFlex } from "./mui";
|
||||
import { DialogCloseIconButton, type DialogVisiblityProps } from "./mui/Dialog";
|
||||
import {
|
||||
DialogCloseIconButton,
|
||||
type DialogVisibilityProps,
|
||||
} from "./mui/Dialog";
|
||||
|
||||
type CollectionMappingChoiceModalProps = DialogVisiblityProps & {
|
||||
type CollectionMappingChoiceModalProps = DialogVisibilityProps & {
|
||||
didSelect: (mapping: CollectionMapping) => void;
|
||||
};
|
||||
|
||||
|
||||
253
web/packages/new/photos/components/CollectionSelector.tsx
Normal file
253
web/packages/new/photos/components/CollectionSelector.tsx
Normal file
@@ -0,0 +1,253 @@
|
||||
import type { Collection } from "@/media/collection";
|
||||
import {
|
||||
CollectionTileButton,
|
||||
ItemCard,
|
||||
ItemTileOverlay,
|
||||
LargeTileTextOverlay,
|
||||
} from "@/new/photos/components/Tiles";
|
||||
import {
|
||||
canAddToCollection,
|
||||
canMoveToCollection,
|
||||
CollectionSummaryOrder,
|
||||
type CollectionSummaries,
|
||||
type CollectionSummary,
|
||||
} from "@/new/photos/services/collection/ui";
|
||||
import { ensure } from "@/utils/ensure";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTitle,
|
||||
styled,
|
||||
Typography,
|
||||
useMediaQuery,
|
||||
} from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { SpaceBetweenFlex } from "./mui";
|
||||
import {
|
||||
DialogCloseIconButton,
|
||||
type DialogVisibilityProps,
|
||||
} from "./mui/Dialog";
|
||||
|
||||
export type CollectionSelectorAction =
|
||||
| "upload"
|
||||
| "add"
|
||||
| "move"
|
||||
| "restore"
|
||||
| "unhide";
|
||||
|
||||
export interface CollectionSelectorAttributes {
|
||||
/**
|
||||
* The {@link action} modifies the title of the dialog, and also removes
|
||||
* some system collections that don't might not make sense for that
|
||||
* particular action.
|
||||
*/
|
||||
action: CollectionSelectorAction;
|
||||
/**
|
||||
* Callback invoked when the user selects one the existing collections
|
||||
* listed in the dialog.
|
||||
*/
|
||||
onSelectCollection: (collection: Collection) => void;
|
||||
/**
|
||||
* Callback invoked when the user selects the option to create a new
|
||||
* collection.
|
||||
*/
|
||||
onCreateCollection: () => void;
|
||||
/**
|
||||
* Callback invoked when the user cancels the collection selection dialog.
|
||||
*/
|
||||
onCancel?: () => void;
|
||||
/**
|
||||
* Some actions, like "add" and "move", happen in the context of an existing
|
||||
* collection. In such cases, their ID can be set as the
|
||||
* {@link ignoredCollectionID} to omit showing them again in the list of
|
||||
* collections.
|
||||
*/
|
||||
ignoredCollectionID?: number | undefined;
|
||||
}
|
||||
|
||||
type CollectionSelectorProps = DialogVisibilityProps & {
|
||||
/**
|
||||
* The same {@link CollectionSelector} can be used for different
|
||||
* purposes by customizing the {@link attributes} prop before opening it.
|
||||
*/
|
||||
attributes: CollectionSelectorAttributes | undefined;
|
||||
/**
|
||||
* The collections to list.
|
||||
*/
|
||||
collectionSummaries: CollectionSummaries;
|
||||
/**
|
||||
* A function to map from a collection ID to a {@link Collection}.
|
||||
*
|
||||
* This is invoked when the user makes a selection, to convert the ID of the
|
||||
* selected collection into a collection object that can be passed to the
|
||||
* {@link callback} attribute of {@link CollectionSelectorAttributes}.
|
||||
*/
|
||||
collectionForCollectionID: (collectionID: number) => Promise<Collection>;
|
||||
};
|
||||
|
||||
/**
|
||||
* A dialog allowing the user to select one of their existing collections or
|
||||
* create a new one.
|
||||
*/
|
||||
export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
attributes,
|
||||
collectionSummaries,
|
||||
collectionForCollectionID,
|
||||
}) => {
|
||||
// Make the dialog fullscreen if the screen is <= the dialog's max width.
|
||||
const isFullScreen = useMediaQuery("(max-width: 494px)");
|
||||
|
||||
const [filteredCollections, setFilteredCollections] = useState<
|
||||
CollectionSummary[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!attributes || !open) {
|
||||
return;
|
||||
}
|
||||
|
||||
const collections = [...collectionSummaries.values()]
|
||||
.filter(({ id, type }) => {
|
||||
if (id === attributes.ignoredCollectionID) {
|
||||
return false;
|
||||
} else if (attributes.action == "add") {
|
||||
return canAddToCollection(type);
|
||||
} else if (attributes.action == "upload") {
|
||||
return canMoveToCollection(type) || type == "uncategorized";
|
||||
} else if (attributes.action == "restore") {
|
||||
return canMoveToCollection(type) || type == "uncategorized";
|
||||
} else {
|
||||
return canMoveToCollection(type);
|
||||
}
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return a.name.localeCompare(b.name);
|
||||
})
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
ensure(CollectionSummaryOrder.get(a.type)) -
|
||||
ensure(CollectionSummaryOrder.get(b.type))
|
||||
);
|
||||
});
|
||||
|
||||
if (collections.length === 0) {
|
||||
onClose();
|
||||
attributes.onCreateCollection();
|
||||
}
|
||||
|
||||
setFilteredCollections(collections);
|
||||
}, [collectionSummaries, attributes, open, onClose]);
|
||||
|
||||
if (!filteredCollections.length) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (!attributes) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
const { action, onSelectCollection, onCancel, onCreateCollection } =
|
||||
attributes;
|
||||
|
||||
const handleCollectionClick = async (collectionID: number) => {
|
||||
onSelectCollection(await collectionForCollectionID(collectionID));
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
onCancel?.();
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={handleClose}
|
||||
fullWidth
|
||||
fullScreen={isFullScreen}
|
||||
PaperProps={{ sx: { maxWidth: "494px" } }}
|
||||
>
|
||||
<SpaceBetweenFlex sx={{ padding: "10px 8px 9px 0" }}>
|
||||
<DialogTitle variant="h3" fontWeight={"bold"}>
|
||||
{titleForAction(action)}
|
||||
</DialogTitle>
|
||||
<DialogCloseIconButton onClose={handleClose} />
|
||||
</SpaceBetweenFlex>
|
||||
|
||||
<DialogContent_>
|
||||
<AddCollectionButton onClick={onCreateCollection} />
|
||||
{filteredCollections.map((collectionSummary) => (
|
||||
<CollectionButton
|
||||
key={collectionSummary.id}
|
||||
collectionSummary={collectionSummary}
|
||||
onCollectionClick={handleCollectionClick}
|
||||
/>
|
||||
))}
|
||||
</DialogContent_>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const DialogContent_ = styled(DialogContent)`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px;
|
||||
`;
|
||||
|
||||
const titleForAction = (action: CollectionSelectorAction) => {
|
||||
switch (action) {
|
||||
case "upload":
|
||||
return t("upload_to_album");
|
||||
case "add":
|
||||
return t("add_to_album");
|
||||
case "move":
|
||||
return t("move_to_album");
|
||||
case "restore":
|
||||
return t("restore_to_album");
|
||||
case "unhide":
|
||||
return t("unhide_to_album");
|
||||
}
|
||||
};
|
||||
|
||||
interface CollectionButtonProps {
|
||||
collectionSummary: CollectionSummary;
|
||||
onCollectionClick: (collectionID: number) => void;
|
||||
}
|
||||
|
||||
const CollectionButton: React.FC<CollectionButtonProps> = ({
|
||||
collectionSummary,
|
||||
onCollectionClick,
|
||||
}) => (
|
||||
<ItemCard
|
||||
TileComponent={CollectionTileButton}
|
||||
coverFile={collectionSummary.coverFile}
|
||||
onClick={() => onCollectionClick(collectionSummary.id)}
|
||||
>
|
||||
<LargeTileTextOverlay>
|
||||
<Typography>{collectionSummary.name}</Typography>
|
||||
</LargeTileTextOverlay>
|
||||
</ItemCard>
|
||||
);
|
||||
|
||||
interface AddCollectionButtonProps {
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
const AddCollectionButton: React.FC<AddCollectionButtonProps> = ({
|
||||
onClick,
|
||||
}) => (
|
||||
<ItemCard TileComponent={CollectionTileButton} onClick={onClick}>
|
||||
<LargeTileTextOverlay>{t("create_albums")}</LargeTileTextOverlay>
|
||||
<PlusOverlay>+</PlusOverlay>
|
||||
</ItemCard>
|
||||
);
|
||||
|
||||
const PlusOverlay = styled(ItemTileOverlay)`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 42px;
|
||||
`;
|
||||
@@ -1,15 +1,15 @@
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import { CollectionsSortOptions } from "@/new/photos/components/CollectionsSortOptions";
|
||||
import {
|
||||
BarItemTile,
|
||||
ItemCard,
|
||||
TileTextOverlay,
|
||||
} from "@/new/photos/components/ItemCards";
|
||||
import { FilledIconButton } from "@/new/photos/components/mui";
|
||||
import {
|
||||
IMAGE_CONTAINER_MAX_WIDTH,
|
||||
MIN_COLUMNS,
|
||||
} from "@/new/photos/components/PhotoList";
|
||||
import {
|
||||
BarItemTile,
|
||||
ItemCard,
|
||||
TileTextOverlay,
|
||||
} from "@/new/photos/components/Tiles";
|
||||
import { UnstyledButton } from "@/new/photos/components/UnstyledButton";
|
||||
import type {
|
||||
CollectionSummary,
|
||||
|
||||
@@ -5,9 +5,9 @@ import SingleInputForm, {
|
||||
import { Dialog, DialogContent, DialogTitle } from "@mui/material";
|
||||
import { t } from "i18next";
|
||||
import React from "react";
|
||||
import { type DialogVisiblityProps } from "./mui/Dialog";
|
||||
import { type DialogVisibilityProps } from "./mui/Dialog";
|
||||
|
||||
type NameInputDialogProps = DialogVisiblityProps & {
|
||||
type NameInputDialogProps = DialogVisibilityProps & {
|
||||
/** Title of the dialog. */
|
||||
title: string;
|
||||
/** Placeholder string to show in the text input when it is empty. */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { assertionFailed } from "@/base/assert";
|
||||
import { useIsMobileWidth } from "@/base/hooks";
|
||||
import { ItemCard, PreviewItemTile } from "@/new/photos/components/ItemCards";
|
||||
import { ItemCard, PreviewItemTile } from "@/new/photos/components/Tiles";
|
||||
import {
|
||||
isMLSupported,
|
||||
mlStatusSnapshot,
|
||||
|
||||
@@ -7,6 +7,7 @@ import { type EnteFile } from "@/new/photos/types/file";
|
||||
import { styled } from "@mui/material";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { faceCrop } from "../services/ml";
|
||||
import { UnstyledButton } from "./UnstyledButton";
|
||||
|
||||
interface ItemCardProps {
|
||||
/**
|
||||
@@ -135,10 +136,32 @@ export const BarItemTile = styled(ItemTile)`
|
||||
`;
|
||||
|
||||
/**
|
||||
* A large 150x150 TileComponent used when showing the list of all collections
|
||||
* in the all collections view.
|
||||
* A variant of {@link ItemTile} meant for use when the tile is interactable.
|
||||
*/
|
||||
export const AllCollectionTile = styled(ItemTile)`
|
||||
export const ItemTileButton = styled(UnstyledButton)`
|
||||
/* Buttons reset this to center */
|
||||
text-align: inherit;
|
||||
|
||||
/* Rest of this is mostly verbatim from ItemTile ... */
|
||||
|
||||
display: flex;
|
||||
/* Act as container for the absolutely positioned ItemTileOverlays. */
|
||||
position: relative;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
& > img {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
`;
|
||||
|
||||
/**
|
||||
* A large 150x150 TileComponent used when showing the list of collections in
|
||||
* the all collections view and in the collection selector.
|
||||
*/
|
||||
export const CollectionTileButton = styled(ItemTileButton)`
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
`;
|
||||
@@ -171,7 +194,7 @@ export const TileTextOverlay = styled(ItemTileOverlay)`
|
||||
|
||||
/**
|
||||
* A variation of {@link TileTextOverlay} for use with larger tiles like the
|
||||
* {@link AllCollectionTile}.
|
||||
* {@link CollectionTile}.
|
||||
*/
|
||||
export const LargeTileTextOverlay = styled(ItemTileOverlay)`
|
||||
padding: 8px;
|
||||
@@ -6,14 +6,14 @@ import React from "react";
|
||||
/**
|
||||
* Common props to control the display of a dialog-like component.
|
||||
*/
|
||||
export interface DialogVisiblityProps {
|
||||
export interface DialogVisibilityProps {
|
||||
/** If `true`, the dialog is shown. */
|
||||
open: boolean;
|
||||
/** Callback fired when the dialog wants to be closed. */
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
type DialogCloseIconButtonProps = Omit<DialogVisiblityProps, "open">;
|
||||
type DialogCloseIconButtonProps = Omit<DialogVisibilityProps, "open">;
|
||||
|
||||
/**
|
||||
* A convenience {@link IconButton} commonly needed on {@link Dialog}s, at the
|
||||
|
||||
@@ -87,24 +87,13 @@ const systemCSTypes = new Set<CollectionSummaryType>([
|
||||
]);
|
||||
|
||||
const addToDisabledCSTypes = new Set<CollectionSummaryType>([
|
||||
"all",
|
||||
"archive",
|
||||
...systemCSTypes,
|
||||
"incomingShareViewer",
|
||||
"trash",
|
||||
"uncategorized",
|
||||
"defaultHidden",
|
||||
"hiddenItems",
|
||||
]);
|
||||
|
||||
const moveToDisabledCSTypes = new Set<CollectionSummaryType>([
|
||||
"all",
|
||||
"archive",
|
||||
"incomingShareViewer",
|
||||
...addToDisabledCSTypes,
|
||||
"incomingShareCollaborator",
|
||||
"trash",
|
||||
"uncategorized",
|
||||
"defaultHidden",
|
||||
"hiddenItems",
|
||||
]);
|
||||
|
||||
const hideFromCollectionBarCSTypes = new Set<CollectionSummaryType>([
|
||||
@@ -114,23 +103,21 @@ const hideFromCollectionBarCSTypes = new Set<CollectionSummaryType>([
|
||||
"defaultHidden",
|
||||
]);
|
||||
|
||||
export const hasNonSystemCollections = (
|
||||
collectionSummaries: CollectionSummaries,
|
||||
) => {
|
||||
for (const collectionSummary of collectionSummaries.values()) {
|
||||
if (!isSystemCollection(collectionSummary.type)) return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
export const isSystemCollection = (type: CollectionSummaryType) =>
|
||||
systemCSTypes.has(type);
|
||||
|
||||
export const canMoveToCollection = (type: CollectionSummaryType) =>
|
||||
!moveToDisabledCSTypes.has(type);
|
||||
export const areOnlySystemCollections = (
|
||||
collectionSummaries: CollectionSummaries,
|
||||
) =>
|
||||
[...collectionSummaries.values()].every(({ type }) =>
|
||||
isSystemCollection(type),
|
||||
);
|
||||
|
||||
export const canAddToCollection = (type: CollectionSummaryType) =>
|
||||
!addToDisabledCSTypes.has(type);
|
||||
|
||||
export const isSystemCollection = (type: CollectionSummaryType) =>
|
||||
systemCSTypes.has(type);
|
||||
export const canMoveToCollection = (type: CollectionSummaryType) =>
|
||||
!moveToDisabledCSTypes.has(type);
|
||||
|
||||
export const shouldShowOnCollectionBar = (type: CollectionSummaryType) =>
|
||||
!hideFromCollectionBarCSTypes.has(type);
|
||||
|
||||
Reference in New Issue
Block a user