feat(plex): P1.5 — Plex as a nav module + integrated search + filter sidebar
Per user feedback: promote Plex from a hidden feed-Source option to a first-class left-nav module, with its own filter sidebar and the shared top search box. - Plex is now a nav-rail module (page='plex', gated on me.plex_enabled) — its own page → correct browser Back/Forward; removed the Feed Source-dropdown 'plex' hack. - PlexSidebar.tsx: left filter column (library scope, watch-state for movies: all/unwatched/in_progress/watched, sort) — per-account persisted (App state). - Header search box now also serves the Plex page (integrated search); the live YouTube-search escalation stays feed-only. - PlexBrowse.tsx reworked: infinite scroll (IntersectionObserver, no 'Load more'), content-visibility for smoother long-list scroll, show drill-down rides history (useHistorySubview) so Back returns to the grid; dropped the stray 'YouTube feed' button on the show page. - backend /browse gains a per-user watch-state filter (show=, movies only). - plex i18n (navLabel + filter.*) en/hu/de. Note: episode-title 'Episode N' on some shows (e.g. Westworld) is Plex-side (unmatched show) — we mirror what Plex has; Match/Refresh in Plex + re-sync fixes it.
This commit is contained in:
parent
62636319b1
commit
219a935121
14 changed files with 290 additions and 133 deletions
|
|
@ -8,8 +8,8 @@ catalog sync.
|
|||
import logging
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy.orm import Session
|
||||
from sqlalchemy import and_, func, or_
|
||||
from sqlalchemy.orm import Session, aliased
|
||||
|
||||
from app import sysconfig
|
||||
from app.auth import current_user
|
||||
|
|
@ -130,14 +130,15 @@ def browse(
|
|||
library: str,
|
||||
q: str | None = None,
|
||||
sort: str = "added",
|
||||
show: str = "all",
|
||||
offset: int = 0,
|
||||
limit: int = Query(default=40, ge=1, le=100),
|
||||
user: User = Depends(current_user),
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
"""List a library's movies (leaves) or shows, with optional FTS search + sorting.
|
||||
Movie libraries return playable movie cards; show libraries return show cards (drill in via
|
||||
/show/{id})."""
|
||||
"""List a library's movies (leaves) or shows, with optional FTS search + sorting + a per-user
|
||||
watch-state filter (`show`: all|unwatched|in_progress|watched — movie libraries only). Movie
|
||||
libraries return playable movie cards; show libraries return show cards (drill in via /show)."""
|
||||
lib = db.query(PlexLibrary).filter_by(plex_key=str(library)).first()
|
||||
if lib is None:
|
||||
raise HTTPException(status_code=404, detail="Unknown Plex library")
|
||||
|
|
@ -147,6 +148,17 @@ def browse(
|
|||
query = db.query(model)
|
||||
if lib.kind == "movie":
|
||||
query = query.filter(PlexItem.library_id == lib.id, PlexItem.kind == "movie")
|
||||
# Per-user watch-state filter (movies only; a show isn't a single watch unit).
|
||||
st = aliased(PlexState)
|
||||
query = query.outerjoin(st, and_(st.item_id == PlexItem.id, st.user_id == user.id))
|
||||
if show == "watched":
|
||||
query = query.filter(st.status == "watched")
|
||||
elif show == "in_progress":
|
||||
query = query.filter(st.position_seconds > 0, or_(st.status.is_(None), st.status != "watched"))
|
||||
elif show == "unwatched":
|
||||
query = query.filter(or_(st.status.is_(None), st.status.notin_(["watched", "hidden"])))
|
||||
else: # all — hide only the explicitly hidden
|
||||
query = query.filter(or_(st.status.is_(None), st.status != "hidden"))
|
||||
else:
|
||||
query = query.filter(PlexShow.library_id == lib.id)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue