Skip to content

Commit d1a9208

Browse files
refactor: split move actions from mover code (#391)
This opens up the possibility of triggering movement from other actions in future, e.g. insert.
1 parent 8ea3ce1 commit d1a9208

File tree

3 files changed

+183
-156
lines changed

3 files changed

+183
-156
lines changed

src/actions/move.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import {
8+
ContextMenuRegistry,
9+
ShortcutRegistry,
10+
utils,
11+
WorkspaceSvg,
12+
} from 'blockly';
13+
import {Direction} from '../drag_direction';
14+
import {Mover} from './mover';
15+
16+
const KeyCodes = utils.KeyCodes;
17+
const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
18+
ShortcutRegistry.registry,
19+
);
20+
21+
/**
22+
* Actions for moving blocks with keyboard shortcuts.
23+
*/
24+
export class MoveActions {
25+
constructor(private mover: Mover) {}
26+
27+
private shortcuts: ShortcutRegistry.KeyboardShortcut[] = [
28+
// Begin and end move.
29+
{
30+
name: 'Start move',
31+
preconditionFn: (workspace) => this.mover.canMove(workspace),
32+
callback: (workspace) => this.mover.startMove(workspace),
33+
keyCodes: [KeyCodes.M],
34+
},
35+
{
36+
name: 'Finish move',
37+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
38+
callback: (workspace) => this.mover.finishMove(workspace),
39+
keyCodes: [KeyCodes.ENTER],
40+
allowCollision: true,
41+
},
42+
{
43+
name: 'Abort move',
44+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
45+
callback: (workspace) => this.mover.abortMove(workspace),
46+
keyCodes: [KeyCodes.ESC],
47+
allowCollision: true,
48+
},
49+
50+
// Constrained moves.
51+
{
52+
name: 'Move left, constrained',
53+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
54+
callback: (workspace) =>
55+
this.mover.moveConstrained(workspace, Direction.Left),
56+
keyCodes: [KeyCodes.LEFT],
57+
allowCollision: true,
58+
},
59+
{
60+
name: 'Move right constrained',
61+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
62+
callback: (workspace) =>
63+
this.mover.moveConstrained(workspace, Direction.Right),
64+
keyCodes: [KeyCodes.RIGHT],
65+
allowCollision: true,
66+
},
67+
{
68+
name: 'Move up, constrained',
69+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
70+
callback: (workspace) =>
71+
this.mover.moveConstrained(workspace, Direction.Up),
72+
keyCodes: [KeyCodes.UP],
73+
allowCollision: true,
74+
},
75+
{
76+
name: 'Move down constrained',
77+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
78+
callback: (workspace) =>
79+
this.mover.moveConstrained(workspace, Direction.Down),
80+
keyCodes: [KeyCodes.DOWN],
81+
allowCollision: true,
82+
},
83+
84+
// Unconstrained moves.
85+
{
86+
name: 'Move left, unconstrained',
87+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
88+
callback: (workspace) =>
89+
this.mover.moveUnconstrained(workspace, Direction.Left),
90+
keyCodes: [
91+
createSerializedKey(KeyCodes.LEFT, [KeyCodes.ALT]),
92+
createSerializedKey(KeyCodes.LEFT, [KeyCodes.CTRL]),
93+
],
94+
},
95+
{
96+
name: 'Move right, unconstrained',
97+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
98+
callback: (workspace) =>
99+
this.mover.moveUnconstrained(workspace, Direction.Right),
100+
keyCodes: [
101+
createSerializedKey(KeyCodes.RIGHT, [KeyCodes.ALT]),
102+
createSerializedKey(KeyCodes.RIGHT, [KeyCodes.CTRL]),
103+
],
104+
},
105+
{
106+
name: 'Move up unconstrained',
107+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
108+
callback: (workspace) =>
109+
this.mover.moveUnconstrained(workspace, Direction.Up),
110+
keyCodes: [
111+
createSerializedKey(KeyCodes.UP, [KeyCodes.ALT]),
112+
createSerializedKey(KeyCodes.UP, [KeyCodes.CTRL]),
113+
],
114+
},
115+
{
116+
name: 'Move down, unconstrained',
117+
preconditionFn: (workspace) => this.mover.isMoving(workspace),
118+
callback: (workspace) =>
119+
this.mover.moveUnconstrained(workspace, Direction.Down),
120+
keyCodes: [
121+
createSerializedKey(KeyCodes.DOWN, [KeyCodes.ALT]),
122+
createSerializedKey(KeyCodes.DOWN, [KeyCodes.CTRL]),
123+
],
124+
},
125+
];
126+
127+
menuItems: ContextMenuRegistry.RegistryItem[] = [
128+
{
129+
displayText: 'Move Block (M)',
130+
preconditionFn: (scope) => {
131+
const workspace = scope.block?.workspace as WorkspaceSvg | null;
132+
if (!workspace) return 'hidden';
133+
return this.mover.canMove(workspace) ? 'enabled' : 'disabled';
134+
},
135+
callback: (scope) => {
136+
const workspace = scope.block?.workspace as WorkspaceSvg | null;
137+
if (!workspace) return false;
138+
this.mover.startMove(workspace);
139+
},
140+
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
141+
id: 'move',
142+
weight: 8.5,
143+
},
144+
];
145+
146+
/**
147+
* Install the actions as both keyboard shortcuts and (where
148+
* applicable) context menu items.
149+
*/
150+
install() {
151+
for (const shortcut of this.shortcuts) {
152+
ShortcutRegistry.registry.register(shortcut);
153+
}
154+
for (const menuItem of this.menuItems) {
155+
ContextMenuRegistry.registry.register(menuItem);
156+
}
157+
}
158+
159+
/**
160+
* Uninstall these actions.
161+
*/
162+
uninstall() {
163+
for (const shortcut of this.shortcuts) {
164+
ShortcutRegistry.registry.unregister(shortcut.name);
165+
}
166+
for (const menuItem of this.menuItems) {
167+
ContextMenuRegistry.registry.unregister(menuItem.id);
168+
}
169+
}
170+
}

