Files
blockly/blocks/logic.js
Andrew n marshall ac3df2759c PR #818: Adding support for string table lookups in dropdown field labels
Adding support for string table lookups in dropdown field labels specified in JSON.

Adds Blockly.utils.replaceMessageReferences() method to handle string replacement without interpolation tokens.  Effectively uses the same old code, now moved into tokenizeInterpolation_(), which takes a parseInterpolationTokens option.

Replaces the direct JavaScript references (not pure JSON, and thus not portable).

Demonstrating this behavior in the logic_boolean dropdown.
2017-01-11 15:47:56 -08:00

527 lines
16 KiB
JavaScript

/**
* @license
* Visual Blocks Editor
*
* Copyright 2012 Google Inc.
* https://developers.google.com/blockly/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @fileoverview Logic blocks for Blockly.
* @author q.neutron@gmail.com (Quynh Neutron)
*/
'use strict';
goog.provide('Blockly.Blocks.logic');
goog.require('Blockly.Blocks');
/**
* Common HSV hue for all blocks in this category.
*/
Blockly.Blocks.logic.HUE = 210;
Blockly.Blocks['controls_if'] = {
/**
* Block for if/elseif/else condition.
* @this Blockly.Block
*/
init: function() {
this.setHelpUrl(Blockly.Msg.CONTROLS_IF_HELPURL);
this.setColour(Blockly.Blocks.logic.HUE);
this.appendValueInput('IF0')
.setCheck('Boolean')
.appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
this.appendStatementInput('DO0')
.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setMutator(new Blockly.Mutator(['controls_if_elseif',
'controls_if_else']));
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
if (!thisBlock.elseifCount_ && !thisBlock.elseCount_) {
return Blockly.Msg.CONTROLS_IF_TOOLTIP_1;
} else if (!thisBlock.elseifCount_ && thisBlock.elseCount_) {
return Blockly.Msg.CONTROLS_IF_TOOLTIP_2;
} else if (thisBlock.elseifCount_ && !thisBlock.elseCount_) {
return Blockly.Msg.CONTROLS_IF_TOOLTIP_3;
} else if (thisBlock.elseifCount_ && thisBlock.elseCount_) {
return Blockly.Msg.CONTROLS_IF_TOOLTIP_4;
}
return '';
});
this.elseifCount_ = 0;
this.elseCount_ = 0;
},
/**
* Create XML to represent the number of else-if and else inputs.
* @return {Element} XML storage element.
* @this Blockly.Block
*/
mutationToDom: function() {
if (!this.elseifCount_ && !this.elseCount_) {
return null;
}
var container = document.createElement('mutation');
if (this.elseifCount_) {
container.setAttribute('elseif', this.elseifCount_);
}
if (this.elseCount_) {
container.setAttribute('else', 1);
}
return container;
},
/**
* Parse XML to restore the else-if and else inputs.
* @param {!Element} xmlElement XML storage element.
* @this Blockly.Block
*/
domToMutation: function(xmlElement) {
this.elseifCount_ = parseInt(xmlElement.getAttribute('elseif'), 10) || 0;
this.elseCount_ = parseInt(xmlElement.getAttribute('else'), 10) || 0;
this.updateShape_();
},
/**
* Populate the mutator's dialog with this block's components.
* @param {!Blockly.Workspace} workspace Mutator's workspace.
* @return {!Blockly.Block} Root block in mutator.
* @this Blockly.Block
*/
decompose: function(workspace) {
var containerBlock = workspace.newBlock('controls_if_if');
containerBlock.initSvg();
var connection = containerBlock.nextConnection;
for (var i = 1; i <= this.elseifCount_; i++) {
var elseifBlock = workspace.newBlock('controls_if_elseif');
elseifBlock.initSvg();
connection.connect(elseifBlock.previousConnection);
connection = elseifBlock.nextConnection;
}
if (this.elseCount_) {
var elseBlock = workspace.newBlock('controls_if_else');
elseBlock.initSvg();
connection.connect(elseBlock.previousConnection);
}
return containerBlock;
},
/**
* Reconfigure this block based on the mutator dialog's components.
* @param {!Blockly.Block} containerBlock Root block in mutator.
* @this Blockly.Block
*/
compose: function(containerBlock) {
var clauseBlock = containerBlock.nextConnection.targetBlock();
// Count number of inputs.
this.elseifCount_ = 0;
this.elseCount_ = 0;
var valueConnections = [null];
var statementConnections = [null];
var elseStatementConnection = null;
while (clauseBlock) {
switch (clauseBlock.type) {
case 'controls_if_elseif':
this.elseifCount_++;
valueConnections.push(clauseBlock.valueConnection_);
statementConnections.push(clauseBlock.statementConnection_);
break;
case 'controls_if_else':
this.elseCount_++;
elseStatementConnection = clauseBlock.statementConnection_;
break;
default:
throw 'Unknown block type.';
}
clauseBlock = clauseBlock.nextConnection &&
clauseBlock.nextConnection.targetBlock();
}
this.updateShape_();
// Reconnect any child blocks.
for (var i = 1; i <= this.elseifCount_; i++) {
Blockly.Mutator.reconnect(valueConnections[i], this, 'IF' + i);
Blockly.Mutator.reconnect(statementConnections[i], this, 'DO' + i);
}
Blockly.Mutator.reconnect(elseStatementConnection, this, 'ELSE');
},
/**
* Store pointers to any connected child blocks.
* @param {!Blockly.Block} containerBlock Root block in mutator.
* @this Blockly.Block
*/
saveConnections: function(containerBlock) {
var clauseBlock = containerBlock.nextConnection.targetBlock();
var i = 1;
while (clauseBlock) {
switch (clauseBlock.type) {
case 'controls_if_elseif':
var inputIf = this.getInput('IF' + i);
var inputDo = this.getInput('DO' + i);
clauseBlock.valueConnection_ =
inputIf && inputIf.connection.targetConnection;
clauseBlock.statementConnection_ =
inputDo && inputDo.connection.targetConnection;
i++;
break;
case 'controls_if_else':
var inputDo = this.getInput('ELSE');
clauseBlock.statementConnection_ =
inputDo && inputDo.connection.targetConnection;
break;
default:
throw 'Unknown block type.';
}
clauseBlock = clauseBlock.nextConnection &&
clauseBlock.nextConnection.targetBlock();
}
},
/**
* Modify this block to have the correct number of inputs.
* @private
* @this Blockly.Block
*/
updateShape_: function() {
// Delete everything.
if (this.getInput('ELSE')) {
this.removeInput('ELSE');
}
var i = 1;
while (this.getInput('IF' + i)) {
this.removeInput('IF' + i);
this.removeInput('DO' + i);
i++;
}
// Rebuild block.
for (var i = 1; i <= this.elseifCount_; i++) {
this.appendValueInput('IF' + i)
.setCheck('Boolean')
.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSEIF);
this.appendStatementInput('DO' + i)
.appendField(Blockly.Msg.CONTROLS_IF_MSG_THEN);
}
if (this.elseCount_) {
this.appendStatementInput('ELSE')
.appendField(Blockly.Msg.CONTROLS_IF_MSG_ELSE);
}
}
};
Blockly.Blocks['controls_if_if'] = {
/**
* Mutator block for if container.
* @this Blockly.Block
*/
init: function() {
this.setColour(Blockly.Blocks.logic.HUE);
this.appendDummyInput()
.appendField(Blockly.Msg.CONTROLS_IF_IF_TITLE_IF);
this.setNextStatement(true);
this.setTooltip(Blockly.Msg.CONTROLS_IF_IF_TOOLTIP);
this.contextMenu = false;
}
};
Blockly.Blocks['controls_if_elseif'] = {
/**
* Mutator block for else-if condition.
* @this Blockly.Block
*/
init: function() {
this.setColour(Blockly.Blocks.logic.HUE);
this.appendDummyInput()
.appendField(Blockly.Msg.CONTROLS_IF_ELSEIF_TITLE_ELSEIF);
this.setPreviousStatement(true);
this.setNextStatement(true);
this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP);
this.contextMenu = false;
}
};
Blockly.Blocks['controls_if_else'] = {
/**
* Mutator block for else condition.
* @this Blockly.Block
*/
init: function() {
this.setColour(Blockly.Blocks.logic.HUE);
this.appendDummyInput()
.appendField(Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE);
this.setPreviousStatement(true);
this.setTooltip(Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP);
this.contextMenu = false;
}
};
Blockly.Blocks['controls_ifelse'] = {
/**
* If/else block that does not use a mutator.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%{BKY_CONTROLS_IF_MSG_IF} %1",
"args0": [
{
"type": "input_value",
"name": "IF0",
"check": "Boolean"
}
],
"message1": "%{BKY_CONTROLS_IF_MSG_THEN} %1",
"args1": [
{
"type": "input_statement",
"name": "DO0"
}
],
"message2": "%{BKY_CONTROLS_IF_MSG_ELSE} %1",
"args2": [
{
"type": "input_statement",
"name": "ELSE"
}
],
"previousStatement": null,
"nextStatement": null,
"colour": Blockly.Blocks.logic.HUE,
"tooltip": Blockly.Msg.CONTROLS_IF_TOOLTIP_2,
"helpUrl": Blockly.Msg.CONTROLS_IF_HELPURL
});
}
};
Blockly.Blocks['logic_compare'] = {
/**
* Block for comparison operator.
* @this Blockly.Block
*/
init: function() {
var rtlOperators = [
['=', 'EQ'],
['\u2260', 'NEQ'],
['\u200F<\u200F', 'LT'],
['\u200F\u2264\u200F', 'LTE'],
['\u200F>\u200F', 'GT'],
['\u200F\u2265\u200F', 'GTE']
];
var ltrOperators = [
['=', 'EQ'],
['\u2260', 'NEQ'],
['<', 'LT'],
['\u2264', 'LTE'],
['>', 'GT'],
['\u2265', 'GTE']
];
var OPERATORS = this.RTL ? rtlOperators : ltrOperators;
this.setHelpUrl(Blockly.Msg.LOGIC_COMPARE_HELPURL);
this.setColour(Blockly.Blocks.logic.HUE);
this.setOutput(true, 'Boolean');
this.appendValueInput('A');
this.appendValueInput('B')
.appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
this.setInputsInline(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var op = thisBlock.getFieldValue('OP');
var TOOLTIPS = {
'EQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_EQ,
'NEQ': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_NEQ,
'LT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LT,
'LTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_LTE,
'GT': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GT,
'GTE': Blockly.Msg.LOGIC_COMPARE_TOOLTIP_GTE
};
return TOOLTIPS[op];
});
this.prevBlocks_ = [null, null];
},
/**
* Called whenever anything on the workspace changes.
* Prevent mismatched types from being compared.
* @param {!Blockly.Events.Abstract} e Change event.
* @this Blockly.Block
*/
onchange: function(e) {
var blockA = this.getInputTargetBlock('A');
var blockB = this.getInputTargetBlock('B');
// Disconnect blocks that existed prior to this change if they don't match.
if (blockA && blockB &&
!blockA.outputConnection.checkType_(blockB.outputConnection)) {
// Mismatch between two inputs. Disconnect previous and bump it away.
// Ensure that any disconnections are grouped with the causing event.
Blockly.Events.setGroup(e.group);
for (var i = 0; i < this.prevBlocks_.length; i++) {
var block = this.prevBlocks_[i];
if (block === blockA || block === blockB) {
block.unplug();
block.bumpNeighbours_();
}
}
Blockly.Events.setGroup(false);
}
this.prevBlocks_[0] = blockA;
this.prevBlocks_[1] = blockB;
}
};
Blockly.Blocks['logic_operation'] = {
/**
* Block for logical operations: 'and', 'or'.
* @this Blockly.Block
*/
init: function() {
var OPERATORS =
[[Blockly.Msg.LOGIC_OPERATION_AND, 'AND'],
[Blockly.Msg.LOGIC_OPERATION_OR, 'OR']];
this.setHelpUrl(Blockly.Msg.LOGIC_OPERATION_HELPURL);
this.setColour(Blockly.Blocks.logic.HUE);
this.setOutput(true, 'Boolean');
this.appendValueInput('A')
.setCheck('Boolean');
this.appendValueInput('B')
.setCheck('Boolean')
.appendField(new Blockly.FieldDropdown(OPERATORS), 'OP');
this.setInputsInline(true);
// Assign 'this' to a variable for use in the tooltip closure below.
var thisBlock = this;
this.setTooltip(function() {
var op = thisBlock.getFieldValue('OP');
var TOOLTIPS = {
'AND': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_AND,
'OR': Blockly.Msg.LOGIC_OPERATION_TOOLTIP_OR
};
return TOOLTIPS[op];
});
}
};
Blockly.Blocks['logic_negate'] = {
/**
* Block for negation.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.LOGIC_NEGATE_TITLE,
"args0": [
{
"type": "input_value",
"name": "BOOL",
"check": "Boolean"
}
],
"output": "Boolean",
"colour": Blockly.Blocks.logic.HUE,
"tooltip": Blockly.Msg.LOGIC_NEGATE_TOOLTIP,
"helpUrl": Blockly.Msg.LOGIC_NEGATE_HELPURL
});
}
};
Blockly.Blocks['logic_boolean'] = {
/**
* Block for boolean data type: true and false.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": "%1",
"args0": [
{
"type": "field_dropdown",
"name": "BOOL",
"options": [
["%{bky_logic_boolean_true}", "TRUE"],
["%{bky_logic_boolean_false}", "FALSE"]
]
}
],
"output": "Boolean",
"colour": Blockly.Blocks.logic.HUE,
"tooltip": Blockly.Msg.LOGIC_BOOLEAN_TOOLTIP,
"helpUrl": Blockly.Msg.LOGIC_BOOLEAN_HELPURL
});
}
};
Blockly.Blocks['logic_null'] = {
/**
* Block for null data type.
* @this Blockly.Block
*/
init: function() {
this.jsonInit({
"message0": Blockly.Msg.LOGIC_NULL,
"output": null,
"colour": Blockly.Blocks.logic.HUE,
"tooltip": Blockly.Msg.LOGIC_NULL_TOOLTIP,
"helpUrl": Blockly.Msg.LOGIC_NULL_HELPURL
});
}
};
Blockly.Blocks['logic_ternary'] = {
/**
* Block for ternary operator.
* @this Blockly.Block
*/
init: function() {
this.setHelpUrl(Blockly.Msg.LOGIC_TERNARY_HELPURL);
this.setColour(Blockly.Blocks.logic.HUE);
this.appendValueInput('IF')
.setCheck('Boolean')
.appendField(Blockly.Msg.LOGIC_TERNARY_CONDITION);
this.appendValueInput('THEN')
.appendField(Blockly.Msg.LOGIC_TERNARY_IF_TRUE);
this.appendValueInput('ELSE')
.appendField(Blockly.Msg.LOGIC_TERNARY_IF_FALSE);
this.setOutput(true);
this.setTooltip(Blockly.Msg.LOGIC_TERNARY_TOOLTIP);
this.prevParentConnection_ = null;
},
/**
* Called whenever anything on the workspace changes.
* Prevent mismatched types.
* @param {!Blockly.Events.Abstract} e Change event.
* @this Blockly.Block
*/
onchange: function(e) {
var blockA = this.getInputTargetBlock('THEN');
var blockB = this.getInputTargetBlock('ELSE');
var parentConnection = this.outputConnection.targetConnection;
// Disconnect blocks that existed prior to this change if they don't match.
if ((blockA || blockB) && parentConnection) {
for (var i = 0; i < 2; i++) {
var block = (i == 1) ? blockA : blockB;
if (block && !block.outputConnection.checkType_(parentConnection)) {
// Ensure that any disconnections are grouped with the causing event.
Blockly.Events.setGroup(e.group);
if (parentConnection === this.prevParentConnection_) {
this.unplug();
parentConnection.getSourceBlock().bumpNeighbours_();
} else {
block.unplug();
block.bumpNeighbours_();
}
Blockly.Events.setGroup(false);
}
}
}
this.prevParentConnection_ = parentConnection;
}
};