Skip to content

Commit d331336

Browse files
authored
fix: Display dragged comments and bubbles atop the toolbox (#9552)
* fix: Display dragged comments and bubbles atop the toolbox * test: Add tests for drag layer behavior
1 parent 93056bc commit d331336

File tree

5 files changed

+132
-7
lines changed

5 files changed

+132
-7
lines changed

core/comments/rendered_workspace_comment.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ import {IFocusableNode} from '../interfaces/i_focusable_node.js';
2323
import type {IFocusableTree} from '../interfaces/i_focusable_tree.js';
2424
import {IRenderedElement} from '../interfaces/i_rendered_element.js';
2525
import {ISelectable} from '../interfaces/i_selectable.js';
26-
import * as layers from '../layers.js';
2726
import * as commentSerialization from '../serialization/workspace_comments.js';
2827
import {Coordinate} from '../utils/coordinate.js';
2928
import * as dom from '../utils/dom.js';
@@ -346,7 +345,7 @@ export class RenderedWorkspaceComment
346345
onNodeFocus(): void {
347346
this.select();
348347
// Ensure that the comment is always at the top when focused.
349-
this.workspace.getLayerManager()?.append(this, layers.BLOCK);
348+
this.getSvgRoot().parentElement?.appendChild(this.getSvgRoot());
350349
this.workspace.scrollBoundsIntoView(this.getBoundingRectangle());
351350
}
352351

core/dragging/block_drag_strategy.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ import {ConnectionType} from '../connection_type.js';
1414
import type {BlockMove} from '../events/events_block_move.js';
1515
import {EventType} from '../events/type.js';
1616
import * as eventUtils from '../events/utils.js';
17+
import type {IBubble} from '../interfaces/i_bubble.js';
1718
import {IConnectionPreviewer} from '../interfaces/i_connection_previewer.js';
1819
import {IDragStrategy} from '../interfaces/i_draggable.js';
20+
import {IHasBubble, hasBubble} from '../interfaces/i_has_bubble.js';
1921
import * as layers from '../layers.js';
2022
import * as registry from '../registry.js';
2123
import {finishQueuedRenders} from '../render_management.js';
@@ -120,6 +122,34 @@ export class BlockDragStrategy implements IDragStrategy {
120122
}
121123
this.block.setDragging(true);
122124
this.workspace.getLayerManager()?.moveToDragLayer(this.block);
125+
this.getVisibleBubbles(this.block).forEach((bubble) => {
126+
this.workspace.getLayerManager()?.moveToDragLayer(bubble, false);
127+
});
128+
}
129+
130+
/**
131+
* Returns an array of visible bubbles attached to the given block or its
132+
* descendants.
133+
*
134+
* @param block The block to identify open bubbles on.
135+
* @returns An array of all currently visible bubbles on the given block or
136+
* its descendants.
137+
*/
138+
private getVisibleBubbles(block: BlockSvg): IBubble[] {
139+
return block
140+
.getDescendants(false)
141+
.flatMap((block) => block.getIcons())
142+
.filter((icon) => hasBubble(icon) && icon.bubbleIsVisible())
143+
.map((icon) => (icon as unknown as IHasBubble).getBubble())
144+
.filter((bubble) => !!bubble) // Convince TS they're non-null.
145+
.sort((a, b) => {
146+
// Sort the bubbles by their position in the DOM in order to maintain
147+
// their relative z-ordering when moving between layers.
148+
const position = a.getSvgRoot().compareDocumentPosition(b.getSvgRoot());
149+
if (position & Node.DOCUMENT_POSITION_PRECEDING) return 1;
150+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) return -1;
151+
return 0;
152+
});
123153
}
124154

