refactor: component files export only components; lint fails on a warning
The 43 react-refresh/only-export-components warnings the gate let through meant an edit to a provider (or AnchoredPopover, Toaster, plexDetailUi, …) could not be hot-swapped: Fast Refresh reloads a module that also exports hooks or helpers. - Each provider's contexts, the types its hooks return and the hooks themselves live in a sibling context module (navigationContext.ts, channelsContext.ts, …); the provider file keeps the component. loadAccountFilters moved next to the feed filters' defaults (feedFiltersInit.ts), following navigationInit/playlistsInit. - Helpers that sat in component files have their own modules: welcomeMode, downloadTitle, plexPlayable, channelSync, ui/anchoredPopoverHooks, ui/pageToolbarSlot, ui/pagerContent, ui/toastLevels, ui/overlayOrder, ui/tableAlign, lib/plexDetail; the cells of the channel columns and the registry's page wrappers are components files of their own (channelCells, modulePages). - usePersistedTab, an alias of useAccountPersistedState, is gone; its callers use the original. npm run lint is now eslint --max-warnings 0, so a warning fails the gate like an error does.
This commit is contained in:
@@ -1,13 +1,4 @@
|
||||
import {
|
||||
createContext,
|
||||
useCallback,
|
||||
useContext,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
type ReactNode,
|
||||
} from "react";
|
||||
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { urlWithParams, type Page } from "../lib/urlState";
|
||||
import { pageTitleKey } from "../lib/pageMeta";
|
||||
@@ -20,57 +11,19 @@ import {
|
||||
rememberNavPosition,
|
||||
} from "../lib/history";
|
||||
import { channelViewFrom, loadInitialPage, PAGE_KEY, type ChannelView } from "./navigationInit";
|
||||
import {
|
||||
NavigationActionsContext,
|
||||
NavigationStateContext,
|
||||
type NavigationActions,
|
||||
type NavigationState,
|
||||
} from "./navigationContext";
|
||||
|
||||
// Owns the app's navigation + browser-history state, lifted out of the App god component so pages
|
||||
// consume it via a hook instead of prop-drilling `page`/`setPage`/`onOpenChannel` through the tree.
|
||||
// Mirrors ConfirmProvider (context + default value + one-line hooks); composed in main.tsx inside
|
||||
// ConfirmProvider, around <App/>.
|
||||
//
|
||||
// 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. */
|
||||
setPage: (next: Page) => void;
|
||||
openChannel: (id: string, name?: string) => void;
|
||||
closeChannel: () => void;
|
||||
enterYtSearch: (q: string) => void;
|
||||
exitYtSearch: () => void;
|
||||
}
|
||||
|
||||
const StateContext = createContext<NavigationState>({
|
||||
page: "feed",
|
||||
channelView: null,
|
||||
ytSearch: null,
|
||||
});
|
||||
const ActionsContext = createContext<NavigationActions>({
|
||||
setPage: () => {},
|
||||
openChannel: () => {},
|
||||
closeChannel: () => {},
|
||||
enterYtSearch: () => {},
|
||||
exitYtSearch: () => {},
|
||||
});
|
||||
|
||||
export function useNavigation(): NavigationState {
|
||||
return useContext(StateContext);
|
||||
}
|
||||
export function useNavigationActions(): NavigationActions {
|
||||
return useContext(ActionsContext);
|
||||
}
|
||||
// The contexts, their value types and the hooks live in navigationContext.ts.
|
||||
|
||||
export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
const { t, i18n } = useTranslation();
|
||||
@@ -256,8 +209,8 @@ export function NavigationProvider({ children }: { children: ReactNode }) {
|
||||
);
|
||||
|
||||
return (
|
||||
<ActionsContext.Provider value={actions}>
|
||||
<StateContext.Provider value={state}>{children}</StateContext.Provider>
|
||||
</ActionsContext.Provider>
|
||||
<NavigationActionsContext.Provider value={actions}>
|
||||
<NavigationStateContext.Provider value={state}>{children}</NavigationStateContext.Provider>
|
||||
</NavigationActionsContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user