Merge pull request from picklesrus/extensions-dynamic-menu

Extensions dynamic menu
This commit is contained in:
Chris Willis-Ford 2018-03-06 16:33:26 -05:00 committed by GitHub
commit 219f083a29
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 64 additions and 16 deletions
src
engine
extension-support
test/integration

View file

@ -569,22 +569,21 @@ 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}`);
let options = null;
if (typeof menuItems === 'function') {
options = 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}`);
}
});
}
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}`);
}
});
return {
json: {
message0: '%1',

View file

@ -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>} menus - the menu defined by the extension.
* @returns {Array.<MenuInfo>} - a menuInfo object with all preprocessing done.
* @private
*/
_prepareMenuInfo (serviceName, menus) {
const menuNames = Object.getOwnPropertyNames(menus);
for (let i = 0; i < menuNames.length; i++) {
const 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];
// TODO: Fix this to use dispatch.call when extensions are running in workers.
menus[item] = serviceObject[menus[item]].bind(serviceObject);
}
}
return menus;
}
/**
* Apply defaults for optional block fields.

View file

@ -22,13 +22,27 @@ class TestInternalExtension {
{
opcode: 'go'
}
]
],
menus: {
simpleMenu: this._buildAMenu(),
dynamicMenu: '_buildDynamicMenu'
}
};
}
go () {
this.status.goCalled = true;
}
_buildAMenu () {
this.status.buildMenuCalled = true;
return ['abcd', 'efgh', 'ijkl'];
}
_buildDynamicMenu () {
this.status.buildDynamicMenuCalled = true;
return [1, 2, 3, 4, 6];
}
}
test('internal extension', t => {
@ -47,5 +61,14 @@ test('internal extension', t => {
t.notOk(extension.status.goCalled);
func();
t.ok(extension.status.goCalled);
// There should be 2 menus - one is an array, one is the function to call.
t.equal(vm.runtime._blockInfo[0].menus.length, 2);
// First menu has 3 items.
t.equal(
vm.runtime._blockInfo[0].menus[0].json.args0[0].options.length, 3);
// Second menu is a dynamic menu and therefore should be a function.
t.type(
vm.runtime._blockInfo[0].menus[1].json.args0[0].options, 'function');
});
});