Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/blockly/core/clipboard/block_paster.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export class BlockPaster implements IPaster<BlockCopyData, BlockSvg> {
// However, the algorithm for deciding where to paste a block depends on
// the starting position of the copied block, so we'll pass those coordinates along
const initialCoordinates =
coordinate ||
coordinate ??
new Coordinate(
copyData.blockState['x'] || 0,
copyData.blockState['y'] || 0,
Expand Down
4 changes: 4 additions & 0 deletions packages/blockly/core/keyboard_nav/navigators/navigator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import {BlockSvg} from '../../block_svg.js';
import {CommentEditor} from '../../comments/comment_editor.js';
import {Field} from '../../field.js';
import {getFocusManager} from '../../focus_manager.js';
import {Icon} from '../../icons/icon.js';
Expand Down Expand Up @@ -499,6 +500,9 @@ export class Navigator {
return node.getSourceBlock();
} else if (node instanceof Icon) {
return node.getSourceBlock() as BlockSvg;
} else if (node instanceof CommentEditor) {
const parent = node.getParent();
return parent instanceof BlockSvg ? parent : null;
}

return null;
Expand Down
11 changes: 11 additions & 0 deletions packages/blockly/core/shortcut_items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,7 @@ export function registerPaste() {
},
callback(workspace: WorkspaceSvg, e: Event) {
const copyData = clipboard.getLastCopiedData();
const focusedNode = getFocusManager().getFocusedNode();
if (!copyData) return false;

const copyWorkspace = clipboard.getLastCopiedWorkspace();
Expand All @@ -355,6 +356,16 @@ export function registerPaste() {
return !!clipboard.paste(copyData, targetWorkspace, mouseCoords);
}

// If the focused node is a block, paste relative to that block's position.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this comment is very slightly inaccurate, it's if the focus node is a block or part of a block (e.g. connection or field)

const block = targetWorkspace
.getNavigator()
.getSourceBlockFromNode(focusedNode);
const pasteOrigin = block?.getRelativeToSurfaceXY();
if (pasteOrigin) {
return !!clipboard.paste(copyData, targetWorkspace, pasteOrigin);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be super cool if when:

  1. a connection is focused
  2. the copied block is compatible with that connection

we connected the block to the connection automatically

but that could be a follow up feature too :)


// No spatial focus target (e.g. workspace root) — use copy-location behavior.
const copyCoords = clipboard.getLastCopiedLocation();
if (!copyCoords) {
// If we don't have location data about the original copyable, let the
Expand Down
73 changes: 73 additions & 0 deletions packages/blockly/tests/mocha/shortcut_items_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,6 +429,79 @@ suite('Keyboard Shortcut Items', function () {
sinon.assert.calledWith(toastSpy, this.workspace, 'copiedHint');
toastSpy.restore();
});

test('Pastes near focused block instead of copy origin', function () {
this.workspace.clear();
const blockA = setSelectedBlock(this.workspace);

this.injectionDiv.dispatchEvent(
createKeyDownEvent(Blockly.utils.KeyCodes.C, [
Blockly.utils.KeyCodes.CTRL_CMD,
]),
);

const blockB = Blockly.serialization.blocks.append(
{type: 'stack_block', x: 300, y: 300},
this.workspace,
);
Blockly.getFocusManager().focusNode(blockB);

this.injectionDiv.dispatchEvent(
createKeyDownEvent(Blockly.utils.KeyCodes.V, [
Blockly.utils.KeyCodes.CTRL_CMD,
]),
);

const pastedBlock = this.workspace
.getAllBlocks(false)
.find((b) => ![blockA, blockB].includes(b));
assert.isDefined(pastedBlock);

const pastedXY = pastedBlock.getRelativeToSurfaceXY();
// Check that the pasted block is closer to blockB than blockA, which means
// it used the focus location instead of the copy origin.
assert.isBelow(
Blockly.utils.Coordinate.distance(
pastedXY,
blockB.getRelativeToSurfaceXY(),
),
Blockly.utils.Coordinate.distance(
pastedXY,
blockA.getRelativeToSurfaceXY(),
),
);
});

test('Uses copy origin when workspace has focus', function () {
const blockA = setSelectedBlock(this.workspace);
this.injectionDiv.dispatchEvent(
createKeyDownEvent(Blockly.utils.KeyCodes.C, [
Blockly.utils.KeyCodes.CTRL_CMD,
]),
);

Blockly.getFocusManager().focusNode(this.workspace);
this.injectionDiv.dispatchEvent(
createKeyDownEvent(Blockly.utils.KeyCodes.V, [
Blockly.utils.KeyCodes.CTRL_CMD,
]),
);

const pastedBlock = this.workspace
.getAllBlocks(false)
.find((b) => b.id !== blockA.id);
assert.isDefined(pastedBlock);

const copyOrigin = blockA.getRelativeToSurfaceXY();
const pastedXY = pastedBlock.getRelativeToSurfaceXY();
assert.isBelow(
Blockly.utils.Coordinate.distance(pastedXY, copyOrigin),
Blockly.utils.Coordinate.distance(
pastedXY,
new Blockly.utils.Coordinate(300, 300),
),
);
});
});

suite('Undo', function () {
Expand Down