125155
/**
@@ -393,6 +423,13 @@ export class BlockDragStrategy implements IDragStrategy {
393423
this.workspace
394424
.getLayerManager()
395425
?.moveOffDragLayer(this.block, layers.BLOCK);
426+
427+
this.getVisibleBubbles(this.block).forEach((bubble) =>
428+
this.workspace
429+
.getLayerManager()
430+
?.moveOffDragLayer(bubble, layers.BUBBLE, false),
431+
);
432+
396433
this.block.setDragging(false);
397434
}
398435

@@ -462,6 +499,12 @@ export class BlockDragStrategy implements IDragStrategy {
462499
this.workspace
463500
.getLayerManager()
464501
?.moveOffDragLayer(this.block, layers.BLOCK);
502+
this.getVisibleBubbles(this.block).forEach((bubble) =>
503+
this.workspace
504+
.getLayerManager()
505+
?.moveOffDragLayer(bubble, layers.BUBBLE, false),
506+
);
507+
465508
// Blocks dragged directly from a flyout may need to be bumped into
466509
// bounds.
467510
bumpObjects.bumpIntoBounds(

core/layer_manager.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,15 @@ export class LayerManager {
9999
* Moves the given element to the drag layer, which exists on top of all other
100100
* layers, and the drag surface.
101101
*
102+
* @param elem The element to move onto the drag layer.
103+
* @param focus Whether or not to focus the element post-move.
104+
*
102105
* @internal
103106
*/
104-
moveToDragLayer(elem: IRenderedElement & IFocusableNode) {
107+
moveToDragLayer(elem: IRenderedElement & IFocusableNode, focus = true) {
105108
this.dragLayer?.appendChild(elem.getSvgRoot());
106109

107-
if (elem.canBeFocused()) {
110+
if (focus && elem.canBeFocused()) {
108111
// Since moving the element to the drag layer will cause it to lose focus,
109112
// ensure it regains focus (to ensure proper highlights & sent events).
110113
getFocusManager().focusNode(elem);
@@ -114,12 +117,22 @@ export class LayerManager {
114117
/**
115118
* Moves the given element off of the drag layer.
116119
*
120+
* @param elem The element to move off of the drag layer.
121+
* @param layerNum The identifier of the layer to move the element onto.
122+
* Should be a constant from layers.ts.
123+
* @param focus Whether or not the element should be focused once moved onto
124+
* the destination layer.
125+
*
117126
* @internal
118127
*/
119-
moveOffDragLayer(elem: IRenderedElement & IFocusableNode, layerNum: number) {
128+
moveOffDragLayer(
129+
elem: IRenderedElement & IFocusableNode,
130+
layerNum: number,
131+
focus = true,
132+
) {
120133
this.append(elem, layerNum);
121134

122-
if (elem.canBeFocused()) {
135+
if (focus && elem.canBeFocused()) {
123136
// Since moving the element off the drag layer will cause it to lose focus,
124137
// ensure it regains focus (to ensure proper highlights & sent events).
125138
getFocusManager().focusNode(elem);
@@ -202,4 +215,13 @@ export class LayerManager {
202215
getBubbleLayer(): SVGGElement {
203216
return this.layers.get(layerNums.BUBBLE)!;
204217
}
218+
219+
/**
220+
* Returns the drag layer.
221+
*
222+
* @internal
223+
*/
224+
getDragLayer(): SVGGElement | undefined {
225+
return this.dragLayer;
226+
}
205227
}

tests/mocha/block_test.js

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2883,4 +2883,50 @@ suite('Blocks', function () {
28832883
assert.equal(block.inputList[1].fieldRow[0].getValue(), 'Row2');
28842884
});
28852885
});
2886+
2887+
suite('Dragging', function () {
2888+
setup(function () {
2889+
this.workspace = Blockly.inject('blocklyDiv');
2890+
this.blocks = createTestBlocks(this.workspace, false);
2891+
for (const block of Object.values(this.blocks)) {
2892+
block.initSvg();
2893+
block.render();
2894+
}
2895+
});
2896+
test('Bubbles are moved to drag layer along with their blocks', async function () {
2897+
this.blocks.A.setCommentText('a');
2898+
this.blocks.B.setCommentText('b');
2899+
this.blocks.C.setCommentText('c');
2900+
const v1 = this.blocks.A.getIcon(
2901+
Blockly.icons.IconType.COMMENT,
2902+
).setBubbleVisible(true);
2903+
const v2 = this.blocks.B.getIcon(
2904+
Blockly.icons.IconType.COMMENT,
2905+
).setBubbleVisible(true);
2906+
const v3 = this.blocks.C.getIcon(
2907+
Blockly.icons.IconType.COMMENT,
2908+
).setBubbleVisible(true);
2909+
2910+
this.clock.tick(1000);
2911+
await Promise.all([v1, v2, v3]);
2912+
2913+
this.blocks.B.startDrag();
2914+
2915+
// Block A stays put and should have its comment stay on the bubble layer.
2916+
assert.equal(
2917+
this.blocks.A.getIcon(Blockly.icons.IconType.COMMENT)
2918+
.getBubble()
2919+
.getSvgRoot().parentElement,
2920+
this.blocks.A.workspace.getLayerManager()?.getBubbleLayer(),
2921+
);
2922+
2923+
// Block B moves to the drag layer and its comment should follow.
2924+
assert.equal(
2925+
this.blocks.B.getIcon(Blockly.icons.IconType.COMMENT)
2926+
.getBubble()
2927+
.getSvgRoot().parentElement,
2928+
this.blocks.B.workspace.getLayerManager()?.getDragLayer(),
2929+
);
2930+
});
2931+
});
28862932
});

tests/mocha/workspace_comment_test.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,10 @@ suite('Workspace comment', function () {
168168
this.workspace.id,
169169
);
170170
});
171+
});
171172

172-
test('focuses the workspace when deleted', function () {
173+
suite('Focus', function () {
174+
test('moves to the workspace when deleted', function () {
173175
const comment = new Blockly.comments.RenderedWorkspaceComment(
174176
this.workspace,
175177
);
@@ -178,5 +180,18 @@ suite('Workspace comment', function () {
178180
comment.view.getCommentBarButtons()[1].performAction();
179181
assert.equal(Blockly.getFocusManager().getFocusedNode(), this.workspace);
180182
});
183+
184+
test('does not change the layer', function () {
185+
const comment = new Blockly.comments.RenderedWorkspaceComment(
186+
this.workspace,
187+
);
188+
189+
this.workspace.getLayerManager()?.moveToDragLayer(comment);
190+
Blockly.getFocusManager().focusNode(comment);
191+
assert.equal(
192+
comment.getSvgRoot().parentElement,
193+
this.workspace.getLayerManager()?.getDragLayer(),
194+
);
195+
});
181196
});
182197
});

0 commit comments

Comments
 (0)