refactor(ui): reuse the store, media-query and deleted-tag rules the review found rebuilt, and name a loading pick
E32 review round 1 (F3–F6). - Channels' open-row store and the chat dock use lib/store's createStore instead of a hand-rolled listener set (the chat dock gets a test of its own). - The Plex backdrop reads prefers-reduced-motion through useMediaQuery; the query string is one constant (REDUCED_MOTION) for the hook and the carousel's one-off read. - A link-restored collection filter showed its raw rating_key in the picker while the titles loaded: usePlexCollectionTitles now says it is pending, the picker draws a placeholder (labelsPending, as the channel picker does) and so does the chip row. - The "deleted tag" rule was written twice with two i18n keys and had drifted: the channel manager showed NO chip for a pick while the tag list loaded, or ever after a failed load, while the pick still narrowed the table. One rule now (lib/tagPicks), one key (common.deletedTag); the manager's row shows every pick, as a placeholder while loading and as its id after a failure.
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { useLayoutEffect, useSyncExternalStore } from "react";
|
||||
import { useLayoutEffect } from "react";
|
||||
import { REDUCED_MOTION, useMediaQuery } from "./useMediaQuery";
|
||||
import { usePref, useSavePref } from "./usePref";
|
||||
import type { TFunction } from "i18next";
|
||||
import type { PlexCard, PlexTmdb } from "./api";
|
||||
@@ -7,18 +8,6 @@ import type { PlexCard, PlexTmdb } from "./api";
|
||||
// prefs, the TMDB backdrop URL, the crossfading art backdrop hook and the episode label. Split out so
|
||||
// plexDetailUi exports only components (Fast Refresh).
|
||||
|
||||
const REDUCED_MOTION = "(prefers-reduced-motion: reduce)";
|
||||
const reducedMotion = () =>
|
||||
typeof window !== "undefined" &&
|
||||
typeof window.matchMedia === "function" &&
|
||||
window.matchMedia(REDUCED_MOTION).matches;
|
||||
const onReducedMotionChange = (cb: () => void) => {
|
||||
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return () => {};
|
||||
const m = window.matchMedia(REDUCED_MOTION);
|
||||
m.addEventListener("change", cb);
|
||||
return () => m.removeEventListener("change", cb);
|
||||
};
|
||||
|
||||
export function useDetailPrefs() {
|
||||
// Three per-user detail-page toggles, each a server pref adopted on login (usePref). Default ON:
|
||||
// undefined → true, only an explicit false hides. The detail pages mount after `me` is cached, so
|
||||
@@ -31,7 +20,7 @@ export function useDetailPrefs() {
|
||||
// Windows also reports whenever its animation effects are off). Once the reader turns it on or off
|
||||
// here, that pick wins on every device: the owner never saw the crossfade, because the system signal
|
||||
// used to switch it off whatever the toggle said (2026-10-03). The toggle shows the state in force.
|
||||
const reduce = useSyncExternalStore(onReducedMotionChange, reducedMotion, () => false);
|
||||
const reduce = useMediaQuery(REDUCED_MOTION, false);
|
||||
const [crossfadePick, setCrossfade] = usePref<boolean | null>("plexInfoArtCrossfade", null, {
|
||||
parse: (raw) => (typeof raw === "boolean" ? raw : null),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user