7
7
import {
8
8
ContextMenuRegistry ,
9
9
ShortcutRegistry ,
10
- ICopyData ,
11
10
isCopyable ,
12
- isDeletable ,
13
- isDraggable ,
14
11
Msg ,
15
12
ShortcutItems ,
16
- Flyout ,
17
- getMainWorkspace ,
13
+ WorkspaceSvg ,
18
14
} from 'blockly' ;
19
15
import * as Constants from '../constants' ;
20
- import { WorkspaceSvg } from 'blockly' ;
21
16
import { Navigation } from '../navigation' ;
22
17
import { getShortActionShortcut } from '../shortcut_formatting' ;
23
18
import { clearPasteHints , showCopiedHint , showCutHint } from '../hints' ;
24
- import { IFocusableNode } from 'blockly/core' ;
25
19
26
20
/**
27
21
* Weight for the first of these three items in the context menu.
@@ -31,29 +25,18 @@ import {IFocusableNode} from 'blockly/core';
31
25
*/
32
26
const BASE_WEIGHT = 12 ;
33
27
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
-
42
28
/**
43
29
* Logic and state for cut/copy/paste actions as both keyboard shortcuts
44
30
* and context menu items.
45
31
* In the long term, this will likely merge with the clipboard code in core.
46
32
*/
47
33
export class Clipboard {
48
- /** Data copied by the copy or cut keyboard shortcuts. */
49
- private copyData : ICopyData | null = null ;
50
-
51
34
/** The workspace a copy or cut keyboard shortcut happened in. */
52
35
private copyWorkspace : WorkspaceSvg | null = null ;
53
36
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 ;
57
40
58
41
constructor ( private navigation : Navigation ) { }
59
42
@@ -92,20 +75,18 @@ export class Clipboard {
92
75
* Identical to the one in core but adds a toast after successful cut.
93
76
*/
94
77
private registerCutShortcut ( ) {
95
- const oldCutShortcut =
78
+ this . oldCutShortcut =
96
79
ShortcutRegistry . registry . getRegistry ( ) [ ShortcutItems . names . CUT ] ;
97
- if ( ! oldCutShortcut )
80
+ if ( ! this . oldCutShortcut )
98
81
throw new Error ( 'No cut keyboard shortcut registered initially' ) ;
99
82
100
- this . oldCutCallback = oldCutShortcut . callback ;
101
-
102
83
const cutShortcut : ShortcutRegistry . KeyboardShortcut = {
103
84
name : Constants . SHORTCUT_NAMES . CUT ,
104
- preconditionFn : oldCutShortcut . preconditionFn ,
85
+ preconditionFn : this . oldCutShortcut . preconditionFn ,
105
86
callback : this . cutCallback . bind ( this ) ,
106
87
// The registry gives back keycodes as an object instead of an array
107
88
// See https://github.com/google/blockly/issues/9008
108
- keyCodes : oldCutShortcut . keyCodes ,
89
+ keyCodes : this . oldCutShortcut . keyCodes ,
109
90
allowCollision : false ,
110
91
} ;
111
92
@@ -127,7 +108,7 @@ export class Clipboard {
127
108
'%1' ,
128
109
getShortActionShortcut ( Constants . SHORTCUT_NAMES . CUT ) ,
129
110
) ,
130
- preconditionFn : ( scope ) => this . cutCopyPrecondition ( scope ) ,
111
+ preconditionFn : ( scope ) => this . cutPrecondition ( scope ) ,
131
112
callback : ( scope , menuOpenEvent ) => {
132
113
if ( ! isCopyable ( scope . focusedNode ) ) return false ;
133
114
const ws = scope . focusedNode . workspace ;
@@ -143,30 +124,67 @@ export class Clipboard {
143
124
}
144
125
145
126
/**
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.
127
+ * Precondition function for the cut context menu. This wraps the core cut
128
+ * precondition to support context menus.
150
129
*
151
- * @param scope scope on which the menu was opened.
152
- * @returns 'enabled', 'disabled', or 'hidden' as appropriate
130
+ * @param scope scope of the shortcut or context menu item
131
+ * @returns 'enabled' if the node can be cut , 'disabled' otherwise.
153
132
*/
154
- private cutCopyPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
133
+ private cutPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
155
134
const focused = scope . focusedNode ;
135
+ if ( ! focused || ! isCopyable ( focused ) ) return 'hidden' ;
136
+
137
+ const workspace = focused . workspace ;
138
+ if ( ! ( workspace instanceof WorkspaceSvg ) ) return 'hidden' ;
139
+
140
+ if (
141
+ this . oldCutShortcut ?. preconditionFn &&
142
+ this . oldCutShortcut . preconditionFn ( workspace , scope )
143
+ ) {
144
+ return 'enabled' ;
145
+ }
146
+ return 'disabled' ;
147
+ }
156
148
149
+ /**
150
+ * Precondition function for the copy context menu. This wraps the core copy
151
+ * precondition to support context menus.
152
+ *
153
+ * @param scope scope of the shortcut or context menu item
154
+ * @returns 'enabled' if the node can be copied, 'disabled' otherwise.
155
+ */
156
+ private copyPrecondition ( scope : ContextMenuRegistry . Scope ) : string {
157
+ const focused = scope . focusedNode ;
157
158
if ( ! focused || ! isCopyable ( focused ) ) return 'hidden' ;
158
159
159
160
const workspace = focused . workspace ;
161
+ if ( ! ( workspace instanceof WorkspaceSvg ) ) return 'hidden' ;
162
+
160
163
if (
161
- ! workspace . isReadOnly ( ) &&
162
- isDeletable ( focused ) &&
163
- focused . isDeletable ( ) &&
164
- isDraggable ( focused ) &&
165
- focused . isMovable ( ) &&
166
- ! focused . workspace . isFlyout
167
- )
164
+ this . oldCopyShortcut ?. preconditionFn &&
165
+ this . oldCopyShortcut . preconditionFn ( workspace , scope )
166
+ ) {
168
167
return 'enabled' ;
168
+ }
169
+ return 'disabled' ;
170
+ }
169
171
172
+ /**
173
+ * Precondition function for the paste context menu. This wraps the core
174
+ * paste precondition to support context menus.
175
+ *
176
+ * @param scope scope of the shortcut or context menu item
177
+ * @returns 'enabled' if the node can be pasted, 'disabled' otherwise.
178
+ */
179
+ private pastePrecondition ( scope : ContextMenuRegistry . Scope ) : string {
180
+ if ( ! this . copyWorkspace ) return 'disabled' ;
181
+
182
+ if (
183
+ this . oldPasteShortcut ?. preconditionFn &&
184
+ this . oldPasteShortcut . preconditionFn ( this . copyWorkspace , scope )
185
+ ) {
186
+ return 'enabled' ;
187
+ }
170
188
return 'disabled' ;
171
189
}
172
190
@@ -189,9 +207,10 @@ export class Clipboard {
189
207
scope : ContextMenuRegistry . Scope ,
190
208
) {
191
209
const didCut =
192
- ! ! this . oldCutCallback &&
193
- this . oldCutCallback ( workspace , e , shortcut , scope ) ;
210
+ ! ! this . oldCutShortcut ?. callback &&
211
+ this . oldCutShortcut . callback ( workspace , e , shortcut , scope ) ;
194
212
if ( didCut ) {
213
+ this . copyWorkspace = workspace ;
195
214
showCutHint ( workspace ) ;
196
215
}
197
216
return didCut ;
@@ -202,20 +221,18 @@ export class Clipboard {
202
221
* Identical to the one in core but pops a toast after succesful copy.
203
222
*/
204
223
private registerCopyShortcut ( ) {
205
- const oldCopyShortcut =
224
+ this . oldCopyShortcut =
206
225
ShortcutRegistry . registry . getRegistry ( ) [ ShortcutItems . names . COPY ] ;
207
- if ( ! oldCopyShortcut )
226
+ if ( ! this . oldCopyShortcut )
208
227
throw new Error ( 'No copy keyboard shortcut registered initially' ) ;
209
228
210
- this . oldCopyCallback = oldCopyShortcut . callback ;
211
-
212
229
const copyShortcut : ShortcutRegistry . KeyboardShortcut = {
213
230
name : Constants . SHORTCUT_NAMES . COPY ,
214
- preconditionFn : oldCopyShortcut . preconditionFn ,
231
+ preconditionFn : this . oldCopyShortcut . preconditionFn ,
215
232
callback : this . copyCallback . bind ( this ) ,
216
233
// The registry gives back keycodes as an object instead of an array
217
234
// See https://github.com/google/blockly/issues/9008
218
- keyCodes : oldCopyShortcut . keyCodes ,
235
+ keyCodes : this . oldCopyShortcut . keyCodes ,
219
236
allowCollision : false ,
220
237
} ;
221
238
@@ -237,7 +254,7 @@ export class Clipboard {
237
254
'%1' ,
238
255
getShortActionShortcut ( Constants . SHORTCUT_NAMES . COPY ) ,
239
256
) ,
240
- preconditionFn : ( scope ) => this . cutCopyPrecondition ( scope ) ,
257
+ preconditionFn : ( scope ) => this . copyPrecondition ( scope ) ,
241
258
callback : ( scope , menuOpenEvent ) => {
242
259
if ( ! isCopyable ( scope . focusedNode ) ) return false ;
243
260
const ws = scope . focusedNode . workspace ;
@@ -271,9 +288,10 @@ export class Clipboard {
271
288
scope : ContextMenuRegistry . Scope ,
272
289
) {
273
290
const didCopy =
274
- ! ! this . oldCopyCallback &&
275
- this . oldCopyCallback ( workspace , e , shortcut , scope ) ;
291
+ ! ! this . oldCopyShortcut ?. callback &&
292
+ this . oldCopyShortcut . callback ( workspace , e , shortcut , scope ) ;
276
293
if ( didCopy ) {
294
+ this . copyWorkspace = workspace ;
277
295
showCopiedHint ( workspace ) ;
278
296
}
279
297
return didCopy ;
@@ -284,38 +302,18 @@ export class Clipboard {
284
302
* Identical to the one in core but clears any paste toasts after.
285
303
*/
286
304
private registerPasteShortcut ( ) {
287
- const oldPasteShortcut =
305
+ this . oldPasteShortcut =
288
306
ShortcutRegistry . registry . getRegistry ( ) [ ShortcutItems . names . PASTE ] ;
289
- if ( ! oldPasteShortcut )
307
+ if ( ! this . oldPasteShortcut )
290
308
throw new Error ( 'No paste keyboard shortcut registered initially' ) ;
291
309
292
- this . oldPasteCallback = oldPasteShortcut . callback ;
293
-
294
310
const pasteShortcut : ShortcutRegistry . KeyboardShortcut = {
295
311
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
- } ,
312
+ preconditionFn : this . oldPasteShortcut . preconditionFn ,
313
+ callback : this . pasteCallback . bind ( this ) ,
316
314
// The registry gives back keycodes as an object instead of an array
317
315
// See https://github.com/google/blockly/issues/9008
318
- keyCodes : oldPasteShortcut . keyCodes ,
316
+ keyCodes : this . oldPasteShortcut . keyCodes ,
319
317
allowCollision : false ,
320
318
} ;
321
319
@@ -337,17 +335,9 @@ export class Clipboard {
337
335
'%1' ,
338
336
getShortActionShortcut ( Constants . SHORTCUT_NAMES . PASTE ) ,
339
337
) ,
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
- } ,
338
+ preconditionFn : ( scope ) => this . pastePrecondition ( scope ) ,
349
339
callback : ( scope : ContextMenuRegistry . Scope , menuOpenEvent : Event ) => {
350
- const workspace = this . getPasteWorkspace ( scope ) ;
340
+ const workspace = this . copyWorkspace ;
351
341
if ( ! workspace ) return ;
352
342
return this . pasteCallback ( workspace , menuOpenEvent , undefined , scope ) ;
353
343
} ,
@@ -358,37 +348,6 @@ export class Clipboard {
358
348
ContextMenuRegistry . registry . register ( pasteAction ) ;
359
349
}
360
350
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
-
392
351
/**
393
352
* The callback for the paste action. Uses the registered version of the paste callback
394
353
* to perform the paste logic, then clears any toasts about pasting.
@@ -408,8 +367,8 @@ export class Clipboard {
408
367
scope : ContextMenuRegistry . Scope ,
409
368
) {
410
369
const didPaste =
411
- ! ! this . oldPasteCallback &&
412
- this . oldPasteCallback ( workspace , e , shortcut , scope ) ;
370
+ ! ! this . oldPasteShortcut ?. callback &&
371
+ this . oldPasteShortcut . callback ( workspace , e , shortcut , scope ) ;
413
372
414
373
// Clear the paste hints regardless of whether something was pasted
415
374
// Some implementations of paste are async and we should clear the hint
0 commit comments