Fold the hand-rolled server-pref plumbing (savePrefs + local mirror + me-cache merge + adopt-on-login) into a single usePref<T>(serverKey, default, opts) hook, plus useSavePref for dynamic multi-key patches. Migrated (6 savePrefs call sites + 2 duplicated adopt effects -> the hook): - FeedViewProvider: view - App: filterCollapsed, playlistsCollapsed (+ their adopt block removed) - PlayerModal: playerAutoAdvance, playerLoop - plexDetailUi: plexInfoArtBg/Cast/Related (savePref via useSavePref) Left as-is by design: panel layouts (a dynamically-indexed Record, not one key), language (i18n store, no React state), PrefsProvider (debounced multi-field auto-save + baseline echo-suppression - a different model). Behaviour-equivalent (net -84 LOC at the call sites). usePref.dom.test covers the three seed sources, persist path, debounce, adopt, parse, and useSavePref. Frontend gate green: typecheck, knip, eslint (0 err), prettier, 238 vitest.
43 lines
1.7 KiB
TypeScript
43 lines
1.7 KiB
TypeScript
import { createContext, useContext, useMemo, type ReactNode } from "react";
|
|
import { parseFeedView, type FeedView } from "../lib/feedView";
|
|
import { LS } from "../lib/storage";
|
|
import { usePref } from "../lib/usePref";
|
|
|
|
// Owns the feed's view mode (the card/row/tile density switcher), lifted out of App so both the feed
|
|
// page and the channel page read it via hooks instead of prop-drilling `view`/`setView` through App.
|
|
// Split State + Actions contexts, mirroring the other providers; composed in main.tsx around <App/>.
|
|
//
|
|
// The view mode is an account PREFERENCE, not a filter: it persists to the server on pick and is
|
|
// adopted from server prefs on login. usePref carries that server-sync (plus the localStorage mirror
|
|
// that seeds the first render synchronously) so this provider is just the two contexts over it.
|
|
|
|
interface FeedViewActions {
|
|
setView: (v: FeedView) => void;
|
|
}
|
|
|
|
const StateContext = createContext<FeedView>("cards");
|
|
const ActionsContext = createContext<FeedViewActions>({ setView: () => {} });
|
|
|
|
export function useFeedView(): FeedView {
|
|
return useContext(StateContext);
|
|
}
|
|
export function useFeedViewActions(): FeedViewActions {
|
|
return useContext(ActionsContext);
|
|
}
|
|
|
|
export function FeedViewProvider({ children }: { children: ReactNode }) {
|
|
// parseFeedView also maps the pre-0.45 "grid"/"list" values forward (idempotent for current values).
|
|
const [view, setView] = usePref<FeedView>("view", "cards", {
|
|
localKey: LS.feedView,
|
|
parse: parseFeedView,
|
|
});
|
|
|
|
const actions = useMemo<FeedViewActions>(() => ({ setView }), [setView]);
|
|
|
|
return (
|
|
<ActionsContext.Provider value={actions}>
|
|
<StateContext.Provider value={view}>{children}</StateContext.Provider>
|
|
</ActionsContext.Provider>
|
|
);
|
|
}
|