Skip to content
Merged
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
30 changes: 30 additions & 0 deletions packages/blockly/core/shortcut_items.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export enum names {
PREVIOUS_STACK = 'previous_stack',
INFORMATION = 'information',
PERFORM_ACTION = 'perform_action',
DUPLICATE = 'duplicate',
CLEANUP = 'cleanup',
}

Expand Down Expand Up @@ -871,6 +872,34 @@ export function registerPerformAction() {
ShortcutRegistry.registry.register(performActionShortcut);
}

/**
* Registers keyboard shortcut to duplicate a block or workspace comment.
*/
export function registerDuplicate() {
const duplicateShortcut: KeyboardShortcut = {
name: names.DUPLICATE,
preconditionFn: (workspace, scope) => {
const {focusedNode} = scope;
return (
!workspace.isDragging() &&
!workspace.isReadOnly() &&
(focusedNode instanceof BlockSvg ? focusedNode.isDuplicatable() : true)
);
},
callback: (workspace, _e, _shortcut, scope) => {
keyboardNavigationController.setIsActive(true);
const copyable = isICopyable(scope.focusedNode) && scope.focusedNode;
if (!copyable) return false;
const data = copyable.toCopyData();
if (!data) return false;
return !!clipboard.paste(data, workspace);
},
keyCodes: [KeyCodes.D],
allowCollision: true,
};
ShortcutRegistry.registry.register(duplicateShortcut);
}

/**
* Registers keyboard shortcut to clean up the workspace.
*/
Expand Down Expand Up @@ -919,6 +948,7 @@ export function registerKeyboardNavigationShortcuts() {
registerDisconnectBlock();
registerStackNavigation();
registerPerformAction();
registerDuplicate();
registerCleanup();
}

Expand Down
58 changes: 58 additions & 0 deletions packages/blockly/tests/mocha/shortcut_items_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -1232,6 +1232,64 @@ suite('Keyboard Shortcut Items', function () {
});
});

suite('Duplicate (D)', function () {
test('Can duplicate blocks', function () {
const block = this.workspace.newBlock('controls_if');
Blockly.getFocusManager().focusNode(block);
assert.equal(this.workspace.getTopBlocks().length, 1);
const event = createKeyDownEvent(Blockly.utils.KeyCodes.D);
this.workspace.getInjectionDiv().dispatchEvent(event);
const topBlocks = this.workspace.getTopBlocks(true);
assert.equal(topBlocks.length, 2);
assert.notEqual(topBlocks[1], block);
assert.equal(topBlocks[1].type, block.type);
});

test('Can duplicate workspace comments', function () {
const comment = this.workspace.newComment();
comment.setText('Hello');
Blockly.getFocusManager().focusNode(comment);
assert.equal(this.workspace.getTopComments().length, 1);
const event = createKeyDownEvent(Blockly.utils.KeyCodes.D);
this.workspace.getInjectionDiv().dispatchEvent(event);
const topComments = this.workspace.getTopComments(true);
assert.equal(topComments.length, 2);
assert.notEqual(topComments[1], comment);
assert.equal(topComments[1].getText(), comment.getText());
});

test('Does not duplicate blocks on a readonly workspace', function () {
const block = this.workspace.newBlock('controls_if');
this.workspace.setIsReadOnly(true);
Blockly.getFocusManager().focusNode(block);
assert.equal(this.workspace.getTopBlocks().length, 1);
const event = createKeyDownEvent(Blockly.utils.KeyCodes.D);
this.workspace.getInjectionDiv().dispatchEvent(event);
assert.equal(this.workspace.getTopBlocks().length, 1);
});

test('Does not duplicate blocks that are not duplicatable', function () {
const block = this.workspace.newBlock('controls_if');
this.workspace.options.maxBlocks = 1;
assert.isFalse(block.isDuplicatable());
assert.equal(this.workspace.getTopBlocks().length, 1);
const event = createKeyDownEvent(Blockly.utils.KeyCodes.D);
this.workspace.getInjectionDiv().dispatchEvent(event);
assert.equal(this.workspace.getTopBlocks().length, 1);
});

test('Does not duplicate workspace comments on a readonly workspace', function () {
const comment = this.workspace.newComment();
comment.setText('Hello');
this.workspace.setIsReadOnly(true);
Blockly.getFocusManager().focusNode(comment);
assert.equal(this.workspace.getTopComments().length, 1);
const event = createKeyDownEvent(Blockly.utils.KeyCodes.D);
this.workspace.getInjectionDiv().dispatchEvent(event);
assert.equal(this.workspace.getTopComments().length, 1);
});
});

suite('Clean up workspace (C)', function () {
test('Arranges all blocks in a vertical column', function () {
this.workspace.newBlock('controls_if');
Expand Down