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:
@@ -152,7 +152,7 @@ export default function PlexBrowse() {
|
||||
jumpTo.type === "show"
|
||||
? { kind: "show", id: jumpTo.id }
|
||||
: jumpTo.type === "play"
|
||||
? { kind: "player", id: jumpTo.id }
|
||||
? { kind: "player", id: jumpTo.id, fromStart: jumpTo.fromStart }
|
||||
: { kind: "info", id: jumpTo.id },
|
||||
// A cross-module hand-off continues the navigation that brought us here rather than adding
|
||||
// to it. It also makes this effect idempotent, which matters more than it looks: under
|
||||
@@ -334,6 +334,11 @@ export default function PlexBrowse() {
|
||||
if (card.type === "show") sub.open({ kind: "show", id: card.id });
|
||||
else sub.open({ kind: "player", id: card.id });
|
||||
}
|
||||
// The started card's Restart (C5): the same trip, from 0 instead of the resume point.
|
||||
function onRestartCard(card: PlexCard) {
|
||||
scrollRef.current = scroller()?.scrollTop ?? 0;
|
||||
sub.open({ kind: "player", id: card.id, fromStart: true });
|
||||
}
|
||||
function onInfo(card: PlexCard) {
|
||||
scrollRef.current = scroller()?.scrollTop ?? 0;
|
||||
rememberInfoScroll(0); // fresh info page starts at the top
|
||||
@@ -370,11 +375,11 @@ export default function PlexBrowse() {
|
||||
resync: () => qc.invalidateQueries({ queryKey: qk.plexLibrary() }),
|
||||
});
|
||||
const actions = usePlexCardActions(
|
||||
{ onOpen: onCard, onInfo, onToggleWatched: toggleWatched },
|
||||
{ onOpen: onCard, onRestart: onRestartCard, onInfo, onToggleWatched: toggleWatched },
|
||||
menu.open,
|
||||
);
|
||||
const epActions = usePlexCardActions(
|
||||
{ onOpen: onCard, onToggleWatched: toggleWatched },
|
||||
{ onOpen: onCard, onRestart: onRestartCard, onToggleWatched: toggleWatched },
|
||||
epMenu.open,
|
||||
);
|
||||
// Apply a metadata filter (clicked on an info page / show hero / cast) then show the unified grid.
|
||||
@@ -408,7 +413,12 @@ export default function PlexBrowse() {
|
||||
<Suspense
|
||||
fallback={createPortal(<div className="fixed inset-0 z-overlay bg-black" />, document.body)}
|
||||
>
|
||||
<PlexPlayer itemId={sub.view.id} queue={sub.view.queue} onClose={sub.back} />
|
||||
<PlexPlayer
|
||||
itemId={sub.view.id}
|
||||
queue={sub.view.queue}
|
||||
fromStart={sub.view.fromStart}
|
||||
onClose={sub.back}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -419,7 +429,9 @@ export default function PlexBrowse() {
|
||||
<PlexPlaylistView
|
||||
playlistId={pid}
|
||||
onBack={sub.back}
|
||||
onPlay={(itemRk, queue) => sub.open({ kind: "player", id: itemRk, queue })}
|
||||
onPlay={(itemRk, queue, fromStart) =>
|
||||
sub.open({ kind: "player", id: itemRk, queue, fromStart })
|
||||
}
|
||||
onInfoCard={onInfo}
|
||||
/>
|
||||
</Suspense>
|
||||
@@ -434,6 +446,7 @@ export default function PlexBrowse() {
|
||||
onBack={sub.back}
|
||||
onPlay={(rk, queue) => sub.open({ kind: "player", id: rk, queue })}
|
||||
onOpenCard={onCard}
|
||||
onRestartCard={onRestartCard}
|
||||
onInfoCard={onInfo}
|
||||
/>
|
||||
</Suspense>
|
||||
@@ -446,7 +459,7 @@ export default function PlexBrowse() {
|
||||
id={infoId}
|
||||
onBack={sub.back}
|
||||
onPlay={() => sub.open({ kind: "player", id: infoId })}
|
||||
onPlayItem={(id) => sub.open({ kind: "player", id })}
|
||||
onPlayItem={(id, fromStart) => sub.open({ kind: "player", id, fromStart })}
|
||||
onFilter={applyFilter}
|
||||
onOpenPerson={onOpenPerson}
|
||||
/>
|
||||
@@ -459,6 +472,7 @@ export default function PlexBrowse() {
|
||||
name={personName}
|
||||
onBack={sub.back}
|
||||
onOpenCard={onCard}
|
||||
onRestartCard={onRestartCard}
|
||||
onInfoCard={onInfo}
|
||||
onFilter={applyFilter}
|
||||
/>
|
||||
@@ -485,7 +499,9 @@ export default function PlexBrowse() {
|
||||
showId={showId}
|
||||
seasonId={seasonId}
|
||||
onBack={sub.back}
|
||||
onPlay={(epRk, queue) => sub.open({ kind: "player", id: epRk, queue })}
|
||||
onPlay={(epRk, queue, fromStart) =>
|
||||
sub.open({ kind: "player", id: epRk, queue, fromStart })
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -510,6 +526,7 @@ export default function PlexBrowse() {
|
||||
<PlexDiscover
|
||||
scope={scope}
|
||||
onOpenCard={onCard}
|
||||
onRestartCard={onRestartCard}
|
||||
onInfoCard={onInfo}
|
||||
onGenre={(g) => applyFilter({ genres: [g] })}
|
||||
onSeeAllRecent={() => sub.open({ kind: "grid" })}
|
||||
@@ -685,6 +702,7 @@ export default function PlexBrowse() {
|
||||
navIndex={i}
|
||||
card={c}
|
||||
onOpen={() => onCard(c)}
|
||||
onRestart={() => onRestartCard(c)}
|
||||
onToggleWatched={toggleWatched}
|
||||
onAdd={actions.add}
|
||||
menuProps={menu.triggerProps}
|
||||
@@ -707,6 +725,7 @@ export default function PlexBrowse() {
|
||||
ep={ep}
|
||||
withShowTitle
|
||||
onPlay={() => onCard(ep)}
|
||||
onRestart={() => onRestartCard(ep)}
|
||||
onToggleWatched={() => toggleWatched(ep)}
|
||||
onAdd={() => epActions.add(ep)}
|
||||
menuProps={epMenu.triggerProps}
|
||||
|
||||
Reference in New Issue
Block a user