- A13: with Plex off, the Home shelf and the hero's comfort tier go YouTube-only like the rails beside them (owner's call: hide the tiles). The pins are kept; GET /pins still lists them. - Reorder deals the listed pins into the slots they already hold, so a partial list (hidden Plex pins, a stale tab) no longer puts two pins on one position; repeats are ignored. - A21: a comfort hero carries `pinned_from`, the shelf entry it was resolved from; its menu acts on that pin, so a pinned show's next episode offers "Unpin" instead of creating a second pin. - B12: the palette's "Meglepetés" asks for the surprise tier (the next press leading with it); the rotation carries on from there. - B13: the surprise token names the history entry it was requested for (minted by setPage's new `then`, after the push) and is dropped on any other entry, so a navigation that never mounted Home cannot replay it later.
57 lines
2.4 KiB
TypeScript
57 lines
2.4 KiB
TypeScript
import { createContext, useContext } from "react";
|
|
import type { Page } from "../lib/urlState";
|
|
import type { ChannelView } from "./navigationInit";
|
|
|
|
// The navigation contexts + their hooks, split out of NavigationProvider so that file exports only
|
|
// components (Fast Refresh can then hot-swap the provider without remounting the tree).
|
|
//
|
|
// TWO contexts on purpose: a STABLE actions context (setPage/openChannel/… never change reference)
|
|
// and a CHANGING state context (page/channelView/ytSearch). Context consumers re-render whenever the
|
|
// value changes, so deep memo'd leaves (VideoCard, PlayerModal) subscribe to actions ONLY and never
|
|
// re-render on a page/channel switch — preserving the memo that keeps feed appends from re-rendering
|
|
// every existing card.
|
|
|
|
export interface NavigationState {
|
|
page: Page;
|
|
/** The open channel page (null = none). Overlays `page`; rides in history.state._chan. */
|
|
channelView: ChannelView | null;
|
|
/** Live YouTube search term (null = normal feed). A feed sub-view; rides in history.state._yt. */
|
|
ytSearch: string | null;
|
|
}
|
|
|
|
export interface NavigationActions {
|
|
/** Switch modules. Closes every open overlay first and pushes only once their history entries are
|
|
* back (`lib/history.closeOverlaysThenNavigate`), so over an open player the push lands a moment
|
|
* after the call; with nothing open it is synchronous. The requested page being already current
|
|
* (with no channel page over it) pushes nothing — but the overlays still close.
|
|
*
|
|
* `then` runs once the navigation has happened (the push made, or found unnecessary) — for a
|
|
* request that belongs to the NEW entry, which over an open player does not exist yet at the
|
|
* call (`lib/homeIntent`). */
|
|
setPage: (next: Page, then?: () => void) => void;
|
|
openChannel: (id: string, name?: string) => void;
|
|
closeChannel: () => void;
|
|
enterYtSearch: (q: string) => void;
|
|
exitYtSearch: () => void;
|
|
}
|
|
|
|
export const NavigationStateContext = createContext<NavigationState>({
|
|
page: "feed",
|
|
channelView: null,
|
|
ytSearch: null,
|
|
});
|
|
export const NavigationActionsContext = createContext<NavigationActions>({
|
|
setPage: () => {},
|
|
openChannel: () => {},
|
|
closeChannel: () => {},
|
|
enterYtSearch: () => {},
|
|
exitYtSearch: () => {},
|
|
});
|
|
|
|
export function useNavigation(): NavigationState {
|
|
return useContext(NavigationStateContext);
|
|
}
|
|
export function useNavigationActions(): NavigationActions {
|
|
return useContext(NavigationActionsContext);
|
|
}
|