Skip to content

Commit a5a18d3

Browse files
authored
refactor!: Move responsibility for block creation out of flyouts (#9610)
* refactor!: Move responsibility for block creation out of flyouts * chore: Clarify naming and documentation * fix: Make test less convoluted * refactor: Use serialization instead of zero-length drag to handle block clicks * fix: Fix undoing when dragging a block from the flyout * refactor: Make `getTargetBlock()` always return a value
1 parent e65ac7f commit a5a18d3

8 files changed

Lines changed: 96 additions & 290 deletions

File tree

packages/blockly/core/dragging/block_drag_strategy.ts

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import * as layers from '../layers.js';
2525
import * as registry from '../registry.js';
2626
import {finishQueuedRenders} from '../render_management.js';
2727
import type {RenderedConnection} from '../rendered_connection.js';
28+
import * as blocks from '../serialization/blocks.js';
2829
import {Coordinate} from '../utils.js';
2930
import * as dom from '../utils/dom.js';
31+
import * as svgMath from '../utils/svg_math.js';
3032
import type {WorkspaceSvg} from '../workspace_svg.js';
3133

3234
/** Represents a nearby valid connection. */
@@ -95,17 +97,73 @@ export class BlockDragStrategy implements IDragStrategy {
9597
this.block.isOwnMovable() &&
9698
!this.block.isDeadOrDying() &&
9799
!this.workspace.isReadOnly() &&
98-
// We never drag blocks in the flyout, only create new blocks that are
99-
// dragged.
100-
!this.block.isInFlyout
100+
(!this.block.isInFlyout ||
101+
(this.block.isEnabled() &&
102+
!this.block.workspace.targetWorkspace?.isReadOnly()))
101103
);
102104
}
103105

