77import {
88 ContextMenuRegistry ,
99 ShortcutRegistry ,
10- ICopyData ,
1110 isCopyable ,
12- isDeletable ,
13- isDraggable ,
1411 Msg ,
1512 ShortcutItems ,
16- Flyout ,
17- getMainWorkspace ,
13+ WorkspaceSvg ,
1814} from 'blockly' ;
1915import * as Constants from '../constants' ;
20- import { WorkspaceSvg } from 'blockly' ;
2116import { Navigation } from '../navigation' ;
2217import { getShortActionShortcut } from '../shortcut_formatting' ;
2318import { clearPasteHints , showCopiedHint , showCutHint } from '../hints' ;
24- import { IFocusableNode } from 'blockly/core' ;
2519
2620/**
2721 * Weight for the first of these three items in the context menu.
@@ -31,29 +25,18 @@ import {IFocusableNode} from 'blockly/core';
3125 */
3226const BASE_WEIGHT = 12 ;
3327
34- /** Type of the callback function for keyboard shortcuts. */
35- type ShortcutCallback = (
36- workspace : WorkspaceSvg ,
37- e : Event ,
38- shortcut : ShortcutRegistry . KeyboardShortcut ,
39- scope : ContextMenuRegistry . Scope ,
40- ) => boolean ;
41-
4228/**
4329 * Logic and state for cut/copy/paste actions as both keyboard shortcuts
4430 * and context menu items.
4531 * In the long term, this will likely merge with the clipboard code in core.
4632 */
4733export class Clipboard {
48- /** Data copied by the copy or cut keyboard shortcuts. */
49- private copyData : ICopyData | null = null ;
50-
5134 /** The workspace a copy or cut keyboard shortcut happened in. */
5235 private copyWorkspace : WorkspaceSvg | null = null ;
5336
54- private oldCutCallback : ShortcutCallback | undefined ;
55- private oldCopyCallback : ShortcutCallback | undefined ;
56- private oldPasteCallback : ShortcutCallback | undefined ;
37+ private oldCutShortcut : ShortcutRegistry . KeyboardShortcut | undefined ;
38+ private oldCopyShortcut : ShortcutRegistry . KeyboardShortcut | undefined ;
39+ private oldPasteShortcut : ShortcutRegistry . KeyboardShortcut | undefined ;
5740
5841 constructor ( private navigation : Navigation ) { }
5942
@@ -92,20 +75,18 @@ export class Clipboard {
9275 * Identical to the one in core but adds a toast after successful cut.
9376 */
9477 private registerCutShortcut ( ) {
95- const oldCutShortcut =
78+ this . oldCutShortcut =
9679 ShortcutRegistry . registry . getRegistry ( ) [ ShortcutItems . names . CUT ] ;
97- if ( ! oldCutShortcut )
80+ if ( ! this . oldCutShortcut )
9881 throw new Error ( 'No cut keyboard shortcut registered initially' ) ;
9982
100- this . oldCutCallback = oldCutShortcut . callback ;
101-
10283 const cutShortcut : ShortcutRegistry . KeyboardShortcut = {
10384 name : Constants . SHORTCUT_NAMES . CUT ,
104- preconditionFn : oldCutShortcut . preconditionFn ,
85+ preconditionFn : this . oldCutShortcut . preconditionFn ,
10586 callback : this . cutCallback . bind ( this ) ,
10687 // The registry gives back keycodes as an object instead of an array
10788 // See https://github.com/google/blockly/issues/9008
108- keyCodes : oldCutShortcut . keyCodes ,
89+ keyCodes : this . oldCutShortcut . keyCodes ,
10990 allowCollision : false ,
11091 } ;
11192
@@ -127,7 +108,7 @@ export class Clipboard {
127108 '%1' ,
128109 getShortActionShortcut ( Constants . SHORTCUT_NAMES . CUT ) ,
129110 ) ,
130- preconditionFn : ( scope ) => this . cutCopyPrecondition ( scope ) ,
111+ preconditionFn : ( scope ) => this . cutPrecondition ( scope ) ,
131112 callback : ( scope , menuOpenEvent ) => {
132113 if ( ! isCopyable ( scope . focusedNode ) ) return false ;
133114 const ws = scope . focusedNode . workspace ;
@@ -142,31 +123,47 @@ export class Clipboard {
142123 ContextMenuRegistry . registry . register ( cutAction ) ;
143124 }
144125
145- /**
146- * Precondition for cut and copy context menus. These are similar to the
147- * ones in core but they don't check if a gesture is in progress,
148- * because a gesture will always be in progress if the context menu
149- * is open.
150- *
151- * @param scope scope on which the menu was opened.
152- * @returns 'enabled', 'disabled', or 'hidden' as appropriate
153- */
154- private cutCopyPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
126+ private cutPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
155127 const focused = scope . focusedNode ;
128+ if ( ! focused || ! isCopyable ( focused ) ) return 'hidden' ;
156129
130+ const workspace = focused . workspace ;
131+ if ( ! ( workspace instanceof WorkspaceSvg ) ) return 'hidden' ;
132+
133+ if (
134+ this . oldCutShortcut ?. preconditionFn &&
135+ this . oldCutShortcut . preconditionFn ( workspace , scope )
136+ ) {
137+ return 'enabled' ;
138+ }
139+ return 'disabled' ;
140+ }
141+
142+ private copyPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
143+ const focused = scope . focusedNode ;
157144 if ( ! focused || ! isCopyable ( focused ) ) return 'hidden' ;
158145
159146 const workspace = focused . workspace ;
147+ if ( ! ( workspace instanceof WorkspaceSvg ) ) return 'hidden' ;
148+
160149 if (
161- ! workspace . isReadOnly ( ) &&
162- isDeletable ( focused ) &&
163- focused . isDeletable ( ) &&
164- isDraggable ( focused ) &&
165- focused . isMovable ( ) &&
166- ! focused . workspace . isFlyout
167- )
150+ this . oldCopyShortcut ?. preconditionFn &&
151+ this . oldCopyShortcut . preconditionFn ( workspace , scope )
152+ ) {
168153 return 'enabled' ;
154+ }
155+ return 'disabled' ;
156+ }
157+
158+ private pastePrecondition ( scope : ContextMenuRegistry . Scope ) : string {
159+ if ( ! this . copyWorkspace ) return 'disabled' ;
169160
161+ if (
162+ this . oldPasteShortcut ?. preconditionFn &&
163+ this . oldPasteShortcut . preconditionFn ( this . copyWorkspace , scope )
164+ ) {
165+ return 'enabled' ;
166+ }
170167 return 'disabled' ;
171168 }
172169
@@ -189,9 +186,10 @@ export class Clipboard {
189186 scope : ContextMenuRegistry . Scope ,
190187 ) {
191188 const didCut =
192- ! ! this . oldCutCallback &&
193- this . oldCutCallback ( workspace , e , shortcut , scope ) ;
189+ ! ! this . oldCutShortcut ?. callback &&
190+ this . oldCutShortcut . callback ( workspace , e , shortcut , scope ) ;
194191 if ( didCut ) {
192+ this . copyWorkspace = workspace ;
195193 showCutHint ( workspace ) ;
196194 }
197195 return didCut ;
@@ -202,20 +200,18 @@ export class Clipboard {
202200 * Identical to the one in core but pops a toast after succesful copy.
203201 */
204202 private registerCopyShortcut ( ) {
205- const oldCopyShortcut =
203+ this . oldCopyShortcut =
206204 ShortcutRegistry . registry . getRegistry ( ) [ ShortcutItems . names . COPY ] ;
207- if ( ! oldCopyShortcut )
205+ if ( ! this . oldCopyShortcut )
208206 throw new Error ( 'No copy keyboard shortcut registered initially' ) ;
209207
210- this . oldCopyCallback = oldCopyShortcut . callback ;
211-
212208 const copyShortcut : ShortcutRegistry . KeyboardShortcut = {
213209 name : Constants . SHORTCUT_NAMES . COPY ,
214- preconditionFn : oldCopyShortcut . preconditionFn ,
210+ preconditionFn : this . oldCopyShortcut . preconditionFn ,
215211 callback : this . copyCallback . bind ( this ) ,
216212 // The registry gives back keycodes as an object instead of an array
217213 // See https://github.com/google/blockly/issues/9008
218- keyCodes : oldCopyShortcut . keyCodes ,
214+ keyCodes : this . oldCopyShortcut . keyCodes ,
219215 allowCollision : false ,
220216 } ;
221217
@@ -237,7 +233,7 @@ export class Clipboard {
237233 '%1' ,
238234 getShortActionShortcut ( Constants . SHORTCUT_NAMES . COPY ) ,
239235 ) ,
240- preconditionFn : ( scope ) => this . cutCopyPrecondition ( scope ) ,
236+ preconditionFn : ( scope ) => this . copyPrecondition ( scope ) ,
241237 callback : ( scope , menuOpenEvent ) => {
242238 if ( ! isCopyable ( scope . focusedNode ) ) return false ;
243239 const ws = scope . focusedNode . workspace ;
@@ -271,9 +267,10 @@ export class Clipboard {
271267 scope : ContextMenuRegistry . Scope ,
272268 ) {
273269 const didCopy =
274- ! ! this . oldCopyCallback &&
275- this . oldCopyCallback ( workspace , e , shortcut , scope ) ;
270+ ! ! this . oldCopyShortcut ?. callback &&
271+ this . oldCopyShortcut . callback ( workspace , e , shortcut , scope ) ;
276272 if ( didCopy ) {
273+ this . copyWorkspace = workspace ;
277274 showCopiedHint ( workspace ) ;
278275 }
279276 return didCopy ;
@@ -284,38 +281,18 @@ export class Clipboard {
284281 * Identical to the one in core but clears any paste toasts after.
285282 */
286283 private registerPasteShortcut ( ) {
287- const oldPasteShortcut =
284+ this . oldPasteShortcut =
288285 ShortcutRegistry . registry . getRegistry ( ) [ ShortcutItems . names . PASTE ] ;
289- if ( ! oldPasteShortcut )
286+ if ( ! this . oldPasteShortcut )
290287 throw new Error ( 'No paste keyboard shortcut registered initially' ) ;
291288
292- this . oldPasteCallback = oldPasteShortcut . callback ;
293-
294289 const pasteShortcut : ShortcutRegistry . KeyboardShortcut = {
295290 name : Constants . SHORTCUT_NAMES . PASTE ,
296- preconditionFn : (
297- workspace : WorkspaceSvg ,
298- scope : ContextMenuRegistry . Scope ,
299- ) => {
300- // Don't use the workspace given as we don't want to paste in the flyout, for example
301- const pasteWorkspace = this . getPasteWorkspace ( scope ) ;
302- if ( ! pasteWorkspace || pasteWorkspace . isReadOnly ( ) ) return false ;
303- return true ;
304- } ,
305- callback : (
306- workspace : WorkspaceSvg ,
307- e : Event ,
308- shortcut : ShortcutRegistry . KeyboardShortcut ,
309- scope : ContextMenuRegistry . Scope ,
310- ) => {
311- // Don't use the workspace given as we don't want to paste in the flyout, for example
312- const pasteWorkspace = this . getPasteWorkspace ( scope ) ;
313- if ( ! pasteWorkspace ) return false ;
314- return this . pasteCallback ( pasteWorkspace , e , shortcut , scope ) ;
315- } ,
291+ preconditionFn : this . oldPasteShortcut . preconditionFn ,
292+ callback : this . pasteCallback . bind ( this ) ,
316293 // The registry gives back keycodes as an object instead of an array
317294 // See https://github.com/google/blockly/issues/9008
318- keyCodes : oldPasteShortcut . keyCodes ,
295+ keyCodes : this . oldPasteShortcut . keyCodes ,
319296 allowCollision : false ,
320297 } ;
321298
@@ -337,17 +314,9 @@ export class Clipboard {
337314 '%1' ,
338315 getShortActionShortcut ( Constants . SHORTCUT_NAMES . PASTE ) ,
339316 ) ,
340- preconditionFn : ( scope : ContextMenuRegistry . Scope ) => {
341- const workspace = this . getPasteWorkspace ( scope ) ;
342- if ( ! workspace ) return 'hidden' ;
343-
344- // Unfortunately, this will return enabled even if nothing is in the clipboard
345- // This is because the clipboard data is not actually exposed in core
346- // so there's no way to check
347- return workspace . isReadOnly ( ) ? 'disabled' : 'enabled' ;
348- } ,
317+ preconditionFn : ( scope ) => this . pastePrecondition ( scope ) ,
349318 callback : ( scope : ContextMenuRegistry . Scope , menuOpenEvent : Event ) => {
350- const workspace = this . getPasteWorkspace ( scope ) ;
319+ const workspace = this . copyWorkspace ;
351320 if ( ! workspace ) return ;
352321 return this . pasteCallback ( workspace , menuOpenEvent , undefined , scope ) ;
353322 } ,
@@ -358,37 +327,6 @@ export class Clipboard {
358327 ContextMenuRegistry . registry . register ( pasteAction ) ;
359328 }
360329
361- /**
362- * Gets the workspace where something should be pasted.
363- * Tries to get the workspace the focusable item is on,
364- * or the target workspace if the focusable item is in a flyout,
365- * or falls back to the main workspace.
366- *
367- * @param scope scope from the action that initiated the paste
368- * @returns a workspace to paste into if possible, otherwise null
369- */
370- private getPasteWorkspace ( scope : ContextMenuRegistry . Scope ) {
371- const focusTree = ( scope . focusedNode as IFocusableNode ) . getFocusableTree ( ) ;
372- let workspace ;
373- if ( focusTree instanceof WorkspaceSvg ) {
374- workspace = focusTree ;
375- } else if ( focusTree instanceof Flyout ) {
376- // Seems like this case doesn't actually happen and a
377- // (flyout) Workspace is returned instead, but it's possible
378- workspace = focusTree . targetWorkspace ;
379- } else {
380- // Give up and just paste in the main workspace
381- workspace = getMainWorkspace ( ) as WorkspaceSvg ;
382- }
383-
384- if ( ! workspace ) return null ;
385- // If we're trying to paste in a flyout, paste in the target workspace instead
386- if ( workspace . isFlyout )
387- workspace = workspace . targetWorkspace as WorkspaceSvg ;
388-
389- return workspace ;
390- }
391-
392330 /**
393331 * The callback for the paste action. Uses the registered version of the paste callback
394332 * to perform the paste logic, then clears any toasts about pasting.
@@ -408,8 +346,8 @@ export class Clipboard {
408346 scope : ContextMenuRegistry . Scope ,
409347 ) {
410348 const didPaste =
411- ! ! this . oldPasteCallback &&
412- this . oldPasteCallback ( workspace , e , shortcut , scope ) ;
349+ ! ! this . oldPasteShortcut ?. callback &&
350+ this . oldPasteShortcut . callback ( workspace , e , shortcut , scope ) ;
413351
414352 // Clear the paste hints regardless of whether something was pasted
415353 // Some implementations of paste are async and we should clear the hint
0 commit comments