diff --git a/src/actions/action_menu.ts b/src/actions/action_menu.ts index 82f3eb36..ed456fbc 100644 --- a/src/actions/action_menu.ts +++ b/src/actions/action_menu.ts @@ -15,7 +15,7 @@ const createSerializedKey = ShortcutRegistry.registry.createSerializedKey.bind( ); /** - * Keyboard shortcut to show the action menu on Cmr/Ctrl/Alt+Enter key. + * Keyboard shortcut to show the action menu on Cmd/Ctrl/Alt+Enter key. */ export class ActionMenu { /** @@ -86,14 +86,13 @@ export class ActionMenu { const node = cursor.getCurNode(); if (!node) return false; // TODO(google/blockly#8847): Add typeguard for IContextMenu in core when this moves over - const location = node.getLocation(); if ( - 'showContextMenu' in location && - typeof location.showContextMenu === 'function' + 'showContextMenu' in node && + typeof node.showContextMenu === 'function' ) { - location.showContextMenu(menuOpenEvent); + node.showContextMenu(menuOpenEvent); } else { - console.info(`No action menu for ASTNode of type ${node.getType()}`); + console.info(`No action menu for node ${node}`); return false; } diff --git a/src/actions/arrow_navigation.ts b/src/actions/arrow_navigation.ts index 7945e72d..e0eef938 100644 --- a/src/actions/arrow_navigation.ts +++ b/src/actions/arrow_navigation.ts @@ -4,9 +4,9 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {ASTNode, ShortcutRegistry, utils as BlocklyUtils} from 'blockly/core'; +import {ShortcutRegistry, utils as BlocklyUtils, Field} from 'blockly/core'; -import type {Field, Toolbox, WorkspaceSvg} from 'blockly/core'; +import type {Toolbox, WorkspaceSvg} from 'blockly/core'; import * as Blockly from 'blockly/core'; import * as Constants from '../constants'; @@ -38,8 +38,8 @@ export class ArrowNavigation { return false; } const curNode = cursor.getCurNode(); - if (curNode?.getType() === ASTNode.types.FIELD) { - return (curNode.getLocation() as Field).onShortcut(shortcut); + if (curNode instanceof Field) { + return curNode.onShortcut(shortcut); } return false; } diff --git a/src/actions/clipboard.ts b/src/actions/clipboard.ts index 677bdf7f..e3227394 100644 --- a/src/actions/clipboard.ts +++ b/src/actions/clipboard.ts @@ -13,10 +13,11 @@ import { clipboard, ICopyData, LineCursor, + FocusableTreeTraverser, getFocusManager, } from 'blockly'; import * as Constants from '../constants'; -import type {BlockSvg, WorkspaceSvg} from 'blockly'; +import type {BlockSvg, WorkspaceSvg, INavigable} from 'blockly'; import {Navigation} from '../navigation'; import {getShortActionShortcut} from '../shortcut_formatting'; import * as Blockly from 'blockly'; @@ -134,17 +135,15 @@ export class Clipboard { */ private cutPrecondition(workspace: WorkspaceSvg) { if (this.navigation.canCurrentlyEdit(workspace)) { - const curNode = workspace.getCursor()?.getCurNode(); - if (curNode && curNode.getSourceBlock()) { - const sourceBlock = curNode.getSourceBlock(); - return !!( - !Gesture.inProgress() && - sourceBlock && - sourceBlock.isDeletable() && - sourceBlock.isMovable() && - !sourceBlock.workspace.isFlyout - ); - } + const sourceBlock = workspace.getCursor()?.getSourceBlock(); + + return !!( + !Gesture.inProgress() && + sourceBlock && + sourceBlock.isDeletable() && + sourceBlock.isMovable() && + !sourceBlock.workspace.isFlyout + ); } return false; } @@ -161,9 +160,7 @@ export class Clipboard { private cutCallback(workspace: WorkspaceSvg) { const cursor = workspace.getCursor(); if (!cursor) throw new TypeError('no cursor'); - const sourceBlock = cursor - .getCurNode() - ?.getSourceBlock() as BlockSvg | null; + const sourceBlock = cursor.getSourceBlock(); if (!sourceBlock) throw new TypeError('no source block'); this.copyData = sourceBlock.toCopyData(); this.copyWorkspace = sourceBlock.workspace; @@ -235,8 +232,7 @@ export class Clipboard { if (!this.navigation.canCurrentlyEdit(workspace)) return false; switch (this.navigation.getState(workspace)) { case Constants.STATE.WORKSPACE: { - const curNode = workspace?.getCursor()?.getCurNode(); - const source = curNode?.getSourceBlock(); + const source = workspace?.getCursor()?.getSourceBlock(); return !!( source?.isDeletable() && source?.isMovable() && @@ -245,10 +241,7 @@ export class Clipboard { } case Constants.STATE.FLYOUT: { const flyoutWorkspace = workspace.getFlyout()?.getWorkspace(); - const sourceBlock = flyoutWorkspace - ?.getCursor() - ?.getCurNode() - ?.getSourceBlock(); + const sourceBlock = flyoutWorkspace?.getCursor()?.getSourceBlock(); return !!(sourceBlock && !Gesture.inProgress()); } default: @@ -271,10 +264,7 @@ export class Clipboard { if (navigationState === Constants.STATE.FLYOUT) { activeWorkspace = workspace.getFlyout()?.getWorkspace(); } - const sourceBlock = activeWorkspace - ?.getCursor() - ?.getCurNode() - ?.getSourceBlock() as BlockSvg; + const sourceBlock = activeWorkspace?.getCursor()?.getSourceBlock(); if (!sourceBlock) return false; this.copyData = sourceBlock.toCopyData(); @@ -376,7 +366,9 @@ export class Clipboard { ? workspace : this.copyWorkspace; - const targetNode = this.navigation.getFocusedASTNode(pasteWorkspace); + const targetNode = FocusableTreeTraverser.findFocusedNode( + pasteWorkspace, + ) as unknown as INavigable; // If we're pasting in the flyout it still targets the workspace. Focus // first as to ensure correct selection handling. getFocusManager().focusTree(workspace); diff --git a/src/actions/delete.ts b/src/actions/delete.ts index 1e32ab45..7874e37a 100644 --- a/src/actions/delete.ts +++ b/src/actions/delete.ts @@ -155,7 +155,7 @@ export class DeleteAction { * @returns True iff `deleteCallback` function should be called. */ private deletePrecondition(workspace: WorkspaceSvg) { - const sourceBlock = workspace.getCursor()?.getCurNode()?.getSourceBlock(); + const sourceBlock = workspace.getCursor()?.getSourceBlock(); return ( !workspace.isDragging() && this.navigation.canCurrentlyEdit(workspace) && @@ -178,9 +178,7 @@ export class DeleteAction { const cursor = workspace.getCursor(); if (!cursor) return false; - const sourceBlock = cursor - .getCurNode() - ?.getSourceBlock() as BlockSvg | null; + const sourceBlock = cursor.getSourceBlock(); if (!sourceBlock) return false; // Delete or backspace. // There is an event if this is triggered from a keyboard shortcut, diff --git a/src/actions/disconnect.ts b/src/actions/disconnect.ts index 0f6b8c94..bbc66b35 100644 --- a/src/actions/disconnect.ts +++ b/src/actions/disconnect.ts @@ -5,14 +5,13 @@ */ import { - ASTNode, BlockSvg, RenderedConnection, ShortcutRegistry, utils as BlocklyUtils, } from 'blockly'; import * as Constants from '../constants'; -import type {WorkspaceSvg} from 'blockly'; +import type {WorkspaceSvg, INavigable} from 'blockly'; import {Navigation} from '../navigation'; const KeyCodes = BlocklyUtils.KeyCodes; @@ -80,45 +79,46 @@ export class DisconnectAction { if (!cursor) { return; } - let curNode: ASTNode | null = cursor.getCurNode(); + let curNode: INavigable | null = cursor.getCurNode(); let wasVisitingConnection = true; - while (curNode && !curNode.isConnection()) { - const location = curNode.getLocation(); - if (location instanceof BlockSvg) { - const previous = location.previousConnection; - const output = location.outputConnection; + while ( + curNode && + !(curNode instanceof RenderedConnection && curNode.isConnected()) + ) { + if (curNode instanceof BlockSvg) { + const previous = curNode.previousConnection; + const output = curNode.outputConnection; if (previous?.isConnected()) { - curNode = ASTNode.createConnectionNode(previous); + curNode = previous; break; } else if (output?.isConnected()) { - curNode = ASTNode.createConnectionNode(output); + curNode = output; break; } } - curNode = curNode.out(); + curNode = workspace.getNavigator().getParent(curNode); wasVisitingConnection = false; } if (!curNode) { console.log('Unable to find a connection to disconnect'); return; } - const curConnection = curNode.getLocation() as RenderedConnection; - if (!curConnection.isConnected()) { + if (!(curNode instanceof RenderedConnection && curNode.isConnected())) { return; } - const targetConnection = curConnection.targetConnection; + const targetConnection = curNode.targetConnection; if (!targetConnection) { throw new Error('Must have target if connected'); } - const superiorConnection = curConnection.isSuperior() - ? curConnection + const superiorConnection = curNode.isSuperior() + ? curNode : targetConnection; - const inferiorConnection = curConnection.isSuperior() + const inferiorConnection = curNode.isSuperior() ? targetConnection - : curConnection; + : curNode; if (inferiorConnection.getSourceBlock().isShadow()) { return; @@ -135,8 +135,7 @@ export class DisconnectAction { rootBlock.bringToFront(); if (wasVisitingConnection) { - const connectionNode = ASTNode.createConnectionNode(superiorConnection); - workspace.getCursor()?.setCurNode(connectionNode); + workspace.getCursor()?.setCurNode(superiorConnection); } } } diff --git a/src/actions/enter.ts b/src/actions/enter.ts index 161199f6..408b14f6 100644 --- a/src/actions/enter.ts +++ b/src/actions/enter.ts @@ -5,21 +5,20 @@ */ import { - ASTNode, Events, ShortcutRegistry, utils as BlocklyUtils, getFocusManager, -} from 'blockly/core'; - -import type { - Block, BlockSvg, - Field, FlyoutButton, + RenderedConnection, WorkspaceSvg, + Field, + FocusableTreeTraverser, } from 'blockly/core'; +import type {Block, INavigable} from 'blockly/core'; + import * as Constants from '../constants'; import type {Navigation} from '../navigation'; import {Mover} from './mover'; @@ -72,15 +71,10 @@ export class EnterAction { return false; } curNode = flyoutCursor.getCurNode(); - nodeType = curNode?.getType(); - - switch (nodeType) { - case ASTNode.types.STACK: - this.insertFromFlyout(workspace); - break; - case ASTNode.types.BUTTON: - this.triggerButtonCallback(workspace); - break; + if (curNode instanceof BlockSvg) { + this.insertFromFlyout(workspace); + } else if (curNode instanceof FlyoutButton) { + this.triggerButtonCallback(workspace); } return true; @@ -102,18 +96,17 @@ export class EnterAction { if (!cursor) return; const curNode = cursor.getCurNode(); if (!curNode) return; - const nodeType = curNode.getType(); - if (nodeType === ASTNode.types.FIELD) { - (curNode.getLocation() as Field).showEditor(); - } else if (nodeType === ASTNode.types.BLOCK) { - const block = curNode.getLocation() as Block; - if (!this.tryShowFullBlockFieldEditor(block)) { + if (curNode instanceof Field) { + curNode.showEditor(); + } else if (curNode instanceof BlockSvg) { + if (!this.tryShowFullBlockFieldEditor(curNode)) { showHelpHint(workspace); } - } else if (curNode.isConnection() || nodeType === ASTNode.types.WORKSPACE) { + } else if ( + curNode instanceof RenderedConnection || + curNode instanceof WorkspaceSvg + ) { this.navigation.openToolboxOrFlyout(workspace); - } else if (nodeType === ASTNode.types.STACK) { - console.warn('Cannot mark a stack.'); } } @@ -135,7 +128,9 @@ export class EnterAction { Events.setGroup(true); } - const stationaryNode = this.navigation.getFocusedASTNode(workspace); + const stationaryNode = FocusableTreeTraverser.findFocusedNode( + workspace, + ) as unknown as INavigable; const newBlock = this.createNewBlock(workspace); if (!newBlock) return; const insertStartPoint = stationaryNode @@ -148,8 +143,7 @@ export class EnterAction { workspace.setResizesEnabled(true); getFocusManager().focusTree(workspace); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - workspace.getCursor()?.setCurNode(ASTNode.createBlockNode(newBlock)!); + workspace.getCursor()?.setCurNode(newBlock); this.mover.startMove(workspace, newBlock, insertStartPoint); const isStartBlock = @@ -253,11 +247,8 @@ export class EnterAction { * containing a flyout with a button. */ private triggerButtonCallback(workspace: WorkspaceSvg) { - const button = this.navigation - .getFlyoutCursor(workspace) - ?.getCurNode() - ?.getLocation() as FlyoutButton | undefined; - if (!button) return; + const button = this.navigation.getFlyoutCursor(workspace)?.getCurNode(); + if (!(button instanceof FlyoutButton)) return; const flyoutButtonCallbacks: Map void> = // @ts-expect-error private field access @@ -310,11 +301,8 @@ export class EnterAction { return null; } - const curBlock = this.navigation - .getFlyoutCursor(workspace) - ?.getCurNode() - ?.getLocation() as BlockSvg | undefined; - if (!curBlock?.isEnabled()) { + const curBlock = this.navigation.getFlyoutCursor(workspace)?.getCurNode(); + if (!(curBlock instanceof BlockSvg) || !curBlock.isEnabled()) { console.warn("Can't insert a disabled block."); return null; } diff --git a/src/actions/move.ts b/src/actions/move.ts index 2734c89e..1ee6b76b 100644 --- a/src/actions/move.ts +++ b/src/actions/move.ts @@ -196,10 +196,9 @@ export class MoveActions { * could be found. */ getCurrentBlock(workspace: WorkspaceSvg): BlockSvg | undefined { - const curNode = workspace?.getCursor()?.getCurNode(); - let block = curNode?.getSourceBlock(); + let block = workspace?.getCursor()?.getSourceBlock(); if (!block) return undefined; - while (block?.isShadow()) { + while (block.isShadow()) { block = block.getParent(); if (!block) { throw new Error( @@ -208,6 +207,6 @@ export class MoveActions { ); } } - return block as BlockSvg; + return block; } } diff --git a/src/actions/mover.ts b/src/actions/mover.ts index 1544d9c4..1f6a648c 100644 --- a/src/actions/mover.ts +++ b/src/actions/mover.ts @@ -12,7 +12,6 @@ import type { RenderedConnection, } from 'blockly'; import { - ASTNode, Connection, dragging, getFocusManager, @@ -202,8 +201,7 @@ export class Mover { ); if (dragStrategy.isNewBlock && target) { - const newNode = ASTNode.createConnectionNode(target); - if (newNode) workspace.getCursor()?.setCurNode(newNode); + workspace.getCursor()?.setCurNode(target); } this.unpatchDragStrategy(info.block); diff --git a/src/actions/ws_movement.ts b/src/actions/ws_movement.ts index 246c3d6d..987736d9 100644 --- a/src/actions/ws_movement.ts +++ b/src/actions/ws_movement.ts @@ -4,7 +4,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import {ASTNode, ShortcutRegistry, utils as BlocklyUtils} from 'blockly'; +import {ShortcutRegistry, utils as BlocklyUtils} from 'blockly'; import * as Constants from '../constants'; import type {WorkspaceSvg} from 'blockly'; import {Navigation} from 'src/navigation'; @@ -103,24 +103,7 @@ export class WorkspaceMovement { xDirection: number, yDirection: number, ): boolean { - const cursor = workspace.getCursor(); - if (!cursor) return false; - const curNode = cursor?.getCurNode(); - if (!curNode || curNode.getType() !== ASTNode.types.WORKSPACE) return false; - - const wsCoord = curNode.getWsCoordinate(); - if (!wsCoord) return false; - - const newX = xDirection * WS_MOVE_DISTANCE + wsCoord.x; - const newY = yDirection * WS_MOVE_DISTANCE + wsCoord.y; - - cursor.setCurNode( - ASTNode.createWorkspaceNode( - workspace, - new BlocklyUtils.Coordinate(newX, newY), - ), - ); - return true; + return false; } /** @@ -129,15 +112,11 @@ export class WorkspaceMovement { * @param workspace The workspace the cursor is on. */ createWSCursor(workspace: WorkspaceSvg) { - const workspaceNode = ASTNode.createWorkspaceNode( - workspace, - new BlocklyUtils.Coordinate(10, 10), - ); const cursor = workspace.getCursor(); - if (!cursor || !workspaceNode) return false; + if (!cursor) return false; - cursor.setCurNode(workspaceNode); + cursor.setCurNode(workspace); return true; } } diff --git a/src/flyout_cursor.ts b/src/flyout_cursor.ts index d69f91bd..4b917324 100644 --- a/src/flyout_cursor.ts +++ b/src/flyout_cursor.ts @@ -33,12 +33,12 @@ export class FlyoutCursor extends Blockly.LineCursor { * @returns The next element, or null if the current node is * not set or there is no next value. */ - override next(): Blockly.ASTNode | null { + override next(): Blockly.INavigable | null { const curNode = this.getCurNode(); if (!curNode) { return null; } - const newNode = curNode.next(); + const newNode = this.workspace.getNavigator().getNextSibling(curNode); if (newNode) { this.setCurNode(newNode); @@ -61,12 +61,12 @@ export class FlyoutCursor extends Blockly.LineCursor { * @returns The previous element, or null if the current * node is not set or there is no previous value. */ - override prev(): Blockly.ASTNode | null { + override prev(): Blockly.INavigable | null { const curNode = this.getCurNode(); if (!curNode) { return null; } - const newNode = curNode.prev(); + const newNode = this.workspace.getNavigator().getPreviousSibling(curNode); if (newNode) { this.setCurNode(newNode); @@ -83,25 +83,19 @@ export class FlyoutCursor extends Blockly.LineCursor { return null; } - override setCurNode(node: Blockly.ASTNode | null) { + override setCurNode(node: Blockly.INavigable | null) { super.setCurNode(node); - const location = node?.getLocation(); let bounds: Blockly.utils.Rect | undefined; if ( - location && - 'getBoundingRectangle' in location && - typeof location.getBoundingRectangle === 'function' + node && + 'getBoundingRectangle' in node && + typeof node.getBoundingRectangle === 'function' ) { - bounds = location.getBoundingRectangle(); - } else if (location instanceof Blockly.FlyoutButton) { - const {x, y} = location.getPosition(); - bounds = new Blockly.utils.Rect( - y, - y + location.height, - x, - x + location.width, - ); + bounds = node.getBoundingRectangle(); + } else if (node instanceof Blockly.FlyoutButton) { + const {x, y} = node.getPosition(); + bounds = new Blockly.utils.Rect(y, y + node.height, x, x + node.width); } if (!(bounds instanceof Blockly.utils.Rect)) return; diff --git a/src/keyboard_drag_strategy.ts b/src/keyboard_drag_strategy.ts index eb1a5960..fa36f2b3 100644 --- a/src/keyboard_drag_strategy.ts +++ b/src/keyboard_drag_strategy.ts @@ -5,13 +5,13 @@ */ import { - ASTNode, BlockSvg, ConnectionType, RenderedConnection, LineCursor, dragging, utils, + INavigable, } from 'blockly'; import {Direction, getDirectionFromXY} from './drag_direction'; import {showUnconstrainedMoveHint} from './hints'; @@ -35,7 +35,7 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { private currentDragDirection: Direction | null = null; /** Where a constrained movement should start when traversing the tree. */ - private searchNode: ASTNode | null = null; + private searchNode: RenderedConnection | null = null; private cursor: LineCursor | null; @@ -52,7 +52,7 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { override startDrag(e?: PointerEvent) { if (!this.cursor) throw new Error('precondition failure'); - this.cursor.setCurNode(ASTNode.createBlockNode(this.block)); + this.cursor.setCurNode(this.block); super.startDrag(e); // Set position of the dragging block, so that it doesn't pop // to the top left of the workspace. @@ -77,7 +77,7 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { .neighbour; // The next constrained move will resume the search from the current // candidate location. - this.searchNode = ASTNode.createConnectionNode(neighbour); + this.searchNode = neighbour; if (this.isConstrainedMovement()) { // Position the moving block down and slightly to the right of the // target connection. @@ -173,13 +173,15 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { if (!this.cursor) throw new Error('precondition failure'); // Helper function for traversal. - function isConnection(node: ASTNode | null): boolean { - return !!node && node.isConnection(); + function isConnection( + node: INavigable | null, + ): node is RenderedConnection { + return node instanceof RenderedConnection; } const connectionChecker = draggingBlock.workspace.connectionChecker; let candidateConnection: ConnectionCandidate | null = null; - let potential: ASTNode | null = this.searchNode; + let potential: INavigable | null = this.searchNode; const dir = this.currentDragDirection; while (potential && !candidateConnection) { if (dir === Direction.Up || dir === Direction.Left) { @@ -189,11 +191,13 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { } localConns.forEach((conn: RenderedConnection) => { - const location = potential?.getLocation() as RenderedConnection; - if (connectionChecker.canConnect(conn, location, true, Infinity)) { + if ( + isConnection(potential) && + connectionChecker.canConnect(conn, potential, true, Infinity) + ) { candidateConnection = { local: conn, - neighbour: location, + neighbour: potential, distance: 0, }; } @@ -291,7 +295,7 @@ export class KeyboardDragStrategy extends dragging.BlockDragStrategy { // @ts-expect-error startParentConn is private. const neighbour = this.insertStartPoint ?? this.startParentConn; if (neighbour) { - this.searchNode = ASTNode.createConnectionNode(neighbour); + this.searchNode = neighbour; switch (neighbour.type) { case ConnectionType.INPUT_VALUE: return { diff --git a/src/navigation.ts b/src/navigation.ts index 14c2c67e..3d8f4cf0 100644 --- a/src/navigation.ts +++ b/src/navigation.ts @@ -135,27 +135,6 @@ export class Navigation { return Constants.STATE.NOWHERE; } - /** - * Searches the specified workspace for an ASTNode representation of its - * current focused node (which may be active or passive). - * - * @param workspace The workspace being searched. - * @returns An ASTNode representation of the current focused node, or null if - * the specified workspace either doesn't have a focused node, or it - * cannot be represented as an ASTNode. - */ - getFocusedASTNode(workspace: Blockly.WorkspaceSvg): Blockly.ASTNode | null { - const passive = Blockly.FocusableTreeTraverser.findFocusedNode(workspace); - if (passive instanceof Blockly.BlockSvg) { - return Blockly.ASTNode.createBlockNode(passive); - } else if (passive instanceof Blockly.Field) { - return Blockly.ASTNode.createFieldNode(passive); - } else if (passive instanceof Blockly.Connection) { - return Blockly.ASTNode.createConnectionNode(passive); - } - return null; - } - /** * Adds all event listeners and cursors to the flyout that are needed for * keyboard navigation to work. @@ -172,7 +151,7 @@ export class Navigation { if (FlyoutCursorClass) { flyoutWorkspace .getMarkerManager() - .setCursor(new FlyoutCursorClass(flyout)); + .setCursor(new Blockly.LineCursor(flyout.getWorkspace())); } } @@ -278,15 +257,15 @@ export class Navigation { } } - private isFlyoutItemDisposed(node: Blockly.ASTNode) { - if (node.getSourceBlock()?.disposed) { + private isFlyoutItemDisposed( + node: Blockly.INavigable, + sourceBlock: Blockly.BlockSvg | null, + ) { + if (sourceBlock?.disposed) { return true; } - const location = node.getLocation(); - if (location instanceof Blockly.FlyoutButton) { - // No nice way to tell for a button. In v12 we could use getSvgGroup(). - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (location as any).svgGroup.parentNode === null; + if (node instanceof Blockly.FlyoutButton) { + return node.getSvgRoot().parentNode === null; } return false; } @@ -305,12 +284,9 @@ export class Navigation { ) { const mutatedBlockId = e.blockId; const cursor = workspace.getCursor(); - if (cursor) { - const curNode = cursor.getCurNode(); - const block = curNode ? curNode.getSourceBlock() : null; - if (block && block.id === mutatedBlockId) { - cursor.setCurNode(Blockly.ASTNode.createBlockNode(block)); - } + const block = cursor?.getSourceBlock(); + if (block && block.id === mutatedBlockId) { + cursor?.setCurNode(block); } } @@ -331,13 +307,11 @@ export class Navigation { if (!cursor) return; // Make sure the cursor is on a block. - const sourceBlock = cursor.getCurNode()?.getSourceBlock(); + const sourceBlock = cursor.getSourceBlock(); if (!sourceBlock) return; if (sourceBlock.id === deletedBlockId || ids.includes(sourceBlock.id)) { - cursor.setCurNode( - Blockly.ASTNode.createWorkspaceNode(workspace, WS_COORDINATE_ON_DELETE), - ); + cursor.setCurNode(workspace); } } @@ -357,9 +331,7 @@ export class Navigation { } const curNodeBlock = block.isShadow() ? block : block.getParent(); if (curNodeBlock) { - this.getFlyoutCursor(mainWorkspace)?.setCurNode( - Blockly.ASTNode.createStackNode(curNodeBlock), - ); + this.getFlyoutCursor(mainWorkspace)?.setCurNode(curNodeBlock); } const flyout = mainWorkspace.getFlyout(); if (flyout) { @@ -380,34 +352,26 @@ export class Navigation { prefer: 'first' | 'last' = 'first', ) { const flyout = workspace.getFlyout(); - if (!flyout) return; + if (!flyout) return false; const flyoutCursor = this.getFlyoutCursor(workspace); - if (!flyoutCursor) return; + if (!flyoutCursor) return false; const curNode = flyoutCursor.getCurNode(); - if (curNode && !this.isFlyoutItemDisposed(curNode)) return false; + const sourceBlock = flyoutCursor.getSourceBlock(); + if (curNode && !this.isFlyoutItemDisposed(curNode, sourceBlock)) + return false; const flyoutContents = flyout.getContents(); const defaultFlyoutItem = prefer === 'first' ? flyoutContents[0] : flyoutContents[flyoutContents.length - 1]; - if (!defaultFlyoutItem) return; + if (!defaultFlyoutItem) return false; const defaultFlyoutItemElement = defaultFlyoutItem.getElement(); - if (defaultFlyoutItemElement instanceof Blockly.FlyoutButton) { - const astNode = Blockly.ASTNode.createButtonNode( - defaultFlyoutItemElement as Blockly.FlyoutButton, - ); - flyoutCursor.setCurNode(astNode); - return true; - } else if (defaultFlyoutItemElement instanceof Blockly.BlockSvg) { - const astNode = Blockly.ASTNode.createStackNode( - defaultFlyoutItemElement as Blockly.BlockSvg, - ); - flyoutCursor.setCurNode(astNode); - return true; - } - return false; + flyoutCursor.setCurNode( + defaultFlyoutItemElement as unknown as Blockly.INavigable, + ); + return true; } /** @@ -430,7 +394,7 @@ export class Navigation { if (!cursor) { return; } - const disposed = cursor.getCurNode()?.getSourceBlock()?.disposed; + const disposed = cursor.getSourceBlock()?.disposed; if (cursor.getCurNode() && !disposed) { // Retain the cursor's previous position since it's set, but only if not // disposed (which can happen when blocks are reloaded). @@ -442,16 +406,10 @@ export class Navigation { ); if (topBlocks.length > 0) { cursor.setCurNode( - Blockly.ASTNode.createTopNode( - topBlocks[prefer === 'first' ? 0 : topBlocks.length - 1], - ), + topBlocks[prefer === 'first' ? 0 : topBlocks.length - 1], ); } else { - const wsNode = Blockly.ASTNode.createWorkspaceNode( - workspace, - wsCoordinates, - ); - cursor.setCurNode(wsNode); + cursor.setCurNode(workspace); } return true; } @@ -479,52 +437,39 @@ export class Navigation { * wrong. */ findInsertStartPoint( - stationaryNode: Blockly.ASTNode, + stationaryNode: Blockly.INavigable, movingBlock: Blockly.BlockSvg, ): Blockly.RenderedConnection | null { - const stationaryType = stationaryNode.getType(); - const stationaryLoc = stationaryNode.getLocation(); const movingHasOutput = !!movingBlock.outputConnection; - if (stationaryNode.getType() === Blockly.ASTNode.types.FIELD) { + if (stationaryNode instanceof Blockly.Field) { // Can't connect a block to a field, so try going up to the source block. - const sourceBlock = stationaryNode.getSourceBlock(); + const sourceBlock = stationaryNode.getSourceBlock() as Blockly.BlockSvg; if (!sourceBlock) return null; - return this.findInsertStartPoint( - Blockly.ASTNode.createBlockNode(sourceBlock), - movingBlock, - ); - } else if (stationaryNode.isConnection()) { - const stationaryAsConnection = - stationaryLoc as Blockly.RenderedConnection; - + return this.findInsertStartPoint(sourceBlock, movingBlock); + } else if (stationaryNode instanceof Blockly.RenderedConnection) { // Move to the block if we're trying to insert a statement block into // a value connection. if ( !movingHasOutput && - stationaryAsConnection.type === Blockly.ConnectionType.INPUT_VALUE + stationaryNode.type === Blockly.ConnectionType.INPUT_VALUE ) { const sourceBlock = stationaryNode.getSourceBlock(); if (!sourceBlock) return null; - return this.findInsertStartPoint( - Blockly.ASTNode.createBlockNode(sourceBlock), - movingBlock, - ); + return this.findInsertStartPoint(sourceBlock, movingBlock); } // Connect the moving block to the stationary connection using // the most plausible connection on the moving block. - return stationaryAsConnection; - } else if (stationaryType === Blockly.ASTNode.types.WORKSPACE) { + return stationaryNode; + } else if (stationaryNode instanceof Blockly.WorkspaceSvg) { return null; - } else if (stationaryType === Blockly.ASTNode.types.BLOCK) { - const stationaryBlock = stationaryLoc as Blockly.BlockSvg; - + } else if (stationaryNode instanceof Blockly.BlockSvg) { // 1. Connect blocks to first compatible input const inputType = movingHasOutput ? Blockly.inputs.inputTypes.VALUE : Blockly.inputs.inputTypes.STATEMENT; - const compatibleInputs = stationaryBlock.inputList.filter( + const compatibleInputs = stationaryNode.inputList.filter( (input) => input.type === inputType, ); const input = compatibleInputs.length > 0 ? compatibleInputs[0] : null; @@ -540,35 +485,26 @@ export class Navigation { } // 2. Connect statement blocks to next connection. - if (stationaryBlock.nextConnection && !movingHasOutput) { - return stationaryBlock.nextConnection; + if (stationaryNode.nextConnection && !movingHasOutput) { + return stationaryNode.nextConnection; } // 3. Output connection. This will wrap around or displace. - if (stationaryBlock.outputConnection) { + if (stationaryNode.outputConnection) { // Try to wrap. - const target = stationaryBlock.outputConnection.targetConnection; + const target = stationaryNode.outputConnection.targetConnection; if (movingHasOutput && target) { - const sourceNode = Blockly.ASTNode.createConnectionNode(target); - if (sourceNode) { - return this.findInsertStartPoint(sourceNode, movingBlock); - } - } else if ( - !movingHasOutput && - stationaryNode.getType() === Blockly.ASTNode.types.BLOCK - ) { + return this.findInsertStartPoint(target, movingBlock); + } else if (!movingHasOutput) { // Move to parent if we're trying to insert a statement block. - const parent = stationaryNode.getSourceBlock()?.getParent(); + const parent = stationaryNode.getParent(); if (!parent) return null; - return this.findInsertStartPoint( - Blockly.ASTNode.createBlockNode(parent), - movingBlock, - ); + return this.findInsertStartPoint(parent, movingBlock); } - return stationaryBlock.outputConnection; + return stationaryNode.outputConnection; } } - this.warn(`Unexpected case in findInsertStartPoint ${stationaryType}.`); + this.warn(`Unexpected case in findInsertStartPoint ${stationaryNode}.`); return null; } @@ -581,7 +517,7 @@ export class Navigation { * @returns True if the connection was successful, false otherwise. */ tryToConnectBlock( - stationaryNode: Blockly.ASTNode, + stationaryNode: Blockly.INavigable, movingBlock: Blockly.BlockSvg, ): boolean { const destConnection = this.findInsertStartPoint( @@ -592,37 +528,6 @@ export class Navigation { return this.insertBlock(movingBlock, destConnection); } - /** - * Disconnects the block from its parent and moves it to the position of the - * workspace node. - * - * @param block The block to be moved to the workspace. - * @param wsNode The workspace node holding the position - * the block will be moved to. - * @returns True if the block can be moved to the workspace, - * false otherwise. - */ - moveBlockToWorkspace( - block: Blockly.BlockSvg | null, - wsNode: Blockly.ASTNode, - ): boolean { - if (!block) { - return false; - } - if (block.isShadow()) { - this.warn('Cannot move a shadow block to the workspace.'); - return false; - } - if (block.getParent()) { - block.unplug(false); - } - const workspaceCoordinate = wsNode.getWsCoordinate(); - if (!workspaceCoordinate) return false; - - block.moveTo(workspaceCoordinate); - return true; - } - /** * Disconnects the child block from its parent block. No-op if the two given * connections are unrelated. @@ -885,10 +790,6 @@ export class Navigation { workspace.keyboardAccessibilityMode ) { workspace.keyboardAccessibilityMode = false; - workspace.getCursor()?.hide(); - if (this.getFlyoutCursor(workspace)) { - this.getFlyoutCursor(workspace)?.hide(); - } } } diff --git a/test/webdriverio/test/test_setup.ts b/test/webdriverio/test/test_setup.ts index 7e108295..c244d7b8 100644 --- a/test/webdriverio/test/test_setup.ts +++ b/test/webdriverio/test/test_setup.ts @@ -181,9 +181,7 @@ export async function setCurrentCursorNodeById( const workspaceSvg = Blockly.getMainWorkspace() as Blockly.WorkspaceSvg; const rootBlock = workspaceSvg.getBlockById(blockId); if (rootBlock) { - workspaceSvg - .getCursor() - ?.setCurNode(Blockly.ASTNode.createBlockNode(rootBlock)); + workspaceSvg.getCursor()?.setCurNode(rootBlock); } }, blockId); } @@ -199,7 +197,7 @@ export async function getCurrentCursorNodeId( ): Promise { return await browser.execute(() => { const workspaceSvg = Blockly.getMainWorkspace() as Blockly.WorkspaceSvg; - return workspaceSvg.getCursor()?.getCurNode()?.getSourceBlock()?.id; + return workspaceSvg.getCursor()?.getSourceBlock()?.id; }); } @@ -214,7 +212,28 @@ export async function getCurrentCursorNodeType( ): Promise { return await browser.execute(() => { const workspaceSvg = Blockly.getMainWorkspace() as Blockly.WorkspaceSvg; - return workspaceSvg.getCursor()?.getCurNode()?.getType(); + const node = workspaceSvg.getCursor()?.getCurNode(); + if (node instanceof Blockly.WorkspaceSvg) { + return 'workspace'; + } else if (node instanceof Blockly.BlockSvg) { + return 'block'; + } else if (node instanceof Blockly.Field) { + return 'field'; + } else if (node instanceof Blockly.FlyoutButton) { + return 'button'; + } else if (node instanceof Blockly.RenderedConnection) { + if (node.getParentInput()) { + return 'input'; + } + + if (node.type === Blockly.ConnectionType.OUTPUT_VALUE) { + return 'output'; + } else if (node.type === Blockly.ConnectionType.NEXT_STATEMENT) { + return 'next'; + } else if (node.type === Blockly.ConnectionType.PREVIOUS_STATEMENT) { + return 'previous'; + } + } }); } @@ -229,10 +248,7 @@ export async function getCurrentCursorNodeFieldName( ): Promise { return await browser.execute(() => { const workspaceSvg = Blockly.getMainWorkspace() as Blockly.WorkspaceSvg; - const field = workspaceSvg - .getCursor() - ?.getCurNode() - ?.getLocation() as Blockly.Field; + const field = workspaceSvg.getCursor()?.getCurNode() as Blockly.Field; return field.name; }); }