fix(plex): a card offers Restart by the menu's own "started" rule
The picture read "started" off the rounded progress %, so a resume point under 1 % showed Play while the card's menu offered "from the start". Both use `plexStarted` now.
This commit is contained in:
@@ -208,6 +208,19 @@ describe("a started Plex card", () => {
|
|||||||
expect(container.querySelector(".flex-col.reveal-hover")).toBeNull();
|
expect(container.querySelector(".flex-col.reveal-hover")).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// E28 review D4: the card read "started" off the rounded progress %, the menu off the position —
|
||||||
|
// 20 s into a 3 h film is 0 % here and started there, so the card offered no Restart the menu had.
|
||||||
|
it("a resume point under 1 % still offers the Restart, as the card's menu does", () => {
|
||||||
|
render(
|
||||||
|
<PlexPosterCard
|
||||||
|
card={{ ...movie("in_progress"), position_seconds: 20, duration_seconds: 10800 }}
|
||||||
|
onOpen={vi.fn()}
|
||||||
|
onRestart={vi.fn()}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
expect(screen.getByRole("button", { name: "card.restart" })).toBeTruthy();
|
||||||
|
});
|
||||||
|
|
||||||
it("a not-started or watched card keeps the one Play", () => {
|
it("a not-started or watched card keeps the one Play", () => {
|
||||||
render(<PlexPosterCard card={movie("watched")} onOpen={vi.fn()} onRestart={vi.fn()} />);
|
render(<PlexPosterCard card={movie("watched")} onOpen={vi.fn()} onRestart={vi.fn()} />);
|
||||||
expect(screen.queryByRole("button", { name: "card.restart" })).toBeNull();
|
expect(screen.queryByRole("button", { name: "card.restart" })).toBeNull();
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Carousel } from "./ui/Carousel";
|
|||||||
import type { PlexCard, PlexCastMember, PlexFilters, PlexSeasonDetail } from "../lib/api";
|
import type { PlexCard, PlexCastMember, PlexFilters, PlexSeasonDetail } from "../lib/api";
|
||||||
import { navItemAttrs } from "../lib/listNav";
|
import { navItemAttrs } from "../lib/listNav";
|
||||||
import { endsTextSelection, movedPastSlop } from "../lib/textSelection";
|
import { endsTextSelection, movedPastSlop } from "../lib/textSelection";
|
||||||
import { plexAddLabelKey } from "./usePlexCardActions";
|
import { plexAddLabelKey, plexStarted } from "./usePlexCardActions";
|
||||||
import { IconButton } from "./ui/IconButton";
|
import { IconButton } from "./ui/IconButton";
|
||||||
import { MoreButton } from "./ui/MoreButton";
|
import { MoreButton } from "./ui/MoreButton";
|
||||||
import { Pressable } from "./ui/Pressable";
|
import { Pressable } from "./ui/Pressable";
|
||||||
@@ -186,6 +186,7 @@ function PlexPicture({
|
|||||||
plays,
|
plays,
|
||||||
small = false,
|
small = false,
|
||||||
pct = 0,
|
pct = 0,
|
||||||
|
started = false,
|
||||||
bar,
|
bar,
|
||||||
children,
|
children,
|
||||||
}: {
|
}: {
|
||||||
@@ -201,23 +202,26 @@ function PlexPicture({
|
|||||||
/** A shelf's short landscape picture: the smaller Play, clear of the hover row under it. */
|
/** A shelf's short landscape picture: the smaller Play, clear of the hover row under it. */
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
pct?: number;
|
pct?: number;
|
||||||
|
/** Has a resume point (`plexStarted`, the card menu's own rule): Continue + Restart, not Play. */
|
||||||
|
started?: boolean;
|
||||||
bar: ReactNode;
|
bar: ReactNode;
|
||||||
/** The badges printed on the picture. */
|
/** The badges printed on the picture. */
|
||||||
children?: ReactNode;
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
// The pct is the resume point's: a watched or untouched item has none, and plays from its start.
|
// Not read off `pct`: that is rounded, so 20 s into a 3 h film was 0 % and "not started" here while
|
||||||
const started = plays && pct > 0 && !!onRestart;
|
// the menu offered "from the start" (E28 review D4). One rule, `plexStarted`, for both.
|
||||||
|
const resumable = plays && started && !!onRestart;
|
||||||
return (
|
return (
|
||||||
<div className={mediaPictureBox(aspect)}>
|
<div className={mediaPictureBox(aspect)}>
|
||||||
<Pressable onClick={onOpen} aria-label={label} data-nav-open="" className={MEDIA_PICTURE}>
|
<Pressable onClick={onOpen} aria-label={label} data-nav-open="" className={MEDIA_PICTURE}>
|
||||||
<img src={thumb} alt="" loading="lazy" decoding="async" className={MEDIA_IMG} />
|
<img src={thumb} alt="" loading="lazy" decoding="async" className={MEDIA_IMG} />
|
||||||
{children}
|
{children}
|
||||||
<div className={MEDIA_HOVER_LAYER}>
|
<div className={MEDIA_HOVER_LAYER}>
|
||||||
{plays && !started && <MediaPlayGlyph small={small} />}
|
{plays && !resumable && <MediaPlayGlyph small={small} />}
|
||||||
</div>
|
</div>
|
||||||
<MediaProgress pct={pct} />
|
<MediaProgress pct={pct} />
|
||||||
</Pressable>
|
</Pressable>
|
||||||
{started && (
|
{resumable && (
|
||||||
<div
|
<div
|
||||||
className={clsx(
|
className={clsx(
|
||||||
HOVER_REVEAL,
|
HOVER_REVEAL,
|
||||||
@@ -332,6 +336,7 @@ export function PlexPosterCard({
|
|||||||
plays={isPlayable}
|
plays={isPlayable}
|
||||||
small={shelf && landscape}
|
small={shelf && landscape}
|
||||||
pct={pct}
|
pct={pct}
|
||||||
|
started={plexStarted(acted)}
|
||||||
bar={
|
bar={
|
||||||
<HoverBar
|
<HoverBar
|
||||||
watched={actedWatched}
|
watched={actedWatched}
|
||||||
@@ -647,6 +652,7 @@ export function EpisodeCard({
|
|||||||
onRestart={onRestart}
|
onRestart={onRestart}
|
||||||
plays
|
plays
|
||||||
pct={pct}
|
pct={pct}
|
||||||
|
started={plexStarted(ep)}
|
||||||
bar={
|
bar={
|
||||||
<HoverBar
|
<HoverBar
|
||||||
watched={watched}
|
watched={watched}
|
||||||
|
|||||||
Reference in New Issue
Block a user