feat(design): players on touch and the C5 control set, restart on the card (E28 S3f2)
Plex player: a finger's tap on the picture only shows or hides the controls, the hide timer starts with playback, a double-tap on a side third skips 10 s, the seek bar is a 2.75rem touch-none hit area with its head shown on touch. The row is play/pause, Back by position, Next only with a next item, mute, time, tracks, a fixed ⋯ (settings, media info, stats, shortcuts, download) and fullscreen; Stop, Restart and the ±10 s buttons are gone. A started card shows the video card's Continue + Restart pair (one shared MediaResumeButtons, beside the picture's button), and its menu lists the restart; the player opens that item at 0 (fromStart). YouTube modal: the description opens from an ⓘ, the queue arrows sit inside the screen.
This commit is contained in:
@@ -11,7 +11,8 @@ import { plexAddLabelKey } from "./usePlexCardActions";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
import { MoreButton } from "./ui/MoreButton";
|
||||
import { Pressable } from "./ui/Pressable";
|
||||
import { HOVER_YIELD } from "./ui/reveal";
|
||||
import { HOVER_REVEAL, HOVER_YIELD } from "./ui/reveal";
|
||||
import { MediaResumeButtons } from "./MediaResumeButtons";
|
||||
import {
|
||||
MEDIA_BADGE,
|
||||
MEDIA_BAR,
|
||||
@@ -173,12 +174,15 @@ function CardTitle({
|
||||
}
|
||||
|
||||
/** A Plex card's picture: the whole box is the card's open/play button (Enter opens it from the
|
||||
* keyboard walk), with the hover row docked beside it rather than inside it. */
|
||||
* keyboard walk), with the hover row docked beside it rather than inside it. A started item shows the
|
||||
* video card's Continue + Restart pair instead of the Play (C5); like the hover row, the pair is a
|
||||
* sibling laid over the picture, never inside its button. */
|
||||
function PlexPicture({
|
||||
aspect,
|
||||
thumb,
|
||||
label,
|
||||
onOpen,
|
||||
onRestart,
|
||||
plays,
|
||||
small = false,
|
||||
pct = 0,
|
||||
@@ -190,6 +194,8 @@ function PlexPicture({
|
||||
/** The button's name: the picture has no text of its own. */
|
||||
label: string;
|
||||
onOpen: () => void;
|
||||
/** Play from the start, where the surface can (the player's `fromStart`). */
|
||||
onRestart?: () => void;
|
||||
/** The click plays (a movie, an episode) — only then does the hover draw a Play. */
|
||||
plays: boolean;
|
||||
/** A shelf's short landscape picture: the smaller Play, clear of the hover row under it. */
|
||||
@@ -199,6 +205,8 @@ function PlexPicture({
|
||||
/** The badges printed on the picture. */
|
||||
children?: ReactNode;
|
||||
}) {
|
||||
// The pct is the resume point's: a watched or untouched item has none, and plays from its start.
|
||||
const started = plays && pct > 0 && !!onRestart;
|
||||
return (
|
||||
<div className={mediaPictureBox(aspect)}>
|
||||
<div
|
||||
@@ -217,9 +225,23 @@ function PlexPicture({
|
||||
>
|
||||
<img src={thumb} alt="" loading="lazy" decoding="async" className={MEDIA_IMG} />
|
||||
{children}
|
||||
<div className={MEDIA_HOVER_LAYER}>{plays && <MediaPlayGlyph small={small} />}</div>
|
||||
<div className={MEDIA_HOVER_LAYER}>
|
||||
{plays && !started && <MediaPlayGlyph small={small} />}
|
||||
</div>
|
||||
<MediaProgress pct={pct} />
|
||||
</div>
|
||||
{started && (
|
||||
<div
|
||||
className={clsx(
|
||||
HOVER_REVEAL,
|
||||
"pointer-events-none absolute inset-0 z-base flex items-center justify-center gap-2 *:pointer-events-auto",
|
||||
// A poster is too narrow for the pair side by side (150px); it stacks, as in the lab.
|
||||
aspect === "poster" && "flex-col",
|
||||
)}
|
||||
>
|
||||
<MediaResumeButtons small={small} onContinue={onOpen} onRestart={onRestart} />
|
||||
</div>
|
||||
)}
|
||||
{bar}
|
||||
</div>
|
||||
);
|
||||
@@ -243,9 +265,12 @@ export function PlexPosterCard({
|
||||
shelf = false,
|
||||
next,
|
||||
onInfo,
|
||||
onRestart,
|
||||
}: {
|
||||
card: PlexCard;
|
||||
onOpen: () => void;
|
||||
/** Play the item (or the `next` episode) from its start, not its resume point (C5). */
|
||||
onRestart?: () => void;
|
||||
onToggleWatched?: (c: PlexCard) => void;
|
||||
/** Add a movie or an episode to a playlist (the list's `usePlexCardActions().add`). */
|
||||
onAdd?: (c: PlexCard) => void;
|
||||
@@ -316,6 +341,7 @@ export function PlexPosterCard({
|
||||
thumb={(wide && card.type !== "episode" && card.art) || card.thumb}
|
||||
label={card.title}
|
||||
onOpen={onOpen}
|
||||
onRestart={onRestart}
|
||||
plays={isPlayable}
|
||||
small={shelf && landscape}
|
||||
pct={pct}
|
||||
@@ -591,6 +617,7 @@ export function RelatedStrip({
|
||||
export function EpisodeCard({
|
||||
ep,
|
||||
onPlay,
|
||||
onRestart,
|
||||
onAdd,
|
||||
onToggleWatched,
|
||||
withShowTitle,
|
||||
@@ -601,6 +628,8 @@ export function EpisodeCard({
|
||||
}: {
|
||||
ep: PlexCard;
|
||||
onPlay: () => void;
|
||||
/** Play the episode from its start, not its resume point (C5). */
|
||||
onRestart?: () => void;
|
||||
onAdd?: () => void;
|
||||
onToggleWatched?: () => void;
|
||||
withShowTitle?: boolean;
|
||||
@@ -628,6 +657,7 @@ export function EpisodeCard({
|
||||
thumb={ep.thumb}
|
||||
label={ep.title}
|
||||
onOpen={onPlay}
|
||||
onRestart={onRestart}
|
||||
plays
|
||||
pct={pct}
|
||||
bar={
|
||||
|
||||
Reference in New Issue
Block a user