The header library-scope pill gains two entry points — Playlist and Collection — next to All/Movies/Shows, a second way into each area (the rail filter is the other). Picking Playlist shows a grid of the user's playlists (a tile opens that playlist's view); Collection shows a searchable grid of collections (a tile filters the title grid by that collection). Pure front-end — reuses the existing plexPlaylists / plexCollections(union) list endpoints. Two new top-level sub-views (`playlists` / `collections`, bookmarkable as ?v=playlists / ?v=collections) rendered by PlexBrowse via the new plexBrowseLists surfaces (hue-tiled like the Discover genre tiles). The PlexViewBridge now carries the view `kind`; PlexScopeToggle is a 5-way that derives its active segment from it (browse-mode when in one, else scope) and drives the two views through the bridge — picking a scope from a browse mode returns to the title grid. The Discover/Browse toggle hides in these modes; Backspace + the search→grid flip cover them. Tests: PlexScopeToggle (5-way + browse-mode drive/active), plexSubUrl round-trip for the two new views. Gate: tsc/lint/knip/prettier + 402 vitest.
33 lines
1.7 KiB
TypeScript
33 lines
1.7 KiB
TypeScript
import { createContext, useContext, useState, type ReactNode } from "react";
|
|
import { type Sub } from "./plexSubUrl";
|
|
|
|
// A one-way bridge so the always-mounted Header can host the Plex Discover ⟷ Browse toggle even though
|
|
// the sub-view state lives in (and is owned by) the plex-only PlexBrowse. PlexBrowse PUBLISHES its
|
|
// current top-level view + opener here (on mount and on view change) and clears it on unmount; the
|
|
// Header READS it to render the toggle beside the library-scope pill.
|
|
//
|
|
// Why a bridge and not a global sub-view provider: PlexBrowse's `useHistorySubview` REMOUNTS every time
|
|
// you enter the plex page, re-seeding the view from history (a page switch clears ?v + drops _sub, so
|
|
// each plex entry starts at the Discover root). Lifting that hook to an always-mounted provider would
|
|
// lose the remount and let the view go stale across navigation. Keeping it in PlexBrowse preserves the
|
|
// reseed; the bridge only mirrors the small slice the header needs.
|
|
export type PlexViewControls = {
|
|
kind: Sub["kind"]; // the current top-level plex view — the header controls derive their state from it
|
|
open: (v: Sub) => void;
|
|
};
|
|
|
|
const ReadCtx = createContext<PlexViewControls | null>(null);
|
|
const PublishCtx = createContext<(c: PlexViewControls | null) => void>(() => {});
|
|
|
|
export function PlexViewBridge({ children }: { children: ReactNode }) {
|
|
const [controls, setControls] = useState<PlexViewControls | null>(null);
|
|
return (
|
|
<PublishCtx.Provider value={setControls}>
|
|
<ReadCtx.Provider value={controls}>{children}</ReadCtx.Provider>
|
|
</PublishCtx.Provider>
|
|
);
|
|
}
|
|
|
|
export const usePlexViewControls = () => useContext(ReadCtx);
|
|
export const usePublishPlexView = () => useContext(PublishCtx);
|