Skip to content

Commit 7fa5557

Browse files
Merge branch 'main' into makecode-tweaks
2 parents f814186 + 7dcbbbe commit 7fa5557

File tree

2 files changed

+146
-56
lines changed

2 files changed

+146
-56
lines changed

src/navigation.ts

Lines changed: 115 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -734,22 +734,22 @@ export class Navigation {
734734
// Connect the moving block to the stationary connection using
735735
// the most plausible connection on the moving block.
736736
if (
737-
movingType == Blockly.ASTNode.types.BLOCK ||
738-
movingType == Blockly.ASTNode.types.STACK
737+
movingType === Blockly.ASTNode.types.BLOCK ||
738+
movingType === Blockly.ASTNode.types.STACK
739739
) {
740740
const stationaryAsConnection =
741741
stationaryLoc as Blockly.RenderedConnection;
742742
const movingAsBlock = movingLoc as Blockly.BlockSvg;
743743
return this.insertBlock(movingAsBlock, stationaryAsConnection);
744744
}
745-
} else if (stationaryType == Blockly.ASTNode.types.WORKSPACE) {
745+
} else if (stationaryType === Blockly.ASTNode.types.WORKSPACE) {
746746
const block = movingNode
747747
? (movingNode.getSourceBlock() as Blockly.BlockSvg)
748748
: null;
749749
return this.moveBlockToWorkspace(block, stationaryNode);
750750
} else if (
751-
stationaryType == Blockly.ASTNode.types.BLOCK &&
752-
movingType == Blockly.ASTNode.types.BLOCK
751+
stationaryType === Blockly.ASTNode.types.BLOCK &&
752+
movingType === Blockly.ASTNode.types.BLOCK
753753
) {
754754
// Insert the moving block above the stationary block, if the
755755
// appropriate connections exist.
@@ -793,19 +793,19 @@ export class Navigation {
793793
const cursorType = cursorNode.getType();
794794

795795
// Check the marker for invalid types.
796-
if (markerType == Blockly.ASTNode.types.FIELD) {
796+
if (markerType === Blockly.ASTNode.types.FIELD) {
797797
this.warn('Should not have been able to mark a field.');
798798
return false;
799-
} else if (markerType == Blockly.ASTNode.types.STACK) {
799+
} else if (markerType === Blockly.ASTNode.types.STACK) {
800800
this.warn('Should not have been able to mark a stack.');
801801
return false;
802802
}
803803

804804
// Check the cursor for invalid types.
805-
if (cursorType == Blockly.ASTNode.types.FIELD) {
805+
if (cursorType === Blockly.ASTNode.types.FIELD) {
806806
this.warn('Cannot attach a field to anything else.');
807807
return false;
808-
} else if (cursorType == Blockly.ASTNode.types.WORKSPACE) {
808+
} else if (cursorType === Blockly.ASTNode.types.WORKSPACE) {
809809
this.warn('Cannot attach a workspace to anything else.');
810810
return false;
811811
}
@@ -1265,9 +1265,9 @@ export class Navigation {
12651265
if (!cursor) return;
12661266
const curNode = cursor.getCurNode();
12671267
const nodeType = curNode.getType();
1268-
if (nodeType == Blockly.ASTNode.types.FIELD) {
1268+
if (nodeType === Blockly.ASTNode.types.FIELD) {
12691269
(curNode.getLocation() as Blockly.Field).showEditor();
1270-
} else if (nodeType == Blockly.ASTNode.types.BLOCK) {
1270+
} else if (nodeType === Blockly.ASTNode.types.BLOCK) {
12711271
const block = curNode.getLocation() as Blockly.Block;
12721272
if (!tryShowFullBlockFieldEditor(block)) {
12731273
const metaKey = navigator.platform.startsWith('Mac') ? 'Cmd' : 'Ctrl';
@@ -1281,10 +1281,10 @@ export class Navigation {
12811281
}
12821282
} else if (
12831283
curNode.isConnection() ||
1284-
nodeType == Blockly.ASTNode.types.WORKSPACE
1284+
nodeType === Blockly.ASTNode.types.WORKSPACE
12851285
) {
12861286
this.openToolboxOrFlyout(workspace);
1287-
} else if (nodeType == Blockly.ASTNode.types.STACK) {
1287+
} else if (nodeType === Blockly.ASTNode.types.STACK) {
12881288
this.warn('Cannot mark a stack.');
12891289
}
12901290
}
@@ -1304,55 +1304,90 @@ export class Navigation {
13041304
}
13051305

13061306
/**
1307-
* Show the action menu for a given node.
1307+
* Show the action menu for the current node.
13081308
*
13091309
* The action menu will contain entries for relevant actions for the
13101310
* node's location. If the location is a block, this will include
13111311
* the contents of the block's context menu (if any).
1312+
*
1313+
* Returns true if it is possible to open the action menu in the
1314+
* current location, even if the menu was not opened due there being
1315+
* no applicable menu items.
13121316
*/
1313-
openActionMenu(node: Blockly.ASTNode) {
1314-
const fakeEvent = fakeEventForNode(node);
1315-
(node.getLocation() as Blockly.BlockSvg).showContextMenu(fakeEvent);
1316-
1317+
openActionMenu(workspace: Blockly.WorkspaceSvg): boolean {
13171318
let menuOptions: Array<
13181319
| Blockly.ContextMenuRegistry.ContextMenuOption
13191320
| Blockly.ContextMenuRegistry.LegacyContextMenuOption
1320-
> | null = null;
1321+
> = [];
13211322
let rtl: boolean;
1322-
let workspace: Blockly.WorkspaceSvg;
13231323

1324+
const cursor = workspace.getCursor();
1325+
if (!cursor) throw new Error('workspace has no cursor');
1326+
const node = cursor.getCurNode();
13241327
const nodeType = node.getType();
13251328
switch (nodeType) {
13261329
case Blockly.ASTNode.types.BLOCK:
13271330
const block = node.getLocation() as Blockly.BlockSvg;
1328-
workspace = block.workspace as Blockly.WorkspaceSvg;
13291331
rtl = block.RTL;
1330-
13311332
// Reimplement BlockSvg.prototype.generateContextMenu as that
13321333
// method is protected.
1333-
if (!workspace.options.readOnly && !block.contextMenu) {
1334+
if (!workspace.options.readOnly && block.contextMenu) {
13341335
menuOptions =
13351336
Blockly.ContextMenuRegistry.registry.getContextMenuOptions(
13361337
Blockly.ContextMenuRegistry.ScopeType.BLOCK,
13371338
{block},
13381339
);
13391340

13401341
// Allow the block to add or modify menuOptions.
1341-
if (block.customContextMenu) {
1342-
block.customContextMenu(menuOptions);
1343-
}
1342+
block.customContextMenu?.(menuOptions);
13441343
}
13451344
// End reimplement.
13461345
break;
1346+
1347+
// case Blockly.ASTNode.types.INPUT:
1348+
case Blockly.ASTNode.types.NEXT:
1349+
case Blockly.ASTNode.types.PREVIOUS:
1350+
const connection = node.getLocation() as Blockly.Connection;
1351+
rtl = connection.getSourceBlock().RTL;
1352+
1353+
// Slightly hacky: get insert action from registry. Hacky
1354+
// because registry typings don't include {connection: ...} as
1355+
// a possible kind of scope.
1356+
const insertAction =
1357+
Blockly.ContextMenuRegistry.registry.getItem('insert');
1358+
if (!insertAction) throw new Error("can't find insert action");
1359+
const possibleOptions = [insertAction /* etc.*/];
1360+
1361+
// Check preconditions and get menu texts.
1362+
const scope = {
1363+
connection,
1364+
} as unknown as Blockly.ContextMenuRegistry.Scope;
1365+
for (const option of possibleOptions) {
1366+
const precondition = option.preconditionFn(scope);
1367+
if (precondition === 'hidden') continue;
1368+
const displayText =
1369+
typeof option.displayText === 'function'
1370+
? option.displayText(scope)
1371+
: option.displayText;
1372+
menuOptions.push({
1373+
text: displayText,
1374+
enabled: precondition === 'enabled',
1375+
callback: option.callback,
1376+
scope,
1377+
weight: option.weight,
1378+
});
1379+
}
1380+
break;
1381+
13471382
default:
1348-
throw new TypeError(
1349-
`unable to show action menu for ASTNode of type ${nodeType}`,
1350-
);
1383+
console.info(`No action menu for ASTNode of type ${nodeType}`);
1384+
return false;
13511385
}
13521386

1353-
if (!menuOptions || !menuOptions.length) return;
1354-
1387+
if (!menuOptions?.length) return true;
1388+
const fakeEvent = fakeEventForNode(node);
13551389
Blockly.ContextMenu.show(fakeEvent, menuOptions, rtl, workspace);
1390+
return true;
13561391
}
13571392

13581393
/**
@@ -1421,12 +1456,29 @@ export class Navigation {
14211456
* Create a fake PointerEvent for opening the action menu for the
14221457
* given ASTNode.
14231458
*
1424-
* Currently only works for block nodes.
1425-
*
14261459
* @param node The node to open the action menu for.
14271460
* @returns A synthetic pointerdown PointerEvent.
14281461
*/
14291462
function fakeEventForNode(node: Blockly.ASTNode): PointerEvent {
1463+
switch (node.getType()) {
1464+
case Blockly.ASTNode.types.BLOCK:
1465+
return fakeEventForBlockNode(node);
1466+
case Blockly.ASTNode.types.NEXT:
1467+
case Blockly.ASTNode.types.PREVIOUS:
1468+
return fakeEventForStackNode(node);
1469+
default:
1470+
throw new TypeError('unhandled node type');
1471+
}
1472+
}
1473+
1474+
/**
1475+
* Create a fake PointerEvent for opening the action menu for the
1476+
* given ASTNode of type BLOCK.
1477+
*
1478+
* @param node The node to open the action menu for.
1479+
* @returns A synthetic pointerdown PointerEvent.
1480+
*/
1481+
function fakeEventForBlockNode(node: Blockly.ASTNode): PointerEvent {
14301482
if (node.getType() !== Blockly.ASTNode.types.BLOCK) {
14311483
throw new TypeError('can only create PointerEvents for BLOCK nodes');
14321484
}
@@ -1459,6 +1511,36 @@ function fakeEventForNode(node: Blockly.ASTNode): PointerEvent {
14591511
});
14601512
}
14611513

1514+
/**
1515+
* Create a fake PointerEvent for opening the action menu for the
1516+
* given ASTNode of type NEXT or PREVIOUS.
1517+
*
1518+
* For now this just puts the action menu in the same place as the
1519+
* context menu for the source block.
1520+
*
1521+
* @param node The node to open the action menu for.
1522+
* @returns A synthetic pointerdown PointerEvent.
1523+
*/
1524+
function fakeEventForStackNode(node: Blockly.ASTNode): PointerEvent {
1525+
if (
1526+
node.getType() !== Blockly.ASTNode.types.NEXT &&
1527+
node.getType() !== Blockly.ASTNode.types.PREVIOUS
1528+
) {
1529+
throw new TypeError(
1530+
'can only create PointerEvents for NEXT / PREVIOUS nodes',
1531+
);
1532+
}
1533+
1534+
const connection = node.getLocation() as Blockly.Connection;
1535+
1536+
return fakeEventForBlockNode(
1537+
new Blockly.ASTNode(
1538+
Blockly.ASTNode.types.BLOCK,
1539+
connection.getSourceBlock(),
1540+
),
1541+
);
1542+
}
1543+
14621544
/**
14631545
* If this block has a full block field then show its editor.
14641546
*

0 commit comments

Comments
 (0)