feat: Display toasts when items are cut/copied/pasted (#9842)

This commit is contained in:
Aaron Dodson
2026-05-11 08:09:20 -07:00
committed by GitHub
parent 4e1bdf62ae
commit d769be07a4
3 changed files with 110 additions and 1 deletions
+45
View File
@@ -5,6 +5,7 @@
*/
import {Msg} from './msg.js';
import {names} from './shortcut_items.js';
import {Toast} from './toast.js';
import {getShortcutKeysShort} from './utils/shortcut_formatting.js';
import * as userAgent from './utils/useragent.js';
@@ -15,6 +16,8 @@ const constrainedMoveHintId = 'constrainedMoveHint';
const helpHintId = 'helpHint';
const blockNavigationHintId = 'blockNavigationHint';
const workspaceNavigationHintId = 'workspaceNavigationHint';
const copiedHintId = 'copiedHint';
const cutHintId = 'cutHint';
/**
* Nudge the user to use unconstrained movement.
@@ -102,3 +105,45 @@ export function showWorkspaceNavigationHint(workspace: WorkspaceSvg) {
const id = workspaceNavigationHintId;
Toast.show(workspace, {message, id});
}
/**
* Nudge the user to paste after a copy.
*
* @param workspace Workspace.
*/
export function showCopiedHint(workspace: WorkspaceSvg) {
Toast.show(workspace, {
message: Msg['KEYBOARD_NAV_COPIED_HINT'].replace(
'%1',
getShortcutKeysShort(names.PASTE),
),
duration: 7,
id: copiedHintId,
});
}
/**
* Nudge the user to paste after a cut.
*
* @param workspace Workspace.
*/
export function showCutHint(workspace: WorkspaceSvg) {
Toast.show(workspace, {
message: Msg['KEYBOARD_NAV_CUT_HINT'].replace(
'%1',
getShortcutKeysShort(names.PASTE),
),
duration: 7,
id: cutHintId,
});
}
/**
* Clear active paste-related hints, if any.
*
* @param workspace The workspace.
*/
export function clearPasteHints(workspace: WorkspaceSvg) {
Toast.hide(workspace, cutHintId);
Toast.hide(workspace, copiedHintId);
}
+11 -1
View File
@@ -13,6 +13,7 @@ import * as contextmenu from './contextmenu.js';
import * as dropDownDiv from './dropdowndiv.js';
import * as eventUtils from './events/utils.js';
import {getFocusManager} from './focus_manager.js';
import {clearPasteHints, showCopiedHint, showCutHint} from './hints.js';
import {hasContextMenu} from './interfaces/i_contextmenu.js';
import {isCopyable as isICopyable} from './interfaces/i_copyable.js';
import {isDeletable as isIDeletable} from './interfaces/i_deletable.js';
@@ -210,7 +211,11 @@ export function registerCopy() {
isDraggable(focused) && focused.workspace == targetWorkspace
? focused.getRelativeToSurfaceXY()
: undefined;
return !!clipboard.copy(focused, copyCoords);
const copied = !!clipboard.copy(focused, copyCoords);
if (copied) {
showCopiedHint(workspace);
}
return copied;
},
keyCodes: [ctrlC],
displayText: () => Msg['COPY_SHORTCUT'],
@@ -258,6 +263,9 @@ export function registerCut() {
workspace.getAudioManager().play('delete');
e.preventDefault();
}
if (copyData) {
showCutHint(workspace);
}
return !!copyData;
},
keyCodes: [ctrlX],
@@ -303,6 +311,8 @@ export function registerPaste() {
const copyWorkspace = clipboard.getLastCopiedWorkspace();
if (!copyWorkspace) return false;
clearPasteHints(workspace);
const targetWorkspace = copyWorkspace.isFlyout
? copyWorkspace.targetWorkspace
: copyWorkspace;
@@ -282,6 +282,23 @@ suite('Keyboard Shortcut Items', function () {
sinon.assert.calledOnce(this.copySpy);
sinon.assert.calledOnce(this.hideChaffSpy);
});
test('Shows a toast when copying a block', function () {
const toastSpy = sinon.spy(Blockly.Toast, 'show');
this.injectionDiv.dispatchEvent(keyEvent);
sinon.assert.called(toastSpy);
assert.include(toastSpy.args[0][1]['message'], 'Copied. Press');
toastSpy.restore();
});
test('Shows a toast when copying a workspace comment', function () {
setSelectedComment(this.workspace);
const toastSpy = sinon.spy(Blockly.Toast, 'show');
this.injectionDiv.dispatchEvent(keyEvent);
sinon.assert.called(toastSpy);
assert.include(toastSpy.args[0][1]['message'], 'Copied. Press');
toastSpy.restore();
});
});
suite('Cut', function () {
@@ -362,6 +379,23 @@ suite('Keyboard Shortcut Items', function () {
sinon.assert.calledOnce(this.copySpy);
sinon.assert.calledOnce(this.disposeSpy);
});
test('Shows a toast when cutting a block', function () {
const toastSpy = sinon.spy(Blockly.Toast, 'show');
this.injectionDiv.dispatchEvent(keyEvent);
sinon.assert.called(toastSpy);
assert.include(toastSpy.args[0][1]['message'], 'Cut. Press');
toastSpy.restore();
});
test('Shows a toast when cutting a workspace comment', function () {
setSelectedComment(this.workspace);
const toastSpy = sinon.spy(Blockly.Toast, 'show');
this.injectionDiv.dispatchEvent(keyEvent);
sinon.assert.called(toastSpy);
assert.include(toastSpy.args[0][1]['message'], 'Cut. Press');
toastSpy.restore();
});
});
suite('Paste', function () {
@@ -375,6 +409,26 @@ suite('Keyboard Shortcut Items', function () {
const isPasteEnabled = pasteShortcut.preconditionFn();
assert.isFalse(isPasteEnabled);
});
test('Hides cut/copy toasts', function () {
setSelectedBlock(this.workspace);
const copyEvent = createKeyDownEvent(Blockly.utils.KeyCodes.C, [
Blockly.utils.KeyCodes.CTRL_CMD,
]);
this.injectionDiv.dispatchEvent(copyEvent);
this.clock.runAll();
const toastSpy = sinon.spy(Blockly.Toast, 'hide');
const pasteEvent = createKeyDownEvent(Blockly.utils.KeyCodes.V, [
Blockly.utils.KeyCodes.CTRL_CMD,
]);
this.injectionDiv.dispatchEvent(pasteEvent);
sinon.assert.calledWith(toastSpy, this.workspace, 'cutHint');
sinon.assert.calledWith(toastSpy, this.workspace, 'copiedHint');
toastSpy.restore();
});
});
suite('Undo', function () {