diff --git a/packages/blockly/core/hints.ts b/packages/blockly/core/hints.ts index 6c88f1ac506..729ddfea1cc 100644 --- a/packages/blockly/core/hints.ts +++ b/packages/blockly/core/hints.ts @@ -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); +} diff --git a/packages/blockly/core/shortcut_items.ts b/packages/blockly/core/shortcut_items.ts index 92a0ea6e507..1d19fc110f5 100644 --- a/packages/blockly/core/shortcut_items.ts +++ b/packages/blockly/core/shortcut_items.ts @@ -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; diff --git a/packages/blockly/tests/mocha/shortcut_items_test.js b/packages/blockly/tests/mocha/shortcut_items_test.js index 90dfe32b616..f03e048c181 100644 --- a/packages/blockly/tests/mocha/shortcut_items_test.js +++ b/packages/blockly/tests/mocha/shortcut_items_test.js @@ -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 () {