Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added functionality to delete images in Pictopy and updated UI #98

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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>
</>
);
}

2 changes: 1 addition & 1 deletion frontend/src/components/Album/Album.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +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] || `D:/Data/Pictopy/PictoPy/frontend/public/tauri.svg`,
coverImage: album.image_paths[0] || `D:/Data/picto/UPDATED-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;
2 changes: 1 addition & 1 deletion frontend/src/components/Navigation/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function Navbar(props: { title?: string }) {
< 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="/Pictopy.svg" alt="" />
<img src="/tauri.svg" height={"20px"} width={"20px"} alt="" />
<span className="font-sans text-lg font-bold text-gray-50">
Pictopy
</span>
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