diff --git a/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx b/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx index f78141335..b96fab050 100644 --- a/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx +++ b/ui/v2.5/src/components/ImageDuplicateChecker/ImageDuplicateChecker.tsx @@ -7,6 +7,9 @@ import { Row, Col, Card, + ButtonGroup, + OverlayTrigger, + Tooltip, } from "react-bootstrap"; import { FormattedMessage, useIntl } from "react-intl"; import { useFindDuplicateImagesQuery } from "src/core/generated-graphql"; @@ -17,6 +20,10 @@ import { ErrorMessage } from "../Shared/ErrorMessage"; import { FileSize } from "../Shared/FileSize"; import { Pagination } from "src/components/List/Pagination"; import { useHistory } from "react-router-dom"; +import { DeleteImagesDialog } from "../Images/DeleteImagesDialog"; +import { EditImagesDialog } from "../Images/EditImagesDialog"; +import { Icon } from "../Shared/Icon"; +import { faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"; const ImageDuplicateCheckerSection = PatchContainerComponent( "ImageDuplicateCheckerSection" @@ -32,6 +39,10 @@ const ImageDuplicateChecker: React.FC = () => { const [isSearching, setIsSearching] = useState(false); const [hasSearched, setHasSearched] = useState(false); + const [checkedImages, setCheckedImages] = useState>({}); + const [selectedImages, setSelectedImages] = useState(); + const [deletingImages, setDeletingImages] = useState(false); + const [editingImages, setEditingImages] = useState(false); const { data, loading, error, refetch } = useFindDuplicateImagesQuery({ variables: { distance: hashDistance }, @@ -42,6 +53,7 @@ const ImageDuplicateChecker: React.FC = () => { const handleSearch = () => { setIsSearching(true); setHasSearched(true); + setCheckedImages({}); refetch({ distance: hashDistance }).finally(() => setIsSearching(false)); }; @@ -67,6 +79,40 @@ const ImageDuplicateChecker: React.FC = () => { return allGroups.slice(start, start + pageSize); }, [allGroups, currentPage, pageSize]); + const checkCount = Object.keys(checkedImages).filter((id) => checkedImages[id]).length; + + const handleCheck = (checked: boolean, imageID: string) => { + setCheckedImages({ ...checkedImages, [imageID]: checked }); + }; + + const handleDeleteChecked = () => { + setSelectedImages(allGroups.flat().filter((i) => checkedImages[i.id])); + setDeletingImages(true); + }; + + const onEdit = () => { + setSelectedImages(allGroups.flat().filter((i) => checkedImages[i.id])); + setEditingImages(true); + setCheckedImages({}); + }; + + const onDeleteDialogClosed = (confirmed: boolean) => { + setDeletingImages(false); + setSelectedImages(undefined); + if (confirmed) { + setCheckedImages({}); + refetch(); + } + }; + + const onEditDialogClosed = (applied: boolean) => { + setEditingImages(false); + setSelectedImages(undefined); + if (applied) { + refetch(); + } + }; + if (error) return ; const renderGroup = (group: GQL.ImageDataFragment[], index: number) => { @@ -83,6 +129,7 @@ const ImageDuplicateChecker: React.FC = () => { + @@ -94,6 +141,12 @@ const ImageDuplicateChecker: React.FC = () => { const file = img.visual_files[0]; return ( +

@@ -191,6 +256,40 @@ const ImageDuplicateChecker: React.FC = () => { )} + {hasSearched && !loading && !error && allGroups.length > 0 && ( +
+
+ Found {allGroups.length} duplicate groups +
+ {checkCount > 0 && ( + + + {intl.formatMessage({ id: "actions.edit" })} + + } + > + + + + {intl.formatMessage({ id: "actions.delete" })} + + } + > + + + + )} +
+ )} + {pagedGroups.map((group, index) => renderGroup(group, index))} {allGroups.length > pageSize && (

Image Details Size
+ handleCheck(e.currentTarget.checked, img.id)} + /> + { return (
+ {deletingImages && selectedImages && ( + + )} + {editingImages && selectedImages && ( + + )}