refactor(plex): scope module state in PlexProvider

Lift the Plex module's shared local state (scope / watch-state / sort / expanded
filters / search term / open-playlist hand-off) out of App into a split-context
PlexProvider with stable useCallback setters. PlexSidebar, PlexBrowse, and the
header search box read it via usePlex()/usePlexActions() instead of props drilled
through App; PlexBrowse is now prop-free. App loses six useStates + the filters
JSON memo/serialise helpers. (Platform S6b.2)
This commit is contained in:
2026-07-18 23:53:10 +02:00
parent d0fc061795
commit 6c32e5c8d6
6 changed files with 148 additions and 98 deletions
+1 -48
View File
@@ -3,11 +3,9 @@ import { useTranslation } from "react-i18next";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import {
api,
EMPTY_PLEX_FILTERS,
getActiveAccount,
setActiveAccount,
setUnauthorizedHandler,
type PlexFilters,
} from "./lib/api";
import { setLanguage, isSupported, type LangCode } from "./i18n";
import {
@@ -156,30 +154,6 @@ export default function App() {
: channelsViewRaw === "blocked"
? "blocked"
: "subscribed";
// Plex module filters (its own left-sidebar filter section) — per-account persisted.
// The former per-library picker is now a cross-library SCOPE: movie | show | both (unified library).
// (Reuses the old LS.plexLibrary key; an old stored library-id value falls back to "both".)
const [plexScopeRaw, setPlexScope] = useAccountPersistedState(LS.plexLibrary, "both");
const plexScope = ["movie", "show", "both"].includes(plexScopeRaw) ? plexScopeRaw : "both";
const [plexShowFilter, setPlexShowFilter] = useAccountPersistedState(LS.plexShow, "all");
const [plexSort, setPlexSort] = useAccountPersistedState(LS.plexSort, "title");
const [plexPlaylistOpen, setPlexPlaylistOpen] = useState<number | null>(null); // sidebar → open a playlist
// The expanded Plex filters live as one JSON blob (the string-only account store serializes it).
const [plexFiltersRaw, setPlexFiltersRaw] = useAccountPersistedState(LS.plexFilters, "{}");
const plexFilters = useMemo<PlexFilters>(() => {
try {
return { ...EMPTY_PLEX_FILTERS, ...JSON.parse(plexFiltersRaw) };
} catch {
return EMPTY_PLEX_FILTERS;
}
}, [plexFiltersRaw]);
const setPlexFilters = (f: PlexFilters) => setPlexFiltersRaw(JSON.stringify(f));
// The Plex library search term is its OWN ephemeral state — deliberately NOT the persisted feed
// `filters.q`. Sharing that box meant a Plex search leaked into the feed and, being persisted,
// greeted you with a stale query (colliding with a persisted collection filter → confusing
// "0 matches") after a reload. Kept in App so it survives page switches within a session, but
// resets on reload.
const [plexQ, setPlexQ] = useAccountPersistedState(LS.plexQ, ""); // survives F5, unlike the feed search
// Bumped to tell the channel manager to drop a stale column filter when we send the user
// there to see a specific set (the header's "without full history" link).
const [channelsFilterReset, setChannelsFilterReset] = useState(0);
@@ -581,15 +555,6 @@ export default function App() {
{page === "plex" && meQuery.data!.plex_enabled && (
<Suspense fallback={null}>
<PlexSidebar
scope={plexScope}
setScope={setPlexScope}
show={plexShowFilter}
setShow={setPlexShowFilter}
sort={plexSort}
setSort={setPlexSort}
filters={plexFilters}
setFilters={setPlexFilters}
onOpenPlaylist={setPlexPlaylistOpen}
layout={panelLayouts.plex}
setLayout={(l) => setPanelLayout("plex", l)}
collapsed={filterCollapsed}
@@ -644,8 +609,6 @@ export default function App() {
header={
<Header
me={meQuery.data!}
plexQ={plexQ}
setPlexQ={setPlexQ}
channelsSearch={channelsSearch}
setChannelsSearch={setChannelsSearch}
onYtSearch={enterYtSearch}
@@ -714,17 +677,7 @@ export default function App() {
onOpenWizard={() => setWizardOpen(true)}
/>
) : page === "plex" && meQuery.data!.plex_enabled ? (
<PlexBrowse
q={plexQ}
scope={plexScope}
setScope={setPlexScope}
show={plexShowFilter}
sort={plexSort}
filters={plexFilters}
setFilters={setPlexFilters}
openPlaylist={plexPlaylistOpen}
onPlaylistOpened={() => setPlexPlaylistOpen(null)}
/>
<PlexBrowse />
) : (
<Feed
filters={filters}