Skip to content

Commit

Permalink
Merge pull request #187 from Jibesh10101011/feat/select-images-tags-d…
Browse files Browse the repository at this point in the history
…elete-pagination-170

Feat/select images tags delete pagination 170
  • Loading branch information
Pranav0-0Aggarwal authored Jan 1, 2025
2 parents 163ef79 + 17242de commit 1ccc6b4
Show file tree
Hide file tree
Showing 7 changed files with 194 additions and 49 deletions.
29 changes: 28 additions & 1 deletion backend/app/routes/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,34 @@ def delete_multiple_images(payload: dict):
},
},
)
path = os.path.normpath(path)
parts = path.split(os.sep)
parts.insert(parts.index("images") + 1, "PictoPy.thumbnails")
thumb_nail_image_path = os.sep.join(parts)


if os.path.exists(path) :
try :
os.remove(path)
except PermissionError:
print(f"Permission denied for file '{thumb_nail_image_path}'.")
except Exception as e:
print(f"An error occurred: {e}")

else:
print(f"File '{path}' does not exist.")

if os.path.exists(thumb_nail_image_path) :
try :
os.remove(thumb_nail_image_path)
except PermissionError:
print(f"Permission denied for file '{thumb_nail_image_path}'.")
except Exception as e:
print(f"An error occurred: {e}")
else :
print(f"File '{thumb_nail_image_path}' does not exist.")


os.remove(path)
delete_image_db(path)
deleted_paths.append(path)

Expand Down Expand Up @@ -289,6 +315,7 @@ def get_all_image_objects():
image_path = get_path_from_id(image_id)
classes = get_objects_db(image_path)
data[image_path] = classes if classes else "None"
print(image_path)

