fix: dragging and disposing of shadows (#8172)

* Revert "fix: dragging blocks by shadows to delete (#8138)"

This reverts commit 3fd749205f.

* fix: dragging shadows
This commit is contained in:
Beka Westberg
2024-05-28 17:28:16 +00:00
committed by GitHub
parent 933e210037
commit 6e4ba00be5
4 changed files with 73 additions and 33 deletions

View File

@@ -675,17 +675,6 @@ export class Block implements IASTNodeLocation {
return block; return block;
} }
/**
* Returns this block if it is a shadow block, or the first non-shadow parent.
*
* @internal
*/
getFirstNonShadowBlock(): this {
if (!this.isShadow()) return this;
// We can assert the parent is non-null because shadows must have parents.
return this.getParent()!.getFirstNonShadowBlock();
}
/** /**
* Find all the blocks that are directly nested inside this one. * Find all the blocks that are directly nested inside this one.
* Includes value and statement inputs, as well as any following statement. * Includes value and statement inputs, as well as any following statement.

View File

@@ -55,15 +55,24 @@ export class BlockDragStrategy implements IDragStrategy {
private dragging = false; private dragging = false;
/**
* If this is a shadow block, the offset between this block and the parent
* block, to add to the drag location. In workspace units.
*/
private dragOffset = new Coordinate(0, 0);
constructor(private block: BlockSvg) { constructor(private block: BlockSvg) {
this.workspace = block.workspace; this.workspace = block.workspace;
} }
/** Returns true if the block is currently movable. False otherwise. */ /** Returns true if the block is currently movable. False otherwise. */
isMovable(): boolean { isMovable(): boolean {
if (this.block.isShadow()) {
return this.block.getParent()?.isMovable() ?? false;
}
return ( return (
this.block.isOwnMovable() && this.block.isOwnMovable() &&
!this.block.isShadow() &&
!this.block.isDeadOrDying() && !this.block.isDeadOrDying() &&
!this.workspace.options.readOnly && !this.workspace.options.readOnly &&
// We never drag blocks in the flyout, only create new blocks that are // We never drag blocks in the flyout, only create new blocks that are
@@ -77,6 +86,11 @@ export class BlockDragStrategy implements IDragStrategy {
* from any parent blocks. * from any parent blocks.
*/ */
startDrag(e?: PointerEvent): void { startDrag(e?: PointerEvent): void {
if (this.block.isShadow()) {
this.startDraggingShadow(e);
return;
}
this.dragging = true; this.dragging = true;
if (!eventUtils.getGroup()) { if (!eventUtils.getGroup()) {
eventUtils.setGroup(true); eventUtils.setGroup(true);
@@ -106,6 +120,22 @@ export class BlockDragStrategy implements IDragStrategy {
this.workspace.getLayerManager()?.moveToDragLayer(this.block); this.workspace.getLayerManager()?.moveToDragLayer(this.block);
} }
/** Starts a drag on a shadow, recording the drag offset. */
private startDraggingShadow(e?: PointerEvent) {
const parent = this.block.getParent();
if (!parent) {
throw new Error(
'Tried to drag a shadow block with no parent. ' +
'Shadow blocks should always have parents.',
);
}
this.dragOffset = Coordinate.difference(
parent.getRelativeToSurfaceXY(),
this.block.getRelativeToSurfaceXY(),
);
parent.startDrag(e);
}
/** /**
* Whether or not we should disconnect the block when a drag is started. * Whether or not we should disconnect the block when a drag is started.
* *
@@ -174,6 +204,11 @@ export class BlockDragStrategy implements IDragStrategy {
/** Moves the block and updates any connection previews. */ /** Moves the block and updates any connection previews. */
drag(newLoc: Coordinate): void { drag(newLoc: Coordinate): void {
if (this.block.isShadow()) {
this.block.getParent()?.drag(Coordinate.sum(newLoc, this.dragOffset));
return;
}
this.block.moveDuringDrag(newLoc); this.block.moveDuringDrag(newLoc);
this.updateConnectionPreview( this.updateConnectionPreview(
this.block, this.block,
@@ -317,7 +352,12 @@ export class BlockDragStrategy implements IDragStrategy {
* Cleans up any state at the end of the drag. Applies any pending * Cleans up any state at the end of the drag. Applies any pending
* connections. * connections.
*/ */
endDrag(): void { endDrag(e?: PointerEvent): void {
if (this.block.isShadow()) {
this.block.getParent()?.endDrag(e);
return;
}
this.fireDragEndEvent(); this.fireDragEndEvent();
this.fireMoveEvent(); this.fireMoveEvent();
@@ -373,6 +413,11 @@ export class BlockDragStrategy implements IDragStrategy {
* including reconnecting connections. * including reconnecting connections.
*/ */
revertDrag(): void { revertDrag(): void {
if (this.block.isShadow()) {
this.block.getParent()?.revertDrag();
return;
}
this.startChildConn?.connect(this.block.nextConnection); this.startChildConn?.connect(this.block.nextConnection);
if (this.startParentConn) { if (this.startParentConn) {
switch (this.startParentConn.type) { switch (this.startParentConn.type) {

View File

@@ -42,13 +42,12 @@ export class Dragger implements IDragger {
*/ */
onDrag(e: PointerEvent, totalDelta: Coordinate) { onDrag(e: PointerEvent, totalDelta: Coordinate) {
this.moveDraggable(e, totalDelta); this.moveDraggable(e, totalDelta);
const root = this.getRoot(this.draggable);
// Must check `wouldDelete` before calling other hooks on drag targets // Must check `wouldDelete` before calling other hooks on drag targets
// since we have documented that we would do so. // since we have documented that we would do so.
if (isDeletable(this.draggable)) { if (isDeletable(root)) {
this.draggable.setDeleteStyle( root.setDeleteStyle(this.wouldDeleteDraggable(e, root));
this.wouldDeleteDraggable(e, this.draggable),
);
} }
this.updateDragTarget(e); this.updateDragTarget(e);
} }
@@ -56,11 +55,12 @@ export class Dragger implements IDragger {
/** Updates the drag target under the pointer (if there is one). */ /** Updates the drag target under the pointer (if there is one). */
protected updateDragTarget(e: PointerEvent) { protected updateDragTarget(e: PointerEvent) {
const newDragTarget = this.workspace.getDragTarget(e); const newDragTarget = this.workspace.getDragTarget(e);
const root = this.getRoot(this.draggable);
if (this.dragTarget !== newDragTarget) { if (this.dragTarget !== newDragTarget) {
this.dragTarget?.onDragExit(this.draggable); this.dragTarget?.onDragExit(root);
newDragTarget?.onDragEnter(this.draggable); newDragTarget?.onDragEnter(root);
} }
newDragTarget?.onDragOver(this.draggable); newDragTarget?.onDragOver(root);
this.dragTarget = newDragTarget; this.dragTarget = newDragTarget;
} }
@@ -80,7 +80,7 @@ export class Dragger implements IDragger {
*/ */
protected wouldDeleteDraggable( protected wouldDeleteDraggable(
e: PointerEvent, e: PointerEvent,
draggable: IDraggable & IDeletable, rootDraggable: IDraggable & IDeletable,
) { ) {
const dragTarget = this.workspace.getDragTarget(e); const dragTarget = this.workspace.getDragTarget(e);
if (!dragTarget) return false; if (!dragTarget) return false;
@@ -92,50 +92,56 @@ export class Dragger implements IDragger {
); );
if (!isDeleteArea) return false; if (!isDeleteArea) return false;
return (dragTarget as IDeleteArea).wouldDelete(draggable); return (dragTarget as IDeleteArea).wouldDelete(rootDraggable);
} }
/** Handles any drag cleanup. */ /** Handles any drag cleanup. */
onDragEnd(e: PointerEvent) { onDragEnd(e: PointerEvent) {
const origGroup = eventUtils.getGroup(); const origGroup = eventUtils.getGroup();
const dragTarget = this.workspace.getDragTarget(e); const dragTarget = this.workspace.getDragTarget(e);
const root = this.getRoot(this.draggable);
if (dragTarget) { if (dragTarget) {
this.dragTarget?.onDrop(this.draggable); this.dragTarget?.onDrop(root);
} }
if (this.shouldReturnToStart(e, this.draggable)) { if (this.shouldReturnToStart(e, root)) {
this.draggable.revertDrag(); this.draggable.revertDrag();
} }
const wouldDelete = const wouldDelete = isDeletable(root) && this.wouldDeleteDraggable(e, root);
isDeletable(this.draggable) &&
this.wouldDeleteDraggable(e, this.draggable);
// TODO(#8148): use a generalized API instead of an instanceof check. // TODO(#8148): use a generalized API instead of an instanceof check.
if (wouldDelete && this.draggable instanceof BlockSvg) { if (wouldDelete && this.draggable instanceof BlockSvg) {
blockAnimations.disposeUiEffect(this.draggable); blockAnimations.disposeUiEffect(this.draggable.getRootBlock());
} }
this.draggable.endDrag(e); this.draggable.endDrag(e);
if (wouldDelete && isDeletable(this.draggable)) { if (wouldDelete && isDeletable(root)) {
// We want to make sure the delete gets grouped with any possible // We want to make sure the delete gets grouped with any possible
// move event. // move event.
const newGroup = eventUtils.getGroup(); const newGroup = eventUtils.getGroup();
eventUtils.setGroup(origGroup); eventUtils.setGroup(origGroup);
this.draggable.dispose(); root.dispose();
eventUtils.setGroup(newGroup); eventUtils.setGroup(newGroup);
} }
} }
// We need to special case blocks for now so that we look at the root block
// instead of the one actually being dragged in most cases.
private getRoot(draggable: IDraggable): IDraggable {
return draggable instanceof BlockSvg ? draggable.getRootBlock() : draggable;
}
/** /**
* Returns true if we should return the draggable to its original location * Returns true if we should return the draggable to its original location
* at the end of the drag. * at the end of the drag.
*/ */
protected shouldReturnToStart(e: PointerEvent, draggable: IDraggable) { protected shouldReturnToStart(e: PointerEvent, rootDraggable: IDraggable) {
const dragTarget = this.workspace.getDragTarget(e); const dragTarget = this.workspace.getDragTarget(e);
if (!dragTarget) return false; if (!dragTarget) return false;
return dragTarget.shouldPreventMove(draggable); return dragTarget.shouldPreventMove(rootDraggable);
} }
protected pixelsToWorkspaceUnits(pixelCoord: Coordinate): Coordinate { protected pixelsToWorkspaceUnits(pixelCoord: Coordinate): Coordinate {

View File

@@ -1015,7 +1015,7 @@ export class Gesture {
// If the gesture already went through a bubble, don't set the start block. // If the gesture already went through a bubble, don't set the start block.
if (!this.startBlock && !this.startBubble) { if (!this.startBlock && !this.startBubble) {
this.startBlock = block; this.startBlock = block;
common.setSelected(this.startBlock.getFirstNonShadowBlock()); common.setSelected(this.startBlock);
if (block.isInFlyout && block !== block.getRootBlock()) { if (block.isInFlyout && block !== block.getRootBlock()) {
this.setTargetBlock(block.getRootBlock()); this.setTargetBlock(block.getRootBlock());
} else { } else {