feat(plex): player HTPC polish — Stop button, keyboard help, time-remaining + clock, scroll-restore

- Explicit Stop button (stops playback + returns to the library).
- Keyboard cheat-sheet overlay toggled with 'H' (movie hides the episode-nav row).
- Backspace = stop & back to feed (HTPC-remote convention); closes the help first.
- Time-remaining readout beside the seek bar + a live wall clock in the top bar
  (both fade with the controls).
- PlexBrowse restores the grid scroll position when returning from the player, so
  Back lands on the same card instead of the top of the library.
- New plex.player i18n keys (stop/help/keys.*) in en/hu/de.
This commit is contained in:
npeter83 2026-07-05 21:27:02 +02:00
parent 4993298838
commit 38c4bee869
7 changed files with 165 additions and 7 deletions

View file

@ -1,4 +1,4 @@
import { lazy, Suspense, useEffect, useRef, type CSSProperties } from "react";
import { lazy, Suspense, useEffect, useLayoutEffect, useRef, type CSSProperties } from "react";
import { useTranslation } from "react-i18next";
import { useInfiniteQuery, useQuery, useQueryClient } from "@tanstack/react-query";
import { ArrowLeft, CheckCircle2, Play } from "lucide-react";
@ -34,6 +34,19 @@ export default function PlexBrowse({ q, library, show, sort }: Props) {
const dq = useDebounced(q.trim(), 350);
const sub = useHistorySubview<Sub>({ kind: "grid" });
// Opening a show/player replaces the grid entirely (the component returns early below), so the
// scroll container resets to the top. Remember where the grid was scrolled when leaving it, and
// 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);
const scroller = () => document.querySelector("main");
useLayoutEffect(() => {
if (sub.view.kind === "grid" && scrollRef.current) {
const el = scroller();
if (el) el.scrollTop = scrollRef.current;
}
}, [sub.view.kind]);
const browseQ = useInfiniteQuery({
queryKey: ["plex-browse", library, dq, sort, show],
enabled: !!library && sub.view.kind === "grid",
@ -66,6 +79,7 @@ export default function PlexBrowse({ q, library, show, sort }: Props) {
}, [hasNextPage, isFetchingNextPage, fetchNextPage]);
function onCard(card: PlexCard) {
scrollRef.current = scroller()?.scrollTop ?? 0; // remember grid position for the trip back
if (card.type === "show") sub.open({ kind: "show", id: card.id });
else sub.open({ kind: "player", id: card.id });
}