From add11012b4415678ea38df616e6a99ba32643d78 Mon Sep 17 00:00:00 2001 From: Rahul Harpal Date: Sat, 4 Jan 2025 21:51:54 +0530 Subject: [PATCH 01/22] Added Multiple folder support and bug fixes --- backend/app/routes/images.py | 184 +++++++++++++----- frontend/api/api-functions/images.ts | 20 +- frontend/api/apiEndpoints.ts | 1 + frontend/package.json | 4 +- frontend/src-tauri/src/services/mod.rs | 136 ++++++------- .../src/components/AITagging/AIgallery.tsx | 106 +++++----- .../components/AITagging/FilterControls.tsx | 4 +- frontend/src/components/Album/Album.tsx | 31 +-- frontend/src/components/Album/AlbumCard.tsx | 4 +- frontend/src/components/Album/AlbumForm.tsx | 23 ++- frontend/src/components/Album/Albumview.tsx | 4 +- .../src/components/Album/ImageSelection.tsx | 4 +- .../FolderPicker/AITaggingFolderPicker.tsx | 48 +++++ .../FolderPicker/DeleteSelectedImagePage.tsx | 42 ++-- .../components/FolderPicker/FolderPicker.tsx | 46 +---- .../src/components/Media/MediaGallery.tsx | 34 +++- frontend/src/components/Media/MediaView.tsx | 8 +- .../src/components/Media/SortningControls.tsx | 2 +- .../components/Navigation/Navbar/Navbar.tsx | 11 +- .../components/Navigation/Sidebar/Sidebar.tsx | 2 +- frontend/src/components/ui/Icons/Icons.tsx | 176 +---------------- .../src/controllers/InitialPageController.tsx | 31 +++ frontend/src/controllers/SetupController.tsx | 32 --- .../Setup/{Setup.tsx => SetupScreen.tsx} | 12 +- frontend/src/hooks/LocalStorage.ts | 14 +- frontend/src/hooks/UseVideos.ts | 12 +- frontend/src/hooks/folderService.ts | 9 +- frontend/src/hooks/useFolderPath.ts | 6 +- frontend/src/hooks/useImages.ts | 117 +++++------ frontend/src/pages/Dashboard/Dashboard.tsx | 6 +- frontend/src/pages/SettingsPage/Settings.tsx | 106 ++++++++-- frontend/src/pages/Setupscreen/Setup.tsx | 8 +- frontend/src/pages/VideosPage/Videos.tsx | 5 +- frontend/src/types/Album.ts | 3 +- frontend/src/types/Media.ts | 2 +- 35 files changed, 634 insertions(+), 619 deletions(-) create mode 100644 frontend/src/components/FolderPicker/AITaggingFolderPicker.tsx create mode 100644 frontend/src/controllers/InitialPageController.tsx delete mode 100644 frontend/src/controllers/SetupController.tsx rename frontend/src/features/Setup/{Setup.tsx => SetupScreen.tsx} (61%) diff --git a/backend/app/routes/images.py b/backend/app/routes/images.py index 48fc9e3b..16b5194d 100644 --- a/backend/app/routes/images.py +++ b/backend/app/routes/images.py @@ -255,28 +255,26 @@ def delete_multiple_images(payload: dict): parts.insert(parts.index("images") + 1, "PictoPy.thumbnails") thumb_nail_image_path = os.sep.join(parts) - - if os.path.exists(path) : - try : + 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 : + + 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 : + else: print(f"File '{thumb_nail_image_path}' does not exist.") - delete_image_db(path) deleted_paths.append(path) @@ -479,6 +477,98 @@ async def add_folder(payload: dict): @router.post("/generate-thumbnails") @exception_handler_wrapper def generate_thumbnails(payload: dict): + if "folder_paths" not in payload or not isinstance(payload["folder_paths"], list): + return JSONResponse( + status_code=400, + content={ + "status_code": 400, + "content": { + "success": False, + "error": "Invalid or missing 'folder_paths' in payload", + "message": "'folder_paths' must be a list of folder paths", + }, + }, + ) + + folder_paths = payload["folder_paths"] + image_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp"] + failed_paths = [] + + for folder_path in folder_paths: + if not os.path.isdir(folder_path): + failed_paths.append( + { + "folder_path": folder_path, + "error": "Invalid folder path", + "message": "The provided path is not a valid directory", + } + ) + continue + + for root, _, files in os.walk(folder_path): + # Do not generate thumbnails for the "PictoPy.thumbnails" folder + if "PictoPy.thumbnails" in root: + continue + + # Create the "PictoPy.thumbnails" folder in the current directory (`root`) + thumbnail_folder = os.path.join(root, "PictoPy.thumbnails") + os.makedirs(thumbnail_folder, exist_ok=True) + + for file in files: + file_path = os.path.join(root, file) + file_extension = os.path.splitext(file_path)[1].lower() + if file_extension in image_extensions: + try: + # Create a unique thumbnail name based on the file name + thumbnail_name = file + thumbnail_path = os.path.join(thumbnail_folder, thumbnail_name) + + # Skip if the thumbnail already exists + if os.path.exists(thumbnail_path): + continue + + # Generate the thumbnail + img = Image.open(file_path) + img.thumbnail((400, 400)) + img.save(thumbnail_path) + except Exception as e: + failed_paths.append( + { + "folder_path": folder_path, + "file": file_path, + "error": "Thumbnail generation error", + "message": f"Error processing file {file}: {str(e)}", + } + ) + + if failed_paths: + return JSONResponse( + status_code=207, # Multi-Status (some succeeded, some failed) + content={ + "status_code": 207, + "content": { + "success": False, + "error": "Partial processing", + "message": "Some folders or files could not be processed", + "failed_paths": failed_paths, + }, + }, + ) + + return JSONResponse( + status_code=201, + content={ + "data": "", + "message": "Thumbnails generated successfully for all valid folders", + "success": True, + }, + ) + + +# Delete all the thumbnails present in the given folder +@router.delete("/delete-thumbnails") +@exception_handler_wrapper +def delete_thumbnails(payload: dict): if "folder_path" not in payload: return JSONResponse( status_code=400, @@ -506,53 +596,47 @@ def generate_thumbnails(payload: dict): }, ) - thumbnail_folder = os.path.join(folder_path, "PictoPy.thumbnails") - os.makedirs(thumbnail_folder, exist_ok=True) + # List to store any errors encountered while deleting thumbnails + failed_deletions = [] - image_extensions = [".jpg", ".jpeg", ".png", ".bmp", ".gif", ".webp"] - for root, _, files in os.walk(folder_path): - if "PictoPy.thumbnails" in root: - continue - for file in files: - file_path = os.path.join(root, file) - file_extension = os.path.splitext(file_path)[1].lower() - if file_extension in image_extensions: + # Walk through the folder path and find all `PictoPy.thumbnails` folders + for root, dirs, _ in os.walk(folder_path): + for dir_name in dirs: + if dir_name == "PictoPy.thumbnails": + thumbnail_folder = os.path.join(root, dir_name) try: - # Create a unique name based on the relative folder structure - relative_path = os.path.relpath(root, folder_path) - sanitized_relative_path = relative_path.replace( - os.sep, "." - ) # Replace path separators - thumbnail_name = ( - f"{sanitized_relative_path}.{file}" - if relative_path != "." - else file - ) - thumbnail_path = os.path.join(thumbnail_folder, thumbnail_name) - if os.path.exists(thumbnail_path): - # print(f"Thumbnail {thumbnail_name} already exists. Skipping.") - continue - img = Image.open(file_path) - img.thumbnail((400, 400)) - img.save(thumbnail_path) + # Delete the thumbnail folder + shutil.rmtree(thumbnail_folder) + print(f"Deleted: {thumbnail_folder}") except Exception as e: - return JSONResponse( - status_code=500, - content={ - "status_code": 500, - "content": { - "success": False, - "error": "Internal server error", - "message": f"Error processing file {file}: {str(e)}", - }, - }, + failed_deletions.append( + { + "folder": thumbnail_folder, + "error": str(e), + } ) + if failed_deletions: + return JSONResponse( + status_code=500, + content={ + "status_code": 500, + "content": { + "success": False, + "error": "Some thumbnail folders could not be deleted", + "message": "See the `failed_deletions` field for details.", + "failed_deletions": failed_deletions, + }, + }, + ) + return JSONResponse( - status_code=201, + status_code=200, content={ - "data": "", - "message": "Thumbnails generated successfully", - "success": True, + "status_code": 200, + "content": { + "success": True, + "message": "All PictoPy.thumbnails folders have been successfully deleted.", + }, }, ) diff --git a/frontend/api/api-functions/images.ts b/frontend/api/api-functions/images.ts index 783dc465..80c8f2e7 100644 --- a/frontend/api/api-functions/images.ts +++ b/frontend/api/api-functions/images.ts @@ -31,11 +31,9 @@ const parseAndSortImageData = (data: APIResponse['data']): Image[] => { const parsedImages: Image[] = Object.entries(data.images).map( ([src, tags]) => { const url = convertFileSrc(src); - const thumbnailUrl = convertFileSrc( - extractThumbnailPath(data.folder_path, src), - ); + const thumbnailUrl = convertFileSrc(extractThumbnailPath(src)); return { - imagePath:src, + imagePath: src, title: src.substring(src.lastIndexOf('\\') + 1), thumbnailUrl, url, @@ -85,12 +83,24 @@ export const addMultImages = async (paths: string[]) => { return data; }; -export const generateThumbnails = async (folderPath: string) => { +export const generateThumbnails = async (folderPath: string[]) => { const response = await fetch(imagesEndpoints.generateThumbnails, { method: 'POST', headers: { 'Content-Type': 'application/json', }, + body: JSON.stringify({ folder_paths: folderPath }), + }); + const data = await response.json(); + return data; +}; + +export const deleteThumbnails = async (folderPath: string) => { + const response = await fetch(imagesEndpoints.deleteThumbnails, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, body: JSON.stringify({ folder_path: folderPath }), }); const data = await response.json(); diff --git a/frontend/api/apiEndpoints.ts b/frontend/api/apiEndpoints.ts index 5949e95c..c5603feb 100644 --- a/frontend/api/apiEndpoints.ts +++ b/frontend/api/apiEndpoints.ts @@ -7,6 +7,7 @@ export const imagesEndpoints = { addFolder: `${BACKED_URL}/images/add-folder`, addMultipleImages: `${BACKED_URL}/images/multiple-images`, generateThumbnails: `${BACKED_URL}/images/generate-thumbnails`, + deleteThumbnails: `${BACKED_URL}/images/delete-thumbnails`, }; export const albumEndpoints = { diff --git a/frontend/package.json b/frontend/package.json index b9084747..ab02743d 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,5 @@ { - "name": "tauri-app", + "name": "PictoPy", "private": true, "version": "0.0.0", "type": "module", @@ -54,4 +54,4 @@ "vite": "^5.0.0", "vite-plugin-eslint": "^1.8.1" } -} +} \ No newline at end of file diff --git a/frontend/src-tauri/src/services/mod.rs b/frontend/src-tauri/src/services/mod.rs index 068dac2e..cf3e66d4 100644 --- a/frontend/src-tauri/src/services/mod.rs +++ b/frontend/src-tauri/src/services/mod.rs @@ -35,7 +35,7 @@ pub fn get_images_in_folder( pub fn get_all_images_with_cache( state: tauri::State, cache_state: tauri::State, - directory: &str, + directories: Vec, ) -> Result>>, String> { let cached_images = cache_state.get_cached_images(); @@ -60,35 +60,35 @@ pub fn get_all_images_with_cache( } map } else { - let all_images = state.get_all_images(directory); let mut map: HashMap>> = HashMap::new(); + let mut all_image_paths: Vec = Vec::new(); - for path in all_images { - if let Ok(metadata) = std::fs::metadata(&path) { - let date = metadata - .created() - .or_else(|_| metadata.modified()) - .unwrap_or_else(|_| SystemTime::now()); + for directory in directories { + let all_images = state.get_all_images(&directory); - let datetime: DateTime = date.into(); - let year = datetime.year() as u32; - let month = datetime.month(); - map.entry(year) - .or_insert_with(HashMap::new) - .entry(month) - .or_insert_with(Vec::new) - .push(path.to_str().unwrap_or_default().to_string()); + for path in all_images { + if let Ok(metadata) = std::fs::metadata(&path) { + let date = metadata + .created() + .or_else(|_| metadata.modified()) + .unwrap_or_else(|_| SystemTime::now()); + + let datetime: DateTime = date.into(); + let year = datetime.year() as u32; + let month = datetime.month(); + map.entry(year) + .or_insert_with(HashMap::new) + .entry(month) + .or_insert_with(Vec::new) + .push(path.to_str().unwrap_or_default().to_string()); + + all_image_paths.push(path); // Collect all paths for caching + } } } // Cache the flattened list of image paths - let flattened: Vec = map - .values() - .flat_map(|year_map| year_map.values()) - .flatten() - .map(|s| PathBuf::from(s)) - .collect(); - if let Err(e) = cache_state.cache_images(&flattened) { + if let Err(e) = cache_state.cache_images(&all_image_paths) { eprintln!("Failed to cache images: {}", e); } @@ -109,59 +109,63 @@ pub fn get_all_images_with_cache( pub fn get_all_videos_with_cache( state: tauri::State, cache_state: tauri::State, - directory: &str, + directories: Vec, // Updated to take an array of directories ) -> Result>>, String> { let cached_videos = cache_state.get_cached_videos(); - let mut videos_by_year_month = if let Some(cached) = cached_videos { - let mut map: HashMap>> = HashMap::new(); - for path in cached { - if let Ok(metadata) = std::fs::metadata(&path) { - if let Ok(created) = metadata.created() { - let datetime: DateTime = created.into(); - let year = datetime.year() as u32; - let month = datetime.month(); - map.entry(year) - .or_insert_with(HashMap::new) - .entry(month) - .or_insert_with(Vec::new) - .push(path.to_str().unwrap_or_default().to_string()); + let mut videos_by_year_month: HashMap>> = + if let Some(cached) = cached_videos { + let mut map: HashMap>> = HashMap::new(); + for path in cached { + if let Ok(metadata) = std::fs::metadata(&path) { + if let Ok(created) = metadata.created() { + let datetime: DateTime = created.into(); + let year = datetime.year() as u32; + let month = datetime.month(); + map.entry(year) + .or_insert_with(HashMap::new) + .entry(month) + .or_insert_with(Vec::new) + .push(path.to_str().unwrap_or_default().to_string()); + } } } - } - map - } else { - let all_videos = state.get_all_videos(directory); - let mut map: HashMap>> = HashMap::new(); - - for path in all_videos { - if let Ok(metadata) = std::fs::metadata(&path) { - if let Ok(created) = metadata.created() { - let datetime: DateTime = created.into(); - let year = datetime.year() as u32; - let month = datetime.month(); - map.entry(year) - .or_insert_with(HashMap::new) - .entry(month) - .or_insert_with(Vec::new) - .push(path.to_str().unwrap_or_default().to_string()); + map + } else { + let mut map: HashMap>> = HashMap::new(); + for directory in directories { + let all_videos = state.get_all_videos(&directory); + for path in all_videos { + if let Ok(metadata) = std::fs::metadata(&path) { + if let Ok(created) = metadata.created() { + let datetime: DateTime = created.into(); + let year = datetime.year() as u32; + let month = datetime.month(); + map.entry(year) + .or_insert_with(HashMap::new) + .entry(month) + .or_insert_with(Vec::new) + .push(path.to_str().unwrap_or_default().to_string()); + } + } } } - } - let flattened: Vec = map - .values() - .flat_map(|year_map| year_map.values()) - .flatten() - .map(|s| PathBuf::from(s)) - .collect(); - if let Err(e) = cache_state.cache_videos(&flattened) { - eprintln!("Failed to cache videos: {}", e); - } + // Cache the aggregated video paths + let flattened: Vec = map + .values() + .flat_map(|year_map| year_map.values()) + .flatten() + .map(|s| PathBuf::from(s)) + .collect(); + if let Err(e) = cache_state.cache_videos(&flattened) { + eprintln!("Failed to cache videos: {}", e); + } - map - }; + map + }; + // Sort the videos within each month for year_map in videos_by_year_month.values_mut() { for month_vec in year_map.values_mut() { month_vec.sort(); diff --git a/frontend/src/components/AITagging/AIgallery.tsx b/frontend/src/components/AITagging/AIgallery.tsx index 45e3fe9d..62aa488c 100644 --- a/frontend/src/components/AITagging/AIgallery.tsx +++ b/frontend/src/components/AITagging/AIgallery.tsx @@ -28,16 +28,15 @@ export default function AIGallery({ type: 'image' | 'video'; folderPath: string; }) { - const { successData, isLoading: loading } = usePictoQuery({ + const { successData, isLoading: isGeneratingTags } = usePictoQuery({ queryFn: async () => await getAllImageObjects(), queryKey: ['ai-tagging-images', 'ai'], }); - const { mutate: generateThumbnail, isPending: isCreating } = usePictoMutation( - { + const { mutate: generateThumbnailAPI, isPending: isGeneratingThumbnails } = + usePictoMutation({ mutationFn: generateThumbnails, autoInvalidateTags: ['ai-tagging-images', 'ai'], - }, - ); + }); let mediaItems = successData ?? []; const [filterTag, setFilterTag] = useState(''); const [currentPage, setCurrentPage] = useState(1); @@ -52,13 +51,12 @@ export default function AIGallery({ ); const filteredMediaItems = useMemo(() => { return filterTag - ? mediaItems.filter((mediaItem: any) => - mediaItem.tags.includes(filterTag), - ) - : mediaItems; -}, [filterTag, mediaItems, loading]); -const [pageNo,setpageNo] = useState(20); - + ? mediaItems.filter((mediaItem: any) => + mediaItem.tags.includes(filterTag), + ) + : mediaItems; + }, [filterTag, mediaItems, isGeneratingTags]); + const [pageNo, setpageNo] = useState(20); const currentItems = useMemo(() => { const indexOfLastItem = currentPage * pageNo; @@ -78,14 +76,14 @@ const [pageNo,setpageNo] = useState(20); }, []); const handleFolderAdded = useCallback(async () => { - generateThumbnail(folderPath); + generateThumbnailAPI([folderPath]); }, []); useEffect(() => { - generateThumbnail(folderPath); + generateThumbnailAPI([folderPath]); }, [folderPath]); - if (isCreating || loading) { + if (isGeneratingThumbnails || isGeneratingTags) { return (
@@ -105,7 +103,7 @@ const [pageNo,setpageNo] = useState(20); setFilterTag={setFilterTag} mediaItems={mediaItems} onFolderAdded={handleFolderAdded} - isLoading={loading} + isLoading={isGeneratingTags} isVisibleSelectedImage={isVisibleSelectedImage} setIsVisibleSelectedImage={setIsVisibleSelectedImage} /> @@ -119,44 +117,47 @@ const [pageNo,setpageNo] = useState(20); openMediaViewer={openMediaViewer} type={type} /> -
- {/* Pagination Controls - Centered */} - +
+ {/* Pagination Controls - Centered */} + - {/* Dropdown Menu - Right-Aligned */} -
- - - - - + + +
+

