@@ -724,22 +724,22 @@ export class Navigation {
724
724
// Connect the moving block to the stationary connection using
725
725
// the most plausible connection on the moving block.
726
726
if (
727
- movingType == Blockly . ASTNode . types . BLOCK ||
728
- movingType == Blockly . ASTNode . types . STACK
727
+ movingType === Blockly . ASTNode . types . BLOCK ||
728
+ movingType === Blockly . ASTNode . types . STACK
729
729
) {
730
730
const stationaryAsConnection =
731
731
stationaryLoc as Blockly . RenderedConnection ;
732
732
const movingAsBlock = movingLoc as Blockly . BlockSvg ;
733
733
return this . insertBlock ( movingAsBlock , stationaryAsConnection ) ;
734
734
}
735
- } else if ( stationaryType == Blockly . ASTNode . types . WORKSPACE ) {
735
+ } else if ( stationaryType === Blockly . ASTNode . types . WORKSPACE ) {
736
736
const block = movingNode
737
737
? ( movingNode . getSourceBlock ( ) as Blockly . BlockSvg )
738
738
: null ;
739
739
return this . moveBlockToWorkspace ( block , stationaryNode ) ;
740
740
} else if (
741
- stationaryType == Blockly . ASTNode . types . BLOCK &&
742
- movingType == Blockly . ASTNode . types . BLOCK
741
+ stationaryType === Blockly . ASTNode . types . BLOCK &&
742
+ movingType === Blockly . ASTNode . types . BLOCK
743
743
) {
744
744
// Insert the moving block above the stationary block, if the
745
745
// appropriate connections exist.
@@ -783,19 +783,19 @@ export class Navigation {
783
783
const cursorType = cursorNode . getType ( ) ;
784
784
785
785
// Check the marker for invalid types.
786
- if ( markerType == Blockly . ASTNode . types . FIELD ) {
786
+ if ( markerType === Blockly . ASTNode . types . FIELD ) {
787
787
this . warn ( 'Should not have been able to mark a field.' ) ;
788
788
return false ;
789
- } else if ( markerType == Blockly . ASTNode . types . STACK ) {
789
+ } else if ( markerType === Blockly . ASTNode . types . STACK ) {
790
790
this . warn ( 'Should not have been able to mark a stack.' ) ;
791
791
return false ;
792
792
}
793
793
794
794
// Check the cursor for invalid types.
795
- if ( cursorType == Blockly . ASTNode . types . FIELD ) {
795
+ if ( cursorType === Blockly . ASTNode . types . FIELD ) {
796
796
this . warn ( 'Cannot attach a field to anything else.' ) ;
797
797
return false ;
798
- } else if ( cursorType == Blockly . ASTNode . types . WORKSPACE ) {
798
+ } else if ( cursorType === Blockly . ASTNode . types . WORKSPACE ) {
799
799
this . warn ( 'Cannot attach a workspace to anything else.' ) ;
800
800
return false ;
801
801
}
@@ -1255,9 +1255,9 @@ export class Navigation {
1255
1255
if ( ! cursor ) return ;
1256
1256
const curNode = cursor . getCurNode ( ) ;
1257
1257
const nodeType = curNode . getType ( ) ;
1258
- if ( nodeType == Blockly . ASTNode . types . FIELD ) {
1258
+ if ( nodeType === Blockly . ASTNode . types . FIELD ) {
1259
1259
( curNode . getLocation ( ) as Blockly . Field ) . showEditor ( ) ;
1260
- } else if ( nodeType == Blockly . ASTNode . types . BLOCK ) {
1260
+ } else if ( nodeType === Blockly . ASTNode . types . BLOCK ) {
1261
1261
const block = curNode . getLocation ( ) as Blockly . Block ;
1262
1262
if ( ! tryShowFullBlockFieldEditor ( block ) ) {
1263
1263
const metaKey = navigator . platform . startsWith ( 'Mac' ) ? 'Cmd' : 'Ctrl' ;
@@ -1271,10 +1271,10 @@ export class Navigation {
1271
1271
}
1272
1272
} else if (
1273
1273
curNode . isConnection ( ) ||
1274
- nodeType == Blockly . ASTNode . types . WORKSPACE
1274
+ nodeType === Blockly . ASTNode . types . WORKSPACE
1275
1275
) {
1276
1276
this . openToolboxOrFlyout ( workspace ) ;
1277
- } else if ( nodeType == Blockly . ASTNode . types . STACK ) {
1277
+ } else if ( nodeType === Blockly . ASTNode . types . STACK ) {
1278
1278
this . warn ( 'Cannot mark a stack.' ) ;
1279
1279
}
1280
1280
}
@@ -1294,55 +1294,90 @@ export class Navigation {
1294
1294
}
1295
1295
1296
1296
/**
1297
- * Show the action menu for a given node.
1297
+ * Show the action menu for the current node.
1298
1298
*
1299
1299
* The action menu will contain entries for relevant actions for the
1300
1300
* node's location. If the location is a block, this will include
1301
1301
* the contents of the block's context menu (if any).
1302
+ *
1303
+ * Returns true if it is possible to open the action menu in the
1304
+ * current location, even if the menu was not opened due there being
1305
+ * no applicable menu items.
1302
1306
*/
1303
- openActionMenu ( node : Blockly . ASTNode ) {
1304
- const fakeEvent = fakeEventForNode ( node ) ;
1305
- ( node . getLocation ( ) as Blockly . BlockSvg ) . showContextMenu ( fakeEvent ) ;
1306
-
1307
+ openActionMenu ( workspace : Blockly . WorkspaceSvg ) : boolean {
1307
1308
let menuOptions : Array <
1308
1309
| Blockly . ContextMenuRegistry . ContextMenuOption
1309
1310
| Blockly . ContextMenuRegistry . LegacyContextMenuOption
1310
- > | null = null ;
1311
+ > = [ ] ;
1311
1312
let rtl : boolean ;
1312
- let workspace : Blockly . WorkspaceSvg ;
1313
1313
1314
+ const cursor = workspace . getCursor ( ) ;
1315
+ if ( ! cursor ) throw new Error ( 'workspace has no cursor' ) ;
1316
+ const node = cursor . getCurNode ( ) ;
1314
1317
const nodeType = node . getType ( ) ;
1315
1318
switch ( nodeType ) {
1316
1319
case Blockly . ASTNode . types . BLOCK :
1317
1320
const block = node . getLocation ( ) as Blockly . BlockSvg ;
1318
- workspace = block . workspace as Blockly . WorkspaceSvg ;
1319
1321
rtl = block . RTL ;
1320
-
1321
1322
// Reimplement BlockSvg.prototype.generateContextMenu as that
1322
1323
// method is protected.
1323
- if ( ! workspace . options . readOnly && ! block . contextMenu ) {
1324
+ if ( ! workspace . options . readOnly && block . contextMenu ) {
1324
1325
menuOptions =
1325
1326
Blockly . ContextMenuRegistry . registry . getContextMenuOptions (
1326
1327
Blockly . ContextMenuRegistry . ScopeType . BLOCK ,
1327
1328
{ block} ,
1328
1329
) ;
1329
1330
1330
1331
// Allow the block to add or modify menuOptions.
1331
- if ( block . customContextMenu ) {
1332
- block . customContextMenu ( menuOptions ) ;
1333
- }
1332
+ block . customContextMenu ?.( menuOptions ) ;
1334
1333
}
1335
1334
// End reimplement.
1336
1335
break ;
1336
+
1337
+ // case Blockly.ASTNode.types.INPUT:
1338
+ case Blockly . ASTNode . types . NEXT :
1339
+ case Blockly . ASTNode . types . PREVIOUS :
1340
+ const connection = node . getLocation ( ) as Blockly . Connection ;
1341
+ rtl = connection . getSourceBlock ( ) . RTL ;
1342
+
1343
+ // Slightly hacky: get insert action from registry. Hacky
1344
+ // because registry typings don't include {connection: ...} as
1345
+ // a possible kind of scope.
1346
+ const insertAction =
1347
+ Blockly . ContextMenuRegistry . registry . getItem ( 'insert' ) ;
1348
+ if ( ! insertAction ) throw new Error ( "can't find insert action" ) ;
1349
+ const possibleOptions = [ insertAction /* etc.*/ ] ;
1350
+
1351
+ // Check preconditions and get menu texts.
1352
+ const scope = {
1353
+ connection,
1354
+ } as unknown as Blockly . ContextMenuRegistry . Scope ;
1355
+ for ( const option of possibleOptions ) {
1356
+ const precondition = option . preconditionFn ( scope ) ;
1357
+ if ( precondition === 'hidden' ) continue ;
1358
+ const displayText =
1359
+ typeof option . displayText === 'function'
1360
+ ? option . displayText ( scope )
1361
+ : option . displayText ;
1362
+ menuOptions . push ( {
1363
+ text : displayText ,
1364
+ enabled : precondition === 'enabled' ,
1365
+ callback : option . callback ,
1366
+ scope,
1367
+ weight : option . weight ,
1368
+ } ) ;
1369
+ }
1370
+ break ;
1371
+
1337
1372
default :
1338
- throw new TypeError (
1339
- `unable to show action menu for ASTNode of type ${ nodeType } ` ,
1340
- ) ;
1373
+ console . info ( `No action menu for ASTNode of type ${ nodeType } ` ) ;
1374
+ return false ;
1341
1375
}
1342
1376
1343
- if ( ! menuOptions || ! menuOptions . length ) return ;
1344
-
1377
+ if ( ! menuOptions ? .length ) return true ;
1378
+ const fakeEvent = fakeEventForNode ( node ) ;
1345
1379
Blockly . ContextMenu . show ( fakeEvent , menuOptions , rtl , workspace ) ;
1380
+ return true ;
1346
1381
}
1347
1382
1348
1383
/**
@@ -1411,12 +1446,29 @@ export class Navigation {
1411
1446
* Create a fake PointerEvent for opening the action menu for the
1412
1447
* given ASTNode.
1413
1448
*
1414
- * Currently only works for block nodes.
1415
- *
1416
1449
* @param node The node to open the action menu for.
1417
1450
* @returns A synthetic pointerdown PointerEvent.
1418
1451
*/
1419
1452
function fakeEventForNode ( node : Blockly . ASTNode ) : PointerEvent {
1453
+ switch ( node . getType ( ) ) {
1454
+ case Blockly . ASTNode . types . BLOCK :
1455
+ return fakeEventForBlockNode ( node ) ;
1456
+ case Blockly . ASTNode . types . NEXT :
1457
+ case Blockly . ASTNode . types . PREVIOUS :
1458
+ return fakeEventForStackNode ( node ) ;
1459
+ default :
1460
+ throw new TypeError ( 'unhandled node type' ) ;
1461
+ }
1462
+ }
1463
+
1464
+ /**
1465
+ * Create a fake PointerEvent for opening the action menu for the
1466
+ * given ASTNode of type BLOCK.
1467
+ *
1468
+ * @param node The node to open the action menu for.
1469
+ * @returns A synthetic pointerdown PointerEvent.
1470
+ */
1471
+ function fakeEventForBlockNode ( node : Blockly . ASTNode ) : PointerEvent {
1420
1472
if ( node . getType ( ) !== Blockly . ASTNode . types . BLOCK ) {
1421
1473
throw new TypeError ( 'can only create PointerEvents for BLOCK nodes' ) ;
1422
1474
}
@@ -1449,6 +1501,36 @@ function fakeEventForNode(node: Blockly.ASTNode): PointerEvent {
1449
1501
} ) ;
1450
1502
}
1451
1503
1504
+ /**
1505
+ * Create a fake PointerEvent for opening the action menu for the
1506
+ * given ASTNode of type NEXT or PREVIOUS.
1507
+ *
1508
+ * For now this just puts the action menu in the same place as the
1509
+ * context menu for the source block.
1510
+ *
1511
+ * @param node The node to open the action menu for.
1512
+ * @returns A synthetic pointerdown PointerEvent.
1513
+ */
1514
+ function fakeEventForStackNode ( node : Blockly . ASTNode ) : PointerEvent {
1515
+ if (
1516
+ node . getType ( ) !== Blockly . ASTNode . types . NEXT &&
1517
+ node . getType ( ) !== Blockly . ASTNode . types . PREVIOUS
1518
+ ) {
1519
+ throw new TypeError (
1520
+ 'can only create PointerEvents for NEXT / PREVIOUS nodes' ,
1521
+ ) ;
1522
+ }
1523
+
1524
+ const connection = node . getLocation ( ) as Blockly . Connection ;
1525
+
1526
+ return fakeEventForBlockNode (
1527
+ new Blockly . ASTNode (
1528
+ Blockly . ASTNode . types . BLOCK ,
1529
+ connection . getSourceBlock ( ) ,
1530
+ ) ,
1531
+ ) ;
1532
+ }
1533
+
1452
1534
/**
1453
1535
* If this block has a full block field then show its editor.
1454
1536
*
0 commit comments