44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7- import {
8- ASTNode ,
9- Events ,
10- ShortcutRegistry ,
11- utils as BlocklyUtils ,
12- } from 'blockly/core' ;
7+ import { ASTNode , utils as BlocklyUtils , ShortcutRegistry } from 'blockly/core' ;
138
149import type {
1510 Block ,
@@ -20,13 +15,9 @@ import type {
2015} from 'blockly/core' ;
2116
2217import * as Constants from '../constants' ;
18+ import { showHelpHint } from '../hints' ;
2319import type { Navigation } from '../navigation' ;
24- import { Mover } from './mover' ;
25- import {
26- showConstrainedMovementHint ,
27- showHelpHint ,
28- showUnconstrainedMoveHint ,
29- } from '../hints' ;
20+ import { Inserter } from './inserter' ;
3021
3122const KeyCodes = BlocklyUtils . KeyCodes ;
3223
@@ -35,8 +26,8 @@ const KeyCodes = BlocklyUtils.KeyCodes;
3526 */
3627export class EnterAction {
3728 constructor (
38- private mover : Mover ,
3929 private navigation : Navigation ,
30+ private inserter : Inserter ,
4031 ) { }
4132
4233 /**
@@ -117,132 +108,13 @@ export class EnterAction {
117108 }
118109
119110 /**
120- * Inserts a block from the flyout.
121- * Tries to find a connection on the block to connect to the marked
122- * location. If no connection has been marked, or there is not a compatible
123- * connection then the block is placed on the workspace.
124- * Trigger a toast per session if possible.
111+ * Inserts a block from the flyout using the shared insertion logic.
125112 *
126113 * @param workspace The main workspace. The workspace
127114 * the block will be placed on.
128115 */
129116 private insertFromFlyout ( workspace : WorkspaceSvg ) {
130- workspace . setResizesEnabled ( false ) ;
131- // Create a new event group or append to the existing group.
132- const existingGroup = Events . getGroup ( ) ;
133- if ( ! existingGroup ) {
134- Events . setGroup ( true ) ;
135- }
136-
137- const stationaryNode = this . navigation . getStationaryNode ( workspace ) ;
138- const newBlock = this . createNewBlock ( workspace ) ;
139- if ( ! newBlock ) return ;
140- const insertStartPoint = stationaryNode
141- ? this . navigation . findInsertStartPoint ( stationaryNode , newBlock )
142- : null ;
143- if ( workspace . getTopBlocks ( ) . includes ( newBlock ) ) {
144- this . positionNewTopLevelBlock ( workspace , newBlock ) ;
145- }
146-
147- workspace . setResizesEnabled ( true ) ;
148-
149- this . navigation . focusWorkspace ( workspace ) ;
150- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
151- workspace . getCursor ( ) ?. setCurNode ( ASTNode . createBlockNode ( newBlock ) ! ) ;
152- this . mover . startMove ( workspace , newBlock , insertStartPoint ) ;
153-
154- const isStartBlock =
155- ! newBlock . outputConnection &&
156- ! newBlock . nextConnection &&
157- ! newBlock . previousConnection ;
158- if ( isStartBlock ) {
159- showUnconstrainedMoveHint ( workspace , false ) ;
160- } else {
161- showConstrainedMovementHint ( workspace ) ;
162- }
163- }
164-
165- /**
166- * Position a new top-level block to avoid overlap at the top left.
167- *
168- * Similar to `WorkspaceSvg.cleanUp()` but does not constrain itself to not
169- * affecting code ordering in order to use horizontal space.
170- *
171- * @param workspace The workspace.
172- * @param newBlock The top-level block to move to free space.
173- */
174- private positionNewTopLevelBlock (
175- workspace : WorkspaceSvg ,
176- newBlock : BlockSvg ,
177- ) {
178- const initialY = 10 ;
179- const initialX = 10 ;
180- const xSpacing = 80 ;
181-
182- const filteredTopBlocks = workspace
183- . getTopBlocks ( true )
184- . filter ( ( block ) => block . id !== newBlock . id ) ;
185- const allBlockBounds = filteredTopBlocks . map ( ( block ) =>
186- block . getBoundingRectangle ( ) ,
187- ) ;
188-
189- const toolboxWidth = workspace . getToolbox ( ) ?. getWidth ( ) ;
190- const workspaceWidth =
191- workspace . getParentSvg ( ) . clientWidth - ( toolboxWidth ?? 0 ) ;
192- const workspaceHeight = workspace . getParentSvg ( ) . clientHeight ;
193- const { height : newBlockHeight , width : newBlockWidth } =
194- newBlock . getHeightWidth ( ) ;
195-
196- const getNextIntersectingBlock = function (
197- newBlockRect : BlocklyUtils . Rect ,
198- ) : BlocklyUtils . Rect | null {
199- for ( const rect of allBlockBounds ) {
200- if ( newBlockRect . intersects ( rect ) ) {
201- return rect ;
202- }
203- }
204- return null ;
205- } ;
206-
207- let cursorY = initialY ;
208- let cursorX = initialX ;
209- const minBlockHeight = workspace
210- . getRenderer ( )
211- . getConstants ( ) . MIN_BLOCK_HEIGHT ;
212- // Make the initial movement of shifting the block to its best possible position.
213- let boundingRect = newBlock . getBoundingRectangle ( ) ;
214- newBlock . moveBy ( cursorX - boundingRect . left , cursorY - boundingRect . top , [
215- 'cleanup' ,
216- ] ) ;
217- newBlock . snapToGrid ( ) ;
218-
219- boundingRect = newBlock . getBoundingRectangle ( ) ;
220- let conflictingRect = getNextIntersectingBlock ( boundingRect ) ;
221- while ( conflictingRect != null ) {
222- const newCursorX =
223- conflictingRect . left + conflictingRect . getWidth ( ) + xSpacing ;
224- const newCursorY =
225- conflictingRect . top + conflictingRect . getHeight ( ) + minBlockHeight ;
226- if ( newCursorX + newBlockWidth <= workspaceWidth ) {
227- cursorX = newCursorX ;
228- } else if ( newCursorY + newBlockHeight <= workspaceHeight ) {
229- cursorY = newCursorY ;
230- cursorX = initialX ;
231- } else {
232- // Off screen, but new blocks will be selected which will scroll them
233- // into view.
234- cursorY = newCursorY ;
235- cursorX = initialX ;
236- }
237- newBlock . moveBy ( cursorX - boundingRect . left , cursorY - boundingRect . top , [
238- 'cleanup' ,
239- ] ) ;
240- newBlock . snapToGrid ( ) ;
241- boundingRect = newBlock . getBoundingRectangle ( ) ;
242- conflictingRect = getNextIntersectingBlock ( boundingRect ) ;
243- }
244-
245- newBlock . bringToFront ( ) ;
117+ this . inserter . insert ( workspace , ( ) => this . createNewBlock ( workspace ) ) ;
246118 }
247119
248120 /**
0 commit comments