Skip to content

Commit c7265cf

Browse files
authored
feat: use IContextMenu to open menu (#445)
1 parent ef420aa commit c7265cf

File tree

1 file changed

+9
-127
lines changed

1 file changed

+9
-127
lines changed

src/actions/action_menu.ts

Lines changed: 9 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,9 @@
44
* SPDX-License-Identifier: Apache-2.0
55
*/
66

7-
import {
8-
ASTNode,
9-
ContextMenu,
10-
ContextMenuRegistry,
11-
ShortcutRegistry,
12-
utils as BlocklyUtils,
13-
WidgetDiv,
14-
} from 'blockly';
7+
import {ShortcutRegistry, utils as BlocklyUtils, WidgetDiv} from 'blockly';
158
import * as Constants from '../constants';
16-
import type {BlockSvg, RenderedConnection, WorkspaceSvg} from 'blockly';
9+
import type {WorkspaceSvg} from 'blockly';
1710
import {Navigation} from '../navigation';
1811

1912
const KeyCodes = BlocklyUtils.KeyCodes;
@@ -85,50 +78,20 @@ export class ActionMenu {
8578
* @param workspace The workspace.
8679
*/
8780
private openActionMenu(workspace: WorkspaceSvg): boolean {
88-
let rtl: boolean;
89-
9081
// TODO(#362): Pass this through the precondition and callback instead of making it up.
9182
const menuOpenEvent = new KeyboardEvent('keydown');
9283

9384
const cursor = workspace.getCursor();
9485
if (!cursor) throw new Error('workspace has no cursor');
9586
const node = cursor.getCurNode();
9687
if (!node) return false;
97-
const nodeType = node.getType();
98-
switch (nodeType) {
99-
case ASTNode.types.BLOCK: {
100-
const block = node.getLocation() as BlockSvg;
101-
block.showContextMenu(menuOpenEvent);
102-
break;
103-
}
104-
105-
// case Blockly.ASTNode.types.INPUT:
106-
case ASTNode.types.NEXT:
107-
case ASTNode.types.PREVIOUS:
108-
case ASTNode.types.INPUT: {
109-
const connection = node.getLocation() as RenderedConnection;
110-
rtl = connection.getSourceBlock().RTL;
111-
112-
const menuOptions = ContextMenuRegistry.registry.getContextMenuOptions(
113-
{focusedNode: connection},
114-
menuOpenEvent,
115-
);
116-
// If no valid options, don't show a menu
117-
if (!menuOptions?.length) return true;
118-
const location = this.calculateLocationForConnectionMenu(connection);
119-
ContextMenu.show(menuOpenEvent, menuOptions, rtl, workspace, location);
120-
break;
121-
}
122-
123-
case ASTNode.types.WORKSPACE: {
124-
const workspace = node.getLocation() as WorkspaceSvg;
125-
workspace.showContextMenu(menuOpenEvent);
126-
break;
127-
}
128-
129-
default:
130-
console.info(`No action menu for ASTNode of type ${nodeType}`);
131-
return false;
88+
// TODO(google/blockly#8847): Add typeguard for IContextMenu in core when this moves over
89+
const location = node.getLocation() as any;
90+
if (location.showContextMenu) {
91+
location.showContextMenu(menuOpenEvent);
92+
} else {
93+
console.info(`No action menu for ASTNode of type ${node.getType()}`);
94+
return false;
13295
}
13396

13497
setTimeout(() => {
@@ -147,85 +110,4 @@ export class ActionMenu {
147110
}, 10);
148111
return true;
149112
}
150-
151-
/**
152-
* Find a context menu action, throwing an `Error` if it is not present or
153-
* not an action. This usefully narrows the type to `ActionRegistryItem`
154-
* which is not exported from Blockly.
155-
*
156-
* @param id The id of the action.
157-
* @returns the action.
158-
*/
159-
private getContextMenuAction(id: string) {
160-
const item = ContextMenuRegistry.registry.getItem(id);
161-
if (!item) {
162-
throw new Error(`can't find context menu item ${id}`);
163-
}
164-
if (!item?.callback) {
165-
throw new Error(`context menu item unexpectedly not action ${id}`);
166-
}
167-
return item;
168-
}
169-
170-
/**
171-
* Create a fake PointerEvent for opening the action menu on the specified
172-
* block.
173-
*
174-
* @param block The block to open the action menu for.
175-
* @returns screen coordinates of where to show a menu for a block
176-
*/
177-
private calculateLocationOfBlock(block: BlockSvg): BlocklyUtils.Coordinate {
178-
// Get the location of the top-left corner of the block in
179-
// screen coordinates.
180-
const blockCoords = BlocklyUtils.svgMath.wsToScreenCoordinates(
181-
block.workspace,
182-
block.getRelativeToSurfaceXY(),
183-
);
184-
185-
// Prefer a y position below the first field in the block.
186-
const fieldBoundingClientRect = block.inputList
187-
.filter((input) => input.isVisible())
188-
.flatMap((input) => input.fieldRow)
189-
.filter((f) => f.isVisible())[0]
190-
?.getSvgRoot()
191-
?.getBoundingClientRect();
192-
193-
const y =
194-
fieldBoundingClientRect && fieldBoundingClientRect.height
195-
? fieldBoundingClientRect.y + fieldBoundingClientRect.height
196-
: blockCoords.y + block.height;
197-
198-
return new BlocklyUtils.Coordinate(blockCoords.x + 5, y + 5);
199-
}
200-
201-
/**
202-
* Create a fake PointerEvent for opening the action menu for the
203-
* given connection.
204-
*
205-
* For now this just puts the action menu in the same place as the
206-
* context menu for the source block.
207-
*
208-
* @param connection The node to open the action menu for.
209-
* @returns Screen coordinates of where to show menu for a connection node.
210-
*/
211-
private calculateLocationForConnectionMenu(
212-
connection: RenderedConnection,
213-
): BlocklyUtils.Coordinate {
214-
const block = connection.getSourceBlock() as BlockSvg;
215-
const workspace = block.workspace as WorkspaceSvg;
216-
217-
if (typeof connection.x !== 'number') {
218-
// No coordinates for connection? Fall back to the parent block.
219-
return this.calculateLocationOfBlock(block);
220-
}
221-
const connectionWSCoords = new BlocklyUtils.Coordinate(
222-
connection.x,
223-
connection.y,
224-
);
225-
const connectionScreenCoords = BlocklyUtils.svgMath.wsToScreenCoordinates(
226-
workspace,
227-
connectionWSCoords,
228-
);
229-
return connectionScreenCoords.translate(5, 5);
230-
}
231113
}

0 commit comments

Comments
 (0)