Skip to content

Commit 08c635b

Browse files
microbit-matt-hillsdonmicrobit-grace
authored andcommitted
Add global shortcuts to improve keyboard navigation
This PR adds three shortcuts to help keyboard users navigate to the editor (workspace), toolbox and trigger the hex download. Co-authored-by: Grace <145345672+microbit-grace@users.noreply.github.com>
1 parent d5f19f0 commit 08c635b

File tree

5 files changed

+83
-0
lines changed

5 files changed

+83
-0
lines changed

localtypings/pxteditor.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ declare namespace pxt.editor {
716716
zoomOut(): void;
717717
resize(): void;
718718
setScale(scale: number): void;
719+
focusWorkspace(): void;
719720
}
720721

721722
export interface IFile {

pxtsim/accessibility.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,30 @@ namespace pxsim.accessibility {
88
elem.setAttribute("tabindex", "0");
99
}
1010

11+
export function getKeyboardShortcutEditorAction(e: KeyboardEvent): pxsim.SimulatorActionMessage["type"] | null {
12+
const meta = e.metaKey || e.ctrlKey;
13+
if (e.key === "e" && meta) {
14+
e.preventDefault();
15+
return "focusWorkspace"
16+
} else if (e.key === "b" && meta) {
17+
e.preventDefault();
18+
return "focusSimulator"
19+
} else if (e.key === "d" && meta) {
20+
e.preventDefault();
21+
return "webUSBDownload"
22+
}
23+
return null
24+
}
25+
26+
export function postKeyboardEvent() {
27+
document.addEventListener("keydown", (e) => {
28+
const action = getKeyboardShortcutEditorAction(e)
29+
if (action) {
30+
Runtime.postMessage({ type: action })
31+
}
32+
});
33+
}
34+
1135
export function enableKeyboardInteraction(elem: Element, handlerKeyDown?: () => void, handlerKeyUp?: () => void): void {
1236
if (handlerKeyDown) {
1337
elem.addEventListener('keydown', (e: KeyboardEvent) => {

pxtsim/embed.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ namespace pxsim {
7575
url: string;
7676
}
7777

78+
export interface SimulatorActionMessage extends SimulatorMessage {
79+
type: "toggleShortcutDoc" | "focusWorkspace" | "focusSimulator" | "webUSBDownload";
80+
}
81+
7882
export interface SimulatorStateMessage extends SimulatorMessage {
7983
type: "status";
8084
frameid?: string;

theme/common.less

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,11 @@ div.simframe > iframe {
347347
top:0; left: 0; width:100%; height:100%;
348348
}
349349

350+
#boardview:focus-visible {
351+
outline: 3px solid var(--pxt-focus-border);
352+
outline-offset: 3px;
353+
}
354+
350355
.simHeadless {
351356
height: 0 !important;
352357
width: 0 !important;

webapp/src/blocks.tsx

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import * as dialogs from "./dialogs";
1717
import * as blocklyFieldView from "./blocklyFieldView";
1818
import { CreateFunctionDialog } from "./createFunction";
1919
import { initializeSnippetExtensions } from './snippetBuilder';
20+
import * as cmds from "./cmds"
2021

2122
import * as pxtblockly from "../../pxtblocks";
2223
import { KeyboardNavigation } from '@blockly/keyboard-experiment';
@@ -39,6 +40,7 @@ import { PathObject } from "../../pxtblocks/plugins/renderer/pathObject";
3940
import { Measurements } from "./constants";
4041
import { flow } from "../../pxtblocks";
4142
import { HIDDEN_CLASS_NAME } from "../../pxtblocks/plugins/flyout/blockInflater";
43+
import { userPrefersDownloadFlagSet } from "./webusb";
4244

4345
interface CopyDataEntry {
4446
version: 1;
@@ -580,6 +582,53 @@ export class Editor extends toolboxeditor.ToolboxEditor {
580582
return true
581583
}
582584
});
585+
586+
const triggerEditorAction = (action: pxsim.SimulatorActionMessage["type"]) => {
587+
switch (action) {
588+
case "focusWorkspace": {
589+
this.parent.editor.focusWorkspace();
590+
return
591+
}
592+
case "focusSimulator": {
593+
// Note that pxtsim.driver.focus() isn't the same as tabbing to the sim.
594+
(document.querySelector("#boardview") as HTMLElement).focus();
595+
return
596+
}
597+
case "webUSBDownload": {
598+
(async () => {
599+
// TODO: refactor and share with editortoolbar.tsx
600+
const shouldShowPairingDialogOnDownload = pxt.appTarget.appTheme.preferWebUSBDownload
601+
&& pxt.appTarget?.compile?.webUSB
602+
&& pxt.usb.isEnabled
603+
&& !userPrefersDownloadFlagSet();
604+
if (shouldShowPairingDialogOnDownload
605+
&& !pxt.packetio.isConnected()
606+
&& !pxt.packetio.isConnecting()
607+
) {
608+
await cmds.pairAsync(true);
609+
}
610+
this.parent.compile();
611+
})();
612+
return
613+
}
614+
}
615+
}
616+
617+
const simulatorOrigins = [
618+
window.location.origin,
619+
// Simulator deployed origin.
620+
"https://trg-microbit.userpxt.io"
621+
]
622+
window.addEventListener("message", (e: MessageEvent<pxsim.SimulatorActionMessage>) => {
623+
// Listen to simulator iframe keydown post messages.
624+
if (simulatorOrigins.includes(e.origin)) {
625+
triggerEditorAction(e.data.type)
626+
}
627+
}, false)
628+
document.addEventListener("keydown", (e: KeyboardEvent) => {
629+
const action = pxsim.accessibility.getKeyboardShortcutEditorAction(e)
630+
triggerEditorAction(action)
631+
});
583632
}
584633
}
585634

0 commit comments

Comments
 (0)