Skip to content

Commit 3ab05d3

Browse files
feat: contextmenu action to insert above a block (#171)
* feat: first pass at inserting above a block from the context menu * chore: format * feat: update tryToConnectNodes to handle two block nodes * chore: update insert action text
1 parent d9b13a9 commit 3ab05d3

File tree

2 files changed

+80
-22
lines changed

2 files changed

+80
-22
lines changed

src/navigation.ts

Lines changed: 48 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -715,25 +715,44 @@ export class Navigation {
715715

716716
const stationaryLoc = stationaryNode.getLocation();
717717
const movingLoc = movingNode.getLocation();
718-
if (stationaryNode.isConnection() && movingNode.isConnection()) {
719-
const stationaryAsConnection =
720-
stationaryLoc as Blockly.RenderedConnection;
721-
const movingAsConnection = movingLoc as Blockly.RenderedConnection;
722-
return this.connect(movingAsConnection, stationaryAsConnection);
723-
} else if (
724-
stationaryNode.isConnection() &&
725-
(movingType == Blockly.ASTNode.types.BLOCK ||
726-
movingType == Blockly.ASTNode.types.STACK)
727-
) {
728-
const stationaryAsConnection =
729-
stationaryLoc as Blockly.RenderedConnection;
730-
const movingAsBlock = movingLoc as Blockly.BlockSvg;
731-
return this.insertBlock(movingAsBlock, stationaryAsConnection);
718+
719+
if (stationaryNode.isConnection()) {
720+
if (movingNode.isConnection()) {
721+
const stationaryAsConnection =
722+
stationaryLoc as Blockly.RenderedConnection;
723+
const movingAsConnection = movingLoc as Blockly.RenderedConnection;
724+
return this.connect(movingAsConnection, stationaryAsConnection);
725+
}
726+
// Connect the moving block to the stationary connection using
727+
// the most plausible connection on the moving block.
728+
if (
729+
movingType == Blockly.ASTNode.types.BLOCK ||
730+
movingType == Blockly.ASTNode.types.STACK
731+
) {
732+
const stationaryAsConnection =
733+
stationaryLoc as Blockly.RenderedConnection;
734+
const movingAsBlock = movingLoc as Blockly.BlockSvg;
735+
return this.insertBlock(movingAsBlock, stationaryAsConnection);
736+
}
732737
} else if (stationaryType == Blockly.ASTNode.types.WORKSPACE) {
733738
const block = movingNode
734739
? (movingNode.getSourceBlock() as Blockly.BlockSvg)
735740
: null;
736741
return this.moveBlockToWorkspace(block, stationaryNode);
742+
} else if (
743+
stationaryType == Blockly.ASTNode.types.BLOCK &&
744+
movingType == Blockly.ASTNode.types.BLOCK
745+
) {
746+
// Insert the moving block above the stationary block, if the
747+
// appropriate connections exist.
748+
const stationaryBlock = stationaryLoc as Blockly.BlockSvg;
749+
const movingBlock = movingLoc as Blockly.BlockSvg;
750+
if (stationaryBlock.previousConnection) {
751+
return this.insertBlock(
752+
movingBlock,
753+
stationaryBlock.previousConnection,
754+
);
755+
}
737756
}
738757
this.warn('Unexpected state in tryToConnectNodes.');
739758
return false;
@@ -767,9 +786,6 @@ export class Navigation {
767786
if (markerType == Blockly.ASTNode.types.FIELD) {
768787
this.warn('Should not have been able to mark a field.');
769788
return false;
770-
} else if (markerType == Blockly.ASTNode.types.BLOCK) {
771-
this.warn('Should not have been able to mark a block.');
772-
return false;
773789
} else if (markerType == Blockly.ASTNode.types.STACK) {
774790
this.warn('Should not have been able to mark a stack.');
775791
return false;
@@ -1257,16 +1273,26 @@ export class Navigation {
12571273
curNode.isConnection() ||
12581274
nodeType == Blockly.ASTNode.types.WORKSPACE
12591275
) {
1260-
if (workspace.getToolbox()) {
1261-
this.focusToolbox(workspace);
1262-
} else {
1263-
this.focusFlyout(workspace);
1264-
}
1276+
this.openToolboxOrFlyout(workspace);
12651277
} else if (nodeType == Blockly.ASTNode.types.STACK) {
12661278
this.warn('Cannot mark a stack.');
12671279
}
12681280
}
12691281

1282+
/**
1283+
* Save the current cursor location and open the toolbox or flyout
1284+
* to select and insert a block.
1285+
* @param workspace The active workspace.
1286+
*/
1287+
openToolboxOrFlyout(workspace: Blockly.WorkspaceSvg) {
1288+
this.markAtCursor(workspace);
1289+
if (workspace.getToolbox()) {
1290+
this.focusToolbox(workspace);
1291+
} else {
1292+
this.focusFlyout(workspace);
1293+
}
1294+
}
1295+
12701296
/**
12711297
* Show the action menu for a given node.
12721298
*

src/navigation_controller.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -978,6 +978,37 @@ export class NavigationController {
978978
ContextMenuRegistry.registry.register(copyAction);
979979
}
980980

981+
/**
982+
* Register the action for inserting above a block.
983+
*/
984+
protected registerInsertAction() {
985+
const insertAboveAction: ContextMenuRegistry.RegistryItem = {
986+
displayText: (scope) => 'Insert block above',
987+
preconditionFn: (scope) => {
988+
const ws = scope.block?.workspace;
989+
if (!ws) return 'hidden';
990+
991+
if (!scope.block?.previousConnection) return 'hidden';
992+
993+
return this.canCurrentlyEdit(ws) ? 'enabled' : 'hidden';
994+
},
995+
callback: (scope) => {
996+
const ws = scope.block?.workspace;
997+
if (!ws) return false;
998+
999+
if (this.navigation.getState(ws) == Constants.STATE.WORKSPACE) {
1000+
this.navigation.openToolboxOrFlyout(ws);
1001+
return true;
1002+
}
1003+
return false;
1004+
},
1005+
scopeType: ContextMenuRegistry.ScopeType.BLOCK,
1006+
id: 'blockInsertAbove',
1007+
weight: 12,
1008+
};
1009+
ContextMenuRegistry.registry.register(insertAboveAction);
1010+
}
1011+
9811012
/**
9821013
* Registers all default keyboard shortcut items for keyboard
9831014
* navigation. This should be called once per instance of
@@ -990,6 +1021,7 @@ export class NavigationController {
9901021

9911022
this.registerDeleteAction();
9921023
this.registerCopyAction();
1024+
this.registerInsertAction();
9931025

9941026
// Initalise the shortcut modal with available shortcuts. Needs
9951027
// to be done separately rather at construction, as many shortcuts

0 commit comments

Comments
 (0)