switch from rejectReporters flag to acceptReporters

Also add more extensive documentation around extension menu definitions,
including some considerations to think about before making a menu accept
reporters.
This commit is contained in:
Christopher Willis-Ford 2019-04-26 13:49:13 -07:00
parent e7bf49c8df
commit 3fbed88a95
2 changed files with 127 additions and 10 deletions

View file

@ -120,6 +120,123 @@ class SomeBlocks {
}
```
### Defining a Menu
To display a drop-down menu for a block argument, specify the `menu` property of that argument and a matching item in
the `menus` section of your extension's definition:
```js
return {
// ...
blocks: [
{
// ...
arguments: {
FOO: {
type: ArgumentType.NUMBER,
menu: 'fooMenu'
}
}
}
],
menus: {
fooMenu: {
items: ['a', 'b', 'c']
}
}
}
```
The items in a menu may be specified with an array or with the name of a function which returns an array. The two
simplest forms for menu definitions are:
```js
getInfo () {
return {
menus: {
staticMenu: ['static 1', 'static 2', 'static 3'],
dynamicMenu: 'getDynamicMenuItems'
}
};
}
// this member function will be called each time the menu opens
getDynamicMenuItems () {
return ['dynamic 1', 'dynamic 2', 'dynamic 3'];
}
```
The examples above are shorthand for these equivalent definitions:
```js
getInfo () {
return {
menus: {
staticMenu: {
items: ['static 1', 'static 2', 'static 3']
},
dynamicMenu: {
items: 'getDynamicMenuItems'
}
}
};
}
// this member function will be called each time the menu opens
getDynamicMenuItems () {
return ['dynamic 1', 'dynamic 2', 'dynamic 3'];
}
```
If a menu item needs a label that doesn't match its value -- for example, if the label needs to be displayed in the
user's language but the value needs to stay constant -- the menu item may be an object instead of a string. This works
for both static and dynamic menu items:
```js
menus: {
staticMenu: [
{
text: formatMessage(/* ... */),
value: 42
}
]
}
```
#### Accepting reporters ("droppable" menus)
By default it is not possible to specify the value of a dropdown menu by inserting a reporter block. While we
encourage extension authors to make their menus accept reporters when possible, doing so requires careful
consideration to avoid confusion and frustration on the part of those using the extension.
A few of these considerations include:
* The valid values for the menu should not change when the user changes the Scratch language setting.
* In particular, changing languages should never break a working project.
* The average Scratch user should be able to figure out the valid values for this input without referring to extension
documentation.
* One way to ensure this is to make an item's text match or include the item's value.
* The block should accept any value as input, even "invalid" values.
* Scratch has no concept of a runtime error!
* For a command block, sometimes the best option is to do nothing.
* For a reporter, returning zero or the empty string might make sense.
* The block should be forgiving in its interpretation of inputs.
* For example, if the block expects a string and receives a number it may make sense to interpret the number as a
string instead of treating it as invalid input.
The `acceptReporters` flag indicates that the user can drop a reporter onto the menu input:
```js
menus: {
staticMenu: {
acceptReporters: true,
items: [/*...*/]
},
dynamicMenu: {
acceptReporters: true,
items: 'getDynamicMenuItems'
}
}
```
## Annotated Example
```js
@ -315,8 +432,8 @@ class SomeBlocks {
// The examples above are shorthand for setting only the `items` property in this full form:
menuC: {
// This flag makes a "non-droppable" menu: the menu will not accept reporters.
rejectReporters: true,
// This flag makes a "droppable" menu: the menu will allow dropping a reporter in for the input.
acceptReporters: true,
// The `item` property may be an array or function name as in previous menu examples.
items: [/*...*/] || 'getItemsForMenuC'

View file

@ -894,7 +894,7 @@ class Runtime extends EventEmitter {
* @param {string} menuName - the name of the menu
* @param {object} menuInfo - a description of this menu and its items
* @property {*} items - an array of menu items or a function to retrieve such an array
* @property {boolean} [rejectReporters] - if true, prevent dropping reporters onto this menu
* @property {boolean} [acceptReporters] - if true, allow dropping reporters onto this menu
* @param {CategoryInfo} categoryInfo - the category for this block
* @returns {object} - a JSON-esque object ready for scratch-blocks' consumption
* @private
@ -927,8 +927,8 @@ class Runtime extends EventEmitter {
colour: categoryInfo.color1,
colourSecondary: categoryInfo.color2,
colourTertiary: categoryInfo.color3,
outputShape: menuInfo.rejectReporters ?
ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE : ScratchBlocksConstants.OUTPUT_SHAPE_ROUND,
outputShape: menuInfo.acceptReporters ?
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
args0: [
{
type: 'field_dropdown',
@ -1235,16 +1235,16 @@ class Runtime extends EventEmitter {
let fieldName;
if (argInfo.menu) {
const menuInfo = argInfo.menu && context.categoryInfo.menuInfo[argInfo.menu];
if (menuInfo.rejectReporters) {
if (menuInfo.acceptReporters) {
valueName = placeholder;
shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
fieldName = argInfo.menu;
} else {
argJSON.type = 'field_dropdown';
argJSON.options = menuInfo.items;
valueName = null;
shadowType = null;
fieldName = placeholder;
} else {
valueName = placeholder;
shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
fieldName = argInfo.menu;
}
} else {
valueName = placeholder;