mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge pull request #2143 from cwillisf/non-droppable-extension-menus
support non-droppable menus in extensions
This commit is contained in:
commit
c6b63a8f09
14 changed files with 734 additions and 452 deletions
|
@ -120,6 +120,124 @@ 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. For example, the official Music
|
||||
extension contains menu items with names like "(1) Piano" with value 1, "(8) Cello" with value 8, and so on.
|
||||
* 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
|
||||
|
@ -311,7 +429,16 @@ class SomeBlocks {
|
|||
|
||||
// Dynamic menu: returns an array as above.
|
||||
// Called each time the menu is opened.
|
||||
menuB: 'getItemsForMenuB'
|
||||
menuB: 'getItemsForMenuB',
|
||||
|
||||
// The examples above are shorthand for setting only the `items` property in this full form:
|
||||
menuC: {
|
||||
// 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'
|
||||
}
|
||||
},
|
||||
|
||||
// Optional: translations (UNSTABLE - NOT YET SUPPORTED)
|
||||
|
|
|
@ -786,10 +786,7 @@ class Runtime extends EventEmitter {
|
|||
name: maybeFormatMessage(extensionInfo.name),
|
||||
showStatusButton: extensionInfo.showStatusButton,
|
||||
blockIconURI: extensionInfo.blockIconURI,
|
||||
menuIconURI: extensionInfo.menuIconURI,
|
||||
customFieldTypes: {},
|
||||
blocks: [],
|
||||
menus: []
|
||||
menuIconURI: extensionInfo.menuIconURI
|
||||
};
|
||||
|
||||
if (extensionInfo.color1) {
|
||||
|
@ -830,8 +827,6 @@ class Runtime extends EventEmitter {
|
|||
const categoryInfo = this._blockInfo.find(info => info.id === extensionInfo.id);
|
||||
if (categoryInfo) {
|
||||
categoryInfo.name = maybeFormatMessage(extensionInfo.name);
|
||||
categoryInfo.blocks = [];
|
||||
categoryInfo.menus = [];
|
||||
this._fillExtensionCategory(categoryInfo, extensionInfo);
|
||||
|
||||
this.emit(Runtime.BLOCKSINFO_UPDATE, categoryInfo);
|
||||
|
@ -846,11 +841,17 @@ class Runtime extends EventEmitter {
|
|||
* @private
|
||||
*/
|
||||
_fillExtensionCategory (categoryInfo, extensionInfo) {
|
||||
categoryInfo.blocks = [];
|
||||
categoryInfo.customFieldTypes = {};
|
||||
categoryInfo.menus = [];
|
||||
categoryInfo.menuInfo = {};
|
||||
|
||||
for (const menuName in extensionInfo.menus) {
|
||||
if (extensionInfo.menus.hasOwnProperty(menuName)) {
|
||||
const menuItems = extensionInfo.menus[menuName];
|
||||
const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuItems, categoryInfo);
|
||||
const menuInfo = extensionInfo.menus[menuName];
|
||||
const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo);
|
||||
categoryInfo.menus.push(convertedMenu);
|
||||
categoryInfo.menuInfo[menuName] = menuInfo;
|
||||
}
|
||||
}
|
||||
for (const fieldTypeName in extensionInfo.customFieldTypes) {
|
||||
|
@ -890,21 +891,16 @@ class Runtime extends EventEmitter {
|
|||
}
|
||||
|
||||
/**
|
||||
* Build the scratch-blocks JSON for a menu. Note that scratch-blocks treats menus as a special kind of block.
|
||||
* @param {string} menuName - the name of the menu
|
||||
* @param {array} menuItems - the list of items for this menu
|
||||
* @param {CategoryInfo} categoryInfo - the category for this block
|
||||
* @returns {object} - a JSON-esque object ready for scratch-blocks' consumption
|
||||
* Convert the given extension menu items into the scratch-blocks style of list of pairs.
|
||||
* If the menu is dynamic (e.g. the passed in argument is a function), return the input unmodified.
|
||||
* @param {object} menuItems - an array of menu items or a function to retrieve such an array
|
||||
* @returns {object} - an array of 2 element arrays or the original input function
|
||||
* @private
|
||||
*/
|
||||
_buildMenuForScratchBlocks (menuName, menuItems, categoryInfo) {
|
||||
const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id);
|
||||
let options = null;
|
||||
if (typeof menuItems === 'function') {
|
||||
options = menuItems;
|
||||
} else {
|
||||
_convertMenuItems (menuItems) {
|
||||
if (typeof menuItems !== 'function') {
|
||||
const extensionMessageContext = this.makeMessageContextForTarget();
|
||||
options = menuItems.map(item => {
|
||||
return menuItems.map(item => {
|
||||
const formattedItem = maybeFormatMessage(item, extensionMessageContext);
|
||||
switch (typeof formattedItem) {
|
||||
case 'string':
|
||||
|
@ -916,6 +912,22 @@ class Runtime extends EventEmitter {
|
|||
}
|
||||
});
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the scratch-blocks JSON for a menu. Note that scratch-blocks treats menus as a special kind of block.
|
||||
* @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} [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
|
||||
*/
|
||||
_buildMenuForScratchBlocks (menuName, menuInfo, categoryInfo) {
|
||||
const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id);
|
||||
const menuItems = this._convertMenuItems(menuInfo.items);
|
||||
return {
|
||||
json: {
|
||||
message0: '%1',
|
||||
|
@ -925,12 +937,13 @@ class Runtime extends EventEmitter {
|
|||
colour: categoryInfo.color1,
|
||||
colourSecondary: categoryInfo.color2,
|
||||
colourTertiary: categoryInfo.color3,
|
||||
outputShape: ScratchBlocksConstants.OUTPUT_SHAPE_ROUND,
|
||||
outputShape: menuInfo.acceptReporters ?
|
||||
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
|
||||
args0: [
|
||||
{
|
||||
type: 'field_dropdown',
|
||||
name: menuName,
|
||||
options: options
|
||||
options: menuItems
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -1227,29 +1240,51 @@ class Runtime extends EventEmitter {
|
|||
argJSON.check = argTypeInfo.check;
|
||||
}
|
||||
|
||||
const shadowType = (argInfo.menu ?
|
||||
this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id) :
|
||||
argTypeInfo.shadowType);
|
||||
const fieldType = argInfo.menu || argTypeInfo.fieldType;
|
||||
let valueName;
|
||||
let shadowType;
|
||||
let fieldName;
|
||||
if (argInfo.menu) {
|
||||
const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
|
||||
if (menuInfo.acceptReporters) {
|
||||
valueName = placeholder;
|
||||
shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
|
||||
fieldName = argInfo.menu;
|
||||
} else {
|
||||
argJSON.type = 'field_dropdown';
|
||||
argJSON.options = this._convertMenuItems(menuInfo.items);
|
||||
valueName = null;
|
||||
shadowType = null;
|
||||
fieldName = placeholder;
|
||||
}
|
||||
} else {
|
||||
valueName = placeholder;
|
||||
shadowType = argTypeInfo.shadowType;
|
||||
fieldName = argTypeInfo.fieldType;
|
||||
}
|
||||
|
||||
// <value> is the ScratchBlocks name for a block input.
|
||||
if (valueName) {
|
||||
context.inputList.push(`<value name="${placeholder}">`);
|
||||
}
|
||||
|
||||
// The <shadow> is a placeholder for a reporter and is visible when there's no reporter in this input.
|
||||
// Boolean inputs don't need to specify a shadow in the XML.
|
||||
if (shadowType) {
|
||||
context.inputList.push(`<shadow type="${shadowType}">`);
|
||||
|
||||
// <field> is a text field that the user can type into. Some shadows, like the color picker, don't allow
|
||||
// text input and therefore don't need a field element.
|
||||
if (fieldType) {
|
||||
context.inputList.push(`<field name="${fieldType}">${defaultValue}</field>`);
|
||||
}
|
||||
|
||||
// A <field> displays a dynamic value: a user-editable text field, a drop-down menu, etc.
|
||||
if (fieldName) {
|
||||
context.inputList.push(`<field name="${fieldName}">${defaultValue}</field>`);
|
||||
}
|
||||
|
||||
if (shadowType) {
|
||||
context.inputList.push('</shadow>');
|
||||
}
|
||||
|
||||
if (valueName) {
|
||||
context.inputList.push('</value>');
|
||||
}
|
||||
|
||||
const argsName = `args${context.outLineNum}`;
|
||||
const blockArgs = (context.blockJSON[argsName] = context.blockJSON[argsName] || []);
|
||||
|
|
|
@ -294,7 +294,7 @@ class ExtensionManager {
|
|||
}
|
||||
return results;
|
||||
}, []);
|
||||
extensionInfo.menus = extensionInfo.menus || [];
|
||||
extensionInfo.menus = extensionInfo.menus || {};
|
||||
extensionInfo.menus = this._prepareMenuInfo(serviceName, extensionInfo.menus);
|
||||
return extensionInfo;
|
||||
}
|
||||
|
@ -309,15 +309,24 @@ class ExtensionManager {
|
|||
_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 menuName = menuNames[i];
|
||||
let menuInfo = menus[menuName];
|
||||
|
||||
// If the menu description is in short form (items only) then normalize it to general form: an object with
|
||||
// its items listed in an `items` property.
|
||||
if (!menuInfo.items) {
|
||||
menuInfo = {
|
||||
items: menuInfo
|
||||
};
|
||||
menus[menuName] = menuInfo;
|
||||
}
|
||||
// If `items` is a string, it should be the name of a function in the extension object. Calling the
|
||||
// function should return an array of items to populate the menu when it is opened.
|
||||
if (typeof menuInfo.items === 'string') {
|
||||
const menuItemFunctionName = menuInfo.items;
|
||||
const serviceObject = dispatch.services[serviceName];
|
||||
const menuName = menus[item];
|
||||
menus[item] = this._getExtensionMenuItems.bind(this, serviceObject, menuName);
|
||||
// Bind the function here so we can pass a simple item generation function to Scratch Blocks later.
|
||||
menuInfo.items = this._getExtensionMenuItems.bind(this, serviceObject, menuItemFunctionName);
|
||||
}
|
||||
}
|
||||
return menus;
|
||||
|
@ -326,11 +335,11 @@ class ExtensionManager {
|
|||
/**
|
||||
* 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.
|
||||
* @param {string} menuItemFunctionName - the name of the menu function to call.
|
||||
* @returns {Array} menu items ready for scratch-blocks.
|
||||
* @private
|
||||
*/
|
||||
_getExtensionMenuItems (extensionObject, menuName) {
|
||||
_getExtensionMenuItems (extensionObject, menuItemFunctionName) {
|
||||
// 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() || this.runtime.getTargetForStage();
|
||||
|
@ -338,7 +347,7 @@ class ExtensionManager {
|
|||
const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);
|
||||
|
||||
// TODO: Fix this to use dispatch.call when extensions are running in workers.
|
||||
const menuFunc = extensionObject[menuName];
|
||||
const menuFunc = extensionObject[menuItemFunctionName];
|
||||
const menuItems = menuFunc.call(extensionObject, editingTargetID).map(
|
||||
item => {
|
||||
item = maybeFormatMessage(item, extensionMessageContext);
|
||||
|
@ -356,7 +365,7 @@ class ExtensionManager {
|
|||
});
|
||||
|
||||
if (!menuItems || menuItems.length < 1) {
|
||||
throw new Error(`Extension menu returned no items: ${menuName}`);
|
||||
throw new Error(`Extension menu returned no items: ${menuItemFunctionName}`);
|
||||
}
|
||||
return menuItems;
|
||||
}
|
||||
|
|
|
@ -1413,7 +1413,9 @@ class Scratch3BoostBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
MOTOR_ID: [
|
||||
MOTOR_ID: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: 'A',
|
||||
value: BoostMotorLabel.A
|
||||
|
@ -1438,8 +1440,11 @@ class Scratch3BoostBlocks {
|
|||
text: 'ABCD',
|
||||
value: BoostMotorLabel.ALL
|
||||
}
|
||||
],
|
||||
MOTOR_REPORTER_ID: [
|
||||
]
|
||||
},
|
||||
MOTOR_REPORTER_ID: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: 'A',
|
||||
value: BoostMotorLabel.A
|
||||
|
@ -1456,13 +1461,17 @@ class Scratch3BoostBlocks {
|
|||
text: 'D',
|
||||
value: BoostMotorLabel.D
|
||||
}
|
||||
],
|
||||
MOTOR_DIRECTION: [
|
||||
]
|
||||
},
|
||||
MOTOR_DIRECTION: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'boost.motorDirection.forward',
|
||||
default: 'this way',
|
||||
description: 'label for forward element in motor direction menu for LEGO Boost extension'
|
||||
description:
|
||||
'label for forward element in motor direction menu for LEGO Boost extension'
|
||||
}),
|
||||
value: BoostMotorDirection.FORWARD
|
||||
},
|
||||
|
@ -1470,7 +1479,8 @@ class Scratch3BoostBlocks {
|
|||
text: formatMessage({
|
||||
id: 'boost.motorDirection.backward',
|
||||
default: 'that way',
|
||||
description: 'label for backward element in motor direction menu for LEGO Boost extension'
|
||||
description:
|
||||
'label for backward element in motor direction menu for LEGO Boost extension'
|
||||
}),
|
||||
value: BoostMotorDirection.BACKWARD
|
||||
},
|
||||
|
@ -1478,12 +1488,16 @@ class Scratch3BoostBlocks {
|
|||
text: formatMessage({
|
||||
id: 'boost.motorDirection.reverse',
|
||||
default: 'reverse',
|
||||
description: 'label for reverse element in motor direction menu for LEGO Boost extension'
|
||||
description:
|
||||
'label for reverse element in motor direction menu for LEGO Boost extension'
|
||||
}),
|
||||
value: BoostMotorDirection.REVERSE
|
||||
}
|
||||
],
|
||||
TILT_DIRECTION: [
|
||||
]
|
||||
},
|
||||
TILT_DIRECTION: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'boost.tiltDirection.up',
|
||||
|
@ -1516,8 +1530,11 @@ class Scratch3BoostBlocks {
|
|||
}),
|
||||
value: BoostTiltDirection.RIGHT
|
||||
}
|
||||
],
|
||||
TILT_DIRECTION_ANY: [
|
||||
]
|
||||
},
|
||||
TILT_DIRECTION_ANY: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'boost.tiltDirection.up',
|
||||
|
@ -1554,8 +1571,11 @@ class Scratch3BoostBlocks {
|
|||
}),
|
||||
value: BoostTiltDirection.ANY
|
||||
}
|
||||
],
|
||||
COLOR: [
|
||||
]
|
||||
},
|
||||
COLOR: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'boost.color.red',
|
||||
|
@ -1614,6 +1634,7 @@ class Scratch3BoostBlocks {
|
|||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1137,8 +1137,14 @@ class Scratch3Ev3Blocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
motorPorts: this._formatMenu(Ev3MotorMenu),
|
||||
sensorPorts: this._formatMenu(Ev3SensorMenu)
|
||||
motorPorts: {
|
||||
acceptReporters: true,
|
||||
items: this._formatMenu(Ev3MotorMenu)
|
||||
},
|
||||
sensorPorts: {
|
||||
acceptReporters: true,
|
||||
items: this._formatMenu(Ev3SensorMenu)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -757,11 +757,26 @@ class Scratch3GdxForBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
pushPullOptions: this.PUSH_PULL_MENU,
|
||||
gestureOptions: this.GESTURE_MENU,
|
||||
axisOptions: this.AXIS_MENU,
|
||||
tiltOptions: this.TILT_MENU,
|
||||
tiltAnyOptions: this.TILT_MENU_ANY
|
||||
pushPullOptions: {
|
||||
acceptReporters: true,
|
||||
items: this.PUSH_PULL_MENU
|
||||
},
|
||||
gestureOptions: {
|
||||
acceptReporters: true,
|
||||
items: this.GESTURE_MENU
|
||||
},
|
||||
axisOptions: {
|
||||
acceptReporters: true,
|
||||
items: this.AXIS_MENU
|
||||
},
|
||||
tiltOptions: {
|
||||
acceptReporters: true,
|
||||
items: this.TILT_MENU
|
||||
},
|
||||
tiltAnyOptions: {
|
||||
acceptReporters: true,
|
||||
items: this.TILT_MENU_ANY
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -207,7 +207,9 @@ class Scratch3MakeyMakeyBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
KEY: [
|
||||
KEY: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'makeymakey.spaceKey',
|
||||
|
@ -254,8 +256,12 @@ class Scratch3MakeyMakeyBlocks {
|
|||
{text: 'd', value: 'd'},
|
||||
{text: 'f', value: 'f'},
|
||||
{text: 'g', value: 'g'}
|
||||
],
|
||||
SEQUENCE: this.buildSequenceMenu(this.DEFAULT_SEQUENCES)
|
||||
]
|
||||
},
|
||||
SEQUENCE: {
|
||||
acceptReporters: true,
|
||||
items: this.buildSequenceMenu(this.DEFAULT_SEQUENCES)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -746,12 +746,30 @@ class Scratch3MicroBitBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
buttons: this.BUTTONS_MENU,
|
||||
gestures: this.GESTURES_MENU,
|
||||
pinState: this.PIN_STATE_MENU,
|
||||
tiltDirection: this.TILT_DIRECTION_MENU,
|
||||
tiltDirectionAny: this.TILT_DIRECTION_ANY_MENU,
|
||||
touchPins: ['0', '1', '2']
|
||||
buttons: {
|
||||
acceptReporters: true,
|
||||
items: this.BUTTONS_MENU
|
||||
},
|
||||
gestures: {
|
||||
acceptReporters: true,
|
||||
items: this.GESTURES_MENU
|
||||
},
|
||||
pinState: {
|
||||
acceptReporters: true,
|
||||
items: this.PIN_STATE_MENU
|
||||
},
|
||||
tiltDirection: {
|
||||
acceptReporters: true,
|
||||
items: this.TILT_DIRECTION_MENU
|
||||
},
|
||||
tiltDirectionAny: {
|
||||
acceptReporters: true,
|
||||
items: this.TILT_DIRECTION_ANY_MENU
|
||||
},
|
||||
touchPins: {
|
||||
acceptReporters: true,
|
||||
items: ['0', '1', '2']
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -911,8 +911,14 @@ class Scratch3MusicBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
DRUM: this._buildMenu(this.DRUM_INFO),
|
||||
INSTRUMENT: this._buildMenu(this.INSTRUMENT_INFO)
|
||||
DRUM: {
|
||||
acceptReporters: true,
|
||||
items: this._buildMenu(this.DRUM_INFO)
|
||||
},
|
||||
INSTRUMENT: {
|
||||
acceptReporters: true,
|
||||
items: this._buildMenu(this.INSTRUMENT_INFO)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -477,7 +477,10 @@ class Scratch3PenBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
colorParam: this._initColorParam()
|
||||
colorParam: {
|
||||
acceptReporters: true,
|
||||
items: this._initColorParam()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -469,8 +469,14 @@ class Scratch3Text2SpeechBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
voices: this.getVoiceMenu(),
|
||||
languages: this.getLanguageMenu()
|
||||
voices: {
|
||||
acceptReporters: true,
|
||||
items: this.getVoiceMenu()
|
||||
},
|
||||
languages: {
|
||||
acceptReporters: true,
|
||||
items: this.getLanguageMenu()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -146,7 +146,10 @@ class Scratch3TranslateBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
languages: this._supportedLanguages
|
||||
languages: {
|
||||
acceptReporters: true,
|
||||
items: this._supportedLanguages
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -488,9 +488,18 @@ class Scratch3VideoSensingBlocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
ATTRIBUTE: this._buildMenu(this.ATTRIBUTE_INFO),
|
||||
SUBJECT: this._buildMenu(this.SUBJECT_INFO),
|
||||
VIDEO_STATE: this._buildMenu(this.VIDEO_STATE_INFO)
|
||||
ATTRIBUTE: {
|
||||
acceptReporters: true,
|
||||
items: this._buildMenu(this.ATTRIBUTE_INFO)
|
||||
},
|
||||
SUBJECT: {
|
||||
acceptReporters: true,
|
||||
items: this._buildMenu(this.SUBJECT_INFO)
|
||||
},
|
||||
VIDEO_STATE: {
|
||||
acceptReporters: true,
|
||||
items: this._buildMenu(this.VIDEO_STATE_INFO)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1133,7 +1133,9 @@ class Scratch3WeDo2Blocks {
|
|||
}
|
||||
],
|
||||
menus: {
|
||||
MOTOR_ID: [
|
||||
MOTOR_ID: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'wedo2.motorId.default',
|
||||
|
@ -1166,13 +1168,17 @@ class Scratch3WeDo2Blocks {
|
|||
}),
|
||||
value: WeDo2MotorLabel.ALL
|
||||
}
|
||||
],
|
||||
MOTOR_DIRECTION: [
|
||||
]
|
||||
},
|
||||
MOTOR_DIRECTION: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'wedo2.motorDirection.forward',
|
||||
default: 'this way',
|
||||
description: 'label for forward element in motor direction menu for LEGO WeDo 2 extension'
|
||||
description:
|
||||
'label for forward element in motor direction menu for LEGO WeDo 2 extension'
|
||||
}),
|
||||
value: WeDo2MotorDirection.FORWARD
|
||||
},
|
||||
|
@ -1180,7 +1186,8 @@ class Scratch3WeDo2Blocks {
|
|||
text: formatMessage({
|
||||
id: 'wedo2.motorDirection.backward',
|
||||
default: 'that way',
|
||||
description: 'label for backward element in motor direction menu for LEGO WeDo 2 extension'
|
||||
description:
|
||||
'label for backward element in motor direction menu for LEGO WeDo 2 extension'
|
||||
}),
|
||||
value: WeDo2MotorDirection.BACKWARD
|
||||
},
|
||||
|
@ -1188,12 +1195,16 @@ class Scratch3WeDo2Blocks {
|
|||
text: formatMessage({
|
||||
id: 'wedo2.motorDirection.reverse',
|
||||
default: 'reverse',
|
||||
description: 'label for reverse element in motor direction menu for LEGO WeDo 2 extension'
|
||||
description:
|
||||
'label for reverse element in motor direction menu for LEGO WeDo 2 extension'
|
||||
}),
|
||||
value: WeDo2MotorDirection.REVERSE
|
||||
}
|
||||
],
|
||||
TILT_DIRECTION: [
|
||||
]
|
||||
},
|
||||
TILT_DIRECTION: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'wedo2.tiltDirection.up',
|
||||
|
@ -1226,8 +1237,11 @@ class Scratch3WeDo2Blocks {
|
|||
}),
|
||||
value: WeDo2TiltDirection.RIGHT
|
||||
}
|
||||
],
|
||||
TILT_DIRECTION_ANY: [
|
||||
]
|
||||
},
|
||||
TILT_DIRECTION_ANY: {
|
||||
acceptReporters: true,
|
||||
items: [
|
||||
{
|
||||
text: formatMessage({
|
||||
id: 'wedo2.tiltDirection.up',
|
||||
|
@ -1264,8 +1278,12 @@ class Scratch3WeDo2Blocks {
|
|||
}),
|
||||
value: WeDo2TiltDirection.ANY
|
||||
}
|
||||
],
|
||||
OP: ['<', '>']
|
||||
]
|
||||
},
|
||||
OP: {
|
||||
acceptReporters: true,
|
||||
items: ['<', '>']
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue