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.
This commit is contained in:
@@ -17,8 +17,10 @@ import { type ActionMenuItem } from "./ui/ActionMenu";
|
||||
// the everyday toggles (watched, add to playlist) on hover; details, copy and the pin live here.
|
||||
|
||||
export interface PlexActionHandlers {
|
||||
/** Play it: a show, the episode it would play (the surface resolves it). */
|
||||
onOpen: (c: PlexCard) => void;
|
||||
/** The media-info page. Omitted where a surface has none (the episode lists). */
|
||||
/** The media-info page, or a show's own page. Omitted where a surface has none (the episode
|
||||
* lists). */
|
||||
onInfo?: (c: PlexCard) => void;
|
||||
onToggleWatched?: (c: PlexCard) => void;
|
||||
/** Play from the start instead of the resume point (C5). Where given, a started title lists it
|
||||
@@ -32,7 +34,8 @@ const icon = "w-4 h-4";
|
||||
export const plexAddLabelKey = (c: PlexCard): string =>
|
||||
c.type === "episode" ? "plex.playlist.addEpisode" : "plex.playlistAdd.manage";
|
||||
|
||||
/** A show opens its drill-down; a movie or an episode plays. */
|
||||
/** A leaf: a movie or an episode, what the watched toggle, the playlist add and a resume point
|
||||
* belong to. A show plays too, through the episode it resolves to. */
|
||||
const isPlexPlayable = (c: PlexCard): boolean => c.type !== "show";
|
||||
|
||||
/** Has a resume point: the click continues it, and "from the start" is a choice. A watched item is
|
||||
@@ -73,15 +76,17 @@ export function usePlexCardActions(handlers: PlexActionHandlers, open: boolean)
|
||||
): ActionMenuItem[] => {
|
||||
const { onOpen, onInfo, onToggleWatched, onRestart } = handlers;
|
||||
const playable = isPlexPlayable(c);
|
||||
const isShow = c.type === "show";
|
||||
const watched = c.status === "watched";
|
||||
const started = plexStarted(c);
|
||||
// A show continues where its rollup says it stands, as its card's Continue + Restart do.
|
||||
const started = isShow ? c.status === "in_progress" : plexStarted(c);
|
||||
return [
|
||||
...(play
|
||||
? [
|
||||
{
|
||||
id: "play",
|
||||
label: !playable ? t("plex.openShow") : started ? t("card.continue") : t("plex.play"),
|
||||
icon: playable ? <Play className={icon} /> : <Tv2 className={icon} />,
|
||||
label: started ? t("card.continue") : t("plex.play"),
|
||||
icon: <Play className={icon} />,
|
||||
onSelect: () => onOpen(c),
|
||||
},
|
||||
// The same words and glyph as the video card's menu (useVideoActions).
|
||||
@@ -131,18 +136,18 @@ export function usePlexCardActions(handlers: PlexActionHandlers, open: boolean)
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(playable && onInfo
|
||||
...(onInfo
|
||||
? [
|
||||
{
|
||||
id: "details",
|
||||
label: t("plex.info.title"),
|
||||
icon: <Info className={icon} />,
|
||||
label: isShow ? t("plex.openShow") : t("plex.info.title"),
|
||||
icon: isShow ? <Tv2 className={icon} /> : <Info className={icon} />,
|
||||
separatorBefore: true,
|
||||
onSelect: () => onInfo(c),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...copy(c.title, null).map((it) => ({ ...it, separatorBefore: !(playable && onInfo) })),
|
||||
...copy(c.title, null).map((it) => ({ ...it, separatorBefore: !onInfo })),
|
||||
...(withPin ? pin.item(pinPlex(c), c.title) : []),
|
||||
];
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user