Skip to content

Commit aeb3924

Browse files
Add Toggle favourite button
1 parent cec1303 commit aeb3924

File tree

22 files changed

+368
-118
lines changed

22 files changed

+368
-118
lines changed

__pycache__/app.cpython-313.pyc

591 Bytes
Binary file not shown.

backend/app/database/images.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def db_create_images_table() -> None:
6363
thumbnailPath TEXT UNIQUE,
6464
metadata TEXT,
6565
isTagged BOOLEAN DEFAULT 0,
66+
isFavourite BOOLEAN DEFAULT 0,
6667
FOREIGN KEY (folder_id) REFERENCES folders(folder_id) ON DELETE CASCADE
6768
)
6869
"""
@@ -143,6 +144,7 @@ def db_get_all_images(tagged: Union[bool, None] = None) -> List[dict]:
143144
i.thumbnailPath,
144145
i.metadata,
145146
i.isTagged,
147+
i.isFavourite,
146148
m.name as tag_name
147149
FROM images i
148150
LEFT JOIN image_classes ic ON i.id = ic.image_id
@@ -169,6 +171,7 @@ def db_get_all_images(tagged: Union[bool, None] = None) -> List[dict]:
169171
thumbnail_path,
170172
metadata,
171173
is_tagged,
174+
is_favourite,
172175
tag_name,
173176
) in results:
174177
if image_id not in images_dict:
@@ -184,6 +187,7 @@ def db_get_all_images(tagged: Union[bool, None] = None) -> List[dict]:
184187
"thumbnailPath": thumbnail_path,
185188
"metadata": metadata_dict,
186189
"isTagged": bool(is_tagged),
190+
"isFavourite": bool(is_favourite),
187191
"tags": [],
188192
}
189193

@@ -390,3 +394,28 @@ def db_delete_images_by_ids(image_ids: List[ImageId]) -> bool:
390394
return False
391395
finally:
392396
conn.close()
397+
398+
399+
def db_toggle_image_favourite_status(image_id: str) -> bool:
400+
conn = sqlite3.connect(DATABASE_PATH)
401+
cursor = conn.cursor()
402+
try:
403+
cursor.execute("SELECT id FROM images WHERE id = ?", (image_id,))
404+
if not cursor.fetchone():
405+
return False
406+
cursor.execute(
407+
"""
408+
UPDATE images
409+
SET isFavourite = CASE WHEN isFavourite = 1 THEN 0 ELSE 1 END
410+
WHERE id = ?
411+
""",
412+
(image_id,),
413+
)
414+
conn.commit()
415+
return cursor.rowcount > 0
416+
except Exception as e:
417+
logger.error(f"Database error: {e}")
418+
conn.rollback()
419+
return False
420+
finally:
421+
conn.close()

backend/app/routes/images.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
from app.schemas.images import ErrorResponse
55
from app.utils.images import image_util_parse_metadata
66
from pydantic import BaseModel
7+
from app.database.images import db_toggle_image_favourite_status
8+
from app.logging.setup_logging import get_logger
79

10+
# Initialize logger
11+
logger = get_logger(__name__)
812
router = APIRouter()
913

1014

@@ -29,6 +33,7 @@ class ImageData(BaseModel):
2933
thumbnailPath: str
3034
metadata: MetadataModel
3135
isTagged: bool
36+
isFavourite: bool
3237
tags: Optional[List[str]] = None
3338

3439

@@ -60,6 +65,7 @@ def get_all_images(
6065
thumbnailPath=image["thumbnailPath"],
6166
metadata=image_util_parse_metadata(image["metadata"]),
6267
isTagged=image["isTagged"],
68+
isFavourite=image.get("isFavourite", False),
6369
tags=image["tags"],
6470
)
6571
for image in images
@@ -80,3 +86,45 @@ def get_all_images(
8086
message=f"Unable to retrieve images: {str(e)}",
8187
).model_dump(),
8288
)
89+
90+
91+
# adding add to favourite and remove from favourite routes
92+
93+
94+
class ToggleFavouriteRequest(BaseModel):
95+
image_id: str
96+
97+
98+
@router.post("/toggle-favourite")
99+
def toggle_favourite(req: ToggleFavouriteRequest):
100+
image_id = req.image_id
101+
try:
102+
success = db_toggle_image_favourite_status(image_id)
103+
if not success:
104+
raise HTTPException(
105+
status_code=404, detail="Image not found or failed to toggle"
106+
)
107+
# Fetch updated status to return
108+
image = next(
109+
(img for img in db_get_all_images() if img["id"] == image_id), None
110+
)
111+
return {
112+
"success": True,
113+
"image_id": image_id,
114+
"isFavourite": image.get("isFavourite", False),
115+
}
116+
117+
except Exception as e:
118+
logger.error(f"error in /toggle-favourite route: {e}")
119+
raise HTTPException(status_code=500, detail=f"Internal server error: {e}")
120+
121+
122+
class ImageInfoResponse(BaseModel):
123+
id: str
124+
path: str
125+
folder_id: str
126+
thumbnailPath: str
127+
metadata: MetadataModel
128+
isTagged: bool
129+
isFavourite: bool
130+
tags: Optional[List[str]] = None

backend/run.bat

Lines changed: 0 additions & 11 deletions
This file was deleted.

docs/backend/backend_python/openapi.json

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,45 @@
887887
}
888888
}
889889
},
890+
"/images/toggle-favourite": {
891+
"post": {
892+
"tags": [
893+
"Images"
894+
],
895+
"summary": "Toggle Favourite",
896+
"operationId": "toggle_favourite_images_toggle_favourite_post",
897+
"requestBody": {
898+
"content": {
899+
"application/json": {
900+
"schema": {
901+
"$ref": "#/components/schemas/ToggleFavouriteRequest"
902+
}
903+
}
904+
},
905+
"required": true
906+
},
907+
"responses": {
908+
"200": {
909+
"description": "Successful Response",
910+
"content": {
911+
"application/json": {
912+
"schema": {}
913+
}
914+
}
915+
},
916+
"422": {
917+
"description": "Validation Error",
918+
"content": {
919+
"application/json": {
920+
"schema": {
921+
"$ref": "#/components/schemas/HTTPValidationError"
922+
}
923+
}
924+
}
925+
}
926+
}
927+
}
928+
},
890929
"/face-clusters/{cluster_id}": {
891930
"put": {
892931
"tags": [
@@ -2094,6 +2133,10 @@
20942133
"type": "boolean",
20952134
"title": "Istagged"
20962135
},
2136+
"isFavourite": {
2137+
"type": "boolean",
2138+
"title": "Isfavourite"
2139+
},
20972140
"tags": {
20982141
"anyOf": [
20992142
{
@@ -2116,7 +2159,8 @@
21162159
"folder_id",
21172160
"thumbnailPath",
21182161
"metadata",
2119-
"isTagged"
2162+
"isTagged",
2163+
"isFavourite"
21202164
],
21212165
"title": "ImageData"
21222166
},
@@ -2506,6 +2550,19 @@
25062550
],
25072551
"title": "SyncFolderResponse"
25082552
},
2553+
"ToggleFavouriteRequest": {
2554+
"properties": {
2555+
"image_id": {
2556+
"type": "string",
2557+
"title": "Image Id"
2558+
}
2559+
},
2560+
"type": "object",
2561+
"required": [
2562+
"image_id"
2563+
],
2564+
"title": "ToggleFavouriteRequest"
2565+
},
25092566
"UpdateAITaggingData": {
25102567
"properties": {
25112568
"updated_count": {

frontend/scripts/setup_env.sh

100755100644
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,4 @@ sudo apt-get clean
115115
sudo rm -rf /var/lib/apt/lists/*
116116

117117
echo -e "${GREEN}All required tools and libraries are installed!${NC}"
118-
echo -e "${YELLOW}Note: You may need to restart your terminal or run 'source ~/.bashrc' to use the installed tools.${NC}"
118+
echo -e "${YELLOW}Note: You may need to restart your terminal or run 'source ~/.bashrc' to use the installed tools.${NC}"

frontend/scripts/setup_win.ps1

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,4 +70,4 @@ if (-not (Test-Command cmake)) {
7070
$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")
7171

7272
Write-Host "All required tools and libraries are installed!" -ForegroundColor Green
73-
Write-Host "Please restart your computer to ensure all changes take effect." -ForegroundColor Yellow
73+
Write-Host "Please restart your computer to ensure all changes take effect." -ForegroundColor Yellow
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { imagesEndpoints } from '../apiEndpoints';
2+
import { apiClient } from '../axiosConfig';
3+
import { APIResponse } from '@/types/API';
4+
5+
export const togglefav = async (image_id: string): Promise<APIResponse> => {
6+
const response = await apiClient.post<APIResponse>(
7+
imagesEndpoints.setFavourite,
8+
{ image_id },
9+
);
10+
return response.data;
11+
};

frontend/src/api/apiEndpoints.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
export const imagesEndpoints = {
22
getAllImages: '/images/',
3+
setFavourite: '/images/toggle-favourite',
34
};
45

56
export const faceClustersEndpoints = {

frontend/src/components/Media/ImageCard.tsx

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ import { AspectRatio } from '@/components/ui/aspect-ratio';
22
import { Button } from '@/components/ui/button';
33
import { cn } from '@/lib/utils';
44
import { Check, Heart, Share2 } from 'lucide-react';
5-
import { useState } from 'react';
5+
import { useCallback, useState } from 'react';
66
import { Image } from '@/types/Media';
77
import { ImageTags } from './ImageTags';
88
import { convertFileSrc } from '@tauri-apps/api/core';
9+
import { useToggleFav } from '@/hooks/useToggleFav';
910

1011
interface ImageCardViewProps {
1112
image: Image;
@@ -23,12 +24,16 @@ export function ImageCard({
2324
showTags = true,
2425
onClick,
2526
}: ImageCardViewProps) {
26-
const [isFavorite, setIsFavorite] = useState(false);
2727
const [isImageHovered, setIsImageHovered] = useState(false);
28-
2928
// Default to empty array if no tags are provided
3029
const tags = image.tags || [];
30+
const { toggleFavourite } = useToggleFav();
3131

32+
const handleToggleFavourite = useCallback(() => {
33+
if (image?.id) {
34+
toggleFavourite(image.id);
35+
}
36+
}, [image, toggleFavourite]);
3237
return (
3338
<div
3439
className={cn(
@@ -67,22 +72,25 @@ export function ImageCard({
6772
<Button
6873
variant="ghost"
6974
size="icon"
70-
className="cursor-pointer rounded-full bg-white/20 text-white hover:!bg-white/40 hover:!text-white"
75+
className={`cursor-pointer rounded-full p-2.5 text-white transition-all duration-300 ${
76+
image.isFavourite
77+
? 'bg-rose-500/80 hover:bg-rose-600 hover:shadow-lg'
78+
: 'bg-white/10 hover:bg-white/20 hover:shadow-lg'
79+
}`}
7180
onClick={(e) => {
81+
console.log(image);
7282
e.stopPropagation();
73-
setIsFavorite(!isFavorite);
83+
handleToggleFavourite();
7484
}}
75-
title={isFavorite ? 'Remove from favorites' : 'Add to favorites'}
76-
aria-label="Add to Favorites"
7785
>
78-
<Heart
79-
className={cn(
80-
'h-5 w-5',
81-
isFavorite ? 'fill-brand-orange text-brand-orange' : '',
82-
)}
83-
/>
84-
<span className="sr-only">Favorite</span>
86+
{image.isFavourite ? (
87+
<Heart className="h-5 w-5" fill="currentColor"></Heart>
88+
) : (
89+
<Heart className="h-5 w-5" />
90+
)}
91+
<span className="sr-only">Favourite</span>
8592
</Button>
93+
8694
<Button
8795
variant="ghost"
8896
size="icon"

0 commit comments

Comments
 (0)