Porting math.js blocks to JSON (#846)

Moving all `math.js` definitions into a single JSON array, complete with i18n syntax for all messages, dropdowns, and tooltips.

Adding Blockly.Extensions.buildTooltipForDropdown(..) to facilitate the creation and error-checking of tooltips that update based on the value of a dropdown.

Now warn on raw string in JSON 'extensions'.
This commit is contained in:
Andrew n marshall
2017-01-23 10:23:55 -08:00
committed by GitHub
parent 46316c7cea
commit 7b0275cd70
6 changed files with 539 additions and 440 deletions

View File

@@ -21,9 +21,9 @@
/**
* @fileoverview Colour blocks for Blockly.
*
* This file is scraped to extract .json file definitions. The array passed to
* defineBlocksWithJsonArray(..) must be strict JSON: double quotes only, no
* outside references, no functions, no trailing commasa, etc. The one
* This file is scraped to extract a .json file of block definitions. The array
* passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes
* only, no outside references, no functions, no trailing commas, etc. The one
* exception is end-of-line comments, which the scraper will remove.
* @author fraser@google.com (Neil Fraser)
*/
@@ -128,4 +128,4 @@ Blockly.defineBlocksWithJsonArray([
"helpUrl": "%{BKY_COLOUR_BLEND_HELPURL}",
"tooltip": "%{BKY_COLOUR_BLEND_TOOLTIP}"
}
]); // End of defineBlocksWithJsonArray(...) DO NOT DELETE
]); // End of defineBlocksWithJsonArray(...) (Do not delete this comment.)

View File

