|
4 | 4 | * SPDX-License-Identifier: Apache-2.0
|
5 | 5 | */
|
6 | 6 |
|
7 |
| -import { |
8 |
| - ContextMenuRegistry, |
9 |
| - Gesture, |
10 |
| - Msg, |
11 |
| - ShortcutRegistry, |
12 |
| - utils as BlocklyUtils, |
13 |
| - LineCursor, |
14 |
| -} from 'blockly'; |
| 7 | +import {ContextMenuRegistry, Msg, ShortcutItems} from 'blockly'; |
15 | 8 | import {getShortActionShortcut} from '../shortcut_formatting';
|
16 |
| -import * as Constants from '../constants'; |
17 |
| -import type {WorkspaceSvg} from 'blockly'; |
18 |
| -import {Navigation} from '../navigation'; |
19 |
| - |
20 |
| -const KeyCodes = BlocklyUtils.KeyCodes; |
21 | 9 |
|
22 | 10 | /**
|
23 | 11 | * Action to delete the block the cursor is currently on.
|
24 |
| - * Registers itself as both a keyboard shortcut and a context menu item. |
25 | 12 | */
|
26 | 13 | export class DeleteAction {
|
27 | 14 | /**
|
28 |
| - * Saved context menu item, which is re-registered when this action |
29 |
| - * is uninstalled. |
30 |
| - */ |
31 |
| - private oldContextMenuItem: ContextMenuRegistry.RegistryItem | null = null; |
32 |
| - |
33 |
| - /** |
34 |
| - * Saved delete shortcut, which is re-registered when this action |
35 |
| - * is uninstalled. |
| 15 | + * Saved context menu item display text function, which is restored |
| 16 | + * when this action is uninstalled. |
36 | 17 | */
|
37 |
| - private oldDeleteShortcut: ShortcutRegistry.KeyboardShortcut | null = null; |
| 18 | + private oldDisplayText: |
| 19 | + | ((scope: ContextMenuRegistry.Scope) => string | HTMLElement) |
| 20 | + | string |
| 21 | + | HTMLElement |
| 22 | + | undefined = undefined; |
38 | 23 |
|
39 | 24 | /**
|
40 |
| - * Registration name for the keyboard shortcut. |
| 25 | + * Saved context menu item, which has its display text restored when |
| 26 | + * this action is uninstalled. |
41 | 27 | */
|
42 |
| - private deleteShortcutName = Constants.SHORTCUT_NAMES.DELETE; |
| 28 | + private oldContextMenuItem: ContextMenuRegistry.RegistryItem | null = null; |
43 | 29 |
|
44 |
| - constructor(private navigation: Navigation) {} |
| 30 | + constructor() {} |
45 | 31 |
|
46 | 32 | /**
|
47 | 33 | * Install this action as both a keyboard shortcut and a context menu item.
|
48 | 34 | */
|
49 | 35 | install() {
|
50 |
| - this.registerShortcut(); |
51 | 36 | this.registerContextMenuAction();
|
52 | 37 | }
|
53 | 38 |
|
54 | 39 | /**
|
55 |
| - * Uninstall this action as both a keyboard shortcut and a context menu item. |
56 |
| - * Reinstall the original context menu action if possible. |
| 40 | + * Reinstall the original context menu display text if possible. |
57 | 41 | */
|
58 | 42 | uninstall() {
|
59 |
| - ContextMenuRegistry.registry.unregister('blockDeleteFromContextMenu'); |
60 |
| - if (this.oldContextMenuItem) { |
61 |
| - ContextMenuRegistry.registry.register(this.oldContextMenuItem); |
62 |
| - } |
63 |
| - ShortcutRegistry.registry.unregister(this.deleteShortcutName); |
64 |
| - if (this.oldDeleteShortcut) { |
65 |
| - ShortcutRegistry.registry.register(this.oldDeleteShortcut); |
| 43 | + if (this.oldContextMenuItem && this.oldDisplayText) { |
| 44 | + this.oldContextMenuItem.displayText = this.oldDisplayText; |
66 | 45 | }
|
67 | 46 | }
|
68 | 47 |
|
69 | 48 | /**
|
70 |
| - * Create and register the keyboard shortcut for this action. |
71 |
| - */ |
72 |
| - private registerShortcut() { |
73 |
| - this.oldDeleteShortcut = ShortcutRegistry.registry.getRegistry()['delete']; |
74 |
| - |
75 |
| - if (!this.oldDeleteShortcut) return; |
76 |
| - |
77 |
| - // Unregister the original shortcut. |
78 |
| - ShortcutRegistry.registry.unregister(this.oldDeleteShortcut.name); |
79 |
| - |
80 |
| - const deleteShortcut: ShortcutRegistry.KeyboardShortcut = { |
81 |
| - name: this.deleteShortcutName, |
82 |
| - preconditionFn: this.deletePrecondition.bind(this), |
83 |
| - callback: this.deleteCallback.bind(this), |
84 |
| - keyCodes: [KeyCodes.DELETE, KeyCodes.BACKSPACE], |
85 |
| - allowCollision: true, |
86 |
| - }; |
87 |
| - |
88 |
| - ShortcutRegistry.registry.register(deleteShortcut); |
89 |
| - } |
90 |
| - |
91 |
| - /** |
92 |
| - * Register the delete block action as a context menu item on blocks. |
93 |
| - * This function mixes together the keyboard and context menu preconditions |
94 |
| - * but only calls the keyboard callback. |
| 49 | + * Updates the text of the context menu delete action to include |
| 50 | + * the keyboard shortcut. |
95 | 51 | */
|
96 | 52 | private registerContextMenuAction() {
|
97 | 53 | this.oldContextMenuItem =
|
98 | 54 | ContextMenuRegistry.registry.getItem('blockDelete');
|
99 | 55 |
|
100 | 56 | if (!this.oldContextMenuItem) return;
|
101 | 57 |
|
102 |
| - // Unregister the original item. |
103 |
| - ContextMenuRegistry.registry.unregister(this.oldContextMenuItem.id); |
104 |
| - |
105 |
| - const deleteItem: ContextMenuRegistry.RegistryItem = { |
106 |
| - displayText: (scope) => { |
107 |
| - const shortcut = getShortActionShortcut(this.deleteShortcutName); |
108 |
| - if (!this.oldContextMenuItem) { |
109 |
| - return Msg['DELETE_BLOCK'].replace('%1', shortcut); |
110 |
| - } |
| 58 | + this.oldDisplayText = this.oldContextMenuItem.displayText; |
111 | 59 |
|
112 |
| - type DisplayTextFn = (p1: ContextMenuRegistry.Scope) => string; |
113 |
| - // Use the original item's text, which is dynamic based on the number |
114 |
| - // of blocks that will be deleted. |
115 |
| - const oldDisplayText = this.oldContextMenuItem |
116 |
| - .displayText as DisplayTextFn; |
117 |
| - return oldDisplayText(scope) + ` (${shortcut})`; |
118 |
| - }, |
119 |
| - preconditionFn: (scope, menuOpenEvent: Event) => { |
120 |
| - const ws = scope.block?.workspace; |
| 60 | + const displayText = (scope: ContextMenuRegistry.Scope) => { |
| 61 | + const shortcut = getShortActionShortcut(ShortcutItems.names.DELETE); |
121 | 62 |
|
122 |
| - // Run the original precondition code, from the context menu option. |
123 |
| - // If the item would be hidden or disabled, respect it. |
124 |
| - const originalPreconditionResult = |
125 |
| - this.oldContextMenuItem?.preconditionFn?.(scope, menuOpenEvent) ?? |
126 |
| - 'enabled'; |
127 |
| - if (!ws || originalPreconditionResult !== 'enabled') { |
128 |
| - return originalPreconditionResult; |
129 |
| - } |
| 63 | + // Use the original item's text, which is dynamic based on the number |
| 64 | + // of blocks that will be deleted. |
| 65 | + if (typeof this.oldDisplayText === 'function') { |
| 66 | + return this.oldDisplayText(scope) + ` (${shortcut})`; |
| 67 | + } else if (typeof this.oldDisplayText === 'string') { |
| 68 | + return this.oldDisplayText + ` (${shortcut})`; |
| 69 | + } |
130 | 70 |
|
131 |
| - // Return enabled if the keyboard shortcut precondition is allowed, |
132 |
| - // and disabled if the context menu precondition is met but the keyboard |
133 |
| - // shortcut precondition is not met. |
134 |
| - return this.deletePrecondition(ws) ? 'enabled' : 'disabled'; |
135 |
| - }, |
136 |
| - callback: (scope) => { |
137 |
| - const ws = scope.block?.workspace; |
138 |
| - if (!ws) return; |
139 |
| - |
140 |
| - // Delete the block(s), and put the cursor back in a sane location. |
141 |
| - return this.deleteCallback(ws, null); |
142 |
| - }, |
143 |
| - scopeType: ContextMenuRegistry.ScopeType.BLOCK, |
144 |
| - id: 'blockDeleteFromContextMenu', |
145 |
| - weight: 11, |
| 71 | + return Msg['DELETE_BLOCK'].replace('%1', shortcut); |
146 | 72 | };
|
147 | 73 |
|
148 |
| - ContextMenuRegistry.registry.register(deleteItem); |
149 |
| - } |
150 |
| - |
151 |
| - /** |
152 |
| - * Precondition function for deleting a block from keyboard |
153 |
| - * navigation. This precondition is shared between keyboard shortcuts |
154 |
| - * and context menu items. |
155 |
| - * |
156 |
| - * @param workspace The `WorkspaceSvg` where the shortcut was |
157 |
| - * invoked. |
158 |
| - * @returns True iff `deleteCallback` function should be called. |
159 |
| - */ |
160 |
| - private deletePrecondition(workspace: WorkspaceSvg) { |
161 |
| - const sourceBlock = workspace.getCursor()?.getSourceBlock(); |
162 |
| - return ( |
163 |
| - !workspace.isDragging() && |
164 |
| - this.navigation.canCurrentlyEdit(workspace) && |
165 |
| - !!sourceBlock?.isDeletable() |
166 |
| - ); |
167 |
| - } |
168 |
| - |
169 |
| - /** |
170 |
| - * Callback function for deleting a block from keyboard |
171 |
| - * navigation. This callback is shared between keyboard shortcuts |
172 |
| - * and context menu items. |
173 |
| - * |
174 |
| - * @param workspace The `WorkspaceSvg` where the shortcut was |
175 |
| - * invoked. |
176 |
| - * @param e The originating event for a keyboard shortcut, or null |
177 |
| - * if called from a context menu. |
178 |
| - * @returns True if this function successfully handled deletion. |
179 |
| - */ |
180 |
| - private deleteCallback(workspace: WorkspaceSvg, e: Event | null) { |
181 |
| - const cursor = workspace.getCursor(); |
182 |
| - if (!cursor) return false; |
183 |
| - |
184 |
| - const sourceBlock = cursor.getSourceBlock(); |
185 |
| - if (!sourceBlock) return false; |
186 |
| - // Delete or backspace. |
187 |
| - // There is an event if this is triggered from a keyboard shortcut, |
188 |
| - // but not if it's triggered from a context menu. |
189 |
| - if (e) { |
190 |
| - // Stop the browser from going back to the previous page. |
191 |
| - // Do this first to prevent an error in the delete code from resulting |
192 |
| - // in data loss. |
193 |
| - e.preventDefault(); |
194 |
| - } |
195 |
| - // Don't delete while dragging. Jeez. |
196 |
| - if (Gesture.inProgress()) false; |
197 |
| - |
198 |
| - if (cursor instanceof LineCursor) cursor.preDelete(sourceBlock); |
199 |
| - sourceBlock.checkAndDelete(); |
200 |
| - if (cursor instanceof LineCursor) cursor.postDelete(); |
201 |
| - return true; |
| 74 | + this.oldContextMenuItem.displayText = displayText; |
202 | 75 | }
|
203 | 76 | }
|
0 commit comments