Wave 5a building blocks under the Seerr-inspired Plex enrichment (E5).
Carousel (frontend/src/components/Carousel.tsx): the one horizontal-rail
component, replacing the hand-rolled `flex overflow-x-auto` strips and the
home of the future Plex-Discover rows. Edge-fade mask, hover/focus scroll
buttons that appear only on the clipped side (useHScroll — the X-axis twin
of useScrollFade, same watch-the-children discipline), native scrollbar
hidden behind .no-scrollbar, reduced-motion-aware paging. First consumers:
CastStrip + RelatedStrip now scroll inside it (dedup + free affordances).
New i18n: common.seeAll / scrollLeft / scrollRight (en+hu).
Card-art serializer (backend/app/plex/cards.py): movie_card + show_card now
emit `art` (the wide backdrop, /api/plex/image/{rk}?variant=art) so rails and
the crossfading hero get a landscape image with no per-item fetch — gated on
art_key so an art-less item stays null and falls back to the poster. FE type
PlexCard.art?.
Tests: Carousel.dom.test (children/header/see-all, clipped-edge buttons +
paging), test_plex_cards (art present/absent by art_key). Added a
ResizeObserver stub to PlexPersonView.dom.test (it renders CastStrip, now a
Carousel consumer).
Gate: tsc x3 / eslint / prettier / knip / vitest 324 green; ruff green;
backend test lane (docker-compose.test.yml) green on the plex card tests.
32 lines
1.3 KiB
Python
32 lines
1.3 KiB
Python
"""Plex card serializers (app.plex.cards) — pure model->dict projections, no DB needed.
|
|
|
|
E5 S1: movie/show cards now also emit `art` (the wide backdrop, for a Discover rail's landscape tile
|
|
and the crossfading hero), but ONLY when the item actually has an `art_key` — otherwise the URL would
|
|
resolve to a blank image on every card that tried to use it, so it must be null and let the client fall
|
|
back to the poster."""
|
|
|
|
from app.models import PlexItem, PlexShow
|
|
from app.plex.cards import movie_card, show_card
|
|
|
|
|
|
def test_movie_card_emits_art_when_art_key_present():
|
|
it = PlexItem(rating_key="m1", kind="movie", title="M", year=2020, art_key="/library/art/1")
|
|
card = movie_card(it, None)
|
|
assert card["thumb"] == "/api/plex/image/m1"
|
|
assert card["art"] == "/api/plex/image/m1?variant=art"
|
|
|
|
|
|
def test_movie_card_art_is_none_without_art_key():
|
|
it = PlexItem(rating_key="m2", kind="movie", title="M2")
|
|
assert movie_card(it, None)["art"] is None
|
|
|
|
|
|
def test_show_card_emits_art_when_art_key_present():
|
|
sh = PlexShow(rating_key="s1", title="S", art_key="/library/art/2")
|
|
assert show_card(sh)["art"] == "/api/plex/image/s1?variant=art"
|
|
|
|
|
|
def test_show_card_art_is_none_without_art_key():
|
|
sh = PlexShow(rating_key="s2", title="S2")
|
|
assert show_card(sh)["art"] is None
|