Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions webapp/src/components/KeyboardControlsHelp.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from "react";
import * as Blockly from "blockly";
import { getShortcutKeysShortAll, LIST_SHORTCUTS_SHORTCUT } from "../shortcut_formatting";
import { CONTROL_KEY_SHORT, getShortcutKeysShortAll, LIST_SHORTCUTS_SHORTCUT } from "../shortcut_formatting";

const names = Blockly.ShortcutItems.names;
const isMacPlatform = pxt.BrowserUtils.isMac();
Expand All @@ -10,20 +10,19 @@ const KeyboardControlsHelp = () => {
React.useEffect(() => {
ref.current?.focus()
}, []);
const ctrl = lf("{id:keyboard symbol}Ctrl");
const ctrl = CONTROL_KEY_SHORT;
const cmd = isMacPlatform ? "⌘" : ctrl;
const orAsJoiner = lf("or")
const enterOrSpace = { shortcuts: getShortcutKeysShortAll(names.PERFORM_ACTION), joiner: orAsJoiner}
// Split around {0} so the modifier renders as a <Key> (for its aria-label).
const moveAnywhere = lf("Hold {0} and press arrow keys").split("{0}");
return (
<aside id="keyboardnavhelp" aria-label={lf("Keyboard Controls")} ref={ref} tabIndex={0}>
<h2>{lf("Keyboard Controls")}</h2>
<h3>{lf("Global")}</h3>
<table>
<tbody>
<Row name={lf("Show/hide shortcut help")} shortcuts={[LIST_SHORTCUTS_SHORTCUT]} />
<Row name={lf("Screen reader mode")} shortcuts={[names.TOGGLE_SCREENREADER]}>
<p className="hint">{lf("Additional audio cues and navigation aids for screen reader users")}</p>
</Row>
<Row name={lf("Move between menus, simulator and the workspace")} shortcuts={[[lf("{id:keyboard symbol}Tab")], [lf("{id:keyboard symbol}Shift"), lf("{id:keyboard symbol}Tab")]]} joiner="row"/>
<Row name={lf("Area menu")} shortcuts={[[cmd, "B"]]}>
<p className="hint">{lf("Then press an area's number, or Tab to it and press Enter")}</p>
Expand All @@ -37,14 +36,17 @@ const KeyboardControlsHelp = () => {
<Row name={lf("Next block stack")} shortcuts={[names.NEXT_STACK]} />
<Row name={lf("Previous block stack")} shortcuts={[names.PREVIOUS_STACK]} />
<Row name={lf("Select workspace")} shortcuts={[names.FOCUS_WORKSPACE]} />
<Row name={lf("Open context menu")} shortcuts={[names.MENU]} />
<Row name={lf("Context menu")} shortcuts={[names.MENU]} />
<Row name={lf("Format code")} shortcuts={[names.CLEANUP]} />
<Row name={lf("Undo / redo")} shortcuts={[names.UNDO, names.REDO]} joiner="/" />
{pxt.canDownload() &&
<Row name={lf("Download your code to the {0}", pxt.appTarget.appTheme.boardName)} shortcuts={[["L"]]} />}
<Row name={lf("Toolbox")} shortcuts={[names.FOCUS_TOOLBOX]} />
{pxt.appTarget.simulator &&
<Row name={lf("Start or stop simulator")} shortcuts={[["S"]]} />}
<Row name={lf("Screen reader mode")} shortcuts={[names.TOGGLE_SCREENREADER]}>
<p className="hint">{lf("Additional audio cues and navigation aids for screen reader users")}</p>
</Row>
</tbody>
</table>
<h3>{lf("Toolbox")}</h3>
Expand Down Expand Up @@ -78,7 +80,7 @@ const KeyboardControlsHelp = () => {
<tbody>
<Row name={lf("Move to positions")} shortcuts={[names.NAVIGATE_UP, names.NAVIGATE_DOWN, names.NAVIGATE_LEFT, names.NAVIGATE_RIGHT]} />
<Row name={lf("Move anywhere")}>
{lf("Hold {0} and press arrow keys", cmd)}
{moveAnywhere[0]}<Key value={cmd} />{moveAnywhere[1]}
</Row>
<Row name={lf("Confirm")} {...enterOrSpace} />
<Row name={lf("Cancel")} shortcuts={[names.ABORT_MOVE]} />
Expand Down Expand Up @@ -177,6 +179,10 @@ const Key = ({ value }: { value: string }) => {
aria = lf("Option");
break;
}
case CONTROL_KEY_SHORT: {
aria = Blockly.Msg['CONTROL_KEY'];
break;
}
}
return <span className="key" aria-label={aria}>{value}</span>
}
Expand Down
21 changes: 15 additions & 6 deletions webapp/src/shortcut_formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import * as Blockly from 'blockly';

const isMacPlatform = pxt.BrowserUtils.isMac();

export const CONTROL_KEY_SHORT = lf("{id:keyboard symbol}Ctrl");

/**
* Name of the shortcut that opens the help dialog. Registered in blocks.tsx
* initKeyboardControls. The literal string is also hardcoded by Blockly core's
Expand Down Expand Up @@ -47,7 +49,7 @@ const longModifierNames: Record<string, string> = {
};

const shortModifierNames: Record<string, string> = {
'Control': Blockly.Msg['CONTROL_KEY'],
'Control': CONTROL_KEY_SHORT,
'Meta': '⌘',
'Alt': isMacPlatform ? '⌥' : Blockly.Msg['ALT_KEY'],
};
Expand Down Expand Up @@ -149,7 +151,8 @@ function getKeyName(keyCode: number): string {
*
* Mirrors Blockly's internal getShortcutKeys (core/utils/shortcut_formatting.ts).
* Kept as a local copy because Blockly doesn't expose this on its public API
* surface; the algorithm should track Blockly's version.
* surface; the algorithm tracks Blockly's version with an exception to avoid
* the menu key for the context menu shortcut (not present on all keyboards).
*
* @param shortcutName The action name, e.g. "cut".
* @param modifierNames The names to use for the Meta/Control/Alt modifiers.
Expand Down Expand Up @@ -186,12 +189,13 @@ function getShortcutKeys(

// If there are modifiers return only one shortcut on the assumption they are
// intended for different platforms. Otherwise assume they are alternatives.
// Skips a bare key here for the menu key case.
const hasModifiers = currentPlatform.some((shortcut) =>
shortcut.some(
(key) => "Meta" === key || "Alt" === key || "Control" === key,
),
shortcut.some(isModifierName),
);
const chosen = hasModifiers ? [currentPlatform[0]] : currentPlatform;
const chosen = hasModifiers
? [currentPlatform.find((shortcut) => shortcut.some(isModifierName)) ?? currentPlatform[0]]
: currentPlatform;
return chosen.map((shortcut) =>
shortcut
.map((maybeNumeric) =>
Expand All @@ -217,3 +221,8 @@ function modifierOrder(key: string): number {
// Regular keys at the end.
return order === -1 ? Number.MAX_VALUE : order;
}

/** Whether a serialized key part is one of the (non-Shift) modifier names. */
function isModifierName(key: string): boolean {
return key === "Meta" || key === "Alt" || key === "Control";
}