feat: Enhance Layout and Search components with dynamic page titles and media type filtering
This commit introduces a new function in the Layout component to dynamically set page titles based on the current route, improving user navigation. Additionally, the Search component has been updated to include a media type filter, allowing users to filter results by images or videos. The UI has been enhanced with collapsible filters for better organization. Documentation has been updated to reflect these changes.
This commit is contained in:
@@ -66,6 +66,7 @@ def search_photos(
|
||||
date_from: Optional[str] = Query(None, description="Date from (YYYY-MM-DD)"),
|
||||
date_to: Optional[str] = Query(None, description="Date to (YYYY-MM-DD)"),
|
||||
folder_path: Optional[str] = Query(None, description="Filter by folder path"),
|
||||
media_type: Optional[str] = Query(None, description="Filter by media type: 'all', 'image', or 'video'"),
|
||||
page: int = Query(1, ge=1),
|
||||
page_size: int = Query(50, ge=1, le=200),
|
||||
db: Session = Depends(get_db),
|
||||
@@ -105,7 +106,7 @@ def search_photos(
|
||||
detail="person_name is required for name search",
|
||||
)
|
||||
results, total = search_photos_by_name(
|
||||
db, person_name, folder_path, page, page_size
|
||||
db, person_name, folder_path, media_type, page, page_size
|
||||
)
|
||||
for photo, full_name in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
@@ -135,7 +136,7 @@ def search_photos(
|
||||
)
|
||||
df = date.fromisoformat(date_from) if date_from else None
|
||||
dt = date.fromisoformat(date_to) if date_to else None
|
||||
results, total = search_photos_by_date(db, df, dt, folder_path, page, page_size)
|
||||
results, total = search_photos_by_date(db, df, dt, folder_path, media_type, page, page_size)
|
||||
for photo in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
face_count = get_photo_face_count(db, photo.id)
|
||||
@@ -170,7 +171,7 @@ def search_photos(
|
||||
detail="At least one tag name is required",
|
||||
)
|
||||
results, total = search_photos_by_tags(
|
||||
db, tag_list, match_all, folder_path, page, page_size
|
||||
db, tag_list, match_all, folder_path, media_type, page, page_size
|
||||
)
|
||||
for photo in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
@@ -194,7 +195,7 @@ def search_photos(
|
||||
)
|
||||
)
|
||||
elif search_type == "no_faces":
|
||||
results, total = get_photos_without_faces(db, folder_path, page, page_size)
|
||||
results, total = get_photos_without_faces(db, folder_path, media_type, page, page_size)
|
||||
for photo in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
# Convert datetime to date for date_added
|
||||
@@ -215,7 +216,7 @@ def search_photos(
|
||||
)
|
||||
)
|
||||
elif search_type == "no_tags":
|
||||
results, total = get_photos_without_tags(db, folder_path, page, page_size)
|
||||
results, total = get_photos_without_tags(db, folder_path, media_type, page, page_size)
|
||||
for photo in results:
|
||||
face_count = get_photo_face_count(db, photo.id)
|
||||
person_name_val = get_photo_person(db, photo.id)
|
||||
@@ -237,7 +238,7 @@ def search_photos(
|
||||
)
|
||||
)
|
||||
elif search_type == "processed":
|
||||
results, total = get_processed_photos(db, folder_path, page, page_size)
|
||||
results, total = get_processed_photos(db, folder_path, media_type, page, page_size)
|
||||
for photo in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
face_count = get_photo_face_count(db, photo.id)
|
||||
@@ -260,7 +261,7 @@ def search_photos(
|
||||
)
|
||||
)
|
||||
elif search_type == "unprocessed":
|
||||
results, total = get_unprocessed_photos(db, folder_path, page, page_size)
|
||||
results, total = get_unprocessed_photos(db, folder_path, media_type, page, page_size)
|
||||
for photo in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
face_count = get_photo_face_count(db, photo.id)
|
||||
@@ -288,7 +289,7 @@ def search_photos(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Authentication required for favorites search",
|
||||
)
|
||||
results, total = get_favorite_photos(db, username, folder_path, page, page_size)
|
||||
results, total = get_favorite_photos(db, username, folder_path, media_type, page, page_size)
|
||||
for photo in results:
|
||||
tags = get_photo_tags(db, photo.id)
|
||||
face_count = get_photo_face_count(db, photo.id)
|
||||
|
||||
@@ -15,6 +15,7 @@ def search_photos_by_name(
|
||||
db: Session,
|
||||
person_name: str,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Tuple[Photo, str]], int]:
|
||||
@@ -26,6 +27,7 @@ def search_photos_by_name(
|
||||
- Searches first_name, last_name, middle_name, maiden_name
|
||||
- Returns (photo, full_name) tuples
|
||||
- Filters by folder_path if provided
|
||||
- Filters by media_type if provided ("image" or "video", None/"all" for all)
|
||||
- Multiple names: comma-separated, searches for photos with ANY matching person
|
||||
"""
|
||||
search_name = (person_name or "").strip()
|
||||
@@ -75,6 +77,10 @@ def search_photos_by_name(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
@@ -95,6 +101,7 @@ def search_photos_by_date(
|
||||
date_from: Optional[date] = None,
|
||||
date_to: Optional[date] = None,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
@@ -103,6 +110,7 @@ def search_photos_by_date(
|
||||
Matches desktop behavior exactly:
|
||||
- Filters by date_taken
|
||||
- Requires at least one date
|
||||
- Filters by media_type if provided ("image" or "video", None/"all" for all)
|
||||
- Returns photos ordered by date_taken DESC
|
||||
"""
|
||||
query = db.query(Photo).filter(Photo.date_taken.is_not(None))
|
||||
@@ -117,6 +125,10 @@ def search_photos_by_date(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
@@ -131,6 +143,7 @@ def search_photos_by_tags(
|
||||
tag_names: List[str],
|
||||
match_all: bool = False,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
@@ -140,6 +153,7 @@ def search_photos_by_tags(
|
||||
- match_all=True: photos must have ALL tags
|
||||
- match_all=False: photos with ANY tag
|
||||
- Case-insensitive tag matching
|
||||
- Filters by media_type if provided ("image" or "video", None/"all" for all)
|
||||
"""
|
||||
if not tag_names:
|
||||
return [], 0
|
||||
@@ -178,6 +192,10 @@ def search_photos_by_tags(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
@@ -190,12 +208,14 @@ def search_photos_by_tags(
|
||||
def get_photos_without_faces(
|
||||
db: Session,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
"""Get photos that have no detected faces.
|
||||
|
||||
Only includes processed photos (photos that have been processed for face detection).
|
||||
Filters by media_type if provided ("image" or "video", None/"all" for all).
|
||||
Matches desktop behavior exactly.
|
||||
"""
|
||||
query = (
|
||||
@@ -210,6 +230,10 @@ def get_photos_without_faces(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
@@ -222,11 +246,13 @@ def get_photos_without_faces(
|
||||
def get_photos_without_tags(
|
||||
db: Session,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
"""Get photos that have no tags.
|
||||
|
||||
Filters by media_type if provided ("image" or "video", None/"all" for all).
|
||||
Matches desktop behavior exactly.
|
||||
"""
|
||||
query = (
|
||||
@@ -240,6 +266,10 @@ def get_photos_without_tags(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
@@ -281,11 +311,13 @@ def get_photo_face_count(db: Session, photo_id: int) -> int:
|
||||
def get_processed_photos(
|
||||
db: Session,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
"""Get photos that have been processed for face detection.
|
||||
|
||||
Filters by media_type if provided ("image" or "video", None/"all" for all).
|
||||
Matches desktop behavior exactly.
|
||||
"""
|
||||
query = db.query(Photo).filter(Photo.processed == True) # noqa: E712
|
||||
@@ -295,6 +327,10 @@ def get_processed_photos(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
@@ -308,10 +344,14 @@ def get_favorite_photos(
|
||||
db: Session,
|
||||
username: str,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
"""Get all favorite photos for a user with pagination."""
|
||||
"""Get all favorite photos for a user with pagination.
|
||||
|
||||
Filters by media_type if provided ("image" or "video", None/"all" for all).
|
||||
"""
|
||||
from src.web.db.models import PhotoFavorite
|
||||
|
||||
# Join favorites with photos
|
||||
@@ -324,6 +364,10 @@ def get_favorite_photos(
|
||||
if folder_path:
|
||||
query = query.filter(Photo.path.like(f"{folder_path}%"))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
total = query.count()
|
||||
|
||||
# Order by favorite date (most recent first), then date_taken
|
||||
@@ -342,11 +386,13 @@ def get_favorite_photos(
|
||||
def get_unprocessed_photos(
|
||||
db: Session,
|
||||
folder_path: Optional[str] = None,
|
||||
media_type: Optional[str] = None,
|
||||
page: int = 1,
|
||||
page_size: int = 50,
|
||||
) -> Tuple[List[Photo], int]:
|
||||
"""Get photos that have not been processed for face detection.
|
||||
|
||||
Filters by media_type if provided ("image" or "video", None/"all" for all).
|
||||
Matches desktop behavior exactly.
|
||||
"""
|
||||
query = db.query(Photo).filter(Photo.processed == False) # noqa: E712
|
||||
@@ -356,6 +402,10 @@ def get_unprocessed_photos(
|
||||
folder_path = folder_path.strip()
|
||||
query = query.filter(Photo.path.startswith(folder_path))
|
||||
|
||||
# Apply media type filter if provided
|
||||
if media_type and media_type.lower() != "all":
|
||||
query = query.filter(Photo.media_type == media_type.lower())
|
||||
|
||||
# Total count
|
||||
total = query.count()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user