@@ -10,17 +10,25 @@ import {
1010 Connection ,
1111 ContextMenuRegistry ,
1212 ShortcutRegistry ,
13+ WorkspaceSvg ,
1314 common ,
15+ registry ,
1416 utils ,
1517} from 'blockly' ;
16- import type { Block , BlockSvg , WorkspaceSvg } from 'blockly' ;
18+ import type { Block , BlockSvg , IDragger } from 'blockly' ;
1719import { Navigation } from '../navigation' ;
1820
1921const KeyCodes = utils . KeyCodes ;
2022const createSerializedKey = ShortcutRegistry . registry . createSerializedKey . bind (
2123 ShortcutRegistry . registry ,
2224) ;
2325
26+ /**
27+ * The distance to move an item, in workspace coordinates, when
28+ * making an unconstrained move.
29+ */
30+ const UNCONSTRAINED_MOVE_DISTANCE = 20 ;
31+
2432/**
2533 * Actions for moving blocks with keyboard shortcuts.
2634 */
@@ -221,11 +229,19 @@ export class Mover {
221229 // Select and focus block.
222230 common . setSelected ( block ) ;
223231 cursor . setCurNode ( ASTNode . createBlockNode ( block ) ) ;
224-
225- // Additional implementation goes here.
226- console . log ( 'startMove' ) ;
227-
228- this . moves . set ( workspace , new MoveInfo ( block ) ) ;
232+ // Begin dragging block.
233+ const DraggerClass = registry . getClassFromOptions (
234+ registry . Type . BLOCK_DRAGGER ,
235+ workspace . options ,
236+ true ,
237+ ) ;
238+ if ( ! DraggerClass ) throw new Error ( 'no Dragger registered' ) ;
239+ const dragger = new DraggerClass ( block , workspace ) ;
240+ // Record that a move is in progress and start dragging.
241+ const info = new MoveInfo ( block , dragger ) ;
242+ this . moves . set ( workspace , info ) ;
243+ // Begin drag.
244+ dragger . onDragStart ( info . fakePointerEvent ( 'pointerdown' ) ) ;
229245 return true ;
230246 }
231247
@@ -241,8 +257,10 @@ export class Mover {
241257 const info = this . moves . get ( workspace ) ;
242258 if ( ! info ) throw new Error ( 'no move info for workspace' ) ;
243259
244- // Additional implementation goes here.
245- console . log ( 'finishMove' ) ;
260+ info . dragger . onDragEnd (
261+ info . fakePointerEvent ( 'pointerup' ) ,
262+ new utils . Coordinate ( 0 , 0 ) ,
263+ ) ;
246264
247265 this . moves . delete ( workspace ) ;
248266 return true ;
@@ -260,8 +278,18 @@ export class Mover {
260278 const info = this . moves . get ( workspace ) ;
261279 if ( ! info ) throw new Error ( 'no move info for workspace' ) ;
262280
263- // Additional implementation goes here.
264- console . log ( 'abortMove' ) ;
281+ // Monkey patch dragger to trigger call to draggable.revertDrag.
282+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
283+ ( info . dragger as any ) . shouldReturnToStart = ( ) => true ;
284+ const blockSvg = info . block as BlockSvg ;
285+
286+ // Explicitly call `hidePreview` because it is not called in revertDrag.
287+ // @ts -expect-error Access to private property dragStrategy.
288+ blockSvg . dragStrategy . connectionPreviewer . hidePreview ( ) ;
289+ info . dragger . onDragEnd (
290+ info . fakePointerEvent ( 'pointerup' ) ,
291+ new utils . Coordinate ( 0 , 0 ) ,
292+ ) ;
265293
266294 this . moves . delete ( workspace ) ;
267295 return true ;
@@ -278,8 +306,11 @@ export class Mover {
278306 workspace : WorkspaceSvg ,
279307 /* ... */
280308 ) {
281- console . log ( 'moveConstrained' ) ;
282309 // Not yet implemented. Absorb keystroke to avoid moving cursor.
310+ alert ( `Constrained movement not implemented.
311+
312+ Use ctrl+arrow or alt+arrow (option+arrow on macOS) for unconstrained move.
313+ Use enter to complete the move, or escape to abort.` ) ;
283314 return true ;
284315 }
285316
@@ -297,8 +328,16 @@ export class Mover {
297328 xDirection : number ,
298329 yDirection : number ,
299330 ) : boolean {
300- console . log ( 'moveUnconstrained' ) ;
301- // Not yet implemented. Absorb keystroke to avoid moving cursor.
331+ if ( ! workspace ) return false ;
332+ const info = this . moves . get ( workspace ) ;
333+ if ( ! info ) throw new Error ( 'no move info for workspace' ) ;
334+
335+ info . totalDelta . x +=
336+ xDirection * UNCONSTRAINED_MOVE_DISTANCE * workspace . scale ;
337+ info . totalDelta . y +=
338+ yDirection * UNCONSTRAINED_MOVE_DISTANCE * workspace . scale ;
339+
340+ info . dragger . onDrag ( info . fakePointerEvent ( 'pointermove' ) , info . totalDelta ) ;
302341 return true ;
303342 }
304343
@@ -322,13 +361,42 @@ export class Mover {
322361 * Workspace.
323362 */
324363export class MoveInfo {
364+ /** Total distance moved, in screen pixels */
365+ totalDelta = new utils . Coordinate ( 0 , 0 ) ;
325366 readonly parentNext : Connection | null ;
326367 readonly parentInput : Connection | null ;
327368 readonly startLocation : utils . Coordinate ;
328369
329- constructor ( readonly block : Block ) {
370+ constructor (
371+ readonly block : Block ,
372+ readonly dragger : IDragger ,
373+ ) {
330374 this . parentNext = block . previousConnection ?. targetConnection ?? null ;
331375 this . parentInput = block . outputConnection ?. targetConnection ?? null ;
332376 this . startLocation = block . getRelativeToSurfaceXY ( ) ;
333377 }
378+
379+ /**
380+ * Create a fake pointer event for dragging.
381+ *
382+ * @param type Which type of pointer event to create.
383+ * @returns A synthetic PointerEvent that can be consumed by Blockly's
384+ * dragging code.
385+ */
386+ fakePointerEvent ( type : string ) : PointerEvent {
387+ const workspace = this . block . workspace ;
388+ if ( ! ( workspace instanceof WorkspaceSvg ) ) throw new TypeError ( ) ;
389+
390+ const blockCoords = utils . svgMath . wsToScreenCoordinates (
391+ workspace ,
392+ new utils . Coordinate (
393+ this . startLocation . x + this . totalDelta . x ,
394+ this . startLocation . y + this . totalDelta . y ,
395+ ) ,
396+ ) ;
397+ return new PointerEvent ( type , {
398+ clientX : blockCoords . x ,
399+ clientY : blockCoords . y ,
400+ } ) ;
401+ }
334402}
0 commit comments