Skip to content

Commit

Permalink
Merge pull request #98 from Jibesh10101011/feat/96-image-deletion
Browse files Browse the repository at this point in the history
Added functionality to delete images in Pictopy and updated UI
  • Loading branch information
Pranav0-0Aggarwal authored Dec 16, 2024
2 parents 6f71d77 + daa6b03 commit 450f62d
Show file tree
Hide file tree
Showing 18 changed files with 302 additions and 33 deletions.
35 changes: 35 additions & 0 deletions backend/app/routes/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,41 @@ def delete_image(payload: dict):
)



@router.delete("/multiple-images")
def delete_multiple_images(payload:dict) :
try :
paths = payload["paths"]
if not isinstance(paths, list):
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="'paths' should be a list",
)

for path in paths:
try:
if not os.path.isfile(path):
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Image file not found"
)

os.remove(path)
delete_image_db(path)
except:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"{path} not in Database",
)

return {"message": f"Images deleted successfully"}

except Exception as e :
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
)



@router.get("/all-image-objects")
def get_all_image_objects():
try:
Expand Down
33 changes: 21 additions & 12 deletions frontend/src/components/AITagging/AIgallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export default function AIGallery({
const [currentPage, setCurrentPage] = useState<number>(1);
const [showMediaViewer, setShowMediaViewer] = useState<boolean>(false);
const [selectedMediaIndex, setSelectedMediaIndex] = useState<number>(0);
const [isVisibleSelectedImage, setIsVisibleSelectedImage] =
useState<boolean>(true);
const itemsPerPage: number = 9;
const itemsPerRow: number = 3;

Expand Down Expand Up @@ -51,27 +53,34 @@ export default function AIGallery({
<div className="container">
<div className="mx-auto max-w-6xl px-4 py-8 dark:bg-background dark:text-foreground md:px-6">
<div className="mb-6 flex items-center justify-between">
<h1 className="text-2xl font-bold">{title}</h1>

{isVisibleSelectedImage && <h1 className="text-2xl font-bold">{title}</h1>}
<FilterControls
filterTag={filterTag}
setFilterTag={setFilterTag}
mediaItems={mediaItems}
onFolderAdded={handleFolderAdded}
isLoading={loading}
isVisibleSelectedImage={isVisibleSelectedImage}
setIsVisibleSelectedImage={setIsVisibleSelectedImage}
/>
</div>
<MediaGrid
mediaItems={currentItems}
itemsPerRow={itemsPerRow}
openMediaViewer={openMediaViewer}
type={type}
/>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>

{isVisibleSelectedImage && (
<>
<MediaGrid
mediaItems={currentItems}
itemsPerRow={itemsPerRow}
openMediaViewer={openMediaViewer}
type={type}
/>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
</>
)}
{showMediaViewer && (
<MediaView
initialIndex={selectedMediaIndex}
Expand Down
45 changes: 44 additions & 1 deletion frontend/src/components/AITagging/FilterControls.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
DropdownMenu,
DropdownMenuContent,
Expand All @@ -13,13 +13,18 @@ import FolderPicker from '../FolderPicker/FolderPicker';
import { useAddFolder } from '@/hooks/AI_Image';
import LoadingScreen from '../ui/LoadingScreen/LoadingScreen';
import { ListOrderedIcon } from '../ui/Icons/Icons';
import DeleteSelectedImagePage from '../FolderPicker/DeleteSelectedImagePage';
import ErrorDialog from '../Album/Error';


interface FilterControlsProps {
filterTag: string;
setFilterTag: (tag: string) => void;
mediaItems: MediaItem[];
onFolderAdded: () => Promise<void>;
isLoading: boolean;
isVisibleSelectedImage: boolean,
setIsVisibleSelectedImage : (value:boolean) => void;
}

export default function FilterControls({
Expand All @@ -28,6 +33,8 @@ export default function FilterControls({
mediaItems,
onFolderAdded,
isLoading,
isVisibleSelectedImage,
setIsVisibleSelectedImage
}: FilterControlsProps) {
const {
addFolder,
Expand All @@ -42,6 +49,7 @@ export default function FilterControls({
.sort();
}, [mediaItems]);


const handleFolderPick = async (path: string) => {
try {
await addFolder(path);
Expand All @@ -51,6 +59,33 @@ export default function FilterControls({
}
};

const [errorDialogContent, setErrorDialogContent] = useState<{
title: string;
description: string;
} | null>(null);

const showErrorDialog = (title: string, err: unknown) => {
setErrorDialogContent({
title,
description:
err instanceof Error ? err.message : 'An unknown error occurred',
});
};


if (!isVisibleSelectedImage) {
return (
<div>
<DeleteSelectedImagePage
setIsVisibleSelectedImage={setIsVisibleSelectedImage}
onError={showErrorDialog}
/>
</div>
);
}



return (
<>
{(isLoading || isAddingFolder) && <LoadingScreen />}
Expand All @@ -59,6 +94,9 @@ export default function FilterControls({
)}
<div className="flex items-center gap-4 overflow-auto">
<FolderPicker setFolderPath={handleFolderPick} />
<Button onClick={() => setIsVisibleSelectedImage(false)} variant="outline">
Delete Image
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" className="flex items-center gap-2">
Expand All @@ -84,7 +122,12 @@ export default function FilterControls({
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
<ErrorDialog
content={errorDialogContent}
onClose={() => setErrorDialogContent(null)}
/>
</div>
</>
);
}

3 changes: 1 addition & 2 deletions frontend/src/components/Album/Album.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,7 @@ const AlbumsView: React.FC = () => {
const transformedAlbums = albums.map((album: Album) => ({
id: album.album_name,
title: album.album_name,
coverImage:
album.image_paths[0] ||
coverImage: album.image_paths[0] ||
`D:/Data/Pictopy/PictoPy/frontend/public/tauri.svg`,
imageCount: album.image_paths.length,
}));
Expand Down
106 changes: 106 additions & 0 deletions frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import React, { useState } from 'react';

import {
useDeleteMultipleImages,
useFetchAllImages,
} from '../../hooks/DeleteImages';
import { Button } from '@/components/ui/button';
import { convertFileSrc } from '@tauri-apps/api/core';

interface DeleteSelectedImageProps {
setIsVisibleSelectedImage:(value:boolean) => void;
onError : (title:string,err:any) => void
}



const DeleteSelectedImagePage: React.FC<DeleteSelectedImageProps> = ({
setIsVisibleSelectedImage,
onError
}) => {
const { images: allImagesData, isLoading } = useFetchAllImages();
const [selectedImages, setSelectedImages] = useState<string[]>([]);
const { deleteMultipleImages, isLoading: isAddingImages } =
useDeleteMultipleImages();

// Extract the array of image paths
const allImages: string[] = allImagesData??[];

const toggleImageSelection = (imagePath: string) => {
setSelectedImages((prev) =>
prev.includes(imagePath)
? prev.filter((path) => path !== imagePath)
: [...prev, imagePath],
);
};

const handleAddSelectedImages = async () => {
if (selectedImages.length > 0) {
try {
await deleteMultipleImages(selectedImages);
console.log("Selected Images : ",selectedImages);
setSelectedImages([]);
if(!isLoading) {
setIsVisibleSelectedImage(true);
}
} catch (err) {
onError('Error during deleting images', err);
}
}
};

const getImageName = (path: string) => {
return path.split('\\').pop() || path;
};

if (isLoading) {
return <div>Loading images...</div>;
}

if (!Array.isArray(allImages) || allImages.length === 0) {
return <div>No images available. Please add some images first.</div>;
}

return (
<div className="container mx-auto p-4">
<h1 className="mb-4 text-2xl font-bold">Select Images</h1>
{/* <FolderPicker setFolderPath={handleFolderPick} /> */}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{allImages.map((imagePath, index) => {
const srcc = convertFileSrc(imagePath);
return (
<div key={index} className="relative">
<div
className={`absolute -right-2 -top-2 z-10 h-6 w-6 cursor-pointer rounded-full border-2 border-white ${
selectedImages.includes(imagePath)
? 'bg-blue-500'
: 'bg-gray-300'
}`}
onClick={() => toggleImageSelection(imagePath)}
/>
<img
src={srcc}
alt={`Image ${getImageName(imagePath)}`}
className="h-40 w-full rounded-lg object-cover"
/>
<div className="absolute bottom-0 left-0 right-0 truncate rounded-b-lg bg-black bg-opacity-50 p-1 text-xs text-white">
{getImageName(imagePath)}
</div>
</div>
);
})}
</div>
<div className="mt-4 flex justify-between">
<Button onClick={()=>setIsVisibleSelectedImage(true)}>Cancel</Button>
<Button
onClick={handleAddSelectedImages}
disabled={isAddingImages || selectedImages.length === 0}
>
Delete Selected Images ({selectedImages.length})
</Button>
</div>
</div>
);
};

export default DeleteSelectedImagePage;
16 changes: 6 additions & 10 deletions frontend/src/components/Navigation/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ export function Navbar(props: { title?: string }) {
return (
<>
<header className="flex w-full flex-row items-center justify-center align-middle">
<div className="mt-3 flex h-16 w-[50%] items-center justify-between rounded-3xl bg-[#333333] px-16">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<img src="/Pictopy.svg" alt="" />
<span className="font-sans text-lg font-bold text-gray-50">
Pictopy
</span>
</div>
</div>
<div className="flex h-16 items-center justify-between bg-[#333333] px-16 w-[50%] mt-3 rounded-3xl ">
<div className="flex items-center gap-4">
<div className="flex items-center gap-2">
<img src="/tauri.svg" height={"20px"} width={"20px"} alt="" />
<span className="font-sans text-lg font-bold text-gray-50">
Pictopy
<div className="flex items-center gap-4">
<span className="font-sans text-lg font-medium text-gray-50">
Welcome {props.title || 'User'}
</span>
</div>
</div>
</header>
</>
);
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/components/Navigation/Sidebar/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,36 +25,36 @@ function Sidebar() {
<Link to="/home" className={linkClasses('/home')}>
<HomeIcon
className="h-5 w-5"
fillColor={isActive('/home') ? '#6465F3' : ' none'}
fill={isActive('/home') ? '#6465F3' : 'currentColor'}
/>
<span className="font-sans">Home</span>
</Link>
<Link to="/ai-tagging" className={linkClasses('/ai-tagging')}>
<FileIcon
className="h-5 w-5"
fillColor={isActive('/ai-tagging') ? '#6465F3' : 'none'}
fill={isActive('/ai-tagging') ? '#6465F3' : 'currentColor'}
/>
<span className="font-sans">AI Tagging</span>
</Link>

<Link to="/videos" className={linkClasses('/videos')}>
<VideoIcon
className="h-5 w-5"
fillColor={isActive('/videos') ? '#6465F3' : 'currentColor'}
fill={isActive('/videos') ? '#6465F3' : 'currentColor'}
/>
<span className="font-sans">Videos</span>
</Link>
<Link to="/albums" className={linkClasses('/albums')}>
<AlbumIcon
className="h-5 w-5"
fillColor={isActive('/albums') ? '#6465F3' : ' none'}
fill={isActive('/albums') ? '#6465F3' : 'currentColor'}
/>
<span className="font-sans">Albums</span>
</Link>
</div>
<Link to="/settings" className={linkClasses('/settings')}>
<SettingsIcon
fillColor={isActive('/settings') ? '#6465F3' : 'currentColor'}
fill={isActive('/settings') ? '#6465F3' : 'currentColor'}
/>
<span className="font-sans">Settings</span>
</Link>
Expand Down
Loading

0 comments on commit 450f62d

Please sign in to comment.