diff --git a/src/engine/runtime.js b/src/engine/runtime.js index bf7969a0c..00e74b1da 100644 --- a/src/engine/runtime.js +++ b/src/engine/runtime.js @@ -551,22 +551,23 @@ class Runtime extends EventEmitter { */ _buildMenuForScratchBlocks (menuName, menuItems, categoryInfo) { const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id); - - /** @TODO: support dynamic menus when 'menuItems' is a method name string (see extension spec) */ - if (typeof menuItems === 'string') { - throw new Error(`Dynamic extension menus are not yet supported. Menu name: ${menuName}`); - } - const options = menuItems.map(item => { - switch (typeof item) { - case 'string': - return [item, item]; - case 'object': - return [item.text, item.value]; - default: - throw new Error(`Can't interpret menu item: ${item}`); + var options = null; + if (typeof menuItems === 'function') { + options = function () { + return menuItems(); } - }); - + } else { + options = menuItems.map(item => { + switch (typeof item) { + case 'string': + return [item, item]; + case 'object': + return [item.text, item.value]; + default: + throw new Error(`Can't interpret menu item: ${item}`); + } + }); + } return { json: { message0: '%1', diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js index 3228f5c2a..1fb4795b2 100644 --- a/src/extension-support/extension-manager.js +++ b/src/extension-support/extension-manager.js @@ -242,8 +242,34 @@ class ExtensionManager { } return result; }, []); + extensionInfo.menus = extensionInfo.menus || []; + extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus); return extensionInfo; } + + /** + * Prepare extension menus. e.g. setup binding for dynamic menu functions. + * @param {string} serviceName - the name of the service hosting this extension block + * @param {Array.} menuInfo - the menu defined by the extension. + * @returns {Array.} - a menuInfo object with all preprocessing done. + * @private + */ + _prepareMenuInfo (serviceName, menus) { + var menuNames = Object.getOwnPropertyNames(menus); + for (let i = 0; i < menuNames.length; i++) { + var item = menuNames[i]; + // If the value is a string, it should be the name of a function in the + // extension object to call to populate the menu whenever it is opened. + // Set up the binding for the function object here so + // we can use it later when converting the menu for Scratch Blocks. + if (typeof menus[item] === 'string') { + const serviceObject = dispatch.services[serviceName]; + const menuFunc = serviceObject[menus[item]].bind(serviceObject); + menus[item] = menuFunc; + } + } + return menus; + } /** * Apply defaults for optional block fields.