From 6a069846787c58551796756e6be9f193d1275bc0 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 6 Mar 2018 16:22:18 -0800 Subject: [PATCH 1/6] Add enum for reporter scope Some reporters are sprite-specific, such as the costume name/number or X position of the sprite. Others are global. This enum allows an extension author to specify the scope of a reporter. --- src/extension-support/reporter-scope.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/extension-support/reporter-scope.js diff --git a/src/extension-support/reporter-scope.js b/src/extension-support/reporter-scope.js new file mode 100644 index 000000000..159d4b1c5 --- /dev/null +++ b/src/extension-support/reporter-scope.js @@ -0,0 +1,6 @@ +const ReporterScope = { + GLOBAL: 'global', + SPRITE: 'sprite' +}; + +module.exports = ReporterScope; From 4201f212d396a32795a6141097d3248c8b824d47 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 6 Mar 2018 16:29:05 -0800 Subject: [PATCH 2/6] Add JSDoc for extension metadata format This is a first pass and is probably not 100% correct. It also contains the new (experimental?) reporter scope field. --- src/extension-support/extension-metadata.js | 60 +++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/extension-support/extension-metadata.js diff --git a/src/extension-support/extension-metadata.js b/src/extension-support/extension-metadata.js new file mode 100644 index 000000000..81dbe5bf6 --- /dev/null +++ b/src/extension-support/extension-metadata.js @@ -0,0 +1,60 @@ +/** + * @typedef {object} ExtensionMetadata + * All the metadata needed to register an extension. + * @property {string} id - a unique alphanumeric identifier for this extension. No special characters allowed. + * @property {string} name - the human-readable name of this extension. + * @property {string} blockIconURI - URI for an image to be placed on each block in this extension. Data URI ok. + * @property {string} menuIconURI - URI for an image to be placed on this extension's category menu entry. Data URI ok. + * @property {string} docsURI - link to documentation content for this extension. + * @property {Array.} - the blocks provided by this extension, with optional separators. + * @property {Object.} - map of menu name to metadata about each of this extension's menus. + */ + +/** + * @typedef {object} ExtensionBlockMetadata + * All the metadata needed to register an extension block. + * @property {string} opcode - a unique alphanumeric identifier for this block. No special characters allowed. + * @property {BlockType} blockType - the type of block (command, reporter, etc.) being described. + * @property {string} func - the name of the function implementing this block. Can be shared with other blocks/opcodes. + * @property {Boolean} hideFromPalette - true if this block should not appear in the block palette. + * @property {ReporterScope} reporterScope - if this block is a reporter, this is the scope/context for its value. + * @property {Boolean} terminal - true if the block ends a stack - no blocks can be connected after it. + * @property {string} text - the text on the block, with [PLACEHOLDERS] for arguments. + * @property {Object.} arguments - map of argument placeholder to metadata about each arg. + */ + +/** + * @typedef {object} ExtensionArgumentMetadata + * All the metadata needed to register an argument for an extension block. + * @property {ArgumentType} type - the type of the argument (number, string, etc.) + * @property {*} defaultValue - the default value of this argument. + * @property {string} menu - the name of the menu to use for this argument, if any. + */ + +/** + * @typedef {ExtensionDynamicMenu|ExtensionMenuItems} ExtensionMenuMetadata + * All the metadata needed to register an extension drop-down menu. + */ + +/** + * @typedef {string} ExtensionDynamicMenu + * The string name of a function which returns menu items. + * @see {ExtensionMenuItems} - the type of data expected to be returned by the specified function. + */ + +/** + * @typedef {Array.} ExtensionMenuItems + * Items in an extension menu. + */ + +/** + * @typedef {string} ExtensionMenuItemSimple + * A menu item for which the label and value are identical strings. + */ + +/** + * @typedef {object} ExtensionMenuItemComplex + * A menu item for which the label and value can differ. + * @property {*} value - the value of the block argument when this menu item is selected. + * @property {string} text - the human-readable label of this menu item in the menu. + */ From 2932f232c46b9d8cdd41faad037acb4e221b7047 Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Mon, 12 Mar 2018 14:03:23 -0800 Subject: [PATCH 3/6] Improve developer feedback for missing block func --- src/extension-support/extension-manager.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js index d49be91cf..b35e26887 100644 --- a/src/extension-support/extension-manager.js +++ b/src/extension-support/extension-manager.js @@ -246,7 +246,7 @@ class ExtensionManager { 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 @@ -297,7 +297,12 @@ class ExtensionManager { blockInfo.func = dispatch.call.bind(dispatch, serviceName, blockInfo.func); } else { const serviceObject = dispatch.services[serviceName]; - blockInfo.func = serviceObject[blockInfo.func].bind(serviceObject); + const func = serviceObject[blockInfo.func]; + if (func) { + blockInfo.func = func.bind(serviceObject); + } else { + throw new Error(`Could not find extension block function called ${blockInfo.func}`); + } } return blockInfo; } From 1049e90a8f2786cbd3fe8feb12b76d4352925e3c Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Mon, 12 Mar 2018 14:06:14 -0800 Subject: [PATCH 4/6] Wrap extension menu function to provide context When the extension system calls a function to retrieve extension menu items, it now provides the ID of the current editing target. There's also now a little bit of error-checking on the results. --- src/extension-support/extension-manager.js | 27 ++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js index b35e26887..bf988adb9 100644 --- a/src/extension-support/extension-manager.js +++ b/src/extension-support/extension-manager.js @@ -264,13 +264,36 @@ class ExtensionManager { // 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); + const menuName = menus[item]; + menus[item] = this._getExtensionMenuItems.bind(this, serviceObject, menuName); } } return menus; } + /** + * Fetch the items for a particular extension menu, providing the target ID for context. + * @param {object} extensionObject - the extension object providing the menu. + * @param {string} menuName - the name of the menu function to call. + * @returns {Array} menu items ready for scratch-blocks. + * @private + */ + _getExtensionMenuItems (extensionObject, menuName) { + // Fetch the items appropriate for the target currently being edited. This assumes that menus only + // collect items when opened by the user while editing a particular target. + const editingTarget = this.runtime.getEditingTarget(); + const editingTargetID = editingTarget ? editingTarget.id : null; + + // TODO: Fix this to use dispatch.call when extensions are running in workers. + const menuFunc = extensionObject[menuName]; + const menuItems = menuFunc.call(extensionObject, editingTargetID); + + if (!menuItems || menuItems.length < 1) { + throw new Error(`Extension menu returned no items: ${menuName}`); + } + return menuItems; + } + /** * Apply defaults for optional block fields. * @param {string} serviceName - the name of the service hosting this extension block From d3352ac54d6137d540f0d40fe92518cac088a6aa Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 20 Mar 2018 11:23:55 -0700 Subject: [PATCH 5/6] Add JSDoc tags for ArgumentType & BlockType --- src/extension-support/argument-type.js | 23 +++++++++++++++++++++++ src/extension-support/block-type.js | 23 +++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/src/extension-support/argument-type.js b/src/extension-support/argument-type.js index 0589e9fb6..538a04f5d 100644 --- a/src/extension-support/argument-type.js +++ b/src/extension-support/argument-type.js @@ -1,8 +1,31 @@ +/** + * Block argument types + * @enum {string} + */ const ArgumentType = { + /** + * Numeric value with angle picker + */ ANGLE: 'angle', + + /** + * Boolean value with hexagonal placeholder + */ BOOLEAN: 'Boolean', + + /** + * Numeric value with color picker + */ COLOR: 'color', + + /** + * Numeric value with text field + */ NUMBER: 'number', + + /** + * String value with text field + */ STRING: 'string' }; diff --git a/src/extension-support/block-type.js b/src/extension-support/block-type.js index 587f7d598..9a87b745f 100644 --- a/src/extension-support/block-type.js +++ b/src/extension-support/block-type.js @@ -1,8 +1,31 @@ +/** + * Types of block + * @enum {string} + */ const BlockType = { + /** + * Boolean reporter with hexagonal shape + */ BOOLEAN: 'Boolean', + + /** + * Command block + */ COMMAND: 'command', + + /** + * Specialized command block which may or may not run a child branch + */ CONDITIONAL: 'conditional', + + /** + * Hat block which conditionally starts a block stack + */ HAT: 'hat', + + /** + * General reporter with numeric or string value + */ REPORTER: 'reporter' }; From 26bc7851bb09e1297b3b48a67ac25e153dd5883a Mon Sep 17 00:00:00 2001 From: Christopher Willis-Ford Date: Tue, 20 Mar 2018 15:30:34 -0700 Subject: [PATCH 6/6] Rename ReporterScope.SPRITE to TARGET & add JSDoc --- src/extension-support/reporter-scope.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/extension-support/reporter-scope.js b/src/extension-support/reporter-scope.js index 159d4b1c5..6157d1c5f 100644 --- a/src/extension-support/reporter-scope.js +++ b/src/extension-support/reporter-scope.js @@ -1,6 +1,18 @@ +/** + * Indicate the scope for a reporter's value. + * @enum {string} + */ const ReporterScope = { + /** + * This reporter's value is global and does not depend on context. + */ GLOBAL: 'global', - SPRITE: 'sprite' + + /** + * This reporter's value is specific to a particular target/sprite. + * Another target may have a different value or may not even have a value. + */ + TARGET: 'target' }; module.exports = ReporterScope;