feat(player): clip mode - mark a range of what is playing and cut it into a download
Both players get a scissors in their control row (Plex: the control row; YouTube: the actions and the docked bar), `c`, and `[` / `]` to mark at the playhead. The clip panel lives in the player, so the watched video is the preview: an overview of the whole item and a zoomed strip around the selection whose ends are sliders (arrows step a tenth, Shift a second), Start / End fields with nudges and "to the playhead", the length, a looping Play selection, a name, Cut. Escape closes the panel before the player and the focus goes back to the scissors. A cut is a toast with a Downloads link plus the usual Ready notification. - ui/RangeTrack + RulerTicks: the shared, domain-free time track (regions, keyboard-steppable handles with a 1.5rem hit area, a playhead, a background slot, over a window of the item). The Downloads editor's cuts moved onto it, so they are keyboard-steppable now. - lib/clipRange holds the marking rules (1 s to 10 min, the server's), lib/timecode the editing timecode the editor's private tc/parseTc became. - keymap: a symbol typed through AltGr answers its bare-symbol row - on the HU layout `[` is AltGr+F, which arrives as Ctrl+Alt; letter and Mod rows still never match Ctrl+Alt. - The YouTube stage sits in a wrapper with the panel, so the maximised stage's blur escape matches at any depth. - media-scope: fields on the always-dark player chrome read white in a light theme too.
This commit is contained in:
@@ -41,6 +41,19 @@ describe("matches", () => {
|
||||
expect(matches(press({ key: "k", ctrlKey: true, altKey: true }), "Mod+k")).toBe(false);
|
||||
});
|
||||
|
||||
// …but a symbol the layout can only type through AltGr IS that symbol: on the HU layout `[` is
|
||||
// AltGr+F (Ctrl+Alt on Windows), and a bare `[` row must answer it, or the clip marks are dead keys.
|
||||
it("matches a bare-symbol row typed through AltGr, never a letter row or a Mod row", () => {
|
||||
const altGr = (key: string) =>
|
||||
press({ key, ctrlKey: true, altKey: true, modifierAltGraph: true } as KeyboardEventInit);
|
||||
expect(matches(altGr("["), "[")).toBe(true);
|
||||
expect(matches(altGr("]"), "]")).toBe(true);
|
||||
expect(matches(altGr("a"), "a")).toBe(false);
|
||||
expect(matches(altGr("k"), "Mod+k")).toBe(false);
|
||||
// A real Ctrl+Alt chord (no AltGr) on a symbol is still not the bare symbol.
|
||||
expect(matches(press({ key: "[", ctrlKey: true, altKey: true }), "[")).toBe(false);
|
||||
});
|
||||
|
||||
it("treats Mod as Ctrl or Cmd", () => {
|
||||
expect(matches(press({ key: "k", ctrlKey: true }), "Mod+k")).toBe(true);
|
||||
expect(matches(press({ key: "k", metaKey: true }), "Mod+k")).toBe(true);
|
||||
@@ -111,10 +124,23 @@ describe("dispatch", () => {
|
||||
subtitle: noop,
|
||||
info: noop,
|
||||
stats: noop,
|
||||
clip: () => calls.push("clip"),
|
||||
markIn: () => calls.push("markIn"),
|
||||
markOut: () => calls.push("markOut"),
|
||||
back: () => calls.push("back"),
|
||||
escape: () => calls.push("escape"),
|
||||
}) as const;
|
||||
|
||||
it("routes the clip marks, typed plain or through AltGr", () => {
|
||||
const calls: string[] = [];
|
||||
const h = plexHandlers(calls);
|
||||
dispatch(press({ key: "c" }), "plexPlayer", h);
|
||||
dispatch(press({ key: "[" }), "plexPlayer", h);
|
||||
const altGr = { ctrlKey: true, altKey: true, modifierAltGraph: true } as KeyboardEventInit;
|
||||
dispatch(press({ key: "]", ...altGr }), "plexPlayer", h);
|
||||
expect(calls).toEqual(["clip", "markIn", "markOut"]);
|
||||
});
|
||||
|
||||
it("routes each arrow variant to its own id", () => {
|
||||
const calls: string[] = [];
|
||||
const h = plexHandlers(calls);
|
||||
|
||||
Reference in New Issue
Block a user