@@ -20,6 +20,11 @@
/**
* @fileoverview Math blocks for Blockly.
*
* This file is scraped to extract a .json file of block definitions. The array
* passed to defineBlocksWithJsonArray(..) must be strict JSON: double quotes
* only, no outside references, no functions, no trailing commas, etc. The one
* exception is end-of-line comments, which the scraper will remove.
* @author q.neutron@gmail.com (Quynh Neutron)
*/
'use strict';
@@ -31,6 +36,7 @@ goog.require('Blockly.Blocks');
/**
* Common HSV hue for all blocks in this category.
* Should be the same as Blockly.Msg.MATH_HUE
*/
Blockly.Blocks.math.HUE = 230;
@@ -49,216 +55,371 @@ Blockly.defineBlocksWithJsonArray([
"helpUrl": "%{BKY_MATH_NUMBER_HELPURL}",
"tooltip": "%{BKY_MATH_NUMBER_TOOLTIP}",
"extensions": ["parent_tooltip_when_inline"]
}
]);
Blockly.Blocks['math_arithmetic'] = {
/**
* Block for basic arithmetic operator.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1 %2 %3",
"args0": [
{
"type": "input_value",
"name": "A",
"check": "Number"
},
{
"type": "field_dropdown",
"name": "OP",
"options":
[[Blockly.Msg.MATH_ADDITION_SYMBOL, 'ADD'],
[Blockly.Msg.MATH_SUBTRACTION_SYMBOL, 'MINUS'],
[Blockly.Msg.MATH_MULTIPLICATION_SYMBOL, 'MULTIPLY'],
[Blockly.Msg.MATH_DIVISION_SYMBOL, 'DIVIDE'],
[Blockly.Msg.MATH_POWER_SYMBOL, 'POWER']]
},
{
"type": "input_value",
"name": "B",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"helpUrl": Blockly.Msg.MATH_ARITHMETIC_HELPURL
});
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var mode = thisBlock.getFieldValue('OP');
var TOOLTIPS = {
'ADD': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_ADD,
'MINUS': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MINUS,
'MULTIPLY': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_MULTIPLY,
'DIVIDE': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_DIVIDE,
'POWER': Blockly.Msg.MATH_ARITHMETIC_TOOLTIP_POWER
};
return TOOLTIPS[mode];
});
}
};
Blockly.Blocks['math_single'] = {
/**
* Block for advanced math operators with single operand.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
[Blockly.Msg.MATH_SINGLE_OP_ROOT, 'ROOT'],
[Blockly.Msg.MATH_SINGLE_OP_ABSOLUTE, 'ABS'],
['-', 'NEG'],
['ln', 'LN'],
['log10', 'LOG10'],
['e^', 'EXP'],
['10^', 'POW10']
]
},
{
"type": "input_value",
"name": "NUM",
"check": "Number"
}
],
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"helpUrl": Blockly.Msg.MATH_SINGLE_HELPURL
});
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var mode = thisBlock.getFieldValue('OP');
var TOOLTIPS = {
'ROOT': Blockly.Msg.MATH_SINGLE_TOOLTIP_ROOT,
'ABS': Blockly.Msg.MATH_SINGLE_TOOLTIP_ABS,
'NEG': Blockly.Msg.MATH_SINGLE_TOOLTIP_NEG,
'LN': Blockly.Msg.MATH_SINGLE_TOOLTIP_LN,
'LOG10': Blockly.Msg.MATH_SINGLE_TOOLTIP_LOG10,
'EXP': Blockly.Msg.MATH_SINGLE_TOOLTIP_EXP,
'POW10': Blockly.Msg.MATH_SINGLE_TOOLTIP_POW10
};
return TOOLTIPS[mode];
});
}
};
Blockly.Blocks['math_trig'] = {
/**
* Block for trigonometry operators.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
[Blockly.Msg.MATH_TRIG_SIN, 'SIN'],
[Blockly.Msg.MATH_TRIG_COS, 'COS'],
[Blockly.Msg.MATH_TRIG_TAN, 'TAN'],
[Blockly.Msg.MATH_TRIG_ASIN, 'ASIN'],
[Blockly.Msg.MATH_TRIG_ACOS, 'ACOS'],
[Blockly.Msg.MATH_TRIG_ATAN, 'ATAN']
]
},
{
"type": "input_value",
"name": "NUM",
"check": "Number"
}
],
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"helpUrl": Blockly.Msg.MATH_TRIG_HELPURL
});
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var mode = thisBlock.getFieldValue('OP');
var TOOLTIPS = {
'SIN': Blockly.Msg.MATH_TRIG_TOOLTIP_SIN,
'COS': Blockly.Msg.MATH_TRIG_TOOLTIP_COS,
'TAN': Blockly.Msg.MATH_TRIG_TOOLTIP_TAN,
'ASIN': Blockly.Msg.MATH_TRIG_TOOLTIP_ASIN,
'ACOS': Blockly.Msg.MATH_TRIG_TOOLTIP_ACOS,
'ATAN': Blockly.Msg.MATH_TRIG_TOOLTIP_ATAN
};
return TOOLTIPS[mode];
});
}
};
Blockly.Blocks['math_constant'] = {
/**
* Block for constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1",
"args0": [
{
"type": "field_dropdown",
"name": "CONSTANT",
"options": [
['\u03c0', 'PI'],
['e', 'E'],
['\u03c6', 'GOLDEN_RATIO'],
['sqrt(2)', 'SQRT2'],
['sqrt(\u00bd)', 'SQRT1_2'],
['\u221e', 'INFINITY']
]
}
],
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"tooltip": Blockly.Msg.MATH_CONSTANT_TOOLTIP,
"helpUrl": Blockly.Msg.MATH_CONSTANT_HELPURL
});
}
};
Blockly.Blocks['math_number_property'] = {
/**
* Block for checking if a number is even, odd, prime, whole, positive,
* negative or if it is divisible by certain number.
* @this Blockly.Block
*/
init: function() {
var PROPERTIES =
[[Blockly.Msg.MATH_IS_EVEN, 'EVEN'],
[Blockly.Msg.MATH_IS_ODD, 'ODD'],
[Blockly.Msg.MATH_IS_PRIME, 'PRIME'],
[Blockly.Msg.MATH_IS_WHOLE, 'WHOLE'],
[Blockly.Msg.MATH_IS_POSITIVE, 'POSITIVE'],
[Blockly.Msg.MATH_IS_NEGATIVE, 'NEGATIVE'],
[Blockly.Msg.MATH_IS_DIVISIBLE_BY, 'DIVISIBLE_BY']];
this.setColour(Blockly.Blocks.math.HUE);
this.appendValueInput('NUMBER_TO_CHECK')
.setCheck('Number');
var dropdown = new Blockly.FieldDropdown(PROPERTIES, function(option) {
var divisorInput = (option == 'DIVISIBLE_BY');
this.sourceBlock_.updateShape_(divisorInput);
});
this.appendDummyInput()
.appendField(dropdown, 'PROPERTY');
this.setInputsInline(true);
this.setOutput(true, 'Boolean');
this.setTooltip(Blockly.Msg.MATH_IS_TOOLTIP);
},
// Block for basic arithmetic operator.
{
"type": "math_arithmetic",
"message0": "%1 %2 %3",
"args0": [
{
"type": "input_value",
"name": "A",
"check": "Number"
},
{
"type": "field_dropdown",
"name": "OP",
"options": [
["%{BKY_MATH_ADDITION_SYMBOL}", "ADD"],
["%{BKY_MATH_SUBTRACTION_SYMBOL}", "MINUS"],
["%{BKY_MATH_MULTIPLICATION_SYMBOL}", "MULTIPLY"],
["%{BKY_MATH_DIVISION_SYMBOL}", "DIVIDE"],
["%{BKY_MATH_POWER_SYMBOL}", "POWER"]
]
},
{
"type": "input_value",
"name": "B",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"helpUrl": "%{BKY_MATH_ARITHMETIC_HELPURL}",
"extensions": ["math_op_tooltip"]
},
// Block for advanced math operators with single operand.
{
"type": "math_single",
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
["%{BKY_MATH_SINGLE_OP_ROOT}", 'ROOT'],
["%{BKY_MATH_SINGLE_OP_ABSOLUTE}", 'ABS'],
['-', 'NEG'],
['ln', 'LN'],
['log10', 'LOG10'],
['e^', 'EXP'],
['10^', 'POW10']
]
},
{
"type": "input_value",
"name": "NUM",
"check": "Number"
}
],
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"helpUrl": "%{BKY_MATH_SINGLE_HELPURL}",
"extensions": ["math_op_tooltip"]
},
// Block for trigonometry operators.
{
"type": "math_trig",
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
["%{BKY_MATH_TRIG_SIN}", "SIN"],
["%{BKY_MATH_TRIG_COS}", "COS"],
["%{BKY_MATH_TRIG_TAN}", "TAN"],
["%{BKY_MATH_TRIG_ASIN}", "ASIN"],
["%{BKY_MATH_TRIG_ACOS}", "ACOS"],
["%{BKY_MATH_TRIG_ATAN}", "ATAN"]
]
},
{
"type": "input_value",
"name": "NUM",
"check": "Number"
}
],
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"helpUrl": "%{BKY_MATH_TRIG_HELPURL}",
"extensions": ["math_op_tooltip"]
},
// Block for constants: PI, E, the Golden Ratio, sqrt(2), 1/sqrt(2), INFINITY.
{
"type": "math_constant",
"message0": "%1",
"args0": [
{
"type": "field_dropdown",
"name": "CONSTANT",
"options": [
["\u03c0", "PI"],
["e", "E"],
["\u03c6", "GOLDEN_RATIO"],
["sqrt(2)", "SQRT2"],
["sqrt(\u00bd)", "SQRT1_2"],
["\u221e", "INFINITY"]
]
}
],
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"tooltip": "%{BKY_MATH_CONSTANT_TOOLTIP}",
"helpUrl": "%{BKY_MATH_CONSTANT_HELPURL}"
},
// Block for checking if a number is even, odd, prime, whole, positive,
// negative or if it is divisible by certain number.
{
"type": "math_number_property",
"message0": "%1 %2",
"args0": [
{
"type": "input_value",
"name": "NUMBER_TO_CHECK",
"check": "Number"
},
{
"type": "field_dropdown",
"name": "PROPERTY",
"options": [
["%{BKY_MATH_IS_EVEN}", "EVEN"],
["%{BKY_MATH_IS_ODD}", "ODD"],
["%{BKY_MATH_IS_PRIME}", "PRIME"],
["%{BKY_MATH_IS_WHOLE}", "WHOLE"],
["%{BKY_MATH_IS_POSITIVE}", "POSITIVE"],
["%{BKY_MATH_IS_NEGATIVE}", "NEGATIVE"],
["%{BKY_MATH_IS_DIVISIBLE_BY}", "DIVISIBLE_BY"]
]
}
],
"inputsInline": true,
"output": "Boolean",
"colour": "%{BKY_MATH_HUE}",
"tooltip": "%{BKY_MATH_IS_TOOLTIP}",
"extensions": ["math_is_divisibleby_mutator"]
},
// Block for adding to a variable in place.
{
"type": "math_change",
"message0": "%{BKY_MATH_CHANGE_TITLE}",
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variable": "%{BKY_MATH_CHANGE_TITLE_ITEM}"
},
{
"type": "input_value",
"name": "DELTA",
"check": "Number"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": "%{BKY_MATH_HUE}",
"helpUrl": "%{BKY_MATH_CHANGE_HELPURL}",
"extensions": ["math_change_tooltip"]
},
// Block for rounding functions.
{
"type": "math_round",
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
["%{BKY_MATH_ROUND_OPERATOR_ROUND}", "ROUND"],
["%{BKY_MATH_ROUND_OPERATOR_ROUNDUP}", "ROUNDUP"],
["%{BKY_MATH_ROUND_OPERATOR_ROUNDDOWN}", "ROUNDDOWN"]
]
},
{
"type": "input_value",
"name": "NUM",
"check": "Number"
}
],
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"helpUrl": "%{BKY_MATH_ROUND_HELPURL}",
"tooltip": "%{BKY_MATH_ROUND_TOOLTIP}"
},
// Block for evaluating a list of numbers to return sum, average, min, max,
// etc. Some functions also work on text (min, max, mode, median).
{
"type": "math_on_list",
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
["%{BKY_MATH_ONLIST_OPERATOR_SUM}", "SUM"],
["%{BKY_MATH_ONLIST_OPERATOR_MIN}", "MIN"],
["%{BKY_MATH_ONLIST_OPERATOR_MAX}", "MAX"],
["%{BKY_MATH_ONLIST_OPERATOR_AVERAGE}", "AVERAGE"],
["%{BKY_MATH_ONLIST_OPERATOR_MEDIAN}", "MEDIAN"],
["%{BKY_MATH_ONLIST_OPERATOR_MODE}", "MODE"],
["%{BKY_MATH_ONLIST_OPERATOR_STD_DEV}", "STD_DEV"],
["%{BKY_MATH_ONLIST_OPERATOR_RANDOM}", "RANDOM"]
]
},
{
"type": "input_value",
"name": "LIST",
"check": "Array"
}
],
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"helpUrl": "%{BKY_MATH_ONLIST_HELPURL}",
"extensions": ["math_op_tooltip", "math_modes_of_list_mutator"]
},
// Block for remainder of a division.
{
"type": "math_modulo",
"message0": "%{BKY_MATH_MODULO_TITLE}",
"args0": [
{
"type": "input_value",
"name": "DIVIDEND",
"check": "Number"
},
{
"type": "input_value",
"name": "DIVISOR",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"tooltip": "%{BKY_MATH_MODULO_TOOLTIP}",
"helpUrl": "%{BKY_MATH_MODULO_HELPURL}"
},
// Block for constraining a number between two limits.
{
"type": "math_constrain",
"message0": "%{BKY_MATH_CONSTRAIN_TITLE}",
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "Number"
},
{
"type": "input_value",
"name": "LOW",
"check": "Number"
},
{
"type": "input_value",
"name": "HIGH",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"tooltip": "%{BKY_MATH_CONSTRAIN_TOOLTIP}",
"helpUrl": "%{BKY_MATH_CONSTRAIN_HELPURL}"
},
// Block for random integer between [X] and [Y].
{
"type": "math_random_int",
"message0": "%{BKY_MATH_RANDOM_INT_TITLE}",
"args0": [
{
"type": "input_value",
"name": "FROM",
"check": "Number"
},
{
"type": "input_value",
"name": "TO",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"tooltip": "%{BKY_MATH_RANDOM_INT_TOOLTIP}",
"helpUrl": "%{BKY_MATH_RANDOM_INT_HELPURL}"
},
// Block for random integer between [X] and [Y].
{
"type": "math_random_float",
"message0": "%{BKY_MATH_RANDOM_FLOAT_TITLE_RANDOM}",
"output": "Number",
"colour": "%{BKY_MATH_HUE}",
"tooltip": "%{BKY_MATH_RANDOM_FLOAT_TOOLTIP}",
"helpUrl": "%{BKY_MATH_RANDOM_FLOAT_HELPURL}"
}
]); // End of defineBlocksWithJsonArray(...) (Do not delete this comment.)
/**
* Mapping of math block OP value to tooltip message for blocks
* math_arithmetic, math_simple, math_trig, and math_on_lists.
*
* Messages are not dereferenced here in order to capture possible language
* changes.
*/
Blockly.Blocks.math.TOOLTIPS_BY_OP_ = {
// math_arithmetic
'ADD': '%{BKY_MATH_ARITHMETIC_TOOLTIP_ADD}',
'MINUS': '%{BKY_MATH_ARITHMETIC_TOOLTIP_MINUS}',
'MULTIPLY': '%{BKY_MATH_ARITHMETIC_TOOLTIP_MULTIPLY}',
'DIVIDE': '%{BKY_MATH_ARITHMETIC_TOOLTIP_DIVIDE}',
'POWER': '%{BKY_MATH_ARITHMETIC_TOOLTIP_POWER}',
// math_simple
'ROOT': '%{BKY_MATH_SINGLE_TOOLTIP_ROOT}',
'ABS': '%{BKY_MATH_SINGLE_TOOLTIP_ABS}',
'NEG': '%{BKY_MATH_SINGLE_TOOLTIP_NEG}',
'LN': '%{BKY_MATH_SINGLE_TOOLTIP_LN}',
'LOG10': '%{BKY_MATH_SINGLE_TOOLTIP_LOG10}',
'EXP': '%{BKY_MATH_SINGLE_TOOLTIP_EXP}',
'POW10': '%{BKY_MATH_SINGLE_TOOLTIP_POW10}',
// math_trig
'SIN': '%{BKY_MATH_TRIG_TOOLTIP_SIN}',
'COS': '%{BKY_MATH_TRIG_TOOLTIP_COS}',
'TAN': '%{BKY_MATH_TRIG_TOOLTIP_TAN}',
'ASIN': '%{BKY_MATH_TRIG_TOOLTIP_ASIN}',
'ACOS': '%{BKY_MATH_TRIG_TOOLTIP_ACOS}',
'ATAN': '%{BKY_MATH_TRIG_TOOLTIP_ATAN}',
// math_on_lists
'SUM': '%{BKY_MATH_ONLIST_TOOLTIP_SUM}',
'MIN': '%{BKY_MATH_ONLIST_TOOLTIP_MIN}',
'MAX': '%{BKY_MATH_ONLIST_TOOLTIP_MAX}',
'AVERAGE': '%{BKY_MATH_ONLIST_TOOLTIP_AVERAGE}',
'MEDIAN': '%{BKY_MATH_ONLIST_TOOLTIP_MEDIAN}',
'MODE': '%{BKY_MATH_ONLIST_TOOLTIP_MODE}',
'STD_DEV': '%{BKY_MATH_ONLIST_TOOLTIP_STD_DEV}',
'RANDOM': '%{BKY_MATH_ONLIST_TOOLTIP_RANDOM}',
};
Blockly.Extensions.register("math_op_tooltip",
Blockly.Extensions.buildTooltipForDropdown(
'OP', Blockly.Blocks.math.TOOLTIPS_BY_OP_));
Blockly.Blocks.math.IS_DIVISIBLEBY_MUTATOR_MIXIN_ = {
/**
* Create XML to represent whether the 'divisorInput' should be present.
* @return {Element} XML storage element.
@@ -299,114 +460,34 @@ Blockly.Blocks['math_number_property'] = {
}
};
Blockly.Blocks['math_change'] = {
Blockly.Extensions.register("math_is_divisibleby_mutator",
/**
* Block for adding to a variable in place.
* @this Blockly.Block
* Update shape (add/remove divisor input) based on whether property is
* "divisble by".
* @this {Blockly.Block}
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.MATH_CHANGE_TITLE,
"args0": [
{
"type": "field_variable",
"name": "VAR",
"variable": Blockly.Msg.MATH_CHANGE_TITLE_ITEM
},
{
"type": "input_value",
"name": "DELTA",
"check": "Number"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": Blockly.Blocks.variables.HUE,
"helpUrl": Blockly.Msg.MATH_CHANGE_HELPURL
function() {
goog.mixin(this, Blockly.Blocks.math.IS_DIVISIBLEBY_MUTATOR_MIXIN_);
this.getField('PROPERTY').setValidator(function(option) {
var divisorInput = (option == 'DIVISIBLE_BY');
this.sourceBlock_.updateShape_(divisorInput);
});
// Assign 'this' to a variable for use in the tooltip closure below.
});
Blockly.Extensions.register("math_change_tooltip",
/**
* Update tooltip with named variable.
* @this {Blockly.Block}
*/
function() {
var thisBlock = this;
this.setTooltip(function() {
return Blockly.Msg.MATH_CHANGE_TOOLTIP.replace('%1',
thisBlock.getFieldValue('VAR'));
});
}
};
});
Blockly.Blocks['math_round'] = {
/**
* Block for rounding functions.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1 %2",
"args0": [
{
"type": "field_dropdown",
"name": "OP",
"options": [
[Blockly.Msg.MATH_ROUND_OPERATOR_ROUND, 'ROUND'],
[Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDUP, 'ROUNDUP'],
[Blockly.Msg.MATH_ROUND_OPERATOR_ROUNDDOWN, 'ROUNDDOWN']
]
},
{
"type": "input_value",
"name": "NUM",
"check": "Number"
}
],
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"tooltip": Blockly.Msg.MATH_ROUND_TOOLTIP,
"helpUrl": Blockly.Msg.MATH_ROUND_HELPURL
});
}
};
Blockly.Blocks['math_on_list'] = {
/**
* Block for evaluating a list of numbers to return sum, average, min, max,
* etc. Some functions also work on text (min, max, mode, median).
* @this Blockly.Block
*/
init: function() {
var OPERATORS =
[[Blockly.Msg.MATH_ONLIST_OPERATOR_SUM, 'SUM'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_MIN, 'MIN'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_MAX, 'MAX'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_AVERAGE, 'AVERAGE'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_MEDIAN, 'MEDIAN'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_MODE, 'MODE'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_STD_DEV, 'STD_DEV'],
[Blockly.Msg.MATH_ONLIST_OPERATOR_RANDOM, 'RANDOM']];
// Assign 'this' to a variable for use in the closures below.
var thisBlock = this;
this.setHelpUrl(Blockly.Msg.MATH_ONLIST_HELPURL);
this.setColour(Blockly.Blocks.math.HUE);
this.setOutput(true, 'Number');
var dropdown = new Blockly.FieldDropdown(OPERATORS, function(newOp) {
thisBlock.updateType_(newOp);
});
this.appendValueInput('LIST')
.setCheck('Array')
.appendField(dropdown, 'OP');
this.setTooltip(function() {
var mode = thisBlock.getFieldValue('OP');
var TOOLTIPS = {
'SUM': Blockly.Msg.MATH_ONLIST_TOOLTIP_SUM,
'MIN': Blockly.Msg.MATH_ONLIST_TOOLTIP_MIN,
'MAX': Blockly.Msg.MATH_ONLIST_TOOLTIP_MAX,
'AVERAGE': Blockly.Msg.MATH_ONLIST_TOOLTIP_AVERAGE,
'MEDIAN': Blockly.Msg.MATH_ONLIST_TOOLTIP_MEDIAN,
'MODE': Blockly.Msg.MATH_ONLIST_TOOLTIP_MODE,
'STD_DEV': Blockly.Msg.MATH_ONLIST_TOOLTIP_STD_DEV,
'RANDOM': Blockly.Msg.MATH_ONLIST_TOOLTIP_RANDOM
};
return TOOLTIPS[mode];
});
},
Blockly.Blocks.math.LIST_MODES_MUTATOR_MIXIN_ = {
/**
* Modify this block to have the correct output type.
* @param {string} newOp Either 'MODE' or some op than returns a number.
@@ -439,111 +520,15 @@ Blockly.Blocks['math_on_list'] = {
this.updateType_(xmlElement.getAttribute('op'));
}
};
Blockly.Blocks['math_modulo'] = {
Blockly.Extensions.register("math_modes_of_list_mutator",
/**
* Block for remainder of a division.
* @this Blockly.Block
* Update output type based on whether the operator is "mode".
* @this {Blockly.Block}
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.MATH_MODULO_TITLE,
"args0": [
{
"type": "input_value",
"name": "DIVIDEND",
"check": "Number"
},
{
"type": "input_value",
"name": "DIVISOR",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"tooltip": Blockly.Msg.MATH_MODULO_TOOLTIP,
"helpUrl": Blockly.Msg.MATH_MODULO_HELPURL
function() {
var thisBlock = this;
goog.mixin(this, Blockly.Blocks.math.LIST_MODES_MUTATOR_MIXIN_);
this.getField('OP').setValidator(function(newOp) {
thisBlock.updateType_(newOp);
});
}
};
Blockly.Blocks['math_constrain'] = {
/**
* Block for constraining a number between two limits.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.MATH_CONSTRAIN_TITLE,
"args0": [
{
"type": "input_value",
"name": "VALUE",
"check": "Number"
},
{
"type": "input_value",
"name": "LOW",
"check": "Number"
},
{
"type": "input_value",
"name": "HIGH",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"tooltip": Blockly.Msg.MATH_CONSTRAIN_TOOLTIP,
"helpUrl": Blockly.Msg.MATH_CONSTRAIN_HELPURL
});
}
};
Blockly.Blocks['math_random_int'] = {
/**
* Block for random integer between [X] and [Y].
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.MATH_RANDOM_INT_TITLE,
"args0": [
{
"type": "input_value",
"name": "FROM",
"check": "Number"
},
{
"type": "input_value",
"name": "TO",
"check": "Number"
}
],
"inputsInline": true,
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"tooltip": Blockly.Msg.MATH_RANDOM_INT_TOOLTIP,
"helpUrl": Blockly.Msg.MATH_RANDOM_INT_HELPURL
});
}
};
Blockly.Blocks['math_random_float'] = {
/**
* Block for random fraction between 0 and 1.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.MATH_RANDOM_FLOAT_TITLE_RANDOM,
"output": "Number",
"colour": Blockly.Blocks.math.HUE,
"tooltip": Blockly.Msg.MATH_RANDOM_FLOAT_TOOLTIP,
"helpUrl": Blockly.Msg.MATH_RANDOM_FLOAT_HELPURL
});
}
};
});

View File

@@ -1024,6 +1024,11 @@ Blockly.Block.prototype.jsonInit = function(json) {
var localizedValue = Blockly.utils.replaceMessageReferences(rawValue);
this.setHelpUrl(localizedValue);
}
if (goog.isString(json['extensions'])) {
console.warn('JSON attribute \'extensions\' should be an array of ' +
'strings. Found raw string in JSON for \'' + json['type'] + '\' block.');
json['extensions'] = [json['extensions']]; // Correct and continue.
}
if (Array.isArray(json['extensions'])) {
var extensionNames = json['extensions'];
for (var i = 0; i < extensionNames.length; ++i) {

View File

@@ -74,6 +74,82 @@ Blockly.Extensions.apply = function(name, block) {
extensionFn.apply(block);
};
/**
* Builds an extension function that will map a dropdown value to a tooltip string.
* Tooltip strings will be passed through Blockly.utils.checkMessageReferences(..)
* immediately and Blockly.utils.replaceMessageReferences(..) at display time.
* @param {string} dropdownName The name of the field whose value is the key
* to the lookup table.
* @param {!Object<string, string>} lookupTable The table of field values to
* tooltip text.
* @return {Function} The extension function.
*/
Blockly.Extensions.buildTooltipForDropdown = function(dropdownName, lookupTable) {
// List of block types already validated, to minimize duplicate warnings.
var blockTypesChecked = [];
// Validate message strings early.
for (var key in lookupTable) {
Blockly.utils.checkMessageReferences(lookupTable[key]);
}
/**
* The actual extension.
* @this {Blockly.Block}
*/
return function() {
var thisBlock = this;
if (this.type && !blockTypesChecked.includes(this.type)) {
Blockly.Extensions.checkDropdownOptionsInTable_(
this, dropdownName, lookupTable);
blockTypesChecked.push(this.type);
}
this.setTooltip(function() {
var value = thisBlock.getFieldValue(dropdownName);
var tooltip = lookupTable[value];
if (tooltip == null) {
if (!blockTypesChecked.includes(thisBlock.type)) {
// Warn for missing values on generated tooltips
var warning = 'No tooltip mapping for value ' + value +
' of field ' + dropdownName;
if (thisBlock.type != null) {
warning += (' of block type ' + thisBlock.type);
}
console.warn(warning + '.');
}
} else {
tooltip = Blockly.utils.replaceMessageReferences(tooltip);
}
return tooltip;
});
};
};
/**
* Checks all options keys are present in the provided string lookup table.
* Emits console warnings when they are not.
* @param {!Blockly.Block} block The block containing the dropdown
* @param {string} dropdownName The name of the dropdown
* @param {!Object<string, string>} lookupTable The string lookup table
*/
Blockly.Extensions.checkDropdownOptionsInTable_ =
function(block, dropdownName, lookupTable) {
// Validate all dropdown options have values.
var dropdown = block.getField(dropdownName);
if (!dropdown.isOptionListDynamic()) {
var options = dropdown.getOptions();
for (var i = 0; i < options.length; ++i) {
var optionKey = options[i][1]; // label, then value
if (lookupTable[optionKey] == null) {
console.warn('No tooltip mapping for value ' + optionKey +
' of field ' + dropdownName + ' of block type ' + block.type);
}
}
}
};
/**
* Configures the tooltip to mimic the parent block when connected. Otherwise,
* uses the tooltip text at the time this extension is initialized. This takes

View File

@@ -52,7 +52,7 @@ goog.require('goog.userAgent');
Blockly.FieldDropdown = function(menuGenerator, opt_validator) {
this.menuGenerator_ = menuGenerator;
this.trimOptions_();
var firstTuple = this.getOptions_()[0];
var firstTuple = this.getOptions()[0];
// Call parent's constructor.
Blockly.FieldDropdown.superClass_.constructor.call(this, firstTuple[1],
@@ -138,7 +138,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
var menu = new goog.ui.Menu();
menu.setRightToLeft(this.sourceBlock_.RTL);
var options = this.getOptions_();
var options = this.getOptions();
for (var i = 0; i < options.length; i++) {
var content = options[i][0]; // Human-readable text or image.
var value = options[i][1]; // Language-neutral value.
@@ -230,7 +230,7 @@ Blockly.FieldDropdown.prototype.onItemSelected = function(menu, menuItem) {
if (value !== null) {
this.setValue(value);
}
}
};
/**
* Factor out common words in statically defined options.
@@ -288,13 +288,19 @@ Blockly.FieldDropdown.prototype.trimOptions_ = function() {
this.menuGenerator_ = newOptions;
};
/**
* @return {boolean} True if the option list is generated by a function. Otherwise false.
*/
Blockly.FieldDropdown.prototype.isOptionListDynamic = function() {
return goog.isFunction(this.menuGenerator_);
};
/**
* Return a list of the options for this dropdown.
* @return {!Array.<!Array>} Array of option tuples:
* (human-readable text or image, language-neutral name).
* @private
*/
Blockly.FieldDropdown.prototype.getOptions_ = function() {
Blockly.FieldDropdown.prototype.getOptions = function() {
if (goog.isFunction(this.menuGenerator_)) {
return this.menuGenerator_.call(this);
}
@@ -323,7 +329,7 @@ Blockly.FieldDropdown.prototype.setValue = function(newValue) {
}
this.value_ = newValue;
// Look up and display the human-readable text.
var options = this.getOptions_();
var options = this.getOptions();
for (var i = 0; i < options.length; i++) {
// Options are tuples of human-readable text and language-neutral values.
if (options[i][1] == newValue) {

View File

@@ -84,15 +84,15 @@ Blockly.utils.removeClass = function(element, className) {
/**
* Checks if an element has the specified CSS class.
* Similar to Closure's goog.dom.classes.has, except it handles SVG elements.
* @param {!Element} element DOM element to check.
* @param {string} className Name of class to check.
* @return {boolean} True if class exists, false otherwise.
* @private
*/
Blockly.utils.hasClass = function(element, className) {
var classes = element.getAttribute('class');
return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1;
};
* @param {!Element} element DOM element to check.
* @param {string} className Name of class to check.
* @return {boolean} True if class exists, false otherwise.
* @private
*/
Blockly.utils.hasClass = function(element, className) {
var classes = element.getAttribute('class');
return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1;
};
/**
* Don't do anything for this event, just halt propagation.
@@ -144,7 +144,7 @@ Blockly.utils.getRelativeXY = function(element) {
}
}
// Then check for style = transform: translate(...) or translate3d(...)
// Then check for style = transform: translate(...) or translate3d(...)
var style = element.getAttribute('style');
if (style && style.indexOf('translate') > -1) {
var styleComponents = style.match(Blockly.utils.getRelativeXY.XY_2D_REGEX_);
@@ -164,7 +164,7 @@ Blockly.utils.getRelativeXY = function(element) {
/**
* Return the coordinates of the top-left corner of this element relative to
* the div blockly was injected into.
* the div blockly was injected into.
* @param {!Element} element SVG element to find the coordinates of. If this is
* not a child of the div blockly was injected into, the behaviour is
* undefined.
@@ -185,7 +185,7 @@ Blockly.utils.getInjectionDivXY_ = function(element) {
}
element = element.parentNode;
}
return new goog.math.Coordinate(x, y);
return new goog.math.Coordinate(x, y);
};
/**
@@ -195,9 +195,9 @@ Blockly.utils.getInjectionDivXY_ = function(element) {
* @private
*/
Blockly.utils.getScale_ = function(element) {
var scale = 1;
var scale = 1;
var transform = element.getAttribute('transform');
if (transform) {
if (transform) {
var transformComponents =
transform.match(Blockly.utils.getScale_.REGEXP_);
if (transformComponents && transformComponents[0]) {
@@ -315,7 +315,7 @@ Blockly.utils.shortestStringLength = function(array) {
if (!array.length) {
return 0;
}
return array.reduce(function (a, b) {
return array.reduce(function(a, b) {
return a.length < b.length ? a : b;
}).length;
};
@@ -402,7 +402,7 @@ Blockly.utils.commonWordSuffix = function(array, opt_shortest) {
*/
Blockly.utils.tokenizeInterpolation = function(message) {
return Blockly.utils.tokenizeInterpolation_(message, true);
}
};
/**
* Replaces string table references in a message string. For example,
@@ -416,7 +416,34 @@ Blockly.utils.replaceMessageReferences = function(message) {
// When parseInterpolationTokens == false, interpolatedResult should be at
// most length 1.
return interpolatedResult.length ? interpolatedResult[0] : "";
}
};
/**
* Validates that any %{BKY_...} references in the message refer to keys of
* the Blockly.Msg string table.
* @param {string} message Text which might contain string table references.
* @return {boolean} True if all message references have matching values.
* Otherwise, false.
*/
Blockly.utils.checkMessageReferences = function(message) {
var isValid = true; // True until a bad reference is found
var regex = /%{BKY_([a-zA-Z][a-zA-Z0-9_]*)}/g;
var match = regex.exec(message);
while (match != null) {
var msgKey = match[1];
if (Blockly.Msg[msgKey] == null) {
console.log('WARNING: No message string for %{BKY_' + msgKey + '}.');
isValid = false;
}
// Re-run on remainder of sting.
message = message.substring(match.index + msgKey.length + 1);
match = regex.exec(message);
}
return isValid;
};
/**
* Internal implemention of the message reference and interpolation token