44 * SPDX-License-Identifier: Apache-2.0
55 */
66
7- import type { BlockSvg , IDragger , IDragStrategy , Gesture } from 'blockly' ;
7+ import type {
8+ BlockSvg ,
9+ IDragger ,
10+ IDragStrategy ,
11+ Gesture ,
12+ RenderedConnection ,
13+ } from 'blockly' ;
814import {
915 ASTNode ,
10- common ,
1116 Connection ,
17+ dragging ,
1218 registry ,
1319 utils ,
1420 WorkspaceSvg ,
@@ -63,11 +69,10 @@ export class Mover {
6369 * currently has focus on the given workspace.
6470 *
6571 * @param workspace The workspace to move on.
72+ * @param block The block to try to drag.
6673 * @returns True iff we can begin a move.
6774 */
68- canMove ( workspace : WorkspaceSvg ) {
69- const block = this . getCurrentBlock ( workspace ) ;
70-
75+ canMove ( workspace : WorkspaceSvg , block : BlockSvg ) {
7176 return ! ! (
7277 this . navigation . getState ( workspace ) === Constants . STATE . WORKSPACE &&
7378 this . navigation . canCurrentlyEdit ( workspace ) &&
@@ -96,19 +101,18 @@ export class Mover {
96101 * Should only be called if canMove has returned true.
97102 *
98103 * @param workspace The workspace we might be moving on.
104+ * @param block The block to start dragging.
105+ * @param insertStartPoint Where to insert the block, or null if the block
106+ * already existed.
99107 * @returns True iff a move has successfully begun.
100108 */
101- startMove ( workspace : WorkspaceSvg ) {
102- const cursor = workspace ?. getCursor ( ) ;
103- const block = this . getCurrentBlock ( workspace ) ;
104- if ( ! cursor || ! block ) throw new Error ( 'precondition failure' ) ;
105-
106- // Select and focus block.
107- common . setSelected ( block ) ;
108- cursor . setCurNode ( ASTNode . createBlockNode ( block ) ) ;
109-
109+ startMove (
110+ workspace : WorkspaceSvg ,
111+ block : BlockSvg ,
112+ insertStartPoint : RenderedConnection | null ,
113+ ) {
110114 this . patchWorkspace ( workspace ) ;
111- this . patchDragStrategy ( block ) ;
115+ this . patchDragStrategy ( block , insertStartPoint ) ;
112116 // Begin dragging block.
113117 const DraggerClass = registry . getClassFromOptions (
114118 registry . Type . BLOCK_DRAGGER ,
@@ -167,19 +171,34 @@ export class Mover {
167171 const info = this . moves . get ( workspace ) ;
168172 if ( ! info ) throw new Error ( 'no move info for workspace' ) ;
169173
170- // Monkey patch dragger to trigger call to draggable.revertDrag.
171- // eslint-disable-next-line @typescript-eslint/no-explicit-any
172- ( info . dragger as any ) . shouldReturnToStart = ( ) => true ;
173- const blockSvg = info . block ;
174+ const dragStrategy = info . block . getDragStrategy ( ) as KeyboardDragStrategy ;
175+ this . patchDragger (
176+ info . dragger as dragging . Dragger ,
177+ dragStrategy . isNewBlock ,
178+ ) ;
174179
175180 // Explicitly call `hidePreview` because it is not called in revertDrag.
176- // @ts -expect-error Access to private property dragStrategy.
177- blockSvg . dragStrategy . connectionPreviewer . hidePreview ( ) ;
181+ // @ts -expect-error Access to private property connectionPreviewer.
182+ dragStrategy . connectionPreviewer . hidePreview ( ) ;
183+
184+ // Save the position so we can put the cursor in a reasonable spot.
185+ // @ts -expect-error Access to private property connectionCandidate.
186+ const target = dragStrategy . connectionCandidate ?. neighbour ;
187+
188+ // Prevent the stragegy connecting the block so we just delete one block.
189+ // @ts -expect-error Access to private property connectionCandidate.
190+ dragStrategy . connectionCandidate = null ;
191+
178192 info . dragger . onDragEnd (
179193 info . fakePointerEvent ( 'pointerup' ) ,
180- new utils . Coordinate ( 0 , 0 ) ,
194+ info . startLocation ,
181195 ) ;
182196
197+ if ( dragStrategy . isNewBlock && target ) {
198+ const newNode = ASTNode . createConnectionNode ( target ) ;
199+ if ( newNode ) workspace . getCursor ( ) ?. setCurNode ( newNode ) ;
200+ }
201+
183202 this . unpatchWorkspace ( workspace ) ;
184203 this . unpatchDragStrategy ( info . block ) ;
185204 this . moves . delete ( workspace ) ;
@@ -233,32 +252,6 @@ export class Mover {
233252 return true ;
234253 }
235254
236- /**
237- * Get the source block for the cursor location, or undefined if no
238- * source block can be found.
239- * If the cursor is on a shadow block, walks up the tree until it finds
240- * a non-shadow block to drag.
241- *
242- * @param workspace The workspace to inspect for a cursor.
243- * @returns The source block, or undefined if no appropriate block
244- * could be found.
245- */
246- protected getCurrentBlock ( workspace : WorkspaceSvg ) : BlockSvg | undefined {
247- const curNode = workspace ?. getCursor ( ) ?. getCurNode ( ) ;
248- let block = curNode ?. getSourceBlock ( ) ;
249- if ( ! block ) return undefined ;
250- while ( block && block . isShadow ( ) ) {
251- block = block . getParent ( ) ;
252- if ( ! block ) {
253- throw new Error (
254- 'Tried to drag a shadow block with no parent. ' +
255- 'Shadow blocks should always have parents.' ,
256- ) ;
257- }
258- }
259- return block as BlockSvg ;
260- }
261-
262255 /**
263256 * Monkeypatch over workspace functions to consider keyboard drags as
264257 * well as mouse/pointer drags.
@@ -297,15 +290,21 @@ export class Mover {
297290 ( workspace as any ) . getGesture = this . oldGetGesture ;
298291 }
299292 }
293+
300294 /**
301295 * Monkeypatch: replace the block's drag strategy and cache the old value.
302296 *
303297 * @param block The block to patch.
298+ * @param insertStartPoint Where to insert the block, or null if the block
299+ * already existed.
304300 */
305- private patchDragStrategy ( block : BlockSvg ) {
301+ private patchDragStrategy (
302+ block : BlockSvg ,
303+ insertStartPoint : RenderedConnection | null ,
304+ ) {
306305 // @ts -expect-error block.dragStrategy is private.
307306 this . oldDragStrategy = block . dragStrategy ;
308- block . setDragStrategy ( new KeyboardDragStrategy ( block ) ) ;
307+ block . setDragStrategy ( new KeyboardDragStrategy ( block , insertStartPoint ) ) ;
309308 }
310309
311310 /**
@@ -328,14 +327,34 @@ export class Mover {
328327 * the workspace's viewport.
329328 */
330329 private scrollCurrentBlockIntoView ( workspace : WorkspaceSvg , padding = 10 ) {
331- const blockToView = this . getCurrentBlock ( workspace ) ;
330+ const blockToView = this . moves . get ( workspace ) ?. block ;
332331 if ( blockToView ) {
333332 workspace . scrollBoundsIntoView (
334333 blockToView . getBoundingRectangleWithoutChildren ( ) ,
335334 padding ,
336335 ) ;
337336 }
338337 }
338+
339+ /**
340+ * Monkeypatch: override either wouldDeleteDraggable or shouldReturnToStart,
341+ * based on whether this was an insertion of a new block or a movement of
342+ * an existing block.
343+ *
344+ * @param dragger The dragger to patch.
345+ * @param isNewBlock Whether the moving block was created for this action.
346+ */
347+ private patchDragger ( dragger : dragging . Dragger , isNewBlock : boolean ) {
348+ if ( isNewBlock ) {
349+ // Monkey patch dragger to trigger delete.
350+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
351+ ( dragger as any ) . wouldDeleteDraggable = ( ) => true ;
352+ } else {
353+ // Monkey patch dragger to trigger call to draggable.revertDrag.
354+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
355+ ( dragger as any ) . shouldReturnToStart = ( ) => true ;
356+ }
357+ }
339358}
340359
341360/**
0 commit comments