Merge pull request #728 from ente-io/photoswipe-delete-file-option

Photoswipe delete file option
This commit is contained in:
Abhinav Kumar 2022-10-11 08:29:55 +05:30 committed by GitHub
commit 677c7e88c4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 71 deletions

View file

@ -1,6 +1,6 @@
import { GalleryContext } from 'pages/gallery'; import { GalleryContext } from 'pages/gallery';
import PreviewCard from './pages/gallery/PreviewCard'; import PreviewCard from './pages/gallery/PreviewCard';
import React, { useContext, useEffect, useRef, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { EnteFile } from 'types/file'; import { EnteFile } from 'types/file';
import { styled } from '@mui/material'; import { styled } from '@mui/material';
import DownloadManager from 'services/downloadManager'; import DownloadManager from 'services/downloadManager';
@ -30,6 +30,7 @@ import { logError } from 'utils/sentry';
import { CustomError } from 'utils/error'; import { CustomError } from 'utils/error';
import { User } from 'types/user'; import { User } from 'types/user';
import { getData, LS_KEYS } from 'utils/storage/localStorage'; import { getData, LS_KEYS } from 'utils/storage/localStorage';
import { useMemo } from 'react';
const Container = styled('div')` const Container = styled('div')`
display: block; display: block;
@ -60,7 +61,8 @@ interface Props {
openUploader?; openUploader?;
isInSearchMode?: boolean; isInSearchMode?: boolean;
search?: Search; search?: Search;
deleted?: number[]; deletedFileIds?: Set<number>;
setDeletedFileIds?: (value: Set<number>) => void;
activeCollection: number; activeCollection: number;
isSharedCollection?: boolean; isSharedCollection?: boolean;
enableDownload?: boolean; enableDownload?: boolean;
@ -86,7 +88,8 @@ const PhotoFrame = ({
isInSearchMode, isInSearchMode,
search, search,
resetSearch, resetSearch,
deleted, deletedFileIds,
setDeletedFileIds,
activeCollection, activeCollection,
isSharedCollection, isSharedCollection,
enableDownload, enableDownload,
@ -104,67 +107,14 @@ const PhotoFrame = ({
const [rangeStart, setRangeStart] = useState(null); const [rangeStart, setRangeStart] = useState(null);
const [currentHover, setCurrentHover] = useState(null); const [currentHover, setCurrentHover] = useState(null);
const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false); const [isShiftKeyPressed, setIsShiftKeyPressed] = useState(false);
const filteredDataRef = useRef<EnteFile[]>([]);
const filteredData = filteredDataRef?.current ?? [];
const router = useRouter(); const router = useRouter();
const [isSourceLoaded, setIsSourceLoaded] = useState(false); const [isSourceLoaded, setIsSourceLoaded] = useState(false);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
setIsShiftKeyPressed(true);
}
};
const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
setIsShiftKeyPressed(false);
}
};
document.addEventListener('keydown', handleKeyDown, false);
document.addEventListener('keyup', handleKeyUp, false);
router.events.on('hashChangeComplete', (url: string) => {
const start = url.indexOf('#');
const hash = url.slice(start !== -1 ? start : url.length);
const shouldPhotoSwipeBeOpened = hash.endsWith(
PHOTOSWIPE_HASH_SUFFIX
);
if (shouldPhotoSwipeBeOpened) {
setOpen(true);
} else {
setOpen(false);
}
});
return () => {
document.addEventListener('keydown', handleKeyDown, false);
document.addEventListener('keyup', handleKeyUp, false);
};
}, []);
useEffect(() => { const filteredData = useMemo(() => {
if (!isNaN(search?.file)) {
const filteredDataIdx = filteredData.findIndex((file) => {
return file.id === search.file;
});
if (!isNaN(filteredDataIdx)) {
onThumbnailClick(filteredDataIdx)();
}
resetSearch();
}
}, [search, filteredData]);
const resetFetching = () => {
setFetching({});
};
useEffect(() => {
if (selected.count === 0) {
setRangeStart(null);
}
}, [selected]);
useEffect(() => {
const idSet = new Set(); const idSet = new Set();
const user: User = getData(LS_KEYS.USER); const user: User = getData(LS_KEYS.USER);
filteredDataRef.current = files
return files
.map((item, index) => ({ .map((item, index) => ({
...item, ...item,
dataIndex: index, dataIndex: index,
@ -172,7 +122,10 @@ const PhotoFrame = ({
h: window.innerHeight, h: window.innerHeight,
})) }))
.filter((item) => { .filter((item) => {
if (deleted?.includes(item.id)) { if (
deletedFileIds?.has(item.id) &&
activeCollection !== TRASH_SECTION
) {
return false; return false;
} }
if ( if (
@ -230,7 +183,7 @@ const PhotoFrame = ({
} }
return false; return false;
}); });
}, [files, deleted, search, activeCollection]); }, [files, deletedFileIds, search, activeCollection]);
useEffect(() => { useEffect(() => {
const currentURL = new URL(window.location.href); const currentURL = new URL(window.location.href);
@ -247,6 +200,59 @@ const PhotoFrame = ({
} }
}, [open]); }, [open]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
setIsShiftKeyPressed(true);
}
};
const handleKeyUp = (e: KeyboardEvent) => {
if (e.key === 'Shift') {
setIsShiftKeyPressed(false);
}
};
document.addEventListener('keydown', handleKeyDown, false);
document.addEventListener('keyup', handleKeyUp, false);
router.events.on('hashChangeComplete', (url: string) => {
const start = url.indexOf('#');
const hash = url.slice(start !== -1 ? start : url.length);
const shouldPhotoSwipeBeOpened = hash.endsWith(
PHOTOSWIPE_HASH_SUFFIX
);
if (shouldPhotoSwipeBeOpened) {
setOpen(true);
} else {
setOpen(false);
}
});
return () => {
document.addEventListener('keydown', handleKeyDown, false);
document.addEventListener('keyup', handleKeyUp, false);
};
}, []);
useEffect(() => {
if (!isNaN(search?.file)) {
const filteredDataIdx = filteredData.findIndex((file) => {
return file.id === search.file;
});
if (!isNaN(filteredDataIdx)) {
onThumbnailClick(filteredDataIdx)();
}
resetSearch();
}
}, [search, filteredData]);
const resetFetching = () => {
setFetching({});
};
useEffect(() => {
if (selected.count === 0) {
setRangeStart(null);
}
}, [selected]);
const getFileIndexFromID = (files: EnteFile[], id: number) => { const getFileIndexFromID = (files: EnteFile[], id: number) => {
const index = files.findIndex((file) => file.id === id); const index = files.findIndex((file) => file.id === id);
if (index === -1) { if (index === -1) {
@ -603,6 +609,8 @@ const PhotoFrame = ({
onClose={handleClose} onClose={handleClose}
gettingData={getSlideData} gettingData={getSlideData}
favItemIds={favItemIds} favItemIds={favItemIds}
deletedFileIds={deletedFileIds}
setDeletedFileIds={setDeletedFileIds}
isSharedCollection={isSharedCollection} isSharedCollection={isSharedCollection}
isTrashCollection={activeCollection === TRASH_SECTION} isTrashCollection={activeCollection === TRASH_SECTION}
enableDownload={enableDownload} enableDownload={enableDownload}

View file

@ -247,6 +247,8 @@ export function PhotoList({
filteredData, filteredData,
showAppDownloadBanner, showAppDownloadBanner,
publicCollectionGalleryContext.accessedThroughSharedURL, publicCollectionGalleryContext.accessedThroughSharedURL,
galleryContext.photoListHeader,
publicCollectionGalleryContext.photoListHeader,
]); ]);
const groupByFileSize = (timeStampList: TimeStampListItem[]) => { const groupByFileSize = (timeStampList: TimeStampListItem[]) => {

View file

@ -28,6 +28,9 @@ import InfoIcon from '@mui/icons-material/InfoOutlined';
import FavoriteIcon from '@mui/icons-material/FavoriteRounded'; import FavoriteIcon from '@mui/icons-material/FavoriteRounded';
import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorderRounded'; import FavoriteBorderIcon from '@mui/icons-material/FavoriteBorderRounded';
import ChevronRight from '@mui/icons-material/ChevronRight'; import ChevronRight from '@mui/icons-material/ChevronRight';
import DeleteIcon from '@mui/icons-material/Delete';
import { trashFiles } from 'services/fileService';
import { getTrashFileMessage } from 'utils/ui';
interface Iprops { interface Iprops {
isOpen: boolean; isOpen: boolean;
@ -38,6 +41,8 @@ interface Iprops {
id?: string; id?: string;
className?: string; className?: string;
favItemIds: Set<number>; favItemIds: Set<number>;
deletedFileIds: Set<number>;
setDeletedFileIds?: (value: Set<number>) => void;
isSharedCollection: boolean; isSharedCollection: boolean;
isTrashCollection: boolean; isTrashCollection: boolean;
enableDownload: boolean; enableDownload: boolean;
@ -262,6 +267,17 @@ function PhotoSwipe(props: Iprops) {
needUpdate.current = true; needUpdate.current = true;
}; };
const trashFile = async (file: EnteFile) => {
const { deletedFileIds, setDeletedFileIds } = props;
deletedFileIds.add(file.id);
setDeletedFileIds(new Set(deletedFileIds));
await trashFiles([file]);
needUpdate.current = true;
};
const confirmTrashFile = (file: EnteFile) =>
appContext.setDialogMessage(getTrashFileMessage(() => trashFile(file)));
const updateItems = (items = []) => { const updateItems = (items = []) => {
if (photoSwipe) { if (photoSwipe) {
photoSwipe.items.length = 0; photoSwipe.items.length = 0;
@ -269,7 +285,9 @@ function PhotoSwipe(props: Iprops) {
photoSwipe.items.push(item); photoSwipe.items.push(item);
}); });
photoSwipe.invalidateCurrItems(); photoSwipe.invalidateCurrItems();
// photoSwipe.updateSize(true); if (isOpen) {
photoSwipe.updateSize(true);
}
} }
}; };
@ -357,6 +375,16 @@ function PhotoSwipe(props: Iprops) {
className="pswp__button pswp__button--close" className="pswp__button pswp__button--close"
title={constants.CLOSE} title={constants.CLOSE}
/> />
<button
className="pswp__button pswp__button--custom"
title={constants.DELETE}
onClick={() => {
confirmTrashFile(
photoSwipe?.currItem as EnteFile
);
}}>
<DeleteIcon fontSize="small" />
</button>
{props.enableDownload && ( {props.enableDownload && (
<button <button
@ -379,6 +407,11 @@ function PhotoSwipe(props: Iprops) {
{!props.isSharedCollection && {!props.isSharedCollection &&
!props.isTrashCollection && ( !props.isTrashCollection && (
<button <button
title={
isFav
? constants.UNFAVORITE
: constants.FAVORITE
}
className="pswp__button pswp__button--custom" className="pswp__button pswp__button--custom"
onClick={() => { onClick={() => {
onFavClick(photoSwipe?.currItem); onFavClick(photoSwipe?.currItem);
@ -390,6 +423,7 @@ function PhotoSwipe(props: Iprops) {
)} )}
</button> </button>
)} )}
{!props.isSharedCollection && ( {!props.isSharedCollection && (
<button <button
className="pswp__button pswp__button--custom" className="pswp__button pswp__button--custom"

View file

@ -265,6 +265,7 @@ export default function App({ Component, err }) {
<LoadingBar color="#51cd7c" ref={loadingBar} /> <LoadingBar color="#51cd7c" ref={loadingBar} />
<DialogBox <DialogBox
sx={{ zIndex: 1600 }}
size="xs" size="xs"
open={messageDialogView} open={messageDialogView}
onClose={closeMessageDialog} onClose={closeMessageDialog}

View file

@ -180,7 +180,9 @@ export default function Gallery() {
useState<SearchResultSummary>(null); useState<SearchResultSummary>(null);
const syncInProgress = useRef(true); const syncInProgress = useRef(true);
const resync = useRef(false); const resync = useRef(false);
const [deleted, setDeleted] = useState<number[]>([]); const [deletedFileIds, setDeletedFileIds] = useState<Set<number>>(
new Set<number>()
);
const { startLoading, finishLoading, setDialogMessage, ...appContext } = const { startLoading, finishLoading, setDialogMessage, ...appContext } =
useContext(AppContext); useContext(AppContext);
const [collectionSummaries, setCollectionSummaries] = const [collectionSummaries, setCollectionSummaries] =
@ -490,12 +492,12 @@ export default function Gallery() {
startLoading(); startLoading();
try { try {
const selectedFiles = getSelectedFiles(selected, files); const selectedFiles = getSelectedFiles(selected, files);
setDeletedFileIds((deletedFileIds) => {
selectedFiles.forEach((file) => deletedFileIds.add(file.id));
return new Set(deletedFileIds);
});
if (permanent) { if (permanent) {
await deleteFromTrash(selectedFiles.map((file) => file.id)); await deleteFromTrash(selectedFiles.map((file) => file.id));
setDeleted([
...deleted,
...selectedFiles.map((file) => file.id),
]);
} else { } else {
await trashFiles(selectedFiles); await trashFiles(selectedFiles);
} }
@ -698,7 +700,8 @@ export default function Gallery() {
openUploader={openUploader} openUploader={openUploader}
isInSearchMode={isInSearchMode} isInSearchMode={isInSearchMode}
search={search} search={search}
deleted={deleted} deletedFileIds={deletedFileIds}
setDeletedFileIds={setDeletedFileIds}
activeCollection={activeCollection} activeCollection={activeCollection}
isSharedCollection={isSharedCollection( isSharedCollection={isSharedCollection(
activeCollection, activeCollection,

View file

@ -318,7 +318,7 @@ export default function PublicCollectionGallery() {
openUploader={() => null} openUploader={() => null}
isInSearchMode={false} isInSearchMode={false}
search={{}} search={{}}
deleted={[]} deletedFileIds={new Set<number>()}
activeCollection={ALL_SECTION} activeCollection={ALL_SECTION}
isSharedCollection isSharedCollection
enableDownload={ enableDownload={

View file

@ -147,14 +147,15 @@ const englishConstants = {
UPLOAD_FIRST_PHOTO: 'Preserve', UPLOAD_FIRST_PHOTO: 'Preserve',
UPLOAD_DROPZONE_MESSAGE: 'Drop to backup your files', UPLOAD_DROPZONE_MESSAGE: 'Drop to backup your files',
WATCH_FOLDER_DROPZONE_MESSAGE: 'Drop to add watched folder', WATCH_FOLDER_DROPZONE_MESSAGE: 'Drop to add watched folder',
CONFIRM_DELETE: 'Confirm deletion',
DELETE_MESSAGE: `The selected files will be permanently deleted and can't be restored `,
TRASH_FILES_TITLE: 'Delete files?', TRASH_FILES_TITLE: 'Delete files?',
TRASH_FILE_TITLE: 'Delete file?',
DELETE_FILES_TITLE: 'Delete immediately?', DELETE_FILES_TITLE: 'Delete immediately?',
DELETE_FILES_MESSAGE: DELETE_FILES_MESSAGE:
'Selected files will be permanently deleted from your ente account.', 'Selected files will be permanently deleted from your ente account.',
DELETE_FILE: 'Delete files', DELETE_FILE: 'Delete files',
DELETE: 'Delete', DELETE: 'Delete',
FAVORITE: 'Favorite',
UNFAVORITE: 'Unfavorite',
MULTI_FOLDER_UPLOAD: 'Multiple folders detected', MULTI_FOLDER_UPLOAD: 'Multiple folders detected',
UPLOAD_STRATEGY_CHOICE: 'Would you like to upload them into', UPLOAD_STRATEGY_CHOICE: 'Would you like to upload them into',
UPLOAD_STRATEGY_SINGLE_COLLECTION: 'A single album', UPLOAD_STRATEGY_SINGLE_COLLECTION: 'A single album',
@ -569,6 +570,8 @@ const englishConstants = {
MOVE_TO_TRASH: 'Move to trash', MOVE_TO_TRASH: 'Move to trash',
TRASH_FILES_MESSAGE: TRASH_FILES_MESSAGE:
'Selected files will be removed from all albums and moved to trash.', 'Selected files will be removed from all albums and moved to trash.',
TRASH_FILE_MESSAGE:
'The file will be removed from all albums and moved to trash.',
DELETE_PERMANENTLY: 'Delete permanently', DELETE_PERMANENTLY: 'Delete permanently',
RESTORE: 'Restore', RESTORE: 'Restore',
CONFIRM_RESTORE: 'Confirm restoration', CONFIRM_RESTORE: 'Confirm restoration',

View file

@ -30,3 +30,14 @@ export const getTrashFilesMessage = (
}, },
close: { text: constants.CANCEL }, close: { text: constants.CANCEL },
}); });
export const getTrashFileMessage = (deleteFileHelper): DialogBoxAttributes => ({
title: constants.TRASH_FILE_TITLE,
content: constants.TRASH_FILE_MESSAGE,
proceed: {
action: deleteFileHelper,
text: constants.MOVE_TO_TRASH,
variant: 'danger',
},
close: { text: constants.CANCEL },
});