feat(plex): info-page polish — per-source strip toggles, glassy look, scroll restore

From UAT feedback on the collections info page:
- Per-source show/hide toggles in the customize menu, one per collection-strip
  type present on the item (Collections/IMDb/TMDb/…), persisted per account.
- Customize menu closes on Escape / outside click (reuses useDismiss).
- Preserve the info-page scroll position when returning from "Browse collection".
- Glassy refresh: content floats as .glass/.glass-card panels over a faint FIXED
  art backdrop on the <main> scroller (HTPC-style, toggleable); frosted menu;
  wider ~90% layout. Uses the existing glass surface system.
This commit is contained in:
npeter83 2026-07-06 06:50:03 +02:00
parent 8a8087ae99
commit f037a4563c
3 changed files with 110 additions and 25 deletions

View file

@ -52,12 +52,17 @@ export default function PlexBrowse({ q, onClearSearch, library, show, sort, filt
// restore it when we come back — so the browser/mouse Back from the player lands on the same card
// instead of the top of the library. The scroller is App's <main> (the page's overflow-y-auto).
const scrollRef = useRef(0);
// Same idea for the info page: "Browse collection" (onFilter) leaves the info view for a filtered
// grid, and browser Back returns to this same info page — restore where the user had scrolled it
// (down to a collection strip) instead of snapping to the top. Reset on a fresh info open so a
// different title's info starts at the top.
const infoScrollRef = useRef(0);
const scroller = () => document.querySelector("main");
useLayoutEffect(() => {
if (sub.view.kind === "grid" && scrollRef.current) {
const el = scroller();
if (el) el.scrollTop = scrollRef.current;
}
const el = scroller();
if (!el) return;
if (sub.view.kind === "grid" && scrollRef.current) el.scrollTop = scrollRef.current;
else if (sub.view.kind === "info" && infoScrollRef.current) el.scrollTop = infoScrollRef.current;
}, [sub.view.kind]);
const browseQ = useInfiniteQuery({
@ -121,6 +126,7 @@ export default function PlexBrowse({ q, onClearSearch, library, show, sort, filt
}
function onInfo(card: PlexCard) {
scrollRef.current = scroller()?.scrollTop ?? 0;
infoScrollRef.current = 0; // fresh info page starts at the top
sub.open({ kind: "info", id: card.id });
}
async function toggleWatched(card: PlexCard) {
@ -158,6 +164,7 @@ export default function PlexBrowse({ q, onClearSearch, library, show, sort, filt
}
}
setFilters(merged as unknown as PlexFilters);
infoScrollRef.current = scroller()?.scrollTop ?? 0; // restore on Back to this info page
sub.open({ kind: "grid" });
}}
/>
@ -373,7 +380,7 @@ function PlexInfoView({
const { t } = useTranslation();
const q = useQuery({ queryKey: ["plex-item", id], queryFn: () => api.plexItem(id) });
return (
<div className="max-w-[1100px] mx-auto">
<div className="w-[90%] max-w-[1600px] mx-auto">
<div className="p-4 pb-0">
<button
onClick={onBack}