diff --git a/src/actions/enter.ts b/src/actions/enter.ts index 6f556cbb..17610bbb 100644 --- a/src/actions/enter.ts +++ b/src/actions/enter.ts @@ -131,14 +131,10 @@ export class EnterAction { const stationaryNode = this.navigation.getStationaryNode(workspace); const newBlock = this.createNewBlock(workspace); if (!newBlock) return; - if (stationaryNode) { - if (!this.navigation.tryToConnectBlock(stationaryNode, newBlock)) { - console.warn( - 'Something went wrong while inserting a block from the flyout.', - ); - } - } + const insertStartPoint = stationaryNode + ? this.navigation.findInsertStartPoint(stationaryNode, newBlock) + : null; if (workspace.getTopBlocks().includes(newBlock)) { this.positionNewTopLevelBlock(workspace, newBlock); } @@ -149,7 +145,7 @@ export class EnterAction { this.navigation.focusWorkspace(workspace); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)!); - this.mover.startMove(workspace); + this.mover.startMove(workspace, insertStartPoint); } /** diff --git a/src/actions/mover.ts b/src/actions/mover.ts index 4d2c40e9..84645237 100644 --- a/src/actions/mover.ts +++ b/src/actions/mover.ts @@ -4,11 +4,18 @@ * SPDX-License-Identifier: Apache-2.0 */ -import type {BlockSvg, IDragger, IDragStrategy, Gesture} from 'blockly'; +import type { + BlockSvg, + IDragger, + IDragStrategy, + Gesture, + RenderedConnection, +} from 'blockly'; import { ASTNode, common, Connection, + Events, registry, utils, WorkspaceSvg, @@ -95,9 +102,14 @@ export class Mover { * Should only be called if canMove has returned true. * * @param workspace The workspace we might be moving on. + * @param insertStartPoint The starting point for the move in the insert case, + * when the block should be deleted if aborted rather than reverted. * @returns True iff a move has successfully begun. */ - startMove(workspace: WorkspaceSvg) { + startMove( + workspace: WorkspaceSvg, + insertStartPoint: RenderedConnection | null = null, + ) { const cursor = workspace?.getCursor(); const block = this.getCurrentBlock(workspace); if (!cursor || !block) throw new Error('precondition failure'); @@ -107,7 +119,7 @@ export class Mover { cursor.setCurNode(ASTNode.createBlockNode(block)); this.patchWorkspace(workspace); - this.patchDragStrategy(block); + this.patchDragStrategy(block, insertStartPoint); // Begin dragging block. const DraggerClass = registry.getClassFromOptions( registry.Type.BLOCK_DRAGGER, @@ -144,6 +156,7 @@ export class Mover { this.unpatchWorkspace(workspace); this.unpatchDragStrategy(info.block); this.moves.delete(workspace); + return true; } @@ -159,17 +172,31 @@ export class Mover { const info = this.moves.get(workspace); if (!info) throw new Error('no move info for workspace'); - // Monkey patch dragger to trigger call to draggable.revertDrag. + // If it's an insert-style move then we delete the block. + const keyboardDragStrategy = + info.block.getDragStrategy() as KeyboardDragStrategy; + const {insertStartPoint} = keyboardDragStrategy; + + // Monkey patch dragger to trigger delete. // eslint-disable-next-line @typescript-eslint/no-explicit-any - (info.dragger as any).shouldReturnToStart = () => true; + (info.dragger as any).wouldDeleteDraggable = () => !!insertStartPoint; + if (!insertStartPoint) { + // Monkey patch dragger to trigger call to draggable.revertDrag. + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (info.dragger as any).shouldReturnToStart = () => true; + } const blockSvg = info.block; // Explicitly call `hidePreview` because it is not called in revertDrag. // @ts-expect-error Access to private property dragStrategy. blockSvg.dragStrategy.connectionPreviewer.hidePreview(); + // Prevent the stragegy connecting the block so we just delete one block. + // @ts-expect-error Access to private property dragStrategy. + blockSvg.dragStrategy.connectionCandidate = null; + info.dragger.onDragEnd( info.fakePointerEvent('pointerup'), - new utils.Coordinate(0, 0), + info.startLocation, ); this.unpatchWorkspace(workspace); @@ -289,11 +316,16 @@ export class Mover { * Monkeypatch: replace the block's drag strategy and cache the old value. * * @param block The block to patch. + * @param insertStartPoint The starting point for the move in the insert case, + * when the block should be deleted if aborted rather than reverted. */ - private patchDragStrategy(block: BlockSvg) { + private patchDragStrategy( + block: BlockSvg, + insertStartPoint: RenderedConnection | null, + ) { // @ts-expect-error block.dragStrategy is private. this.oldDragStrategy = block.dragStrategy; - block.setDragStrategy(new KeyboardDragStrategy(block)); + block.setDragStrategy(new KeyboardDragStrategy(block, insertStartPoint)); } /** diff --git a/src/keyboard_drag_strategy.ts b/src/keyboard_drag_strategy.ts index 31ff8aea..73973e19 100644 --- a/src/keyboard_drag_strategy.ts +++ b/src/keyboard_drag_strategy.ts @@ -35,6 +35,13 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { /** Where a constrained movement should start when traversing the tree. */ private searchNode: ASTNode | null = null; + constructor( + block: BlockSvg, + public insertStartPoint: RenderedConnection | null, + ) { + super(block); + } + override startDrag(e?: PointerEvent) { super.startDrag(e); // Set position of the dragging block, so that it doesn't pop @@ -262,7 +269,7 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { */ private createInitialCandidate(): ConnectionCandidate | null { // @ts-expect-error startParentConn is private. - const neighbour = this.startParentConn; + const neighbour = this.insertStartPoint ?? this.startParentConn; if (neighbour) { this.searchNode = ASTNode.createConnectionNode(neighbour); switch (neighbour.type) { diff --git a/src/navigation.ts b/src/navigation.ts index 6bb1caf3..1687eb21 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -404,8 +404,8 @@ export class Navigation { const passiveFocusNode = this.passiveFocusIndicator.getCurNode(); this.passiveFocusIndicator.hide(); const disposed = passiveFocusNode?.getSourceBlock()?.disposed; - // If there's a gesture then it will either set the node if it has not - // been disposed (which can happen when blocks are reloaded) or be a click + // If there's a gesture then it will either set the node if it has not + // been disposed (which can happen when blocks are reloaded) or be a click // that should not set one. if (!Blockly.Gesture.inProgress() && passiveFocusNode && !disposed) { cursor.setCurNode(passiveFocusNode); @@ -662,25 +662,23 @@ export class Navigation { } /** - * Tries to intelligently connect the blocks or connections - * represented by the given nodes, based on node types and locations. + * Finds the starting point for an insert. * - * @param stationaryNode The first node to connect. + * @param stationaryNode The first node to find a connection for. * @param movingBlock The block we're moving. - * @returns True if the key was handled; false if something went - * wrong. + * @returns The initial connection to use or begin move mode at. */ - tryToConnectBlock( + findInsertStartPoint( stationaryNode: Blockly.ASTNode, movingBlock: Blockly.BlockSvg, - ): boolean { + ): Blockly.RenderedConnection | null { const stationaryType = stationaryNode.getType(); const stationaryLoc = stationaryNode.getLocation(); if (stationaryNode.getType() === Blockly.ASTNode.types.FIELD) { const sourceBlock = stationaryNode.getSourceBlock(); - if (!sourceBlock) return false; - return this.tryToConnectBlock( + if (!sourceBlock) return null; + return this.findInsertStartPoint( Blockly.ASTNode.createBlockNode(sourceBlock), movingBlock, ); @@ -695,8 +693,8 @@ export class Navigation { stationaryAsConnection.type === Blockly.ConnectionType.INPUT_VALUE ) { const sourceBlock = stationaryNode.getSourceBlock(); - if (!sourceBlock) return false; - return this.tryToConnectBlock( + if (!sourceBlock) return null; + return this.findInsertStartPoint( Blockly.ASTNode.createBlockNode(sourceBlock), movingBlock, ); @@ -704,9 +702,9 @@ export class Navigation { // Connect the moving block to the stationary connection using // the most plausible connection on the moving block. - return this.insertBlock(movingBlock, stationaryAsConnection); + return stationaryAsConnection; } else if (stationaryType === Blockly.ASTNode.types.WORKSPACE) { - return this.moveBlockToWorkspace(movingBlock, stationaryNode); + return null; } else if (stationaryType === Blockly.ASTNode.types.BLOCK) { const stationaryBlock = stationaryLoc as Blockly.BlockSvg; @@ -726,15 +724,12 @@ export class Navigation { connection = connection.targetBlock()!.nextConnection!; } } - return this.insertBlock( - movingBlock, - connection as Blockly.RenderedConnection, - ); + return connection as Blockly.RenderedConnection; } // 2. Connect statement blocks to next connection. if (stationaryBlock.nextConnection && !movingBlock.outputConnection) { - return this.insertBlock(movingBlock, stationaryBlock.nextConnection); + return stationaryBlock.nextConnection; } // 3. Output connection. This will wrap around or displace. @@ -745,17 +740,38 @@ export class Navigation { stationaryNode.getType() === Blockly.ASTNode.types.BLOCK ) { const parent = stationaryNode.getSourceBlock()?.getParent(); - if (!parent) return false; - return this.tryToConnectBlock( + if (!parent) return null; + return this.findInsertStartPoint( Blockly.ASTNode.createBlockNode(parent), movingBlock, ); } - return this.insertBlock(movingBlock, stationaryBlock.outputConnection); + return stationaryBlock.outputConnection; } } - this.warn(`Unexpected case in tryToConnectBlock ${stationaryType}.`); - return false; + this.warn(`Unexpected case in findInsertStartPoint ${stationaryType}.`); + return null; + } + + /** + * Tries to intelligently connect the blocks or connections + * represented by the given nodes, based on node types and locations. + * + * @param stationaryNode The first node to connect. + * @param movingBlock The block we're moving. + * @returns True if the key was handled; false if something went + * wrong. + */ + tryToConnectBlock( + stationaryNode: Blockly.ASTNode, + movingBlock: Blockly.BlockSvg, + ): boolean { + const destConnection = this.findInsertStartPoint( + stationaryNode, + movingBlock, + ); + if (!destConnection) return false; + return this.insertBlock(movingBlock, destConnection); } /**