feat(e10): a jump-to palette on Ctrl/Cmd+K
Reaching a channel three modules away cost a rail, a page and a scroll. Now it costs a keystroke and half a word: Mod+K searches modules, channels, playlists, the saved views applicable where you stand, and the app-wide actions. Scope agreed with the product owner: navigation + actions, but only the reversible local ones (the shortcuts sheet, the theme). "Sync from YouTube" is deliberately absent — it is a long-running job against a quota, it wants the progress and error surface the Channels page already gives it, and one mistyped Enter is a worse failure than the walk it would have saved. Matching is word-wise and accent-folded, because this is a bilingual app and nobody reaches for the accent keys in a hurry: "pod thevr" finds TheVR Podcast and "penzugy" finds pénzügy. The arrows walk one flat result list so crossing a group heading is invisible, and the field carries the combobox/listbox aria tree that virtual selection needs. ui/CommandPalette is domain-free (items are a prop, per the ui/ import rule); AppCommandPalette supplies them. The shell is Modal again, with a new `top` placement so a search-first dialog grows downwards instead of jumping as results change. Also fixes a bug the palette hit on its first run, and that PlayerModal had already hit once: closing a dialog reclaims its Back-to-close history entry with a programmatic history.back(), which undoes any pushState made before it lands — so a command that navigates only closed the palette. The rule is now closeThenNavigate() in history.ts with a test, and PlayerModal's inline copy is gone. The global `h` moves out of KeyboardHelp into useGlobalKeymap beside Mod+K, so one listener owns the whole `global` scope and dispatch's exhaustiveness keeps meaning what it says.
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
import { Fragment, useEffect } from "react";
|
||||
import { Fragment } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import Modal from "./Modal";
|
||||
import {
|
||||
closeKeyboardHelp,
|
||||
dispatch,
|
||||
formatChord,
|
||||
helpLines,
|
||||
toggleKeyboardHelp,
|
||||
useActiveScopes,
|
||||
useHelpConditionValues,
|
||||
useKeyboardHelpOpen,
|
||||
} from "../../lib/keymap";
|
||||
|
||||
// The app's one keyboard cheat sheet, plus the global `h` that opens it. Mounted once by App.
|
||||
// The app's one keyboard cheat sheet. Mounted once by App; the global `h` that opens it lives in
|
||||
// `useGlobalKeymap`, beside the palette's Mod+K, so one listener owns the whole `global` scope.
|
||||
//
|
||||
// It renders the shared SHORTCUTS table filtered to the scopes that are live right now — the Plex
|
||||
// player's keys while you are in the player, the app-wide ones underneath — so there is a single
|
||||
@@ -31,19 +30,6 @@ export default function KeyboardHelp() {
|
||||
const scopes = useActiveScopes();
|
||||
const conditions = useHelpConditionValues();
|
||||
|
||||
useEffect(() => {
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
dispatch(e, "global", {
|
||||
help: (ev) => {
|
||||
ev.preventDefault();
|
||||
toggleKeyboardHelp();
|
||||
},
|
||||
});
|
||||
};
|
||||
window.addEventListener("keydown", onKey);
|
||||
return () => window.removeEventListener("keydown", onKey);
|
||||
}, []);
|
||||
|
||||
if (!open) return null;
|
||||
// A `when` row is listed only where it applies — "Previous / next episode" inside a movie would be
|
||||
// a promise the player does not keep.
|
||||
|
||||
Reference in New Issue
Block a user