return JSONResponse(
status_code=200,
Expand Down
1 change: 1 addition & 0 deletions frontend/api/api-functions/images.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const parseAndSortImageData = (data: APIResponse['data']): Image[] => {
extractThumbnailPath(data.folder_path, src),
);
return {
imagePath:src,
title: src.substring(src.lastIndexOf('\\') + 1),
thumbnailUrl,
url,
Expand Down
80 changes: 63 additions & 17 deletions frontend/src/components/AITagging/AIgallery.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@ import {
getAllImageObjects,
generateThumbnails,
} from '../../../api/api-functions/images';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@radix-ui/react-dropdown-menu';
import { Button } from '../ui/button';

export default function AIGallery({
title,
Expand Down Expand Up @@ -37,24 +45,28 @@ export default function AIGallery({
const [selectedMediaIndex, setSelectedMediaIndex] = useState<number>(0);
const [isVisibleSelectedImage, setIsVisibleSelectedImage] =
useState<boolean>(true);
const itemsPerPage: number = 20;
const itemsPerRow: number = 3;

const noOfPages: number[] = Array.from(
{ length: 41 },
(_, index) => index + 10,
);
const filteredMediaItems = useMemo(() => {
return filterTag
? mediaItems.filter((mediaItem: any) =>
mediaItem.tags.includes(filterTag),
)
: mediaItems;
}, [filterTag, mediaItems, loading]);
? mediaItems.filter((mediaItem: any) =>
mediaItem.tags.includes(filterTag),
)
: mediaItems;
}, [filterTag, mediaItems, loading]);
const [pageNo,setpageNo] = useState<number>(20);


const currentItems = useMemo(() => {
const indexOfLastItem = currentPage * itemsPerPage;
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
const indexOfLastItem = currentPage * pageNo;
const indexOfFirstItem = indexOfLastItem - pageNo;
return filteredMediaItems.slice(indexOfFirstItem, indexOfLastItem);
}, [filteredMediaItems, currentPage, itemsPerPage, mediaItems]);
}, [filteredMediaItems, currentPage, pageNo, mediaItems]);

const totalPages = Math.ceil(filteredMediaItems.length / itemsPerPage);
const totalPages = Math.ceil(filteredMediaItems.length / pageNo);

const openMediaViewer = useCallback((index: number) => {
setSelectedMediaIndex(index);
Expand Down Expand Up @@ -107,11 +119,45 @@ export default function AIGallery({
openMediaViewer={openMediaViewer}
type={type}
/>
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>
<div className="relative flex items-center justify-center gap-4">
{/* Pagination Controls - Centered */}
<PaginationControls
currentPage={currentPage}
totalPages={totalPages}
onPageChange={setCurrentPage}
/>

{/* Dropdown Menu - Right-Aligned */}
<div className="absolute mt-5 right-0">
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 border-gray-500 hover:bg-accent dark:hover:bg-white/10"
>
<p className="hidden lg:inline">
Num of images per page : {pageNo}
</p>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="max-h-[500px] w-[200px] overflow-y-auto"
align="end"
>
<DropdownMenuRadioGroup
className="overflow-auto bg-gray-950 p-4 cursor-pointer"
onValueChange={(value)=>setpageNo(Number(value))}
>
{noOfPages.map((itemsPerPage) => (
<DropdownMenuRadioItem key={itemsPerPage} value={`${itemsPerPage}`}>
{itemsPerPage}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</>
)}
{showMediaViewer && (
Expand All @@ -120,7 +166,7 @@ export default function AIGallery({
onClose={closeMediaViewer}
allMedia={filteredMediaItems.map((item: any) => item.url)}
currentPage={currentPage}
itemsPerPage={itemsPerPage}
itemsPerPage={pageNo}
type={type}
/>
)}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/components/AITagging/FilterControls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ export default function FilterControls({
<DeleteSelectedImagePage
setIsVisibleSelectedImage={setIsVisibleSelectedImage}
onError={showErrorDialog}
uniqueTags={uniqueTags}
mediaItems={mediaItems}
/>
</div>
);
Expand Down
128 changes: 98 additions & 30 deletions frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { convertFileSrc } from '@tauri-apps/api/core';
import {
usePictoQuery,
usePictoMutation,
Expand All @@ -10,15 +9,27 @@ import {
delMultipleImages,
fetchAllImages,
} from '../../../api/api-functions/images';
import { extractThumbnailPath } from '@/hooks/useImages';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuTrigger,
} from '@radix-ui/react-dropdown-menu';
import { Filter } from 'lucide-react';
import { MediaItem } from '@/types/Media';
interface DeleteSelectedImageProps {
setIsVisibleSelectedImage: (value: boolean) => void;
onError: (title: string, err: any) => void;
uniqueTags: string[];
mediaItems: MediaItem[];
}

const DeleteSelectedImagePage: React.FC<DeleteSelectedImageProps> = ({
setIsVisibleSelectedImage,
onError,
uniqueTags,
mediaItems
}) => {
const [selectedImages, setSelectedImages] = useState<string[]>([]);

Expand All @@ -27,29 +38,18 @@ const DeleteSelectedImagePage: React.FC<DeleteSelectedImageProps> = ({
queryKey: ['all-images'],
});

console.log('All Images Data : ', response);


const { mutate: deleteMultipleImages, isPending: isAddingImages } =
usePictoMutation({
mutationFn: delMultipleImages,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['all-images'] });
},
autoInvalidateTags: ['ai-tagging-images', 'ai'],
});

usePictoMutation({
mutationFn: delMultipleImages,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['all-images'] });
},
autoInvalidateTags: ['ai-tagging-images', 'ai'],
});
// Extract the array of image paths
const allImages: string[] = response?.image_files || [];

const imagesWithThumbnails = allImages.map((imagePath) => {
return {
imagePath,
url: convertFileSrc(imagePath),
thumbnailUrl: convertFileSrc(
extractThumbnailPath(response.folder_path, imagePath),
),
};
});
const toggleImageSelection = (imagePath: string) => {
setSelectedImages((prev) =>
prev.includes(imagePath)
Expand All @@ -73,13 +73,33 @@ const DeleteSelectedImagePage: React.FC<DeleteSelectedImageProps> = ({
}
};

const handleSelectAllImages = () => {
if (selectedImages.length === allImages.length) {
setSelectedImages([]);

const [filterTag, setFilterTag] = useState<string>(uniqueTags[0]);

const handleFilterTag = (value: string) => {
setSelectedImages([]);
setFilterTag(value);

if(value.length === 0) {
setSelectedImages(allImages);
return;
}
setSelectedImages(allImages);

const selectedImagesPaths: string[] = [];

mediaItems.forEach((ele) => {
if (ele.tags?.includes(value)) {
selectedImagesPaths.push(ele.imagePath);
}
});

console.log("Selected Images Path = ", selectedImagesPaths);
setSelectedImages(selectedImagesPaths);
};





const getImageName = (path: string) => {
return path.split('\\').pop() || path;
Expand All @@ -97,10 +117,52 @@ const DeleteSelectedImagePage: React.FC<DeleteSelectedImageProps> = ({
<div className="container mx-auto p-4">
<div className="flex items-center justify-between">
<h1 className="mb-4 text-2xl font-bold">Select Images</h1>
<button onClick={handleSelectAllImages}>Select All</button>
<div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="outline"
className="flex items-center gap-2 rounded-lg border-gray-500 px-4 py-2 shadow-sm transition duration-300 ease-in-out hover:bg-accent dark:hover:bg-white/10"
>
<Filter className="h-4 w-4 text-gray-700 dark:text-white" />
<p className="hidden text-sm text-gray-700 dark:text-white lg:inline">
Select Tag : {filterTag || 'tags'}
</p>
</Button>
</DropdownMenuTrigger>

<DropdownMenuContent
className="z-50 max-h-[500px] w-[200px] overflow-y-auto rounded-lg bg-gray-800 p-2 shadow-lg dark:bg-gray-900"
align="end"
>
<DropdownMenuRadioGroup
className="overflow-auto rounded-lg bg-gray-950 text-white"
value={filterTag}
onValueChange={(value)=>handleFilterTag(value)}
>
<DropdownMenuRadioItem
value=""
className="rounded-md px-4 py-2 text-sm transition-colors duration-200 hover:bg-gray-700 hover:text-white"
>
All tags
</DropdownMenuRadioItem>
{uniqueTags.map((tag) => (
<DropdownMenuRadioItem
key={tag}
value={tag}
className="rounded-md px-4 py-2 text-sm transition-colors duration-200 hover:bg-gray-700 hover:text-white"
>
{tag}
</DropdownMenuRadioItem>
))}
</DropdownMenuRadioGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
{/* <button onClick={handleSelectAllImages}>Select All</button> */}
</div>
<div className="grid grid-cols-2 gap-4 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5">
{imagesWithThumbnails.map(({ imagePath, thumbnailUrl }, index) => {
{mediaItems.map(({ imagePath, thumbnailUrl }, index) => {
return (
<div key={index} className="relative">
<div
Expand All @@ -123,10 +185,16 @@ const DeleteSelectedImagePage: React.FC<DeleteSelectedImageProps> = ({
);
})}
</div>
<div className="mt-4 flex justify-between">
<Button onClick={() => setIsVisibleSelectedImage(true)}>Cancel</Button>
<div className="fixed bottom-0 left-0 right-0 z-50 mb-4 flex justify-evenly bg-transparent p-4 shadow-lg">
<Button
variant="secondary"
onClick={() => setIsVisibleSelectedImage(true)}
>
Cancel
</Button>
<Button
onClick={handleAddSelectedImages}
variant="destructive"
disabled={isAddingImages || selectedImages.length === 0}
>
Delete Selected Images ({selectedImages.length})
Expand Down
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 @@ -6,7 +6,7 @@ export function Navbar(props: { title?: string }) {
<div className="bg-theme-light mb-4 mt-3 flex h-16 items-center justify-between rounded-2xl border border-gray-200 px-4 sm:px-8 md:px-16 shadow-md backdrop-blur-md backdrop-saturate-150 dark:border-white/10 dark:bg-white/5 w-[90%] sm:w-[70%] md:w-[55%]">
<div className="flex items-center gap-2 sm:gap-4">
<div className="flex items-center gap-2">
<img src="/public/PictoPy_Logo.png" className="h-7" alt="PictoPy Logo" />
<img src="/PictoPy_Logo.png" className="h-7" alt="PictoPy Logo" />
<span className="text-theme-dark dark:text-theme-light font-sans text-base sm:text-lg font-bold drop-shadow-sm">
PictoPy
</span>
Expand Down
1 change: 1 addition & 0 deletions frontend/src/types/Media.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface MediaItem {
date?: string;
title?: string;
tags?: string[];
imagePath:string;
}
export interface MediaCardProps {
item: MediaItem;
Expand Down

0 comments on commit 1ccc6b4

Please sign in to comment.