From f0aee70d11abcefbaedc4b228e5eb5936658abcf Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Wed, 9 Apr 2025 09:17:47 -0700 Subject: [PATCH 1/3] fix: add looping for finding the next/previous node during movement --- src/keyboard_drag_strategy.ts | 83 +++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 8 deletions(-) diff --git a/src/keyboard_drag_strategy.ts b/src/keyboard_drag_strategy.ts index c8fee5db..b25d61d4 100644 --- a/src/keyboard_drag_strategy.ts +++ b/src/keyboard_drag_strategy.ts @@ -150,15 +150,9 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { const dir = this.currentDragDirection; while (potential && !candidateConnection) { if (dir === Direction.Up || dir === Direction.Left) { - potential = cursor.getPreviousNode(potential, (node) => { - // @ts-expect-error isConnectionType is private. - return node && ASTNode.isConnectionType(node.getType()); - }); + potential = this.getPreviousNode(potential, cursor); } else if (dir === Direction.Down || dir === Direction.Right) { - potential = cursor.getNextNode(potential, (node) => { - // @ts-expect-error isConnectionType is private. - return node && ASTNode.isConnectionType(node.getType()); - }); + potential = this.getNextNode(potential, cursor); } localConns.forEach((conn: RenderedConnection) => { @@ -289,4 +283,77 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { override shouldHealStack(e: PointerEvent | undefined): boolean { return true; } + + /** + * Get the previous node in the tree, with loopback to the last + * stack on the workspace if needed. + * + * @param start Where to start traversal. + * @param cursor The workspace's cursor + * @returns The previous node, or null if there were no valid nodes + * on the workspace. + */ + private getPreviousNode(start: ASTNode, cursor: LineCursor) { + let potential: ASTNode | null = start; + potential = cursor.getPreviousNode(potential, (node) => { + // @ts-expect-error isConnectionType is private. + return node && ASTNode.isConnectionType(node.getType()); + }); + if (!potential) { + // Loop back to last block if it exists. + // @ts-expect-error workspace is private + const topBlocks = this.workspace.getTopBlocks(true); + if (!topBlocks.length) return null; + + // Find the last stack. + const lastTopBlockNode = ASTNode.createStackNode( + topBlocks[topBlocks.length - 1], + ); + let prevNode = lastTopBlockNode; + let nextNode: ASTNode | null = lastTopBlockNode; + // Iterate until you fall off the end of the stack. + while (nextNode) { + prevNode = nextNode; + nextNode = cursor.getNextNode(prevNode, (node) => { + return !!node; + }); + } + + // Resume searching. + potential = cursor.getPreviousNode(prevNode, (node) => { + // @ts-expect-error isConnectionType is private. + return node && ASTNode.isConnectionType(node.getType()); + }); + } + return potential; + } + + /** + * Get the next node in the tree, with loopback to the first + * stack on the workspace if needed. + * + * @param start Where to start traversal. + * @param cursor The workspace's cursor + * @returns The next node, or null if there were no valid nodes + * on the workspace. + */ + private getNextNode(start: ASTNode, cursor: LineCursor) { + let potential: ASTNode | null = start; + potential = cursor.getNextNode(potential, (node) => { + // @ts-expect-error isConnectionType is private. + return node && ASTNode.isConnectionType(node.getType()); + }); + if (!potential) { + // Loop back to first block if it exists. + // @ts-expect-error workspace is private + const topBlocks = this.workspace.getTopBlocks(true); + if (!topBlocks.length) return null; + const initial = ASTNode.createTopNode(topBlocks[0]); + potential = cursor.getNextNode(initial, (node) => { + // @ts-expect-error isConnectionType is private. + return node && ASTNode.isConnectionType(node.getType()); + }); + } + return potential; + } } From 90de0523ce3c1218a7e63a72aa01846feb840cd4 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Wed, 9 Apr 2025 09:18:28 -0700 Subject: [PATCH 2/3] chore: fix lint in undo_redo.ts --- src/actions/undo_redo.ts | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/actions/undo_redo.ts b/src/actions/undo_redo.ts index e43e9b66..a2509767 100644 --- a/src/actions/undo_redo.ts +++ b/src/actions/undo_redo.ts @@ -4,17 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { - ShortcutRegistry, - utils as BlocklyUtils, - ShortcutItems, - WorkspaceSvg, -} from 'blockly/core'; - -import * as Constants from '../constants'; -import type {Navigation} from '../navigation'; - -const KeyCodes = BlocklyUtils.KeyCodes; +import {ShortcutRegistry, ShortcutItems, WorkspaceSvg} from 'blockly/core'; /** * Class for registering a shortcut for undo/redo actions. From b0b16115de4dc7b7af86cd9f2f70050ab22e3ad0 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 11 Apr 2025 20:24:16 -0700 Subject: [PATCH 3/3] feat: use core looping feature --- src/keyboard_drag_strategy.ts | 82 +++-------------------------------- 1 file changed, 7 insertions(+), 75 deletions(-) diff --git a/src/keyboard_drag_strategy.ts b/src/keyboard_drag_strategy.ts index b25d61d4..31ff8aea 100644 --- a/src/keyboard_drag_strategy.ts +++ b/src/keyboard_drag_strategy.ts @@ -144,15 +144,20 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { const cursor = draggingBlock.workspace.getCursor() as LineCursor; if (!cursor) return null; + // Helper function for traversal. + function isConnection(node: ASTNode | null): boolean { + return !!node && node.isConnection(); + } + const connectionChecker = draggingBlock.workspace.connectionChecker; let candidateConnection: ConnectionCandidate | null = null; let potential: ASTNode | null = this.searchNode; const dir = this.currentDragDirection; while (potential && !candidateConnection) { if (dir === Direction.Up || dir === Direction.Left) { - potential = this.getPreviousNode(potential, cursor); + potential = cursor.getPreviousNode(potential, isConnection, true); } else if (dir === Direction.Down || dir === Direction.Right) { - potential = this.getNextNode(potential, cursor); + potential = cursor.getNextNode(potential, isConnection, true); } localConns.forEach((conn: RenderedConnection) => { @@ -283,77 +288,4 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { override shouldHealStack(e: PointerEvent | undefined): boolean { return true; } - - /** - * Get the previous node in the tree, with loopback to the last - * stack on the workspace if needed. - * - * @param start Where to start traversal. - * @param cursor The workspace's cursor - * @returns The previous node, or null if there were no valid nodes - * on the workspace. - */ - private getPreviousNode(start: ASTNode, cursor: LineCursor) { - let potential: ASTNode | null = start; - potential = cursor.getPreviousNode(potential, (node) => { - // @ts-expect-error isConnectionType is private. - return node && ASTNode.isConnectionType(node.getType()); - }); - if (!potential) { - // Loop back to last block if it exists. - // @ts-expect-error workspace is private - const topBlocks = this.workspace.getTopBlocks(true); - if (!topBlocks.length) return null; - - // Find the last stack. - const lastTopBlockNode = ASTNode.createStackNode( - topBlocks[topBlocks.length - 1], - ); - let prevNode = lastTopBlockNode; - let nextNode: ASTNode | null = lastTopBlockNode; - // Iterate until you fall off the end of the stack. - while (nextNode) { - prevNode = nextNode; - nextNode = cursor.getNextNode(prevNode, (node) => { - return !!node; - }); - } - - // Resume searching. - potential = cursor.getPreviousNode(prevNode, (node) => { - // @ts-expect-error isConnectionType is private. - return node && ASTNode.isConnectionType(node.getType()); - }); - } - return potential; - } - - /** - * Get the next node in the tree, with loopback to the first - * stack on the workspace if needed. - * - * @param start Where to start traversal. - * @param cursor The workspace's cursor - * @returns The next node, or null if there were no valid nodes - * on the workspace. - */ - private getNextNode(start: ASTNode, cursor: LineCursor) { - let potential: ASTNode | null = start; - potential = cursor.getNextNode(potential, (node) => { - // @ts-expect-error isConnectionType is private. - return node && ASTNode.isConnectionType(node.getType()); - }); - if (!potential) { - // Loop back to first block if it exists. - // @ts-expect-error workspace is private - const topBlocks = this.workspace.getTopBlocks(true); - if (!topBlocks.length) return null; - const initial = ASTNode.createTopNode(topBlocks[0]); - potential = cursor.getNextNode(initial, (node) => { - // @ts-expect-error isConnectionType is private. - return node && ASTNode.isConnectionType(node.getType()); - }); - } - return potential; - } }