From f1148f1732c523a26be30a38c5d39d4302112004 Mon Sep 17 00:00:00 2001 From: Rachel Fenichel Date: Fri, 18 Feb 2022 11:36:41 -0800 Subject: [PATCH] refactor: convert registry files to es6 classes (#5940) * refactor: convert shortcut registry to es6 class * refactor: convert context menu registry to es6 class * refactor: convert registry type to es6 class * chore: format * chore: respond to PR comments --- core/contextmenu_registry.js | 183 +++++---- core/registry.js | 35 +- core/shortcut_registry.js | 564 +++++++++++++------------- tests/mocha/contextmenu_items_test.js | 5 +- tests/mocha/shortcut_registry_test.js | 3 +- 5 files changed, 402 insertions(+), 388 deletions(-) diff --git a/core/contextmenu_registry.js b/core/contextmenu_registry.js index 91a015c3b50..1fe8f9c97a9 100644 --- a/core/contextmenu_registry.js +++ b/core/contextmenu_registry.js @@ -25,21 +25,101 @@ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); * Class for the registry of context menu items. This is intended to be a * singleton. You should not create a new instance, and only access this class * from ContextMenuRegistry.registry. - * @constructor - * @private - * @alias Blockly.ContextMenuRegistry */ -const ContextMenuRegistry = function() { - // Singleton instance should be registered once. - ContextMenuRegistry.registry = this; +class ContextMenuRegistry { + /** + * @alias Blockly.ContextMenuRegistry + */ + constructor() { + this.reset(); + } /** - * Registry of all registered RegistryItems, keyed by ID. - * @type {!Object} - * @private + * Clear and recreate the registry. */ - this.registry_ = Object.create(null); -}; + reset() { + /** + * Registry of all registered RegistryItems, keyed by ID. + * @type {!Object} + * @private + */ + this.registry_ = Object.create(null); + } + + /** + * Registers a RegistryItem. + * @param {!ContextMenuRegistry.RegistryItem} item Context menu item to + * register. + * @throws {Error} if an item with the given ID already exists. + */ + register(item) { + if (this.registry_[item.id]) { + throw Error('Menu item with ID "' + item.id + '" is already registered.'); + } + this.registry_[item.id] = item; + } + + /** + * Unregisters a RegistryItem with the given ID. + * @param {string} id The ID of the RegistryItem to remove. + * @throws {Error} if an item with the given ID does not exist. + */ + unregister(id) { + if (!this.registry_[id]) { + throw new Error('Menu item with ID "' + id + '" not found.'); + } + delete this.registry_[id]; + } + + /** + * @param {string} id The ID of the RegistryItem to get. + * @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not + * found + */ + getItem(id) { + return this.registry_[id] || null; + } + + /** + * Gets the valid context menu options for the given scope type (e.g. block or + * workspace) and scope. Blocks are only shown if the preconditionFn shows + * they should not be hidden. + * @param {!ContextMenuRegistry.ScopeType} scopeType Type of scope where menu + * should be shown (e.g. on a block or on a workspace) + * @param {!ContextMenuRegistry.Scope} scope Current scope of context menu + * (i.e., the exact workspace or block being clicked on) + * @return {!Array} the list of + * ContextMenuOptions + */ + getContextMenuOptions(scopeType, scope) { + const menuOptions = []; + const registry = this.registry_; + Object.keys(registry).forEach(function(id) { + const item = registry[id]; + if (scopeType === item.scopeType) { + const precondition = item.preconditionFn(scope); + if (precondition !== 'hidden') { + const displayText = typeof item.displayText === 'function' ? + item.displayText(scope) : + item.displayText; + /** @type {!ContextMenuRegistry.ContextMenuOption} */ + const menuOption = { + text: displayText, + enabled: (precondition === 'enabled'), + callback: item.callback, + scope: scope, + weight: item.weight, + }; + menuOptions.push(menuOption); + } + } + }); + menuOptions.sort(function(a, b) { + return a.weight - b.weight; + }); + return menuOptions; + } +} /** * Where this menu item should be rendered. If the menu item should be rendered @@ -90,85 +170,8 @@ ContextMenuRegistry.ContextMenuOption; /** * Singleton instance of this class. All interactions with this class should be * done on this object. - * @type {?ContextMenuRegistry} - */ -ContextMenuRegistry.registry = null; - -/** - * Registers a RegistryItem. - * @param {!ContextMenuRegistry.RegistryItem} item Context menu item to - * register. - * @throws {Error} if an item with the given ID already exists. - */ -ContextMenuRegistry.prototype.register = function(item) { - if (this.registry_[item.id]) { - throw Error('Menu item with ID "' + item.id + '" is already registered.'); - } - this.registry_[item.id] = item; -}; - -/** - * Unregisters a RegistryItem with the given ID. - * @param {string} id The ID of the RegistryItem to remove. - * @throws {Error} if an item with the given ID does not exist. - */ -ContextMenuRegistry.prototype.unregister = function(id) { - if (!this.registry_[id]) { - throw new Error('Menu item with ID "' + id + '" not found.'); - } - delete this.registry_[id]; -}; - -/** - * @param {string} id The ID of the RegistryItem to get. - * @return {?ContextMenuRegistry.RegistryItem} RegistryItem or null if not found + * @type {!ContextMenuRegistry} */ -ContextMenuRegistry.prototype.getItem = function(id) { - return this.registry_[id] || null; -}; - -/** - * Gets the valid context menu options for the given scope type (e.g. block or - * workspace) and scope. Blocks are only shown if the preconditionFn shows they - * should not be hidden. - * @param {!ContextMenuRegistry.ScopeType} scopeType Type of scope where menu - * should be shown (e.g. on a block or on a workspace) - * @param {!ContextMenuRegistry.Scope} scope Current scope of context menu - * (i.e., the exact workspace or block being clicked on) - * @return {!Array} the list of - * ContextMenuOptions - */ -ContextMenuRegistry.prototype.getContextMenuOptions = function( - scopeType, scope) { - const menuOptions = []; - const registry = this.registry_; - Object.keys(registry).forEach(function(id) { - const item = registry[id]; - if (scopeType === item.scopeType) { - const precondition = item.preconditionFn(scope); - if (precondition !== 'hidden') { - const displayText = typeof item.displayText === 'function' ? - item.displayText(scope) : - item.displayText; - /** @type {!ContextMenuRegistry.ContextMenuOption} */ - const menuOption = { - text: displayText, - enabled: (precondition === 'enabled'), - callback: item.callback, - scope: scope, - weight: item.weight, - }; - menuOptions.push(menuOption); - } - } - }); - menuOptions.sort(function(a, b) { - return a.weight - b.weight; - }); - return menuOptions; -}; - -// Creates and assigns the singleton instance. -new ContextMenuRegistry(); +ContextMenuRegistry.registry = new ContextMenuRegistry(); exports.ContextMenuRegistry = ContextMenuRegistry; diff --git a/core/registry.js b/core/registry.js index 4b80d2560d5..85b36bd7d10 100644 --- a/core/registry.js +++ b/core/registry.js @@ -73,28 +73,31 @@ exports.DEFAULT = DEFAULT; /** * A name with the type of the element stored in the generic. - * @param {string} name The name of the registry type. - * @constructor * @template T - * @alias Blockly.registry.Type */ -const Type = function(name) { +class Type { /** - * @type {string} - * @private + * @param {string} name The name of the registry type. + * @alias Blockly.registry.Type */ - this.name_ = name; -}; + constructor(name) { + /** + * @type {string} + * @private + */ + this.name_ = name; + } + + /** + * Returns the name of the type. + * @return {string} The name. + */ + toString() { + return this.name_; + } +} exports.Type = Type; -/** - * Returns the name of the type. - * @return {string} The name. - * @override - */ -Type.prototype.toString = function() { - return this.name_; -}; /** @type {!Type} */ Type.CONNECTION_CHECKER = new Type('connectionChecker'); diff --git a/core/shortcut_registry.js b/core/shortcut_registry.js index 4fcec3f7cbf..e5147f93477 100644 --- a/core/shortcut_registry.js +++ b/core/shortcut_registry.js @@ -27,331 +27,339 @@ const {Workspace} = goog.requireType('Blockly.Workspace'); * Class for the registry of keyboard shortcuts. This is intended to be a * singleton. You should not create a new instance, and only access this class * from ShortcutRegistry.registry. - * @constructor - * @alias Blockly.ShortcutRegistry */ -const ShortcutRegistry = function() { - // Singleton instance should be registered once. - ShortcutRegistry.registry = this; - +class ShortcutRegistry { /** - * Registry of all keyboard shortcuts, keyed by name of shortcut. - * @type {!Object} - * @private + * @alias Blockly.ShortcutRegistry */ - this.registry_ = Object.create(null); + constructor() { + this.reset(); + } /** - * Map of key codes to an array of shortcut names. - * @type {!Object>} - * @private + * Clear and recreate the registry and keyMap. */ - this.keyMap_ = Object.create(null); -}; - -/** - * Enum of valid modifiers. - * @enum {!KeyCodes} - */ -ShortcutRegistry.modifierKeys = { - 'Shift': KeyCodes.SHIFT, - 'Control': KeyCodes.CTRL, - 'Alt': KeyCodes.ALT, - 'Meta': KeyCodes.META, -}; - -/** - * A keyboard shortcut. - * @typedef {{ - * callback: ((function(!Workspace, Event, - * !ShortcutRegistry.KeyboardShortcut):boolean)|undefined), - * name: string, - * preconditionFn: ((function(!Workspace):boolean)|undefined), - * metadata: (Object|undefined) - * }} - */ -ShortcutRegistry.KeyboardShortcut; - -/** - * Registers a keyboard shortcut. - * @param {!ShortcutRegistry.KeyboardShortcut} shortcut The - * shortcut for this key code. - * @param {boolean=} opt_allowOverrides True to prevent a warning when - * overriding an already registered item. - * @throws {Error} if a shortcut with the same name already exists. - * @public - */ -ShortcutRegistry.prototype.register = function(shortcut, opt_allowOverrides) { - const registeredShortcut = this.registry_[shortcut.name]; - if (registeredShortcut && !opt_allowOverrides) { - throw new Error( - 'Shortcut with name "' + shortcut.name + '" already exists.'); + reset() { + /** + * Registry of all keyboard shortcuts, keyed by name of shortcut. + * @type {!Object} + * @private + */ + this.registry_ = Object.create(null); + + /** + * Map of key codes to an array of shortcut names. + * @type {!Object>} + * @private + */ + this.keyMap_ = Object.create(null); } - this.registry_[shortcut.name] = shortcut; -}; -/** - * Unregisters a keyboard shortcut registered with the given key code. This will - * also remove any key mappings that reference this shortcut. - * @param {string} shortcutName The name of the shortcut to unregister. - * @return {boolean} True if an item was unregistered, false otherwise. - * @public - */ -ShortcutRegistry.prototype.unregister = function(shortcutName) { - const shortcut = this.registry_[shortcutName]; - - if (!shortcut) { - console.warn( - 'Keyboard shortcut with name "' + shortcutName + '" not found.'); - return false; + /** + * Registers a keyboard shortcut. + * @param {!ShortcutRegistry.KeyboardShortcut} shortcut The + * shortcut for this key code. + * @param {boolean=} opt_allowOverrides True to prevent a warning when + * overriding an already registered item. + * @throws {Error} if a shortcut with the same name already exists. + * @public + */ + register(shortcut, opt_allowOverrides) { + const registeredShortcut = this.registry_[shortcut.name]; + if (registeredShortcut && !opt_allowOverrides) { + throw new Error( + 'Shortcut with name "' + shortcut.name + '" already exists.'); + } + this.registry_[shortcut.name] = shortcut; } - this.removeAllKeyMappings(shortcutName); - - delete this.registry_[shortcutName]; - return true; -}; + /** + * Unregisters a keyboard shortcut registered with the given key code. This + * will also remove any key mappings that reference this shortcut. + * @param {string} shortcutName The name of the shortcut to unregister. + * @return {boolean} True if an item was unregistered, false otherwise. + * @public + */ + unregister(shortcutName) { + const shortcut = this.registry_[shortcutName]; -/** - * Adds a mapping between a keycode and a keyboard shortcut. - * @param {string|KeyCodes} keyCode The key code for the keyboard - * shortcut. If registering a key code with a modifier (ex: ctrl+c) use - * ShortcutRegistry.registry.createSerializedKey; - * @param {string} shortcutName The name of the shortcut to execute when the - * given keycode is pressed. - * @param {boolean=} opt_allowCollision True to prevent an error when adding a - * shortcut to a key that is already mapped to a shortcut. - * @throws {Error} if the given key code is already mapped to a shortcut. - * @public - */ -ShortcutRegistry.prototype.addKeyMapping = function( - keyCode, shortcutName, opt_allowCollision) { - keyCode = String(keyCode); - const shortcutNames = this.keyMap_[keyCode]; - if (shortcutNames && !opt_allowCollision) { - throw new Error( - 'Shortcut with name "' + shortcutName + '" collides with shortcuts ' + - shortcutNames.toString()); - } else if (shortcutNames && opt_allowCollision) { - shortcutNames.unshift(shortcutName); - } else { - this.keyMap_[keyCode] = [shortcutName]; - } -}; + if (!shortcut) { + console.warn( + 'Keyboard shortcut with name "' + shortcutName + '" not found.'); + return false; + } -/** - * Removes a mapping between a keycode and a keyboard shortcut. - * @param {string} keyCode The key code for the keyboard shortcut. If - * registering a key code with a modifier (ex: ctrl+c) use - * ShortcutRegistry.registry.createSerializedKey; - * @param {string} shortcutName The name of the shortcut to execute when the - * given keycode is pressed. - * @param {boolean=} opt_quiet True to not console warn when there is no - * shortcut to remove. - * @return {boolean} True if a key mapping was removed, false otherwise. - * @public - */ -ShortcutRegistry.prototype.removeKeyMapping = function( - keyCode, shortcutName, opt_quiet) { - const shortcutNames = this.keyMap_[keyCode]; - - if (!shortcutNames && !opt_quiet) { - console.warn( - 'No keyboard shortcut with name "' + shortcutName + - '" registered with key code "' + keyCode + '"'); - return false; - } + this.removeAllKeyMappings(shortcutName); - const shortcutIdx = shortcutNames.indexOf(shortcutName); - if (shortcutIdx > -1) { - shortcutNames.splice(shortcutIdx, 1); - if (shortcutNames.length === 0) { - delete this.keyMap_[keyCode]; - } + delete this.registry_[shortcutName]; return true; } - if (!opt_quiet) { - console.warn( - 'No keyboard shortcut with name "' + shortcutName + - '" registered with key code "' + keyCode + '"'); - } - return false; -}; -/** - * Removes all the key mappings for a shortcut with the given name. - * Useful when changing the default key mappings and the key codes registered to - * the shortcut are unknown. - * @param {string} shortcutName The name of the shortcut to remove from the key - * map. - * @public - */ -ShortcutRegistry.prototype.removeAllKeyMappings = function(shortcutName) { - for (const keyCode in this.keyMap_) { - this.removeKeyMapping(keyCode, shortcutName, true); + /** + * Adds a mapping between a keycode and a keyboard shortcut. + * @param {string|KeyCodes} keyCode The key code for the keyboard + * shortcut. If registering a key code with a modifier (ex: ctrl+c) use + * ShortcutRegistry.registry.createSerializedKey; + * @param {string} shortcutName The name of the shortcut to execute when the + * given keycode is pressed. + * @param {boolean=} opt_allowCollision True to prevent an error when adding a + * shortcut to a key that is already mapped to a shortcut. + * @throws {Error} if the given key code is already mapped to a shortcut. + * @public + */ + addKeyMapping(keyCode, shortcutName, opt_allowCollision) { + keyCode = String(keyCode); + const shortcutNames = this.keyMap_[keyCode]; + if (shortcutNames && !opt_allowCollision) { + throw new Error( + 'Shortcut with name "' + shortcutName + '" collides with shortcuts ' + + shortcutNames.toString()); + } else if (shortcutNames && opt_allowCollision) { + shortcutNames.unshift(shortcutName); + } else { + this.keyMap_[keyCode] = [shortcutName]; + } } -}; -/** - * Sets the key map. Setting the key map will override any default key mappings. - * @param {!Object>} keyMap The object with key code to - * shortcut names. - * @public - */ -ShortcutRegistry.prototype.setKeyMap = function(keyMap) { - this.keyMap_ = keyMap; -}; - -/** - * Gets the current key map. - * @return {!Object>} - * The object holding key codes to ShortcutRegistry.KeyboardShortcut. - * @public - */ -ShortcutRegistry.prototype.getKeyMap = function() { - return object.deepMerge(Object.create(null), this.keyMap_); -}; - -/** - * Gets the registry of keyboard shortcuts. - * @return {!Object} - * The registry of keyboard shortcuts. - * @public - */ -ShortcutRegistry.prototype.getRegistry = function() { - return object.deepMerge(Object.create(null), this.registry_); -}; + /** + * Removes a mapping between a keycode and a keyboard shortcut. + * @param {string} keyCode The key code for the keyboard shortcut. If + * registering a key code with a modifier (ex: ctrl+c) use + * ShortcutRegistry.registry.createSerializedKey; + * @param {string} shortcutName The name of the shortcut to execute when the + * given keycode is pressed. + * @param {boolean=} opt_quiet True to not console warn when there is no + * shortcut to remove. + * @return {boolean} True if a key mapping was removed, false otherwise. + * @public + */ + removeKeyMapping(keyCode, shortcutName, opt_quiet) { + const shortcutNames = this.keyMap_[keyCode]; + + if (!shortcutNames && !opt_quiet) { + console.warn( + 'No keyboard shortcut with name "' + shortcutName + + '" registered with key code "' + keyCode + '"'); + return false; + } -/** - * Handles key down events. - * @param {!Workspace} workspace The main workspace where the event was - * captured. - * @param {!Event} e The key down event. - * @return {boolean} True if the event was handled, false otherwise. - * @public - */ -ShortcutRegistry.prototype.onKeyDown = function(workspace, e) { - const key = this.serializeKeyEvent_(e); - const shortcutNames = this.getShortcutNamesByKeyCode(key); - if (!shortcutNames) { + const shortcutIdx = shortcutNames.indexOf(shortcutName); + if (shortcutIdx > -1) { + shortcutNames.splice(shortcutIdx, 1); + if (shortcutNames.length === 0) { + delete this.keyMap_[keyCode]; + } + return true; + } + if (!opt_quiet) { + console.warn( + 'No keyboard shortcut with name "' + shortcutName + + '" registered with key code "' + keyCode + '"'); + } return false; } - for (let i = 0, shortcutName; (shortcutName = shortcutNames[i]); i++) { - const shortcut = this.registry_[shortcutName]; - if (!shortcut.preconditionFn || shortcut.preconditionFn(workspace)) { - // If the key has been handled, stop processing shortcuts. - if (shortcut.callback && shortcut.callback(workspace, e, shortcut)) { - return true; - } + + /** + * Removes all the key mappings for a shortcut with the given name. + * Useful when changing the default key mappings and the key codes registered + * to the shortcut are unknown. + * @param {string} shortcutName The name of the shortcut to remove from the + * key map. + * @public + */ + removeAllKeyMappings(shortcutName) { + for (const keyCode in this.keyMap_) { + this.removeKeyMapping(keyCode, shortcutName, true); } } - return false; -}; -/** - * Gets the shortcuts registered to the given key code. - * @param {string} keyCode The serialized key code. - * @return {!Array|undefined} The list of shortcuts to call when the - * given keyCode is used. Undefined if no shortcuts exist. - * @public - */ -ShortcutRegistry.prototype.getShortcutNamesByKeyCode = function(keyCode) { - return this.keyMap_[keyCode] || []; -}; + /** + * Sets the key map. Setting the key map will override any default key + * mappings. + * @param {!Object>} keyMap The object with key code to + * shortcut names. + * @public + */ + setKeyMap(keyMap) { + this.keyMap_ = keyMap; + } -/** - * Gets the serialized key codes that the shortcut with the given name is - * registered under. - * @param {string} shortcutName The name of the shortcut. - * @return {!Array} An array with all the key codes the shortcut is - * registered under. - * @public - */ -ShortcutRegistry.prototype.getKeyCodesByShortcutName = function(shortcutName) { - const keys = []; - for (const keyCode in this.keyMap_) { - const shortcuts = this.keyMap_[keyCode]; - const shortcutIdx = shortcuts.indexOf(shortcutName); - if (shortcutIdx > -1) { - keys.push(keyCode); - } + /** + * Gets the current key map. + * @return {!Object>} + * The object holding key codes to ShortcutRegistry.KeyboardShortcut. + * @public + */ + getKeyMap() { + return object.deepMerge(Object.create(null), this.keyMap_); } - return keys; -}; -/** - * Serializes a key event. - * @param {!Event} e A key down event. - * @return {string} The serialized key code for the given event. - * @private - */ -ShortcutRegistry.prototype.serializeKeyEvent_ = function(e) { - let serializedKey = ''; - for (const modifier in ShortcutRegistry.modifierKeys) { - if (e.getModifierState(modifier)) { - if (serializedKey !== '') { - serializedKey += '+'; + /** + * Gets the registry of keyboard shortcuts. + * @return {!Object} + * The registry of keyboard shortcuts. + * @public + */ + getRegistry() { + return object.deepMerge(Object.create(null), this.registry_); + } + + /** + * Handles key down events. + * @param {!Workspace} workspace The main workspace where the event was + * captured. + * @param {!Event} e The key down event. + * @return {boolean} True if the event was handled, false otherwise. + * @public + */ + onKeyDown(workspace, e) { + const key = this.serializeKeyEvent_(e); + const shortcutNames = this.getShortcutNamesByKeyCode(key); + if (!shortcutNames) { + return false; + } + for (let i = 0, shortcutName; (shortcutName = shortcutNames[i]); i++) { + const shortcut = this.registry_[shortcutName]; + if (!shortcut.preconditionFn || shortcut.preconditionFn(workspace)) { + // If the key has been handled, stop processing shortcuts. + if (shortcut.callback && shortcut.callback(workspace, e, shortcut)) { + return true; + } } - serializedKey += modifier; } + return false; } - if (serializedKey !== '' && e.keyCode) { - serializedKey = serializedKey + '+' + e.keyCode; - } else if (e.keyCode) { - serializedKey = e.keyCode.toString(); + + /** + * Gets the shortcuts registered to the given key code. + * @param {string} keyCode The serialized key code. + * @return {!Array|undefined} The list of shortcuts to call when the + * given keyCode is used. Undefined if no shortcuts exist. + * @public + */ + getShortcutNamesByKeyCode(keyCode) { + return this.keyMap_[keyCode] || []; } - return serializedKey; -}; -/** - * Checks whether any of the given modifiers are not valid. - * @param {!Array} modifiers List of modifiers to be used with the key. - * @throws {Error} if the modifier is not in the valid modifiers list. - * @private - */ -ShortcutRegistry.prototype.checkModifiers_ = function(modifiers) { - const validModifiers = object.values(ShortcutRegistry.modifierKeys); - for (let i = 0, modifier; (modifier = modifiers[i]); i++) { - if (validModifiers.indexOf(modifier) < 0) { - throw new Error(modifier + ' is not a valid modifier key.'); + /** + * Gets the serialized key codes that the shortcut with the given name is + * registered under. + * @param {string} shortcutName The name of the shortcut. + * @return {!Array} An array with all the key codes the shortcut is + * registered under. + * @public + */ + getKeyCodesByShortcutName(shortcutName) { + const keys = []; + for (const keyCode in this.keyMap_) { + const shortcuts = this.keyMap_[keyCode]; + const shortcutIdx = shortcuts.indexOf(shortcutName); + if (shortcutIdx > -1) { + keys.push(keyCode); + } } + return keys; } -}; - -/** - * Creates the serialized key code that will be used in the key map. - * @param {number} keyCode Number code representing the key. - * @param {?Array} modifiers List of modifier key codes to be used with - * the key. All valid modifiers can be found in the - * ShortcutRegistry.modifierKeys. - * @return {string} The serialized key code for the given modifiers and key. - * @public - */ -ShortcutRegistry.prototype.createSerializedKey = function(keyCode, modifiers) { - let serializedKey = ''; - if (modifiers) { - this.checkModifiers_(modifiers); + /** + * Serializes a key event. + * @param {!Event} e A key down event. + * @return {string} The serialized key code for the given event. + * @private + */ + serializeKeyEvent_(e) { + let serializedKey = ''; for (const modifier in ShortcutRegistry.modifierKeys) { - const modifierKeyCode = ShortcutRegistry.modifierKeys[modifier]; - if (modifiers.indexOf(modifierKeyCode) > -1) { + if (e.getModifierState(modifier)) { if (serializedKey !== '') { serializedKey += '+'; } serializedKey += modifier; } } + if (serializedKey !== '' && e.keyCode) { + serializedKey = serializedKey + '+' + e.keyCode; + } else if (e.keyCode) { + serializedKey = e.keyCode.toString(); + } + return serializedKey; } - if (serializedKey !== '' && keyCode) { - serializedKey = serializedKey + '+' + keyCode; - } else if (keyCode) { - serializedKey = keyCode.toString(); + /** + * Checks whether any of the given modifiers are not valid. + * @param {!Array} modifiers List of modifiers to be used with the + * key. + * @throws {Error} if the modifier is not in the valid modifiers list. + * @private + */ + checkModifiers_(modifiers) { + const validModifiers = object.values(ShortcutRegistry.modifierKeys); + for (let i = 0, modifier; (modifier = modifiers[i]); i++) { + if (validModifiers.indexOf(modifier) < 0) { + throw new Error(modifier + ' is not a valid modifier key.'); + } + } } - return serializedKey; + + /** + * Creates the serialized key code that will be used in the key map. + * @param {number} keyCode Number code representing the key. + * @param {?Array} modifiers List of modifier key codes to be used + * with the key. All valid modifiers can be found in the + * ShortcutRegistry.modifierKeys. + * @return {string} The serialized key code for the given modifiers and key. + * @public + */ + createSerializedKey(keyCode, modifiers) { + let serializedKey = ''; + + if (modifiers) { + this.checkModifiers_(modifiers); + for (const modifier in ShortcutRegistry.modifierKeys) { + const modifierKeyCode = ShortcutRegistry.modifierKeys[modifier]; + if (modifiers.indexOf(modifierKeyCode) > -1) { + if (serializedKey !== '') { + serializedKey += '+'; + } + serializedKey += modifier; + } + } + } + + if (serializedKey !== '' && keyCode) { + serializedKey = serializedKey + '+' + keyCode; + } else if (keyCode) { + serializedKey = keyCode.toString(); + } + return serializedKey; + } +} + +/** + * Enum of valid modifiers. + * @enum {!KeyCodes} + */ +ShortcutRegistry.modifierKeys = { + 'Shift': KeyCodes.SHIFT, + 'Control': KeyCodes.CTRL, + 'Alt': KeyCodes.ALT, + 'Meta': KeyCodes.META, }; +/** + * A keyboard shortcut. + * @typedef {{ + * callback: ((function(!Workspace, Event, + * !ShortcutRegistry.KeyboardShortcut):boolean)|undefined), + * name: string, + * preconditionFn: ((function(!Workspace):boolean)|undefined), + * metadata: (Object|undefined) + * }} + */ +ShortcutRegistry.KeyboardShortcut; + // Creates and assigns the singleton instance. -new ShortcutRegistry(); +const registry = new ShortcutRegistry(); +ShortcutRegistry.registry = registry; exports.ShortcutRegistry = ShortcutRegistry; diff --git a/tests/mocha/contextmenu_items_test.js b/tests/mocha/contextmenu_items_test.js index 56ac23f4baf..1c82072e74f 100644 --- a/tests/mocha/contextmenu_items_test.js +++ b/tests/mocha/contextmenu_items_test.js @@ -17,10 +17,9 @@ suite('Context Menu Items', function() { const toolbox = document.getElementById('toolbox-categories'); this.workspace = Blockly.inject('blocklyDiv', {toolbox: toolbox}); - // Declare a new registry to ensure default options are called. - new Blockly.ContextMenuRegistry(); - Blockly.ContextMenuItems.registerDefaultOptions(); this.registry = Blockly.ContextMenuRegistry.registry; + this.registry.reset(); + Blockly.ContextMenuItems.registerDefaultOptions(); }); teardown(function() { diff --git a/tests/mocha/shortcut_registry_test.js b/tests/mocha/shortcut_registry_test.js index 8039659e9d9..dc8440f6aa7 100644 --- a/tests/mocha/shortcut_registry_test.js +++ b/tests/mocha/shortcut_registry_test.js @@ -12,7 +12,8 @@ const {createKeyDownEvent, sharedTestSetup, sharedTestTeardown} = goog.require(' suite('Keyboard Shortcut Registry Test', function() { setup(function() { sharedTestSetup.call(this); - this.registry = new Blockly.ShortcutRegistry(); + this.registry = Blockly.ShortcutRegistry.registry; + this.registry.reset(); Blockly.ShortcutItems.registerDefaultShortcuts(); }); teardown(function() {