[web] File selection handler options refactoring (#6410)
This commit is contained in:
@@ -92,17 +92,13 @@ interface UploadProps {
|
||||
* albums app, this prop can be omitted.
|
||||
*/
|
||||
user?: LocalUser;
|
||||
isFirstUpload?: boolean;
|
||||
uploadTypeSelectorView: boolean;
|
||||
dragAndDropFiles: File[];
|
||||
uploadCollection?: Collection;
|
||||
uploadTypeSelectorIntent: UploadTypeSelectorIntent;
|
||||
activeCollection?: Collection;
|
||||
closeUploadTypeSelector: () => void;
|
||||
/**
|
||||
* Show the collection selector with the given {@link attributes}.
|
||||
*/
|
||||
onOpenCollectionSelector?: (
|
||||
attributes: CollectionSelectorAttributes,
|
||||
) => void;
|
||||
/**
|
||||
* Close the collection selector if it is open.
|
||||
*/
|
||||
onCloseCollectionSelector?: () => void;
|
||||
setLoading: SetLoading;
|
||||
setShouldDisableDropzone: (value: boolean) => void;
|
||||
showCollectionSelector?: () => void;
|
||||
@@ -127,6 +123,16 @@ interface UploadProps {
|
||||
* this property is optional; the public albums code need not provide it.
|
||||
*/
|
||||
onRemoteFilesPull?: () => Promise<void>;
|
||||
/**
|
||||
* Show the collection selector with the given {@link attributes}.
|
||||
*/
|
||||
onOpenCollectionSelector?: (
|
||||
attributes: CollectionSelectorAttributes,
|
||||
) => void;
|
||||
/**
|
||||
* Close the collection selector if it is open.
|
||||
*/
|
||||
onCloseCollectionSelector?: () => void;
|
||||
/**
|
||||
* Callback invoked when a file is uploaded.
|
||||
*
|
||||
@@ -140,13 +146,11 @@ interface UploadProps {
|
||||
* app, where the scenario requiring this will not arise.
|
||||
*/
|
||||
onShowPlanSelector?: () => void;
|
||||
isFirstUpload?: boolean;
|
||||
uploadTypeSelectorView: boolean;
|
||||
showSessionExpiredMessage: () => void;
|
||||
dragAndDropFiles: File[];
|
||||
uploadCollection?: Collection;
|
||||
uploadTypeSelectorIntent: UploadTypeSelectorIntent;
|
||||
activeCollection?: Collection;
|
||||
/**
|
||||
* Called when the upload failed because the user's session has expired, and
|
||||
* the Upload component wants to prompt the user to log in again.
|
||||
*/
|
||||
onShowSessionExpiredDialog: () => void;
|
||||
}
|
||||
|
||||
type UploadType = "files" | "folders" | "zips";
|
||||
@@ -160,9 +164,11 @@ export const Upload: React.FC<UploadProps> = ({
|
||||
dragAndDropFiles,
|
||||
onRemotePull,
|
||||
onRemoteFilesPull,
|
||||
onOpenCollectionSelector,
|
||||
onCloseCollectionSelector,
|
||||
onUploadFile,
|
||||
onShowPlanSelector,
|
||||
showSessionExpiredMessage,
|
||||
onShowSessionExpiredDialog,
|
||||
...props
|
||||
}) => {
|
||||
const { showMiniDialog, onGenericError } = useBaseContext();
|
||||
@@ -570,7 +576,7 @@ export const Upload: React.FC<UploadProps> = ({
|
||||
};
|
||||
}
|
||||
|
||||
props.onOpenCollectionSelector({
|
||||
onOpenCollectionSelector({
|
||||
action: "upload",
|
||||
onSelectCollection: uploadFilesToExistingCollection,
|
||||
onCreateCollection: showNextModal,
|
||||
@@ -580,7 +586,7 @@ export const Upload: React.FC<UploadProps> = ({
|
||||
}, [webFiles, desktopFiles, desktopFilePaths, desktopZipItems]);
|
||||
|
||||
const preCollectionCreationAction = async () => {
|
||||
props.onCloseCollectionSelector?.();
|
||||
onCloseCollectionSelector?.();
|
||||
props.setShouldDisableDropzone(uploadManager.isUploadInProgress());
|
||||
setUploadPhase("preparing");
|
||||
setUploadProgressView(true);
|
||||
@@ -756,7 +762,7 @@ export const Upload: React.FC<UploadProps> = ({
|
||||
const notifyUser = (e: unknown) => {
|
||||
switch (e instanceof Error && e.message) {
|
||||
case sessionExpiredErrorMessage:
|
||||
showSessionExpiredMessage();
|
||||
onShowSessionExpiredDialog();
|
||||
break;
|
||||
case subscriptionExpiredErrorMessage:
|
||||
showNotification({
|
||||
|
||||
@@ -1,390 +0,0 @@
|
||||
import ClockIcon from "@mui/icons-material/AccessTime";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import ArchiveIcon from "@mui/icons-material/ArchiveOutlined";
|
||||
import MoveIcon from "@mui/icons-material/ArrowForward";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorderRounded";
|
||||
import RemoveIcon from "@mui/icons-material/RemoveCircleOutline";
|
||||
import RestoreIcon from "@mui/icons-material/Restore";
|
||||
import UnArchiveIcon from "@mui/icons-material/Unarchive";
|
||||
import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
|
||||
import { IconButton, Tooltip, Typography } from "@mui/material";
|
||||
import { SpacedRow } from "ente-base/components/containers";
|
||||
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,
|
||||
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";
|
||||
|
||||
interface Props {
|
||||
handleCollectionOp: (op: CollectionOp) => (...args: any[]) => void;
|
||||
handleFileOp: (op: FileOp) => (...args: any[]) => void;
|
||||
showCreateCollectionModal: (op: CollectionOp) => () => void;
|
||||
/**
|
||||
* 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;
|
||||
barMode?: GalleryBarMode;
|
||||
activeCollectionID: number;
|
||||
/**
|
||||
* TODO: Need to implement delete-equivalent from shared albums.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* - Delete action should not be enabled 3 selected (0 Yours). There should
|
||||
* be separate remove action.
|
||||
*
|
||||
* - On remove, if the file and collection both belong to current user, we
|
||||
* just use move api to existing or uncat collection.
|
||||
*
|
||||
* - Otherwise, we call /collections/v3/remove-files (when collection and
|
||||
* file belong to different users).
|
||||
*
|
||||
* - Album owner can remove files of all other users from their collection.
|
||||
* Particiapant (viewer/collaborator) can only remove files that belong to
|
||||
* them.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
activeCollectionSummary: CollectionSummary | undefined;
|
||||
isInSearchMode: boolean;
|
||||
selectedCollection: Collection;
|
||||
isInHiddenSection: boolean;
|
||||
}
|
||||
|
||||
const SelectedFileOptions = ({
|
||||
showCreateCollectionModal,
|
||||
onOpenCollectionSelector,
|
||||
handleCollectionOp,
|
||||
handleFileOp,
|
||||
selectedCollection,
|
||||
count,
|
||||
ownCount,
|
||||
clearSelection,
|
||||
barMode,
|
||||
activeCollectionID,
|
||||
activeCollectionSummary,
|
||||
isInSearchMode,
|
||||
isInHiddenSection,
|
||||
}: Props) => {
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
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",
|
||||
onSelectCollection: handleCollectionOp("add"),
|
||||
onCreateCollection: showCreateCollectionModal("add"),
|
||||
relatedCollectionID:
|
||||
isInSearchMode || peopleMode ? undefined : activeCollectionID,
|
||||
});
|
||||
|
||||
const trashHandler = () =>
|
||||
showMiniDialog({
|
||||
title: t("trash_files_title"),
|
||||
message: t("trash_files_message"),
|
||||
continue: {
|
||||
text: t("move_to_trash"),
|
||||
color: "critical",
|
||||
action: handleFileOp("trash"),
|
||||
},
|
||||
});
|
||||
|
||||
const permanentlyDeleteHandler = () =>
|
||||
showMiniDialog({
|
||||
title: t("delete_files_title"),
|
||||
message: t("delete_files_message"),
|
||||
continue: {
|
||||
text: t("delete"),
|
||||
color: "critical",
|
||||
action: handleFileOp("deletePermanently"),
|
||||
},
|
||||
});
|
||||
|
||||
const restoreHandler = () =>
|
||||
onOpenCollectionSelector({
|
||||
action: "restore",
|
||||
onSelectCollection: handleCollectionOp("restore"),
|
||||
onCreateCollection: showCreateCollectionModal("restore"),
|
||||
});
|
||||
|
||||
const removeFromCollectionHandler = () => {
|
||||
if (ownCount === count) {
|
||||
showMiniDialog({
|
||||
title: t("remove_from_album"),
|
||||
message: t("confirm_remove_message"),
|
||||
continue: {
|
||||
text: t("yes_remove"),
|
||||
color: "primary",
|
||||
|
||||
action: () =>
|
||||
handleCollectionOp("remove")(selectedCollection),
|
||||
},
|
||||
});
|
||||
} else {
|
||||
showMiniDialog({
|
||||
title: t("remove_from_album"),
|
||||
message: t("confirm_remove_incl_others_message"),
|
||||
continue: {
|
||||
text: t("yes_remove"),
|
||||
color: "critical",
|
||||
action: () =>
|
||||
handleCollectionOp("remove")(selectedCollection),
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const moveToCollection = () => {
|
||||
onOpenCollectionSelector({
|
||||
action: "move",
|
||||
onSelectCollection: handleCollectionOp("move"),
|
||||
onCreateCollection: showCreateCollectionModal("move"),
|
||||
relatedCollectionID:
|
||||
isInSearchMode || peopleMode ? undefined : activeCollectionID,
|
||||
});
|
||||
};
|
||||
|
||||
const unhideToCollection = () => {
|
||||
onOpenCollectionSelector({
|
||||
action: "unhide",
|
||||
onSelectCollection: handleCollectionOp("unhide"),
|
||||
onCreateCollection: showCreateCollectionModal("unhide"),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SpacedRow sx={{ flex: 1, gap: 1, flexWrap: "wrap" }}>
|
||||
<IconButton onClick={clearSelection}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography sx={{ mr: "auto" }}>
|
||||
{ownCount === count
|
||||
? t("selected_count", { selected: count })
|
||||
: t("selected_and_yours_count", {
|
||||
selected: count,
|
||||
yours: ownCount,
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
{isInSearchMode ? (
|
||||
<>
|
||||
<Tooltip title={t("fix_creation_time")}>
|
||||
<IconButton onClick={handleFileOp("fixTime")}>
|
||||
<ClockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("add")}>
|
||||
<IconButton onClick={addToCollection}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("archive")}>
|
||||
<IconButton onClick={handleFileOp("archive")}>
|
||||
<ArchiveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("hide")}>
|
||||
<IconButton onClick={handleFileOp("hide")}>
|
||||
<VisibilityOffOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("delete")}>
|
||||
<IconButton onClick={trashHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : peopleMode ? (
|
||||
<>
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("add")}>
|
||||
<IconButton onClick={addToCollection}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("archive")}>
|
||||
<IconButton onClick={handleFileOp("archive")}>
|
||||
<ArchiveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("hide")}>
|
||||
<IconButton onClick={handleFileOp("hide")}>
|
||||
<VisibilityOffOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("delete")}>
|
||||
<IconButton onClick={trashHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : activeCollectionID == PseudoCollectionID.trash ? (
|
||||
<>
|
||||
<Tooltip title={t("restore")}>
|
||||
<IconButton onClick={restoreHandler}>
|
||||
<RestoreIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("delete_permanently")}>
|
||||
<IconButton onClick={permanentlyDeleteHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : isUncategorizedCollection ? (
|
||||
<>
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("move")}>
|
||||
<IconButton onClick={moveToCollection}>
|
||||
<MoveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("delete")}>
|
||||
<IconButton onClick={trashHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : isSharedIncomingCollection ? (
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
) : isInHiddenSection ? (
|
||||
<>
|
||||
<Tooltip title={t("unhide")}>
|
||||
<IconButton onClick={unhideToCollection}>
|
||||
<VisibilityOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t("delete")}>
|
||||
<IconButton onClick={trashHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Tooltip title={t("fix_creation_time")}>
|
||||
<IconButton onClick={handleFileOp("fixTime")}>
|
||||
<ClockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{!isFavoriteCollection &&
|
||||
activeCollectionID !=
|
||||
PseudoCollectionID.archiveItems && (
|
||||
<Tooltip title={t("favorite")}>
|
||||
<IconButton onClick={handleFileOp("favorite")}>
|
||||
<FavoriteBorderIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton onClick={handleFileOp("download")}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("add")}>
|
||||
<IconButton onClick={addToCollection}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
{activeCollectionID == PseudoCollectionID.archiveItems && (
|
||||
<Tooltip title={t("unarchive")}>
|
||||
<IconButton onClick={handleFileOp("unarchive")}>
|
||||
<UnArchiveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{activeCollectionID === PseudoCollectionID.all && (
|
||||
<Tooltip title={t("archive")}>
|
||||
<IconButton onClick={handleFileOp("archive")}>
|
||||
<ArchiveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{activeCollectionID !== PseudoCollectionID.all &&
|
||||
activeCollectionID != PseudoCollectionID.archiveItems &&
|
||||
!isFavoriteCollection && (
|
||||
<>
|
||||
<Tooltip title={t("move")}>
|
||||
<IconButton onClick={moveToCollection}>
|
||||
<MoveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title={t("remove")}>
|
||||
<IconButton
|
||||
onClick={removeFromCollectionHandler}
|
||||
>
|
||||
<RemoveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
<Tooltip title={t("hide")}>
|
||||
<IconButton onClick={handleFileOp("hide")}>
|
||||
<VisibilityOffOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip title={t("delete")}>
|
||||
<IconButton onClick={trashHandler}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</SpacedRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectedFileOptions;
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
import { FixCreationTime } from "components/FixCreationTime";
|
||||
import { Sidebar } from "components/Sidebar";
|
||||
import { Upload } from "components/Upload";
|
||||
import SelectedFileOptions from "components/pages/gallery/SelectedFileOptions";
|
||||
import { sessionExpiredDialogAttributes } from "ente-accounts/components/utils/dialog";
|
||||
import { stashRedirect } from "ente-accounts/services/redirect";
|
||||
import { isSessionInvalid } from "ente-accounts/services/session";
|
||||
@@ -49,6 +48,11 @@ import {
|
||||
SearchBar,
|
||||
type SearchBarProps,
|
||||
} from "ente-new/photos/components/SearchBar";
|
||||
import {
|
||||
SelectedFileOptions,
|
||||
type CollectionOp,
|
||||
type FileOp,
|
||||
} from "ente-new/photos/components/SelectedFileOptions";
|
||||
import { WhatsNew } from "ente-new/photos/components/WhatsNew";
|
||||
import {
|
||||
GalleryEmptyState,
|
||||
@@ -121,12 +125,8 @@ import {
|
||||
SetFilesDownloadProgressAttributes,
|
||||
SetFilesDownloadProgressAttributesCreator,
|
||||
} from "types/gallery";
|
||||
import {
|
||||
getSelectedCollection,
|
||||
handleCollectionOp,
|
||||
type CollectionOp,
|
||||
} from "utils/collection";
|
||||
import { getSelectedFiles, handleFileOp, type FileOp } from "utils/file";
|
||||
import { handleCollectionOp } from "utils/collection";
|
||||
import { getSelectedFiles, handleFileOp } from "utils/file";
|
||||
|
||||
/**
|
||||
* The default view for logged in users.
|
||||
@@ -921,22 +921,21 @@ const Page: React.FC = () => {
|
||||
>
|
||||
{showSelectionBar ? (
|
||||
<SelectedFileOptions
|
||||
barMode={barMode}
|
||||
isInSearchMode={isInSearchMode}
|
||||
collection={
|
||||
isInSearchMode ? undefined : activeCollection
|
||||
}
|
||||
collectionSummary={
|
||||
isInSearchMode ? undefined : activeCollectionSummary
|
||||
}
|
||||
selectedFileCount={selected.count}
|
||||
selectedOwnFileCount={selected.ownCount}
|
||||
onClearSelection={clearSelection}
|
||||
onShowCreateCollectionModal={handleCreateAlbumForOp}
|
||||
onOpenCollectionSelector={handleOpenCollectionSelector}
|
||||
handleCollectionOp={collectionOpsHelper}
|
||||
handleFileOp={fileOpHelper}
|
||||
showCreateCollectionModal={handleCreateAlbumForOp}
|
||||
onOpenCollectionSelector={handleOpenCollectionSelector}
|
||||
count={selected.count}
|
||||
ownCount={selected.ownCount}
|
||||
clearSelection={clearSelection}
|
||||
barMode={barMode}
|
||||
activeCollectionID={activeCollectionID}
|
||||
selectedCollection={getSelectedCollection(
|
||||
selected.collectionID,
|
||||
state.collections,
|
||||
)}
|
||||
activeCollectionSummary={activeCollectionSummary}
|
||||
isInSearchMode={isInSearchMode}
|
||||
isInHiddenSection={barMode == "hidden-albums"}
|
||||
/>
|
||||
) : barMode == "hidden-albums" ? (
|
||||
<HiddenSectionNavbarContents
|
||||
@@ -993,23 +992,23 @@ const Page: React.FC = () => {
|
||||
uploadTypeSelectorIntent,
|
||||
uploadTypeSelectorView,
|
||||
}}
|
||||
isFirstUpload={haveOnlySystemCollections(
|
||||
normalCollectionSummaries,
|
||||
)}
|
||||
activeCollection={activeCollection}
|
||||
closeUploadTypeSelector={setUploadTypeSelectorView.bind(
|
||||
null,
|
||||
false,
|
||||
)}
|
||||
onOpenCollectionSelector={handleOpenCollectionSelector}
|
||||
onCloseCollectionSelector={handleCloseCollectionSelector}
|
||||
setLoading={setBlockingLoad}
|
||||
setShouldDisableDropzone={setShouldDisableDropzone}
|
||||
onRemotePull={remotePull}
|
||||
onRemoteFilesPull={remoteFilesPull}
|
||||
onOpenCollectionSelector={handleOpenCollectionSelector}
|
||||
onCloseCollectionSelector={handleCloseCollectionSelector}
|
||||
onUploadFile={(file) => dispatch({ type: "uploadFile", file })}
|
||||
onShowPlanSelector={showPlanSelector}
|
||||
isFirstUpload={haveOnlySystemCollections(
|
||||
normalCollectionSummaries,
|
||||
)}
|
||||
showSessionExpiredMessage={showSessionExpiredDialog}
|
||||
onShowSessionExpiredDialog={showSessionExpiredDialog}
|
||||
/>
|
||||
<Sidebar
|
||||
{...sidebarVisibilityProps}
|
||||
|
||||
@@ -495,9 +495,9 @@ export default function PublicCollectionGallery() {
|
||||
>
|
||||
{selected.count > 0 ? (
|
||||
<SelectedFileOptions
|
||||
downloadFilesHelper={downloadFilesHelper}
|
||||
clearSelection={clearSelection}
|
||||
count={selected.count}
|
||||
clearSelection={clearSelection}
|
||||
downloadFilesHelper={downloadFilesHelper}
|
||||
/>
|
||||
) : (
|
||||
<SpacedRow sx={{ flex: 1 }}>
|
||||
@@ -531,14 +531,14 @@ export default function PublicCollectionGallery() {
|
||||
uploadCollection={publicCollection}
|
||||
setLoading={setBlockingLoad}
|
||||
setShouldDisableDropzone={setShouldDisableDropzone}
|
||||
uploadTypeSelectorIntent="collect"
|
||||
uploadTypeSelectorView={uploadTypeSelectorView}
|
||||
onRemotePull={publicAlbumsRemotePull}
|
||||
onUploadFile={(file) =>
|
||||
setPublicFiles(sortFiles([...publicFiles, file]))
|
||||
}
|
||||
uploadTypeSelectorView={uploadTypeSelectorView}
|
||||
closeUploadTypeSelector={closeUploadTypeSelectorView}
|
||||
showSessionExpiredMessage={showPublicLinkExpiredMessage}
|
||||
uploadTypeSelectorIntent="collect"
|
||||
onShowSessionExpiredDialog={showPublicLinkExpiredMessage}
|
||||
{...{ dragAndDropFiles }}
|
||||
/>
|
||||
<FilesDownloadProgress
|
||||
@@ -616,9 +616,9 @@ interface SelectedFileOptionsProps {
|
||||
}
|
||||
|
||||
const SelectedFileOptions: React.FC<SelectedFileOptionsProps> = ({
|
||||
downloadFilesHelper,
|
||||
count,
|
||||
clearSelection,
|
||||
downloadFilesHelper,
|
||||
}) => (
|
||||
<Stack
|
||||
direction="row"
|
||||
|
||||
@@ -5,6 +5,7 @@ import log from "ente-base/log";
|
||||
import { uniqueFilesByID } from "ente-gallery/utils/file";
|
||||
import { type Collection, CollectionSubType } from "ente-media/collection";
|
||||
import { EnteFile } from "ente-media/file";
|
||||
import { type CollectionOp } from "ente-new/photos/components/SelectedFileOptions";
|
||||
import {
|
||||
addToCollection,
|
||||
createAlbum,
|
||||
@@ -29,8 +30,6 @@ import {
|
||||
} from "types/gallery";
|
||||
import { downloadFilesWithProgress } from "utils/file";
|
||||
|
||||
export type CollectionOp = "add" | "move" | "remove" | "restore" | "unhide";
|
||||
|
||||
export async function handleCollectionOp(
|
||||
op: CollectionOp,
|
||||
collection: Collection,
|
||||
@@ -60,13 +59,6 @@ export async function handleCollectionOp(
|
||||
}
|
||||
}
|
||||
|
||||
export function getSelectedCollection(
|
||||
collectionID: number,
|
||||
collections: Collection[],
|
||||
) {
|
||||
return collections.find((collection) => collection.id === collectionID);
|
||||
}
|
||||
|
||||
export async function downloadCollectionHelper(
|
||||
collectionID: number,
|
||||
setFilesDownloadProgressAttributes: SetFilesDownloadProgressAttributes,
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from "ente-media/file-metadata";
|
||||
import { FileType } from "ente-media/file-type";
|
||||
import { decodeLivePhoto } from "ente-media/live-photo";
|
||||
import { type FileOp } from "ente-new/photos/components/SelectedFileOptions";
|
||||
import {
|
||||
addToFavorites,
|
||||
deleteFromTrash,
|
||||
@@ -31,16 +32,6 @@ import {
|
||||
SetFilesDownloadProgressAttributesCreator,
|
||||
} from "types/gallery";
|
||||
|
||||
export type FileOp =
|
||||
| "download"
|
||||
| "fixTime"
|
||||
| "favorite"
|
||||
| "archive"
|
||||
| "unarchive"
|
||||
| "hide"
|
||||
| "trash"
|
||||
| "deletePermanently";
|
||||
|
||||
function getSelectedFileIds(selectedFiles: SelectedState) {
|
||||
const filesIDs: number[] = [];
|
||||
for (const [key, val] of Object.entries(selectedFiles)) {
|
||||
|
||||
@@ -39,6 +39,14 @@ export interface CollectionSelectorAttributes {
|
||||
* particular action.
|
||||
*/
|
||||
action: CollectionSelectorAction;
|
||||
/**
|
||||
* Some actions, like "add" and "move", happen in the context of an existing
|
||||
* collection summary.
|
||||
*
|
||||
* In such cases, the ID of the collection summary can be set as the
|
||||
* {@link sourceCollectionID} to omit showing it in the list again.
|
||||
*/
|
||||
sourceCollectionSummaryID?: number;
|
||||
/**
|
||||
* Callback invoked when the user selects one the existing collections
|
||||
* listed in the dialog.
|
||||
@@ -53,12 +61,6 @@ export interface CollectionSelectorAttributes {
|
||||
* 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, the ID of this collection can be set as the
|
||||
* {@link relatedCollectionID} to omit showing it in the list again.
|
||||
*/
|
||||
relatedCollectionID?: number | undefined;
|
||||
}
|
||||
|
||||
type CollectionSelectorProps = ModalVisibilityProps & {
|
||||
@@ -123,7 +125,7 @@ export const CollectionSelector: React.FC<CollectionSelectorProps> = ({
|
||||
|
||||
const collections = [...collectionSummaries.values()]
|
||||
.filter((cs) => {
|
||||
if (cs.id === attributes.relatedCollectionID) {
|
||||
if (cs.id === attributes.sourceCollectionSummaryID) {
|
||||
return false;
|
||||
} else if (attributes.action == "add") {
|
||||
return canAddToCollection(cs);
|
||||
|
||||
403
web/packages/new/photos/components/SelectedFileOptions.tsx
Normal file
403
web/packages/new/photos/components/SelectedFileOptions.tsx
Normal file
@@ -0,0 +1,403 @@
|
||||
import ClockIcon from "@mui/icons-material/AccessTime";
|
||||
import AddIcon from "@mui/icons-material/Add";
|
||||
import ArchiveIcon from "@mui/icons-material/ArchiveOutlined";
|
||||
import MoveIcon from "@mui/icons-material/ArrowForward";
|
||||
import CloseIcon from "@mui/icons-material/Close";
|
||||
import DeleteIcon from "@mui/icons-material/Delete";
|
||||
import DownloadIcon from "@mui/icons-material/Download";
|
||||
import FavoriteBorderIcon from "@mui/icons-material/FavoriteBorderRounded";
|
||||
import RemoveIcon from "@mui/icons-material/RemoveCircleOutline";
|
||||
import RestoreIcon from "@mui/icons-material/Restore";
|
||||
import UnArchiveIcon from "@mui/icons-material/Unarchive";
|
||||
import VisibilityOffOutlinedIcon from "@mui/icons-material/VisibilityOffOutlined";
|
||||
import VisibilityOutlinedIcon from "@mui/icons-material/VisibilityOutlined";
|
||||
import { IconButton, Tooltip, Typography } from "@mui/material";
|
||||
import { SpacedRow } from "ente-base/components/containers";
|
||||
import type { ButtonishProps } from "ente-base/components/mui";
|
||||
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,
|
||||
type CollectionSummary,
|
||||
} from "ente-new/photos/services/collection-summary";
|
||||
import { t } from "i18next";
|
||||
|
||||
export type CollectionOp = "add" | "move" | "remove" | "restore" | "unhide";
|
||||
|
||||
export type FileOp =
|
||||
| "download"
|
||||
| "fixTime"
|
||||
| "favorite"
|
||||
| "archive"
|
||||
| "unarchive"
|
||||
| "hide"
|
||||
| "trash"
|
||||
| "deletePermanently";
|
||||
|
||||
interface SelectedFileOptionsProps {
|
||||
barMode?: GalleryBarMode;
|
||||
isInSearchMode: boolean;
|
||||
selectedCollection?: Collection;
|
||||
/**
|
||||
* If {@link collectionSummary} is set and is not a pseudo-collection, then
|
||||
* this will be set to the corresponding {@link Collection}.
|
||||
*/
|
||||
collection: Collection | undefined;
|
||||
/**
|
||||
* The collection summary in whose context the selection happened.
|
||||
*
|
||||
* This will not be set if we are in the people section, or if we are
|
||||
* showing search results.
|
||||
*
|
||||
* TODO: Need to implement delete-equivalent from shared albums.
|
||||
*
|
||||
* Notes:
|
||||
*
|
||||
* - Delete action should not be enabled 3 selected (0 Yours). There should
|
||||
* be separate remove action.
|
||||
*
|
||||
* - On remove, if the file and collection both belong to current user, we
|
||||
* just use move api to existing or uncat collection.
|
||||
*
|
||||
* - Otherwise, we call /collections/v3/remove-files (when collection and
|
||||
* file belong to different users).
|
||||
*
|
||||
* - Album owner can remove files of all other users from their collection.
|
||||
* Particiapant (viewer/collaborator) can only remove files that belong to
|
||||
* them.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
collectionSummary: CollectionSummary | undefined;
|
||||
/**
|
||||
* The total number of files selected by the user.
|
||||
*/
|
||||
selectedFileCount: number;
|
||||
/**
|
||||
* The subset of {@link selectedFileCount} that are also owned by the user.
|
||||
*/
|
||||
selectedOwnFileCount: number;
|
||||
/**
|
||||
* Called when the user clears the selection by pressing the cancel button
|
||||
* on the selection bar.
|
||||
*/
|
||||
onClearSelection: () => void;
|
||||
/**
|
||||
* Called when an operation requires prompting the user to create a new
|
||||
* collection (e.g. adding to a new album).
|
||||
*
|
||||
* The callback is also passed the operation that caused it to be shown.
|
||||
*/
|
||||
onShowCreateCollectionModal: (op: CollectionOp) => () => void;
|
||||
/**
|
||||
* 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;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handleCollectionOp: (op: CollectionOp) => (...args: any[]) => void;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
handleFileOp: (op: FileOp) => (...args: any[]) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* The selection bar shown at the top of the viewport when the user has selected
|
||||
* one or more files in the photos app gallery.
|
||||
*/
|
||||
export const SelectedFileOptions: React.FC<SelectedFileOptionsProps> = ({
|
||||
barMode,
|
||||
isInSearchMode,
|
||||
collection,
|
||||
collectionSummary,
|
||||
selectedFileCount,
|
||||
selectedOwnFileCount,
|
||||
onClearSelection,
|
||||
onShowCreateCollectionModal,
|
||||
onOpenCollectionSelector,
|
||||
handleCollectionOp,
|
||||
handleFileOp,
|
||||
}) => {
|
||||
const { showMiniDialog } = useBaseContext();
|
||||
|
||||
const isUserFavorites =
|
||||
!!collectionSummary?.attributes.has("userFavorites");
|
||||
|
||||
const handleUnhide = () => {
|
||||
onOpenCollectionSelector({
|
||||
action: "unhide",
|
||||
onSelectCollection: handleCollectionOp("unhide"),
|
||||
onCreateCollection: onShowCreateCollectionModal("unhide"),
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = () =>
|
||||
showMiniDialog({
|
||||
title: t("trash_files_title"),
|
||||
message: t("trash_files_message"),
|
||||
continue: {
|
||||
text: t("move_to_trash"),
|
||||
color: "critical",
|
||||
action: handleFileOp("trash"),
|
||||
},
|
||||
});
|
||||
|
||||
const handleRestore = () =>
|
||||
onOpenCollectionSelector({
|
||||
action: "restore",
|
||||
onSelectCollection: handleCollectionOp("restore"),
|
||||
onCreateCollection: onShowCreateCollectionModal("restore"),
|
||||
});
|
||||
|
||||
const handleDeletePermanently = () =>
|
||||
showMiniDialog({
|
||||
title: t("delete_files_title"),
|
||||
message: t("delete_files_message"),
|
||||
continue: {
|
||||
text: t("delete"),
|
||||
color: "critical",
|
||||
action: handleFileOp("deletePermanently"),
|
||||
},
|
||||
});
|
||||
|
||||
const handleAddToCollection = () =>
|
||||
onOpenCollectionSelector({
|
||||
action: "add",
|
||||
sourceCollectionSummaryID: collectionSummary?.id,
|
||||
onSelectCollection: handleCollectionOp("add"),
|
||||
onCreateCollection: onShowCreateCollectionModal("add"),
|
||||
});
|
||||
|
||||
const handleRemoveFromOwnCollection = () => {
|
||||
showMiniDialog(
|
||||
selectedFileCount == selectedOwnFileCount
|
||||
? {
|
||||
title: t("remove_from_album"),
|
||||
message: t("confirm_remove_message"),
|
||||
continue: {
|
||||
text: t("yes_remove"),
|
||||
color: "primary",
|
||||
action: () =>
|
||||
handleCollectionOp("remove")(collection),
|
||||
},
|
||||
}
|
||||
: {
|
||||
title: t("remove_from_album"),
|
||||
message: t("confirm_remove_incl_others_message"),
|
||||
continue: {
|
||||
text: t("yes_remove"),
|
||||
color: "critical",
|
||||
action: () =>
|
||||
handleCollectionOp("remove")(collection),
|
||||
},
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
const handleMoveToCollection = () => {
|
||||
onOpenCollectionSelector({
|
||||
action: "move",
|
||||
sourceCollectionSummaryID: collectionSummary?.id,
|
||||
onSelectCollection: handleCollectionOp("move"),
|
||||
onCreateCollection: onShowCreateCollectionModal("move"),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<SpacedRow sx={{ flex: 1, gap: 1, flexWrap: "wrap" }}>
|
||||
<IconButton onClick={onClearSelection}>
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
<Typography sx={{ mr: "auto" }}>
|
||||
{selectedFileCount == selectedOwnFileCount
|
||||
? t("selected_count", { selected: selectedFileCount })
|
||||
: t("selected_and_yours_count", {
|
||||
selected: selectedFileCount,
|
||||
yours: selectedOwnFileCount,
|
||||
})}
|
||||
</Typography>
|
||||
|
||||
{isInSearchMode ? (
|
||||
<>
|
||||
<FixTimeButton onClick={handleFileOp("fixTime")} />
|
||||
<DownloadButton onClick={handleFileOp("download")} />
|
||||
<AddToCollectionButton onClick={handleAddToCollection} />
|
||||
<ArchiveButton onClick={handleFileOp("archive")} />
|
||||
<HideButton onClick={handleFileOp("hide")} />
|
||||
<DeleteButton onClick={handleDelete} />
|
||||
</>
|
||||
) : barMode == "people" ? (
|
||||
<>
|
||||
<DownloadButton onClick={handleFileOp("download")} />
|
||||
<AddToCollectionButton onClick={handleAddToCollection} />
|
||||
<ArchiveButton onClick={handleFileOp("archive")} />
|
||||
<HideButton onClick={handleFileOp("hide")} />
|
||||
<DeleteButton onClick={handleDelete} />
|
||||
</>
|
||||
) : collectionSummary?.id == PseudoCollectionID.trash ? (
|
||||
<>
|
||||
<RestoreButton onClick={handleRestore} />
|
||||
<DeletePermanentlyButton
|
||||
onClick={handleDeletePermanently}
|
||||
/>
|
||||
</>
|
||||
) : collectionSummary?.attributes.has("uncategorized") ? (
|
||||
<>
|
||||
<DownloadButton onClick={handleFileOp("download")} />
|
||||
<MoveToCollectionButton onClick={handleMoveToCollection} />
|
||||
<DeleteButton onClick={handleDelete} />
|
||||
</>
|
||||
) : collectionSummary?.attributes.has("sharedIncoming") ? (
|
||||
<DownloadButton onClick={handleFileOp("download")} />
|
||||
) : barMode == "hidden-albums" ? (
|
||||
<>
|
||||
<DownloadButton onClick={handleFileOp("download")} />
|
||||
<UnhideButton onClick={handleUnhide} />
|
||||
<DeleteButton onClick={handleDelete} />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{!isUserFavorites &&
|
||||
collectionSummary?.id !=
|
||||
PseudoCollectionID.archiveItems && (
|
||||
<FavoriteButton
|
||||
onClick={handleFileOp("favorite")}
|
||||
/>
|
||||
)}
|
||||
<FixTimeButton onClick={handleFileOp("fixTime")} />
|
||||
<DownloadButton onClick={handleFileOp("download")} />
|
||||
<AddToCollectionButton onClick={handleAddToCollection} />
|
||||
{collectionSummary?.id === PseudoCollectionID.all ? (
|
||||
<ArchiveButton onClick={handleFileOp("archive")} />
|
||||
) : collectionSummary?.id ==
|
||||
PseudoCollectionID.archiveItems ? (
|
||||
<UnarchiveButton onClick={handleFileOp("unarchive")} />
|
||||
) : (
|
||||
!isUserFavorites && (
|
||||
<>
|
||||
<MoveToCollectionButton
|
||||
onClick={handleMoveToCollection}
|
||||
/>
|
||||
<RemoveFromCollectionButton
|
||||
onClick={handleRemoveFromOwnCollection}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
)}
|
||||
<HideButton onClick={handleFileOp("hide")} />
|
||||
<DeleteButton onClick={handleDelete} />
|
||||
</>
|
||||
)}
|
||||
</SpacedRow>
|
||||
);
|
||||
};
|
||||
|
||||
const DownloadButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("download")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<DownloadIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const FavoriteButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("favorite")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<FavoriteBorderIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const ArchiveButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("archive")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<ArchiveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const UnarchiveButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("unarchive")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<UnArchiveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const HideButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("hide")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<VisibilityOffOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const UnhideButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("unhide")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<VisibilityOutlinedIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const DeleteButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("delete")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const RestoreButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("restore")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<RestoreIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const DeletePermanentlyButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("delete_permanently")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<DeleteIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const FixTimeButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("fix_creation_time")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<ClockIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const AddToCollectionButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("add")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<AddIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const MoveToCollectionButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("move")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<MoveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
|
||||
const RemoveFromCollectionButton: React.FC<ButtonishProps> = ({ onClick }) => (
|
||||
<Tooltip title={t("remove")}>
|
||||
<IconButton {...{ onClick }}>
|
||||
<RemoveIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
Reference in New Issue
Block a user