@@ -239,7 +239,7 @@ export class NavigationController {
239239 protected deletePreconditionFn ( workspace : WorkspaceSvg ) {
240240 if ( ! this . canCurrentlyEdit ( workspace ) ) return false ;
241241 const sourceBlock = workspace . getCursor ( ) ?. getCurNode ( ) . getSourceBlock ( ) ;
242- return ! ! ( sourceBlock ?. isDeletable ( ) ) ;
242+ return ! ! sourceBlock ?. isDeletable ( ) ;
243243 }
244244
245245 /**
@@ -275,6 +275,67 @@ export class NavigationController {
275275 return true ;
276276 }
277277
278+ /**
279+ * Precondition function for copying a block from keyboard
280+ * navigation. This precondition is shared between keyboard shortcuts
281+ * and context menu items.
282+ *
283+ * FIXME: This should be better encapsulated.
284+ *
285+ * @param workspace The `WorkspaceSvg` where the shortcut was
286+ * invoked.
287+ * @returns True iff `deleteCallbackFn` function should be called.
288+ */
289+ protected blockCopyPreconditionFn ( workspace : WorkspaceSvg ) {
290+ if ( ! this . canCurrentlyEdit ( workspace ) ) return false ;
291+ switch ( this . navigation . getState ( workspace ) ) {
292+ case Constants . STATE . WORKSPACE :
293+ const curNode = workspace ?. getCursor ( ) ?. getCurNode ( ) ;
294+ const source = curNode ?. getSourceBlock ( ) ;
295+ return ! ! (
296+ source ?. isDeletable ( ) &&
297+ source ?. isMovable ( ) &&
298+ ! Blockly . Gesture . inProgress ( )
299+ ) ;
300+ case Constants . STATE . FLYOUT :
301+ const flyoutWorkspace = workspace . getFlyout ( ) ?. getWorkspace ( ) ;
302+ const sourceBlock = flyoutWorkspace
303+ ?. getCursor ( )
304+ ?. getCurNode ( )
305+ ?. getSourceBlock ( ) ;
306+ return ! ! ( sourceBlock && ! Blockly . Gesture . inProgress ( ) ) ;
307+ default :
308+ return false ;
309+ }
310+ }
311+
312+ /**
313+ * Callback function for copying a block from keyboard
314+ * navigation. This callback is shared between keyboard shortcuts
315+ * and context menu items.
316+ *
317+ * FIXME: This should be better encapsulated.
318+ *
319+ * @param workspace The `WorkspaceSvg` where the shortcut was
320+ * invoked.
321+ * @returns True if this function successfully handled copying.
322+ */
323+ protected blockCopyCallbackFn ( workspace : WorkspaceSvg ) {
324+ const navigationState = this . navigation . getState ( workspace ) ;
325+ let activeWorkspace : Blockly . WorkspaceSvg | undefined = workspace ;
326+ if ( navigationState == Constants . STATE . FLYOUT ) {
327+ activeWorkspace = workspace . getFlyout ( ) ?. getWorkspace ( ) ;
328+ }
329+ const sourceBlock = activeWorkspace
330+ ?. getCursor ( )
331+ ?. getCurNode ( )
332+ . getSourceBlock ( ) as BlockSvg ;
333+ workspace . hideChaff ( ) ;
334+ this . copyData = sourceBlock . toCopyData ( ) ;
335+ this . copyWorkspace = sourceBlock . workspace ;
336+ return ! ! this . copyData ;
337+ }
338+
278339 /**
279340 * List all the currently registered shortcuts.
280341 */
@@ -590,45 +651,8 @@ export class NavigationController {
590651 /** Copy the block the cursor is currently on. */
591652 copy : {
592653 name : Constants . SHORTCUT_NAMES . COPY ,
593- preconditionFn : ( workspace ) => {
594- if ( this . canCurrentlyEdit ( workspace ) ) {
595- switch ( this . navigation . getState ( workspace ) ) {
596- case Constants . STATE . WORKSPACE :
597- const curNode = workspace ?. getCursor ( ) ?. getCurNode ( ) ;
598- const source = curNode ?. getSourceBlock ( ) ;
599- return ! ! (
600- source ?. isDeletable ( ) &&
601- source ?. isMovable ( ) &&
602- ! Blockly . Gesture . inProgress ( )
603- ) ;
604- case Constants . STATE . FLYOUT :
605- const flyoutWorkspace = workspace . getFlyout ( ) ?. getWorkspace ( ) ;
606- const sourceBlock = flyoutWorkspace
607- ?. getCursor ( )
608- ?. getCurNode ( )
609- ?. getSourceBlock ( ) ;
610- return ! ! ( sourceBlock && ! Blockly . Gesture . inProgress ( ) ) ;
611- default :
612- return false ;
613- }
614- }
615- return false ;
616- } ,
617- callback : ( workspace ) => {
618- const navigationState = this . navigation . getState ( workspace ) ;
619- let activeWorkspace : Blockly . WorkspaceSvg | undefined = workspace ;
620- if ( navigationState == Constants . STATE . FLYOUT ) {
621- activeWorkspace = workspace . getFlyout ( ) ?. getWorkspace ( ) ;
622- }
623- const sourceBlock = activeWorkspace
624- ?. getCursor ( )
625- ?. getCurNode ( )
626- . getSourceBlock ( ) as BlockSvg ;
627- workspace . hideChaff ( ) ;
628- this . copyData = sourceBlock . toCopyData ( ) ;
629- this . copyWorkspace = sourceBlock . workspace ;
630- return ! ! this . copyData ;
631- } ,
654+ preconditionFn : this . blockCopyPreconditionFn ,
655+ callback : this . blockCopyCallbackFn ,
632656 keyCodes : [
633657 createSerializedKey ( KeyCodes . C , [ KeyCodes . CTRL ] ) ,
634658 createSerializedKey ( KeyCodes . C , [ KeyCodes . ALT ] ) ,
@@ -901,6 +925,31 @@ export class NavigationController {
901925 ContextMenuRegistry . registry . register ( deleteItem ) ;
902926 }
903927
928+ /**
929+ * Register the block copy action as a context menu item on blocks.
930+ */
931+ protected registerCopyAction ( ) {
932+ const copyAction : ContextMenuRegistry . RegistryItem = {
933+ displayText : ( scope ) => 'Keyboard Navigation: copy' ,
934+ preconditionFn : ( scope ) => {
935+ const ws = scope . block ?. workspace ;
936+ if ( ! ws ) return 'hidden' ;
937+
938+ return this . blockCopyPreconditionFn ( ws ) ? 'enabled' : 'disabled' ;
939+ } ,
940+ callback : ( scope ) => {
941+ const ws = scope . block ?. workspace ;
942+ if ( ! ws ) return ;
943+ return this . blockCopyCallbackFn ( ws ) ;
944+ } ,
945+ scopeType : ContextMenuRegistry . ScopeType . BLOCK ,
946+ id : 'blockCopyFromContextMenu' ,
947+ weight : 11 ,
948+ } ;
949+
950+ ContextMenuRegistry . registry . register ( copyAction ) ;
951+ }
952+
904953 /**
905954 * Registers all default keyboard shortcut items for keyboard
906955 * navigation. This should be called once per instance of
@@ -912,6 +961,7 @@ export class NavigationController {
912961 }
913962
914963 this . registerDeleteAction ( ) ;
964+ this . registerCopyAction ( ) ;
915965
916966 // Initalise the shortcut modal with available shortcuts. Needs
917967 // to be done separately rather at construction, as many shortcuts
0 commit comments