src/actions/mover.ts

Lines changed: 7 additions & 153 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,19 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import * as Constants from '../constants';
7+
import type {BlockSvg, IDragger, IDragStrategy} from 'blockly';
88
import {
99
ASTNode,
10-
Connection,
11-
ContextMenuRegistry,
12-
ShortcutRegistry,
13-
WorkspaceSvg,
1410
common,
11+
Connection,
1512
registry,
1613
utils,
14+
WorkspaceSvg,
1715
} from 'blockly';
18-
import type {BlockSvg, IDragger, IDragStrategy} from 'blockly';
19-
import {Navigation} from '../navigation';
20-
import {KeyboardDragStrategy} from '../keyboard_drag_strategy';
16+
import * as Constants from '../constants';
2117
import {Direction, getXYFromDirection} from '../drag_direction';
22-
23-
const KeyCodes = utils.KeyCodes;
24-
const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
25-
ShortcutRegistry.registry,
26-
);
18+
import {KeyboardDragStrategy} from '../keyboard_drag_strategy';
19+
import {Navigation} from '../navigation';
2720

2821
/**
2922
* The distance to move an item, in workspace coordinates, when
@@ -32,7 +25,7 @@ const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind(
3225
const UNCONSTRAINED_MOVE_DISTANCE = 20;
3326

3427
/**
35-
* Actions for moving blocks with keyboard shortcuts.
28+
* Low-level code for moving blocks with keyboard shortcuts.
3629
*/
3730
export class Mover {
3831
/**
@@ -58,145 +51,6 @@ export class Mover {
5851

5952
constructor(protected navigation: Navigation) {}
6053

61-
private shortcuts: ShortcutRegistry.KeyboardShortcut[] = [
62-
// Begin and end move.
63-
{
64-
name: 'Start move',
65-
preconditionFn: (workspace) => this.canMove(workspace),
66-
callback: (workspace) => this.startMove(workspace),
67-
keyCodes: [KeyCodes.M],
68-
},
69-
{
70-
name: 'Finish move',
71-
preconditionFn: (workspace) => this.isMoving(workspace),
72-
callback: (workspace) => this.finishMove(workspace),
73-
keyCodes: [KeyCodes.ENTER],
74-
allowCollision: true,
75-
},
76-
{
77-
name: 'Abort move',
78-
preconditionFn: (workspace) => this.isMoving(workspace),
79-
callback: (workspace) => this.abortMove(workspace),
80-
keyCodes: [KeyCodes.ESC],
81-
allowCollision: true,
82-
},
83-
84-
// Constrained moves.
85-
{
86-
name: 'Move left, constrained',
87-
preconditionFn: (workspace) => this.isMoving(workspace),
88-
callback: (workspace) => this.moveConstrained(workspace, Direction.Left),
89-
keyCodes: [KeyCodes.LEFT],
90-
allowCollision: true,
91-
},
92-
{
93-
name: 'Move right unconstrained',
94-
preconditionFn: (workspace) => this.isMoving(workspace),
95-
callback: (workspace) => this.moveConstrained(workspace, Direction.Right),
96-
keyCodes: [KeyCodes.RIGHT],
97-
allowCollision: true,
98-
},
99-
{
100-
name: 'Move up, constrained',
101-
preconditionFn: (workspace) => this.isMoving(workspace),
102-
callback: (workspace) => this.moveConstrained(workspace, Direction.Up),
103-
keyCodes: [KeyCodes.UP],
104-
allowCollision: true,
105-
},
106-
{
107-
name: 'Move down constrained',
108-
preconditionFn: (workspace) => this.isMoving(workspace),
109-
callback: (workspace) => this.moveConstrained(workspace, Direction.Down),
110-
keyCodes: [KeyCodes.DOWN],
111-
allowCollision: true,
112-
},
113-
114-
// Unconstrained moves.
115-
{
116-
name: 'Move left, unconstrained',
117-
preconditionFn: (workspace) => this.isMoving(workspace),
118-
callback: (workspace) =>
119-
this.moveUnconstrained(workspace, Direction.Left),
120-
keyCodes: [
121-
createSerializedKey(KeyCodes.LEFT, [KeyCodes.ALT]),
122-
createSerializedKey(KeyCodes.LEFT, [KeyCodes.CTRL]),
123-
],
124-
},
125-
{
126-
name: 'Move right, unconstrained',
127-
preconditionFn: (workspace) => this.isMoving(workspace),
128-
callback: (workspace) =>
129-
this.moveUnconstrained(workspace, Direction.Right),
130-
keyCodes: [
131-
createSerializedKey(KeyCodes.RIGHT, [KeyCodes.ALT]),
132-
createSerializedKey(KeyCodes.RIGHT, [KeyCodes.CTRL]),
133-
],
134-
},
135-
{
136-
name: 'Move up unconstrained',
137-
preconditionFn: (workspace) => this.isMoving(workspace),
138-
callback: (workspace) => this.moveUnconstrained(workspace, Direction.Up),
139-
keyCodes: [
140-
createSerializedKey(KeyCodes.UP, [KeyCodes.ALT]),
141-
createSerializedKey(KeyCodes.UP, [KeyCodes.CTRL]),
142-
],
143-
},
144-
{
145-
name: 'Move down, unconstrained',
146-
preconditionFn: (workspace) => this.isMoving(workspace),
147-
callback: (workspace) =>
148-
this.moveUnconstrained(workspace, Direction.Down),
149-
keyCodes: [
150-
createSerializedKey(KeyCodes.DOWN, [KeyCodes.ALT]),
151-
createSerializedKey(KeyCodes.DOWN, [KeyCodes.CTRL]),
152-
],
153-
},
154-
];
155-
156-
menuItems: ContextMenuRegistry.RegistryItem[] = [
157-
{
158-
displayText: 'Move Block (M)',
159-
preconditionFn: (scope) => {
160-
const workspace = scope.block?.workspace as WorkspaceSvg | null;
161-
if (!workspace) return 'hidden';
162-
return this.canMove(workspace) ? 'enabled' : 'disabled';
163-
},
164-
callback: (scope) => {
165-
const workspace = scope.block?.workspace as WorkspaceSvg | null;
166-
if (!workspace) return false;
167-
this.startMove(workspace);
168-
},
169-
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
170-
id: 'move',
171-
weight: 8.5,
172-
},
173-
];
174-
175-
/**
176-
* Install the actions as both keyboard shortcuts and (where
177-
* applicable) context menu items.
178-
*/
179-
install() {
180-
for (const shortcut of this.shortcuts) {
181-
ShortcutRegistry.registry.register(shortcut);
182-
}
183-
for (const menuItem of this.menuItems) {
184-
ContextMenuRegistry.registry.register(menuItem);
185-
}
186-
}
187-
188-
/**
189-
* Uninstall these actions.
190-
*/
191-
uninstall() {
192-
for (const shortcut of this.shortcuts) {
193-
ShortcutRegistry.registry.unregister(shortcut.name);
194-
}
195-
for (const menuItem of this.menuItems) {
196-
ContextMenuRegistry.registry.unregister(menuItem.id);
197-
}
198-
}
199-
20054
/**
20155
* Returns true iff we are able to begin moving the block which
20256
* currently has focus on the given workspace.

0 commit comments

Comments
 (0)