diff --git a/frontend/src/components/PlexBrowse.tsx b/frontend/src/components/PlexBrowse.tsx index bc8476d..d5e5fbf 100644 --- a/frontend/src/components/PlexBrowse.tsx +++ b/frontend/src/components/PlexBrowse.tsx @@ -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
(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 ( -
+
{customizing && ( -
+
+ {presentSources.map((src) => ( + toggleSource(src)} + /> + ))}
)}
@@ -273,7 +337,7 @@ export default function PlexInfo({ {detail.status === "watched" ? (
+
+ {/* End hero panel. */} {/* Cast row (toggleable), circular photos. */} {cast.length > 0 && ( -
+

{t("plex.info.cast")}

@@ -357,9 +426,16 @@ export default function PlexInfo({
)} - {/* Collection strips — the other titles in this movie's collection(s), directly playable. */} - {detail.collections?.map((col) => ( -
+ {/* Collection strips — the other titles in this movie's collection(s), directly playable. + Each source bucket (Collections / IMDb / TMDb / smart / …) is independently hideable. */} + {detail.collections + ?.filter((col) => !hiddenSources.includes(col.source)) + .map((col) => ( +

{col.title}

{onFilter && ( diff --git a/frontend/src/lib/releaseNotes.ts b/frontend/src/lib/releaseNotes.ts index 1a6ba9a..f94d6ce 100644 --- a/frontend/src/lib/releaseNotes.ts +++ b/frontend/src/lib/releaseNotes.ts @@ -22,6 +22,8 @@ export const RELEASE_NOTES: ReleaseEntry[] = [ "Plex collections: every Plex collection (franchises like Avatar, curated sets, and community lists like IMDb Top 250) is now mirrored locally by the background sync, so browsing is instant — no more waiting on Plex's slow collection queries.", "Plex library: a searchable Collection filter in the sidebar — pick a collection to see just its titles, combinable with every other filter.", "Plex info page: a title's collection(s) now appear as strips of the sibling films, playable straight from there (most-specific collection first).", + "Plex info page: show or hide each collection-strip type independently — keep genuine franchises (Avatar, James Bond) while hiding the big auto-lists (IMDb Top 250, TMDb Trending), remembered per account.", + "Plex info page: a refreshed, wider layout — content floats as frosted-glass panels over a faint, fixed backdrop of the title's own art (toggleable), matching the app's glassy look.", ], }, {