feat(plex): sort direction, genre any/all, multi-person filters; fix back-nav + player stop
UAT follow-ups on the Plex filter epic: - Sort now has an asc/desc toggle (sort_dir), applied to any sort field. - Genre multi-select gains an Any/All mode (genre_mode: OR vs AND containment). - Director/actor/studio become multi-value: people AND (titles featuring all selected), studios OR; clicking them on the info page stacks (unions) instead of replacing, and the sidebar shows each as a removable 'Active' chip. - fix(history): clicking a metadata filter on the info page now pushes a fresh grid entry instead of history.back(), so browser Back returns to the info page rather than leaving the Plex module. - fix(player): fully tear down the <video> + hls on unmount and guard late play() calls, so backing out of a just-started video no longer leaves audio playing in the background. i18n en/hu/de (match any/all, sort direction).
This commit is contained in:
parent
eefd7e3abd
commit
1982cfa7b9
10 changed files with 160 additions and 60 deletions
|
|
@ -193,6 +193,7 @@ def browse(
|
|||
offset: int = 0,
|
||||
limit: int = Query(default=40, ge=1, le=100),
|
||||
genres: str | None = None,
|
||||
genre_mode: str = "any",
|
||||
content_ratings: str | None = None,
|
||||
year_min: int | None = None,
|
||||
year_max: int | None = None,
|
||||
|
|
@ -200,9 +201,10 @@ def browse(
|
|||
duration_min: int | None = None,
|
||||
duration_max: int | None = None,
|
||||
added_within: str | None = None,
|
||||
director: str | None = None,
|
||||
actor: str | None = None,
|
||||
studio: str | None = None,
|
||||
directors: str | None = None,
|
||||
actors: str | None = None,
|
||||
studios: str | None = None,
|
||||
sort_dir: str = "desc",
|
||||
user: User = Depends(current_user),
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
|
|
@ -230,9 +232,10 @@ def browse(
|
|||
else: # all — hide only the explicitly hidden
|
||||
query = query.filter(or_(st.status.is_(None), st.status != "hidden"))
|
||||
# --- Metadata filters (movie libraries; @> containment is GIN-indexed) ---
|
||||
genre_any = _csv(genres)
|
||||
if genre_any:
|
||||
query = query.filter(or_(*[PlexItem.genres.contains([g]) for g in genre_any]))
|
||||
gsel = _csv(genres)
|
||||
if gsel:
|
||||
conds = [PlexItem.genres.contains([g]) for g in gsel]
|
||||
query = query.filter(and_(*conds) if genre_mode == "all" else or_(*conds))
|
||||
crs = _csv(content_ratings)
|
||||
if crs:
|
||||
query = query.filter(PlexItem.content_rating.in_(crs))
|
||||
|
|
@ -249,12 +252,13 @@ def browse(
|
|||
cutoff = _added_cutoff(added_within)
|
||||
if cutoff is not None:
|
||||
query = query.filter(PlexItem.added_at >= cutoff)
|
||||
if director:
|
||||
query = query.filter(PlexItem.directors.contains([director]))
|
||||
if actor:
|
||||
query = query.filter(PlexItem.cast_names.contains([actor]))
|
||||
if studio:
|
||||
query = query.filter(PlexItem.studio == studio)
|
||||
for d in _csv(directors): # AND — titles this whole set directed
|
||||
query = query.filter(PlexItem.directors.contains([d]))
|
||||
for a in _csv(actors): # AND — titles featuring all these people
|
||||
query = query.filter(PlexItem.cast_names.contains([a]))
|
||||
stds = _csv(studios)
|
||||
if stds: # OR — a title has one studio
|
||||
query = query.filter(PlexItem.studio.in_(stds))
|
||||
else:
|
||||
query = query.filter(PlexShow.library_id == lib.id)
|
||||
|
||||
|
|
@ -268,18 +272,21 @@ def browse(
|
|||
|
||||
if tsq is not None:
|
||||
order = [func.ts_rank(model.search_vector, tsq).desc()]
|
||||
elif sort == "title":
|
||||
order = [func.lower(model.title).asc()]
|
||||
elif sort == "year" and model is PlexItem:
|
||||
order = [PlexItem.year.desc().nullslast()]
|
||||
elif sort == "rating" and model is PlexItem:
|
||||
order = [PlexItem.rating.desc().nullslast()]
|
||||
elif sort == "duration" and model is PlexItem:
|
||||
order = [PlexItem.duration_s.desc().nullslast()]
|
||||
elif sort == "release" and model is PlexItem:
|
||||
order = [PlexItem.originally_available_at.desc().nullslast()]
|
||||
else: # added — newest first
|
||||
order = [model.added_at.desc().nullslast()]
|
||||
else:
|
||||
if sort == "title":
|
||||
col = func.lower(model.title)
|
||||
elif sort == "year" and model is PlexItem:
|
||||
col = PlexItem.year
|
||||
elif sort == "rating" and model is PlexItem:
|
||||
col = PlexItem.rating
|
||||
elif sort == "duration" and model is PlexItem:
|
||||
col = PlexItem.duration_s
|
||||
elif sort == "release" and model is PlexItem:
|
||||
col = PlexItem.originally_available_at
|
||||
else: # added
|
||||
col = model.added_at
|
||||
asc = sort_dir == "asc"
|
||||
order = [(col.asc() if asc else col.desc()).nullslast()]
|
||||
order.append(model.id.desc())
|
||||
|
||||
rows = query.order_by(*order).offset(offset).limit(limit).all()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue