From f85dc1281957211ece97b7e91046890cd204e10c Mon Sep 17 00:00:00 2001 From: npeter83 Date: Sat, 22 Aug 2026 18:02:55 +0200 Subject: [PATCH] feat(plex): move the Discover/Browse toggle into the header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Discover ⟷ Browse switch now sits in the header next to the library-scope pill (All / Movies / Shows), instead of on the chip row / atop the Discover surface. It's driven through a small one-way PlexViewBridge: PlexBrowse still OWNS the sub-view (useHistorySubview) and publishes its top-level view + opener to the bridge; the always-mounted Header reads it to render PlexViewToggle. A global sub-view provider was deliberately avoided — PlexBrowse's hook remounts on every plex entry and re-seeds from history (a page switch clears ?v + drops _sub), so each entry starts at the Discover root; lifting it out would let the view go stale across navigation. The toggle hides on deeper views (show/info/person/…) and on non-plex pages (bridge cleared on unmount). sortView is just SortControl now; the grid toolbar no longer carries the toggle. Verified: toggle in the header, switches discover⟷grid, hides on info + on Feed, and Feed→Plex resets to Discover (no stale view); no console errors. --- frontend/src/components/Header.tsx | 5 +- frontend/src/components/PlexBrowse.tsx | 57 ++++++++++------------ frontend/src/components/PlexViewBridge.tsx | 33 +++++++++++++ frontend/src/components/PlexViewToggle.tsx | 24 +++++++++ frontend/src/main.tsx | 21 ++++---- 5 files changed, 99 insertions(+), 41 deletions(-) create mode 100644 frontend/src/components/PlexViewBridge.tsx create mode 100644 frontend/src/components/PlexViewToggle.tsx diff --git a/frontend/src/components/Header.tsx b/frontend/src/components/Header.tsx index 7618accc..de2f13aa 100644 --- a/frontend/src/components/Header.tsx +++ b/frontend/src/components/Header.tsx @@ -5,6 +5,7 @@ import type { Me } from "../lib/api"; import SearchBar from "./SearchBar"; import PlexSearchBox from "./PlexSearchBox"; import PlexScopeToggle from "./PlexScopeToggle"; +import PlexViewToggle from "./PlexViewToggle"; import FeedScopeToggle from "./FeedScopeToggle"; import SyncStatus from "./SyncStatus"; import { useNavigation } from "./NavigationProvider"; @@ -103,8 +104,10 @@ const Header = forwardRef< so the primary "what am I browsing" control is always in reach (mirrors Plex's scope toggle). */} {page === "feed" && } {/* Library scope (movie/show/both) sits right of the search box on Plex — relocated here from - the filter sidebar (S3) so the primary "what am I browsing" control is always in reach. */} + the filter sidebar (S3) so the primary "what am I browsing" control is always in reach. The + Discover ⟷ Browse toggle sits right beside it (driven via the PlexViewBridge). */} {page === "plex" && me.plex_enabled && } + {page === "plex" && me.plex_enabled && } {/* Per-user sync status — it used to cost the nav rail a whole block. It sits with the other pills rather than pinned to the far right: on a wide window that just strands it. Only on the two modules it says anything about: the feed those subscriptions fill, and the manager diff --git a/frontend/src/components/PlexBrowse.tsx b/frontend/src/components/PlexBrowse.tsx index 72e38d6b..878b9225 100644 --- a/frontend/src/components/PlexBrowse.tsx +++ b/frontend/src/components/PlexBrowse.tsx @@ -25,7 +25,6 @@ import { useHistorySubview } from "../lib/history"; import { PLEX_SUB_URL, type Sub } from "./plexSubUrl"; import { toggleWatchState } from "../lib/plexWatch"; import SortControl from "./SortControl"; -import SegmentedToggle from "./SegmentedToggle"; import FilterSelect from "./FilterSelect"; import PlexDiscover from "./PlexDiscover"; import { PageToolbar } from "./PageShell"; @@ -34,6 +33,7 @@ import ItemCount from "./ItemCount"; import { StateMessage } from "./QueryState"; import { usePlexActiveFilters, usePlexClearFilters } from "./usePlexActiveFilters"; import { usePlex, usePlexActions } from "./PlexProvider"; +import { usePublishPlexView } from "./PlexViewBridge"; import { EpisodeCard, PlexPosterCard } from "./plexCards"; import { PlexInfoView, PlexPersonView, PlexSeasonView, PlexShowView } from "./plexViews"; @@ -82,6 +82,20 @@ export default function PlexBrowse() { // or an added filter is a browse intent → flip to the grid (below); an explicit toggle returns. const sub = useHistorySubview({ kind: "discover" }, PLEX_SUB_URL); + // Publish the top-level view (discover vs grid) + opener to the header's Discover/Browse toggle via + // the bridge; clear it on unmount so the toggle disappears when you leave the plex page. Keyed on the + // view KIND (open is behaviourally stable — it takes the next view as an arg, closing over nothing). + const publishView = usePublishPlexView(); + useLayoutEffect(() => { + publishView({ + isDiscover: sub.view.kind === "discover", + toggleable: sub.view.kind === "discover" || sub.view.kind === "grid", + open: sub.open, + }); + return () => publishView(null); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sub.view.kind]); + // Sidebar asked to open a playlist → push it as a subview (browser Back returns to the grid), then // clear the signal so it doesn't re-open. useEffect(() => { @@ -366,42 +380,23 @@ export default function PlexBrowse() { ); } - // Discover ⟷ Browse switch, shown on both surfaces so either can return to the other. - const viewToggle = ( - sub.open(v === "discover" ? { kind: "discover" } : { kind: "grid" })} - /> - ); - - // Sort + the Discover/Browse toggle are tightly bound to the grid below, so on the grid view they - // live pinned to the right of the applied-filter chip row (directly above the cards), not on their - // own toolbar line — the same idiom as the Feed's `sortView`. + // Sort is tightly bound to the grid below, so on the grid view it lives pinned to the right of the + // applied-filter chip row (directly above the cards), not on its own toolbar line — the same idiom as + // the Feed's `sortView`. The Discover/Browse toggle moved up to the header (next to the scope pill). const sortView = ( - <> - setSort(next ?? PLEX_SORT_FALLBACK)} - label={t("plex.filter.sort")} - ascLabel={t("plex.filter.dir.asc")} - descLabel={t("plex.filter.dir.desc")} - /> -