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:
2026-09-27 02:12:54 +02:00
parent 17916b411a
commit 89e65ca331
33 changed files with 1100 additions and 282 deletions
+24 -1
View File
@@ -102,7 +102,12 @@ function Grid({
}
function setup(c: PlexCard, opts: Omit<Parameters<typeof Grid>[0], "c" | "handlers"> = {}) {
const handlers = { onOpen: vi.fn(), onInfo: vi.fn(), onToggleWatched: vi.fn() };
const handlers = {
onOpen: vi.fn(),
onInfo: vi.fn(),
onToggleWatched: vi.fn(),
onRestart: vi.fn(),
};
render(
<QueryClientProvider client={new QueryClient()}>
<FloatingTree>
@@ -131,6 +136,24 @@ describe("a Plex title's menu", () => {
]);
});
// C5: on touch the card's hover pair is gone, so the menu is where a started title starts over.
it("a started title continues from its Play row and starts over from the next one", async () => {
const { handlers } = setup(card({ status: "in_progress", position_seconds: 600 }));
rightClick();
expect(labels().slice(0, 2)).toEqual(["card.continue", "card.restartTitle"]);
fireEvent.click(item("card.restartTitle"));
// A row acts a task after the menu closes (closeThenAct).
await vi.waitFor(() =>
expect(handlers.onRestart).toHaveBeenCalledWith(expect.objectContaining({ id: "m1" })),
);
});
it("a watched title has no restart row: it plays from the start anyway", () => {
setup(card({ status: "watched", position_seconds: 600 }));
rightClick();
expect(labels()).not.toContain("card.restartTitle");
});
it("a show opens its page and has no watched, add or details rows", () => {
setup(card({ type: "show" }));
rightClick();