Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions src/actions/enter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand All @@ -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);
}

/**
Expand Down
48 changes: 40 additions & 8 deletions src/actions/mover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,

Check warning on line 18 in src/actions/mover.ts

View workflow job for this annotation

GitHub Actions / build

'Events' is defined but never used
registry,
utils,
WorkspaceSvg,
Expand Down Expand Up @@ -95,9 +102,14 @@
* 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');
Expand All @@ -107,7 +119,7 @@
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,
Expand Down Expand Up @@ -144,6 +156,7 @@
this.unpatchWorkspace(workspace);
this.unpatchDragStrategy(info.block);
this.moves.delete(workspace);

return true;
}

Expand All @@ -159,17 +172,31 @@
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);
Expand Down Expand Up @@ -289,11 +316,16 @@
* 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));
}

/**
Expand Down
9 changes: 8 additions & 1 deletion src/keyboard_drag_strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) {
Expand Down
66 changes: 41 additions & 25 deletions src/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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,
);
Expand All @@ -695,18 +693,18 @@ 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,
);
}

// 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;

Expand All @@ -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.
Expand All @@ -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);
}

/**
Expand Down