Files
siftlode/frontend/src/components/ui/useMediaPreview.ts
T
peter 3eb87e7108 feat(plex): every show card plays the episode it would put on
A show card's picture opened the show page everywhere but Home, whose shelves carried the
resolved `next`. Now every Plex picture plays: a show asks `GET /api/plex/show/{id}/next`
(the in-progress episode, else the first unwatched, else S1·E1 — the rule Home and the show
page's Resume follow) on the click, fresh each time. A started show shows Continue + Restart;
its title, an ⓘ and the menu's "Open show" row open its page; one with no episodes opens there.

The question is asked per show, never carried on every card: three probes a show would cost a
grid page up to 180 queries. The hover preview asks it once it opens and names the episode
("Next: S6·E16 — Title"). Related shows on a show page play the same way.
2026-10-09 01:22:00 +02:00

42 lines
1.6 KiB
TypeScript

import { useState, type HTMLAttributes } from "react";
import { useAnchoredPopover } from "./anchoredPopoverHooks";
// The hover wiring of a media card's preview (./MediaPreview has the why).
/** How long the pointer must rest on a card: long enough that a sweep across a grid opens nothing. */
const MEDIA_PREVIEW_DELAY = 900;
export function useMediaPreview(enabled = true) {
const [open, setOpen] = useState(false);
const pop = useAnchoredPopover({
open: open && enabled,
onOpenChange: setOpen,
mode: "hover",
placement: "right-start",
offset: 8,
hoverDelay: { open: MEDIA_PREVIEW_DELAY, close: 0 },
pointerOnly: true,
});
return {
popover: pop,
/** The preview is up: the pointer has rested on the card (what a card may fetch for it waits on). */
open: open && enabled,
/** The card root's props: the anchor, the hover wiring, and a close on any press — merged with
* the root's own (its menu's right-click and long-press). */
rootProps: (own: HTMLAttributes<HTMLDivElement> & Record<string, unknown> = {}) => ({
...(pop.getReferenceProps({
...own,
onPointerDown: (e: React.PointerEvent<HTMLDivElement>) => {
setOpen(false);
(own.onPointerDown as ((e: React.PointerEvent<HTMLDivElement>) => void) | undefined)?.(e);
},
onContextMenu: (e: React.MouseEvent<HTMLDivElement>) => {
setOpen(false);
(own.onContextMenu as ((e: React.MouseEvent<HTMLDivElement>) => void) | undefined)?.(e);
},
}) as HTMLAttributes<HTMLDivElement> & Record<string, unknown>),
ref: pop.refs.setReference,
}),
};
}