@@ -16,6 +16,7 @@ import * as Blockly from 'blockly/core';
1616import {
1717 ASTNode ,
1818 BlockSvg ,
19+ ContextMenuRegistry ,
1920 ICopyData ,
2021 ShortcutRegistry ,
2122 Toolbox ,
@@ -224,6 +225,56 @@ export class NavigationController {
224225 return false ;
225226 }
226227
228+ /**
229+ * Precondition function for deleting a block from keyboard
230+ * navigation. This precondition is shared between keyboard shortcuts
231+ * and context menu items.
232+ *
233+ * FIXME: This should be better encapsulated.
234+ *
235+ * @param workspace The `WorkspaceSvg` where the shortcut was
236+ * invoked.
237+ * @returns True iff `deleteCallbackFn` function should be called.
238+ */
239+ protected deletePreconditionFn ( workspace : WorkspaceSvg ) {
240+ if ( ! this . canCurrentlyEdit ( workspace ) ) return false ;
241+ const sourceBlock = workspace . getCursor ( ) ?. getCurNode ( ) . getSourceBlock ( ) ;
242+ return ! ! ( sourceBlock ?. isDeletable ( ) ) ;
243+ }
244+
245+ /**
246+ * Callback function for deleting a block from keyboard
247+ * navigation. This callback is shared between keyboard shortcuts
248+ * and context menu items.
249+ *
250+ * FIXME: This should be better encapsulated.
251+ *
252+ * @param workspace The `WorkspaceSvg` where the shortcut was
253+ * invoked.
254+ * @param e The originating event for a keyboard shortcut, or null
255+ * if called from a context menu.
256+ * @returns True if this function successfully handled deletion.
257+ */
258+ protected deleteCallbackFn ( workspace : WorkspaceSvg , e : Event | null ) {
259+ const cursor = workspace . getCursor ( ) ;
260+ if ( ! cursor ) return false ;
261+ const sourceBlock = cursor . getCurNode ( ) . getSourceBlock ( ) as BlockSvg ;
262+ // Delete or backspace.
263+ // There is an event if this is triggered from a keyboard shortcut,
264+ // but not if it's triggered from a context menu.
265+ if ( e ) {
266+ // Stop the browser from going back to the previous page.
267+ // Do this first to prevent an error in the delete code from resulting
268+ // in data loss.
269+ e . preventDefault ( ) ;
270+ }
271+ // Don't delete while dragging. Jeez.
272+ if ( Blockly . Gesture . inProgress ( ) ) false ;
273+ this . navigation . moveCursorOnBlockDelete ( workspace , sourceBlock ) ;
274+ sourceBlock . checkAndDelete ( ) ;
275+ return true ;
276+ }
277+
227278 /**
228279 * List all the currently registered shortcuts.
229280 */
@@ -404,7 +455,7 @@ export class NavigationController {
404455 * - On the workspace: open the context menu.
405456 */
406457 enter : {
407- name : Constants . SHORTCUT_NAMES . MARK , // FIXME
458+ name : Constants . SHORTCUT_NAMES . MARK , // FIXME
408459 preconditionFn : ( workspace ) => this . canCurrentlyEdit ( workspace ) ,
409460 callback : ( workspace ) => {
410461 let flyoutCursor ;
@@ -545,11 +596,11 @@ export class NavigationController {
545596 case Constants . STATE . WORKSPACE :
546597 const curNode = workspace ?. getCursor ( ) ?. getCurNode ( ) ;
547598 const source = curNode ?. getSourceBlock ( ) ;
548- return ! ! (
549- source ?. isDeletable ( ) &&
550- source ?. isMovable ( ) &&
551- ! Blockly . Gesture . inProgress ( )
552- ) ;
599+ return ! ! (
600+ source ?. isDeletable ( ) &&
601+ source ?. isMovable ( ) &&
602+ ! Blockly . Gesture . inProgress ( )
603+ ) ;
553604 case Constants . STATE . FLYOUT :
554605 const flyoutWorkspace = workspace . getFlyout ( ) ?. getWorkspace ( ) ;
555606 const sourceBlock = flyoutWorkspace
@@ -650,35 +701,8 @@ export class NavigationController {
650701 /** Keyboard shortcut to delete the block the cursor is currently on. */
651702 delete : {
652703 name : Constants . SHORTCUT_NAMES . DELETE ,
653- preconditionFn : ( workspace ) => {
654- if ( this . canCurrentlyEdit ( workspace ) ) {
655- const curNode = workspace . getCursor ( ) ?. getCurNode ( ) ;
656- if ( curNode && curNode . getSourceBlock ( ) ) {
657- const sourceBlock = curNode . getSourceBlock ( ) ;
658- return ! ! ( sourceBlock && sourceBlock . isDeletable ( ) ) ;
659- }
660- }
661- return false ;
662- } ,
663- callback : ( workspace , e ) => {
664- const cursor = workspace . getCursor ( ) ;
665- if ( ! cursor ) {
666- return false ;
667- }
668- const sourceBlock = cursor . getCurNode ( ) . getSourceBlock ( ) as BlockSvg ;
669- // Delete or backspace.
670- // Stop the browser from going back to the previous page.
671- // Do this first to prevent an error in the delete code from resulting
672- // in data loss.
673- e . preventDefault ( ) ;
674- // Don't delete while dragging. Jeez.
675- if ( Blockly . Gesture . inProgress ( ) ) {
676- return false ;
677- }
678- this . navigation . moveCursorOnBlockDelete ( workspace , sourceBlock ) ;
679- sourceBlock . checkAndDelete ( ) ;
680- return true ;
681- } ,
704+ preconditionFn : this . deletePreconditionFn ,
705+ callback : this . deleteCallbackFn ,
682706 keyCodes : [ KeyCodes . DELETE , KeyCodes . BACKSPACE ] ,
683707 allowCollision : true ,
684708 } ,
@@ -827,6 +851,56 @@ export class NavigationController {
827851 } ,
828852 } ;
829853
854+ /**
855+ * Register the delete block action as a context menu item on blocks.
856+ * This function mixes together the keyboard and context menu preconditions
857+ * but only calls the keyboard callback.
858+ */
859+ protected registerDeleteAction ( ) {
860+ const originalDeleteItem =
861+ ContextMenuRegistry . registry . getItem ( 'blockDelete' ) ;
862+ if ( ! originalDeleteItem ) return ;
863+
864+ const deleteItem : ContextMenuRegistry . RegistryItem = {
865+ displayText : ( scope ) => {
866+ // FIXME: Consider using the original delete item's display text,
867+ // which is dynamic based on the nubmer of blocks to delete.
868+ return 'Keyboard Navigation: delete' ;
869+ } ,
870+ preconditionFn : ( scope ) => {
871+ // FIXME: Find a better way to get the workspace, or use `as WorkspaceSvg`.
872+ const ws = scope . block ?. workspace ;
873+
874+ // Run the original precondition code, from the context menu option.
875+ // If the item would be hidden or disabled, respect it.
876+ const originalPreconditionResult =
877+ originalDeleteItem . preconditionFn ( scope ) ;
878+ if ( ! ws || originalPreconditionResult != 'enabled' ) {
879+ return originalPreconditionResult ;
880+ }
881+
882+ // Return enabled if the keyboard shortcut precondition is allowed,
883+ // and disabled if the context menu precondition is met but the keyboard
884+ // shortcut precondition is not met.
885+ return this . deletePreconditionFn ( ws ) ? 'enabled' : 'disabled' ;
886+ } ,
887+ callback : ( scope ) => {
888+ // FIXME: Find a better way to get the workspace, or use `as WorkspaceSvg`.
889+ const ws = scope . block ?. workspace ;
890+ if ( ! ws ) return ;
891+
892+ // Delete the block(s), and put the cursor back in a sane location.
893+ return this . deleteCallbackFn ( ws , null ) ;
894+ } ,
895+ scopeType : ContextMenuRegistry . ScopeType . BLOCK ,
896+ id : 'blockDeleteFromContextMenu' ,
897+ weight : 10 ,
898+ } ;
899+
900+ // FIXME: Decide whether to unregister the original item.
901+ ContextMenuRegistry . registry . register ( deleteItem ) ;
902+ }
903+
830904 /**
831905 * Registers all default keyboard shortcut items for keyboard
832906 * navigation. This should be called once per instance of
@@ -837,6 +911,8 @@ export class NavigationController {
837911 ShortcutRegistry . registry . register ( shortcut ) ;
838912 }
839913
914+ this . registerDeleteAction ( ) ;
915+
840916 // Initalise the shortcut modal with available shortcuts. Needs
841917 // to be done separately rather at construction, as many shortcuts
842918 // are not registered at that point.
0 commit comments