Skip to content

Commit a2894f0

Browse files
chore: move disconnect action to its own file (#284)
1 parent 0ae1b73 commit a2894f0

File tree

3 files changed

+142
-74
lines changed

3 files changed

+142
-74
lines changed

src/actions/disconnect.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {
8+
ASTNode,
9+
RenderedConnection,
10+
ShortcutRegistry,
11+
utils as BlocklyUtils,
12+
} from 'blockly';
13+
import * as Constants from '../constants';
14+
import type {WorkspaceSvg} from 'blockly';
15+
import {Navigation} from '../navigation';
16+
17+
const KeyCodes = BlocklyUtils.KeyCodes;
18+
19+
/**
20+
* Action to insert a block into the workspace.
21+
*
22+
* This action registers itself as both a keyboard shortcut and a context menu
23+
* item.
24+
*/
25+
export class DisconnectAction {
26+
/**
27+
* Function provided by the navigation controller to say whether editing
28+
* is allowed.
29+
*/
30+
private canCurrentlyEdit: (ws: WorkspaceSvg) => boolean;
31+
32+
/**
33+
* Registration name for the keyboard shortcut.
34+
*/
35+
private shortcutName = Constants.SHORTCUT_NAMES.DISCONNECT;
36+
37+
constructor(
38+
private navigation: Navigation,
39+
canEdit: (ws: WorkspaceSvg) => boolean,
40+
) {
41+
this.canCurrentlyEdit = canEdit;
42+
}
43+
44+
/**
45+
* Install this action as both a keyboard shortcut and a context menu item.
46+
*/
47+
install() {
48+
this.registerShortcut();
49+
}
50+
51+
/**
52+
* Uninstall this action as both a keyboard shortcut and a context menu item.
53+
* Reinstall the original context menu action if possible.
54+
*/
55+
uninstall() {
56+
ShortcutRegistry.registry.unregister(this.shortcutName);
57+
}
58+
59+
/**
60+
* Create and register the keyboard shortcut for this action.
61+
*/
62+
private registerShortcut() {
63+
const disconnectShortcut: ShortcutRegistry.KeyboardShortcut = {
64+
name: this.shortcutName,
65+
preconditionFn: (workspace) => this.canCurrentlyEdit(workspace),
66+
callback: (workspace) => {
67+
switch (this.navigation.getState(workspace)) {
68+
case Constants.STATE.WORKSPACE:
69+
this.disconnectBlocks(workspace);
70+
return true;
71+
default:
72+
return false;
73+
}
74+
},
75+
keyCodes: [KeyCodes.X],
76+
};
77+
ShortcutRegistry.registry.register(disconnectShortcut);
78+
}
79+
80+
/**
81+
* Disconnects the connection that the cursor is pointing to, and bump blocks.
82+
* This is a no-op if the connection cannot be broken or if the cursor is not
83+
* pointing to a connection.
84+
*
85+
* @param workspace The workspace.
86+
*/
87+
disconnectBlocks(workspace: WorkspaceSvg) {
88+
const cursor = workspace.getCursor();
89+
if (!cursor) {
90+
return;
91+
}
92+
let curNode: ASTNode | null = cursor.getCurNode();
93+
let wasVisitingConnection = true;
94+
while (curNode && !curNode.isConnection()) {
95+
curNode = curNode.out();
96+
wasVisitingConnection = false;
97+
}
98+
if (!curNode) {
99+
console.log('Unable to find a connection to disconnect');
100+
return;
101+
}
102+
const curConnection = curNode.getLocation() as RenderedConnection;
103+
if (!curConnection.isConnected()) {
104+
return;
105+
}
106+
const superiorConnection = curConnection.isSuperior()
107+
? curConnection
108+
: curConnection.targetConnection!;
109+
110+
const inferiorConnection = curConnection.isSuperior()
111+
? curConnection.targetConnection!
112+
: curConnection;
113+
114+
if (inferiorConnection.getSourceBlock().isShadow()) {
115+
return;
116+
}
117+
118+
if (!inferiorConnection.getSourceBlock().isMovable()) {
119+
return;
120+
}
121+
122+
superiorConnection.disconnect();
123+
inferiorConnection.bumpAwayFrom(superiorConnection);
124+
125+
const rootBlock = superiorConnection.getSourceBlock().getRootBlock();
126+
rootBlock.bringToFront();
127+
128+
if (wasVisitingConnection) {
129+
const connectionNode = ASTNode.createConnectionNode(superiorConnection);
130+
workspace.getCursor()!.setCurNode(connectionNode!);
131+
}
132+
}
133+
}

src/navigation.ts

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,64 +1052,6 @@ export class Navigation {
10521052
return false;
10531053
}
10541054

1055-
/**
1056-
* Disconnects the connection that the cursor is pointing to, and bump blocks.
1057-
* This is a no-op if the connection cannot be broken or if the cursor is not
1058-
* pointing to a connection.
1059-
*
1060-
* @param workspace The workspace.
1061-
*/
1062-
disconnectBlocks(workspace: Blockly.WorkspaceSvg) {
1063-
const cursor = workspace.getCursor();
1064-
if (!cursor) {
1065-
return;
1066-
}
1067-
let curNode: Blockly.ASTNode | null = cursor.getCurNode();
1068-
let wasVisitingConnection = true;
1069-
while (curNode && !curNode.isConnection()) {
1070-
curNode = curNode.out();
1071-
wasVisitingConnection = false;
1072-
}
1073-
if (!curNode) {
1074-
this.log('Unable to find a connection to disconnect');
1075-
return;
1076-
}
1077-
const curConnection = curNode.getLocation() as Blockly.RenderedConnection;
1078-
if (!curConnection.isConnected()) {
1079-
this.log('Cannot disconnect unconnected connection');
1080-
return;
1081-
}
1082-
const superiorConnection = curConnection.isSuperior()
1083-
? curConnection
1084-
: curConnection.targetConnection!;
1085-
1086-
const inferiorConnection = curConnection.isSuperior()
1087-
? curConnection.targetConnection!
1088-
: curConnection;
1089-
1090-
if (inferiorConnection.getSourceBlock().isShadow()) {
1091-
this.log('Cannot disconnect a shadow block');
1092-
return;
1093-
}
1094-
1095-
if (!inferiorConnection.getSourceBlock().isMovable()) {
1096-
this.log('Cannot disconnect an immovable block');
1097-
return;
1098-
}
1099-
1100-
superiorConnection.disconnect();
1101-
inferiorConnection.bumpAwayFrom(superiorConnection);
1102-
1103-
const rootBlock = superiorConnection.getSourceBlock().getRootBlock();
1104-
rootBlock.bringToFront();
1105-
1106-
if (wasVisitingConnection) {
1107-
const connectionNode =
1108-
Blockly.ASTNode.createConnectionNode(superiorConnection);
1109-
workspace.getCursor()!.setCurNode(connectionNode!);
1110-
}
1111-
}
1112-
11131055
/**
11141056
* Moves the passive focus indicator to the cursor's current location.
11151057
*

src/navigation_controller.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import {DeleteAction} from './actions/delete';
3131
import {InsertAction} from './actions/insert';
3232
import {Clipboard} from './actions/clipboard';
3333
import {WorkspaceMovement} from './actions/ws_movement';
34+
import {DisconnectAction} from './actions/disconnect';
3435

3536
const KeyCodes = BlocklyUtils.KeyCodes;
3637
const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
@@ -67,6 +68,12 @@ export class NavigationController {
6768
this.canCurrentlyEdit.bind(this),
6869
);
6970

71+
/** Keyboard shortcut for disconnection. */
72+
disconnectAction: DisconnectAction = new DisconnectAction(
73+
this.navigation,
74+
this.canCurrentlyEdit.bind(this),
75+
);
76+
7077
clipboard: Clipboard = new Clipboard(
7178
this.navigation,
7279
this.canCurrentlyEdit.bind(this),
@@ -501,22 +508,6 @@ export class NavigationController {
501508
],
502509
},
503510

504-
/** Disconnect two blocks. */
505-
disconnect: {
506-
name: Constants.SHORTCUT_NAMES.DISCONNECT,
507-
preconditionFn: (workspace) => this.canCurrentlyEdit(workspace),
508-
callback: (workspace) => {
509-
switch (this.navigation.getState(workspace)) {
510-
case Constants.STATE.WORKSPACE:
511-
this.navigation.disconnectBlocks(workspace);
512-
return true;
513-
default:
514-
return false;
515-
}
516-
},
517-
keyCodes: [KeyCodes.X],
518-
},
519-
520511
/** Move focus to or from the toolbox. */
521512
focusToolbox: {
522513
name: Constants.SHORTCUT_NAMES.TOOLBOX,
@@ -703,6 +694,7 @@ export class NavigationController {
703694
this.deleteAction.install();
704695
this.insertAction.install();
705696
this.workspaceMovement.install();
697+
this.disconnectAction.install();
706698

707699
this.clipboard.install();
708700
this.shortcutDialog.install();
@@ -723,6 +715,7 @@ export class NavigationController {
723715

724716
this.deleteAction.uninstall();
725717
this.insertAction.uninstall();
718+
this.disconnectAction.uninstall();
726719
this.clipboard.uninstall();
727720
this.workspaceMovement.uninstall();
728721
this.shortcutDialog.uninstall();

0 commit comments

Comments
 (0)