feat(plex): P1 frontend — Plex feed source, browse/search, show drill-down

- PlexBrowse.tsx: self-contained Plex mode (library scope tabs, own search +
  sort, poster-card grid with watch state + playability tint, paged; show →
  seasons/episodes drill-down). Playback announces (P2 wires the real player).
- Feed source dropdown gains a 'Plex' option (gated on me.plex_enabled); App
  swaps <Feed> for <PlexBrowse> when librarySource==='plex' so the video-feed
  hooks don't run in Plex mode. me exposes plex_enabled.
- api client (plexLibraries/plexBrowse/plexShow/plexImageUrl) + types; librarySource
  union + share-URL restore gain 'plex'; new plex i18n namespace en/hu/de.
- Verified over HTTP (authed): libraries, browse+FTS, image proxy 200 image/jpeg.
This commit is contained in:
npeter83 2026-07-05 02:32:00 +02:00
parent 63c6e782c8
commit 62636319b1
12 changed files with 452 additions and 5 deletions

View file

@ -62,6 +62,7 @@ import { CURRENT_VERSION } from "./lib/releaseNotes";
// so the initial load — and the logged-out landing — pulls only the shell, and admin-only pages
// never reach non-admins. All are rendered inside a <Suspense> boundary below.
const Feed = lazy(() => import("./components/Feed"));
const PlexBrowse = lazy(() => import("./components/PlexBrowse"));
const ChannelPage = lazy(() => import("./components/ChannelPage"));
const Channels = lazy(() => import("./components/Channels"));
const Playlists = lazy(() => import("./components/Playlists"));
@ -781,6 +782,8 @@ export default function App() {
prefs={prefsCtl}
onOpenWizard={() => setWizardOpen(true)}
/>
) : meQuery.data!.plex_enabled && filters.librarySource === "plex" ? (
<PlexBrowse filters={filters} setFilters={setFilters} />
) : (
<Feed
filters={filters}
@ -788,6 +791,7 @@ export default function App() {
view={view}
canRead={meQuery.data!.can_read}
isDemo={meQuery.data!.is_demo}
plexEnabled={meQuery.data!.plex_enabled}
onOpenWizard={() => setWizardOpen(true)}
ytSearch={ytSearch}
onYtSearch={enterYtSearch}