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:
@@ -10,6 +10,7 @@ import {
|
||||
PinOff,
|
||||
Play,
|
||||
RotateCcw,
|
||||
SkipBack,
|
||||
Star,
|
||||
} from "lucide-react";
|
||||
import { qk } from "../lib/queryKeys";
|
||||
@@ -51,7 +52,8 @@ import { SURFACE } from "./ui/surfaces";
|
||||
import clsx from "clsx";
|
||||
import { IconButton } from "./ui/IconButton";
|
||||
import { MoreButton } from "./ui/MoreButton";
|
||||
import { HOVER_YIELD } from "./ui/reveal";
|
||||
import { HOVER_REVEAL, HOVER_YIELD } from "./ui/reveal";
|
||||
import { MediaResumeButtons } from "./MediaResumeButtons";
|
||||
import { MediaCard, MediaPlayGlyph } from "./ui/MediaCard";
|
||||
import {
|
||||
MEDIA_BAR,
|
||||
@@ -171,6 +173,17 @@ function useHomeCardMenu({
|
||||
disabled: !(target.next ?? target).video && !(target.next ?? target).plex,
|
||||
onSelect: () => onPlay(target),
|
||||
},
|
||||
// What the Play row would continue can also start over (C5), with the card menus' words.
|
||||
...((target.next ?? target).position_seconds > 0
|
||||
? [
|
||||
{
|
||||
id: "restart",
|
||||
label: t("card.restartTitle"),
|
||||
icon: <SkipBack className="w-4 h-4" />,
|
||||
onSelect: () => onPlay(target, 0),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
...(target.video
|
||||
? videoActions
|
||||
.items(target.video, menu.anchor, { pin: false })
|
||||
@@ -344,6 +357,7 @@ function Rail({
|
||||
shelf
|
||||
navIndex={i}
|
||||
onOpen={() => onPlay(c)}
|
||||
onRestart={() => onPlay(c, 0)}
|
||||
onToggleWatched={onToggleWatched}
|
||||
onAdd={plexAdd}
|
||||
next={c.next?.plex}
|
||||
@@ -569,10 +583,21 @@ function Hero({
|
||||
/>
|
||||
</span>
|
||||
) : null}
|
||||
<div className={MEDIA_HOVER_LAYER}>
|
||||
<MediaPlayGlyph />
|
||||
</div>
|
||||
<div className={MEDIA_HOVER_LAYER}>{!started && <MediaPlayGlyph />}</div>
|
||||
</Pressable>
|
||||
{/* A started hero continues on a click, and offers the start as a card does (C5): the pair
|
||||
is a sibling over the picture, never inside its button. */}
|
||||
{started && (
|
||||
<div
|
||||
className={clsx(
|
||||
HOVER_REVEAL,
|
||||
"pointer-events-none absolute inset-0 z-base flex items-center justify-center gap-2 *:pointer-events-auto",
|
||||
card.aspect === "poster" && "flex-col",
|
||||
)}
|
||||
>
|
||||
<MediaResumeButtons onContinue={() => onPlay(card)} onRestart={() => onPlay(card, 0)} />
|
||||
</div>
|
||||
)}
|
||||
{/* The card's own hover row, as on every card: a sibling of the picture's button. */}
|
||||
{card.video ? (
|
||||
<div className={MEDIA_BAR}>
|
||||
@@ -851,6 +876,7 @@ export default function Home() {
|
||||
setJumpTo({
|
||||
id: target.plex.id,
|
||||
type: target.plex.type === "show" ? "show" : "play",
|
||||
fromStart: startAt === 0,
|
||||
replace: true,
|
||||
});
|
||||
setPage("plex");
|
||||
|
||||
Reference in New Issue
Block a user