|
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