+ Num of images per page : {pageNo} +

+ + + + setpageNo(Number(value))} + > + {noOfPages.map((itemsPerPage) => ( + + {itemsPerPage} + + ))} + + + +
)} @@ -174,4 +175,3 @@ const [pageNo,setpageNo] = useState(20);
); } - diff --git a/frontend/src/components/AITagging/FilterControls.tsx b/frontend/src/components/AITagging/FilterControls.tsx index 4aed312e..541fd1b5 100644 --- a/frontend/src/components/AITagging/FilterControls.tsx +++ b/frontend/src/components/AITagging/FilterControls.tsx @@ -9,7 +9,7 @@ import { import { Button } from '../ui/button'; import { MediaItem } from '@/types/Media'; -import FolderPicker from '../FolderPicker/FolderPicker'; +import AITaggingFolderPicker from '../FolderPicker/AITaggingFolderPicker'; import LoadingScreen from '../ui/LoadingScreen/LoadingScreen'; import DeleteSelectedImagePage from '../FolderPicker/DeleteSelectedImagePage'; import ErrorDialog from '../Album/Error'; @@ -96,7 +96,7 @@ export default function FilterControls({
Error: {errorMessage}
)}
- + - diff --git a/frontend/src/components/Album/Albumview.tsx b/frontend/src/components/Album/Albumview.tsx index 1a425b44..2b388629 100644 --- a/frontend/src/components/Album/Albumview.tsx +++ b/frontend/src/components/Album/Albumview.tsx @@ -80,9 +80,7 @@ const AlbumView: React.FC = ({ const convertedImagePaths = albumData.photos.map((path) => { return { url: convertFileSrc(path), - thumbnailUrl: convertFileSrc( - extractThumbnailPath(albumData.folder_path, path), - ), + thumbnailUrl: convertFileSrc(extractThumbnailPath(path)), }; }); diff --git a/frontend/src/components/Album/ImageSelection.tsx b/frontend/src/components/Album/ImageSelection.tsx index e3a563e7..949c9c07 100644 --- a/frontend/src/components/Album/ImageSelection.tsx +++ b/frontend/src/components/Album/ImageSelection.tsx @@ -45,9 +45,7 @@ const ImageSelectionPage: React.FC = ({ const imagesWithThumbnails = allImages.map((imagePath) => ({ imagePath, url: convertFileSrc(imagePath), - thumbnailUrl: convertFileSrc( - extractThumbnailPath(allImagesData.folder_path, imagePath), - ), + thumbnailUrl: convertFileSrc(extractThumbnailPath(imagePath)), })); useEffect(() => { if (errorMessage && errorMessage !== 'Something went wrong') { diff --git a/frontend/src/components/FolderPicker/AITaggingFolderPicker.tsx b/frontend/src/components/FolderPicker/AITaggingFolderPicker.tsx new file mode 100644 index 00000000..8503ce21 --- /dev/null +++ b/frontend/src/components/FolderPicker/AITaggingFolderPicker.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +import { Button } from '../ui/button'; +import { open } from '@tauri-apps/plugin-dialog'; +import { FolderPlus } from 'lucide-react'; +interface FolderPickerProps { + setFolderPath: (path: string) => void; + className?: string; + handleDeleteCache?: () => void; +} + +const AITaggingFolderPicker: React.FC = ({ + setFolderPath, + className, + handleDeleteCache, +}) => { + const pickFolder = async () => { + try { + const selected = await open({ + directory: true, + multiple: false, + title: 'Select a folder', + }); + if (selected && typeof selected === 'string') { + setFolderPath(selected); + console.log('Selected folder:', selected); + if (handleDeleteCache) { + handleDeleteCache(); + } + } + } catch (error) { + console.error('Error picking folder:', error); + } + }; + return ( +
+ +
+ ); +}; + +export default AITaggingFolderPicker; diff --git a/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx b/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx index 54b7ed54..168e7d95 100644 --- a/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx +++ b/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx @@ -29,7 +29,7 @@ const DeleteSelectedImagePage: React.FC = ({ setIsVisibleSelectedImage, onError, uniqueTags, - mediaItems + mediaItems, }) => { const [selectedImages, setSelectedImages] = useState([]); @@ -38,16 +38,15 @@ const DeleteSelectedImagePage: React.FC = ({ queryKey: ['all-images'], }); - 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 toggleImageSelection = (imagePath: string) => { @@ -73,33 +72,28 @@ const DeleteSelectedImagePage: React.FC = ({ } }; - const [filterTag, setFilterTag] = useState(uniqueTags[0]); const handleFilterTag = (value: string) => { - setSelectedImages([]); - setFilterTag(value); - - if(value.length === 0) { + setSelectedImages([]); + setFilterTag(value); + + if (value.length === 0) { setSelectedImages(allImages); return; } const selectedImagesPaths: string[] = []; - + mediaItems.forEach((ele) => { if (ele.tags?.includes(value)) { selectedImagesPaths.push(ele.imagePath); } }); - - console.log("Selected Images Path = ", selectedImagesPaths); + + console.log('Selected Images Path = ', selectedImagesPaths); setSelectedImages(selectedImagesPaths); }; - - - - const getImageName = (path: string) => { return path.split('\\').pop() || path; @@ -126,7 +120,7 @@ const DeleteSelectedImagePage: React.FC = ({ >

- Select Tag : {filterTag || 'tags'} + Select Tag : {filterTag || 'tags'}

@@ -138,7 +132,7 @@ const DeleteSelectedImagePage: React.FC = ({ handleFilterTag(value)} + onValueChange={(value) => handleFilterTag(value)} > void; + setFolderPaths: (paths: string[]) => void; className?: string; - settingsPage?: boolean; - setIsLoading?: (loading: boolean) => void; - handleDeleteCache?: () => void; } const FolderPicker: React.FC = ({ - setFolderPath, + setFolderPaths, className, - settingsPage, - setIsLoading, - handleDeleteCache, }) => { - const { mutate: generateThumbnail } = usePictoMutation({ - mutationFn: generateThumbnails, - onSuccess: () => { - if (setIsLoading) { - setIsLoading(false); - } - }, - autoInvalidateTags: ['ai-tagging-images', 'ai'], - }); - const pickFolder = async () => { try { const selected = await open({ directory: true, - multiple: false, - title: 'Select a folder', + multiple: true, // Allow multiple folder selection + title: 'Select folders', }); - if (selected && typeof selected === 'string') { - setFolderPath(selected); - console.log('Selected folder:', selected); - if (settingsPage) { - setIsLoading && setIsLoading(true); - generateThumbnail(selected); - } - if (handleDeleteCache) { - handleDeleteCache(); - } + if (selected && Array.isArray(selected)) { + setFolderPaths(selected); } } catch (error) { - console.error('Error picking folder:', error); + console.error('Error picking folders:', error); } }; + return (
); diff --git a/frontend/src/components/Media/MediaGallery.tsx b/frontend/src/components/Media/MediaGallery.tsx index f1cc477c..d428ef1e 100644 --- a/frontend/src/components/Media/MediaGallery.tsx +++ b/frontend/src/components/Media/MediaGallery.tsx @@ -5,6 +5,9 @@ import SortingControls from './SortningControls'; import PaginationControls from '../ui/PaginationControls'; import { MediaGalleryProps } from '@/types/Media'; import { sortMedia } from '@/utils/Media'; +import { Button } from '@/components/ui/button'; +import { RefreshCw } from 'lucide-react'; +import { deleteCache } from '@/services/cacheService'; export default function MediaGallery({ mediaItems, @@ -43,16 +46,37 @@ export default function MediaGallery({ const closeMediaViewer = useCallback(() => { setShowMediaViewer(false); }, []); + const handleRefreshClick = async () => { + try { + const result = await deleteCache(); + if (result) { + console.log('Cache deleted'); + } + window.location.reload(); + } catch (error) { + console.error('Error deleting cache:', error); + } + }; return (

{title || currentYear}

- +
+ + +
= ({ {type === 'image' ? (
{ + if (e.target === e.currentTarget) { + onClose(); + } + }} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp} @@ -184,4 +190,4 @@ const MediaView: React.FC = ({ ); }; -export default MediaView; \ No newline at end of file +export default MediaView; diff --git a/frontend/src/components/Media/SortningControls.tsx b/frontend/src/components/Media/SortningControls.tsx index bbfbf2c1..54eee146 100644 --- a/frontend/src/components/Media/SortningControls.tsx +++ b/frontend/src/components/Media/SortningControls.tsx @@ -49,7 +49,7 @@ const SortingControls: React.FC = ({ variant="outline" className="flex items-center gap-2 border-gray-500" > - + {`Sort: ${sortingOptions.find((opt) => opt.value === sortBy)?.label || 'Select'}`} diff --git a/frontend/src/components/Navigation/Navbar/Navbar.tsx b/frontend/src/components/Navigation/Navbar/Navbar.tsx index 0d7540dc..6ae80124 100644 --- a/frontend/src/components/Navigation/Navbar/Navbar.tsx +++ b/frontend/src/components/Navigation/Navbar/Navbar.tsx @@ -33,7 +33,7 @@ export function Navbar({ title, onNameChange }: NavbarProps) { } } }, - [onNameChange] + [onNameChange], ); const handleNameClick = useCallback(() => { @@ -53,12 +53,12 @@ export function Navbar({ title, onNameChange }: NavbarProps) { } setIsEditing(false); }, - [onNameChange] + [onNameChange], ); return (
-
+
PictoPy Logo - + PictoPy
@@ -91,7 +91,7 @@ export function Navbar({ title, onNameChange }: NavbarProps) { ) : ( +
+ )) + ) : ( +
+ No folder paths selected +
+ )}
+ setErrorDialogContent(null)} + />
); }; diff --git a/frontend/src/pages/Setupscreen/Setup.tsx b/frontend/src/pages/Setupscreen/Setup.tsx index 5fdda2ff..c61f0c26 100644 --- a/frontend/src/pages/Setupscreen/Setup.tsx +++ b/frontend/src/pages/Setupscreen/Setup.tsx @@ -1,13 +1,13 @@ import React from 'react'; -import { useInitialPageController } from '@/controllers/SetupController'; -import { SetupScreen } from '@/features/Setup/Setup'; +import { useInitialPageController } from '@/controllers/InitialPageController'; +import { SetupScreen } from '@/features/Setup/SetupScreen'; import { LoadingScreen } from '@/components/LoadingScreen/LoadingScreen'; export const InitialPage: React.FC = () => { - const { loading, handleFolderPathChange } = useInitialPageController(); + const { loading, handleFolderPathsChange } = useInitialPageController(); if (loading) { return ; } - return ; + return ; }; diff --git a/frontend/src/pages/VideosPage/Videos.tsx b/frontend/src/pages/VideosPage/Videos.tsx index bf4a96d8..04f1cda7 100644 --- a/frontend/src/pages/VideosPage/Videos.tsx +++ b/frontend/src/pages/VideosPage/Videos.tsx @@ -1,10 +1,11 @@ import { LoadingScreen } from '@/components/ui/LoadingScreen/LoadingScreen'; import MediaGallery from '@/components/Media/MediaGallery'; import { useVideos } from '@/hooks/UseVideos'; +import { useLocalStorage } from '@/hooks/LocalStorage'; const Videos: React.FC = () => { - const localPath = localStorage.getItem('folderPath') || ''; - const { videos, loading } = useVideos(localPath); + const [currentPaths] = useLocalStorage('folderPaths', []); + const { videos, loading } = useVideos(currentPaths); if (loading) { return ; diff --git a/frontend/src/types/Album.ts b/frontend/src/types/Album.ts index 0d97d56a..4ad8df0f 100644 --- a/frontend/src/types/Album.ts +++ b/frontend/src/types/Album.ts @@ -29,8 +29,7 @@ export interface AlbumData { } export interface CreateAlbumFormProps { isOpen: boolean; - onClose: () => void; - onSuccess: () => void; + closeForm: () => void; onError: (title: string, error: unknown) => void; } diff --git a/frontend/src/types/Media.ts b/frontend/src/types/Media.ts index 3219c37d..a9cbf54f 100644 --- a/frontend/src/types/Media.ts +++ b/frontend/src/types/Media.ts @@ -5,7 +5,7 @@ export interface MediaItem { date?: string; title?: string; tags?: string[]; - imagePath:string; + imagePath?: string; } export interface MediaCardProps { item: MediaItem; From de382739273a7e9c7b29abdc6d576d8f49ab99b1 Mon Sep 17 00:00:00 2001 From: Rahul Harpal Date: Sat, 4 Jan 2025 23:03:55 +0530 Subject: [PATCH 02/22] Fix image path error for unix-like paths --- frontend/src/hooks/useImages.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/hooks/useImages.ts b/frontend/src/hooks/useImages.ts index b099be48..af077497 100644 --- a/frontend/src/hooks/useImages.ts +++ b/frontend/src/hooks/useImages.ts @@ -74,7 +74,8 @@ export const useImages = (folderPaths: string[]) => { const mappedImages = await Promise.all( imagePaths.map(async (imagePath: string) => { const original = imagePath; - const fileName = imagePath.split('\\').pop(); + const cleanedImagePath = imagePath.replace(/\\/g, '/'); // Replaces all '\' with '/' + const fileName = cleanedImagePath.split('/').pop(); const url = convertFileSrc(imagePath); const thumbnailUrl = convertFileSrc( extractThumbnailPath(imagePath), From e132d8374f45548dc1558f38e368aec2615bc936 Mon Sep 17 00:00:00 2001 From: Rahul Harpal Date: Sat, 4 Jan 2025 23:20:37 +0530 Subject: [PATCH 03/22] thumbnail deletion error fixed --- backend/app/routes/images.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/app/routes/images.py b/backend/app/routes/images.py index 16b5194d..68fba698 100644 --- a/backend/app/routes/images.py +++ b/backend/app/routes/images.py @@ -251,21 +251,22 @@ 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) + folder_path, filename = os.path.split(path) + thumbnail_folder = os.path.join(folder_path, "PictoPy.thumbnails") + thumb_nail_image_path = os.path.join(thumbnail_folder, filename) + # Check and remove the original file if os.path.exists(path): try: os.remove(path) except PermissionError: - print(f"Permission denied for file '{thumb_nail_image_path}'.") + print(f"Permission denied for file '{path}'.") except Exception as e: print(f"An error occurred: {e}") - else: print(f"File '{path}' does not exist.") + # Check and remove the thumbnail file if os.path.exists(thumb_nail_image_path): try: os.remove(thumb_nail_image_path) From e80ae2b3e5f0350f5f9d7f3438cf1228de44c9bf Mon Sep 17 00:00:00 2001 From: Rahul Harpal Date: Sat, 4 Jan 2025 23:55:00 +0530 Subject: [PATCH 04/22] Implement cache deletion on dashboard load --- frontend/src/pages/Dashboard/Dashboard.tsx | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/src/pages/Dashboard/Dashboard.tsx b/frontend/src/pages/Dashboard/Dashboard.tsx index ba9bd4dd..ba9ac1de 100644 --- a/frontend/src/pages/Dashboard/Dashboard.tsx +++ b/frontend/src/pages/Dashboard/Dashboard.tsx @@ -2,10 +2,25 @@ import MediaGallery from '@/components/Media/MediaGallery'; import LoadingScreen from '@/components/ui/LoadingScreen/LoadingScreen'; import { useImages } from '@/hooks/useImages'; import { useLocalStorage } from '@/hooks/LocalStorage'; +import { useEffect } from 'react'; +import { deleteCache } from '@/services/cacheService'; function Dashboard() { const [currentPaths] = useLocalStorage('folderPaths', []); const { images, isCreating: loading } = useImages(currentPaths); + useEffect(() => { + const func = async () => { + try { + const result = await deleteCache(); + if (result) { + console.log('Cache deleted'); + } + } catch (error) { + console.error('Error deleting cache:', error); + } + }; + func(); + }, [currentPaths]); if (loading) { return ; } From ed42880c455c19ec6b9dc207e0426a6cc5480948 Mon Sep 17 00:00:00 2001 From: Rahul Harpal Date: Mon, 6 Jan 2025 15:37:21 +0530 Subject: [PATCH 05/22] Fix null checks for image paths in DeleteSelectedImagePage component --- .../FolderPicker/DeleteSelectedImagePage.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx b/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx index 34308a52..60d2eb66 100644 --- a/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx +++ b/frontend/src/components/FolderPicker/DeleteSelectedImagePage.tsx @@ -87,7 +87,7 @@ const DeleteSelectedImagePage: React.FC = ({ mediaItems.forEach((ele) => { if (ele.tags?.includes(value)) { - selectedImagesPaths.push(ele.imagePath); + ele.imagePath && selectedImagesPaths.push(ele.imagePath); } }); @@ -160,20 +160,20 @@ const DeleteSelectedImagePage: React.FC = ({ return (
toggleImageSelection(imagePath)} + onClick={() => imagePath && toggleImageSelection(imagePath)} /> {`Image
- {getImageName(imagePath)} + {imagePath && getImageName(imagePath)}
); From 1e8b70393a30c02d010c0fd697ed2e9c0093d3cf Mon Sep 17 00:00:00 2001 From: rahulharpal1603 Date: Sun, 26 Jan 2025 16:38:08 +0530 Subject: [PATCH 06/22] Using thumbnail urls instead of actual url while in MediaView component. --- frontend/src/components/Media/MediaGallery.tsx | 6 +++++- frontend/src/components/Media/MediaView.tsx | 2 +- frontend/src/types/Media.ts | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/src/components/Media/MediaGallery.tsx b/frontend/src/components/Media/MediaGallery.tsx index 2783ac24..2bd8811e 100644 --- a/frontend/src/components/Media/MediaGallery.tsx +++ b/frontend/src/components/Media/MediaGallery.tsx @@ -142,7 +142,11 @@ export default function MediaGallery({ initialIndex={selectedMediaIndex} onClose={closeMediaViewer} allMedia={sortedMedia.map((item) => { - return { url: item.url, path: item?.imagePath }; + return { + url: item.url, + path: item?.imagePath, + thumbnailUrl: item.thumbnailUrl, + }; })} currentPage={currentPage} itemsPerPage={itemsPerPage} diff --git a/frontend/src/components/Media/MediaView.tsx b/frontend/src/components/Media/MediaView.tsx index 7afb7b61..09a2db1a 100644 --- a/frontend/src/components/Media/MediaView.tsx +++ b/frontend/src/components/Media/MediaView.tsx @@ -495,7 +495,7 @@ const MediaView: React.FC = ({ )} {type === 'image' ? ( {`thumbnail-${index}`} diff --git a/frontend/src/types/Media.ts b/frontend/src/types/Media.ts index 02d87e79..dea3c8bd 100644 --- a/frontend/src/types/Media.ts +++ b/frontend/src/types/Media.ts @@ -27,7 +27,7 @@ export interface MediaGridProps { export interface MediaViewProps { initialIndex: number; onClose: () => void; - allMedia: { url: string; path?: string }[]; + allMedia: { url: string; path?: string; thumbnailUrl?: string }[]; currentPage: number; itemsPerPage: number; type: 'image' | 'video'; From d6c45d5e74d6432071eb0400af8e4bc0a0a1e9f3 Mon Sep 17 00:00:00 2001 From: rahulharpal1603 Date: Tue, 4 Feb 2025 02:50:52 +0530 Subject: [PATCH 07/22] reenable multiple folder paths in memories. --- frontend/src/components/Memories/Memories.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/src/components/Memories/Memories.tsx b/frontend/src/components/Memories/Memories.tsx index b50b2e8f..f7dbb15f 100644 --- a/frontend/src/components/Memories/Memories.tsx +++ b/frontend/src/components/Memories/Memories.tsx @@ -22,8 +22,7 @@ const Memories: React.FC = () => { const [storyIndex, setStoryIndex] = useState(0); const itemsPerPage = 12; const [currentPath] = useLocalStorage('folderPath', ''); - // const [currentPaths] = useLocalStorage('folderPaths', []); Temporarily commented out, will be uncommented after open PR related to multiple folder support is merged. - const currentPaths: string[] = []; // Temporarily added to avoid TypeScript error, will be removed after open PR related to multiple folder support is merged. + const [currentPaths] = useLocalStorage('folderPaths', []); const storyDuration = 3000; // 3 seconds per story useEffect(() => { From a1378d54f23580298b18bd67248c27362fdc01e1 Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 20:00:12 +0530 Subject: [PATCH 08/22] added workflow --- .github/workflows/pr-tests.yml | 52 ++++++++++++++++++++++++++++++++++ frontend/src/utils/isProd.ts | 2 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/pr-tests.yml diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml new file mode 100644 index 00000000..eda455cd --- /dev/null +++ b/.github/workflows/pr-tests.yml @@ -0,0 +1,52 @@ +name: PR Tests Workflow + +on: + pull_request: + branches: + - main + +jobs: + run-tests: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + # Frontend Tests + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install and Test Frontend + run: | + cd frontend + npm install + npm test + + # Backend Tests + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.11' + + - name: Install Backend Dependencies and Run Tests + run: | + cd backend + python -m pip install --upgrade pip + pip install -r requirements.txt + pyinstaller main.py --name PictoPy_Server --onedir --distpath dist + pytest + + # Tauri (Rust) Tests + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Run Tauri Tests + run: | + cd frontend/src-tauri + cargo test diff --git a/frontend/src/utils/isProd.ts b/frontend/src/utils/isProd.ts index 93956dae..532679ed 100644 --- a/frontend/src/utils/isProd.ts +++ b/frontend/src/utils/isProd.ts @@ -1,3 +1,3 @@ // Usage: import { isProd } from '@/utils/isProd'; // Utility function to check if the environment is in production mode -export const isProd = () =>import.meta.env.PROD; +export const isProd = () =>process.env.PROD; From a095f88c6e9454ce0898cffe6d2e81a3e8fc8a54 Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 20:08:50 +0530 Subject: [PATCH 09/22] seperating workflows --- .github/workflows/pr-tests.yml | 39 ++++++++++++++++++++++++---------- backend/requirements.txt | 3 ++- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index eda455cd..e0eca1da 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -3,35 +3,44 @@ name: PR Tests Workflow on: pull_request: branches: - - main + - main jobs: - run-tests: - runs-on: ubuntu-latest + # -------------------- Frontend Tests -------------------- + frontend-tests: + name: Frontend Tests + runs-on: ubuntu-latest steps: - - name: Checkout code + - name: Checkout Code uses: actions/checkout@v3 - # Frontend Tests - name: Set up Node.js uses: actions/setup-node@v3 with: - node-version: '18' + node-version: '18' - - name: Install and Test Frontend + - name: Install Dependencies & Run Tests run: | cd frontend npm install npm test - # Backend Tests + # -------------------- Backend Tests -------------------- + backend-tests: + name: Backend Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.11' + python-version: '3.11' - - name: Install Backend Dependencies and Run Tests + - name: Install Dependencies & Run Tests run: | cd backend python -m pip install --upgrade pip @@ -39,7 +48,15 @@ jobs: pyinstaller main.py --name PictoPy_Server --onedir --distpath dist pytest - # Tauri (Rust) Tests + # -------------------- Tauri (Rust) Tests -------------------- + tauri-tests: + name: Tauri Tests + runs-on: ubuntu-latest + + steps: + - name: Checkout Code + uses: actions/checkout@v3 + - name: Set up Rust uses: actions-rs/toolchain@v1 with: diff --git a/backend/requirements.txt b/backend/requirements.txt index 9f4e32ee..c45325f3 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -58,4 +58,5 @@ uvicorn==0.30.1 watchfiles==0.21.0 websockets==12.0 wsproto==1.2.0 -pyinstaller==6.11 \ No newline at end of file +pyinstaller==6.11 +pytest>=7.0,<8.0 \ No newline at end of file From 77cb040a2515a7397ce90d0c5b51bbe05eb38b57 Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 20:48:23 +0530 Subject: [PATCH 10/22] added fixes --- .github/workflows/ci.yml | 102 +++++++++++++++++++++++++++------ backend/app/database/albums.py | 1 + backend/app/database/images.py | 6 +- backend/requirements.txt | 2 +- 4 files changed, 90 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 068eac79..8ee3116b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,28 +1,92 @@ -name: ci +name: CI for PictoPy + on: - push: + pull_request: branches: - main -permissions: - contents: write + jobs: - deploy: + # Frontend Test Job + frontend: runs-on: ubuntu-latest + name: Frontend Tests steps: - - uses: actions/checkout@v4 - - name: Configure Git Credentials + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install Dependencies & Run Tests run: | - git config user.name github-actions[bot] - git config user.email 41898282+github-actions[bot]@users.noreply.github.com - - uses: actions/setup-python@v5 + cd frontend + npm install + npm test + + # Backend Test Job + backend: + runs-on: ubuntu-latest + name: Backend Tests + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 with: - python-version: 3.x - - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV - - uses: actions/cache@v4 + python-version: '3.11' + + - name: Install Python Dependencies & Run Tests + run: | + cd backend + python -m pip install --upgrade pip + pip install -r requirements.txt + pyinstaller main.py --name PictoPy_Server --onedir --distpath dist + pytest + + # Tauri Test Job + tauri: + runs-on: ubuntu-latest + name: Tauri Tests + steps: + - name: Checkout Code + uses: actions/checkout@v3 + + - name: Install Dependencies for Tauri + run: | + sudo apt-get update -y + sudo apt-get install -y \ + curl build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ + wget xz-utils libssl-dev libglib2.0-dev libgirepository1.0-dev pkg-config \ + software-properties-common libjavascriptcoregtk-4.0-dev libjavascriptcoregtk-4.1-dev \ + libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev file libglib2.0-dev libgl1-mesa-glx + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + override: true + + - name: Set up Node.js + uses: actions/setup-node@v3 with: - key: mkdocs-material-${{ env.cache_id }} - path: .cache - restore-keys: | - mkdocs-material- - - run: pip install mkdocs-material - - run: mkdocs gh-deploy --force + node-version: '18' + + - name: Install Frontend Dependencies + run: | + cd frontend + npm install + + - name: Build Backend for Tauri + run: | + cd backend + python -m pip install --upgrade pip + pip install -r requirements.txt + pyinstaller main.py --name PictoPy_Server --onedir --distpath dist + + - name: Run Tauri Tests + run: | + cd frontend/src-tauri + cargo test \ No newline at end of file diff --git a/backend/app/database/albums.py b/backend/app/database/albums.py index 14bd4e65..ea60d1ab 100644 --- a/backend/app/database/albums.py +++ b/backend/app/database/albums.py @@ -15,6 +15,7 @@ def create_albums_table(): """ CREATE TABLE IF NOT EXISTS albums ( album_name TEXT PRIMARY KEY, + is_hidden BOOLEAN DEFAULT FALSE, image_ids TEXT, description TEXT, date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, diff --git a/backend/app/database/images.py b/backend/app/database/images.py index e42dde28..5f56013d 100644 --- a/backend/app/database/images.py +++ b/backend/app/database/images.py @@ -157,7 +157,11 @@ def get_objects_db(path): class_ids_json = result[0] class_ids = json.loads(class_ids_json) - class_ids = class_ids.split(",") + if isinstance(class_ids, list): + class_ids = [str(class_id) for class_id in class_ids] + else: + class_ids = class_ids.split(",") + conn_mappings = sqlite3.connect(MAPPINGS_DATABASE_PATH) cursor_mappings = conn_mappings.cursor() diff --git a/backend/requirements.txt b/backend/requirements.txt index c45325f3..c7b0e7c4 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -59,4 +59,4 @@ watchfiles==0.21.0 websockets==12.0 wsproto==1.2.0 pyinstaller==6.11 -pytest>=7.0,<8.0 \ No newline at end of file +pytest>=8.2 \ No newline at end of file From 2038383ad66bcf499b57723b7d2dddf56c3c4d45 Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 20:49:33 +0530 Subject: [PATCH 11/22] minor changes --- .github/workflows/ci.yml | 102 ++++++--------------------------- .github/workflows/pr-tests.yml | 56 ++++++++++++------ 2 files changed, 59 insertions(+), 99 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8ee3116b..068eac79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,92 +1,28 @@ -name: CI for PictoPy - +name: ci on: - pull_request: + push: branches: - main - +permissions: + contents: write jobs: - # Frontend Test Job - frontend: + deploy: runs-on: ubuntu-latest - name: Frontend Tests steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Set up Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' - - - name: Install Dependencies & Run Tests + - uses: actions/checkout@v4 + - name: Configure Git Credentials run: | - cd frontend - npm install - npm test - - # Backend Test Job - backend: - runs-on: ubuntu-latest - name: Backend Tests - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Set up Python - uses: actions/setup-python@v4 + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + - uses: actions/setup-python@v5 with: - python-version: '3.11' - - - name: Install Python Dependencies & Run Tests - run: | - cd backend - python -m pip install --upgrade pip - pip install -r requirements.txt - pyinstaller main.py --name PictoPy_Server --onedir --distpath dist - pytest - - # Tauri Test Job - tauri: - runs-on: ubuntu-latest - name: Tauri Tests - steps: - - name: Checkout Code - uses: actions/checkout@v3 - - - name: Install Dependencies for Tauri - run: | - sudo apt-get update -y - sudo apt-get install -y \ - curl build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ - wget xz-utils libssl-dev libglib2.0-dev libgirepository1.0-dev pkg-config \ - software-properties-common libjavascriptcoregtk-4.0-dev libjavascriptcoregtk-4.1-dev \ - libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev file libglib2.0-dev libgl1-mesa-glx - - - name: Set up Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - override: true - - - name: Set up Node.js - uses: actions/setup-node@v3 + python-version: 3.x + - run: echo "cache_id=$(date --utc '+%V')" >> $GITHUB_ENV + - uses: actions/cache@v4 with: - node-version: '18' - - - name: Install Frontend Dependencies - run: | - cd frontend - npm install - - - name: Build Backend for Tauri - run: | - cd backend - python -m pip install --upgrade pip - pip install -r requirements.txt - pyinstaller main.py --name PictoPy_Server --onedir --distpath dist - - - name: Run Tauri Tests - run: | - cd frontend/src-tauri - cargo test \ No newline at end of file + key: mkdocs-material-${{ env.cache_id }} + path: .cache + restore-keys: | + mkdocs-material- + - run: pip install mkdocs-material + - run: mkdocs gh-deploy --force diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index e0eca1da..9cf2f459 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -1,16 +1,15 @@ -name: PR Tests Workflow +name: CI for PictoPy on: pull_request: branches: - - main + - main jobs: - # -------------------- Frontend Tests -------------------- - frontend-tests: - name: Frontend Tests + # Frontend Test Job + frontend: runs-on: ubuntu-latest - + name: Frontend Tests steps: - name: Checkout Code uses: actions/checkout@v3 @@ -26,11 +25,10 @@ jobs: npm install npm test - # -------------------- Backend Tests -------------------- - backend-tests: - name: Backend Tests + # Backend Test Job + backend: runs-on: ubuntu-latest - + name: Backend Tests steps: - name: Checkout Code uses: actions/checkout@v3 @@ -40,7 +38,7 @@ jobs: with: python-version: '3.11' - - name: Install Dependencies & Run Tests + - name: Install Python Dependencies & Run Tests run: | cd backend python -m pip install --upgrade pip @@ -48,22 +46,48 @@ jobs: pyinstaller main.py --name PictoPy_Server --onedir --distpath dist pytest - # -------------------- Tauri (Rust) Tests -------------------- - tauri-tests: - name: Tauri Tests + # Tauri Test Job + tauri: runs-on: ubuntu-latest - + name: Tauri Tests steps: - name: Checkout Code uses: actions/checkout@v3 + - name: Install Dependencies for Tauri + run: | + sudo apt-get update -y + sudo apt-get install -y \ + curl build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ + wget xz-utils libssl-dev libglib2.0-dev libgirepository1.0-dev pkg-config \ + software-properties-common libjavascriptcoregtk-4.0-dev libjavascriptcoregtk-4.1-dev \ + libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev file libglib2.0-dev libgl1-mesa-glx + + - name: Set up Rust uses: actions-rs/toolchain@v1 with: toolchain: stable override: true + - name: Set up Node.js + uses: actions/setup-node@v3 + with: + node-version: '18' + + - name: Install Frontend Dependencies + run: | + cd frontend + npm install + + - name: Build Backend for Tauri + run: | + cd backend + python -m pip install --upgrade pip + pip install -r requirements.txt + pyinstaller main.py --name PictoPy_Server --onedir --distpath dist + - name: Run Tauri Tests run: | cd frontend/src-tauri - cargo test + cargo test \ No newline at end of file From e36ba4867a66ab57b76f2f5501bac75a49042930 Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 20:56:07 +0530 Subject: [PATCH 12/22] fixed failing tests in python --- .github/workflows/pr-tests.yml | 1 + backend/app/database/albums.py | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 9cf2f459..69ec7c15 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -57,6 +57,7 @@ jobs: - name: Install Dependencies for Tauri run: | sudo apt-get update -y + deb http://gb.archive.ubuntu.com/ubuntu jammy main sudo apt-get install -y \ curl build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ wget xz-utils libssl-dev libglib2.0-dev libgirepository1.0-dev pkg-config \ diff --git a/backend/app/database/albums.py b/backend/app/database/albums.py index ea60d1ab..14bd4e65 100644 --- a/backend/app/database/albums.py +++ b/backend/app/database/albums.py @@ -15,7 +15,6 @@ def create_albums_table(): """ CREATE TABLE IF NOT EXISTS albums ( album_name TEXT PRIMARY KEY, - is_hidden BOOLEAN DEFAULT FALSE, image_ids TEXT, description TEXT, date_created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, From da6b75fc4305339b5257a5e8c08a085f45875425 Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 21:05:10 +0530 Subject: [PATCH 13/22] initialising tables --- .github/workflows/pr-tests.yml | 2 +- backend/tests/test_albums.py | 17 +++++++++++++++++ backend/tests/test_images.py | 17 +++++++++++++++++ 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 69ec7c15..944aa3f5 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -57,7 +57,7 @@ jobs: - name: Install Dependencies for Tauri run: | sudo apt-get update -y - deb http://gb.archive.ubuntu.com/ubuntu jammy main + echo "deb http://gb.archive.ubuntu.com/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/gb-ubuntu.list sudo apt-get install -y \ curl build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ wget xz-utils libssl-dev libglib2.0-dev libgirepository1.0-dev pkg-config \ diff --git a/backend/tests/test_albums.py b/backend/tests/test_albums.py index 4d520e40..9f96aa51 100644 --- a/backend/tests/test_albums.py +++ b/backend/tests/test_albums.py @@ -10,11 +10,28 @@ insert_image_db, extract_metadata, ) +from app.database.images import create_image_id_mapping_table, create_images_table +from app.database.albums import create_albums_table +from app.database.yolo_mapping import create_YOLO_mappings +from app.database.faces import cleanup_face_embeddings, create_faces_table +from app.facecluster.init_face_cluster import get_face_cluster, init_face_cluster sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) client = TestClient(app) +@pytest.fixture(scope="session", autouse=True) +def initialize_database_and_services(): + # Run your initialization functions before any tests are executed + create_YOLO_mappings() + create_faces_table() + create_image_id_mapping_table() + create_images_table() + create_albums_table() + cleanup_face_embeddings() + init_face_cluster() + yield + @pytest.fixture(scope="module") def test_images(tmp_path_factory): # Create a temporary directory for test images diff --git a/backend/tests/test_images.py b/backend/tests/test_images.py index 49ab4c98..7bce9f7b 100644 --- a/backend/tests/test_images.py +++ b/backend/tests/test_images.py @@ -4,6 +4,11 @@ import sys import shutil import os +from app.database.images import create_image_id_mapping_table, create_images_table +from app.database.albums import create_albums_table +from app.database.yolo_mapping import create_YOLO_mappings +from app.database.faces import cleanup_face_embeddings, create_faces_table +from app.facecluster.init_face_cluster import get_face_cluster, init_face_cluster # Add the project root to the Python path sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) @@ -13,6 +18,18 @@ client = TestClient(app) +@pytest.fixture(scope="session", autouse=True) +def initialize_database_and_services(): + # Run your initialization functions before any tests are executed + create_YOLO_mappings() + create_faces_table() + create_image_id_mapping_table() + create_images_table() + create_albums_table() + cleanup_face_embeddings() + init_face_cluster() + yield + @pytest.fixture(scope="module") def test_images(tmp_path_factory): # Create a temporary directory for test images From 9f7f4a96105110fbfa4795691790affd1523125d Mon Sep 17 00:00:00 2001 From: rishab Date: Wed, 5 Feb 2025 21:20:05 +0530 Subject: [PATCH 14/22] added more checks in rust --- .github/workflows/pr-tests.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 944aa3f5..d266bf0e 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -57,14 +57,19 @@ jobs: - name: Install Dependencies for Tauri run: | sudo apt-get update -y - echo "deb http://gb.archive.ubuntu.com/ubuntu jammy main" | sudo tee /etc/apt/sources.list.d/gb-ubuntu.list + + echo "deb http://archive.ubuntu.com/ubuntu jammy main universe multiverse" | sudo tee /etc/apt/sources.list.d/ubuntu-jammy.list + + echo "deb http://security.ubuntu.com/ubuntu jammy-security main universe multiverse" | sudo tee -a /etc/apt/sources.list.d/ubuntu-jammy-security.list + + sudo apt-get update -y + sudo apt-get install -y \ curl build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libappindicator3-dev \ wget xz-utils libssl-dev libglib2.0-dev libgirepository1.0-dev pkg-config \ software-properties-common libjavascriptcoregtk-4.0-dev libjavascriptcoregtk-4.1-dev \ libsoup-3.0-dev libwebkit2gtk-4.1-dev librsvg2-dev file libglib2.0-dev libgl1-mesa-glx - - name: Set up Rust uses: actions-rs/toolchain@v1 with: From faa1d682c2b2c2e2c984038264c136ea7ce19d1b Mon Sep 17 00:00:00 2001 From: rahulharpal1603 Date: Fri, 7 Feb 2025 18:42:16 +0530 Subject: [PATCH 15/22] Update GitHub Actions workflow to trigger on pull requests and specify paths for backend and frontend --- .github/workflows/merge.yml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index f2bcee4c..fdcf8bdb 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -3,8 +3,12 @@ name: Merge Check on: pull_request_review: - # Trigger this workflow when a pull request review is submitted + # Trigger this workflow when a pull request review is submitted types: [submitted] + pull_request: + paths: + - "backend/**" + - "frontend/**" jobs: build-tauri: @@ -27,22 +31,22 @@ jobs: uses: actions/setup-node@v4 with: node-version: lts/* - cache: 'npm' - + cache: "npm" + - name: Install Rust uses: dtolnay/rust-toolchain@stable - name: Rust cache uses: swatinem/rust-cache@v2 with: - # Cache Rust build artifacts for faster build times - workspaces: './src-tauri -> target' - + # Cache Rust build artifacts for faster build times + workspaces: "./src-tauri -> target" + - name: Install frontend dependencies run: | cd frontend npm install - + - name: Build Tauri uses: tauri-apps/tauri-action@v0 with: @@ -51,4 +55,4 @@ jobs: env: # Use secrets for signing the Tauri app TAURI_SIGNING_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5NlF2SjE3cWNXOVlQQ0JBTlNITEpOUVoyQ3ZuNTdOSkwyNE1NN2RmVWQ1a0FBQkFBQUFBQUFBQUFBQUlBQUFBQU9XOGpTSFNRd0Q4SjNSbm5Oc1E0OThIUGx6SS9lWXI3ZjJxN3BESEh1QTRiQXlkR2E5aG1oK1g0Tk5kcmFzc0IvZFZScEpubnptRkxlbDlUR2R1d1Y5OGRSYUVmUGoxNTFBcHpQZ1dSS2lHWklZVHNkV1Byd1VQSnZCdTZFWlVGOUFNVENBRlgweUU9Cg== - TAURI_SIGNING_PRIVATE_KEY_PASSWORD : pass + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: pass From a300d43b62f87c9360ea3e5a4765a483538e3cb4 Mon Sep 17 00:00:00 2001 From: rahulharpal1603 Date: Sat, 8 Feb 2025 01:10:00 +0530 Subject: [PATCH 16/22] Add PR checks for windows and mac --- .github/workflows/merge.yml | 75 +++++++++++-------- frontend/.eslintrc.json | 1 + frontend/babel.config.cjs | 8 +- frontend/index.html | 1 - frontend/src-tauri/capabilities/migrated.json | 32 ++------ frontend/src-tauri/tauri.conf.json | 12 +-- frontend/src/App.tsx | 1 - frontend/src/Config/Backend.ts | 3 +- .../components/AITagging/FilterControls.tsx | 35 ++++----- frontend/src/components/Album/AlbumCard.tsx | 2 +- frontend/src/components/Album/AlbumList.tsx | 4 +- .../FolderPicker/DeleteSelectedImagePage.tsx | 44 +++++------ .../src/components/Media/MediaGallery.tsx | 2 +- .../src/components/Media/SortningControls.tsx | 2 +- .../components/Navigation/Navbar/Navbar.tsx | 28 +++---- .../src/components/ui/PaginationControls.tsx | 7 +- frontend/src/components/ui/button.tsx | 3 +- frontend/src/components/ui/pagination.tsx | 5 +- frontend/src/hooks/useImages.ts | 4 +- frontend/src/types/Album.ts | 2 +- frontend/src/utils/isProd.ts | 2 +- frontend/tailwind.config.cjs | 1 - frontend/tailwind.config.js | 3 +- 23 files changed, 125 insertions(+), 152 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index f2bcee4c..7ca1dcf0 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -1,54 +1,63 @@ -# This workflow is named "Merge Check" and gets triggered on pull request reviews -name: Merge Check +name: "PR Check for PictoPy" on: pull_request_review: - # Trigger this workflow when a pull request review is submitted + # Trigger this workflow when a pull request review is submitted types: [submitted] + pull_request: + paths: + - "backend/**" + - "frontend/**" +# This workflow will build your tauri app without uploading it anywhere. jobs: - build-tauri: - permissions: - # Grant write permission to repository contents - contents: write + test-tauri: strategy: - # Stop all parallel jobs on first failure - fail-fast: true - runs-on: ubuntu-22.04 + fail-fast: false + matrix: + include: + - platform: "macos-latest" # for Arm based macs (M1 and above). + args: "--target aarch64-apple-darwin" + - platform: "ubuntu-22.04" + args: "" + - platform: "windows-latest" + args: "" + + runs-on: ${{ matrix.platform }} steps: - uses: actions/checkout@v4 - # Check out the repository code - - name: Install system dependencies - run: | - sudo apt-get update - sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf - - name: Setup Node.js + - name: setup node uses: actions/setup-node@v4 with: node-version: lts/* - cache: 'npm' - - - name: Install Rust - uses: dtolnay/rust-toolchain@stable - - name: Rust cache - uses: swatinem/rust-cache@v2 + - name: install Rust stable + uses: dtolnay/rust-toolchain@stable with: - # Cache Rust build artifacts for faster build times - workspaces: './src-tauri -> target' - - - name: Install frontend dependencies + # Those targets are only used on macos runners so it's in an `if` to slightly speed up windows and linux builds. + targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }} + + - name: install dependencies (ubuntu only) + if: matrix.platform == 'ubuntu-22.04' # This must match the platform value defined above. run: | - cd frontend - npm install - - - name: Build Tauri - uses: tauri-apps/tauri-action@v0 + sudo apt-get update + sudo apt-get install -y libwebkit2gtk-4.0-dev libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf + # webkitgtk 4.0 is for Tauri v1 - webkitgtk 4.1 is for Tauri v2. + # You can remove the one that doesn't apply to your app to speed up the workflow a bit. + + - name: install frontend dependencies + run: yarn install # change this to npm, pnpm or bun depending on which one you use. + + # If tagName and releaseId are omitted tauri-action will only build the app and won't try to upload any assets. + - uses: tauri-apps/tauri-action@v0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: + args: ${{ matrix.args }} # Run the Tauri build in the frontend directory projectPath: frontend - env: + env: # Use secrets for signing the Tauri app TAURI_SIGNING_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5NlF2SjE3cWNXOVlQQ0JBTlNITEpOUVoyQ3ZuNTdOSkwyNE1NN2RmVWQ1a0FBQkFBQUFBQUFBQUFBQUlBQUFBQU9XOGpTSFNRd0Q4SjNSbm5Oc1E0OThIUGx6SS9lWXI3ZjJxN3BESEh1QTRiQXlkR2E5aG1oK1g0Tk5kcmFzc0IvZFZScEpubnptRkxlbDlUR2R1d1Y5OGRSYUVmUGoxNTFBcHpQZ1dSS2lHWklZVHNkV1Byd1VQSnZCdTZFWlVGOUFNVENBRlgweUU9Cg== - TAURI_SIGNING_PRIVATE_KEY_PASSWORD : pass + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: pass diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 7b6ebc97..d57fbde7 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -1,5 +1,6 @@ { "extends": ["react-app", "eslint-config-prettier"], + "ignorePatterns": ["src-tauri/target"], "rules": { // Accessibility rules "jsx-a11y/click-events-have-key-events": "off", diff --git a/frontend/babel.config.cjs b/frontend/babel.config.cjs index c40a8d7f..e3901107 100644 --- a/frontend/babel.config.cjs +++ b/frontend/babel.config.cjs @@ -1,7 +1,7 @@ module.exports = { presets: [ - ["@babel/preset-env", { targets: { node: "current" } }], - "@babel/preset-typescript", - ["@babel/preset-react", { runtime: "automatic" }], + ['@babel/preset-env', { targets: { node: 'current' } }], + '@babel/preset-typescript', + ['@babel/preset-react', { runtime: 'automatic' }], ], -} \ No newline at end of file +}; diff --git a/frontend/index.html b/frontend/index.html index bf3975be..ff93803b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -5,7 +5,6 @@ Tauri + React + Typescript - diff --git a/frontend/src-tauri/capabilities/migrated.json b/frontend/src-tauri/capabilities/migrated.json index 134ea5d6..4bb1bd7c 100644 --- a/frontend/src-tauri/capabilities/migrated.json +++ b/frontend/src-tauri/capabilities/migrated.json @@ -2,9 +2,7 @@ "identifier": "migrated", "description": "permissions that were migrated from v1", "local": true, - "windows": [ - "main" - ], + "windows": ["main"], "permissions": [ "path:default", "event:default", @@ -80,15 +78,11 @@ "fs:write-files", { "identifier": "fs:scope", - "allow": [ - "**" - ] + "allow": ["**"] }, { "identifier": "fs:read-all", - "allow": [ - "**" - ] + "allow": ["**"] }, "shell:default", "dialog:default", @@ -99,10 +93,7 @@ { "name": "StartServerUnix", "cmd": "bash", - "args": [ - "-c", - "./PictoPy_Server" - ], + "args": ["-c", "./PictoPy_Server"], "sidecar": false } ] @@ -113,9 +104,7 @@ { "name": "StartServerWindows", "cmd": "powershell", - "args": [ - "./PictoPy_Server" - ], + "args": ["./PictoPy_Server"], "sidecar": false } ] @@ -126,10 +115,7 @@ { "name": "killProcessUnix", "cmd": "bash", - "args": [ - "-c", - "pkill -f PictoPy_Server" - ], + "args": ["-c", "pkill -f PictoPy_Server"], "sidecar": false } ] @@ -140,12 +126,10 @@ { "name": "killProcessWindows", "cmd": "powershell", - "args": [ - "taskkill /t /f /im PictoPy_Server.exe" - ], + "args": ["taskkill /t /f /im PictoPy_Server.exe"], "sidecar": false } ] } ] -} \ No newline at end of file +} diff --git a/frontend/src-tauri/tauri.conf.json b/frontend/src-tauri/tauri.conf.json index 9cda0414..79c3645c 100644 --- a/frontend/src-tauri/tauri.conf.json +++ b/frontend/src-tauri/tauri.conf.json @@ -7,11 +7,7 @@ }, "bundle": { "active": true, - "targets": [ - "nsis", - "deb", - "dmg" - ], + "targets": ["nsis", "deb", "dmg"], "linux": { "deb": { "postInstallScript": "./postinstall.sh" @@ -49,12 +45,10 @@ ], "security": { "assetProtocol": { - "scope": [ - "**" - ], + "scope": ["**"], "enable": true }, "csp": null } } -} \ No newline at end of file +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 4b35d2dd..5d7fb801 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -16,5 +16,4 @@ const App: React.FC = () => { ); }; - export default App; diff --git a/frontend/src/Config/Backend.ts b/frontend/src/Config/Backend.ts index be3dfc64..3cf7aee0 100644 --- a/frontend/src/Config/Backend.ts +++ b/frontend/src/Config/Backend.ts @@ -1,2 +1 @@ -export const BACKED_URL = - process.env.BACKEND_URL || 'http://localhost:8000'; +export const BACKED_URL = process.env.BACKEND_URL || 'http://localhost:8000'; diff --git a/frontend/src/components/AITagging/FilterControls.tsx b/frontend/src/components/AITagging/FilterControls.tsx index 2c440999..3df44cca 100644 --- a/frontend/src/components/AITagging/FilterControls.tsx +++ b/frontend/src/components/AITagging/FilterControls.tsx @@ -13,10 +13,7 @@ import FolderPicker from '../FolderPicker/FolderPicker'; import LoadingScreen from '../ui/LoadingScreen/LoadingScreen'; import DeleteSelectedImagePage from '../FolderPicker/DeleteSelectedImagePage'; import ErrorDialog from '../Album/Error'; -import { - Trash2, - Filter, -} from 'lucide-react'; +import { Trash2, Filter } from 'lucide-react'; import { queryClient, usePictoMutation } from '@/hooks/useQueryExtensio'; import { addFolder } from '../../../api/api-functions/images'; @@ -77,24 +74,23 @@ export default function FilterControls({ setSelectedFlags(updatedFlags); }; - - const handleFilterFlag = ()=>{ - let flags : string[] = []; - if(selectedFlags[0].isChecked) { + const handleFilterFlag = () => { + let flags: string[] = []; + if (selectedFlags[0].isChecked) { setFilterTag([]); return; } - selectedFlags.forEach((ele)=>{ - if(ele.isChecked) flags.push(ele.tag); - }) + selectedFlags.forEach((ele) => { + if (ele.isChecked) flags.push(ele.tag); + }); - console.log("Updated Filter Flags = ",flags); + console.log('Updated Filter Flags = ', flags); setFilterTag(flags); - } + }; - const handleToggleDropdown = (event:Event) => { + const handleToggleDropdown = (event: Event) => { event.preventDefault(); - setIsDropdownOpen((prevState) => !prevState); // Toggle dropdown visibility + setIsDropdownOpen((prevState) => !prevState); // Toggle dropdown visibility }; const handleFolderPick = async (path: string) => { try { @@ -153,7 +149,7 @@ export default function FilterControls({ @@ -138,7 +132,7 @@ const DeleteSelectedImagePage: React.FC = ({ handleFilterTag(value)} + onValueChange={(value) => handleFilterTag(value)} > = ({ /> {`Image
diff --git a/frontend/src/components/Media/MediaGallery.tsx b/frontend/src/components/Media/MediaGallery.tsx index fed4ddf4..6247cdbe 100644 --- a/frontend/src/components/Media/MediaGallery.tsx +++ b/frontend/src/components/Media/MediaGallery.tsx @@ -74,7 +74,7 @@ export default function MediaGallery({ type={type} /> {totalPages >= 1 && ( -
+
= ({ ); }; -export default SortingControls \ No newline at end of file +export default SortingControls; diff --git a/frontend/src/components/Navigation/Navbar/Navbar.tsx b/frontend/src/components/Navigation/Navbar/Navbar.tsx index 67859931..2715ba32 100644 --- a/frontend/src/components/Navigation/Navbar/Navbar.tsx +++ b/frontend/src/components/Navigation/Navbar/Navbar.tsx @@ -1,5 +1,5 @@ -import React, { useState, useEffect, useCallback } from "react"; -import { ThemeToggle } from "@/components/ThemeToggle"; +import React, { useState, useEffect, useCallback } from 'react'; +import { ThemeToggle } from '@/components/ThemeToggle'; interface NavbarProps { title?: string; @@ -8,12 +8,12 @@ interface NavbarProps { export function Navbar({ title, onNameChange }: NavbarProps) { const [isEditing, setIsEditing] = useState(false); - const [name, setName] = useState(title || ""); + const [name, setName] = useState(title || ''); const [showPlaceholder, setShowPlaceholder] = useState(!title); // Handle initial load and localStorage useEffect(() => { - const storedName = localStorage.getItem("pictopy-username"); + const storedName = localStorage.getItem('pictopy-username'); if (storedName) { setName(storedName); setShowPlaceholder(false); @@ -22,18 +22,18 @@ export function Navbar({ title, onNameChange }: NavbarProps) { const handleNameSubmit = useCallback( (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { const inputValue = (e.target as HTMLInputElement).value.trim(); if (inputValue) { setName(inputValue); setShowPlaceholder(false); setIsEditing(false); - localStorage.setItem("pictopy-username", inputValue); + localStorage.setItem('pictopy-username', inputValue); onNameChange?.(inputValue); } } }, - [onNameChange] + [onNameChange], ); const handleNameClick = useCallback(() => { @@ -48,17 +48,17 @@ export function Navbar({ title, onNameChange }: NavbarProps) { if (inputValue) { setName(inputValue); setShowPlaceholder(false); - localStorage.setItem("pictopy-username", inputValue); + localStorage.setItem('pictopy-username', inputValue); onNameChange?.(inputValue); } setIsEditing(false); }, - [onNameChange] + [onNameChange], ); return (
-
+
{/* Logo Section */}
@@ -67,7 +67,7 @@ export function Navbar({ title, onNameChange }: NavbarProps) { className="h-7 transition-opacity duration-200 hover:opacity-80" alt="PictoPy Logo" /> - + PictoPy
@@ -77,7 +77,7 @@ export function Navbar({ title, onNameChange }: NavbarProps) {
- Welcome{" "} + Welcome{' '} {isEditing || showPlaceholder ? ( - {name || "User"} + {name || 'User'} )}
diff --git a/frontend/src/components/ui/PaginationControls.tsx b/frontend/src/components/ui/PaginationControls.tsx index 92a8b9b1..98272f36 100644 --- a/frontend/src/components/ui/PaginationControls.tsx +++ b/frontend/src/components/ui/PaginationControls.tsx @@ -64,11 +64,14 @@ export default function PaginationControls({ {getPageNumbers().map((page, index) => page === '...' ? ( - {page} + + {page} + ) : ( - onPageChange(Number(page))} > diff --git a/frontend/src/components/ui/button.tsx b/frontend/src/components/ui/button.tsx index e244184f..a442d8cd 100644 --- a/frontend/src/components/ui/button.tsx +++ b/frontend/src/components/ui/button.tsx @@ -19,8 +19,7 @@ const buttonVariants = cva( 'bg-secondary text-secondary-foreground hover:bg-secondary/80 active:bg-secondary/70 focus:ring-secondary dark:bg-secondary-dark dark:text-secondary-foreground-dark dark:hover:bg-secondary-dark/80 dark:active:bg-secondary-dark/70', ghost: 'hover:bg-accent hover:text-accent-foreground active:bg-accent/90 focus:ring-accent dark:hover:bg-accent-dark dark:text-accent-foreground-dark', - link: - 'text-primary underline-offset-4 hover:underline active:text-primary/80 focus:ring-primary dark:text-primary-dark dark:hover:underline', + link: 'text-primary underline-offset-4 hover:underline active:text-primary/80 focus:ring-primary dark:text-primary-dark dark:hover:underline', }, size: { default: 'h-10 px-4 py-2', diff --git a/frontend/src/components/ui/pagination.tsx b/frontend/src/components/ui/pagination.tsx index 6fdc297c..b16851b0 100644 --- a/frontend/src/components/ui/pagination.tsx +++ b/frontend/src/components/ui/pagination.tsx @@ -70,7 +70,7 @@ const PaginationPrevious = ({ {...props} > - Previous + Previous ); PaginationPrevious.displayName = 'PaginationPrevious'; @@ -85,7 +85,7 @@ const PaginationNext = ({ className={cn('gap-1 pr-2.5', className)} {...props} > - Next + Next ); @@ -115,4 +115,3 @@ export { PaginationNext, PaginationPrevious, }; - diff --git a/frontend/src/hooks/useImages.ts b/frontend/src/hooks/useImages.ts index 2e52a2a6..c594cd28 100644 --- a/frontend/src/hooks/useImages.ts +++ b/frontend/src/hooks/useImages.ts @@ -11,10 +11,9 @@ interface ImageData { title: string; date: string; tags: string[]; - imagePath:string; + imagePath: string; } - interface ResponseData { [year: string]: { [month: string]: string[]; @@ -112,7 +111,6 @@ export const useImages = (folderPath: string) => { title: `Image ${imagePath}`, date, tags: [], - }; }), ); diff --git a/frontend/src/types/Album.ts b/frontend/src/types/Album.ts index 415a08f6..3bf85a93 100644 --- a/frontend/src/types/Album.ts +++ b/frontend/src/types/Album.ts @@ -112,4 +112,4 @@ export interface AlbumCredentials { export type WithPassword = T & { password?: string; -}; \ No newline at end of file +}; diff --git a/frontend/src/utils/isProd.ts b/frontend/src/utils/isProd.ts index 93956dae..4022e272 100644 --- a/frontend/src/utils/isProd.ts +++ b/frontend/src/utils/isProd.ts @@ -1,3 +1,3 @@ // Usage: import { isProd } from '@/utils/isProd'; // Utility function to check if the environment is in production mode -export const isProd = () =>import.meta.env.PROD; +export const isProd = () => import.meta.env.PROD; diff --git a/frontend/tailwind.config.cjs b/frontend/tailwind.config.cjs index e4bad266..fe12db56 100644 --- a/frontend/tailwind.config.cjs +++ b/frontend/tailwind.config.cjs @@ -75,4 +75,3 @@ module.exports = { }, plugins: [require('tailwindcss-animate')], }; - diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index edbc16f2..892a9301 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -63,7 +63,8 @@ module.exports = { boxShadow: { lg: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', xl: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', - darkLg: '0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.3)', + darkLg: + '0 10px 15px -3px rgba(0, 0, 0, 0.5), 0 4px 6px -2px rgba(0, 0, 0, 0.3)', }, keyframes: { 'accordion-down': { From 4e96e828f1ba7dd3e0da1280db7960b06798c1e3 Mon Sep 17 00:00:00 2001 From: rahulharpal1603 Date: Sat, 8 Feb 2025 01:15:41 +0530 Subject: [PATCH 17/22] workflow error fix --- .github/workflows/merge.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index 7ca1dcf0..b5874496 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -53,11 +53,9 @@ jobs: - uses: tauri-apps/tauri-action@v0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + TAURI_SIGNING_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5NlF2SjE3cWNXOVlQQ0JBTlNITEpOUVoyQ3ZuNTdOSkwyNE1NN2RmVWQ1a0FBQkFBQUFBQUFBQUFBQUlBQUFBQU9XOGpTSFNRd0Q4SjNSbm5Oc1E0OThIUGx6SS9lWXI3ZjJxN3BESEh1QTRiQXlkR2E5aG1oK1g0Tk5kcmFzc0IvZFZScEpubnptRkxlbDlUR2R1d1Y5OGRSYUVmUGoxNTFBcHpQZ1dSS2lHWklZVHNkV1Byd1VQSnZCdTZFWlVGOUFNVENBRlgweUU9Cg== + TAURI_SIGNING_PRIVATE_KEY_PASSWORD: pass with: args: ${{ matrix.args }} # Run the Tauri build in the frontend directory projectPath: frontend - env: - # Use secrets for signing the Tauri app - TAURI_SIGNING_PRIVATE_KEY: dW50cnVzdGVkIGNvbW1lbnQ6IHJzaWduIGVuY3J5cHRlZCBzZWNyZXQga2V5ClJXUlRZMEl5NlF2SjE3cWNXOVlQQ0JBTlNITEpOUVoyQ3ZuNTdOSkwyNE1NN2RmVWQ1a0FBQkFBQUFBQUFBQUFBQUlBQUFBQU9XOGpTSFNRd0Q4SjNSbm5Oc1E0OThIUGx6SS9lWXI3ZjJxN3BESEh1QTRiQXlkR2E5aG1oK1g0Tk5kcmFzc0IvZFZScEpubnptRkxlbDlUR2R1d1Y5OGRSYUVmUGoxNTFBcHpQZ1dSS2lHWklZVHNkV1Byd1VQSnZCdTZFWlVGOUFNVENBRlgweUU9Cg== - TAURI_SIGNING_PRIVATE_KEY_PASSWORD: pass From 1eba3dae7a008745872eb9a524f961f7a2e1aa6c Mon Sep 17 00:00:00 2001 From: rahulharpal1603 Date: Sat, 8 Feb 2025 01:17:50 +0530 Subject: [PATCH 18/22] fix projectPath in workflow --- .github/workflows/merge.yml | 2 +- frontend/src/components/Media/MediaView.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/merge.yml b/.github/workflows/merge.yml index b5874496..4c417f7b 100644 --- a/.github/workflows/merge.yml +++ b/.github/workflows/merge.yml @@ -58,4 +58,4 @@ jobs: with: args: ${{ matrix.args }} # Run the Tauri build in the frontend directory - projectPath: frontend + projectPath: ./frontend diff --git a/frontend/src/components/Media/MediaView.tsx b/frontend/src/components/Media/MediaView.tsx index 6d2f8a06..118cdda5 100644 --- a/frontend/src/components/Media/MediaView.tsx +++ b/frontend/src/components/Media/MediaView.tsx @@ -472,7 +472,7 @@ const MediaView: React.FC = ({
- {type == 'image' ? ( + {type === 'image' ? (