106+
/**
107+
* Positions a cloned block on its new workspace.
108+
*
109+
* @param oldBlock The flyout block that was cloned.
110+
* @param newBlock The new block to position.
111+
*/
112+
private positionNewBlock(oldBlock: BlockSvg, newBlock: BlockSvg) {
113+
const screenCoordinate = svgMath.wsToScreenCoordinates(
114+
oldBlock.workspace,
115+
oldBlock.getRelativeToSurfaceXY(),
116+
);
117+
const workspaceCoordinates = svgMath.screenToWsCoordinates(
118+
newBlock.workspace,
119+
screenCoordinate,
120+
);
121+
newBlock.moveTo(workspaceCoordinates);
122+
}
123+
124+
/**
125+
* Returns the block to use for the current drag operation. This may create
126+
* and return a newly instantiated block when e.g. dragging from a flyout.
127+
*/
128+
protected getTargetBlock() {
129+
if (this.block.isShadow()) {
130+
const parent = this.block.getParent();
131+
if (parent) {
132+
return parent;
133+
}
134+
} else if (this.block.isInFlyout && this.block.workspace.targetWorkspace) {
135+
const rootBlock = this.block.getRootBlock();
136+
137+
const json = blocks.save(rootBlock);
138+
if (json) {
139+
const newBlock = blocks.appendInternal(
140+
json,
141+
this.block.workspace.targetWorkspace,
142+
{
143+
recordUndo: true,
144+
},
145+
) as BlockSvg;
146+
eventUtils.setRecordUndo(false);
147+
this.positionNewBlock(this.block, newBlock);
148+
eventUtils.setRecordUndo(true);
149+
150+
return newBlock;
151+
}
152+
}
153+
154+
return this.block;
155+
}
156+
104157
/**
105158
* Handles any setup for starting the drag, including disconnecting the block
106159
* from any parent blocks.
107160
*/
108161
startDrag(e?: PointerEvent | KeyboardEvent) {
162+
const alternateTarget = this.getTargetBlock();
163+
if (alternateTarget !== this.block) {
164+
return alternateTarget.startDrag(e);
165+
}
166+
109167
this.dragging = true;
110168
this.fireDragStartEvent();
111169

packages/blockly/core/dragging/dragger.ts

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,13 @@ import type {IDragger} from '../interfaces/i_dragger.js';
1616
import {isFocusableNode} from '../interfaces/i_focusable_node.js';
1717
import * as registry from '../registry.js';
1818
import {Coordinate} from '../utils/coordinate.js';
19-
import type {WorkspaceSvg} from '../workspace_svg.js';
2019

2120
export class Dragger implements IDragger {
2221
protected startLoc: Coordinate;
2322

2423
protected dragTarget: IDragTarget | null = null;
2524

26-
constructor(
27-
protected draggable: IDraggable,
28-
protected workspace: WorkspaceSvg,
29-
) {
25+
constructor(protected draggable: IDraggable) {
3026
this.startLoc = draggable.getRelativeToSurfaceXY();
3127
}
3228

@@ -65,7 +61,7 @@ export class Dragger implements IDragger {
6561

6662
/** Updates the drag target under the pointer (if there is one). */
6763
protected updateDragTarget(coordinate: Coordinate) {
68-
const newDragTarget = this.workspace.getDragTarget(coordinate);
64+
const newDragTarget = this.draggable.workspace.getDragTarget(coordinate);
6965
if (this.dragTarget !== newDragTarget) {
7066
this.dragTarget?.onDragExit(this.draggable);
7167
newDragTarget?.onDragEnter(this.draggable);
@@ -95,10 +91,10 @@ export class Dragger implements IDragger {
9591
coordinate: Coordinate,
9692
rootDraggable: IDraggable & IDeletable,
9793
) {
98-
const dragTarget = this.workspace.getDragTarget(coordinate);
94+
const dragTarget = this.draggable.workspace.getDragTarget(coordinate);
9995
if (!dragTarget) return false;
10096

101-
const componentManager = this.workspace.getComponentManager();
97+
const componentManager = this.draggable.workspace.getComponentManager();
10298
const isDeleteArea = componentManager.hasCapability(
10399
dragTarget.id,
104100
ComponentManager.Capability.DELETE_AREA,
@@ -111,7 +107,7 @@ export class Dragger implements IDragger {
111107
/** Handles any drag cleanup. */
112108
onDragEnd(e?: PointerEvent | KeyboardEvent) {
113109
const origGroup = eventUtils.getGroup();
114-
const dragTarget = this.workspace.getDragTarget(
110+
const dragTarget = this.draggable.workspace.getDragTarget(
115111
this.draggable.getRelativeToSurfaceXY(),
116112
);
117113

@@ -175,21 +171,21 @@ export class Dragger implements IDragger {
175171
coordinate: Coordinate,
176172
rootDraggable: IDraggable,
177173
) {
178-
const dragTarget = this.workspace.getDragTarget(coordinate);
174+
const dragTarget = this.draggable.workspace.getDragTarget(coordinate);
179175
if (!dragTarget) return false;
180176
return dragTarget.shouldPreventMove(rootDraggable);
181177
}
182178

183179
protected pixelsToWorkspaceUnits(pixelCoord: Coordinate): Coordinate {
184180
const result = new Coordinate(
185-
pixelCoord.x / this.workspace.scale,
186-
pixelCoord.y / this.workspace.scale,
181+
pixelCoord.x / this.draggable.workspace.scale,
182+
pixelCoord.y / this.draggable.workspace.scale,
187183
);
188-
if (this.workspace.isMutator) {
184+
if (this.draggable.workspace.isMutator) {
189185
// If we're in a mutator, its scale is always 1, purely because of some
190186
// oddities in our rendering optimizations. The actual scale is the same
191187
// as the scale on the parent workspace. Fix that for dragging.
192-
const mainScale = this.workspace.options.parentWorkspace!.scale;
188+
const mainScale = this.draggable.workspace.options.parentWorkspace!.scale;
193189
result.scale(1 / mainScale);
194190
}
195191
return result;

packages/blockly/core/flyout_base.ts

Lines changed: 0 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
*/
1212
// Former goog.module ID: Blockly.Flyout
1313

14-
import {BlockSvg} from './block_svg.js';
1514
import * as browserEvents from './browser_events.js';
1615
import {ComponentManager} from './component_manager.js';
1716
import {DeleteArea} from './delete_area.js';
@@ -32,8 +31,6 @@ import * as registry from './registry.js';
3231
import * as renderManagement from './render_management.js';
3332
import {ScrollbarPair} from './scrollbar_pair.js';
3433
import {SEPARATOR_TYPE} from './separator_flyout_inflater.js';
35-
import * as blocks from './serialization/blocks.js';
36-
import {Coordinate} from './utils/coordinate.js';
3734
import * as dom from './utils/dom.js';
3835
import * as idGenerator from './utils/idgenerator.js';
3936
import {Svg} from './utils/svg.js';
@@ -52,17 +49,6 @@ export abstract class Flyout
5249
*/
5350
abstract position(): void;
5451

55-
/**
56-
* Determine if a drag delta is toward the workspace, based on the position
57-
* and orientation of the flyout. This is used in determineDragIntention_ to
58-
* determine if a new block should be created or if the flyout should scroll.
59-
*
60-
* @param currentDragDeltaXY How far the pointer has
61-
* moved from the position at mouse down, in pixel units.
62-
* @returns True if the drag is toward the workspace.
63-
*/
64-
abstract isDragTowardWorkspace(currentDragDeltaXY: Coordinate): boolean;
65-
6652
/**
6753
* Sets the translation of the flyout to match the scrollbars.
6854
*
@@ -191,29 +177,6 @@ export abstract class Flyout
191177
* Height of flyout.
192178
*/
193179
protected height_ = 0;
194-
// clang-format off
195-
/**
196-
* Range of a drag angle from a flyout considered "dragging toward
197-
* workspace". Drags that are within the bounds of this many degrees from
198-
* the orthogonal line to the flyout edge are considered to be "drags toward
199-
* the workspace".
200-
*
201-
* @example
202-
*
203-
* ```
204-
* Flyout Edge Workspace
205-
* [block] / <-within this angle, drags "toward workspace" |
206-
* [block] ---- orthogonal to flyout boundary ---- |
207-
* [block] \ |
208-
* ```
209-
*
210-
* The angle is given in degrees from the orthogonal.
211-
*
212-
* This is used to know when to create a new block and when to scroll the
213-
* flyout. Setting it to 360 means that all drags create a new block.
214-
*/
215-
// clang-format on
216-
protected dragAngleRange_ = 70;
217180

218181
/**
219182
* The path around the background of the flyout, which will be filled with a
@@ -790,47 +753,6 @@ export abstract class Flyout
790753
}
791754
}
792755

793-
/**
794-
* Does this flyout allow you to create a new instance of the given block?
795-
* Used for deciding if a block can be "dragged out of" the flyout.
796-
*
797-
* @param block The block to copy from the flyout.
798-
* @returns True if you can create a new instance of the block, false
799-
* otherwise.
800-
* @internal
801-
*/
802-
isBlockCreatable(block: BlockSvg): boolean {
803-
return block.isEnabled() && !this.getTargetWorkspace().isReadOnly();
804-
}
805-
806-
/**
807-
* Create a copy of this block on the workspace.
808-
*
809-
* @param originalBlock The block to copy from the flyout.
810-
* @returns The newly created block.
811-
* @throws {Error} if something went wrong with deserialization.
812-
* @internal
813-
*/
814-
createBlock(originalBlock: BlockSvg): BlockSvg {
815-
const targetWorkspace = this.targetWorkspace;
816-
const svgRootOld = originalBlock.getSvgRoot();
817-
if (!svgRootOld) {
818-
throw Error('oldBlock is not rendered');
819-
}
820-
821-
// Clone the block.
822-
const json = this.serializeBlock(originalBlock);
823-
// Normally this resizes leading to weird jumps. Save it for terminateDrag.
824-
targetWorkspace.setResizesEnabled(false);
825-
const block = blocks.appendInternal(json, targetWorkspace, {
826-
recordUndo: true,
827-
}) as BlockSvg;
828-
829-
this.positionNewBlock(originalBlock, block);
830-
targetWorkspace.hideChaff();
831-
return block;
832-
}
833-
834756
/**
835757
* Reflow flyout contents.
836758
*/
@@ -851,59 +773,6 @@ export abstract class Flyout
851773
: false;
852774
}
853775

854-
/**
855-
* Serialize a block to JSON.
856-
*
857-
* @param block The block to serialize.
858-
* @returns A serialized representation of the block.
859-
*/
860-
protected serializeBlock(block: BlockSvg): blocks.State {
861-
return blocks.save(block) as blocks.State;
862-
}
863-
864-
/**
865-
* Positions a block on the target workspace.
866-
*
867-
* @param oldBlock The flyout block being copied.
868-
* @param block The block to posiiton.
869-
*/
870-
private positionNewBlock(oldBlock: BlockSvg, block: BlockSvg) {
871-
const targetWorkspace = this.targetWorkspace;
872-
873-
// The offset in pixels between the main workspace's origin and the upper
874-
// left corner of the injection div.
875-
const mainOffsetPixels = targetWorkspace.getOriginOffsetInPixels();
876-
877-
// The offset in pixels between the flyout workspace's origin and the upper
878-
// left corner of the injection div.
879-
const flyoutOffsetPixels = this.workspace_.getOriginOffsetInPixels();
880-
881-
// The position of the old block in flyout workspace coordinates.
882-
const oldBlockPos = oldBlock.getRelativeToSurfaceXY();
883-
// The position of the old block in pixels relative to the flyout
884-
// workspace's origin.
885-
oldBlockPos.scale(this.workspace_.scale);
886-
887-
// The position of the old block in pixels relative to the upper left corner
888-
// of the injection div.
889-
const oldBlockOffsetPixels = Coordinate.sum(
890-
flyoutOffsetPixels,
891-
oldBlockPos,
892-
);
893-
894-
// The position of the old block in pixels relative to the origin of the
895-
// main workspace.
896-
const finalOffset = Coordinate.difference(
897-
oldBlockOffsetPixels,
898-
mainOffsetPixels,
899-
);
900-
// The position of the old block in main workspace coordinates.
901-
finalOffset.scale(1 / targetWorkspace.scale);
902-
903-
// No 'reason' provided since events are disabled.
904-
block.moveTo(new Coordinate(finalOffset.x, finalOffset.y));
905-
}
906-
907776
/**
908777
* Returns the inflater responsible for constructing items of the given type.
909778
*

packages/blockly/core/flyout_horizontal.ts

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import type {FlyoutItem} from './flyout_item.js';
1818
import type {Options} from './options.js';
1919
import * as registry from './registry.js';
2020
import {Scrollbar} from './scrollbar.js';
21-
import type {Coordinate} from './utils/coordinate.js';
2221
import {Rect} from './utils/rect.js';
2322
import * as toolbox from './utils/toolbox.js';
2423
import * as WidgetDiv from './widgetdiv.js';
@@ -271,32 +270,6 @@ export class HorizontalFlyout extends Flyout {
271270
}
272271
}
273272

274-
/**
275-
* Determine if a drag delta is toward the workspace, based on the position
276-
* and orientation of the flyout. This is used in determineDragIntention_ to
277-
* determine if a new block should be created or if the flyout should scroll.
278-
*
279-
* @param currentDragDeltaXY How far the pointer has moved from the position
280-
* at mouse down, in pixel units.
281-
* @returns True if the drag is toward the workspace.
282-
*/
283-
override isDragTowardWorkspace(currentDragDeltaXY: Coordinate): boolean {
284-
const dx = currentDragDeltaXY.x;
285-
const dy = currentDragDeltaXY.y;
286-
// Direction goes from -180 to 180, with 0 toward the right and 90 on top.
287-
const dragDirection = (Math.atan2(dy, dx) / Math.PI) * 180;
288-
289-
const range = this.dragAngleRange_;
290-
// Check for up or down dragging.
291-
if (
292-
(dragDirection < 90 + range && dragDirection > 90 - range) ||
293-
(dragDirection > -90 - range && dragDirection < -90 + range)
294-
) {
295-
return true;
296-
}
297-
return false;
298-
}
299-
300273
/**
301274
* Returns the bounding rectangle of the drag target area in pixel units
302275
* relative to viewport.

0 commit comments

Comments
 (0)