Skip to content

Commit bee1a5e

Browse files
committed
Navigation between top-level stacks
1 parent ef1faa8 commit bee1a5e

2 files changed

Lines changed: 87 additions & 0 deletions

File tree

src/actions/stack_navigation.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {
8+
ShortcutRegistry,
9+
WorkspaceSvg,
10+
getFocusManager,
11+
utils,
12+
} from 'blockly/core';
13+
import { KeyboardShortcut } from 'node_modules/blockly/core/shortcut_registry';
14+
15+
const SHORTCUT_PREV_STACK = utils.KeyCodes.P;
16+
const SHORTCUT_NEXT_STACK = utils.KeyCodes.N;
17+
18+
/**
19+
* Class for registering a shortcut for quick movement between root blocks.
20+
*/
21+
export class StackNavigationAction {
22+
stackShortcuts: KeyboardShortcut[] = [];
23+
install() {
24+
const preconditionFn = (workspace: WorkspaceSvg) => {
25+
const currentRoot = workspace?.getCursor()?.getSourceBlock()?.getRootBlock();
26+
return !!currentRoot;
27+
}
28+
29+
const previousStackShortcut: KeyboardShortcut = {
30+
name: 'go_to_previous_stack',
31+
preconditionFn,
32+
callback: (workspace) => {
33+
const currentRoot = workspace?.getCursor()?.getSourceBlock()?.getRootBlock();
34+
if (!currentRoot) return false;
35+
const rootList = workspace.getTopBlocks();
36+
37+
const index = rootList.indexOf(currentRoot);
38+
let newIndex;
39+
do {
40+
newIndex = (index - 1 + rootList.length) % rootList.length;
41+
} while (!rootList[newIndex].canBeFocused());
42+
43+
getFocusManager().focusNode(rootList[newIndex]);
44+
return true;
45+
},
46+
keyCodes: [SHORTCUT_PREV_STACK],
47+
};
48+
49+
const nextStackShortcut: KeyboardShortcut = {
50+
name: 'go_to_next_stack',
51+
preconditionFn,
52+
callback: (workspace) => {
53+
const currentRoot = workspace?.getCursor()?.getSourceBlock()?.getRootBlock();
54+
if (!currentRoot) return false;
55+
const rootList = workspace.getTopBlocks();
56+
57+
const index = rootList.indexOf(currentRoot);
58+
let newIndex;
59+
do {
60+
newIndex = (index + 1) % rootList.length;
61+
} while (!rootList[newIndex].canBeFocused());
62+
63+
getFocusManager().focusNode(rootList[newIndex]);
64+
return true;
65+
},
66+
keyCodes: [SHORTCUT_NEXT_STACK],
67+
};
68+
69+
ShortcutRegistry.registry.register(previousStackShortcut);
70+
this.stackShortcuts.push(previousStackShortcut);
71+
ShortcutRegistry.registry.register(nextStackShortcut);
72+
this.stackShortcuts.push(nextStackShortcut);
73+
}
74+
75+
/**
76+
* Reverts the patched undo/redo shortcuts in the registry.
77+
*/
78+
uninstall() {
79+
this.stackShortcuts.forEach(ss => ShortcutRegistry.registry.unregister(ss.name));
80+
this.stackShortcuts = [];
81+
}
82+
}

src/navigation_controller.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import {ActionMenu} from './actions/action_menu';
3636
import {MoveActions} from './actions/move';
3737
import {Mover} from './actions/mover';
3838
import {UndoRedoAction} from './actions/undo_redo';
39+
import { StackNavigationAction } from './actions/stack_navigation';
3940

4041
const KeyCodes = BlocklyUtils.KeyCodes;
4142

@@ -75,6 +76,8 @@ export class NavigationController {
7576

7677
moveActions = new MoveActions(this.mover);
7778

79+
stackNavigationAction: StackNavigationAction = new StackNavigationAction();
80+
7881
/**
7982
* Original Toolbox.prototype.onShortcut method, saved by
8083
* addShortcutHandlers.
@@ -250,6 +253,7 @@ export class NavigationController {
250253
this.clipboard.install();
251254
this.moveActions.install();
252255
this.shortcutDialog.install();
256+
this.stackNavigationAction.install();
253257

254258
// Initialize the shortcut modal with available shortcuts. Needs
255259
// to be done separately rather at construction, as many shortcuts
@@ -273,6 +277,7 @@ export class NavigationController {
273277
this.undoRedoAction.uninstall();
274278
this.actionMenu.uninstall();
275279
this.shortcutDialog.uninstall();
280+
this.stackNavigationAction.uninstall();
276281

277282
for (const shortcut of Object.values(this.shortcuts)) {
278283
ShortcutRegistry.registry.unregister(shortcut.name);

0 commit comments

Comments
 (0)