mirror of
https://github.com/scratchfoundation/scratch-blocks.git
synced 2025-08-28 22:10:31 -04:00
Merge from Upstream Blockly (#776)
* WIP merge from upstream google/blockly
* fixing some merging bugs. Getting the drag surface to work, updating some function calls, etc.
* make the trash can lid animate again and fix the cursor to show an x when things will be removed.
* Fix text rendering logic in fields
* Hand apply some more changes from blockly flyout.js to flyout_base.js.
Also revert 08efd1381c
in scratch-blocks since it breaks variables and there is more work fenichel is planning
to do here.
* Fix drop-down field rendering
* Resolve issue with text input field widths on init
* Remove unused blockly (built) files
* Remove unused language files
* Resolve lint issues and rebuild
* Add blockly build targets to cleanup script
* Return 0 if 'this.arrow_' does not exist in a dropdown field
* Remove unused / irrelevant comments
This commit is contained in:
parent
ccd2da9173
commit
be9d5fefed
59 changed files with 4804 additions and 1167 deletions
841
blocks/lists.js
Normal file
841
blocks/lists.js
Normal file
|
@ -0,0 +1,841 @@
|
|||
/**
|
||||
* @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 List blocks for Blockly.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.lists');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
|
||||
/**
|
||||
* Common HSV hue for all blocks in this category.
|
||||
*/
|
||||
Blockly.Blocks.lists.HUE = 260;
|
||||
|
||||
Blockly.Blocks['lists_create_empty'] = {
|
||||
/**
|
||||
* Block for creating an empty list.
|
||||
* The 'list_create_with' block is preferred as it is more flexible.
|
||||
* <block type="lists_create_with">
|
||||
* <mutation items="0"></mutation>
|
||||
* </block>
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LISTS_CREATE_EMPTY_TITLE,
|
||||
"output": "Array",
|
||||
"colour": Blockly.Blocks.lists.HUE,
|
||||
"tooltip": Blockly.Msg.LISTS_CREATE_EMPTY_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.LISTS_CREATE_EMPTY_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_create_with'] = {
|
||||
/**
|
||||
* Block for creating a list with any number of elements of any type.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_CREATE_WITH_HELPURL);
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.itemCount_ = 3;
|
||||
this.updateShape_();
|
||||
this.setOutput(true, 'Array');
|
||||
this.setMutator(new Blockly.Mutator(['lists_create_with_item']));
|
||||
this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_TOOLTIP);
|
||||
},
|
||||
/**
|
||||
* Create XML to represent list inputs.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('items', this.itemCount_);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the list inputs.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
this.itemCount_ = parseInt(xmlElement.getAttribute('items'), 10);
|
||||
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('lists_create_with_container');
|
||||
containerBlock.initSvg();
|
||||
var connection = containerBlock.getInput('STACK').connection;
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
var itemBlock = workspace.newBlock('lists_create_with_item');
|
||||
itemBlock.initSvg();
|
||||
connection.connect(itemBlock.previousConnection);
|
||||
connection = itemBlock.nextConnection;
|
||||
}
|
||||
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 itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
// Count number of inputs.
|
||||
var connections = [];
|
||||
while (itemBlock) {
|
||||
connections.push(itemBlock.valueConnection_);
|
||||
itemBlock = itemBlock.nextConnection &&
|
||||
itemBlock.nextConnection.targetBlock();
|
||||
}
|
||||
// Disconnect any children that don't belong.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
var connection = this.getInput('ADD' + i).connection.targetConnection;
|
||||
if (connection && connections.indexOf(connection) == -1) {
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
this.itemCount_ = connections.length;
|
||||
this.updateShape_();
|
||||
// Reconnect any child blocks.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
Blockly.Mutator.reconnect(connections[i], this, 'ADD' + i);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Store pointers to any connected child blocks.
|
||||
* @param {!Blockly.Block} containerBlock Root block in mutator.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
saveConnections: function(containerBlock) {
|
||||
var itemBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
var i = 0;
|
||||
while (itemBlock) {
|
||||
var input = this.getInput('ADD' + i);
|
||||
itemBlock.valueConnection_ = input && input.connection.targetConnection;
|
||||
i++;
|
||||
itemBlock = itemBlock.nextConnection &&
|
||||
itemBlock.nextConnection.targetBlock();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Modify this block to have the correct number of inputs.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateShape_: function() {
|
||||
if (this.itemCount_ && this.getInput('EMPTY')) {
|
||||
this.removeInput('EMPTY');
|
||||
} else if (!this.itemCount_ && !this.getInput('EMPTY')) {
|
||||
this.appendDummyInput('EMPTY')
|
||||
.appendField(Blockly.Msg.LISTS_CREATE_EMPTY_TITLE);
|
||||
}
|
||||
// Add new inputs.
|
||||
for (var i = 0; i < this.itemCount_; i++) {
|
||||
if (!this.getInput('ADD' + i)) {
|
||||
var input = this.appendValueInput('ADD' + i);
|
||||
if (i == 0) {
|
||||
input.appendField(Blockly.Msg.LISTS_CREATE_WITH_INPUT_WITH);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Remove deleted inputs.
|
||||
while (this.getInput('ADD' + i)) {
|
||||
this.removeInput('ADD' + i);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_create_with_container'] = {
|
||||
/**
|
||||
* Mutator block for list container.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TITLE_ADD);
|
||||
this.appendStatementInput('STACK');
|
||||
this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_CONTAINER_TOOLTIP);
|
||||
this.contextMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_create_with_item'] = {
|
||||
/**
|
||||
* Mutator block for adding items.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.LISTS_CREATE_WITH_ITEM_TITLE);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setTooltip(Blockly.Msg.LISTS_CREATE_WITH_ITEM_TOOLTIP);
|
||||
this.contextMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_repeat'] = {
|
||||
/**
|
||||
* Block for creating a list with one element repeated.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LISTS_REPEAT_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "ITEM"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "NUM",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"output": "Array",
|
||||
"colour": Blockly.Blocks.lists.HUE,
|
||||
"tooltip": Blockly.Msg.LISTS_REPEAT_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.LISTS_REPEAT_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_length'] = {
|
||||
/**
|
||||
* Block for list length.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LISTS_LENGTH_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": ['String', 'Array']
|
||||
}
|
||||
],
|
||||
"output": 'Number',
|
||||
"colour": Blockly.Blocks.lists.HUE,
|
||||
"tooltip": Blockly.Msg.LISTS_LENGTH_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.LISTS_LENGTH_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_isEmpty'] = {
|
||||
/**
|
||||
* Block for is the list empty?
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LISTS_ISEMPTY_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "VALUE",
|
||||
"check": ['String', 'Array']
|
||||
}
|
||||
],
|
||||
"output": 'Boolean',
|
||||
"colour": Blockly.Blocks.lists.HUE,
|
||||
"tooltip": Blockly.Msg.LISTS_ISEMPTY_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.LISTS_ISEMPTY_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_indexOf'] = {
|
||||
/**
|
||||
* Block for finding an item in the list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var OPERATORS =
|
||||
[[Blockly.Msg.LISTS_INDEX_OF_FIRST, 'FIRST'],
|
||||
[Blockly.Msg.LISTS_INDEX_OF_LAST, 'LAST']];
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_INDEX_OF_HELPURL);
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.setOutput(true, 'Number');
|
||||
this.appendValueInput('VALUE')
|
||||
.setCheck('Array')
|
||||
.appendField(Blockly.Msg.LISTS_INDEX_OF_INPUT_IN_LIST);
|
||||
this.appendValueInput('FIND')
|
||||
.appendField(new Blockly.FieldDropdown(OPERATORS), 'END');
|
||||
this.setInputsInline(true);
|
||||
this.setTooltip(function() {
|
||||
return Blockly.Msg.LISTS_INDEX_OF_TOOLTIP.replace('%1',
|
||||
this.workspace.options.oneBasedIndex ? '0' : '-1');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_getIndex'] = {
|
||||
/**
|
||||
* Block for getting element at index.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var MODE =
|
||||
[[Blockly.Msg.LISTS_GET_INDEX_GET, 'GET'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_GET_REMOVE, 'GET_REMOVE'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_REMOVE, 'REMOVE']];
|
||||
this.WHERE_OPTIONS =
|
||||
[[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM']];
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_GET_INDEX_HELPURL);
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
var modeMenu = new Blockly.FieldDropdown(MODE, function(value) {
|
||||
var isStatement = (value == 'REMOVE');
|
||||
this.sourceBlock_.updateStatement_(isStatement);
|
||||
});
|
||||
this.appendValueInput('VALUE')
|
||||
.setCheck('Array')
|
||||
.appendField(Blockly.Msg.LISTS_GET_INDEX_INPUT_IN_LIST);
|
||||
this.appendDummyInput()
|
||||
.appendField(modeMenu, 'MODE')
|
||||
.appendField('', 'SPACE');
|
||||
this.appendDummyInput('AT');
|
||||
if (Blockly.Msg.LISTS_GET_INDEX_TAIL) {
|
||||
this.appendDummyInput('TAIL')
|
||||
.appendField(Blockly.Msg.LISTS_GET_INDEX_TAIL);
|
||||
}
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true);
|
||||
this.updateAt_(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
var mode = thisBlock.getFieldValue('MODE');
|
||||
var where = thisBlock.getFieldValue('WHERE');
|
||||
var tooltip = '';
|
||||
switch (mode + ' ' + where) {
|
||||
case 'GET FROM_START':
|
||||
case 'GET FROM_END':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FROM;
|
||||
break;
|
||||
case 'GET FIRST':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_FIRST;
|
||||
break;
|
||||
case 'GET LAST':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_LAST;
|
||||
break;
|
||||
case 'GET RANDOM':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_RANDOM;
|
||||
break;
|
||||
case 'GET_REMOVE FROM_START':
|
||||
case 'GET_REMOVE FROM_END':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FROM;
|
||||
break;
|
||||
case 'GET_REMOVE FIRST':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_FIRST;
|
||||
break;
|
||||
case 'GET_REMOVE LAST':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_LAST;
|
||||
break;
|
||||
case 'GET_REMOVE RANDOM':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_GET_REMOVE_RANDOM;
|
||||
break;
|
||||
case 'REMOVE FROM_START':
|
||||
case 'REMOVE FROM_END':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FROM;
|
||||
break;
|
||||
case 'REMOVE FIRST':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_FIRST;
|
||||
break;
|
||||
case 'REMOVE LAST':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_LAST;
|
||||
break;
|
||||
case 'REMOVE RANDOM':
|
||||
tooltip = Blockly.Msg.LISTS_GET_INDEX_TOOLTIP_REMOVE_RANDOM;
|
||||
break;
|
||||
}
|
||||
if (where == 'FROM_START' || where == 'FROM_END') {
|
||||
var msg = (where == 'FROM_START') ?
|
||||
Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP :
|
||||
Blockly.Msg.LISTS_INDEX_FROM_END_TOOLTIP;
|
||||
tooltip += ' ' + msg.replace('%1',
|
||||
thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
|
||||
}
|
||||
return tooltip;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Create XML to represent whether the block is a statement or a value.
|
||||
* Also represent whether there is an 'AT' input.
|
||||
* @return {Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
var isStatement = !this.outputConnection;
|
||||
container.setAttribute('statement', isStatement);
|
||||
var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
|
||||
container.setAttribute('at', isAt);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the 'AT' input.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
// Note: Until January 2013 this block did not have mutations,
|
||||
// so 'statement' defaults to false and 'at' defaults to true.
|
||||
var isStatement = (xmlElement.getAttribute('statement') == 'true');
|
||||
this.updateStatement_(isStatement);
|
||||
var isAt = (xmlElement.getAttribute('at') != 'false');
|
||||
this.updateAt_(isAt);
|
||||
},
|
||||
/**
|
||||
* Switch between a value block and a statement block.
|
||||
* @param {boolean} newStatement True if the block should be a statement.
|
||||
* False if the block should be a value.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateStatement_: function(newStatement) {
|
||||
var oldStatement = !this.outputConnection;
|
||||
if (newStatement != oldStatement) {
|
||||
this.unplug(true, true);
|
||||
if (newStatement) {
|
||||
this.setOutput(false);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
} else {
|
||||
this.setPreviousStatement(false);
|
||||
this.setNextStatement(false);
|
||||
this.setOutput(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create or delete an input for the numeric index.
|
||||
* @param {boolean} isAt True if the input should exist.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateAt_: function(isAt) {
|
||||
// Destroy old 'AT' and 'ORDINAL' inputs.
|
||||
this.removeInput('AT');
|
||||
this.removeInput('ORDINAL', true);
|
||||
// Create either a value 'AT' input or a dummy input.
|
||||
if (isAt) {
|
||||
this.appendValueInput('AT').setCheck('Number');
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput('ORDINAL')
|
||||
.appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput('AT');
|
||||
}
|
||||
var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
|
||||
var newAt = (value == 'FROM_START') || (value == 'FROM_END');
|
||||
// The 'isAt' variable is available due to this function being a closure.
|
||||
if (newAt != isAt) {
|
||||
var block = this.sourceBlock_;
|
||||
block.updateAt_(newAt);
|
||||
// This menu has been destroyed and replaced. Update the replacement.
|
||||
block.setFieldValue(value, 'WHERE');
|
||||
return null;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
this.getInput('AT').appendField(menu, 'WHERE');
|
||||
if (Blockly.Msg.LISTS_GET_INDEX_TAIL) {
|
||||
this.moveInputBefore('TAIL', null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_setIndex'] = {
|
||||
/**
|
||||
* Block for setting the element at index.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var MODE =
|
||||
[[Blockly.Msg.LISTS_SET_INDEX_SET, 'SET'],
|
||||
[Blockly.Msg.LISTS_SET_INDEX_INSERT, 'INSERT']];
|
||||
this.WHERE_OPTIONS =
|
||||
[[Blockly.Msg.LISTS_GET_INDEX_FROM_START, 'FROM_START'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FROM_END, 'FROM_END'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_FIRST, 'FIRST'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_LAST, 'LAST'],
|
||||
[Blockly.Msg.LISTS_GET_INDEX_RANDOM, 'RANDOM']];
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_SET_INDEX_HELPURL);
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.appendValueInput('LIST')
|
||||
.setCheck('Array')
|
||||
.appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_IN_LIST);
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldDropdown(MODE), 'MODE')
|
||||
.appendField('', 'SPACE');
|
||||
this.appendDummyInput('AT');
|
||||
this.appendValueInput('TO')
|
||||
.appendField(Blockly.Msg.LISTS_SET_INDEX_INPUT_TO);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setTooltip(Blockly.Msg.LISTS_SET_INDEX_TOOLTIP);
|
||||
this.updateAt_(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
var mode = thisBlock.getFieldValue('MODE');
|
||||
var where = thisBlock.getFieldValue('WHERE');
|
||||
var tooltip = '';
|
||||
switch (mode + ' ' + where) {
|
||||
case 'SET FROM_START':
|
||||
case 'SET FROM_END':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FROM;
|
||||
break;
|
||||
case 'SET FIRST':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_FIRST;
|
||||
break;
|
||||
case 'SET LAST':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_LAST;
|
||||
break;
|
||||
case 'SET RANDOM':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_SET_RANDOM;
|
||||
break;
|
||||
case 'INSERT FROM_START':
|
||||
case 'INSERT FROM_END':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FROM;
|
||||
break;
|
||||
case 'INSERT FIRST':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_FIRST;
|
||||
break;
|
||||
case 'INSERT LAST':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_LAST;
|
||||
break;
|
||||
case 'INSERT RANDOM':
|
||||
tooltip = Blockly.Msg.LISTS_SET_INDEX_TOOLTIP_INSERT_RANDOM;
|
||||
break;
|
||||
}
|
||||
if (where == 'FROM_START' || where == 'FROM_END') {
|
||||
tooltip += ' ' + Blockly.Msg.LISTS_INDEX_FROM_START_TOOLTIP
|
||||
.replace('%1',
|
||||
thisBlock.workspace.options.oneBasedIndex ? '#1' : '#0');
|
||||
}
|
||||
return tooltip;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Create XML to represent whether there is an 'AT' input.
|
||||
* @return {Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
var isAt = this.getInput('AT').type == Blockly.INPUT_VALUE;
|
||||
container.setAttribute('at', isAt);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the 'AT' input.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
// Note: Until January 2013 this block did not have mutations,
|
||||
// so 'at' defaults to true.
|
||||
var isAt = (xmlElement.getAttribute('at') != 'false');
|
||||
this.updateAt_(isAt);
|
||||
},
|
||||
/**
|
||||
* Create or delete an input for the numeric index.
|
||||
* @param {boolean} isAt True if the input should exist.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateAt_: function(isAt) {
|
||||
// Destroy old 'AT' and 'ORDINAL' input.
|
||||
this.removeInput('AT');
|
||||
this.removeInput('ORDINAL', true);
|
||||
// Create either a value 'AT' input or a dummy input.
|
||||
if (isAt) {
|
||||
this.appendValueInput('AT').setCheck('Number');
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput('ORDINAL')
|
||||
.appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput('AT');
|
||||
}
|
||||
var menu = new Blockly.FieldDropdown(this.WHERE_OPTIONS, function(value) {
|
||||
var newAt = (value == 'FROM_START') || (value == 'FROM_END');
|
||||
// The 'isAt' variable is available due to this function being a closure.
|
||||
if (newAt != isAt) {
|
||||
var block = this.sourceBlock_;
|
||||
block.updateAt_(newAt);
|
||||
// This menu has been destroyed and replaced. Update the replacement.
|
||||
block.setFieldValue(value, 'WHERE');
|
||||
return null;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
this.moveInputBefore('AT', 'TO');
|
||||
if (this.getInput('ORDINAL')) {
|
||||
this.moveInputBefore('ORDINAL', 'TO');
|
||||
}
|
||||
|
||||
this.getInput('AT').appendField(menu, 'WHERE');
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_getSublist'] = {
|
||||
/**
|
||||
* Block for getting sublist.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this['WHERE_OPTIONS_1'] =
|
||||
[[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_START, 'FROM_START'],
|
||||
[Blockly.Msg.LISTS_GET_SUBLIST_START_FROM_END, 'FROM_END'],
|
||||
[Blockly.Msg.LISTS_GET_SUBLIST_START_FIRST, 'FIRST']];
|
||||
this['WHERE_OPTIONS_2'] =
|
||||
[[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_START, 'FROM_START'],
|
||||
[Blockly.Msg.LISTS_GET_SUBLIST_END_FROM_END, 'FROM_END'],
|
||||
[Blockly.Msg.LISTS_GET_SUBLIST_END_LAST, 'LAST']];
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_GET_SUBLIST_HELPURL);
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.appendValueInput('LIST')
|
||||
.setCheck('Array')
|
||||
.appendField(Blockly.Msg.LISTS_GET_SUBLIST_INPUT_IN_LIST);
|
||||
this.appendDummyInput('AT1');
|
||||
this.appendDummyInput('AT2');
|
||||
if (Blockly.Msg.LISTS_GET_SUBLIST_TAIL) {
|
||||
this.appendDummyInput('TAIL')
|
||||
.appendField(Blockly.Msg.LISTS_GET_SUBLIST_TAIL);
|
||||
}
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, 'Array');
|
||||
this.updateAt_(1, true);
|
||||
this.updateAt_(2, true);
|
||||
this.setTooltip(Blockly.Msg.LISTS_GET_SUBLIST_TOOLTIP);
|
||||
},
|
||||
/**
|
||||
* Create XML to represent whether there are 'AT' inputs.
|
||||
* @return {Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
var isAt1 = this.getInput('AT1').type == Blockly.INPUT_VALUE;
|
||||
container.setAttribute('at1', isAt1);
|
||||
var isAt2 = this.getInput('AT2').type == Blockly.INPUT_VALUE;
|
||||
container.setAttribute('at2', isAt2);
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the 'AT' inputs.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
var isAt1 = (xmlElement.getAttribute('at1') == 'true');
|
||||
var isAt2 = (xmlElement.getAttribute('at2') == 'true');
|
||||
this.updateAt_(1, isAt1);
|
||||
this.updateAt_(2, isAt2);
|
||||
},
|
||||
/**
|
||||
* Create or delete an input for a numeric index.
|
||||
* This block has two such inputs, independant of each other.
|
||||
* @param {number} n Specify first or second input (1 or 2).
|
||||
* @param {boolean} isAt True if the input should exist.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateAt_: function(n, isAt) {
|
||||
// Create or delete an input for the numeric index.
|
||||
// Destroy old 'AT' and 'ORDINAL' inputs.
|
||||
this.removeInput('AT' + n);
|
||||
this.removeInput('ORDINAL' + n, true);
|
||||
// Create either a value 'AT' input or a dummy input.
|
||||
if (isAt) {
|
||||
this.appendValueInput('AT' + n).setCheck('Number');
|
||||
if (Blockly.Msg.ORDINAL_NUMBER_SUFFIX) {
|
||||
this.appendDummyInput('ORDINAL' + n)
|
||||
.appendField(Blockly.Msg.ORDINAL_NUMBER_SUFFIX);
|
||||
}
|
||||
} else {
|
||||
this.appendDummyInput('AT' + n);
|
||||
}
|
||||
var menu = new Blockly.FieldDropdown(this['WHERE_OPTIONS_' + n],
|
||||
function(value) {
|
||||
var newAt = (value == 'FROM_START') || (value == 'FROM_END');
|
||||
// The 'isAt' variable is available due to this function being a
|
||||
// closure.
|
||||
if (newAt != isAt) {
|
||||
var block = this.sourceBlock_;
|
||||
block.updateAt_(n, newAt);
|
||||
// This menu has been destroyed and replaced.
|
||||
// Update the replacement.
|
||||
block.setFieldValue(value, 'WHERE' + n);
|
||||
return null;
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
this.getInput('AT' + n)
|
||||
.appendField(menu, 'WHERE' + n);
|
||||
if (n == 1) {
|
||||
this.moveInputBefore('AT1', 'AT2');
|
||||
if (this.getInput('ORDINAL1')) {
|
||||
this.moveInputBefore('ORDINAL1', 'AT2');
|
||||
}
|
||||
}
|
||||
if (Blockly.Msg.LISTS_GET_SUBLIST_TAIL) {
|
||||
this.moveInputBefore('TAIL', null);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_sort'] = {
|
||||
/**
|
||||
* Block for sorting a list.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.LISTS_SORT_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "TYPE",
|
||||
"options": [
|
||||
[Blockly.Msg.LISTS_SORT_TYPE_NUMERIC, "NUMERIC"],
|
||||
[Blockly.Msg.LISTS_SORT_TYPE_TEXT, "TEXT"],
|
||||
[Blockly.Msg.LISTS_SORT_TYPE_IGNORECASE, "IGNORE_CASE"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "field_dropdown",
|
||||
"name": "DIRECTION",
|
||||
"options": [
|
||||
[Blockly.Msg.LISTS_SORT_ORDER_ASCENDING, "1"],
|
||||
[Blockly.Msg.LISTS_SORT_ORDER_DESCENDING, "-1"]
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "LIST",
|
||||
"check": "Array"
|
||||
}
|
||||
],
|
||||
"output": "Array",
|
||||
"colour": Blockly.Blocks.lists.HUE,
|
||||
"tooltip": Blockly.Msg.LISTS_SORT_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.LISTS_SORT_HELPURL
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['lists_split'] = {
|
||||
/**
|
||||
* Block for splitting text into a list, or joining a list into text.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
// Assign 'this' to a variable for use in the closures below.
|
||||
var thisBlock = this;
|
||||
var dropdown = new Blockly.FieldDropdown(
|
||||
[[Blockly.Msg.LISTS_SPLIT_LIST_FROM_TEXT, 'SPLIT'],
|
||||
[Blockly.Msg.LISTS_SPLIT_TEXT_FROM_LIST, 'JOIN']],
|
||||
function(newMode) {
|
||||
thisBlock.updateType_(newMode);
|
||||
});
|
||||
this.setHelpUrl(Blockly.Msg.LISTS_SPLIT_HELPURL);
|
||||
this.setColour(Blockly.Blocks.lists.HUE);
|
||||
this.appendValueInput('INPUT')
|
||||
.setCheck('String')
|
||||
.appendField(dropdown, 'MODE');
|
||||
this.appendValueInput('DELIM')
|
||||
.setCheck('String')
|
||||
.appendField(Blockly.Msg.LISTS_SPLIT_WITH_DELIMITER);
|
||||
this.setInputsInline(true);
|
||||
this.setOutput(true, 'Array');
|
||||
this.setTooltip(function() {
|
||||
var mode = thisBlock.getFieldValue('MODE');
|
||||
if (mode == 'SPLIT') {
|
||||
return Blockly.Msg.LISTS_SPLIT_TOOLTIP_SPLIT;
|
||||
} else if (mode == 'JOIN') {
|
||||
return Blockly.Msg.LISTS_SPLIT_TOOLTIP_JOIN;
|
||||
}
|
||||
throw 'Unknown mode: ' + mode;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Modify this block to have the correct input and output types.
|
||||
* @param {string} newMode Either 'SPLIT' or 'JOIN'.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateType_: function(newMode) {
|
||||
if (newMode == 'SPLIT') {
|
||||
this.outputConnection.setCheck('Array');
|
||||
this.getInput('INPUT').setCheck('String');
|
||||
} else {
|
||||
this.outputConnection.setCheck('String');
|
||||
this.getInput('INPUT').setCheck('Array');
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create XML to represent the input and output types.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('mode', this.getFieldValue('MODE'));
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the input and output types.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
this.updateType_(xmlElement.getAttribute('mode'));
|
||||
}
|
||||
};
|
526
blocks/logic.js
Normal file
526
blocks/logic.js
Normal file
|
@ -0,0 +1,526 @@
|
|||
/**
|
||||
* @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;
|
||||
}
|
||||
};
|
292
blocks/loops.js
Normal file
292
blocks/loops.js
Normal file
|
@ -0,0 +1,292 @@
|
|||
/**
|
||||
* @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 Loop blocks for Blockly.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.loops');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
|
||||
/**
|
||||
* Common HSV hue for all blocks in this category.
|
||||
*/
|
||||
Blockly.Blocks.loops.HUE = 120;
|
||||
|
||||
Blockly.Blocks['controls_repeat_ext'] = {
|
||||
/**
|
||||
* Block for repeat n times (external number).
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROLS_REPEAT_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TIMES",
|
||||
"check": "Number"
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": Blockly.Blocks.loops.HUE,
|
||||
"tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL
|
||||
});
|
||||
this.appendStatementInput('DO')
|
||||
.appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['controls_repeat'] = {
|
||||
/**
|
||||
* Block for repeat n times (internal number).
|
||||
* The 'controls_repeat_ext' block is preferred as it is more flexible.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROLS_REPEAT_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_number",
|
||||
"name": "TIMES",
|
||||
"value": 10,
|
||||
"min": 0,
|
||||
"precision": 1
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": Blockly.Blocks.loops.HUE,
|
||||
"tooltip": Blockly.Msg.CONTROLS_REPEAT_TOOLTIP,
|
||||
"helpUrl": Blockly.Msg.CONTROLS_REPEAT_HELPURL
|
||||
});
|
||||
this.appendStatementInput('DO')
|
||||
.appendField(Blockly.Msg.CONTROLS_REPEAT_INPUT_DO);
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['controls_whileUntil'] = {
|
||||
/**
|
||||
* Block for 'do while/until' loop.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var OPERATORS =
|
||||
[[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_WHILE, 'WHILE'],
|
||||
[Blockly.Msg.CONTROLS_WHILEUNTIL_OPERATOR_UNTIL, 'UNTIL']];
|
||||
this.setHelpUrl(Blockly.Msg.CONTROLS_WHILEUNTIL_HELPURL);
|
||||
this.setColour(Blockly.Blocks.loops.HUE);
|
||||
this.appendValueInput('BOOL')
|
||||
.setCheck('Boolean')
|
||||
.appendField(new Blockly.FieldDropdown(OPERATORS), 'MODE');
|
||||
this.appendStatementInput('DO')
|
||||
.appendField(Blockly.Msg.CONTROLS_WHILEUNTIL_INPUT_DO);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
var op = thisBlock.getFieldValue('MODE');
|
||||
var TOOLTIPS = {
|
||||
'WHILE': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_WHILE,
|
||||
'UNTIL': Blockly.Msg.CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL
|
||||
};
|
||||
return TOOLTIPS[op];
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['controls_for'] = {
|
||||
/**
|
||||
* Block for 'for' loop.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROLS_FOR_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VAR",
|
||||
"variable": null
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "FROM",
|
||||
"check": "Number",
|
||||
"align": "RIGHT"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "TO",
|
||||
"check": "Number",
|
||||
"align": "RIGHT"
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "BY",
|
||||
"check": "Number",
|
||||
"align": "RIGHT"
|
||||
}
|
||||
],
|
||||
"inputsInline": true,
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": Blockly.Blocks.loops.HUE,
|
||||
"helpUrl": Blockly.Msg.CONTROLS_FOR_HELPURL
|
||||
});
|
||||
this.appendStatementInput('DO')
|
||||
.appendField(Blockly.Msg.CONTROLS_FOR_INPUT_DO);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
return Blockly.Msg.CONTROLS_FOR_TOOLTIP.replace('%1',
|
||||
thisBlock.getFieldValue('VAR'));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Add menu option to create getter block for loop variable.
|
||||
* @param {!Array} options List of menu options to add to.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(options) {
|
||||
if (!this.isCollapsed()) {
|
||||
var option = {enabled: true};
|
||||
var name = this.getFieldValue('VAR');
|
||||
option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
|
||||
var xmlField = goog.dom.createDom('field', null, name);
|
||||
xmlField.setAttribute('name', 'VAR');
|
||||
var xmlBlock = goog.dom.createDom('block', null, xmlField);
|
||||
xmlBlock.setAttribute('type', 'variables_get');
|
||||
option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['controls_forEach'] = {
|
||||
/**
|
||||
* Block for 'for each' loop.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.jsonInit({
|
||||
"message0": Blockly.Msg.CONTROLS_FOREACH_TITLE,
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_variable",
|
||||
"name": "VAR",
|
||||
"variable": null
|
||||
},
|
||||
{
|
||||
"type": "input_value",
|
||||
"name": "LIST",
|
||||
"check": "Array"
|
||||
}
|
||||
],
|
||||
"previousStatement": null,
|
||||
"nextStatement": null,
|
||||
"colour": Blockly.Blocks.loops.HUE,
|
||||
"helpUrl": Blockly.Msg.CONTROLS_FOREACH_HELPURL
|
||||
});
|
||||
this.appendStatementInput('DO')
|
||||
.appendField(Blockly.Msg.CONTROLS_FOREACH_INPUT_DO);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
return Blockly.Msg.CONTROLS_FOREACH_TOOLTIP.replace('%1',
|
||||
thisBlock.getFieldValue('VAR'));
|
||||
});
|
||||
},
|
||||
customContextMenu: Blockly.Blocks['controls_for'].customContextMenu
|
||||
};
|
||||
|
||||
Blockly.Blocks['controls_flow_statements'] = {
|
||||
/**
|
||||
* Block for flow statements: continue, break.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var OPERATORS =
|
||||
[[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_BREAK, 'BREAK'],
|
||||
[Blockly.Msg.CONTROLS_FLOW_STATEMENTS_OPERATOR_CONTINUE, 'CONTINUE']];
|
||||
this.setHelpUrl(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_HELPURL);
|
||||
this.setColour(Blockly.Blocks.loops.HUE);
|
||||
this.appendDummyInput()
|
||||
.appendField(new Blockly.FieldDropdown(OPERATORS), 'FLOW');
|
||||
this.setPreviousStatement(true);
|
||||
// Assign 'this' to a variable for use in the tooltip closure below.
|
||||
var thisBlock = this;
|
||||
this.setTooltip(function() {
|
||||
var op = thisBlock.getFieldValue('FLOW');
|
||||
var TOOLTIPS = {
|
||||
'BREAK': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_BREAK,
|
||||
'CONTINUE': Blockly.Msg.CONTROLS_FLOW_STATEMENTS_TOOLTIP_CONTINUE
|
||||
};
|
||||
return TOOLTIPS[op];
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Called whenever anything on the workspace changes.
|
||||
* Add warning if this flow block is not nested inside a loop.
|
||||
* @param {!Blockly.Events.Abstract} e Change event.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
onchange: function() {
|
||||
if (!this.workspace.isDragging || this.workspace.isDragging()) {
|
||||
return; // Don't change state at the start of a drag.
|
||||
}
|
||||
var legal = false;
|
||||
// Is the block nested in a loop?
|
||||
var block = this;
|
||||
do {
|
||||
if (this.LOOP_TYPES.indexOf(block.type) != -1) {
|
||||
legal = true;
|
||||
break;
|
||||
}
|
||||
block = block.getSurroundParent();
|
||||
} while (block);
|
||||
if (legal) {
|
||||
this.setWarningText(null);
|
||||
if (!this.isInFlyout) {
|
||||
this.setDisabled(false);
|
||||
}
|
||||
} else {
|
||||
this.setWarningText(Blockly.Msg.CONTROLS_FLOW_STATEMENTS_WARNING);
|
||||
if (!this.isInFlyout && !this.getInheritedDisabled()) {
|
||||
this.setDisabled(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* List of block types that are loops and thus do not need warnings.
|
||||
* To add a new loop type add this to your code:
|
||||
* Blockly.Blocks['controls_flow_statements'].LOOP_TYPES.push('custom_loop');
|
||||
*/
|
||||
LOOP_TYPES: ['controls_repeat', 'controls_repeat_ext', 'controls_forEach',
|
||||
'controls_for', 'controls_whileUntil']
|
||||
};
|
888
blocks/procedures.js
Normal file
888
blocks/procedures.js
Normal file
|
@ -0,0 +1,888 @@
|
|||
/**
|
||||
* @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 Procedure blocks for Blockly.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Blocks.procedures');
|
||||
|
||||
goog.require('Blockly.Blocks');
|
||||
|
||||
|
||||
/**
|
||||
* Common HSV hue for all blocks in this category.
|
||||
*/
|
||||
Blockly.Blocks.procedures.HUE = 290;
|
||||
|
||||
Blockly.Blocks['procedures_defnoreturn'] = {
|
||||
/**
|
||||
* Block for defining a procedure with no return value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var nameField = new Blockly.FieldTextInput('',
|
||||
Blockly.Procedures.rename);
|
||||
nameField.setSpellcheck(false);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_TITLE)
|
||||
.appendField(nameField, 'NAME')
|
||||
.appendField('', 'PARAMS');
|
||||
this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
|
||||
if ((this.workspace.options.comments ||
|
||||
(this.workspace.options.parentWorkspace &&
|
||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||
Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT) {
|
||||
this.setCommentText(Blockly.Msg.PROCEDURES_DEFNORETURN_COMMENT);
|
||||
}
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
this.setTooltip(Blockly.Msg.PROCEDURES_DEFNORETURN_TOOLTIP);
|
||||
this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFNORETURN_HELPURL);
|
||||
this.arguments_ = [];
|
||||
this.setStatements_(true);
|
||||
this.statementConnection_ = null;
|
||||
},
|
||||
/**
|
||||
* Add or remove the statement block from this function definition.
|
||||
* @param {boolean} hasStatements True if a statement block is needed.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
setStatements_: function(hasStatements) {
|
||||
if (this.hasStatements_ === hasStatements) {
|
||||
return;
|
||||
}
|
||||
if (hasStatements) {
|
||||
this.appendStatementInput('STACK')
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFNORETURN_DO);
|
||||
if (this.getInput('RETURN')) {
|
||||
this.moveInputBefore('STACK', 'RETURN');
|
||||
}
|
||||
} else {
|
||||
this.removeInput('STACK', true);
|
||||
}
|
||||
this.hasStatements_ = hasStatements;
|
||||
},
|
||||
/**
|
||||
* Update the display of parameters for this procedure definition block.
|
||||
* Display a warning if there are duplicately named parameters.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateParams_: function() {
|
||||
// Check for duplicated arguments.
|
||||
var badArg = false;
|
||||
var hash = {};
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
if (hash['arg_' + this.arguments_[i].toLowerCase()]) {
|
||||
badArg = true;
|
||||
break;
|
||||
}
|
||||
hash['arg_' + this.arguments_[i].toLowerCase()] = true;
|
||||
}
|
||||
if (badArg) {
|
||||
this.setWarningText(Blockly.Msg.PROCEDURES_DEF_DUPLICATE_WARNING);
|
||||
} else {
|
||||
this.setWarningText(null);
|
||||
}
|
||||
// Merge the arguments into a human-readable list.
|
||||
var paramString = '';
|
||||
if (this.arguments_.length) {
|
||||
paramString = Blockly.Msg.PROCEDURES_BEFORE_PARAMS +
|
||||
' ' + this.arguments_.join(', ');
|
||||
}
|
||||
// The params field is deterministic based on the mutation,
|
||||
// no need to fire a change event.
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
this.setFieldValue(paramString, 'PARAMS');
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create XML to represent the argument inputs.
|
||||
* @param {boolean} opt_paramIds If true include the IDs of the parameter
|
||||
* quarks. Used by Blockly.Procedures.mutateCallers for reconnection.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function(opt_paramIds) {
|
||||
var container = document.createElement('mutation');
|
||||
if (opt_paramIds) {
|
||||
container.setAttribute('name', this.getFieldValue('NAME'));
|
||||
}
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var parameter = document.createElement('arg');
|
||||
parameter.setAttribute('name', this.arguments_[i]);
|
||||
if (opt_paramIds && this.paramIds_) {
|
||||
parameter.setAttribute('paramId', this.paramIds_[i]);
|
||||
}
|
||||
container.appendChild(parameter);
|
||||
}
|
||||
|
||||
// Save whether the statement input is visible.
|
||||
if (!this.hasStatements_) {
|
||||
container.setAttribute('statements', 'false');
|
||||
}
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the argument inputs.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
this.arguments_ = [];
|
||||
for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
|
||||
if (childNode.nodeName.toLowerCase() == 'arg') {
|
||||
this.arguments_.push(childNode.getAttribute('name'));
|
||||
}
|
||||
}
|
||||
this.updateParams_();
|
||||
Blockly.Procedures.mutateCallers(this);
|
||||
|
||||
// Show or hide the statement input.
|
||||
this.setStatements_(xmlElement.getAttribute('statements') !== 'false');
|
||||
},
|
||||
/**
|
||||
* 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('procedures_mutatorcontainer');
|
||||
containerBlock.initSvg();
|
||||
|
||||
// Check/uncheck the allow statement box.
|
||||
if (this.getInput('RETURN')) {
|
||||
containerBlock.setFieldValue(this.hasStatements_ ? 'TRUE' : 'FALSE',
|
||||
'STATEMENTS');
|
||||
} else {
|
||||
containerBlock.getInput('STATEMENT_INPUT').setVisible(false);
|
||||
}
|
||||
|
||||
// Parameter list.
|
||||
var connection = containerBlock.getInput('STACK').connection;
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var paramBlock = workspace.newBlock('procedures_mutatorarg');
|
||||
paramBlock.initSvg();
|
||||
paramBlock.setFieldValue(this.arguments_[i], 'NAME');
|
||||
// Store the old location.
|
||||
paramBlock.oldLocation = i;
|
||||
connection.connect(paramBlock.previousConnection);
|
||||
connection = paramBlock.nextConnection;
|
||||
}
|
||||
// Initialize procedure's callers with blank IDs.
|
||||
Blockly.Procedures.mutateCallers(this);
|
||||
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) {
|
||||
// Parameter list.
|
||||
this.arguments_ = [];
|
||||
this.paramIds_ = [];
|
||||
var paramBlock = containerBlock.getInputTargetBlock('STACK');
|
||||
while (paramBlock) {
|
||||
this.arguments_.push(paramBlock.getFieldValue('NAME'));
|
||||
this.paramIds_.push(paramBlock.id);
|
||||
paramBlock = paramBlock.nextConnection &&
|
||||
paramBlock.nextConnection.targetBlock();
|
||||
}
|
||||
this.updateParams_();
|
||||
Blockly.Procedures.mutateCallers(this);
|
||||
|
||||
// Show/hide the statement input.
|
||||
var hasStatements = containerBlock.getFieldValue('STATEMENTS');
|
||||
if (hasStatements !== null) {
|
||||
hasStatements = hasStatements == 'TRUE';
|
||||
if (this.hasStatements_ != hasStatements) {
|
||||
if (hasStatements) {
|
||||
this.setStatements_(true);
|
||||
// Restore the stack, if one was saved.
|
||||
Blockly.Mutator.reconnect(this.statementConnection_, this, 'STACK');
|
||||
this.statementConnection_ = null;
|
||||
} else {
|
||||
// Save the stack, then disconnect it.
|
||||
var stackConnection = this.getInput('STACK').connection;
|
||||
this.statementConnection_ = stackConnection.targetConnection;
|
||||
if (this.statementConnection_) {
|
||||
var stackBlock = stackConnection.targetBlock();
|
||||
stackBlock.unplug();
|
||||
stackBlock.bumpNeighbours_();
|
||||
}
|
||||
this.setStatements_(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Return the signature of this procedure definition.
|
||||
* @return {!Array} Tuple containing three elements:
|
||||
* - the name of the defined procedure,
|
||||
* - a list of all its arguments,
|
||||
* - that it DOES NOT have a return value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
getProcedureDef: function() {
|
||||
return [this.getFieldValue('NAME'), this.arguments_, false];
|
||||
},
|
||||
/**
|
||||
* Return all variables referenced by this block.
|
||||
* @return {!Array.<string>} List of variable names.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
getVars: function() {
|
||||
return this.arguments_;
|
||||
},
|
||||
/**
|
||||
* Notification that a variable is renaming.
|
||||
* If the name matches one of this block's variables, rename it.
|
||||
* @param {string} oldName Previous name of variable.
|
||||
* @param {string} newName Renamed variable.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
renameVar: function(oldName, newName) {
|
||||
var change = false;
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
if (Blockly.Names.equals(oldName, this.arguments_[i])) {
|
||||
this.arguments_[i] = newName;
|
||||
change = true;
|
||||
}
|
||||
}
|
||||
if (change) {
|
||||
this.updateParams_();
|
||||
// Update the mutator's variables if the mutator is open.
|
||||
if (this.mutator.isVisible()) {
|
||||
var blocks = this.mutator.workspace_.getAllBlocks();
|
||||
for (var i = 0, block; block = blocks[i]; i++) {
|
||||
if (block.type == 'procedures_mutatorarg' &&
|
||||
Blockly.Names.equals(oldName, block.getFieldValue('NAME'))) {
|
||||
block.setFieldValue(newName, 'NAME');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add custom menu options to this block's context menu.
|
||||
* @param {!Array} options List of menu options to add to.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(options) {
|
||||
// Add option to create caller.
|
||||
var option = {enabled: true};
|
||||
var name = this.getFieldValue('NAME');
|
||||
option.text = Blockly.Msg.PROCEDURES_CREATE_DO.replace('%1', name);
|
||||
var xmlMutation = goog.dom.createDom('mutation');
|
||||
xmlMutation.setAttribute('name', name);
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var xmlArg = goog.dom.createDom('arg');
|
||||
xmlArg.setAttribute('name', this.arguments_[i]);
|
||||
xmlMutation.appendChild(xmlArg);
|
||||
}
|
||||
var xmlBlock = goog.dom.createDom('block', null, xmlMutation);
|
||||
xmlBlock.setAttribute('type', this.callType_);
|
||||
option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
|
||||
options.push(option);
|
||||
|
||||
// Add options to create getters for each parameter.
|
||||
if (!this.isCollapsed()) {
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var option = {enabled: true};
|
||||
var name = this.arguments_[i];
|
||||
option.text = Blockly.Msg.VARIABLES_SET_CREATE_GET.replace('%1', name);
|
||||
var xmlField = goog.dom.createDom('field', null, name);
|
||||
xmlField.setAttribute('name', 'VAR');
|
||||
var xmlBlock = goog.dom.createDom('block', null, xmlField);
|
||||
xmlBlock.setAttribute('type', 'variables_get');
|
||||
option.callback = Blockly.ContextMenu.callbackFactory(this, xmlBlock);
|
||||
options.push(option);
|
||||
}
|
||||
}
|
||||
},
|
||||
callType_: 'procedures_callnoreturn'
|
||||
};
|
||||
|
||||
Blockly.Blocks['procedures_defreturn'] = {
|
||||
/**
|
||||
* Block for defining a procedure with a return value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var nameField = new Blockly.FieldTextInput('',
|
||||
Blockly.Procedures.rename);
|
||||
nameField.setSpellcheck(false);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_TITLE)
|
||||
.appendField(nameField, 'NAME')
|
||||
.appendField('', 'PARAMS');
|
||||
this.appendValueInput('RETURN')
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
|
||||
this.setMutator(new Blockly.Mutator(['procedures_mutatorarg']));
|
||||
if ((this.workspace.options.comments ||
|
||||
(this.workspace.options.parentWorkspace &&
|
||||
this.workspace.options.parentWorkspace.options.comments)) &&
|
||||
Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT) {
|
||||
this.setCommentText(Blockly.Msg.PROCEDURES_DEFRETURN_COMMENT);
|
||||
}
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
this.setTooltip(Blockly.Msg.PROCEDURES_DEFRETURN_TOOLTIP);
|
||||
this.setHelpUrl(Blockly.Msg.PROCEDURES_DEFRETURN_HELPURL);
|
||||
this.arguments_ = [];
|
||||
this.setStatements_(true);
|
||||
this.statementConnection_ = null;
|
||||
},
|
||||
setStatements_: Blockly.Blocks['procedures_defnoreturn'].setStatements_,
|
||||
updateParams_: Blockly.Blocks['procedures_defnoreturn'].updateParams_,
|
||||
mutationToDom: Blockly.Blocks['procedures_defnoreturn'].mutationToDom,
|
||||
domToMutation: Blockly.Blocks['procedures_defnoreturn'].domToMutation,
|
||||
decompose: Blockly.Blocks['procedures_defnoreturn'].decompose,
|
||||
compose: Blockly.Blocks['procedures_defnoreturn'].compose,
|
||||
/**
|
||||
* Return the signature of this procedure definition.
|
||||
* @return {!Array} Tuple containing three elements:
|
||||
* - the name of the defined procedure,
|
||||
* - a list of all its arguments,
|
||||
* - that it DOES have a return value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
getProcedureDef: function() {
|
||||
return [this.getFieldValue('NAME'), this.arguments_, true];
|
||||
},
|
||||
getVars: Blockly.Blocks['procedures_defnoreturn'].getVars,
|
||||
renameVar: Blockly.Blocks['procedures_defnoreturn'].renameVar,
|
||||
customContextMenu: Blockly.Blocks['procedures_defnoreturn'].customContextMenu,
|
||||
callType_: 'procedures_callreturn'
|
||||
};
|
||||
|
||||
Blockly.Blocks['procedures_mutatorcontainer'] = {
|
||||
/**
|
||||
* Mutator block for procedure container.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TITLE);
|
||||
this.appendStatementInput('STACK');
|
||||
this.appendDummyInput('STATEMENT_INPUT')
|
||||
.appendField(Blockly.Msg.PROCEDURES_ALLOW_STATEMENTS)
|
||||
.appendField(new Blockly.FieldCheckbox('TRUE'), 'STATEMENTS');
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORCONTAINER_TOOLTIP);
|
||||
this.contextMenu = false;
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['procedures_mutatorarg'] = {
|
||||
/**
|
||||
* Mutator block for procedure argument.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
var field = new Blockly.FieldTextInput('x', this.validator_);
|
||||
this.appendDummyInput()
|
||||
.appendField(Blockly.Msg.PROCEDURES_MUTATORARG_TITLE)
|
||||
.appendField(field, 'NAME');
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
this.setTooltip(Blockly.Msg.PROCEDURES_MUTATORARG_TOOLTIP);
|
||||
this.contextMenu = false;
|
||||
|
||||
// Create the default variable when we drag the block in from the flyout.
|
||||
// Have to do this after installing the field on the block.
|
||||
field.onFinishEditing_ = this.createNewVar_;
|
||||
field.onFinishEditing_('x');
|
||||
},
|
||||
/**
|
||||
* Obtain a valid name for the procedure.
|
||||
* Merge runs of whitespace. Strip leading and trailing whitespace.
|
||||
* Beyond this, all names are legal.
|
||||
* @param {string} newVar User-supplied name.
|
||||
* @return {?string} Valid name, or null if a name was not specified.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
validator_: function(newVar) {
|
||||
newVar = newVar.replace(/[\s\xa0]+/g, ' ').replace(/^ | $/g, '');
|
||||
return newVar || null;
|
||||
},
|
||||
/**
|
||||
* Called when focusing away from the text field.
|
||||
* Creates a new variable with this name.
|
||||
* @param {string} newText The new variable name.
|
||||
* @private
|
||||
* @this Blockly.FieldTextInput
|
||||
*/
|
||||
createNewVar_: function(newText) {
|
||||
var source = this.sourceBlock_;
|
||||
if (source && source.workspace && source.workspace.options &&
|
||||
source.workspace.options.parentWorkspace) {
|
||||
source.workspace.options.parentWorkspace.createVariable(newText);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Blockly.Blocks['procedures_callnoreturn'] = {
|
||||
/**
|
||||
* Block for calling a procedure with no return value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput('TOPROW')
|
||||
.appendField(this.id, 'NAME');
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
// Tooltip is set in renameProcedure.
|
||||
this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLNORETURN_HELPURL);
|
||||
this.arguments_ = [];
|
||||
this.quarkConnections_ = {};
|
||||
this.quarkIds_ = null;
|
||||
},
|
||||
/**
|
||||
* Returns the name of the procedure this block calls.
|
||||
* @return {string} Procedure name.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
getProcedureCall: function() {
|
||||
// The NAME field is guaranteed to exist, null will never be returned.
|
||||
return /** @type {string} */ (this.getFieldValue('NAME'));
|
||||
},
|
||||
/**
|
||||
* Notification that a procedure is renaming.
|
||||
* If the name matches this block's procedure, rename it.
|
||||
* @param {string} oldName Previous name of procedure.
|
||||
* @param {string} newName Renamed procedure.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
renameProcedure: function(oldName, newName) {
|
||||
if (Blockly.Names.equals(oldName, this.getProcedureCall())) {
|
||||
this.setFieldValue(newName, 'NAME');
|
||||
this.setTooltip(
|
||||
(this.outputConnection ? Blockly.Msg.PROCEDURES_CALLRETURN_TOOLTIP :
|
||||
Blockly.Msg.PROCEDURES_CALLNORETURN_TOOLTIP)
|
||||
.replace('%1', newName));
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Notification that the procedure's parameters have changed.
|
||||
* @param {!Array.<string>} paramNames New param names, e.g. ['x', 'y', 'z'].
|
||||
* @param {!Array.<string>} paramIds IDs of params (consistent for each
|
||||
* parameter through the life of a mutator, regardless of param renaming),
|
||||
* e.g. ['piua', 'f8b_', 'oi.o'].
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
setProcedureParameters_: function(paramNames, paramIds) {
|
||||
// Data structures:
|
||||
// this.arguments = ['x', 'y']
|
||||
// Existing param names.
|
||||
// this.quarkConnections_ {piua: null, f8b_: Blockly.Connection}
|
||||
// Look-up of paramIds to connections plugged into the call block.
|
||||
// this.quarkIds_ = ['piua', 'f8b_']
|
||||
// Existing param IDs.
|
||||
// Note that quarkConnections_ may include IDs that no longer exist, but
|
||||
// which might reappear if a param is reattached in the mutator.
|
||||
var defBlock = Blockly.Procedures.getDefinition(this.getProcedureCall(),
|
||||
this.workspace);
|
||||
var mutatorOpen = defBlock && defBlock.mutator &&
|
||||
defBlock.mutator.isVisible();
|
||||
if (!mutatorOpen) {
|
||||
this.quarkConnections_ = {};
|
||||
this.quarkIds_ = null;
|
||||
}
|
||||
if (!paramIds) {
|
||||
// Reset the quarks (a mutator is about to open).
|
||||
return;
|
||||
}
|
||||
if (goog.array.equals(this.arguments_, paramNames)) {
|
||||
// No change.
|
||||
this.quarkIds_ = paramIds;
|
||||
return;
|
||||
}
|
||||
if (paramIds.length != paramNames.length) {
|
||||
throw 'Error: paramNames and paramIds must be the same length.';
|
||||
}
|
||||
this.setCollapsed(false);
|
||||
if (!this.quarkIds_) {
|
||||
// Initialize tracking for this block.
|
||||
this.quarkConnections_ = {};
|
||||
if (paramNames.join('\n') == this.arguments_.join('\n')) {
|
||||
// No change to the parameters, allow quarkConnections_ to be
|
||||
// populated with the existing connections.
|
||||
this.quarkIds_ = paramIds;
|
||||
} else {
|
||||
this.quarkIds_ = [];
|
||||
}
|
||||
}
|
||||
// Switch off rendering while the block is rebuilt.
|
||||
var savedRendered = this.rendered;
|
||||
this.rendered = false;
|
||||
// Update the quarkConnections_ with existing connections.
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var input = this.getInput('ARG' + i);
|
||||
if (input) {
|
||||
var connection = input.connection.targetConnection;
|
||||
this.quarkConnections_[this.quarkIds_[i]] = connection;
|
||||
if (mutatorOpen && connection &&
|
||||
paramIds.indexOf(this.quarkIds_[i]) == -1) {
|
||||
// This connection should no longer be attached to this block.
|
||||
connection.disconnect();
|
||||
connection.getSourceBlock().bumpNeighbours_();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Rebuild the block's arguments.
|
||||
this.arguments_ = [].concat(paramNames);
|
||||
this.updateShape_();
|
||||
this.quarkIds_ = paramIds;
|
||||
// Reconnect any child blocks.
|
||||
if (this.quarkIds_) {
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var quarkId = this.quarkIds_[i];
|
||||
if (quarkId in this.quarkConnections_) {
|
||||
var connection = this.quarkConnections_[quarkId];
|
||||
if (!Blockly.Mutator.reconnect(connection, this, 'ARG' + i)) {
|
||||
// Block no longer exists or has been attached elsewhere.
|
||||
delete this.quarkConnections_[quarkId];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Restore rendering and show the changes.
|
||||
this.rendered = savedRendered;
|
||||
if (this.rendered) {
|
||||
this.render();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Modify this block to have the correct number of arguments.
|
||||
* @private
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
updateShape_: function() {
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var field = this.getField('ARGNAME' + i);
|
||||
if (field) {
|
||||
// Ensure argument name is up to date.
|
||||
// The argument name field is deterministic based on the mutation,
|
||||
// no need to fire a change event.
|
||||
Blockly.Events.disable();
|
||||
try {
|
||||
field.setValue(this.arguments_[i]);
|
||||
} finally {
|
||||
Blockly.Events.enable();
|
||||
}
|
||||
} else {
|
||||
// Add new input.
|
||||
field = new Blockly.FieldLabel(this.arguments_[i]);
|
||||
var input = this.appendValueInput('ARG' + i)
|
||||
.setAlign(Blockly.ALIGN_RIGHT)
|
||||
.appendField(field, 'ARGNAME' + i);
|
||||
input.init();
|
||||
}
|
||||
}
|
||||
// Remove deleted inputs.
|
||||
while (this.getInput('ARG' + i)) {
|
||||
this.removeInput('ARG' + i);
|
||||
i++;
|
||||
}
|
||||
// Add 'with:' if there are parameters, remove otherwise.
|
||||
var topRow = this.getInput('TOPROW');
|
||||
if (topRow) {
|
||||
if (this.arguments_.length) {
|
||||
if (!this.getField('WITH')) {
|
||||
topRow.appendField(Blockly.Msg.PROCEDURES_CALL_BEFORE_PARAMS, 'WITH');
|
||||
topRow.init();
|
||||
}
|
||||
} else {
|
||||
if (this.getField('WITH')) {
|
||||
topRow.removeField('WITH');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Create XML to represent the (non-editable) name and arguments.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('name', this.getProcedureCall());
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
var parameter = document.createElement('arg');
|
||||
parameter.setAttribute('name', this.arguments_[i]);
|
||||
container.appendChild(parameter);
|
||||
}
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore the (non-editable) name and parameters.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
var name = xmlElement.getAttribute('name');
|
||||
this.renameProcedure(this.getProcedureCall(), name);
|
||||
var args = [];
|
||||
var paramIds = [];
|
||||
for (var i = 0, childNode; childNode = xmlElement.childNodes[i]; i++) {
|
||||
if (childNode.nodeName.toLowerCase() == 'arg') {
|
||||
args.push(childNode.getAttribute('name'));
|
||||
paramIds.push(childNode.getAttribute('paramId'));
|
||||
}
|
||||
}
|
||||
this.setProcedureParameters_(args, paramIds);
|
||||
},
|
||||
/**
|
||||
* Notification that a variable is renaming.
|
||||
* If the name matches one of this block's variables, rename it.
|
||||
* @param {string} oldName Previous name of variable.
|
||||
* @param {string} newName Renamed variable.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
renameVar: function(oldName, newName) {
|
||||
for (var i = 0; i < this.arguments_.length; i++) {
|
||||
if (Blockly.Names.equals(oldName, this.arguments_[i])) {
|
||||
this.arguments_[i] = newName;
|
||||
this.getField('ARGNAME' + i).setValue(newName);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Procedure calls cannot exist without the corresponding procedure
|
||||
* definition. Enforce this link whenever an event is fired.
|
||||
* @param {!Blockly.Events.Abstract} event Procedure onchange event.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
onchange: function(event) {
|
||||
if (!this.workspace || this.workspace.isFlyout) {
|
||||
// Block is deleted or is in a flyout.
|
||||
return;
|
||||
}
|
||||
if (event.type == Blockly.Events.CREATE &&
|
||||
event.ids.indexOf(this.id) != -1) {
|
||||
// Look for the case where a procedure call was created (usually through
|
||||
// paste) and there is no matching definition. In this case, create
|
||||
// an empty definition block with the correct signature.
|
||||
var name = this.getProcedureCall();
|
||||
var def = Blockly.Procedures.getDefinition(name, this.workspace);
|
||||
if (def && (def.type != this.defType_ ||
|
||||
JSON.stringify(def.arguments_) != JSON.stringify(this.arguments_))) {
|
||||
// The signatures don't match.
|
||||
def = null;
|
||||
}
|
||||
if (!def) {
|
||||
Blockly.Events.setGroup(event.group);
|
||||
/**
|
||||
* Create matching definition block.
|
||||
* <xml>
|
||||
* <block type="procedures_defreturn" x="10" y="20">
|
||||
* <mutation name="test">
|
||||
* <arg name="x"></arg>
|
||||
* </mutation>
|
||||
* <field name="NAME">test</field>
|
||||
* </block>
|
||||
* </xml>
|
||||
*/
|
||||
var xml = goog.dom.createDom('xml');
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', this.defType_);
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
var x = xy.x + Blockly.SNAP_RADIUS * (this.RTL ? -1 : 1);
|
||||
var y = xy.y + Blockly.SNAP_RADIUS * 2;
|
||||
block.setAttribute('x', x);
|
||||
block.setAttribute('y', y);
|
||||
var mutation = this.mutationToDom();
|
||||
block.appendChild(mutation);
|
||||
var field = goog.dom.createDom('field');
|
||||
field.setAttribute('name', 'NAME');
|
||||
field.appendChild(document.createTextNode(this.getProcedureCall()));
|
||||
block.appendChild(field);
|
||||
xml.appendChild(block);
|
||||
Blockly.Xml.domToWorkspace(xml, this.workspace);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
} else if (event.type == Blockly.Events.DELETE) {
|
||||
// Look for the case where a procedure definition has been deleted,
|
||||
// leaving this block (a procedure call) orphaned. In this case, delete
|
||||
// the orphan.
|
||||
var name = this.getProcedureCall();
|
||||
var def = Blockly.Procedures.getDefinition(name, this.workspace);
|
||||
if (!def) {
|
||||
Blockly.Events.setGroup(event.group);
|
||||
this.dispose(true, false);
|
||||
Blockly.Events.setGroup(false);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Add menu option to find the definition block for this call.
|
||||
* @param {!Array} options List of menu options to add to.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
customContextMenu: function(options) {
|
||||
var option = {enabled: true};
|
||||
option.text = Blockly.Msg.PROCEDURES_HIGHLIGHT_DEF;
|
||||
var name = this.getProcedureCall();
|
||||
var workspace = this.workspace;
|
||||
option.callback = function() {
|
||||
var def = Blockly.Procedures.getDefinition(name, workspace);
|
||||
def && def.select();
|
||||
};
|
||||
options.push(option);
|
||||
},
|
||||
defType_: 'procedures_defnoreturn'
|
||||
};
|
||||
|
||||
Blockly.Blocks['procedures_callreturn'] = {
|
||||
/**
|
||||
* Block for calling a procedure with a return value.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendDummyInput('TOPROW')
|
||||
.appendField('', 'NAME');
|
||||
this.setOutput(true);
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
// Tooltip is set in domToMutation.
|
||||
this.setHelpUrl(Blockly.Msg.PROCEDURES_CALLRETURN_HELPURL);
|
||||
this.arguments_ = [];
|
||||
this.quarkConnections_ = {};
|
||||
this.quarkIds_ = null;
|
||||
},
|
||||
getProcedureCall: Blockly.Blocks['procedures_callnoreturn'].getProcedureCall,
|
||||
renameProcedure: Blockly.Blocks['procedures_callnoreturn'].renameProcedure,
|
||||
setProcedureParameters_:
|
||||
Blockly.Blocks['procedures_callnoreturn'].setProcedureParameters_,
|
||||
updateShape_: Blockly.Blocks['procedures_callnoreturn'].updateShape_,
|
||||
mutationToDom: Blockly.Blocks['procedures_callnoreturn'].mutationToDom,
|
||||
domToMutation: Blockly.Blocks['procedures_callnoreturn'].domToMutation,
|
||||
renameVar: Blockly.Blocks['procedures_callnoreturn'].renameVar,
|
||||
onchange: Blockly.Blocks['procedures_callnoreturn'].onchange,
|
||||
customContextMenu:
|
||||
Blockly.Blocks['procedures_callnoreturn'].customContextMenu,
|
||||
defType_: 'procedures_defreturn'
|
||||
};
|
||||
|
||||
Blockly.Blocks['procedures_ifreturn'] = {
|
||||
/**
|
||||
* Block for conditionally returning a value from a procedure.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
init: function() {
|
||||
this.appendValueInput('CONDITION')
|
||||
.setCheck('Boolean')
|
||||
.appendField(Blockly.Msg.CONTROLS_IF_MSG_IF);
|
||||
this.appendValueInput('VALUE')
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
|
||||
this.setInputsInline(true);
|
||||
this.setPreviousStatement(true);
|
||||
this.setNextStatement(true);
|
||||
this.setColour(Blockly.Blocks.procedures.HUE);
|
||||
this.setTooltip(Blockly.Msg.PROCEDURES_IFRETURN_TOOLTIP);
|
||||
this.setHelpUrl(Blockly.Msg.PROCEDURES_IFRETURN_HELPURL);
|
||||
this.hasReturnValue_ = true;
|
||||
},
|
||||
/**
|
||||
* Create XML to represent whether this block has a return value.
|
||||
* @return {!Element} XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
mutationToDom: function() {
|
||||
var container = document.createElement('mutation');
|
||||
container.setAttribute('value', Number(this.hasReturnValue_));
|
||||
return container;
|
||||
},
|
||||
/**
|
||||
* Parse XML to restore whether this block has a return value.
|
||||
* @param {!Element} xmlElement XML storage element.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
domToMutation: function(xmlElement) {
|
||||
var value = xmlElement.getAttribute('value');
|
||||
this.hasReturnValue_ = (value == 1);
|
||||
if (!this.hasReturnValue_) {
|
||||
this.removeInput('VALUE');
|
||||
this.appendDummyInput('VALUE')
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Called whenever anything on the workspace changes.
|
||||
* Add warning if this flow block is not nested inside a loop.
|
||||
* @this Blockly.Block
|
||||
*/
|
||||
onchange: function() {
|
||||
if (!this.workspace.isDragging || this.workspace.isDragging()) {
|
||||
return; // Don't change state at the start of a drag.
|
||||
}
|
||||
var legal = false;
|
||||
// Is the block nested in a procedure?
|
||||
var block = this;
|
||||
do {
|
||||
if (this.FUNCTION_TYPES.indexOf(block.type) != -1) {
|
||||
legal = true;
|
||||
break;
|
||||
}
|
||||
block = block.getSurroundParent();
|
||||
} while (block);
|
||||
if (legal) {
|
||||
// If needed, toggle whether this block has a return value.
|
||||
if (block.type == 'procedures_defnoreturn' && this.hasReturnValue_) {
|
||||
this.removeInput('VALUE');
|
||||
this.appendDummyInput('VALUE')
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
|
||||
this.hasReturnValue_ = false;
|
||||
} else if (block.type == 'procedures_defreturn' &&
|
||||
!this.hasReturnValue_) {
|
||||
this.removeInput('VALUE');
|
||||
this.appendValueInput('VALUE')
|
||||
.appendField(Blockly.Msg.PROCEDURES_DEFRETURN_RETURN);
|
||||
this.hasReturnValue_ = true;
|
||||
}
|
||||
this.setWarningText(null);
|
||||
if (!this.isInFlyout) {
|
||||
this.setDisabled(false);
|
||||
}
|
||||
} else {
|
||||
this.setWarningText(Blockly.Msg.PROCEDURES_IFRETURN_WARNING);
|
||||
if (!this.isInFlyout && !this.getInheritedDisabled()) {
|
||||
this.setDisabled(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
/**
|
||||
* List of block types that are functions and thus do not need warnings.
|
||||
* To add a new function type add this to your code:
|
||||
* Blockly.Blocks['procedures_ifreturn'].FUNCTION_TYPES.push('custom_func');
|
||||
*/
|
||||
FUNCTION_TYPES: ['procedures_defnoreturn', 'procedures_defreturn']
|
||||
};
|
33
blocks_compressed.js
Normal file
33
blocks_compressed.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
// Do not edit this file; automatically generated by build.py.
|
||||
'use strict';
|
||||
|
||||
|
||||
// Copyright 2016 Google Inc. Apache License 2.0
|
||||
Blockly.constants={};Blockly.DRAG_RADIUS=3;Blockly.SNAP_RADIUS=48;Blockly.CONNECTING_SNAP_RADIUS=96;Blockly.CURRENT_CONNECTION_PREFERENCE=20;Blockly.BUMP_DELAY=0;Blockly.COLLAPSE_CHARS=30;Blockly.LONGPRESS=750;Blockly.SOUND_LIMIT=100;Blockly.HSV_SATURATION=.45;Blockly.HSV_VALUE=.65;Blockly.SPRITE={width:96,height:124,url:"sprites.png"};Blockly.SVG_NS="http://www.w3.org/2000/svg";Blockly.HTML_NS="http://www.w3.org/1999/xhtml";Blockly.INPUT_VALUE=1;Blockly.OUTPUT_VALUE=2;Blockly.NEXT_STATEMENT=3;
|
||||
Blockly.PREVIOUS_STATEMENT=4;Blockly.DUMMY_INPUT=5;Blockly.ALIGN_LEFT=-1;Blockly.ALIGN_CENTRE=0;Blockly.ALIGN_RIGHT=1;Blockly.DRAG_NONE=0;Blockly.DRAG_STICKY=1;Blockly.DRAG_BEGIN=1;Blockly.DRAG_FREE=2;Blockly.OPPOSITE_TYPE=[];Blockly.OPPOSITE_TYPE[Blockly.INPUT_VALUE]=Blockly.OUTPUT_VALUE;Blockly.OPPOSITE_TYPE[Blockly.OUTPUT_VALUE]=Blockly.INPUT_VALUE;Blockly.OPPOSITE_TYPE[Blockly.NEXT_STATEMENT]=Blockly.PREVIOUS_STATEMENT;Blockly.OPPOSITE_TYPE[Blockly.PREVIOUS_STATEMENT]=Blockly.NEXT_STATEMENT;
|
||||
Blockly.TOOLBOX_AT_TOP=0;Blockly.TOOLBOX_AT_BOTTOM=1;Blockly.TOOLBOX_AT_LEFT=2;Blockly.TOOLBOX_AT_RIGHT=3;Blockly.OUTPUT_SHAPE_HEXAGONAL=1;Blockly.OUTPUT_SHAPE_ROUND=2;Blockly.OUTPUT_SHAPE_SQUARE=3;Blockly.STACK_GLOW_RADIUS=1.3;Blockly.REPLACEMENT_GLOW_RADIUS=2;Blockly.Categories={motion:"motion",looks:"looks",sound:"sounds",pen:"pen",data:"data",event:"events",control:"control",sensing:"sensing",operators:"operators",more:"more"};Blockly.DELETE_AREA_TRASH=1;Blockly.DELETE_AREA_TOOLBOX=2;
|
||||
// Copyright 2012 Google Inc. Apache License 2.0
|
||||
Blockly.Blocks.colour={};function randomColour(){return"#"+("00000"+Math.floor(Math.random()*Math.pow(2,24)).toString(16)).substr(-6)}Blockly.Blocks.colour_picker={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_colour",name:"COLOUR",colour:randomColour()}],outputShape:Blockly.OUTPUT_SHAPE_ROUND,output:"Colour"})}};/*
|
||||
|
||||
Visual Blocks Editor
|
||||
|
||||
Copyright 2016 Massachusetts Institute of Technology
|
||||
All rights reserved.
|
||||
|
||||
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.
|
||||
*/
|
||||
Blockly.Colours={motion:{primary:"#4C97FF",secondary:"#4280D7",tertiary:"#3373CC"},looks:{primary:"#9966FF",secondary:"#855CD6",tertiary:"#774DCB"},sounds:{primary:"#CF63CF",secondary:"#C94FC9",tertiary:"#BD42BD"},control:{primary:"#FFAB19",secondary:"#EC9C13",tertiary:"#CF8B17"},event:{primary:"#FFBF00",secondary:"#E6AC00",tertiary:"#CC9900"},sensing:{primary:"#5CB1D6",secondary:"#47A8D1",tertiary:"#2E8EB8"},pen:{primary:"#0fBD8C",secondary:"#0DA57A",tertiary:"#0B8E69"},operators:{primary:"#59C059",
|
||||
secondary:"#46B946",tertiary:"#389438"},data:{primary:"#FF8C1A",secondary:"#FF8000",tertiary:"#DB6E00"},more:{primary:"#FF6680",secondary:"#FF4D6A",tertiary:"#FF3355"},text:"#575E75",workspace:"#F5F8FF",toolboxHover:"#4C97FF",toolboxSelected:"#e9eef2",toolboxText:"#575E75",toolbox:"#FFFFFF",flyout:"#DDDDDD",scrollbar:"#CCCCCC",scrollbarHover:"#BBBBBB",textField:"#FFFFFF",insertionMarker:"#949494",insertionMarkerOpacity:.6,dragShadowOpacity:.3,stackGlow:"#FFF200",stackGlowOpacity:1,replacementGlow:"#FFFFFF",
|
||||
replacementGlowOpacity:1,colourPickerStroke:"#FFFFFF",fieldShadow:"rgba(0,0,0,0.1)",dropDownShadow:"rgba(0, 0, 0, .3)",numPadBackground:"#547AB2",numPadBorder:"#435F91",numPadActiveBackground:"#435F91",numPadText:"#FFFFFF",valueReportBackground:"#FFFFFF",valueReportBorder:"#AAAAAA"};Blockly.Blocks.math={};Blockly.Blocks.math_number={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_number",name:"NUM",value:"0"}],output:"Number",outputShape:Blockly.OUTPUT_SHAPE_ROUND,colour:Blockly.Colours.textField})}};Blockly.Blocks.math_integer={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_number",name:"NUM",precision:1}],output:"Number",outputShape:Blockly.OUTPUT_SHAPE_ROUND,colour:Blockly.Colours.textField})}};
|
||||
Blockly.Blocks.math_whole_number={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_number",name:"NUM",min:0,precision:1}],output:"Number",outputShape:Blockly.OUTPUT_SHAPE_ROUND,colour:Blockly.Colours.textField})}};Blockly.Blocks.math_positive_number={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_number",name:"NUM",min:0}],output:"Number",outputShape:Blockly.OUTPUT_SHAPE_ROUND,colour:Blockly.Colours.textField})}};
|
||||
Blockly.Blocks.math_angle={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_angle",name:"NUM",value:90}],output:"Number",outputShape:Blockly.OUTPUT_SHAPE_ROUND,colour:Blockly.Colours.textField})}};Blockly.Blocks.texts={};Blockly.Blocks.text={init:function(){this.jsonInit({message0:"%1",args0:[{type:"field_input",name:"TEXT"}],output:"String",outputShape:Blockly.OUTPUT_SHAPE_ROUND,colour:Blockly.Colours.textField})}};
|
|
@ -10,7 +10,8 @@ git rm -rf demos
|
|||
git rm -rf generators
|
||||
git rm -rf tests/generators
|
||||
git rm -rf appengine
|
||||
|
||||
git rm blockly_compressed.js
|
||||
git rm blockly_uncompressed.js
|
||||
git rm -f python_compressed.js php_compressed.js tests/playground.html core/block_render_svg.js dart_compressed.js javascript_compressed.js lua_compressed.js
|
||||
|
||||
# Turn on more powerful globbing
|
||||
|
|
|
@ -57,7 +57,7 @@ Blockly.Block = function(workspace, prototypeName, opt_id) {
|
|||
/** @type {string} */
|
||||
this.id = (opt_id && !workspace.getBlockById(opt_id) &&
|
||||
(!flyoutWorkspace || !flyoutWorkspace.getBlockById(opt_id))) ?
|
||||
opt_id : Blockly.genUid();
|
||||
opt_id : Blockly.utils.genUid();
|
||||
workspace.blockDB_[this.id] = this;
|
||||
/** @type {Blockly.Connection} */
|
||||
this.outputConnection = null;
|
||||
|
@ -370,7 +370,7 @@ Blockly.Block.prototype.bumpNeighbours_ = function() {
|
|||
if (rootBlock.isInFlyout) {
|
||||
return; // Don't move blocks around in a flyout.
|
||||
}
|
||||
// Loop though every connection on this block.
|
||||
// Loop through every connection on this block.
|
||||
var myConnections = this.getConnections_(false);
|
||||
for (var i = 0, connection; connection = myConnections[i]; i++) {
|
||||
// Spider down from this block bumping all sub-blocks.
|
||||
|
@ -626,7 +626,7 @@ Blockly.Block.prototype.setInsertionMarker = function(insertionMarker) {
|
|||
if (this.isInsertionMarker_) {
|
||||
this.setColour(Blockly.Colours.insertionMarker);
|
||||
this.setOpacity(Blockly.Colours.insertionMarkerOpacity);
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyInsertionMarker');
|
||||
}
|
||||
};
|
||||
|
@ -750,7 +750,6 @@ Blockly.Block.prototype.getColourTertiary = function() {
|
|||
return this.colourTertiary_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Create an #RRGGBB string colour from a colour HSV hue value or #RRGGBB string.
|
||||
* @param {number|string} colour HSV hue value, or #RRGGBB string.
|
||||
|
|
205
core/block_drag_surface.js
Normal file
205
core/block_drag_surface.js
Normal file
|
@ -0,0 +1,205 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 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 A class that manages a surface for dragging blocks. When a
|
||||
* block drag is started, we move the block (and children) to a separate dom
|
||||
* element that we move around using translate3d. At the end of the drag, the
|
||||
* blocks are put back in into the svg they came from. This helps performance by
|
||||
* avoiding repainting the entire svg on every mouse move while dragging blocks.
|
||||
* @author picklesrus
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.BlockDragSurfaceSvg');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a drag surface for the currently dragged block. This is a separate
|
||||
* SVG that contains only the currently moving block, or nothing.
|
||||
* @param {!Element} container Containing element.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg = function(container) {
|
||||
/**
|
||||
* @type {!Element}
|
||||
* @private
|
||||
*/
|
||||
this.container_ = container;
|
||||
this.createDom();
|
||||
};
|
||||
|
||||
/**
|
||||
* The SVG drag surface. Set once by Blockly.BlockDragSurfaceSvg.createDom.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.SVG_ = null;
|
||||
|
||||
/**
|
||||
* This is where blocks live while they are being dragged if the drag surface
|
||||
* is enabled.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.dragGroup_ = null;
|
||||
|
||||
/**
|
||||
* Containing HTML element; parent of the workspace and the drag surface.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.container_ = null;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.scale_ = 1;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.createDom = function() {
|
||||
if (this.SVG_) {
|
||||
return; // Already created.
|
||||
}
|
||||
this.SVG_ = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': Blockly.SVG_NS,
|
||||
'xmlns:html': Blockly.HTML_NS,
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'class': 'blocklyBlockDragSurface'
|
||||
}, this.container_);
|
||||
this.dragGroup_ = Blockly.utils.createSvgElement('g', {}, this.SVG_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the SVG blocks on the drag surface's group and show the surface.
|
||||
* Only one block group should be on the drag surface at a time.
|
||||
* @param {!Element} blocks Block or group of blocks to place on the drag
|
||||
* surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0,
|
||||
'Already dragging a block.');
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.SVG_.style.display = 'block';
|
||||
// This allows blocks to be dragged outside of the blockly svg space.
|
||||
// This should be reset to hidden at the end of the block drag.
|
||||
// Note that this behavior is different from blockly where block disappear
|
||||
// "under" the blockly area.
|
||||
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
|
||||
injectionDiv.style.overflow = 'visible';
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to keep in sync with the
|
||||
* workspace.
|
||||
* @param {number} x X translation
|
||||
* @param {number} y Y translation
|
||||
* @param {number} scale Scale of the group.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
|
||||
this.scale_ = scale;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
this.dragGroup_.setAttribute('transform', 'translate('+ x + ','+ y + ')' +
|
||||
' scale(' + scale + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
* so that the browser avoids repainting the SVG.
|
||||
* Because of this, the drag coordinates must be adjusted by scale.
|
||||
* @param {number} x X translation for the entire surface.
|
||||
* @param {number} y Y translation for the entire surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
var transform;
|
||||
x *= this.scale_;
|
||||
y *= this.scale_;
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being dragged on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
transform =
|
||||
'transform: translate3d(' + x + 'px, ' + y + 'px, 0px); display: block;';
|
||||
this.SVG_.setAttribute('style', transform);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
* @return {!goog.math.Coordinate} Current translation of the surface.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
var xy = Blockly.utils.getRelativeXY(this.SVG_);
|
||||
return new goog.math.Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a reference to the drag group (primarily for
|
||||
* BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {Element} Drag surface group element.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.getGroup = function() {
|
||||
return this.dragGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current blocks on the drag surface, if any (primarily
|
||||
* for BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {!Element|undefined} Drag surface block DOM element, or undefined
|
||||
* if no blocks exist.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.getCurrentBlock = function() {
|
||||
return this.dragGroup_.firstChild;
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided
|
||||
* element.
|
||||
* @param {!Element} newSurface Surface the dragging blocks should be moved to.
|
||||
*/
|
||||
Blockly.BlockDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
newSurface.appendChild(this.getCurrentBlock());
|
||||
this.SVG_.style.display = 'none';
|
||||
// Reset the overflow property back to hidden so that nothing appears outside
|
||||
// of the blockly area.
|
||||
// Note that this behavior is different from blockly. See note in
|
||||
// setBlocksAndShow.
|
||||
var injectionDiv = document.getElementsByClassName('injectionDiv')[0];
|
||||
injectionDiv.style.overflow = 'hidden';
|
||||
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0,
|
||||
'Drag group was not cleared.');
|
||||
};
|
|
@ -336,11 +336,11 @@ Blockly.BlockSvg.prototype.updateColour = function() {
|
|||
Blockly.BlockSvg.prototype.highlightForReplacement = function(add) {
|
||||
if (add) {
|
||||
this.svgPath_.setAttribute('filter', 'url(#blocklyReplacementGlowFilter)');
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
} else {
|
||||
this.svgPath_.removeAttribute('filter');
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -510,11 +510,11 @@ Blockly.BlockSvg.prototype.updateColour = function() {
|
|||
Blockly.BlockSvg.prototype.highlightForReplacement = function(add) {
|
||||
if (add) {
|
||||
this.svgPath_.setAttribute('filter', 'url(#blocklyReplacementGlowFilter)');
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
} else {
|
||||
this.svgPath_.removeAttribute('filter');
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
}
|
||||
};
|
||||
|
@ -533,11 +533,11 @@ Blockly.BlockSvg.prototype.highlightShapeForInput = function(conn, add) {
|
|||
var inputShape = this.inputShapes_[input.name];
|
||||
if (add) {
|
||||
inputShape.setAttribute('filter', 'url(#blocklyReplacementGlowFilter)');
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
} else {
|
||||
inputShape.removeAttribute('filter');
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyReplaceable');
|
||||
}
|
||||
};
|
||||
|
|
|
@ -53,9 +53,9 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
|
|||
* @type {SVGElement}
|
||||
* @private
|
||||
*/
|
||||
this.svgGroup_ = Blockly.createSvgElement('g', {}, null);
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
/** @type {SVGElement} */
|
||||
this.svgPath_ = Blockly.createSvgElement('path', {'class': 'blocklyPath blocklyBlockBackground'},
|
||||
this.svgPath_ = Blockly.utils.createSvgElement('path', {'class': 'blocklyPath blocklyBlockBackground'},
|
||||
this.svgGroup_);
|
||||
this.svgPath_.tooltip = this;
|
||||
|
||||
|
@ -65,6 +65,14 @@ Blockly.BlockSvg = function(workspace, prototypeName, opt_id) {
|
|||
/** @type {Object.<string,Element>} */
|
||||
this.inputShapes_ = {};
|
||||
|
||||
/**
|
||||
* Whether to move the block to the drag surface when it is dragged.
|
||||
* True if it should move, false if it should be translated directly.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.useDragSurface_ = Blockly.utils.is3dSupported() && workspace.blockDragSurface_;
|
||||
|
||||
Blockly.Tooltip.bindMouseEvents(this.svgPath_);
|
||||
Blockly.BlockSvg.superClass_.constructor.call(this,
|
||||
workspace, prototypeName, opt_id);
|
||||
|
@ -170,7 +178,7 @@ Blockly.BlockSvg.prototype.initInputShape = function(input) {
|
|||
// there's a shadow block that will always cover the input shape.
|
||||
return;
|
||||
}
|
||||
this.inputShapes_[input.name] = Blockly.createSvgElement(
|
||||
this.inputShapes_[input.name] = Blockly.utils.createSvgElement(
|
||||
'path',
|
||||
{
|
||||
'class': 'blocklyPath',
|
||||
|
@ -406,26 +414,28 @@ Blockly.BlockSvg.prototype.getRelativeToSurfaceXY = function() {
|
|||
// or to the drag surface group.
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var dragSurfaceGroup = (this.workspace.dragSurface) ?
|
||||
this.workspace.dragSurface.getGroup() : null;
|
||||
|
||||
var dragSurfaceGroup = this.useDragSurface_ ?
|
||||
this.workspace.blockDragSurface_.getGroup() : null;
|
||||
|
||||
var element = this.getSvgRoot();
|
||||
if (element) {
|
||||
do {
|
||||
// Loop through this block and every parent.
|
||||
var xy = Blockly.getRelativeXY_(element);
|
||||
var xy = Blockly.utils.getRelativeXY(element);
|
||||
x += xy.x;
|
||||
y += xy.y;
|
||||
// If this element is the current element on the drag surface, include
|
||||
// the translation of the drag surface itself.
|
||||
if (this.workspace.dragSurface &&
|
||||
this.workspace.dragSurface.getCurrentBlock() == element) {
|
||||
var surfaceTranslation = this.workspace.dragSurface.getSurfaceTranslation();
|
||||
if (this.useDragSurface_ &&
|
||||
this.workspace.blockDragSurface_.getCurrentBlock() == element) {
|
||||
var surfaceTranslation = this.workspace.blockDragSurface_.getSurfaceTranslation();
|
||||
x += surfaceTranslation.x;
|
||||
y += surfaceTranslation.y;
|
||||
}
|
||||
element = element.parentNode;
|
||||
} while (element && element != this.workspace.getCanvas() &&
|
||||
element != dragSurfaceGroup);
|
||||
element != dragSurfaceGroup);
|
||||
}
|
||||
return new goog.math.Coordinate(x, y);
|
||||
};
|
||||
|
@ -467,6 +477,63 @@ Blockly.BlockSvg.prototype.translate = function(x, y, opt_use3d) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Transforms a block by setting the translation on the transform attribute
|
||||
* of the block's SVG.
|
||||
* @param {number} x The x coordinate of the translation.
|
||||
* @param {number} y The y coordinate of the translation.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.translate = function(x, y) {
|
||||
this.getSvgRoot().setAttribute('transform',
|
||||
'translate(' + x + ',' + y + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block to its workspace's drag surface, accounting for positioning.
|
||||
* Generally should be called at the same time as setDragging_(true).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.blockDragSurface_.translateSurface(xy.x, xy.y);
|
||||
// Execute the move on the top-level SVG component
|
||||
this.workspace.blockDragSurface_.setBlocksAndShow(this.getSvgRoot());
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block back to the workspace block canvas.
|
||||
* Generally should be called at the same time as setDragging_(false).
|
||||
* Does nothing if useDragSurface_ is false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveOffDragSurface_ = function() {
|
||||
if (!this.useDragSurface_) {
|
||||
return;
|
||||
}
|
||||
// Translate to current position, turning off 3d.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.translate(xy.x, xy.y);
|
||||
this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas());
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the block of transform="..." attributes.
|
||||
* Used when the block is switching from 3d to 2d transform or vice versa.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.clearTransformAttributes_ = function() {
|
||||
this.getSvgRoot().removeAttribute('transform');
|
||||
};
|
||||
|
||||
/**
|
||||
* Snap this block to the nearest grid point.
|
||||
*/
|
||||
|
@ -645,7 +712,7 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
|
|||
// that behaviour is wrong, because Blockly.Flyout.prototype.blockMouseDown
|
||||
// should be called for a mousedown on a block in the flyout, which blocks
|
||||
// execution of the block's onMouseDown_ function.
|
||||
if (e.type == 'touchstart' && Blockly.isRightButton(e)) {
|
||||
if (e.type == 'touchstart' && Blockly.utils.isRightButton(e)) {
|
||||
Blockly.Flyout.blockRightClick_(e, this);
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
|
@ -664,7 +731,7 @@ Blockly.BlockSvg.prototype.onMouseDown_ = function(e) {
|
|||
this.select();
|
||||
Blockly.hideChaff();
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
this.showContextMenu_(e);
|
||||
// Click, not drag, so stop waiting for other touches from this identifier.
|
||||
|
@ -893,11 +960,11 @@ Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
|
|||
group.skew_ = '';
|
||||
Blockly.draggingConnections_ =
|
||||
Blockly.draggingConnections_.concat(this.getConnections_(true));
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDragging');
|
||||
} else {
|
||||
Blockly.draggingConnections_ = [];
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDragging');
|
||||
}
|
||||
// Recurse through all blocks attached under this one.
|
||||
|
@ -906,21 +973,6 @@ Blockly.BlockSvg.prototype.setDragging_ = function(adding) {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block to its workspace's drag surface, accounting for positioning.
|
||||
* Generally should be called at the same time as setDragging_(true).
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.moveToDragSurface_ = function() {
|
||||
// The translation for drag surface blocks,
|
||||
// is equal to the current relative-to-surface position,
|
||||
// to keep the position in sync as it move on/off the surface.
|
||||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.workspace.dragSurface.translateSurface(xy.x, xy.y);
|
||||
// Execute the move on the top-level SVG component
|
||||
this.workspace.dragSurface.setBlocksAndShow(this.getSvgRoot());
|
||||
};
|
||||
|
||||
/**
|
||||
* Move this block back to the workspace block canvas.
|
||||
|
@ -932,7 +984,7 @@ Blockly.BlockSvg.prototype.moveOffDragSurface_ = function() {
|
|||
var xy = this.getRelativeToSurfaceXY();
|
||||
this.clearTransformAttributes_();
|
||||
this.translate(xy.x, xy.y, false);
|
||||
this.workspace.dragSurface.clearAndHide(this.workspace.getCanvas());
|
||||
this.workspace.blockDragSurface_.clearAndHide(this.workspace.getCanvas());
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -977,12 +1029,7 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
|
|||
// Switch to unrestricted dragging.
|
||||
Blockly.dragMode_ = Blockly.DRAG_FREE;
|
||||
Blockly.longStop_();
|
||||
// Must move to drag surface before unplug(),
|
||||
// or else connections will calculate the wrong relative to surface XY
|
||||
// in tighten_(). Then blocks connected to this block move around on the
|
||||
// drag surface. By moving to the drag surface before unplug, connection
|
||||
// positions will be calculated correctly.
|
||||
this.moveToDragSurface_();
|
||||
|
||||
// Disable workspace resizing as an optimization.
|
||||
this.workspace.setResizesEnabled(false);
|
||||
// Clear WidgetDiv/DropDownDiv without animating, in case blocks are moved
|
||||
|
@ -994,6 +1041,7 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
|
|||
this.unplug();
|
||||
}
|
||||
this.setDragging_(true);
|
||||
this.moveToDragSurface_();
|
||||
}
|
||||
}
|
||||
if (Blockly.dragMode_ == Blockly.DRAG_FREE) {
|
||||
|
@ -1015,7 +1063,13 @@ Blockly.BlockSvg.prototype.onMouseMove_ = function(e) {
|
|||
*/
|
||||
Blockly.BlockSvg.prototype.handleDragFree_ = function(oldXY, newXY, e) {
|
||||
var dxy = goog.math.Coordinate.difference(oldXY, this.dragStartXY_);
|
||||
this.workspace.dragSurface.translateSurface(newXY.x, newXY.y);
|
||||
var group = this.getSvgRoot();
|
||||
if (this.useDragSurface_) {
|
||||
this.workspace.blockDragSurface_.translateSurface(newXY.x, newXY.y);
|
||||
} else {
|
||||
group.translate_ = 'translate(' + newXY.x + ',' + newXY.y + ')';
|
||||
group.setAttribute('transform', group.translate_ + group.skew_);
|
||||
}
|
||||
// Drag all the nested bubbles.
|
||||
for (var i = 0; i < this.draggedBubbles_.length; i++) {
|
||||
var commentData = this.draggedBubbles_[i];
|
||||
|
@ -1120,8 +1174,10 @@ Blockly.BlockSvg.prototype.updatePreviews = function(closestConnection,
|
|||
Blockly.localConnection_ = null;
|
||||
}
|
||||
|
||||
var wouldDeleteBlock = this.updateCursor_(e, closestConnection);
|
||||
|
||||
// Add an insertion marker or replacement marker if needed.
|
||||
if (closestConnection &&
|
||||
if (!wouldDeleteBlock && closestConnection &&
|
||||
closestConnection != Blockly.highlightedConnection_ &&
|
||||
!closestConnection.sourceBlock_.isInsertionMarker()) {
|
||||
Blockly.highlightedConnection_ = closestConnection;
|
||||
|
@ -1271,15 +1327,50 @@ Blockly.BlockSvg.disconnectInsertionMarker = function() {
|
|||
Blockly.insertionMarker_.getSvgRoot().setAttribute('visibility', 'hidden');
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide visual indication of whether the block will be deleted if
|
||||
* dropped here.
|
||||
* Prefer connecting over dropping into the trash can, but prefer dragging to
|
||||
* the toolbox over connecting to other blocks.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @param {Blockly.Connection} closestConnection The connection this block would
|
||||
* potentially connect to if dropped here, or null.
|
||||
* @return {boolean} True if the block would be deleted if dropped here,
|
||||
* otherwise false.
|
||||
* @private
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.updateCursor_ = function(e, closestConnection) {
|
||||
var deleteArea = this.workspace.isDeleteArea(e);
|
||||
var wouldConnect = Blockly.selected && closestConnection &&
|
||||
deleteArea != Blockly.DELETE_AREA_TOOLBOX;
|
||||
var wouldDelete = deleteArea && !this.getParent() &&
|
||||
Blockly.selected.isDeletable();
|
||||
var showDeleteCursor = wouldDelete && !wouldConnect;
|
||||
|
||||
if (showDeleteCursor) {
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
|
||||
if (deleteArea == Blockly.DELETE_AREA_TRASH && this.workspace.trashcan) {
|
||||
this.workspace.trashcan.setOpen_(true);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
|
||||
if (this.workspace.trashcan) {
|
||||
this.workspace.trashcan.setOpen_(false);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add or remove the UI indicating if this block is movable or not.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.updateMovable = function() {
|
||||
if (this.isMovable()) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggable');
|
||||
} else {
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklyDraggable');
|
||||
}
|
||||
};
|
||||
|
@ -1392,8 +1483,7 @@ Blockly.BlockSvg.prototype.dispose = function(healStack, animate) {
|
|||
Blockly.BlockSvg.prototype.disposeUiEffect = function() {
|
||||
this.workspace.playAudio('delete');
|
||||
|
||||
var xy = Blockly.getSvgXY_(/** @type {!Element} */ (this.svgGroup_),
|
||||
this.workspace);
|
||||
var xy = this.workspace.getSvgXY(/** @type {!Element} */ (this.svgGroup_));
|
||||
// Deeply clone the current block.
|
||||
var clone = this.svgGroup_.cloneNode(true);
|
||||
clone.translateX_ = xy.x;
|
||||
|
@ -1416,7 +1506,7 @@ Blockly.BlockSvg.prototype.connectionUiEffect = function() {
|
|||
|
||||
/**
|
||||
* Animate a cloned block and eventually dispose of it.
|
||||
* This is a class method, not an instace method since the original block has
|
||||
* This is a class method, not an instance method since the original block has
|
||||
* been destroyed and is no longer accessible.
|
||||
* @param {!Element} clone SVG element to animate and dispose of.
|
||||
* @param {boolean} rtl True if RTL, false if LTR.
|
||||
|
@ -1502,7 +1592,7 @@ Blockly.BlockSvg.prototype.setWarningText = function(text, opt_id) {
|
|||
}
|
||||
var id = opt_id || '';
|
||||
if (!id) {
|
||||
// Kill all previous pending processes, this edit supercedes them all.
|
||||
// Kill all previous pending processes, this edit supersedes them all.
|
||||
for (var n in this.setWarningText.pid_) {
|
||||
clearTimeout(this.setWarningText.pid_[n]);
|
||||
delete this.setWarningText.pid_[n];
|
||||
|
@ -1576,7 +1666,7 @@ Blockly.BlockSvg.prototype.setMutator = function(mutator) {
|
|||
* Select this block. Highlight it visually.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.addSelect = function() {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklySelected');
|
||||
// Move the selected block to the top of the stack.
|
||||
var block = this;
|
||||
|
@ -1591,7 +1681,7 @@ Blockly.BlockSvg.prototype.addSelect = function() {
|
|||
* Unselect this block. Remove its highlighting.
|
||||
*/
|
||||
Blockly.BlockSvg.prototype.removeSelect = function() {
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.svgGroup_),
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.svgGroup_),
|
||||
'blocklySelected');
|
||||
};
|
||||
|
||||
|
|
190
core/blockly.js
190
core/blockly.js
|
@ -156,26 +156,11 @@ Blockly.clipboardSource_ = null;
|
|||
Blockly.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
||||
/**
|
||||
* Map from function names to callbacks, for deciding what to do when a button
|
||||
* is clicked.
|
||||
* @type {!Object<string, function(!Blockly.FlyoutButton)}
|
||||
* Cached value for whether 3D is supported.
|
||||
* @type {!boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.flyoutButtonCallbacks_ = {};
|
||||
|
||||
/**
|
||||
* Register a callback function associated with a given key, for clicks on
|
||||
* buttons and labels in the flyout.
|
||||
* For instance, a button specified by the XML
|
||||
* <button text="create variable" callbackKey="CREATE_VARIABLE"></button>
|
||||
* should be matched by a call to
|
||||
* registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction).
|
||||
* @param {string} key The name to use to look up this function.
|
||||
* @param {function(!Blockly.FlyoutButton)} func The function to call when the
|
||||
* given button is clicked.
|
||||
*/
|
||||
Blockly.registerButtonCallback = function(key, func) {
|
||||
Blockly.flyoutButtonCallbacks_[key] = func;
|
||||
};
|
||||
Blockly.cache3dSupported_ = null;
|
||||
|
||||
/**
|
||||
* Convert a hue (HSV model) into an RGB hex triplet.
|
||||
|
@ -244,7 +229,7 @@ Blockly.svgResize = function(workspace) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.onKeyDown_ = function(e) {
|
||||
if (Blockly.mainWorkspace.options.readOnly || Blockly.isTargetInput_(e)) {
|
||||
if (Blockly.mainWorkspace.options.readOnly || Blockly.utils.isTargetInput(e)) {
|
||||
// No key actions on readonly workspaces.
|
||||
// When focused on an HTML text input widget, don't trap any keys.
|
||||
return;
|
||||
|
@ -343,7 +328,7 @@ Blockly.duplicate_ = function(block) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.onContextMenu_ = function(e) {
|
||||
if (!Blockly.isTargetInput_(e)) {
|
||||
if (!Blockly.utils.isTargetInput(e)) {
|
||||
// When focused on an HTML text input widget, don't cancel the context menu.
|
||||
e.preventDefault();
|
||||
}
|
||||
|
@ -406,12 +391,173 @@ Blockly.confirm = function(message, callback) {
|
|||
* recommend testing mobile when overriding this.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {string} defaultValue The value to initialize the prompt with.
|
||||
* @param {!function(string)} callback The callback for handling user reponse.
|
||||
* @param {!function(string)} callback The callback for handling user response.
|
||||
*/
|
||||
Blockly.prompt = function(message, defaultValue, callback) {
|
||||
callback(window.prompt(message, defaultValue));
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function for defining a block from JSON. The resulting function has
|
||||
* the correct value of jsonDef at the point in code where jsonInit is called.
|
||||
* @param {!Object} jsonDef The JSON definition of a block.
|
||||
* @return {function} A function that calls jsonInit with the correct value
|
||||
* of jsonDef.
|
||||
* @private
|
||||
*/
|
||||
Blockly.jsonInitFactory_ = function(jsonDef) {
|
||||
return function() {
|
||||
this.jsonInit(jsonDef);
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Define blocks from an array of JSON block definitions, as might be generated
|
||||
* by the Blockly Developer Tools.
|
||||
* @param {!Array.<!Object>} jsonArray An array of JSON block definitions.
|
||||
*/
|
||||
Blockly.defineBlocksWithJsonArray = function(jsonArray) {
|
||||
for (var i = 0, elem; elem = jsonArray[i]; i++) {
|
||||
Blockly.Blocks[elem.type] = {
|
||||
init: Blockly.jsonInitFactory_(elem)
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. When calling the function, verifies that
|
||||
* it belongs to the touch stream that is currently being processed, and splits
|
||||
* multitouch events into multiple events as needed.
|
||||
* @param {!Node} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @param {boolean} opt_noCaptureIdentifier True if triggering on this event
|
||||
* should not block execution of other event handlers on this touch or other
|
||||
* simultaneous touches.
|
||||
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
|
||||
opt_noCaptureIdentifier) {
|
||||
var handled = false;
|
||||
var wrapFunc = function(e) {
|
||||
var captureIdentifier = !opt_noCaptureIdentifier;
|
||||
// Handle each touch point separately. If the event was a mouse event, this
|
||||
// will hand back an array with one element, which we're fine handling.
|
||||
var events = Blockly.Touch.splitEventByTouches(e);
|
||||
for (var i = 0, event; event = events[i]; i++) {
|
||||
if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) {
|
||||
continue;
|
||||
}
|
||||
Blockly.Touch.setClientFromTouch(event);
|
||||
if (thisObject) {
|
||||
func.call(thisObject, event);
|
||||
} else {
|
||||
func(event);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
wrapFunc(e);
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
for (var i = 0, eventName;
|
||||
eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(eventName, touchWrapFunc, false);
|
||||
bindData.push([node, eventName, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. Handles multitouch events by using the
|
||||
* coordinates of the first changed touch, and doesn't do any safety checks for
|
||||
* simultaneous event processing.
|
||||
* @deprecated in favor of bindEventWithChecks_, but preserved for external
|
||||
* users.
|
||||
* @param {!Node} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.bindEvent_ = function(node, name, thisObject, func) {
|
||||
var wrapFunc = function(e) {
|
||||
if (thisObject) {
|
||||
func.call(thisObject, e);
|
||||
} else {
|
||||
func(e);
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches.length == 1) {
|
||||
// Map the touch event's properties to the event.
|
||||
var touchPoint = e.changedTouches[0];
|
||||
e.clientX = touchPoint.clientX;
|
||||
e.clientY = touchPoint.clientY;
|
||||
}
|
||||
wrapFunc(e);
|
||||
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
e.preventDefault();
|
||||
};
|
||||
for (var i = 0, eventName;
|
||||
eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(eventName, touchWrapFunc, false);
|
||||
bindData.push([node, eventName, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
* @param {!Array.<!Array>} bindData Opaque data from bindEvent_.
|
||||
* This list is emptied during the course of calling this function.
|
||||
* @return {!Function} The function call.
|
||||
* @private
|
||||
*/
|
||||
Blockly.unbindEvent_ = function(bindData) {
|
||||
while (bindData.length) {
|
||||
var bindDatum = bindData.pop();
|
||||
var node = bindDatum[0];
|
||||
var name = bindDatum[1];
|
||||
var func = bindDatum[2];
|
||||
node.removeEventListener(name, func, false);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the given string a number (includes negative and decimals).
|
||||
* @param {string} str Input string.
|
||||
* @return {boolean} True if number, false otherwise.
|
||||
*/
|
||||
Blockly.isNumber = function(str) {
|
||||
return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
|
||||
};
|
||||
|
||||
// IE9 does not have a console. Create a stub to stop errors.
|
||||
if (!goog.global['console']) {
|
||||
goog.global['console'] = {
|
||||
|
|
|
@ -222,7 +222,7 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
|
|||
[...content goes here...]
|
||||
</g>
|
||||
*/
|
||||
this.bubbleGroup_ = Blockly.createSvgElement('g', {}, null);
|
||||
this.bubbleGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
var filter =
|
||||
{'filter': 'url(#' + this.workspace_.options.embossFilterId + ')'};
|
||||
if (goog.userAgent.getUserAgentString().indexOf('JavaFX') != -1) {
|
||||
|
@ -232,27 +232,27 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
|
|||
// https://github.com/google/blockly/issues/99
|
||||
filter = {};
|
||||
}
|
||||
var bubbleEmboss = Blockly.createSvgElement('g',
|
||||
var bubbleEmboss = Blockly.utils.createSvgElement('g',
|
||||
filter, this.bubbleGroup_);
|
||||
this.bubbleArrow_ = Blockly.createSvgElement('path', {}, bubbleEmboss);
|
||||
this.bubbleBack_ = Blockly.createSvgElement('rect',
|
||||
this.bubbleArrow_ = Blockly.utils.createSvgElement('path', {}, bubbleEmboss);
|
||||
this.bubbleBack_ = Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyDraggable', 'x': 0, 'y': 0,
|
||||
'rx': Blockly.Bubble.BORDER_WIDTH, 'ry': Blockly.Bubble.BORDER_WIDTH},
|
||||
bubbleEmboss);
|
||||
if (hasResize) {
|
||||
this.resizeGroup_ = Blockly.createSvgElement('g',
|
||||
this.resizeGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': this.workspace_.RTL ?
|
||||
'blocklyResizeSW' : 'blocklyResizeSE'},
|
||||
this.bubbleGroup_);
|
||||
var resizeSize = 2 * Blockly.Bubble.BORDER_WIDTH;
|
||||
Blockly.createSvgElement('polygon',
|
||||
Blockly.utils.createSvgElement('polygon',
|
||||
{'points': '0,x x,x x,0'.replace(/x/g, resizeSize.toString())},
|
||||
this.resizeGroup_);
|
||||
Blockly.createSvgElement('line',
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize / 3, 'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1, 'y2': resizeSize / 3}, this.resizeGroup_);
|
||||
Blockly.createSvgElement('line',
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'class': 'blocklyResizeLine',
|
||||
'x1': resizeSize * 2 / 3, 'y1': resizeSize - 1,
|
||||
'x2': resizeSize - 1, 'y2': resizeSize * 2 / 3}, this.resizeGroup_);
|
||||
|
@ -271,11 +271,11 @@ Blockly.Bubble.prototype.createDom_ = function(content, hasResize) {
|
|||
Blockly.Bubble.prototype.bubbleMouseDown_ = function(e) {
|
||||
this.promote_();
|
||||
Blockly.Bubble.unbindDragEvents_();
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// No right-click.
|
||||
e.stopPropagation();
|
||||
return;
|
||||
} else if (Blockly.isTargetInput_(e)) {
|
||||
} else if (Blockly.utils.isTargetInput(e)) {
|
||||
// When focused on an HTML text input widget, don't trap any events.
|
||||
return;
|
||||
}
|
||||
|
@ -317,7 +317,7 @@ Blockly.Bubble.prototype.bubbleMouseMove_ = function(e) {
|
|||
Blockly.Bubble.prototype.resizeMouseDown_ = function(e) {
|
||||
this.promote_();
|
||||
Blockly.Bubble.unbindDragEvents_();
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// No right-click.
|
||||
e.stopPropagation();
|
||||
return;
|
||||
|
|
|
@ -68,20 +68,20 @@ Blockly.Comment.prototype.height_ = 80;
|
|||
*/
|
||||
Blockly.Comment.prototype.drawIcon_ = function(group) {
|
||||
// Circle.
|
||||
Blockly.createSvgElement('circle',
|
||||
Blockly.utils.createSvgElement('circle',
|
||||
{'class': 'blocklyIconShape', 'r': '8', 'cx': '8', 'cy': '8'},
|
||||
group);
|
||||
// Can't use a real '?' text character since different browsers and operating
|
||||
// systems render it differently.
|
||||
// Body of question mark.
|
||||
Blockly.createSvgElement('path',
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyIconSymbol',
|
||||
'd': 'm6.8,10h2c0.003,-0.617 0.271,-0.962 0.633,-1.266 2.875,-2.405' +
|
||||
' 0.607,-5.534 -3.765,-3.874v1.7c3.12,-1.657 3.698,0.118 2.336,1.25' +
|
||||
' -1.201,0.998 -1.201,1.528 -1.204,2.19z'},
|
||||
group);
|
||||
// Dot of question point.
|
||||
Blockly.createSvgElement('rect',
|
||||
// Dot of question mark.
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyIconSymbol',
|
||||
'x': '6.8', 'y': '10.78', 'height': '2', 'width': '2'},
|
||||
group);
|
||||
|
@ -102,7 +102,7 @@ Blockly.Comment.prototype.createEditor_ = function() {
|
|||
</body>
|
||||
</foreignObject>
|
||||
*/
|
||||
this.foreignObject_ = Blockly.createSvgElement('foreignObject',
|
||||
this.foreignObject_ = Blockly.utils.createSvgElement('foreignObject',
|
||||
{'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
|
||||
null);
|
||||
var body = document.createElementNS(Blockly.HTML_NS, 'body');
|
||||
|
|
|
@ -261,3 +261,16 @@ Blockly.Categories = {
|
|||
"operators": "operators",
|
||||
"more": "more"
|
||||
};
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is in the delete area of the trash can.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_TRASH = 1;
|
||||
|
||||
/**
|
||||
* ENUM representing that an event is in the delete area of the toolbox or
|
||||
* flyout.
|
||||
* @const
|
||||
*/
|
||||
Blockly.DELETE_AREA_TOOLBOX = 2;
|
||||
|
|
|
@ -80,9 +80,10 @@ Blockly.ContextMenu.show = function(e, options, rtl) {
|
|||
var div = Blockly.WidgetDiv.DIV;
|
||||
menu.render(div);
|
||||
var menuDom = menu.getElement();
|
||||
Blockly.addClass_(menuDom, 'blocklyContextMenu');
|
||||
Blockly.utils.addClass(menuDom, 'blocklyContextMenu');
|
||||
// Prevent system context menu when right-clicking a Blockly context menu.
|
||||
Blockly.bindEventWithChecks_(menuDom, 'contextmenu', null, Blockly.noEvent);
|
||||
Blockly.bindEventWithChecks_(menuDom, 'contextmenu', null,
|
||||
Blockly.utils.noEvent);
|
||||
// Record menuSize after adding menu.
|
||||
var menuSize = goog.style.getSize(menuDom);
|
||||
|
||||
|
|
59
core/css.js
59
core/css.js
|
@ -98,9 +98,11 @@ Blockly.Css.inject = function(hasCss, pathToMedia) {
|
|||
);
|
||||
}
|
||||
}
|
||||
// Inject CSS tag.
|
||||
|
||||
// Inject CSS tag at start of head.
|
||||
var cssNode = document.createElement('style');
|
||||
document.head.appendChild(cssNode);
|
||||
document.head.insertBefore(cssNode, document.head.firstChild);
|
||||
|
||||
var cssTextNode = document.createTextNode(text);
|
||||
cssNode.appendChild(cssTextNode);
|
||||
Blockly.Css.styleSheet_ = cssNode.sheet;
|
||||
|
@ -160,6 +162,7 @@ Blockly.Css.CONTENT = [
|
|||
'background-color: $colour_workspace;',
|
||||
'outline: none;',
|
||||
'overflow: hidden;', /* IE overflows by default. */
|
||||
'position: absolute;',
|
||||
'display: block;',
|
||||
'}',
|
||||
|
||||
|
@ -179,6 +182,7 @@ Blockly.Css.CONTENT = [
|
|||
'.injectionDiv {',
|
||||
'height: 100%;',
|
||||
'position: relative;',
|
||||
'overflow: hidden;', /* So blocks in drag surface disappear at edges */
|
||||
'}',
|
||||
|
||||
'.blocklyNonSelectable {',
|
||||
|
@ -209,6 +213,25 @@ Blockly.Css.CONTENT = [
|
|||
'-ms-user-select: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyWsDragSurface {',
|
||||
'display: none;',
|
||||
'position: absolute;',
|
||||
'overflow: visible;',
|
||||
'top: 0;',
|
||||
'left: 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyBlockDragSurface {',
|
||||
'display: none;',
|
||||
'position: absolute;',
|
||||
'top: 0;',
|
||||
'left: 0;',
|
||||
'right: 0;',
|
||||
'bottom: 0;',
|
||||
'overflow: visible !important;',
|
||||
'z-index: 5000;',
|
||||
'}',
|
||||
|
||||
'.blocklyTooltipDiv {',
|
||||
'background-color: #ffffc7;',
|
||||
'border: 1px solid #ddc;',
|
||||
|
@ -223,21 +246,6 @@ Blockly.Css.CONTENT = [
|
|||
'z-index: 100000;', /* big value for bootstrap3 compatibility */
|
||||
'}',
|
||||
|
||||
'.blocklyDragSurface {',
|
||||
'display: none;',
|
||||
'position: absolute;',
|
||||
'top: 0;',
|
||||
'left: 0;',
|
||||
'right: 0;',
|
||||
'bottom: 0;',
|
||||
'overflow: visible !important;',
|
||||
'z-index: 5000;', /* Always display on top */
|
||||
'-webkit-backface-visibility: hidden;',
|
||||
'backface-visibility: hidden;',
|
||||
'-webkit-perspective: 1000;',
|
||||
'perspective: 1000;',
|
||||
'}',
|
||||
|
||||
'.blocklyDropDownDiv {',
|
||||
'position: fixed;',
|
||||
'left: 0;',
|
||||
|
@ -420,6 +428,10 @@ Blockly.Css.CONTENT = [
|
|||
'fill: $colour_text;',
|
||||
'}',
|
||||
|
||||
'.blocklyFlyout {',
|
||||
'position: absolute;',
|
||||
'z-index: 20;',
|
||||
'}',
|
||||
'.blocklyFlyoutButton {',
|
||||
'fill: #888;',
|
||||
'cursor: default;',
|
||||
|
@ -445,15 +457,11 @@ Blockly.Css.CONTENT = [
|
|||
'fill: #000;',
|
||||
'}',
|
||||
|
||||
'.blocklyFlyoutLabelText:hover {',
|
||||
'fill: #aaa;',
|
||||
'}',
|
||||
|
||||
/*
|
||||
Don't allow users to select text. It gets annoying when trying to
|
||||
drag a block and selected text moves instead.
|
||||
*/
|
||||
'.blocklySvg text {',
|
||||
'.blocklySvg text, .blocklyBlockDragSurface text {',
|
||||
'user-select: none;',
|
||||
'-moz-user-select: none;',
|
||||
'-webkit-user-select: none;',
|
||||
|
@ -530,6 +538,12 @@ Blockly.Css.CONTENT = [
|
|||
'fill-opacity: .8;',
|
||||
'}',
|
||||
|
||||
'.blocklyScrollbarHorizontal, .blocklyScrollbarVertical {',
|
||||
'position: absolute;',
|
||||
'outline: none;',
|
||||
'z-index: 30;',
|
||||
'}',
|
||||
|
||||
'.blocklyScrollbarBackground {',
|
||||
'opacity: 0;',
|
||||
'}',
|
||||
|
@ -619,6 +633,7 @@ Blockly.Css.CONTENT = [
|
|||
'overflow-y: auto;',
|
||||
'position: absolute;',
|
||||
'font-family: "Helvetica Neue", Helvetica, sans-serif;',
|
||||
'z-index: 70;', /* so blocks go under toolbox when dragging */
|
||||
'}',
|
||||
|
||||
'.blocklyTreeRoot {',
|
||||
|
|
|
@ -1,231 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 Massachusetts Institute of Technology
|
||||
* All rights reserved.
|
||||
*
|
||||
* 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 An SVG that floats on top of the workspace.
|
||||
* Blocks are moved into this SVG during a drag, improving performance.
|
||||
* The entire SVG is translated, so the blocks are never repainted during drag.
|
||||
* @author tmickel@mit.edu (Tim Mickel)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.DragSurfaceSvg');
|
||||
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.Colours');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
/**
|
||||
* Class for a Drag Surface SVG.
|
||||
* @param {Element} container Containing element.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.DragSurfaceSvg = function(container) {
|
||||
this.container_ = container;
|
||||
};
|
||||
|
||||
/**
|
||||
* The SVG drag surface. Set once by Blockly.DragSurfaceSvg.createDom.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.SVG_ = null;
|
||||
|
||||
/**
|
||||
* SVG group inside the drag surface. This is where blocks are moved to.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.dragGroup_ = null;
|
||||
|
||||
/**
|
||||
* Containing HTML element; parent of the workspace and the drag surface.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.container_ = null;
|
||||
|
||||
/**
|
||||
* Cached value for the scale of the drag surface.
|
||||
* Used to set/get the correct translation during and after a drag.
|
||||
* @type {Number}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.scale_ = 1;
|
||||
|
||||
/**
|
||||
* ID for the drag shadow filter, set in createDom.
|
||||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.dragShadowFilterId_ = '';
|
||||
|
||||
/**
|
||||
* Standard deviation for gaussian blur on drag shadow, in px.
|
||||
* @type {number}
|
||||
* @const
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.SHADOW_STD_DEVIATION = 6;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.createDom = function() {
|
||||
if (this.SVG_) {
|
||||
return; // Already created.
|
||||
}
|
||||
this.SVG_ = Blockly.createSvgElement('svg', {
|
||||
'xmlns': Blockly.SVG_NS,
|
||||
'xmlns:html': Blockly.HTML_NS,
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'class': 'blocklyDragSurface'
|
||||
}, this.container_);
|
||||
var defs = Blockly.createSvgElement('defs', {}, this.SVG_);
|
||||
this.dragShadowFilterId_ = this.createDropShadowDom_(defs);
|
||||
this.dragGroup_ = Blockly.createSvgElement('g', {}, this.SVG_);
|
||||
this.dragGroup_.setAttribute('filter', 'url(#' + this.dragShadowFilterId_ + ')');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the SVG def for the drop shadow.
|
||||
* @param {Element} defs Defs element to insert the shadow filter definition
|
||||
* @return {string} ID for the filter element
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.createDropShadowDom_ = function(defs) {
|
||||
// Adjust these width/height, x/y properties to prevent the shadow from clipping
|
||||
var dragShadowFilter = Blockly.createSvgElement('filter',
|
||||
{'id': 'blocklyDragShadowFilter', 'height': '140%', 'width': '140%', y: '-20%', x: '-20%'}, defs);
|
||||
Blockly.createSvgElement('feGaussianBlur',
|
||||
{'in': 'SourceAlpha', 'stdDeviation': Blockly.DragSurfaceSvg.SHADOW_STD_DEVIATION}, dragShadowFilter);
|
||||
var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'offsetBlur'}, dragShadowFilter);
|
||||
// Shadow opacity is specified in the adjustable colour library,
|
||||
// since the darkness of the shadow largely depends on the workspace colour.
|
||||
Blockly.createSvgElement('feFuncA',
|
||||
{'type': 'linear', 'slope': Blockly.Colours.dragShadowOpacity}, componentTransfer);
|
||||
Blockly.createSvgElement('feComposite',
|
||||
{'in': 'SourceGraphic', 'in2': 'offsetBlur', 'operator': 'over'}, dragShadowFilter);
|
||||
return dragShadowFilter.id;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the SVG blocks on the drag surface's group and show the surface.
|
||||
* Only one block should be on the drag surface at a time.
|
||||
* @param {!Element} blocks Block or group of blocks to place on the drag surface
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.setBlocksAndShow = function(blocks) {
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0, 'Already dragging a block.');
|
||||
// appendChild removes the blocks from the previous parent
|
||||
this.dragGroup_.appendChild(blocks);
|
||||
this.SVG_.style.display = 'block';
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate and scale the entire drag surface group to keep in sync with the workspace.
|
||||
* @param {Number} x X translation
|
||||
* @param {Number} y Y translation
|
||||
* @param {Number} scale Scale of the group
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.translateAndScaleGroup = function(x, y, scale) {
|
||||
var transform;
|
||||
this.scale_ = scale;
|
||||
// Force values to have two decimal points.
|
||||
// This is a work-around to prevent a bug in Safari, where numbers close to 0
|
||||
// are sometimes reported as something like "2.9842794901924208e-12".
|
||||
// That is incompatible with translate3d, causing bugs.
|
||||
x = x.toFixed(2);
|
||||
y = y.toFixed(2);
|
||||
if (Blockly.is3dSupported()) {
|
||||
transform = 'transform: translate3d(' + x + 'px, ' + y + 'px, 0px)' +
|
||||
'scale3d(' + scale + ',' + scale + ',' + scale + ')';
|
||||
this.dragGroup_.setAttribute('style', transform);
|
||||
} else {
|
||||
transform = 'translate(' + x + ', ' + y + ') scale(' + scale + ')';
|
||||
this.dragGroup_.setAttribute('transform', transform);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
* so that the browser avoids repainting the SVG.
|
||||
* Because of this, the drag coordinates must be adjusted by scale.
|
||||
* @param {Number} x X translation for the entire surface
|
||||
* @param {Number} y Y translation for the entire surface
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
var transform;
|
||||
x *= this.scale_;
|
||||
y *= this.scale_;
|
||||
// Force values to have two decimal points.
|
||||
// This is a work-around to prevent a bug in Safari, where numbers close to 0
|
||||
// are sometimes reported as something like "2.9842794901924208e-12".
|
||||
// That is incompatible with translate3d, causing bugs.
|
||||
x = x.toFixed(2);
|
||||
y = y.toFixed(2);
|
||||
if (Blockly.is3dSupported()) {
|
||||
transform = 'transform: translate3d(' + x + 'px, ' + y + 'px, 0px); display: block;';
|
||||
this.SVG_.setAttribute('style', transform);
|
||||
} else {
|
||||
transform = 'translate(' + x + ', ' + y + ')';
|
||||
this.SVG_.setAttribute('transform', transform);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
* @return {goog.math.Coordinate} Current translation of the surface
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
var xy = Blockly.getRelativeXY_(this.SVG_);
|
||||
return new goog.math.Coordinate(xy.x / this.scale_, xy.y / this.scale_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Provide a reference to the drag group (primarily for BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {Element} Drag surface group element
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.getGroup = function() {
|
||||
return this.dragGroup_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current blocks on the drag surface, if any (primarily for BlockSvg.getRelativeToSurfaceXY).
|
||||
* @return {Element} Drag surface block DOM element
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.getCurrentBlock = function() {
|
||||
return this.dragGroup_.childNodes[0];
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear the group and hide the surface; move the blocks off onto the provided element.
|
||||
* @param {!Element} newSurface Surface the dragging blocks should be moved to
|
||||
*/
|
||||
Blockly.DragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
// appendChild removes the node from this.dragGroup_
|
||||
newSurface.appendChild(this.getCurrentBlock());
|
||||
this.SVG_.style.display = 'none';
|
||||
goog.asserts.assert(this.dragGroup_.childNodes.length == 0, 'Drag group was not cleared.');
|
||||
};
|
|
@ -234,7 +234,7 @@ Blockly.Events.getGroup = function() {
|
|||
*/
|
||||
Blockly.Events.setGroup = function(state) {
|
||||
if (typeof state == 'boolean') {
|
||||
Blockly.Events.group_ = state ? Blockly.genUid() : '';
|
||||
Blockly.Events.group_ = state ? Blockly.utils.genUid() : '';
|
||||
} else {
|
||||
Blockly.Events.group_ = state;
|
||||
}
|
||||
|
|
145
core/field.js
145
core/field.js
|
@ -145,7 +145,7 @@ Blockly.Field.prototype.init = function() {
|
|||
return;
|
||||
}
|
||||
// Build the DOM.
|
||||
this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
|
||||
this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
if (!this.visible_) {
|
||||
this.fieldGroup_.style.display = 'none';
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ Blockly.Field.prototype.init = function() {
|
|||
var size = this.getSize();
|
||||
var fieldX = (this.sourceBlock_.RTL) ? -size.width / 2 : size.width / 2;
|
||||
/** @type {!Element} */
|
||||
this.textElement_ = Blockly.createSvgElement('text',
|
||||
this.textElement_ = Blockly.utils.createSvgElement('text',
|
||||
{'class': 'blocklyText',
|
||||
'x': fieldX,
|
||||
'y': size.height / 2 + Blockly.BlockSvg.FIELD_TOP_PADDING,
|
||||
|
@ -177,7 +177,8 @@ Blockly.Field.prototype.init = function() {
|
|||
Blockly.bindEventWithChecks_(this.getClickTarget_(), 'mouseup', this,
|
||||
this.onMouseUp_);
|
||||
// Force a render.
|
||||
this.updateTextNode_();
|
||||
this.render_();
|
||||
this.size_.width = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -204,13 +205,13 @@ Blockly.Field.prototype.updateEditable = function() {
|
|||
return;
|
||||
}
|
||||
if (this.sourceBlock_.isEditable()) {
|
||||
Blockly.addClass_(group, 'blocklyEditableText');
|
||||
Blockly.removeClass_(group, 'blocklyNonEditableText');
|
||||
this.getClickTarget_().style.cursor = this.CURSOR;
|
||||
Blockly.utils.addClass(group, 'blocklyEditableText');
|
||||
Blockly.utils.removeClass(group, 'blocklyNonEditableText');
|
||||
this.fieldGroup_.style.cursor = this.CURSOR;
|
||||
} else {
|
||||
Blockly.addClass_(group, 'blocklyNonEditableText');
|
||||
Blockly.removeClass_(group, 'blocklyEditableText');
|
||||
this.getClickTarget_().style.cursor = '';
|
||||
Blockly.utils.addClass(group, 'blocklyNonEditableText');
|
||||
Blockly.utils.removeClass(group, 'blocklyEditableText');
|
||||
this.fieldGroup_.style.cursor = '';
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -328,42 +329,40 @@ Blockly.Field.prototype.getSvgRoot = function() {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.render_ = function() {
|
||||
var width = 0;
|
||||
|
||||
if (this.visible_ && this.textElement_) {
|
||||
var key = this.textElement_.textContent + '\n' +
|
||||
this.textElement_.className.baseVal;
|
||||
if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) {
|
||||
var width = Blockly.Field.cacheWidths_[key];
|
||||
} else {
|
||||
try {
|
||||
var width = this.textElement_.getComputedTextLength();
|
||||
} catch (e) {
|
||||
// MSIE 11 is known to throw "Unexpected call to method or property
|
||||
// access." if Blockly is hidden.
|
||||
var width = this.textElement_.textContent.length * 8;
|
||||
}
|
||||
if (Blockly.Field.cacheWidths_) {
|
||||
Blockly.Field.cacheWidths_[key] = width;
|
||||
}
|
||||
}
|
||||
// Replace the text.
|
||||
goog.dom.removeChildren(/** @type {!Element} */ (this.textElement_));
|
||||
var textNode = document.createTextNode(this.getDisplayText_());
|
||||
this.textElement_.appendChild(textNode);
|
||||
|
||||
// Calculate width of field
|
||||
width = Blockly.Field.getCachedWidth(this.textElement_);
|
||||
|
||||
// Add padding to left and right of text.
|
||||
if (this.EDITABLE) {
|
||||
// Add padding to left and right of text.
|
||||
width += Blockly.BlockSvg.EDITABLE_FIELD_PADDING;
|
||||
}
|
||||
|
||||
// Adjust width for drop-down arrows.
|
||||
var arrowWidth = 0;
|
||||
if (this.positionArrow) {
|
||||
arrowWidth = this.positionArrow(width);
|
||||
width += arrowWidth;
|
||||
}
|
||||
|
||||
// Add padding to any drawn box.
|
||||
if (this.box_) {
|
||||
// Add padding to any drawn box.
|
||||
width += 2 * Blockly.BlockSvg.BOX_FIELD_PADDING;
|
||||
}
|
||||
|
||||
// Update text centering, based on newly calculated width.
|
||||
var centerTextX = (width - arrowWidth) / 2;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
centerTextX += arrowWidth;
|
||||
}
|
||||
|
||||
// In a text-editing shadow block's field,
|
||||
// if half the text length is not at least center of
|
||||
// visible field (FIELD_WIDTH), center it there instead,
|
||||
|
@ -382,12 +381,14 @@ Blockly.Field.prototype.render_ = function() {
|
|||
centerTextX = Math.max(minOffset, centerTextX);
|
||||
}
|
||||
}
|
||||
this.textElement_.setAttribute('x', centerTextX);
|
||||
|
||||
} else {
|
||||
var width = 0;
|
||||
// Apply new text element x position.
|
||||
this.textElement_.setAttribute('x', centerTextX);
|
||||
}
|
||||
|
||||
// Set width of the field.
|
||||
this.size_.width = width;
|
||||
|
||||
// Update any drawn box to the correct width and height.
|
||||
if (this.box_) {
|
||||
this.box_.setAttribute('width', this.size_.width);
|
||||
|
@ -395,6 +396,31 @@ Blockly.Field.prototype.render_ = function() {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the width of a text element, caching it in the process.
|
||||
* @param {!Element} textElement An SVG 'text' element.
|
||||
* @return {number} Width of element.
|
||||
*/
|
||||
Blockly.Field.getCachedWidth = function(textElement) {
|
||||
var key = textElement.textContent + '\n' + textElement.className.baseVal;
|
||||
if (Blockly.Field.cacheWidths_ && Blockly.Field.cacheWidths_[key]) {
|
||||
var width = Blockly.Field.cacheWidths_[key];
|
||||
} else {
|
||||
try {
|
||||
var width = textElement.getComputedTextLength();
|
||||
} catch (e) {
|
||||
// MSIE 11 is known to throw "Unexpected call to method or property
|
||||
// access." if Blockly is hidden.
|
||||
var width = textElement.textContent.length * 8;
|
||||
}
|
||||
|
||||
if (Blockly.Field.cacheWidths_) {
|
||||
Blockly.Field.cacheWidths_[key] = width;
|
||||
}
|
||||
}
|
||||
return width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Start caching field widths. Every call to this function MUST also call
|
||||
* stopCache. Caches must not survive between execution threads.
|
||||
|
@ -441,6 +467,31 @@ Blockly.Field.prototype.getScaledBBox_ = function() {
|
|||
size.height * this.sourceBlock_.workspace.scale);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field as displayed on screen. May differ from getText
|
||||
* due to ellipsis, and other formatting.
|
||||
* @return {string} Currently displayed text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Field.prototype.getDisplayText_ = function() {
|
||||
var text = this.text_;
|
||||
if (!text) {
|
||||
// Prevent the field from disappearing if empty.
|
||||
return Blockly.Field.NBSP;
|
||||
}
|
||||
if (text.length > this.maxDisplayLength) {
|
||||
// Truncate displayed string and add an ellipsis ('...').
|
||||
text = text.substring(0, this.maxDisplayLength - 2) + '\u2026';
|
||||
}
|
||||
// Replace whitespace with non-breaking spaces so the text doesn't collapse.
|
||||
text = text.replace(/\s/g, Blockly.Field.NBSP);
|
||||
if (this.sourceBlock_.RTL) {
|
||||
// The SVG is LTR, force text to be RTL.
|
||||
text += '\u200F';
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the text from this field.
|
||||
* @return {string} Current text.
|
||||
|
@ -451,21 +502,21 @@ Blockly.Field.prototype.getText = function() {
|
|||
|
||||
/**
|
||||
* Set the text in this field. Trigger a rerender of the source block.
|
||||
* @param {*} text New text.
|
||||
* @param {*} newText New text.
|
||||
*/
|
||||
Blockly.Field.prototype.setText = function(text) {
|
||||
if (text === null) {
|
||||
Blockly.Field.prototype.setText = function(newText) {
|
||||
if (newText === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
text = String(text);
|
||||
if (text === this.text_) {
|
||||
newText = String(newText);
|
||||
if (newText === this.text_) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
this.text_ = text;
|
||||
this.updateTextNode_();
|
||||
|
||||
this.text_ = newText;
|
||||
// Set width to 0 to force a rerender of this field.
|
||||
this.size_.width = 0;
|
||||
if (this.sourceBlock_ && this.sourceBlock_.rendered) {
|
||||
this.sourceBlock_.render();
|
||||
this.sourceBlock_.bumpNeighbours_();
|
||||
|
@ -512,7 +563,7 @@ Blockly.Field.prototype.updateTextNode_ = function() {
|
|||
/**
|
||||
* By default there is no difference between the human-readable text and
|
||||
* the language-neutral values. Subclasses (such as dropdown) may define this.
|
||||
* @return {string} Current text.
|
||||
* @return {string} Current value.
|
||||
*/
|
||||
Blockly.Field.prototype.getValue = function() {
|
||||
return this.getText();
|
||||
|
@ -521,22 +572,22 @@ Blockly.Field.prototype.getValue = function() {
|
|||
/**
|
||||
* By default there is no difference between the human-readable text and
|
||||
* the language-neutral values. Subclasses (such as dropdown) may define this.
|
||||
* @param {string} newText New text.
|
||||
* @param {string} newValue New value.
|
||||
*/
|
||||
Blockly.Field.prototype.setValue = function(newText) {
|
||||
if (newText === null) {
|
||||
Blockly.Field.prototype.setValue = function(newValue) {
|
||||
if (newValue === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
var oldText = this.getValue();
|
||||
if (oldText == newText) {
|
||||
var oldValue = this.getValue();
|
||||
if (oldValue == newValue) {
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
this.sourceBlock_, 'field', this.name, oldText, newText));
|
||||
this.sourceBlock_, 'field', this.name, oldValue, newValue));
|
||||
}
|
||||
this.setText(newText);
|
||||
this.setText(newValue);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -551,7 +602,7 @@ Blockly.Field.prototype.onMouseUp_ = function(e) {
|
|||
// Old iOS spawns a bogus event on the next touch after a 'prompt()' edit.
|
||||
// Unlike the real events, these have a layerX and layerY set.
|
||||
return;
|
||||
} else if (Blockly.isRightButton(e)) {
|
||||
} else if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
return;
|
||||
} else if (this.sourceBlock_.workspace.isDragging()) {
|
||||
|
|
|
@ -44,7 +44,7 @@ goog.require('goog.userAgent');
|
|||
*/
|
||||
Blockly.FieldAngle = function(text, opt_validator) {
|
||||
// Add degree symbol: "360°" (LTR) or "°360" (RTL)
|
||||
this.symbol_ = Blockly.createSvgElement('tspan', {}, null);
|
||||
this.symbol_ = Blockly.utils.createSvgElement('tspan', {}, null);
|
||||
this.symbol_.appendChild(document.createTextNode('\u00B0'));
|
||||
|
||||
Blockly.FieldAngle.superClass_.constructor.call(this, text, opt_validator);
|
||||
|
@ -135,7 +135,7 @@ Blockly.FieldAngle.prototype.showEditor_ = function() {
|
|||
Blockly.DropDownDiv.clearContent();
|
||||
var div = Blockly.DropDownDiv.getContentDiv();
|
||||
// Build the SVG DOM.
|
||||
var svg = Blockly.createSvgElement('svg', {
|
||||
var svg = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
|
@ -143,20 +143,20 @@ Blockly.FieldAngle.prototype.showEditor_ = function() {
|
|||
'height': (Blockly.FieldAngle.HALF * 2) + 'px',
|
||||
'width': (Blockly.FieldAngle.HALF * 2) + 'px'
|
||||
}, div);
|
||||
var circle = Blockly.createSvgElement('circle', {
|
||||
var circle = Blockly.utils.createSvgElement('circle', {
|
||||
'cx': Blockly.FieldAngle.HALF, 'cy': Blockly.FieldAngle.HALF,
|
||||
'r': Blockly.FieldAngle.RADIUS,
|
||||
'class': 'blocklyAngleCircle'
|
||||
}, svg);
|
||||
this.gauge_ = Blockly.createSvgElement('path',
|
||||
this.gauge_ = Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyAngleGauge'}, svg);
|
||||
this.line_ = Blockly.createSvgElement('line',
|
||||
this.line_ = Blockly.utils.createSvgElement('line',
|
||||
{'x1': Blockly.FieldAngle.HALF,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'class': 'blocklyAngleLine'}, svg);
|
||||
// Draw markers around the edge.
|
||||
for (var angle = 0; angle < 360; angle += 15) {
|
||||
Blockly.createSvgElement('line', {
|
||||
Blockly.utils.createSvgElement('line', {
|
||||
'x1': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS,
|
||||
'y1': Blockly.FieldAngle.HALF,
|
||||
'x2': Blockly.FieldAngle.HALF + Blockly.FieldAngle.RADIUS -
|
||||
|
@ -200,7 +200,7 @@ Blockly.FieldAngle.prototype.onMouseMove = function(e) {
|
|||
var dy = e.clientY - bBox.top - Blockly.FieldAngle.HALF;
|
||||
var angle = Math.atan(-dy / dx);
|
||||
if (isNaN(angle)) {
|
||||
// This shouldn't happen, but let's not let this error propogate further.
|
||||
// This shouldn't happen, but let's not let this error propagate further.
|
||||
return;
|
||||
}
|
||||
angle = goog.math.toDegrees(angle);
|
||||
|
|
|
@ -68,7 +68,7 @@ Blockly.FieldCheckbox.prototype.init = function() {
|
|||
Blockly.FieldCheckbox.superClass_.init.call(this);
|
||||
// The checkbox doesn't use the inherited text element.
|
||||
// Instead it uses a custom checkmark element that is either visible or not.
|
||||
this.checkElement_ = Blockly.createSvgElement('text',
|
||||
this.checkElement_ = Blockly.utils.createSvgElement('text',
|
||||
{'class': 'blocklyText blocklyCheckbox', 'x': -3, 'y': 14},
|
||||
this.fieldGroup_);
|
||||
var textNode = document.createTextNode(Blockly.FieldCheckbox.CHECK_CHAR);
|
||||
|
@ -85,11 +85,13 @@ Blockly.FieldCheckbox.prototype.getValue = function() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Set the checkbox to be checked if strBool is 'TRUE', unchecks otherwise.
|
||||
* @param {string} strBool New state.
|
||||
* Set the checkbox to be checked if newBool is 'TRUE' or true,
|
||||
* unchecks otherwise.
|
||||
* @param {string|boolean} newBool New state.
|
||||
*/
|
||||
Blockly.FieldCheckbox.prototype.setValue = function(strBool) {
|
||||
var newState = (strBool.toUpperCase() == 'TRUE');
|
||||
Blockly.FieldCheckbox.prototype.setValue = function(newBool) {
|
||||
var newState = (typeof newBool == 'string') ?
|
||||
(newBool.toUpperCase() == 'TRUE') : !!newBool;
|
||||
if (this.state_ !== newState) {
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
|
|
|
@ -243,4 +243,5 @@ Blockly.FieldColour.widgetDispose_ = function() {
|
|||
if (Blockly.FieldColour.changeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldColour.changeEventKey_);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
|
|
@ -166,6 +166,7 @@ Blockly.FieldDate.widgetDispose_ = function() {
|
|||
if (Blockly.FieldDate.changeEventKey_) {
|
||||
goog.events.unlistenByKey(Blockly.FieldDate.changeEventKey_);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -94,7 +94,7 @@ Blockly.FieldDropdown.prototype.init = function() {
|
|||
this.arrowX_ = 0;
|
||||
/** @type {Number} */
|
||||
this.arrowY_ = 11;
|
||||
this.arrow_ = Blockly.createSvgElement('image', {
|
||||
this.arrow_ = Blockly.utils.createSvgElement('image', {
|
||||
'height': this.arrowSize_ + 'px',
|
||||
'width': this.arrowSize_ + 'px'
|
||||
});
|
||||
|
@ -104,7 +104,7 @@ Blockly.FieldDropdown.prototype.init = function() {
|
|||
Blockly.FieldDropdown.superClass_.init.call(this);
|
||||
// If not in a shadow block, draw a box.
|
||||
if (!this.sourceBlock_.isShadow()) {
|
||||
this.box_ = Blockly.createSvgElement('rect', {
|
||||
this.box_ = Blockly.utils.createSvgElement('rect', {
|
||||
'rx': Blockly.BlockSvg.CORNER_RADIUS,
|
||||
'ry': Blockly.BlockSvg.CORNER_RADIUS,
|
||||
'x': 0,
|
||||
|
@ -185,7 +185,7 @@ Blockly.FieldDropdown.prototype.showEditor_ = function() {
|
|||
// Record windowSize and scrollOffset before adding menu.
|
||||
menu.render(contentDiv);
|
||||
var menuDom = menu.getElement();
|
||||
Blockly.addClass_(menuDom, 'blocklyDropdownMenu');
|
||||
Blockly.utils.addClass(menuDom, 'blocklyDropdownMenu');
|
||||
// Record menuSize after adding menu.
|
||||
var menuSize = goog.style.getSize(menuDom);
|
||||
// Recalculate height for the total content, not only box height.
|
||||
|
@ -275,9 +275,9 @@ Blockly.FieldDropdown.prototype.trimOptions_ = function() {
|
|||
return;
|
||||
}
|
||||
var strings = options.map(function(t) {return t[0];});
|
||||
var shortest = Blockly.shortestStringLength(strings);
|
||||
var prefixLength = Blockly.commonWordPrefix(strings, shortest);
|
||||
var suffixLength = Blockly.commonWordSuffix(strings, shortest);
|
||||
var shortest = Blockly.utils.shortestStringLength(strings);
|
||||
var prefixLength = Blockly.utils.commonWordPrefix(strings, shortest);
|
||||
var suffixLength = Blockly.utils.commonWordSuffix(strings, shortest);
|
||||
if (!prefixLength && !suffixLength) {
|
||||
return;
|
||||
}
|
||||
|
@ -388,6 +388,10 @@ Blockly.FieldDropdown.prototype.setText = function(text) {
|
|||
* @return {number} Amount of space the arrow is taking up, in px.
|
||||
*/
|
||||
Blockly.FieldDropdown.prototype.positionArrow = function(x) {
|
||||
if (!this.arrow_) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
var addedWidth = 0;
|
||||
if (this.sourceBlock_.RTL) {
|
||||
this.arrowX_ = this.arrowSize_ - Blockly.BlockSvg.DROPDOWN_ARROW_PADDING;
|
||||
|
|
|
@ -81,7 +81,7 @@ Blockly.FieldIconMenu.prototype.init = function(block) {
|
|||
this.arrowX_ = -this.arrowX_ - arrowSize;
|
||||
}
|
||||
/** @type {Element} */
|
||||
this.arrowIcon_ = Blockly.createSvgElement('image', {
|
||||
this.arrowIcon_ = Blockly.utils.createSvgElement('image', {
|
||||
'height': arrowSize + 'px',
|
||||
'width': arrowSize + 'px',
|
||||
'transform': 'translate(' + this.arrowX_ + ',' + this.arrowY_ + ')'
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Image field. Used for titles, labels, etc.
|
||||
* @fileoverview Image field. Used for pictures, icons, etc.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
@ -54,13 +54,6 @@ Blockly.FieldImage = function(src, width, height, opt_alt, flip_rtl) {
|
|||
};
|
||||
goog.inherits(Blockly.FieldImage, Blockly.Field);
|
||||
|
||||
/**
|
||||
* Rectangular mask used by Firefox.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.FieldImage.prototype.rectElement_ = null;
|
||||
|
||||
/**
|
||||
* Editable fields are saved by the XML renderer, non-editable fields are not.
|
||||
*/
|
||||
|
@ -76,32 +69,20 @@ Blockly.FieldImage.prototype.init = function() {
|
|||
}
|
||||
// Build the DOM.
|
||||
/** @type {SVGElement} */
|
||||
this.fieldGroup_ = Blockly.createSvgElement('g', {}, null);
|
||||
this.fieldGroup_ = Blockly.utils.createSvgElement('g', {}, null);
|
||||
if (!this.visible_) {
|
||||
this.fieldGroup_.style.display = 'none';
|
||||
}
|
||||
/** @type {SVGElement} */
|
||||
this.imageElement_ = Blockly.createSvgElement('image',
|
||||
this.imageElement_ = Blockly.utils.createSvgElement('image',
|
||||
{'height': this.height_ + 'px',
|
||||
'width': this.width_ + 'px'}, this.fieldGroup_);
|
||||
this.setValue(this.src_);
|
||||
if (goog.userAgent.GECKO) {
|
||||
/**
|
||||
* Due to a Firefox bug which eats mouse events on image elements,
|
||||
* a transparent rectangle needs to be placed on top of the image.
|
||||
* @type {SVGElement}
|
||||
*/
|
||||
this.rectElement_ = Blockly.createSvgElement('rect',
|
||||
{'height': this.height_ + 'px',
|
||||
'width': this.width_ + 'px',
|
||||
'fill-opacity': 0}, this.fieldGroup_);
|
||||
}
|
||||
this.sourceBlock_.getSvgRoot().appendChild(this.fieldGroup_);
|
||||
|
||||
// Configure the field to be transparent with respect to tooltips.
|
||||
var topElement = this.rectElement_ || this.imageElement_;
|
||||
topElement.tooltip = this.sourceBlock_;
|
||||
Blockly.Tooltip.bindMouseEvents(topElement);
|
||||
this.setTooltip(this.sourceBlock_);
|
||||
Blockly.Tooltip.bindMouseEvents(this.imageElement_);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -111,7 +92,6 @@ Blockly.FieldImage.prototype.dispose = function() {
|
|||
goog.dom.removeNode(this.fieldGroup_);
|
||||
this.fieldGroup_ = null;
|
||||
this.imageElement_ = null;
|
||||
this.rectElement_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -120,8 +100,7 @@ Blockly.FieldImage.prototype.dispose = function() {
|
|||
* link to for its tooltip.
|
||||
*/
|
||||
Blockly.FieldImage.prototype.setTooltip = function(newTip) {
|
||||
var topElement = this.rectElement_ || this.imageElement_;
|
||||
topElement.tooltip = newTip;
|
||||
this.imageElement_.tooltip = newTip;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -146,7 +125,7 @@ Blockly.FieldImage.prototype.setValue = function(src) {
|
|||
this.src_ = src;
|
||||
if (this.imageElement_) {
|
||||
this.imageElement_.setAttributeNS('http://www.w3.org/1999/xlink',
|
||||
'xlink:href', goog.isString(src) ? src : '');
|
||||
'xlink:href', src || '');
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -60,14 +60,14 @@ Blockly.FieldLabel.prototype.init = function() {
|
|||
return;
|
||||
}
|
||||
// Build the DOM.
|
||||
this.textElement_ = Blockly.createSvgElement('text',
|
||||
this.textElement_ = Blockly.utils.createSvgElement('text',
|
||||
{'class': 'blocklyText',
|
||||
'y': Blockly.BlockSvg.FIELD_TOP_PADDING,
|
||||
'text-anchor': 'middle',
|
||||
'dominant-baseline': 'middle'
|
||||
}, null);
|
||||
if (this.class_) {
|
||||
Blockly.addClass_(this.textElement_, this.class_);
|
||||
Blockly.utils.addClass(this.textElement_, this.class_);
|
||||
}
|
||||
if (!this.visible_) {
|
||||
this.textElement_.style.display = 'none';
|
||||
|
@ -78,7 +78,7 @@ Blockly.FieldLabel.prototype.init = function() {
|
|||
this.textElement_.tooltip = this.sourceBlock_;
|
||||
Blockly.Tooltip.bindMouseEvents(this.textElement_);
|
||||
// Force a render.
|
||||
this.updateTextNode_();
|
||||
this.render_();
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -69,7 +69,7 @@ Blockly.FieldTextDropdown.prototype.init = function() {
|
|||
this.arrowX_ = 0;
|
||||
/** @type {Number} */
|
||||
this.arrowY_ = 11;
|
||||
this.arrow_ = Blockly.createSvgElement('image', {
|
||||
this.arrow_ = Blockly.utils.createSvgElement('image', {
|
||||
'height': this.arrowSize_ + 'px',
|
||||
'width': this.arrowSize_ + 'px'
|
||||
});
|
||||
|
|
|
@ -87,7 +87,7 @@ Blockly.FieldTextInput.prototype.init = function() {
|
|||
Blockly.FieldTextInput.superClass_.init.call(this);
|
||||
// If not in a shadow block, draw a box.
|
||||
if (!this.sourceBlock_.isShadow()) {
|
||||
this.box_ = Blockly.createSvgElement('rect', {
|
||||
this.box_ = Blockly.utils.createSvgElement('rect', {
|
||||
'rx': Blockly.BlockSvg.CORNER_RADIUS,
|
||||
'ry': Blockly.BlockSvg.CORNER_RADIUS,
|
||||
'x': 0,
|
||||
|
@ -110,23 +110,44 @@ Blockly.FieldTextInput.prototype.dispose = function() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Set the text in this field.
|
||||
* @param {?string} text New text.
|
||||
* Set the value of this field.
|
||||
* @param {?string} newValue New value.
|
||||
* @override
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.setValue = function(text) {
|
||||
if (text === null) {
|
||||
Blockly.FieldTextInput.prototype.setValue = function(newValue) {
|
||||
if (newValue === null) {
|
||||
return; // No change if null.
|
||||
}
|
||||
if (this.sourceBlock_) {
|
||||
var validated = this.callValidator(text);
|
||||
// If the new text is invalid, validation returns null.
|
||||
var validated = this.callValidator(newValue);
|
||||
// If the new value is invalid, validation returns null.
|
||||
// In this case we still want to display the illegal result.
|
||||
if (validated !== null) {
|
||||
text = validated;
|
||||
newValue = validated;
|
||||
}
|
||||
}
|
||||
Blockly.Field.prototype.setValue.call(this, text);
|
||||
Blockly.Field.prototype.setValue.call(this, newValue);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the text in this field and fire a change event.
|
||||
* @param {*} newText New text.
|
||||
*/
|
||||
Blockly.FieldTextInput.prototype.setText = function(newText) {
|
||||
if (newText === null) {
|
||||
// No change if null.
|
||||
return;
|
||||
}
|
||||
newText = String(newText);
|
||||
if (newText === this.text_) {
|
||||
// No change.
|
||||
return;
|
||||
}
|
||||
if (this.sourceBlock_ && Blockly.Events.isEnabled()) {
|
||||
Blockly.Events.fire(new Blockly.Events.Change(
|
||||
this.sourceBlock_, 'field', this.name, this.text_, newText));
|
||||
}
|
||||
Blockly.Field.prototype.setText.call(this, newText);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -326,7 +347,7 @@ Blockly.FieldTextInput.prototype.onHtmlInputChange_ = function(e) {
|
|||
var text = htmlInput.value;
|
||||
if (text !== htmlInput.oldValue_) {
|
||||
htmlInput.oldValue_ = text;
|
||||
this.setValue(text);
|
||||
this.setText(text);
|
||||
this.validate_();
|
||||
} else if (goog.userAgent.WEBKIT) {
|
||||
// Cursor key. Render the source block to show the caret moving.
|
||||
|
@ -349,9 +370,9 @@ Blockly.FieldTextInput.prototype.validate_ = function() {
|
|||
valid = this.callValidator(htmlInput.value);
|
||||
}
|
||||
if (valid === null) {
|
||||
Blockly.addClass_(htmlInput, 'blocklyInvalidInput');
|
||||
Blockly.utils.addClass(htmlInput, 'blocklyInvalidInput');
|
||||
} else {
|
||||
Blockly.removeClass_(htmlInput, 'blocklyInvalidInput');
|
||||
Blockly.utils.removeClass(htmlInput, 'blocklyInvalidInput');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -478,6 +499,7 @@ Blockly.FieldTextInput.prototype.widgetDispose_ = function() {
|
|||
}
|
||||
thisField.workspace_.removeChangeListener(
|
||||
htmlInput.onWorkspaceChangeWrapper_);
|
||||
Blockly.Events.setGroup(false);
|
||||
|
||||
// Animation of disposal
|
||||
htmlInput.style.fontSize = Blockly.BlockSvg.FIELD_TEXTINPUT_FONTSIZE_INITIAL + 'pt';
|
||||
|
|
|
@ -148,8 +148,8 @@ Blockly.FieldVariable.dropdownCreate = function() {
|
|||
* Handle the selection of an item in the variable dropdown menu.
|
||||
* Special case the 'Rename variable...' and 'Delete variable...' options.
|
||||
* In the rename case, prompt the user for a new name.
|
||||
* @param {goog.ui.Menu} menu The Menu component clicked.
|
||||
* @param {goog.ui.MenuItem} menuItem The MenuItem selected within menu.
|
||||
* @param {!goog.ui.Menu} menu The Menu component clicked.
|
||||
* @param {!goog.ui.MenuItem} menuItem The MenuItem selected within menu.
|
||||
*/
|
||||
Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
|
||||
var itemText = menuItem.getValue();
|
||||
|
@ -157,7 +157,7 @@ Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
|
|||
var workspace = this.sourceBlock_.workspace;
|
||||
if (this.renameVarItemIndex_ >= 0 &&
|
||||
menu.getChildAt(this.renameVarItemIndex_) === menuItem) {
|
||||
// Rename variable
|
||||
// Rename variable.
|
||||
var oldName = this.getText();
|
||||
Blockly.hideChaff();
|
||||
Blockly.Variables.promptName(
|
||||
|
@ -170,7 +170,7 @@ Blockly.FieldVariable.prototype.onItemSelected = function(menu, menuItem) {
|
|||
return;
|
||||
} else if (this.deleteVarItemIndex_ >= 0 &&
|
||||
menu.getChildAt(this.deleteVarItemIndex_) === menuItem) {
|
||||
// Delete variable
|
||||
// Delete variable.
|
||||
workspace.deleteVariable(this.getText());
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -186,6 +186,20 @@ Blockly.Flyout.onMouseMoveBlockWrapper_ = null;
|
|||
*/
|
||||
Blockly.Flyout.prototype.autoClose = true;
|
||||
|
||||
/**
|
||||
* Whether the flyout is visible.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.isVisible_ = false;
|
||||
|
||||
/**
|
||||
* Whether the workspace containing this flyout is visible.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.containerVisible_ = true;
|
||||
|
||||
/**
|
||||
* Corner radius of the flyout background.
|
||||
* @type {number}
|
||||
|
@ -292,19 +306,25 @@ Blockly.Flyout.prototype.dragMode_ = Blockly.DRAG_NONE;
|
|||
|
||||
|
||||
/**
|
||||
* Creates the flyout's DOM. Only needs to be called once.
|
||||
* Creates the flyout's DOM. Only needs to be called once. The flyout can
|
||||
* either exist as its own <svg> element or be a <g> nested inside a separate
|
||||
* <svg> element.
|
||||
* @param {string} tagName The type of tag to put the flyout in. This
|
||||
* should be <svg> or <g>.
|
||||
* @return {!Element} The flyout's SVG group.
|
||||
*/
|
||||
Blockly.Flyout.prototype.createDom = function() {
|
||||
Blockly.Flyout.prototype.createDom = function(tagName) {
|
||||
/*
|
||||
<g>
|
||||
<svg | g>
|
||||
<path class="blocklyFlyoutBackground"/>
|
||||
<g class="blocklyFlyout"></g>
|
||||
</g>
|
||||
</ svg | g>
|
||||
*/
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
{'class': 'blocklyFlyout'}, null);
|
||||
this.svgBackground_ = Blockly.createSvgElement('path',
|
||||
// Setting style to display:none to start. The toolbox and flyout
|
||||
// hide/show code will set up proper visibility and size later.
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement(tagName,
|
||||
{'class': 'blocklyFlyout', 'style' : 'display: none'}, null);
|
||||
this.svgBackground_ = Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyFlyoutBackground'}, this.svgGroup_);
|
||||
this.svgGroup_.appendChild(this.workspace_.createDom());
|
||||
return this.svgGroup_;
|
||||
|
@ -394,7 +414,51 @@ Blockly.Flyout.prototype.getWorkspace = function() {
|
|||
* @return {boolean} True if visible.
|
||||
*/
|
||||
Blockly.Flyout.prototype.isVisible = function() {
|
||||
return this.svgGroup_ && this.svgGroup_.style.display == 'block';
|
||||
return this.isVisible_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the flyout is visible. A value of true does not necessarily mean
|
||||
* that the flyout is shown. It could be hidden because its container is hidden.
|
||||
* @param {boolean} visible True if visible.
|
||||
*/
|
||||
Blockly.Flyout.prototype.setVisible = function(visible) {
|
||||
var visibilityChanged = (visible != this.isVisible());
|
||||
|
||||
this.isVisible_ = visible;
|
||||
if (visibilityChanged) {
|
||||
this.updateDisplay_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this flyout's container is visible.
|
||||
* @param {boolean} visible Whether the container is visible.
|
||||
*/
|
||||
Blockly.Flyout.prototype.setContainerVisible = function(visible) {
|
||||
var visibilityChanged = (visible != this.containerVisible_);
|
||||
this.containerVisible_ = visible;
|
||||
if (visibilityChanged) {
|
||||
this.updateDisplay_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update the display property of the flyout based whether it thinks it should
|
||||
* be visible and whether its containing workspace is visible.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.updateDisplay_ = function() {
|
||||
var show = true;
|
||||
if (!this.containerVisible_) {
|
||||
show = false;
|
||||
} else {
|
||||
show = this.isVisible();
|
||||
}
|
||||
this.svgGroup_.style.display = show ? 'block' : 'none';
|
||||
// Update the scrollbar's visiblity too since it should mimic the
|
||||
// flyout's visibility.
|
||||
this.scrollbar_.setContainerVisible(show);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -404,7 +468,7 @@ Blockly.Flyout.prototype.hide = function() {
|
|||
if (!this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
this.svgGroup_.style.display = 'none';
|
||||
this.setVisible(false);
|
||||
// Delete all the event listeners.
|
||||
for (var x = 0, listen; listen = this.listeners_[x]; x++) {
|
||||
Blockly.unbindEvent_(listen);
|
||||
|
@ -424,6 +488,7 @@ Blockly.Flyout.prototype.hide = function() {
|
|||
* Variables and procedures have a custom set of blocks.
|
||||
*/
|
||||
Blockly.Flyout.prototype.show = function(xmlList) {
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
this.hide();
|
||||
this.clearOldBlocks_();
|
||||
|
||||
|
@ -437,7 +502,7 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
|||
Blockly.Procedures.flyoutCategory(this.workspace_.targetWorkspace);
|
||||
}
|
||||
|
||||
this.svgGroup_.style.display = 'block';
|
||||
this.setVisible(true);
|
||||
// Create the blocks to be shown in this flyout.
|
||||
var contents = [];
|
||||
var gaps = [];
|
||||
|
@ -473,10 +538,8 @@ Blockly.Flyout.prototype.show = function(xmlList) {
|
|||
} else if (tagName == 'BUTTON' || tagName == 'LABEL') {
|
||||
// Labels behave the same as buttons, but are styled differently.
|
||||
var isLabel = tagName == 'LABEL';
|
||||
var text = xml.getAttribute('text');
|
||||
var callbackKey = xml.getAttribute('callbackKey');
|
||||
var curButton = new Blockly.FlyoutButton(this.workspace_,
|
||||
this.targetWorkspace_, text, callbackKey, isLabel);
|
||||
this.targetWorkspace_, xml, isLabel);
|
||||
contents.push({type: 'button', button: curButton});
|
||||
gaps.push(default_gap);
|
||||
}
|
||||
|
@ -585,7 +648,7 @@ Blockly.Flyout.blockRightClick_ = function(e, block) {
|
|||
Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
|
||||
var flyout = this;
|
||||
return function(e) {
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
Blockly.Flyout.blockRightClick_(e, block);
|
||||
} else {
|
||||
flyout.dragMode_ = Blockly.DRAG_NONE;
|
||||
|
@ -618,7 +681,7 @@ Blockly.Flyout.prototype.blockMouseDown_ = function(block) {
|
|||
*/
|
||||
Blockly.Flyout.prototype.onMouseDown_ = function(e) {
|
||||
this.dragMode_ = Blockly.DRAG_FREE;
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Don't start drags with right clicks.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
return;
|
||||
|
@ -772,7 +835,7 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
|
|||
// Hide drop-downs and animating WidgetDiv immediately
|
||||
Blockly.WidgetDiv.hide(true);
|
||||
Blockly.DropDownDiv.hideWithoutAnimation();
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click. Don't create a block, let the context menu show.
|
||||
return;
|
||||
}
|
||||
|
@ -781,6 +844,10 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
|
|||
return;
|
||||
}
|
||||
Blockly.Events.disable();
|
||||
// Disable workspace resizing. Reenable at the end of the drag. This avoids
|
||||
// a spurious resize between creating the new block and placing it in the
|
||||
// workspace.
|
||||
flyout.targetWorkspace_.setResizesEnabled(false);
|
||||
try {
|
||||
var block = flyout.placeNewBlock_(originBlock);
|
||||
} finally {
|
||||
|
@ -797,8 +864,6 @@ Blockly.Flyout.prototype.createBlockFunc_ = function(originBlock) {
|
|||
block.onMouseDown_(e);
|
||||
Blockly.dragMode_ = Blockly.DRAG_FREE;
|
||||
block.setDragging_(true);
|
||||
// Disable workspace resizing. Reenable at the end of the drag.
|
||||
flyout.targetWorkspace_.setResizesEnabled(false);
|
||||
block.moveToDragSurface_();
|
||||
};
|
||||
};
|
||||
|
|
|
@ -35,14 +35,13 @@ goog.require('goog.math.Coordinate');
|
|||
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to place this
|
||||
* button.
|
||||
* @param {!Blockly.WorkspaceSvg} targetWorkspace The flyout's target workspace.
|
||||
* @param {string} text The text to display on the button.
|
||||
* @param {string} callbackKey The key to use when looking up the callback for a
|
||||
* click on this button.
|
||||
* @param {!Element} xml The XML specifying the label/button.
|
||||
* @param {boolean} isLabel Whether this button should be styled as a label.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.FlyoutButton = function(workspace, targetWorkspace, text, callbackKey,
|
||||
isLabel) {
|
||||
Blockly.FlyoutButton = function(workspace, targetWorkspace, xml, isLabel) {
|
||||
// Labels behave the same as buttons, but are styled differently.
|
||||
|
||||
/**
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
|
@ -59,7 +58,7 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, text, callbackKey,
|
|||
* @type {string}
|
||||
* @private
|
||||
*/
|
||||
this.text_ = text;
|
||||
this.text_ = xml.getAttribute('text');
|
||||
|
||||
/**
|
||||
* @type {!goog.math.Coordinate}
|
||||
|
@ -67,19 +66,36 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, text, callbackKey,
|
|||
*/
|
||||
this.position_ = new goog.math.Coordinate(0, 0);
|
||||
|
||||
/**
|
||||
* Function to call when this button is clicked.
|
||||
* @type {function(!Blockly.FlyoutButton)}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = Blockly.flyoutButtonCallbacks_[callbackKey];
|
||||
|
||||
/**
|
||||
* Whether this button should be styled as a label.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.isLabel_ = isLabel;
|
||||
|
||||
/**
|
||||
* Function to call when this button is clicked.
|
||||
* @type {function(!Blockly.FlyoutButton)}
|
||||
* @private
|
||||
*/
|
||||
this.callback_ = null;
|
||||
|
||||
var callbackKey = xml.getAttribute('callbackKey');
|
||||
if (this.isLabel_ && callbackKey) {
|
||||
console.warn('Labels should not have callbacks. Label text: ' + this.text_);
|
||||
} else if (!this.isLabel_ &&
|
||||
!(callbackKey && targetWorkspace.getButtonCallback(callbackKey))) {
|
||||
console.warn('Buttons should have callbacks. Button text: ' + this.text_);
|
||||
} else {
|
||||
this.callback_ = targetWorkspace.getButtonCallback(callbackKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* If specified, a CSS class to add to this button.
|
||||
* @type {?string}
|
||||
* @private
|
||||
*/
|
||||
this.cssClass_ = xml.getAttribute('web-class') || null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -104,28 +120,32 @@ Blockly.FlyoutButton.prototype.height = 0;
|
|||
* @return {!Element} The button's SVG group.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.createDom = function() {
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
{'class': this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton'},
|
||||
var cssClass = this.isLabel_ ? 'blocklyFlyoutLabel' : 'blocklyFlyoutButton';
|
||||
if (this.cssClass_) {
|
||||
cssClass += ' ' + this.cssClass_;
|
||||
}
|
||||
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g', {'class': cssClass},
|
||||
this.workspace_.getCanvas());
|
||||
|
||||
if (!this.isLabel_) {
|
||||
// Shadow rectangle (light source does not mirror in RTL).
|
||||
var shadow = Blockly.createSvgElement('rect',
|
||||
var shadow = Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyFlyoutButtonShadow',
|
||||
'rx': 4, 'ry': 4, 'x': 1, 'y': 1},
|
||||
this.svgGroup_);
|
||||
}
|
||||
// Background rectangle.
|
||||
var rect = Blockly.createSvgElement('rect',
|
||||
var rect = Blockly.utils.createSvgElement('rect',
|
||||
{'class': this.isLabel_ ?
|
||||
'blocklyFlyoutLabelBackground' : 'blocklyFlyoutButtonBackground',
|
||||
'rx': 4, 'ry': 4},
|
||||
this.svgGroup_);
|
||||
|
||||
var svgText = Blockly.createSvgElement('text',
|
||||
{'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText',
|
||||
'x': 0, 'y': 0,
|
||||
'text-anchor': 'middle'}, this.svgGroup_);
|
||||
var svgText = Blockly.utils.createSvgElement('text',
|
||||
{'class': this.isLabel_ ? 'blocklyFlyoutLabelText' : 'blocklyText',
|
||||
'x': 0, 'y': 0, 'text-anchor': 'middle'},
|
||||
this.svgGroup_);
|
||||
svgText.textContent = this.text_;
|
||||
|
||||
this.width = svgText.getComputedTextLength() +
|
||||
|
|
|
@ -94,7 +94,7 @@ Blockly.HorizontalFlyout.prototype.getMetrics_ = function() {
|
|||
}
|
||||
var viewHeight = this.height_;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_TOP) {
|
||||
viewHeight += this.MARGIN - this.SCROLLBAR_PADDING;
|
||||
viewHeight += this.MARGIN;
|
||||
}
|
||||
var viewWidth = this.width_ - 2 * this.SCROLLBAR_PADDING;
|
||||
|
||||
|
@ -148,15 +148,14 @@ Blockly.HorizontalFlyout.prototype.position = function() {
|
|||
return;
|
||||
}
|
||||
var edgeWidth = this.horizontalLayout_ ?
|
||||
targetWorkspaceMetrics.viewWidth : this.width_;
|
||||
edgeWidth -= this.CORNER_RADIUS;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
edgeWidth *= -1;
|
||||
}
|
||||
targetWorkspaceMetrics.viewWidth - 2 * this.CORNER_RADIUS :
|
||||
this.width_ - this.CORNER_RADIUS;
|
||||
|
||||
this.setBackgroundPath_(edgeWidth,
|
||||
this.horizontalLayout_ ? this.height_ :
|
||||
targetWorkspaceMetrics.viewHeight);
|
||||
var edgeHeight = this.horizontalLayout_ ?
|
||||
this.height_ - this.CORNER_RADIUS :
|
||||
targetWorkspaceMetrics.viewHeight - 2 * this.CORNER_RADIUS;
|
||||
|
||||
this.setBackgroundPath_(edgeWidth, edgeHeight);
|
||||
|
||||
var x = targetWorkspaceMetrics.absoluteLeft;
|
||||
if (this.toolboxPosition_ == Blockly.TOOLBOX_AT_RIGHT) {
|
||||
|
@ -170,8 +169,6 @@ Blockly.HorizontalFlyout.prototype.position = function() {
|
|||
y -= this.height_;
|
||||
}
|
||||
|
||||
this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')');
|
||||
|
||||
// Record the height for Blockly.Flyout.getMetrics_, or width if the layout is
|
||||
// horizontal.
|
||||
if (this.horizontalLayout_) {
|
||||
|
@ -180,8 +177,15 @@ Blockly.HorizontalFlyout.prototype.position = function() {
|
|||
this.height_ = targetWorkspaceMetrics.viewHeight;
|
||||
}
|
||||
|
||||
this.svgGroup_.setAttribute("width", this.width_);
|
||||
this.svgGroup_.setAttribute("height", this.height_);
|
||||
var transform = 'translate(' + x + 'px,' + y + 'px)';
|
||||
this.svgGroup_.style.transform = transform;
|
||||
|
||||
// Update the scrollbar (if one exists).
|
||||
if (this.scrollbar_) {
|
||||
// Set the scrollbars origin to be the top left of the flyout.
|
||||
this.scrollbar_.setOrigin(x, y);
|
||||
this.scrollbar_.resize();
|
||||
}
|
||||
// The blocks need to be visible in order to be laid out and measured correctly, but we don't
|
||||
|
@ -205,13 +209,13 @@ Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width, height)
|
|||
|
||||
if (atTop) {
|
||||
// Top.
|
||||
path.push('h', width + this.CORNER_RADIUS);
|
||||
path.push('h', width + 2 * this.CORNER_RADIUS);
|
||||
// Right.
|
||||
path.push('v', height);
|
||||
// Bottom.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
-this.CORNER_RADIUS, this.CORNER_RADIUS);
|
||||
path.push('h', -1 * (width - this.CORNER_RADIUS));
|
||||
path.push('h', -1 * width);
|
||||
// Left.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
-this.CORNER_RADIUS, -this.CORNER_RADIUS);
|
||||
|
@ -220,13 +224,13 @@ Blockly.HorizontalFlyout.prototype.setBackgroundPath_ = function(width, height)
|
|||
// Top.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
this.CORNER_RADIUS, -this.CORNER_RADIUS);
|
||||
path.push('h', width - this.CORNER_RADIUS);
|
||||
path.push('h', width);
|
||||
// Right.
|
||||
path.push('a', this.CORNER_RADIUS, this.CORNER_RADIUS, 0, 0, 1,
|
||||
this.CORNER_RADIUS, this.CORNER_RADIUS);
|
||||
path.push('v', height - this.CORNER_RADIUS);
|
||||
path.push('v', height);
|
||||
// Bottom.
|
||||
path.push('h', -width - this.CORNER_RADIUS);
|
||||
path.push('h', -width - 2 * this.CORNER_RADIUS);
|
||||
// Left.
|
||||
path.push('z');
|
||||
}
|
||||
|
@ -308,7 +312,7 @@ Blockly.HorizontalFlyout.prototype.layout_ = function(contents, gaps) {
|
|||
|
||||
// Create an invisible rectangle under the block to act as a button. Just
|
||||
// using the block as a button is poor, since blocks have holes in them.
|
||||
var rect = Blockly.createSvgElement('rect', {'fill-opacity': 0}, null);
|
||||
var rect = Blockly.utils.createSvgElement('rect', {'fill-opacity': 0}, null);
|
||||
rect.tooltip = block;
|
||||
Blockly.Tooltip.bindMouseEvents(rect);
|
||||
// Add the rectangles under the blocks, so that the blocks' tooltips work.
|
||||
|
@ -390,7 +394,7 @@ Blockly.HorizontalFlyout.prototype.placeNewBlock_ = function(originBlock) {
|
|||
}
|
||||
// Figure out where the original block is on the screen, relative to the upper
|
||||
// left corner of the main workspace.
|
||||
var xyOld = Blockly.getSvgXY_(svgRootOld, targetWorkspace);
|
||||
var xyOld = Blockly.utils.getInjectionDivXY_(svgRootOld);
|
||||
// Take into account that the flyout might have been scrolled horizontally
|
||||
// (separately from the main workspace).
|
||||
// Generally a no-op in vertical mode but likely to happen in horizontal
|
||||
|
@ -434,7 +438,8 @@ Blockly.HorizontalFlyout.prototype.placeNewBlock_ = function(originBlock) {
|
|||
// upper left corner of the workspace. This may not be the same as the
|
||||
// original block because the flyout's origin may not be the same as the
|
||||
// main workspace's origin.
|
||||
var xyNew = Blockly.getSvgXY_(svgRootNew, targetWorkspace);
|
||||
var xyNew = Blockly.utils.getInjectionDivXY_(svgRootNew);
|
||||
|
||||
// Scale the scroll (getSvgXY_ did not do this).
|
||||
xyNew.x +=
|
||||
targetWorkspace.scrollX / targetWorkspace.scale - targetWorkspace.scrollX;
|
||||
|
|
|
@ -114,10 +114,11 @@ Blockly.VerticalFlyout.prototype.init = function(targetWorkspace) {
|
|||
|
||||
/**
|
||||
* Creates the flyout's DOM. Only needs to be called once.
|
||||
* @param {string} tagName HTML element
|
||||
* @return {!Element} The flyout's SVG group.
|
||||
*/
|
||||
Blockly.VerticalFlyout.prototype.createDom = function() {
|
||||
Blockly.VerticalFlyout.superClass_.createDom.call(this);
|
||||
Blockly.VerticalFlyout.prototype.createDom = function(tagName) {
|
||||
Blockly.VerticalFlyout.superClass_.createDom.call(this, tagName);
|
||||
|
||||
/*
|
||||
<defs>
|
||||
|
@ -128,10 +129,10 @@ Blockly.VerticalFlyout.prototype.createDom = function() {
|
|||
</clipPath>
|
||||
</defs>
|
||||
*/
|
||||
this.defs_ = Blockly.createSvgElement('defs', {}, this.svgGroup_);
|
||||
var clipPath = Blockly.createSvgElement('clipPath',
|
||||
this.defs_ = Blockly.utils.createSvgElement('defs', {}, this.svgGroup_);
|
||||
var clipPath = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id':'blocklyBlockMenuClipPath'}, this.defs_);
|
||||
this.clipRect_ = Blockly.createSvgElement('rect',
|
||||
this.clipRect_ = Blockly.utils.createSvgElement('rect',
|
||||
{'id': 'blocklyBlockMenuClipRect',
|
||||
'height': '0',
|
||||
'width': '0',
|
||||
|
@ -246,14 +247,20 @@ Blockly.VerticalFlyout.prototype.position = function() {
|
|||
var y = 0;
|
||||
}
|
||||
|
||||
this.svgGroup_.setAttribute('transform', 'translate(' + x + ',' + y + ')');
|
||||
|
||||
// Record the height for Blockly.Flyout.getMetrics_
|
||||
this.height_ = targetWorkspaceMetrics.viewHeight - y;
|
||||
|
||||
this.setBackgroundPath_(this.width_, this.height_);
|
||||
|
||||
this.svgGroup_.setAttribute("width", this.width_);
|
||||
this.svgGroup_.setAttribute("height", this.height_);
|
||||
var transform = 'translate(' + x + 'px,' + y + 'px)';
|
||||
this.svgGroup_.style.transform = transform;
|
||||
|
||||
// Update the scrollbar (if one exists).
|
||||
if (this.scrollbar_) {
|
||||
// Set the scrollbars origin to be the top left of the flyout.
|
||||
this.scrollbar_.setOrigin(x, y);
|
||||
this.scrollbar_.resize();
|
||||
}
|
||||
// The blocks need to be visible in order to be laid out and measured
|
||||
|
@ -444,7 +451,7 @@ Blockly.VerticalFlyout.prototype.createRect_ = function(block, x, y,
|
|||
blockHW, index) {
|
||||
// Create an invisible rectangle under the block to act as a button. Just
|
||||
// using the block as a button is poor, since blocks have holes in them.
|
||||
var rect = Blockly.createSvgElement('rect',
|
||||
var rect = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'fill-opacity': 0,
|
||||
'x': x,
|
||||
|
@ -475,7 +482,7 @@ Blockly.VerticalFlyout.prototype.createCheckbox_ = function(block, cursorX,
|
|||
cursorY, blockHW) {
|
||||
var svgRoot = block.getSvgRoot();
|
||||
var extraSpace = this.CHECKBOX_SIZE + this.CHECKBOX_MARGIN;
|
||||
var checkboxRect = Blockly.createSvgElement('rect',
|
||||
var checkboxRect = Blockly.utils.createSvgElement('rect',
|
||||
{
|
||||
'class': 'blocklyFlyoutCheckbox',
|
||||
'height': this.CHECKBOX_SIZE,
|
||||
|
@ -593,7 +600,7 @@ Blockly.VerticalFlyout.prototype.placeNewBlock_ = function(originBlock) {
|
|||
// Figure out where the original block is on the screen, relative to the upper
|
||||
// left corner of the main workspace.
|
||||
// In what coordinates? Pixels?
|
||||
var xyOld = Blockly.getSvgXY_(svgRootOld, targetWorkspace);
|
||||
var xyOld = Blockly.utils.getInjectionDivXY_(svgRootOld);
|
||||
|
||||
// Take into account that the flyout might have been scrolled horizontally
|
||||
// (separately from the main workspace).
|
||||
|
@ -651,7 +658,7 @@ Blockly.VerticalFlyout.prototype.placeNewBlock_ = function(originBlock) {
|
|||
// upper left corner of the workspace. This may not be the same as the
|
||||
// original block because the flyout's origin may not be the same as the
|
||||
// main workspace's origin.
|
||||
var xyNew = Blockly.getSvgXY_(svgRootNew, targetWorkspace);
|
||||
var xyNew = Blockly.utils.getInjectionDivXY_(svgRootNew);
|
||||
|
||||
// Scale the scroll (getSvgXY_ did not do this).
|
||||
xyNew.x +=
|
||||
|
|
|
@ -249,7 +249,7 @@ Blockly.Generator.prototype.valueToCode = function(block, name, outerOrder) {
|
|||
// In all known languages multiple such code blocks are not order
|
||||
// sensitive. In fact in Python ('a' 'b') 'c' would fail.
|
||||
} else {
|
||||
// The operators outside this code are stonger than the operators
|
||||
// The operators outside this code are stronger than the operators
|
||||
// inside this code. To prevent the code from being pulled apart,
|
||||
// wrap the code in parentheses.
|
||||
parensNeeded = true;
|
||||
|
@ -373,3 +373,43 @@ Blockly.Generator.prototype.provideFunction_ = function(desiredName, code) {
|
|||
}
|
||||
return this.functionNames_[desiredName];
|
||||
};
|
||||
|
||||
/**
|
||||
* Hook for code to run before code generation starts.
|
||||
* Subclasses may override this, e.g. to initialise the database of variable
|
||||
* names.
|
||||
* @param {!Blockly.Workspace} workspace Workspace to generate code from.
|
||||
*/
|
||||
Blockly.Generator.prototype.init = undefined;
|
||||
|
||||
/**
|
||||
* Common tasks for generating code from blocks. This is called from
|
||||
* blockToCode and is called on every block, not just top level blocks.
|
||||
* Subclasses may override this, e.g. to generate code for statements following
|
||||
* the block, or to handle comments for the specified block and any connected
|
||||
* value blocks.
|
||||
* @param {!Blockly.Block} block The current block.
|
||||
* @param {string} code The JavaScript code created for this block.
|
||||
* @return {string} JavaScript code with comments and subsequent blocks added.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Generator.prototype.scrub_ = undefined;
|
||||
|
||||
/**
|
||||
* Hook for code to run at end of code generation.
|
||||
* Subclasses may override this, e.g. to prepend the generated code with the
|
||||
* variable definitions.
|
||||
* @param {string} code Generated code.
|
||||
* @return {string} Completed code.
|
||||
*/
|
||||
Blockly.Generator.prototype.finish = undefined;
|
||||
|
||||
/**
|
||||
* Naked values are top-level blocks with outputs that aren't plugged into
|
||||
* anything.
|
||||
* Subclasses may override this, e.g. if their language does not allow
|
||||
* naked values.
|
||||
* @param {string} line Line of generated code.
|
||||
* @return {string} Legal line of code.
|
||||
*/
|
||||
Blockly.Generator.prototype.scrubNakedValue = undefined;
|
||||
|
|
|
@ -76,10 +76,10 @@ Blockly.Icon.prototype.createIcon = function() {
|
|||
...
|
||||
</g>
|
||||
*/
|
||||
this.iconGroup_ = Blockly.createSvgElement('g',
|
||||
this.iconGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyIconGroup'}, null);
|
||||
if (this.block_.isInFlyout) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
this.drawIcon_(this.iconGroup_);
|
||||
|
@ -126,7 +126,7 @@ Blockly.Icon.prototype.iconClick_ = function(e) {
|
|||
// Drag operation is concluding. Don't open the editor.
|
||||
return;
|
||||
}
|
||||
if (!this.block_.isInFlyout && !Blockly.isRightButton(e)) {
|
||||
if (!this.block_.isInFlyout && !Blockly.utils.isRightButton(e)) {
|
||||
this.setVisible(!this.isVisible());
|
||||
}
|
||||
};
|
||||
|
@ -186,7 +186,7 @@ Blockly.Icon.prototype.setIconLocation = function(xy) {
|
|||
Blockly.Icon.prototype.computeIconLocation = function() {
|
||||
// Find coordinates for the centre of the icon and update the arrow.
|
||||
var blockXY = this.block_.getRelativeToSurfaceXY();
|
||||
var iconXY = Blockly.getRelativeXY_(this.iconGroup_);
|
||||
var iconXY = Blockly.utils.getRelativeXY(this.iconGroup_);
|
||||
var newXY = new goog.math.Coordinate(
|
||||
blockXY.x + iconXY.x + this.SIZE / 2,
|
||||
blockXY.y + iconXY.y + this.SIZE / 2);
|
||||
|
|
108
core/inject.js
108
core/inject.js
|
@ -26,12 +26,13 @@
|
|||
|
||||
goog.provide('Blockly.inject');
|
||||
|
||||
goog.require('Blockly.BlockDragSurfaceSvg');
|
||||
goog.require('Blockly.Css');
|
||||
goog.require('Blockly.constants');
|
||||
goog.require('Blockly.Options');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('Blockly.DragSurfaceSvg');
|
||||
goog.require('Blockly.DropDownDiv');
|
||||
goog.require('Blockly.WorkspaceDragSurfaceSvg');
|
||||
goog.require('goog.dom');
|
||||
goog.require('goog.ui.Component');
|
||||
goog.require('goog.userAgent');
|
||||
|
@ -57,10 +58,13 @@ Blockly.inject = function(container, opt_options) {
|
|||
container.appendChild(subContainer);
|
||||
|
||||
var svg = Blockly.createDom_(subContainer, options);
|
||||
var dragSurface = new Blockly.DragSurfaceSvg(subContainer);
|
||||
dragSurface.createDom();
|
||||
var workspace = Blockly.createMainWorkspace_(svg, options, dragSurface);
|
||||
// Create surfaces for dragging things. These are optimizations
|
||||
// so that the broowser does not repaint during the drag.
|
||||
var blockDragSurface = new Blockly.BlockDragSurfaceSvg(subContainer);
|
||||
var workspaceDragSurface = new Blockly.workspaceDragSurfaceSvg(subContainer);
|
||||
|
||||
var workspace = Blockly.createMainWorkspace_(svg, options, blockDragSurface,
|
||||
workspaceDragSurface);
|
||||
Blockly.init_(workspace);
|
||||
workspace.markFocused();
|
||||
Blockly.bindEventWithChecks_(svg, 'focus', workspace, workspace.markFocused);
|
||||
|
@ -87,14 +91,29 @@ Blockly.createDom_ = function(container, options) {
|
|||
Blockly.Css.inject(options.hasCss, options.pathToMedia);
|
||||
|
||||
// Build the SVG DOM.
|
||||
var svg = Blockly.createSvgElement('svg', {
|
||||
/*
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:html="http://www.w3.org/1999/xhtml"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
version="1.1"
|
||||
class="blocklySvg">
|
||||
...
|
||||
</svg>
|
||||
*/
|
||||
var svg = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': 'http://www.w3.org/2000/svg',
|
||||
'xmlns:html': 'http://www.w3.org/1999/xhtml',
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'class': 'blocklySvg'
|
||||
}, container);
|
||||
var defs = Blockly.createSvgElement('defs', {}, svg);
|
||||
/*
|
||||
<defs>
|
||||
... filters go here ...
|
||||
</defs>
|
||||
*/
|
||||
var defs = Blockly.utils.createSvgElement('defs', {}, svg);
|
||||
// Each filter/pattern needs a unique ID for the case of multiple Blockly
|
||||
// instances on a page. Browser behaviour becomes undefined otherwise.
|
||||
// https://neil.fraser.name/news/2015/11/01/
|
||||
|
@ -105,66 +124,80 @@ Blockly.createDom_ = function(container, options) {
|
|||
|
||||
// Using a dilate distorts the block shape.
|
||||
// Instead use a gaussian blur, and then set all alpha to 1 with a transfer.
|
||||
var stackGlowFilter = Blockly.createSvgElement('filter',
|
||||
var stackGlowFilter = Blockly.utils.createSvgElement('filter',
|
||||
{'id': 'blocklyStackGlowFilter',
|
||||
'height': '160%', 'width': '180%', y: '-30%', x: '-40%'}, defs);
|
||||
options.stackGlowBlur = Blockly.createSvgElement('feGaussianBlur',
|
||||
options.stackGlowBlur = Blockly.utils.createSvgElement('feGaussianBlur',
|
||||
{'in': 'SourceGraphic',
|
||||
'stdDeviation': Blockly.STACK_GLOW_RADIUS}, stackGlowFilter);
|
||||
// Set all gaussian blur pixels to 1 opacity before applying flood
|
||||
var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, stackGlowFilter);
|
||||
Blockly.createSvgElement('feFuncA',
|
||||
var componentTransfer = Blockly.utils.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, stackGlowFilter);
|
||||
Blockly.utils.createSvgElement('feFuncA',
|
||||
{'type': 'table', 'tableValues': '0' + goog.string.repeat(' 1', 16)}, componentTransfer);
|
||||
// Color the highlight
|
||||
Blockly.createSvgElement('feFlood',
|
||||
Blockly.utils.createSvgElement('feFlood',
|
||||
{'flood-color': Blockly.Colours.stackGlow,
|
||||
'flood-opacity': Blockly.Colours.stackGlowOpacity, 'result': 'outColor'}, stackGlowFilter);
|
||||
Blockly.createSvgElement('feComposite',
|
||||
Blockly.utils.createSvgElement('feComposite',
|
||||
{'in': 'outColor', 'in2': 'outBlur',
|
||||
'operator': 'in', 'result': 'outGlow'}, stackGlowFilter);
|
||||
Blockly.createSvgElement('feComposite',
|
||||
Blockly.utils.createSvgElement('feComposite',
|
||||
{'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, stackGlowFilter);
|
||||
|
||||
// Filter for replacement marker
|
||||
var replacementGlowFilter = Blockly.createSvgElement('filter',
|
||||
var replacementGlowFilter = Blockly.utils.createSvgElement('filter',
|
||||
{'id': 'blocklyReplacementGlowFilter',
|
||||
'height': '160%', 'width': '180%', y: '-30%', x: '-40%'}, defs);
|
||||
Blockly.createSvgElement('feGaussianBlur',
|
||||
Blockly.utils.createSvgElement('feGaussianBlur',
|
||||
{'in': 'SourceGraphic',
|
||||
'stdDeviation': Blockly.REPLACEMENT_GLOW_RADIUS}, replacementGlowFilter);
|
||||
// Set all gaussian blur pixels to 1 opacity before applying flood
|
||||
var componentTransfer = Blockly.createSvgElement('feComponentTransfer', {'result': 'outBlur'}, replacementGlowFilter);
|
||||
Blockly.createSvgElement('feFuncA',
|
||||
var componentTransfer = Blockly.utils.createSvgElement('feComponentTransfer',
|
||||
{'result': 'outBlur'}, replacementGlowFilter);
|
||||
Blockly.utils.createSvgElement('feFuncA',
|
||||
{'type': 'table', 'tableValues': '0' + goog.string.repeat(' 1', 16)}, componentTransfer);
|
||||
// Color the highlight
|
||||
Blockly.createSvgElement('feFlood',
|
||||
Blockly.utils.createSvgElement('feFlood',
|
||||
{'flood-color': Blockly.Colours.replacementGlow,
|
||||
'flood-opacity': Blockly.Colours.replacementGlowOpacity, 'result': 'outColor'}, replacementGlowFilter);
|
||||
Blockly.createSvgElement('feComposite',
|
||||
Blockly.utils.createSvgElement('feComposite',
|
||||
{'in': 'outColor', 'in2': 'outBlur',
|
||||
'operator': 'in', 'result': 'outGlow'}, replacementGlowFilter);
|
||||
Blockly.createSvgElement('feComposite',
|
||||
Blockly.utils.createSvgElement('feComposite',
|
||||
{'in': 'SourceGraphic', 'in2': 'outGlow', 'operator': 'over'}, replacementGlowFilter);
|
||||
|
||||
var disabledPattern = Blockly.createSvgElement('pattern',
|
||||
/*
|
||||
<pattern id="blocklyDisabledPattern837493" patternUnits="userSpaceOnUse"
|
||||
width="10" height="10">
|
||||
<rect width="10" height="10" fill="#aaa" />
|
||||
<path d="M 0 0 L 10 10 M 10 0 L 0 10" stroke="#cc0" />
|
||||
</pattern>
|
||||
*/
|
||||
var disabledPattern = Blockly.utils.createSvgElement('pattern',
|
||||
{'id': 'blocklyDisabledPattern' + rnd,
|
||||
'patternUnits': 'userSpaceOnUse',
|
||||
'width': 10, 'height': 10}, defs);
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'width': 10, 'height': 10, 'fill': '#aaa'}, disabledPattern);
|
||||
Blockly.createSvgElement('path',
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{'d': 'M 0 0 L 10 10 M 10 0 L 0 10', 'stroke': '#cc0'}, disabledPattern);
|
||||
options.disabledPatternId = disabledPattern.id;
|
||||
|
||||
var gridPattern = Blockly.createSvgElement('pattern',
|
||||
/*
|
||||
<pattern id="blocklyGridPattern837493" patternUnits="userSpaceOnUse">
|
||||
<rect stroke="#888" />
|
||||
<rect stroke="#888" />
|
||||
</pattern>
|
||||
*/
|
||||
var gridPattern = Blockly.utils.createSvgElement('pattern',
|
||||
{'id': 'blocklyGridPattern' + rnd,
|
||||
'patternUnits': 'userSpaceOnUse'}, defs);
|
||||
if (options.gridOptions['length'] > 0 && options.gridOptions['spacing'] > 0) {
|
||||
Blockly.createSvgElement('line',
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'stroke': options.gridOptions['colour']},
|
||||
gridPattern);
|
||||
if (options.gridOptions['length'] > 1) {
|
||||
Blockly.createSvgElement('line',
|
||||
Blockly.utils.createSvgElement('line',
|
||||
{'stroke': options.gridOptions['colour']},
|
||||
gridPattern);
|
||||
}
|
||||
|
@ -178,15 +211,25 @@ Blockly.createDom_ = function(container, options) {
|
|||
* Create a main workspace and add it to the SVG.
|
||||
* @param {!Element} svg SVG element with pattern defined.
|
||||
* @param {!Blockly.Options} options Dictionary of options.
|
||||
* @param {!Blockly.DragSurfaceSvg} dragSurface Drag surface SVG for the workspace.
|
||||
* @param {!Blockly.BlockDragSurfaceSvg} blockDragSurface Drag surface SVG
|
||||
* for the blocks.
|
||||
* @param {!Blockly.WorkspaceDragSurfaceSvg} workspaceDragSurface Drag surface
|
||||
* SVG for the workspace.
|
||||
* @return {!Blockly.Workspace} Newly created main workspace.
|
||||
* @private
|
||||
*/
|
||||
Blockly.createMainWorkspace_ = function(svg, options, dragSurface) {
|
||||
Blockly.createMainWorkspace_ = function(svg, options, blockDragSurface, workspaceDragSurface) {
|
||||
options.parentWorkspace = null;
|
||||
var mainWorkspace = new Blockly.WorkspaceSvg(options, dragSurface);
|
||||
var mainWorkspace = new Blockly.WorkspaceSvg(options, blockDragSurface, workspaceDragSurface);
|
||||
mainWorkspace.scale = options.zoomOptions.startScale;
|
||||
svg.appendChild(mainWorkspace.createDom('blocklyMainBackground'));
|
||||
|
||||
if (!options.hasCategories && options.languageTree) {
|
||||
// Add flyout as an <svg> that is a sibling of the workspace svg.
|
||||
var flyout = mainWorkspace.addFlyout_('svg');
|
||||
Blockly.utils.insertAfter_(flyout, svg);
|
||||
}
|
||||
|
||||
// A null translation will also apply the correct initial scale.
|
||||
mainWorkspace.translate(0, 0);
|
||||
mainWorkspace.markFocused();
|
||||
|
@ -256,10 +299,10 @@ Blockly.init_ = function(mainWorkspace) {
|
|||
var options = mainWorkspace.options;
|
||||
var svg = mainWorkspace.getParentSvg();
|
||||
|
||||
// Supress the browser's context menu.
|
||||
// Suppress the browser's context menu.
|
||||
Blockly.bindEventWithChecks_(svg, 'contextmenu', null,
|
||||
function(e) {
|
||||
if (!Blockly.isTargetInput_(e)) {
|
||||
if (!Blockly.utils.isTargetInput(e)) {
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
|
@ -327,7 +370,7 @@ Blockly.inject.bindDocumentEvents_ = function() {
|
|||
Blockly.bindEvent_(document, 'touchcancel', null,
|
||||
Blockly.longStop_);
|
||||
// Don't use bindEvent_ for document's mouseup since that would create a
|
||||
// corresponding touch handler that would squeltch the ability to interact
|
||||
// corresponding touch handler that would squelch the ability to interact
|
||||
// with non-Blockly elements.
|
||||
document.addEventListener('mouseup', Blockly.onMouseUp_, false);
|
||||
// Some iPad versions don't fire resize after portrait to landscape change.
|
||||
|
@ -377,6 +420,7 @@ Blockly.inject.loadSounds_ = function(pathToMedia, workspace) {
|
|||
/**
|
||||
* Modify the block tree on the existing toolbox.
|
||||
* @param {Node|string} tree DOM tree of blocks, or text representation of same.
|
||||
* @deprecated April 2015
|
||||
*/
|
||||
Blockly.updateToolbox = function(tree) {
|
||||
console.warn('Deprecated call to Blockly.updateToolbox, ' +
|
||||
|
|
|
@ -64,13 +64,13 @@ Blockly.Mutator.prototype.workspaceHeight_ = 0;
|
|||
*/
|
||||
Blockly.Mutator.prototype.drawIcon_ = function(group) {
|
||||
// Square with rounded corners.
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyIconShape',
|
||||
'rx': '4', 'ry': '4',
|
||||
'height': '16', 'width': '16'},
|
||||
group);
|
||||
// Gear teeth.
|
||||
Blockly.createSvgElement('path',
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyIconSymbol',
|
||||
'd': 'm4.203,7.296 0,1.368 -0.92,0.677 -0.11,0.41 0.9,1.559 ' +
|
||||
'0.41,0.11 1.043,-0.457 1.187,0.683 0.127,1.134 0.3,0.3 ' +
|
||||
|
@ -81,7 +81,7 @@ Blockly.Mutator.prototype.drawIcon_ = function(group) {
|
|||
'-0.41,0.11 -0.899,1.559 0.108,0.409z'},
|
||||
group);
|
||||
// Axle hole.
|
||||
Blockly.createSvgElement('circle',
|
||||
Blockly.utils.createSvgElement('circle',
|
||||
{'class': 'blocklyIconShape', 'r': '2.7', 'cx': '8', 'cy': '8'},
|
||||
group);
|
||||
};
|
||||
|
@ -110,7 +110,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
|||
[Workspace]
|
||||
</svg>
|
||||
*/
|
||||
this.svgDialog_ = Blockly.createSvgElement('svg',
|
||||
this.svgDialog_ = Blockly.utils.createSvgElement('svg',
|
||||
{'x': Blockly.Bubble.BORDER_WIDTH, 'y': Blockly.Bubble.BORDER_WIDTH},
|
||||
null);
|
||||
// Convert the list of names into a list of XML objects for the flyout.
|
||||
|
@ -135,8 +135,16 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
|||
};
|
||||
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions, this.block_.workspace.dragSurface);
|
||||
this.workspace_.isMutator = true;
|
||||
this.svgDialog_.appendChild(
|
||||
this.workspace_.createDom('blocklyMutatorBackground'));
|
||||
|
||||
// Mutator flyouts go inside the mutator workspace's <g> rather than in
|
||||
// a top level svg. Instead of handling scale themselves, mutators
|
||||
// inherit scale from the parent workspace.
|
||||
// To fix this, scale needs to be applied at a different level in the dom.
|
||||
var flyoutSvg = this.workspace_.addFlyout_('g');
|
||||
var background = this.workspace_.createDom('blocklyMutatorBackground');
|
||||
background.appendChild(flyoutSvg);
|
||||
this.svgDialog_.appendChild(background);
|
||||
|
||||
return this.svgDialog_;
|
||||
};
|
||||
|
||||
|
@ -147,14 +155,14 @@ Blockly.Mutator.prototype.updateEditable = function() {
|
|||
if (!this.block_.isInFlyout) {
|
||||
if (this.block_.isEditable()) {
|
||||
if (this.iconGroup_) {
|
||||
Blockly.removeClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
Blockly.utils.removeClass(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
} else {
|
||||
// Close any mutator bubble. Icon is not clickable.
|
||||
this.setVisible(false);
|
||||
if (this.iconGroup_) {
|
||||
Blockly.addClass_(/** @type {!Element} */ (this.iconGroup_),
|
||||
Blockly.utils.addClass(/** @type {!Element} */ (this.iconGroup_),
|
||||
'blocklyIconGroupReadonly');
|
||||
}
|
||||
}
|
||||
|
@ -308,7 +316,7 @@ Blockly.Mutator.prototype.workspaceChanged_ = function() {
|
|||
block.compose(this.rootBlock_);
|
||||
// Restore rendering and show the changes.
|
||||
block.rendered = savedRendered;
|
||||
// Mutation may have added some elements that need initalizing.
|
||||
// Mutation may have added some elements that need initializing.
|
||||
block.initSvg();
|
||||
var newMutationDom = block.mutationToDom();
|
||||
var newMutation = newMutationDom && Blockly.Xml.domToText(newMutationDom);
|
||||
|
|
|
@ -161,17 +161,29 @@ Blockly.Procedures.rename = function(name) {
|
|||
Blockly.Procedures.flyoutCategory = function(workspace) {
|
||||
var xmlList = [];
|
||||
if (Blockly.Blocks['procedures_defnoreturn']) {
|
||||
// <block type="procedures_defnoreturn" gap="16"></block>
|
||||
// <block type="procedures_defnoreturn" gap="16">
|
||||
// <field name="NAME">do something</field>
|
||||
// </block>
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'procedures_defnoreturn');
|
||||
block.setAttribute('gap', 16);
|
||||
var nameField = goog.dom.createDom('field', null,
|
||||
Blockly.Msg.PROCEDURES_DEFNORETURN_PROCEDURE);
|
||||
nameField.setAttribute('name', 'NAME');
|
||||
block.appendChild(nameField);
|
||||
xmlList.push(block);
|
||||
}
|
||||
if (Blockly.Blocks['procedures_defreturn']) {
|
||||
// <block type="procedures_defreturn" gap="16"></block>
|
||||
// <block type="procedures_defreturn" gap="16">
|
||||
// <field name="NAME">do something</field>
|
||||
// </block>
|
||||
var block = goog.dom.createDom('block');
|
||||
block.setAttribute('type', 'procedures_defreturn');
|
||||
block.setAttribute('gap', 16);
|
||||
var nameField = goog.dom.createDom('field', null,
|
||||
Blockly.Msg.PROCEDURES_DEFRETURN_PROCEDURE);
|
||||
nameField.setAttribute('name', 'NAME');
|
||||
block.appendChild(nameField);
|
||||
xmlList.push(block);
|
||||
}
|
||||
if (Blockly.Blocks['procedures_report']) {
|
||||
|
|
|
@ -161,7 +161,7 @@ Blockly.RenderedConnection.prototype.tighten_ = function() {
|
|||
if (!svgRoot) {
|
||||
throw 'block is not rendered.';
|
||||
}
|
||||
var xy = Blockly.getRelativeXY_(svgRoot);
|
||||
var xy = Blockly.utils.getRelativeXY(svgRoot);
|
||||
block.getSvgRoot().setAttribute('transform',
|
||||
'translate(' + (xy.x - dx) + ',' + (xy.y - dy) + ')');
|
||||
block.moveConnections_(-dx, -dy);
|
||||
|
@ -190,7 +190,7 @@ Blockly.RenderedConnection.prototype.highlight = function() {
|
|||
var xy = this.sourceBlock_.getRelativeToSurfaceXY();
|
||||
var x = this.x_ - xy.x;
|
||||
var y = this.y_ - xy.y;
|
||||
Blockly.Connection.highlightedPath_ = Blockly.createSvgElement('path',
|
||||
Blockly.Connection.highlightedPath_ = Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyHighlightedConnectionPath',
|
||||
'd': steps,
|
||||
transform: 'translate(' + x + ',' + y + ')' +
|
||||
|
|
|
@ -40,11 +40,11 @@ Blockly.ScrollbarPair = function(workspace) {
|
|||
this.workspace_ = workspace;
|
||||
this.hScroll = new Blockly.Scrollbar(workspace, true, true);
|
||||
this.vScroll = new Blockly.Scrollbar(workspace, false, true);
|
||||
this.corner_ = Blockly.createSvgElement('rect',
|
||||
this.corner_ = Blockly.utils.createSvgElement('rect',
|
||||
{'height': Blockly.Scrollbar.scrollbarThickness,
|
||||
'width': Blockly.Scrollbar.scrollbarThickness,
|
||||
'class': 'blocklyScrollbarBackground'}, null);
|
||||
Blockly.Scrollbar.insertAfter_(this.corner_, workspace.getBubbleCanvas());
|
||||
Blockly.utils.insertAfter_(this.corner_, workspace.getBubbleCanvas());
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -202,6 +202,8 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) {
|
|||
if (horizontal) {
|
||||
this.svgBackground_.setAttribute('height',
|
||||
Blockly.Scrollbar.scrollbarThickness);
|
||||
this.outerSvg_.setAttribute('height',
|
||||
Blockly.Scrollbar.scrollbarThickness);
|
||||
this.svgHandle_.setAttribute('height',
|
||||
Blockly.Scrollbar.scrollbarThickness - 5);
|
||||
this.svgHandle_.setAttribute('y', 2.5);
|
||||
|
@ -211,6 +213,8 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) {
|
|||
} else {
|
||||
this.svgBackground_.setAttribute('width',
|
||||
Blockly.Scrollbar.scrollbarThickness);
|
||||
this.outerSvg_.setAttribute('width',
|
||||
Blockly.Scrollbar.scrollbarThickness);
|
||||
this.svgHandle_.setAttribute('width',
|
||||
Blockly.Scrollbar.scrollbarThickness - 5);
|
||||
this.svgHandle_.setAttribute('x', 2.5);
|
||||
|
@ -224,6 +228,20 @@ Blockly.Scrollbar = function(workspace, horizontal, opt_pair) {
|
|||
this.onMouseDownHandleWrapper_ = Blockly.bindEventWithChecks_(this.svgHandle_,
|
||||
'mousedown', scrollbar, scrollbar.onMouseDownHandle_);
|
||||
};
|
||||
/**
|
||||
* The coordinate of the upper left corner of the scrollbar SVG.
|
||||
* @type {goog.math.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.origin_ = new goog.math.Coordinate(0, 0);
|
||||
|
||||
/**
|
||||
* Whether or not the origin of the scrollbar has changed. Used
|
||||
* to help decide whether or not the reflow/resize calls need to happen.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.originHasChanged_ = true;
|
||||
|
||||
/**
|
||||
* The size of the area within which the scrollbar handle can move.
|
||||
|
@ -253,6 +271,13 @@ Blockly.Scrollbar.prototype.handlePosition_ = 0;
|
|||
*/
|
||||
Blockly.Scrollbar.prototype.isVisible_ = true;
|
||||
|
||||
/**
|
||||
* Whether the workspace containing this scrollbar is visible.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.containerVisible_ = true;
|
||||
|
||||
/**
|
||||
* Width of vertical scrollbar or height of horizontal scrollbar.
|
||||
* Increase the size of scrollbars on touch devices.
|
||||
|
@ -303,7 +328,8 @@ Blockly.Scrollbar.prototype.dispose = function() {
|
|||
Blockly.unbindEvent_(this.onMouseDownHandleWrapper_);
|
||||
this.onMouseDownHandleWrapper_ = null;
|
||||
|
||||
goog.dom.removeNode(this.svgGroup_);
|
||||
goog.dom.removeNode(this.outerSvg_);
|
||||
this.outerSvg_ = null;
|
||||
this.svgGroup_ = null;
|
||||
this.svgBackground_ = null;
|
||||
this.svgHandle_ = null;
|
||||
|
@ -338,9 +364,19 @@ Blockly.Scrollbar.prototype.setHandlePosition = function(newPosition) {
|
|||
*/
|
||||
Blockly.Scrollbar.prototype.setScrollViewSize_ = function(newSize) {
|
||||
this.scrollViewSize_ = newSize;
|
||||
this.outerSvg_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
|
||||
this.svgBackground_.setAttribute(this.lengthAttribute_, this.scrollViewSize_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether this scrollbar's container is visible.
|
||||
* @param {boolean} visible Whether the container is visible.
|
||||
*/
|
||||
Blockly.ScrollbarPair.prototype.setContainerVisible = function(visible) {
|
||||
this.hScroll.setContainerVisible(visible);
|
||||
this.vScroll.setContainerVisible(visible);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the position of the scrollbar's svg group.
|
||||
* @param {number} x The new x coordinate.
|
||||
|
@ -350,8 +386,10 @@ Blockly.Scrollbar.prototype.setPosition = function(x, y) {
|
|||
this.position_.x = x;
|
||||
this.position_.y = y;
|
||||
|
||||
this.svgGroup_.setAttribute('transform',
|
||||
'translate(' + this.position_.x + ',' + this.position_.y + ')');
|
||||
var tempX = this.position_.x + this.origin_.x;
|
||||
var tempY = this.position_.y + this.origin_.y;
|
||||
var transform = 'translate(' + tempX + 'px,' + tempY + 'px)';
|
||||
this.outerSvg_.style.transform = transform;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -371,7 +409,11 @@ Blockly.Scrollbar.prototype.resize = function(opt_metrics) {
|
|||
}
|
||||
}
|
||||
|
||||
if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics,
|
||||
// If the origin has changed (e.g. the toolbox is moving from start to end)
|
||||
// we want to continue with the resize even if workspace metrics haven't.
|
||||
if (this.originHasChanged_) {
|
||||
this.originHasChanged_ = false;
|
||||
} else if (Blockly.Scrollbar.metricsAreEquivalent_(hostMetrics,
|
||||
this.oldHostMetrics_)) {
|
||||
return;
|
||||
}
|
||||
|
@ -539,22 +581,26 @@ Blockly.Scrollbar.prototype.resizeContentVertical = function(hostMetrics) {
|
|||
*/
|
||||
Blockly.Scrollbar.prototype.createDom_ = function() {
|
||||
/* Create the following DOM:
|
||||
<g class="blocklyScrollbarHorizontal">
|
||||
<rect class="blocklyScrollbarBackground" />
|
||||
<rect class="blocklyScrollbarHandle" rx="8" ry="8" />
|
||||
</g>
|
||||
<svg class="blocklyScrollbarHorizontal">
|
||||
<g>
|
||||
<rect class="blocklyScrollbarBackground" />
|
||||
<rect class="blocklyScrollbarHandle" rx="8" ry="8" />
|
||||
</g>
|
||||
</svg>
|
||||
*/
|
||||
var className = 'blocklyScrollbar' +
|
||||
(this.horizontal_ ? 'Horizontal' : 'Vertical');
|
||||
this.svgGroup_ = Blockly.createSvgElement('g', {'class': className}, null);
|
||||
this.svgBackground_ = Blockly.createSvgElement('rect',
|
||||
this.outerSvg_ = Blockly.utils.createSvgElement('svg', {'class': className},
|
||||
null);
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g', {}, this.outerSvg_);
|
||||
this.svgBackground_ = Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyScrollbarBackground'}, this.svgGroup_);
|
||||
var radius = Math.floor((Blockly.Scrollbar.scrollbarThickness - 5) / 2);
|
||||
this.svgHandle_ = Blockly.createSvgElement('rect',
|
||||
this.svgHandle_ = Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyScrollbarHandle', 'rx': radius, 'ry': radius},
|
||||
this.svgGroup_);
|
||||
Blockly.Scrollbar.insertAfter_(this.svgGroup_,
|
||||
this.workspace_.getBubbleCanvas());
|
||||
Blockly.utils.insertAfter_(this.outerSvg_,
|
||||
this.workspace_.getParentSvg());
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -566,29 +612,57 @@ Blockly.Scrollbar.prototype.isVisible = function() {
|
|||
return this.isVisible_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the scrollbar's container is visible and update
|
||||
* display accordingly if visibility has changed.
|
||||
* @param {boolean} visible Whether the container is visible
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.setContainerVisible = function(visible) {
|
||||
var visibilityChanged = (visible != this.containerVisible_);
|
||||
|
||||
this.containerVisible_ = visible;
|
||||
if (visibilityChanged) {
|
||||
this.updateDisplay_();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set whether the scrollbar is visible.
|
||||
* Only applies to non-paired scrollbars.
|
||||
* @param {boolean} visible True if visible.
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.setVisible = function(visible) {
|
||||
if (visible == this.isVisible()) {
|
||||
return;
|
||||
}
|
||||
var visibilityChanged = (visible != this.isVisible());
|
||||
|
||||
// Ideally this would also apply to scrollbar pairs, but that's a bigger
|
||||
// headache (due to interactions with the corner square).
|
||||
if (this.pair_) {
|
||||
throw 'Unable to toggle visibility of paired scrollbars.';
|
||||
}
|
||||
|
||||
this.isVisible_ = visible;
|
||||
if (visibilityChanged) {
|
||||
this.updateDisplay_();
|
||||
}
|
||||
};
|
||||
|
||||
if (visible) {
|
||||
this.svgGroup_.setAttribute('display', 'block');
|
||||
/**
|
||||
* Update visibility of scrollbar based on whether it thinks it should
|
||||
* be visible and whether its containing workspace is visible.
|
||||
* We cannot rely on the containing workspace being hidden to hide us
|
||||
* because it is not necessarily our parent in the dom.
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.updateDisplay_ = function() {
|
||||
var show = true;
|
||||
// Check whether our parent/container is visible.
|
||||
if (!this.containerVisible_) {
|
||||
show = false;
|
||||
} else {
|
||||
// Hide the scrollbar.
|
||||
this.workspace_.setMetrics({x: 0, y: 0});
|
||||
this.svgGroup_.setAttribute('display', 'none');
|
||||
show = this.isVisible();
|
||||
}
|
||||
if (show) {
|
||||
this.outerSvg_.setAttribute('display', 'block');
|
||||
} else {
|
||||
this.outerSvg_.setAttribute('display', 'none');
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -602,17 +676,17 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
|
|||
this.workspace_.markFocused();
|
||||
Blockly.Touch.clearTouchIdentifier(); // This is really a click.
|
||||
this.cleanUp_();
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
// Scrollbars have no context menu.
|
||||
e.stopPropagation();
|
||||
return;
|
||||
}
|
||||
var mouseXY = Blockly.mouseToSvg(e, this.workspace_.getParentSvg(),
|
||||
var mouseXY = Blockly.utils.mouseToSvg(e, this.workspace_.getParentSvg(),
|
||||
this.workspace_.getInverseScreenCTM());
|
||||
var mouseLocation = this.horizontal_ ? mouseXY.x : mouseXY.y;
|
||||
|
||||
var handleXY = Blockly.getSvgXY_(this.svgHandle_, this.workspace_);
|
||||
var handleXY = Blockly.utils.getInjectionDivXY_(this.svgHandle_);
|
||||
var handleStart = this.horizontal_ ? handleXY.x : handleXY.y;
|
||||
var handlePosition = this.handlePosition_;
|
||||
|
||||
|
@ -644,7 +718,7 @@ Blockly.Scrollbar.prototype.onMouseDownBar_ = function(e) {
|
|||
Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
|
||||
this.workspace_.markFocused();
|
||||
this.cleanUp_();
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
// Scrollbars have no context menu.
|
||||
e.stopPropagation();
|
||||
|
@ -652,6 +726,12 @@ Blockly.Scrollbar.prototype.onMouseDownHandle_ = function(e) {
|
|||
}
|
||||
// Look up the current translation and record it.
|
||||
this.startDragHandle = this.handlePosition_;
|
||||
|
||||
// Tell the workspace to setup its drag surface since it is about to move.
|
||||
// onMouseMoveHandle will call onScroll which actually tells the workspace
|
||||
// to move.
|
||||
this.workspace_.setupDragSurface();
|
||||
|
||||
// Record the current mouse position.
|
||||
this.startDragMouse = this.horizontal_ ? e.clientX : e.clientY;
|
||||
Blockly.Scrollbar.onMouseUpWrapper_ = Blockly.bindEventWithChecks_(document,
|
||||
|
@ -686,6 +766,8 @@ Blockly.Scrollbar.prototype.onMouseMoveHandle_ = function(e) {
|
|||
* @private
|
||||
*/
|
||||
Blockly.Scrollbar.prototype.onMouseUpHandle_ = function() {
|
||||
// Tell the workspace to clean up now that the workspace is done moving.
|
||||
this.workspace_.resetDragSurface();
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
this.cleanUp_();
|
||||
};
|
||||
|
@ -751,21 +833,15 @@ Blockly.Scrollbar.prototype.set = function(value) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Insert a node after a reference node.
|
||||
* Contrast with node.insertBefore function.
|
||||
* @param {!Element} newNode New element to insert.
|
||||
* @param {!Element} refNode Existing element to precede new node.
|
||||
* @private
|
||||
* Set the origin of the upper left of the scrollbar. This if for times
|
||||
* when the scrollbar is used in an object whose origin isn't the same
|
||||
* as the main workspace (e.g. in a flyout.)
|
||||
* @param {number} x The x coordinate of the scrollbar's origin.
|
||||
* @param {number} y The y coordinate of the scrollbar's origin.
|
||||
*/
|
||||
Blockly.Scrollbar.insertAfter_ = function(newNode, refNode) {
|
||||
var siblingNode = refNode.nextSibling;
|
||||
var parentNode = refNode.parentNode;
|
||||
if (!parentNode) {
|
||||
throw 'Reference node has no parent.';
|
||||
}
|
||||
if (siblingNode) {
|
||||
parentNode.insertBefore(newNode, siblingNode);
|
||||
} else {
|
||||
parentNode.appendChild(newNode);
|
||||
Blockly.Scrollbar.prototype.setOrigin = function(x, y) {
|
||||
if (x != this.origin_.x || y != this.origin_.y) {
|
||||
this.origin_ = new goog.math.Coordinate(x, y);
|
||||
this.originHasChanged_ = true;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -117,7 +117,7 @@ Blockly.Toolbox.prototype.init = function() {
|
|||
Blockly.bindEventWithChecks_(this.HtmlDiv, 'mousedown', this,
|
||||
function(e) {
|
||||
Blockly.DropDownDiv.hide();
|
||||
if (Blockly.isRightButton(e) || e.target == this.HtmlDiv) {
|
||||
if (Blockly.utils.isRightButton(e) || e.target == this.HtmlDiv) {
|
||||
// Close flyout.
|
||||
Blockly.hideChaff(false);
|
||||
} else {
|
||||
|
@ -168,7 +168,8 @@ Blockly.Toolbox.prototype.createFlyout_ = function() {
|
|||
}
|
||||
this.flyout_.setParentToolbox(this);
|
||||
|
||||
goog.dom.insertSiblingAfter(this.flyout_.createDom(), workspace.svgGroup_);
|
||||
goog.dom.insertSiblingAfter(this.flyout_.createDom('svg'),
|
||||
this.workspace_.getParentSvg());
|
||||
this.flyout_.init(workspace);
|
||||
};
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ Blockly.Tooltip.onMouseOver_ = function(e) {
|
|||
Blockly.Tooltip.poisonedElement_ = null;
|
||||
Blockly.Tooltip.element_ = element;
|
||||
}
|
||||
// Forget about any immediately preceeding mouseOut event.
|
||||
// Forget about any immediately preceding mouseOut event.
|
||||
clearTimeout(Blockly.Tooltip.mouseOutPid_);
|
||||
};
|
||||
|
||||
|
|
|
@ -107,6 +107,9 @@ Blockly.onMouseUp_ = function(/* e */) {
|
|||
return;
|
||||
}
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
|
||||
// TODO(#781): Check whether this needs to be called for all drag modes.
|
||||
workspace.resetDragSurface();
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.OPEN);
|
||||
workspace.dragMode_ = Blockly.DRAG_NONE;
|
||||
// Unbind the touch event if it exists.
|
||||
|
|
|
@ -163,17 +163,17 @@ Blockly.Trashcan.prototype.createDom = function() {
|
|||
clip-path="url(#blocklyTrashLidClipPath837493)"></image>
|
||||
</g>
|
||||
*/
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyTrash'}, null);
|
||||
var rnd = String(Math.random()).substring(2);
|
||||
var clip = Blockly.createSvgElement('clipPath',
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyTrashBodyClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'width': this.WIDTH_, 'height': this.BODY_HEIGHT_,
|
||||
'y': this.LID_HEIGHT_},
|
||||
clip);
|
||||
var body = Blockly.createSvgElement('image',
|
||||
var body = Blockly.utils.createSvgElement('image',
|
||||
{'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_,
|
||||
'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_,
|
||||
'clip-path': 'url(#blocklyTrashBodyClipPath' + rnd + ')'},
|
||||
|
@ -181,12 +181,12 @@ Blockly.Trashcan.prototype.createDom = function() {
|
|||
body.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
|
||||
this.workspace_.options.pathToMedia + Blockly.SPRITE.url);
|
||||
|
||||
var clip = Blockly.createSvgElement('clipPath',
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyTrashLidClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'width': this.WIDTH_, 'height': this.LID_HEIGHT_}, clip);
|
||||
this.svgLid_ = Blockly.createSvgElement('image',
|
||||
this.svgLid_ = Blockly.utils.createSvgElement('image',
|
||||
{'width': Blockly.SPRITE.width, 'x': -this.SPRITE_LEFT_,
|
||||
'height': Blockly.SPRITE.height, 'y': -this.SPRITE_TOP_,
|
||||
'clip-path': 'url(#blocklyTrashLidClipPath' + rnd + ')'},
|
||||
|
|
561
core/utils.js
561
core/utils.js
|
@ -47,16 +47,18 @@ Blockly.cache3dSupported_ = null;
|
|||
* Similar to Closure's goog.dom.classes.add, except it handles SVG elements.
|
||||
* @param {!Element} element DOM element to add class to.
|
||||
* @param {string} className Name of class to add.
|
||||
* @private
|
||||
* @return {boolean} True if class was added, false if already present.
|
||||
*/
|
||||
Blockly.addClass_ = function(element, className) {
|
||||
Blockly.utils.addClass = function(element, className) {
|
||||
var classes = element.getAttribute('class') || '';
|
||||
if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) {
|
||||
if (classes) {
|
||||
classes += ' ';
|
||||
}
|
||||
element.setAttribute('class', classes + className);
|
||||
if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) {
|
||||
return false;
|
||||
}
|
||||
if (classes) {
|
||||
classes += ' ';
|
||||
}
|
||||
element.setAttribute('class', classes + className);
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -64,24 +66,26 @@ Blockly.addClass_ = function(element, className) {
|
|||
* Similar to Closure's goog.dom.classes.remove, except it handles SVG elements.
|
||||
* @param {!Element} element DOM element to remove class from.
|
||||
* @param {string} className Name of class to remove.
|
||||
* @private
|
||||
* @return {boolean} True if class was removed, false if never present.
|
||||
*/
|
||||
Blockly.removeClass_ = function(element, className) {
|
||||
Blockly.utils.removeClass = function(element, className) {
|
||||
var classes = element.getAttribute('class');
|
||||
if ((' ' + classes + ' ').indexOf(' ' + className + ' ') != -1) {
|
||||
var classList = classes.split(/\s+/);
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
if (!classList[i] || classList[i] == className) {
|
||||
classList.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (classList.length) {
|
||||
element.setAttribute('class', classList.join(' '));
|
||||
} else {
|
||||
element.removeAttribute('class');
|
||||
if ((' ' + classes + ' ').indexOf(' ' + className + ' ') == -1) {
|
||||
return false;
|
||||
}
|
||||
var classList = classes.split(/\s+/);
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
if (!classList[i] || classList[i] == className) {
|
||||
classList.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
if (classList.length) {
|
||||
element.setAttribute('class', classList.join(' '));
|
||||
} else {
|
||||
element.removeAttribute('class');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -92,141 +96,16 @@ Blockly.removeClass_ = function(element, className) {
|
|||
* @return {boolean} True if class exists, false otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.hasClass_ = function(element, className) {
|
||||
Blockly.utils.hasClass = function(element, className) {
|
||||
var classes = element.getAttribute('class');
|
||||
return (' ' + classes + ' ').indexOf(' ' + className + ' ') != -1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. When calling the function, verifies that
|
||||
* it belongs to the touch stream that is currently being processsed, and splits
|
||||
* multitouch events into multiple events as needed.
|
||||
* @param {!Node} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @param {boolean} opt_noCaptureIdentifier True if triggering on this event
|
||||
* should not block execution of other event handlers on this touch or other
|
||||
* simultaneous touches.
|
||||
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.bindEventWithChecks_ = function(node, name, thisObject, func,
|
||||
opt_noCaptureIdentifier) {
|
||||
var handled = false;
|
||||
var wrapFunc = function(e) {
|
||||
var captureIdentifier = !opt_noCaptureIdentifier;
|
||||
// Handle each touch point separately. If the event was a mouse event, this
|
||||
// will hand back an array with one element, which we're fine handling.
|
||||
var events = Blockly.Touch.splitEventByTouches(e);
|
||||
for (var i = 0, event; event = events[i]; i++) {
|
||||
if (captureIdentifier && !Blockly.Touch.shouldHandleEvent(event)) {
|
||||
continue;
|
||||
}
|
||||
Blockly.Touch.setClientFromTouch(event);
|
||||
if (thisObject) {
|
||||
func.call(thisObject, event);
|
||||
} else {
|
||||
func(event);
|
||||
}
|
||||
handled = true;
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
wrapFunc(e);
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
for (var i = 0, eventName;
|
||||
eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(eventName, touchWrapFunc, false);
|
||||
bindData.push([node, eventName, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Bind an event to a function call. Handles multitouch events by using the
|
||||
* coordinates of the first changed touch, and doesn't do any safety checks for
|
||||
* simultaneous event processing.
|
||||
* @deprecated in favor of bindEventWithChecks_, but preserved for external
|
||||
* users.
|
||||
* @param {!Node} node Node upon which to listen.
|
||||
* @param {string} name Event name to listen to (e.g. 'mousedown').
|
||||
* @param {Object} thisObject The value of 'this' in the function.
|
||||
* @param {!Function} func Function to call when event is triggered.
|
||||
* @return {!Array.<!Array>} Opaque data that can be passed to unbindEvent_.
|
||||
* @private
|
||||
*/
|
||||
Blockly.bindEvent_ = function(node, name, thisObject, func) {
|
||||
var wrapFunc = function(e) {
|
||||
if (thisObject) {
|
||||
func.call(thisObject, e);
|
||||
} else {
|
||||
func(e);
|
||||
}
|
||||
};
|
||||
|
||||
node.addEventListener(name, wrapFunc, false);
|
||||
var bindData = [[node, name, wrapFunc]];
|
||||
|
||||
// Add equivalent touch event.
|
||||
if (name in Blockly.Touch.TOUCH_MAP) {
|
||||
var touchWrapFunc = function(e) {
|
||||
// Punt on multitouch events.
|
||||
if (e.changedTouches.length == 1) {
|
||||
// Map the touch event's properties to the event.
|
||||
var touchPoint = e.changedTouches[0];
|
||||
e.clientX = touchPoint.clientX;
|
||||
e.clientY = touchPoint.clientY;
|
||||
}
|
||||
wrapFunc(e);
|
||||
|
||||
// Stop the browser from scrolling/zooming the page.
|
||||
e.preventDefault();
|
||||
};
|
||||
for (var i = 0, eventName;
|
||||
eventName = Blockly.Touch.TOUCH_MAP[name][i]; i++) {
|
||||
node.addEventListener(eventName, touchWrapFunc, false);
|
||||
bindData.push([node, eventName, touchWrapFunc]);
|
||||
}
|
||||
}
|
||||
return bindData;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unbind one or more events event from a function call.
|
||||
* @param {!Array.<!Array>} bindData Opaque data from bindEvent_. This list is
|
||||
* emptied during the course of calling this function.
|
||||
* @return {!Function} The function call.
|
||||
* @private
|
||||
*/
|
||||
Blockly.unbindEvent_ = function(bindData) {
|
||||
while (bindData.length) {
|
||||
var bindDatum = bindData.pop();
|
||||
var node = bindDatum[0];
|
||||
var name = bindDatum[1];
|
||||
var func = bindDatum[2];
|
||||
node.removeEventListener(name, func, false);
|
||||
}
|
||||
return func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Don't do anything for this event, just halt propagation.
|
||||
* @param {!Event} e An event.
|
||||
*/
|
||||
Blockly.noEvent = function(e) {
|
||||
Blockly.utils.noEvent = function(e) {
|
||||
// This event has been handled. No need to bubble up to the document.
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
@ -236,9 +115,8 @@ Blockly.noEvent = function(e) {
|
|||
* Is this event targeting a text input widget?
|
||||
* @param {!Event} e An event.
|
||||
* @return {boolean} True if text input.
|
||||
* @private
|
||||
*/
|
||||
Blockly.isTargetInput_ = function(e) {
|
||||
Blockly.utils.isTargetInput = function(e) {
|
||||
return e.target.type == 'textarea' || e.target.type == 'text' ||
|
||||
e.target.type == 'number' || e.target.type == 'email' ||
|
||||
e.target.type == 'password' || e.target.type == 'search' ||
|
||||
|
@ -251,9 +129,8 @@ Blockly.isTargetInput_ = function(e) {
|
|||
* its parent. Only for SVG elements and children (e.g. rect, g, path).
|
||||
* @param {!Element} element SVG element to find the coordinates of.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
* @private
|
||||
*/
|
||||
Blockly.getRelativeXY_ = function(element) {
|
||||
Blockly.utils.getRelativeXY = function(element) {
|
||||
var xy = new goog.math.Coordinate(0, 0);
|
||||
// First, check for x and y attributes.
|
||||
var x = element.getAttribute('x');
|
||||
|
@ -266,21 +143,22 @@ Blockly.getRelativeXY_ = function(element) {
|
|||
}
|
||||
// Second, check for transform="translate(...)" attribute.
|
||||
var transform = element.getAttribute('transform');
|
||||
if (transform) {
|
||||
var transformComponents =
|
||||
transform.match(Blockly.getRelativeXY_.XY_REGEXP_);
|
||||
if (transformComponents) {
|
||||
xy.x += parseFloat(transformComponents[1]);
|
||||
if (transformComponents[3]) {
|
||||
xy.y += parseFloat(transformComponents[3]);
|
||||
}
|
||||
var r = transform && transform.match(Blockly.utils.getRelativeXY.XY_REGEX_);
|
||||
if (r) {
|
||||
xy.x += parseFloat(r[1]);
|
||||
if (r[3]) {
|
||||
xy.y += parseFloat(r[3]);
|
||||
}
|
||||
}
|
||||
|
||||
// Third, check for style="transform: translate3d(...)".
|
||||
// Then check for style = transform: translate(...) or translate3d(...)
|
||||
var style = element.getAttribute('style');
|
||||
if (style && style.indexOf('translate3d') > -1) {
|
||||
var styleComponents = style.match(Blockly.getRelativeXY_.XY_3D_REGEXP_);
|
||||
if (style && style.indexOf('translate') > -1) {
|
||||
var styleComponents = style.match(Blockly.utils.getRelativeXY.XY_2D_REGEX_);
|
||||
// Try transform3d if 2d transform wasn't there.
|
||||
if (!styleComponents) {
|
||||
styleComponents = style.match(Blockly.utils.getRelativeXY.XY_3D_REGEX_);
|
||||
}
|
||||
if (styleComponents) {
|
||||
xy.x += parseFloat(styleComponents[1]);
|
||||
if (styleComponents[3]) {
|
||||
|
@ -288,10 +166,54 @@ Blockly.getRelativeXY_ = function(element) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return xy;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the coordinates of the top-left corner of this element relative to
|
||||
* 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.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
*/
|
||||
Blockly.utils.getInjectionDivXY_ = function(element) {
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var scale = 1;
|
||||
while (element) {
|
||||
var xy = Blockly.utils.getRelativeXY(element);
|
||||
var scale = Blockly.utils.getScale_(element);
|
||||
x = (x * scale) + xy.x;
|
||||
y = (y * scale) + xy.y;
|
||||
var classes = element.getAttribute('class') || '';
|
||||
if ((' ' + classes + ' ').indexOf(' injectionDiv ') != -1) {
|
||||
break;
|
||||
}
|
||||
element = element.parentNode;
|
||||
}
|
||||
return new goog.math.Coordinate(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the scale of this element.
|
||||
* @param {!Element} element The element to find the coordinates of.
|
||||
* @return {!number} number represending the scale applied to the element.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.getScale_ = function(element) {
|
||||
var scale = 1;
|
||||
var transform = element.getAttribute('transform');
|
||||
if (transform) {
|
||||
var transformComponents =
|
||||
transform.match(Blockly.utils.getScale_.REGEXP_);
|
||||
if (transformComponents && transformComponents[0]) {
|
||||
scale = parseFloat(transformComponents[0]);
|
||||
}
|
||||
}
|
||||
return scale;
|
||||
};
|
||||
|
||||
/**
|
||||
* Static regex to pull the x,y values out of an SVG translate() directive.
|
||||
* Note that Firefox and IE (9,10) return 'translate(12)' instead of
|
||||
|
@ -301,90 +223,35 @@ Blockly.getRelativeXY_ = function(element) {
|
|||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
Blockly.getRelativeXY_.XY_REGEXP_ =
|
||||
Blockly.utils.getRelativeXY.XY_REGEX_ =
|
||||
/translate\(\s*([-+\d.e]+)([ ,]\s*([-+\d.e]+)\s*\))?/;
|
||||
|
||||
|
||||
/**
|
||||
* Static regex to pull the scale values out of a transform style property.
|
||||
* Accounts for same exceptions as XY_REGEXP_.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.getScale_REGEXP_ = /scale\(\s*([-+\d.e]+)\s*\)/;
|
||||
|
||||
/**
|
||||
* Static regex to pull the x,y,z values out of a translate3d() style property.
|
||||
* Accounts for same exceptions as XY_REGEXP_.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
Blockly.getRelativeXY_.XY_3D_REGEXP_ =
|
||||
Blockly.utils.getRelativeXY.XY_3D_REGEX_ =
|
||||
/transform:\s*translate3d\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px([ ,]\s*([-+\d.e]+)\s*)px\)?/;
|
||||
|
||||
/**
|
||||
* Return the absolute coordinates of the top-left corner of this element,
|
||||
* scales that after canvas SVG element, if it's a descendant.
|
||||
* The origin (0,0) is the top-left corner of the Blockly SVG.
|
||||
* @param {!Element} element Element to find the coordinates of.
|
||||
* @param {!Blockly.Workspace} workspace Element must be in this workspace.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
* Static regex to pull the x,y,z values out of a translate3d() style property.
|
||||
* Accounts for same exceptions as XY_REGEXP_.
|
||||
* @type {!RegExp}
|
||||
* @private
|
||||
*/
|
||||
Blockly.getSvgXY_ = function(element, workspace) {
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var scale = 1;
|
||||
if (goog.dom.contains(workspace.getCanvas(), element) ||
|
||||
goog.dom.contains(workspace.getBubbleCanvas(), element)) {
|
||||
// Before the SVG canvas, scale the coordinates.
|
||||
scale = workspace.scale;
|
||||
}
|
||||
do {
|
||||
// Loop through this block and every parent.
|
||||
var xy = Blockly.getRelativeXY_(element);
|
||||
if (element == workspace.getCanvas() ||
|
||||
element == workspace.getBubbleCanvas()) {
|
||||
// After the SVG canvas, don't scale the coordinates.
|
||||
scale = 1;
|
||||
}
|
||||
x += xy.x * scale;
|
||||
y += xy.y * scale;
|
||||
element = element.parentNode;
|
||||
} while (element && element != workspace.getParentSvg());
|
||||
return new goog.math.Coordinate(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if 3D transforms are supported by adding an element
|
||||
* and attempting to set the property.
|
||||
* @return {boolean} true if 3D transforms are supported
|
||||
*/
|
||||
Blockly.is3dSupported = function() {
|
||||
if (Blockly.cache3dSupported_ !== null) {
|
||||
return Blockly.cache3dSupported_;
|
||||
}
|
||||
// CC-BY-SA Lorenzo Polidori
|
||||
// https://stackoverflow.com/questions/5661671/detecting-transform-translate3d-support
|
||||
if (!window.getComputedStyle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var el = document.createElement('p'),
|
||||
has3d,
|
||||
transforms = {
|
||||
'webkitTransform': '-webkit-transform',
|
||||
'OTransform': '-o-transform',
|
||||
'msTransform': '-ms-transform',
|
||||
'MozTransform': '-moz-transform',
|
||||
'transform': 'transform'
|
||||
};
|
||||
|
||||
// Add it to the body to get the computed style.
|
||||
document.body.insertBefore(el, null);
|
||||
|
||||
for (var t in transforms) {
|
||||
if (el.style[t] !== undefined) {
|
||||
el.style[t] = 'translate3d(1px,1px,1px)';
|
||||
has3d = window.getComputedStyle(el).getPropertyValue(transforms[t]);
|
||||
}
|
||||
}
|
||||
|
||||
document.body.removeChild(el);
|
||||
Blockly.cache3dSupported_ = (has3d !== undefined && has3d.length > 0 && has3d !== "none");
|
||||
return Blockly.cache3dSupported_;
|
||||
};
|
||||
Blockly.utils.getRelativeXY.XY_2D_REGEX_ =
|
||||
/transform:\s*translate\(\s*([-+\d.e]+)px([ ,]\s*([-+\d.e]+)\s*)px\)?/;
|
||||
|
||||
/**
|
||||
* Helper method for creating SVG elements.
|
||||
|
@ -393,7 +260,7 @@ Blockly.is3dSupported = function() {
|
|||
* @param {Element} parent Optional parent on which to append the element.
|
||||
* @return {!SVGElement} Newly created SVG element.
|
||||
*/
|
||||
Blockly.createSvgElement = function(name, attrs, parent) {
|
||||
Blockly.utils.createSvgElement = function(name, attrs, parent) {
|
||||
var e = /** @type {!SVGElement} */ (
|
||||
document.createElementNS(Blockly.SVG_NS, name));
|
||||
for (var key in attrs) {
|
||||
|
@ -416,7 +283,7 @@ Blockly.createSvgElement = function(name, attrs, parent) {
|
|||
* @param {!Event} e Mouse event.
|
||||
* @return {boolean} True if right-click.
|
||||
*/
|
||||
Blockly.isRightButton = function(e) {
|
||||
Blockly.utils.isRightButton = function(e) {
|
||||
if (e.ctrlKey && goog.userAgent.MAC) {
|
||||
// Control-clicking on Mac OS X is treated as a right-click.
|
||||
// WebKit on Mac OS X fails to change button to 2 (but Gecko does).
|
||||
|
@ -427,13 +294,13 @@ Blockly.isRightButton = function(e) {
|
|||
|
||||
/**
|
||||
* Return the converted coordinates of the given mouse event.
|
||||
* The origin (0,0) is the top-left corner of the Blockly svg.
|
||||
* The origin (0,0) is the top-left corner of the Blockly SVG.
|
||||
* @param {!Event} e Mouse event.
|
||||
* @param {!Element} svg SVG element.
|
||||
* @param {SVGMatrix} matrix Inverted screen CTM to use.
|
||||
* @return {!Object} Object with .x and .y properties.
|
||||
*/
|
||||
Blockly.mouseToSvg = function(e, svg, matrix) {
|
||||
Blockly.utils.mouseToSvg = function(e, svg, matrix) {
|
||||
var svgPoint = svg.createSVGPoint();
|
||||
svgPoint.x = e.clientX;
|
||||
svgPoint.y = e.clientY;
|
||||
|
@ -449,15 +316,13 @@ Blockly.mouseToSvg = function(e, svg, matrix) {
|
|||
* @param {!Array.<string>} array Array of strings.
|
||||
* @return {number} Length of shortest string.
|
||||
*/
|
||||
Blockly.shortestStringLength = function(array) {
|
||||
Blockly.utils.shortestStringLength = function(array) {
|
||||
if (!array.length) {
|
||||
return 0;
|
||||
}
|
||||
var len = array[0].length;
|
||||
for (var i = 1; i < array.length; i++) {
|
||||
len = Math.min(len, array[i].length);
|
||||
}
|
||||
return len;
|
||||
return array.reduce(function(a, b) {
|
||||
return a.length < b.length ? a : b;
|
||||
}).length;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -467,14 +332,14 @@ Blockly.shortestStringLength = function(array) {
|
|||
* @param {number=} opt_shortest Length of shortest string.
|
||||
* @return {number} Length of common prefix.
|
||||
*/
|
||||
Blockly.commonWordPrefix = function(array, opt_shortest) {
|
||||
Blockly.utils.commonWordPrefix = function(array, opt_shortest) {
|
||||
if (!array.length) {
|
||||
return 0;
|
||||
} else if (array.length == 1) {
|
||||
return array[0].length;
|
||||
}
|
||||
var wordPrefix = 0;
|
||||
var max = opt_shortest || Blockly.shortestStringLength(array);
|
||||
var max = opt_shortest || Blockly.utils.shortestStringLength(array);
|
||||
for (var len = 0; len < max; len++) {
|
||||
var letter = array[0][len];
|
||||
for (var i = 1; i < array.length; i++) {
|
||||
|
@ -502,14 +367,14 @@ Blockly.commonWordPrefix = function(array, opt_shortest) {
|
|||
* @param {number=} opt_shortest Length of shortest string.
|
||||
* @return {number} Length of common suffix.
|
||||
*/
|
||||
Blockly.commonWordSuffix = function(array, opt_shortest) {
|
||||
Blockly.utils.commonWordSuffix = function(array, opt_shortest) {
|
||||
if (!array.length) {
|
||||
return 0;
|
||||
} else if (array.length == 1) {
|
||||
return array[0].length;
|
||||
}
|
||||
var wordPrefix = 0;
|
||||
var max = opt_shortest || Blockly.shortestStringLength(array);
|
||||
var max = opt_shortest || Blockly.utils.shortestStringLength(array);
|
||||
for (var len = 0; len < max; len++) {
|
||||
var letter = array[0].substr(-len - 1, 1);
|
||||
for (var i = 1; i < array.length; i++) {
|
||||
|
@ -530,22 +395,45 @@ Blockly.commonWordSuffix = function(array, opt_shortest) {
|
|||
return max;
|
||||
};
|
||||
|
||||
/**
|
||||
* Is the given string a number (includes negative and decimals).
|
||||
* @param {string} str Input string.
|
||||
* @return {boolean} True if number, false otherwise.
|
||||
*/
|
||||
Blockly.isNumber = function(str) {
|
||||
return !!str.match(/^\s*-?\d+(\.\d+)?\s*$/);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse a string with any number of interpolation tokens (%1, %2, ...).
|
||||
* '%' characters may be self-escaped (%%).
|
||||
* @param {string} message Text containing interpolation tokens.
|
||||
* It will also replace string table references (e.g., %{bky_my_msg} and
|
||||
* %{BKY_MY_MSG} will both be replaced with the value in
|
||||
* Blockly.Msg['MY_MSG']). Percentage sign characters '%' may be self-escaped
|
||||
* (e.g., '%%').
|
||||
* @param {string} message Text which might contain string table references and
|
||||
* interpolation tokens.
|
||||
* @return {!Array.<string|number>} Array of strings and numbers.
|
||||
*/
|
||||
Blockly.utils.tokenizeInterpolation = function(message) {
|
||||
return Blockly.utils.tokenizeInterpolation_(message, true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Replaces string table references in a message string. For example,
|
||||
* %{bky_my_msg} and %{BKY_MY_MSG} will both be replaced with the value in
|
||||
* Blockly.Msg['MY_MSG'].
|
||||
* @param {string} message Text which might contain string table references.
|
||||
* @return {!string} String with message references replaced.
|
||||
*/
|
||||
Blockly.utils.replaceMessageReferences = function(message) {
|
||||
var interpolatedResult = Blockly.utils.tokenizeInterpolation_(message, false);
|
||||
// When parseInterpolationTokens == false, interpolatedResult should be at
|
||||
// most length 1.
|
||||
return interpolatedResult.length ? interpolatedResult[0] : "";
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal implemention of the message reference and interpolation token
|
||||
* parsing used by tokenizeInterpolation() and replaceMessageReferences().
|
||||
* @param {string} message Text which might contain string table references and
|
||||
* interpolation tokens.
|
||||
* @param {boolean} parseInterpolationTokens Option to parse numeric
|
||||
* interpolation tokens (%1, %2, ...) when true.
|
||||
* @return {!Array.<string|number>} Array of strings and numbers.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.tokenizeInterpolation_ = function(message, parseInterpolationTokens) {
|
||||
var tokens = [];
|
||||
var chars = message.split('');
|
||||
chars.push(''); // End marker.
|
||||
|
@ -553,6 +441,7 @@ Blockly.utils.tokenizeInterpolation = function(message) {
|
|||
// 0 - Base case.
|
||||
// 1 - % found.
|
||||
// 2 - Digit found.
|
||||
// 3 - Message ref found
|
||||
var state = 0;
|
||||
var buffer = [];
|
||||
var number = null;
|
||||
|
@ -560,6 +449,11 @@ Blockly.utils.tokenizeInterpolation = function(message) {
|
|||
var c = chars[i];
|
||||
if (state == 0) {
|
||||
if (c == '%') {
|
||||
var text = buffer.join('');
|
||||
if (text) {
|
||||
tokens.push(text);
|
||||
}
|
||||
buffer.length = 0;
|
||||
state = 1; // Start escape.
|
||||
} else {
|
||||
buffer.push(c); // Regular char.
|
||||
|
@ -568,7 +462,7 @@ Blockly.utils.tokenizeInterpolation = function(message) {
|
|||
if (c == '%') {
|
||||
buffer.push(c); // Escaped %: %%
|
||||
state = 0;
|
||||
} else if ('0' <= c && c <= '9') {
|
||||
} else if (parseInterpolationTokens && '0' <= c && c <= '9') {
|
||||
state = 2;
|
||||
number = c;
|
||||
var text = buffer.join('');
|
||||
|
@ -576,8 +470,10 @@ Blockly.utils.tokenizeInterpolation = function(message) {
|
|||
tokens.push(text);
|
||||
}
|
||||
buffer.length = 0;
|
||||
} else if (c == '{') {
|
||||
state = 3;
|
||||
} else {
|
||||
buffer.push('%', c); // Not an escape: %a
|
||||
buffer.push('%', c); // Not recognized. Return as literal.
|
||||
state = 0;
|
||||
}
|
||||
} else if (state == 2) {
|
||||
|
@ -588,13 +484,70 @@ Blockly.utils.tokenizeInterpolation = function(message) {
|
|||
i--; // Parse this char again.
|
||||
state = 0;
|
||||
}
|
||||
} else if (state == 3) { // String table reference
|
||||
if (c == '') {
|
||||
// Premature end before closing '}'
|
||||
buffer.splice(0, 0, '%{'); // Re-insert leading delimiter
|
||||
i--; // Parse this char again.
|
||||
state = 0; // and parse as string literal.
|
||||
} else if (c != '}') {
|
||||
buffer.push(c);
|
||||
} else {
|
||||
var rawKey = buffer.join('');
|
||||
if (/[a-zA-Z][a-zA-Z0-9_]*/.test(rawKey)) { // Strict matching
|
||||
// Found a valid string key. Attempt case insensitive match.
|
||||
var keyUpper = rawKey.toUpperCase();
|
||||
|
||||
// BKY_ is the prefix used to namespace the strings used in Blockly
|
||||
// core files and the predefined blocks in ../blocks/. These strings
|
||||
// are defined in ../msgs/ files.
|
||||
var bklyKey = goog.string.startsWith(keyUpper, 'BKY_') ?
|
||||
keyUpper.substring(4) : null;
|
||||
if (bklyKey && bklyKey in Blockly.Msg) {
|
||||
var rawValue = Blockly.Msg[bklyKey];
|
||||
var subTokens = Blockly.utils.tokenizeInterpolation(rawValue);
|
||||
tokens = tokens.concat(subTokens);
|
||||
} else {
|
||||
// No entry found in the string table. Pass reference as string.
|
||||
tokens.push('%{' + rawKey + '}');
|
||||
}
|
||||
buffer.length = 0; // Clear the array
|
||||
state = 0;
|
||||
} else {
|
||||
tokens.push('%{' + rawKey + '}');
|
||||
buffer.length = 0;
|
||||
state = 0; // and parse as string literal.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var text = buffer.join('');
|
||||
if (text) {
|
||||
tokens.push(text);
|
||||
}
|
||||
return tokens;
|
||||
|
||||
// Merge adjacent text tokens into a single string.
|
||||
var mergedTokens = [];
|
||||
buffer.length = 0;
|
||||
for (var i = 0; i < tokens.length; ++i) {
|
||||
if (typeof tokens[i] == 'string') {
|
||||
buffer.push(tokens[i]);
|
||||
} else {
|
||||
text = buffer.join('');
|
||||
if (text) {
|
||||
mergedTokens.push(text);
|
||||
}
|
||||
buffer.length = 0;
|
||||
mergedTokens.push(tokens[i]);
|
||||
}
|
||||
}
|
||||
text = buffer.join('');
|
||||
if (text) {
|
||||
mergedTokens.push(text);
|
||||
}
|
||||
buffer.length = 0;
|
||||
|
||||
return mergedTokens;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -602,12 +555,12 @@ Blockly.utils.tokenizeInterpolation = function(message) {
|
|||
* 87 characters ^ 20 length > 128 bits (better than a UUID).
|
||||
* @return {string} A globally unique ID string.
|
||||
*/
|
||||
Blockly.genUid = function() {
|
||||
Blockly.utils.genUid = function() {
|
||||
var length = 20;
|
||||
var soupLength = Blockly.genUid.soup_.length;
|
||||
var soupLength = Blockly.utils.genUid.soup_.length;
|
||||
var id = [];
|
||||
for (var i = 0; i < length; i++) {
|
||||
id[i] = Blockly.genUid.soup_.charAt(Math.random() * soupLength);
|
||||
id[i] = Blockly.utils.genUid.soup_.charAt(Math.random() * soupLength);
|
||||
}
|
||||
return id.join('');
|
||||
};
|
||||
|
@ -619,7 +572,7 @@ Blockly.genUid = function() {
|
|||
* to properly escape in your own environment. Issues #251, #625, #682.
|
||||
* @private
|
||||
*/
|
||||
Blockly.genUid.soup_ = '!#$%()*+,-./:;=?@[]^_`{|}~' +
|
||||
Blockly.utils.genUid.soup_ = '!#$%()*+,-./:;=?@[]^_`{|}~' +
|
||||
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
|
||||
|
||||
/**
|
||||
|
@ -631,7 +584,7 @@ Blockly.genUid.soup_ = '!#$%()*+,-./:;=?@[]^_`{|}~' +
|
|||
Blockly.utils.wrap = function(text, limit) {
|
||||
var lines = text.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
lines[i] = Blockly.utils.wrap_line_(lines[i], limit);
|
||||
lines[i] = Blockly.utils.wrapLine_(lines[i], limit);
|
||||
}
|
||||
return lines.join('\n');
|
||||
};
|
||||
|
@ -643,7 +596,7 @@ Blockly.utils.wrap = function(text, limit) {
|
|||
* @return {string} Wrapped text.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.wrap_line_ = function(text, limit) {
|
||||
Blockly.utils.wrapLine_ = function(text, limit) {
|
||||
if (text.length <= limit) {
|
||||
// Short text, no need to wrap.
|
||||
return text;
|
||||
|
@ -817,3 +770,73 @@ Blockly.encodeEntities = function(rawStr) {
|
|||
return '&#' + i.charCodeAt(0) + ';';
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Check if 3D transforms are supported by adding an element
|
||||
* and attempting to set the property.
|
||||
* @return {boolean} true if 3D transforms are supported.
|
||||
*/
|
||||
Blockly.utils.is3dSupported = function() {
|
||||
if (Blockly.utils.is3dSupported.cached_ !== undefined) {
|
||||
return Blockly.utils.is3dSupported.cached_;
|
||||
}
|
||||
// CC-BY-SA Lorenzo Polidori
|
||||
// stackoverflow.com/questions/5661671/detecting-transform-translate3d-support
|
||||
if (!goog.global.getComputedStyle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var el = document.createElement('p');
|
||||
var has3d = 'none';
|
||||
var transforms = {
|
||||
'webkitTransform': '-webkit-transform',
|
||||
'OTransform': '-o-transform',
|
||||
'msTransform': '-ms-transform',
|
||||
'MozTransform': '-moz-transform',
|
||||
'transform': 'transform'
|
||||
};
|
||||
|
||||
// Add it to the body to get the computed style.
|
||||
document.body.insertBefore(el, null);
|
||||
|
||||
for (var t in transforms) {
|
||||
if (el.style[t] !== undefined) {
|
||||
el.style[t] = 'translate3d(1px,1px,1px)';
|
||||
var computedStyle = goog.global.getComputedStyle(el);
|
||||
if (!computedStyle) {
|
||||
// getComputedStyle in Firefox returns null when blockly is loaded
|
||||
// inside an iframe with display: none. Returning false and not
|
||||
// caching is3dSupported means we try again later. This is most likely
|
||||
// when users are interacting with blocks which should mean blockly is
|
||||
// visible again.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=548397
|
||||
document.body.removeChild(el);
|
||||
return false;
|
||||
}
|
||||
has3d = computedStyle.getPropertyValue(transforms[t]);
|
||||
}
|
||||
}
|
||||
document.body.removeChild(el);
|
||||
Blockly.utils.is3dSupported.cached_ = has3d !== 'none';
|
||||
return Blockly.utils.is3dSupported.cached_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Insert a node after a reference node.
|
||||
* Contrast with node.insertBefore function.
|
||||
* @param {!Element} newNode New element to insert.
|
||||
* @param {!Element} refNode Existing element to precede new node.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.insertAfter_ = function(newNode, refNode) {
|
||||
var siblingNode = refNode.nextSibling;
|
||||
var parentNode = refNode.parentNode;
|
||||
if (!parentNode) {
|
||||
throw 'Reference node has no parent.';
|
||||
}
|
||||
if (siblingNode) {
|
||||
parentNode.insertBefore(newNode, siblingNode);
|
||||
} else {
|
||||
parentNode.appendChild(newNode);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -106,7 +106,7 @@ Blockly.Variables.flyoutCategory = function(workspace) {
|
|||
button.setAttribute('text', Blockly.Msg.NEW_VARIABLE);
|
||||
button.setAttribute('callbackKey', 'CREATE_VARIABLE');
|
||||
|
||||
Blockly.registerButtonCallback('CREATE_VARIABLE', function(button) {
|
||||
workspace.registerButtonCallback('CREATE_VARIABLE', function(button) {
|
||||
Blockly.Variables.createVariable(button.getTargetWorkspace());
|
||||
});
|
||||
|
||||
|
|
|
@ -56,19 +56,19 @@ Blockly.Warning.prototype.collapseHidden = false;
|
|||
*/
|
||||
Blockly.Warning.prototype.drawIcon_ = function(group) {
|
||||
// Triangle with rounded corners.
|
||||
Blockly.createSvgElement('path',
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyIconShape',
|
||||
'd': 'M2,15Q-1,15 0.5,12L6.5,1.7Q8,-1 9.5,1.7L15.5,12Q17,15 14,15z'},
|
||||
group);
|
||||
// Can't use a real '!' text character since different browsers and operating
|
||||
// systems render it differently.
|
||||
// Body of exclamation point.
|
||||
Blockly.createSvgElement('path',
|
||||
Blockly.utils.createSvgElement('path',
|
||||
{'class': 'blocklyIconSymbol',
|
||||
'd': 'm7,4.8v3.16l0.27,2.27h1.46l0.27,-2.27v-3.16z'},
|
||||
group);
|
||||
// Dot of exclamation point.
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'class': 'blocklyIconSymbol',
|
||||
'x': '7', 'y': '11', 'height': '2', 'width': '2'},
|
||||
group);
|
||||
|
@ -82,13 +82,13 @@ Blockly.Warning.prototype.drawIcon_ = function(group) {
|
|||
*/
|
||||
Blockly.Warning.textToDom_ = function(text) {
|
||||
var paragraph = /** @type {!SVGTextElement} */ (
|
||||
Blockly.createSvgElement('text',
|
||||
Blockly.utils.createSvgElement('text',
|
||||
{'class': 'blocklyText blocklyBubbleText',
|
||||
'y': Blockly.Bubble.BORDER_WIDTH},
|
||||
null));
|
||||
var lines = text.split('\n');
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
var tspanElement = Blockly.createSvgElement('tspan',
|
||||
var tspanElement = Blockly.utils.createSvgElement('tspan',
|
||||
{'dy': '1em', 'x': Blockly.Bubble.BORDER_WIDTH}, paragraph);
|
||||
var textNode = document.createTextNode(lines[i]);
|
||||
tspanElement.appendChild(textNode);
|
||||
|
|
|
@ -38,7 +38,7 @@ goog.require('goog.math');
|
|||
*/
|
||||
Blockly.Workspace = function(opt_options) {
|
||||
/** @type {string} */
|
||||
this.id = Blockly.genUid();
|
||||
this.id = Blockly.utils.genUid();
|
||||
Blockly.Workspace.WorkspaceDB_[this.id] = this;
|
||||
/** @type {!Blockly.Options} */
|
||||
this.options = opt_options || {};
|
||||
|
@ -313,45 +313,46 @@ Blockly.Workspace.prototype.getVariableUses = function(name) {
|
|||
* @param {string} name Name of variable to delete.
|
||||
*/
|
||||
Blockly.Workspace.prototype.deleteVariable = function(name) {
|
||||
var variableIndex = this.variableIndexOf(name);
|
||||
if (variableIndex == -1) {
|
||||
return;
|
||||
}
|
||||
// Check whether this variable is a function parameter before deleting.
|
||||
var uses = this.getVariableUses(name);
|
||||
for (var i = 0, block; block = uses[i]; i++) {
|
||||
if (block.type == 'procedures_defnoreturn' ||
|
||||
block.type == 'procedures_defreturn') {
|
||||
var procedureName = block.getFieldValue('NAME');
|
||||
Blockly.alert(
|
||||
Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.
|
||||
replace('%1', name).
|
||||
replace('%2', procedureName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
var workspace = this;
|
||||
function doDeletion(variableUses, index) {
|
||||
function doDeletion() {
|
||||
Blockly.Events.setGroup(true);
|
||||
for (var i = 0; i < variableUses.length; i++) {
|
||||
variableUses[i].dispose(true, false);
|
||||
for (var i = 0; i < uses.length; i++) {
|
||||
uses[i].dispose(true, false);
|
||||
}
|
||||
Blockly.Events.setGroup(false);
|
||||
workspace.variableList.splice(index, 1);
|
||||
workspace.variableList.splice(variableIndex, 1);
|
||||
}
|
||||
var variableIndex = this.variableIndexOf(name);
|
||||
if (variableIndex != -1) {
|
||||
// Check whether this variable is a function parameter before deleting.
|
||||
var uses = this.getVariableUses(name);
|
||||
for (var i = 0, block; block = uses[i]; i++) {
|
||||
if (block.type == 'procedures_defnoreturn' ||
|
||||
block.type == 'procedures_defreturn') {
|
||||
var procedureName = block.getFieldValue('NAME');
|
||||
Blockly.alert(
|
||||
Blockly.Msg.CANNOT_DELETE_VARIABLE_PROCEDURE.
|
||||
replace('%1', name).
|
||||
replace('%2', procedureName));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (uses.length > 1) {
|
||||
// Confirm before deleting multiple blocks.
|
||||
Blockly.confirm(
|
||||
Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length).
|
||||
replace('%2', name),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
doDeletion(uses, variableIndex);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No confirmation necessary for a single block.
|
||||
doDeletion();
|
||||
}
|
||||
if (uses.length > 1) {
|
||||
// Confirm before deleting multiple blocks.
|
||||
Blockly.confirm(
|
||||
Blockly.Msg.DELETE_VARIABLE_CONFIRMATION.replace('%1', uses.length).
|
||||
replace('%2', name),
|
||||
function(ok) {
|
||||
if (ok) {
|
||||
doDeletion();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// No confirmation necessary for a single block.
|
||||
doDeletion();
|
||||
}
|
||||
};
|
||||
|
||||
|
|
191
core/workspace_drag_surface_svg.js
Normal file
191
core/workspace_drag_surface_svg.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2016 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 An SVG that floats on top of the workspace.
|
||||
* Blocks are moved into this SVG during a drag, improving performance.
|
||||
* The entire SVG is translated using css translation instead of SVG so the
|
||||
* blocks are never repainted during drag improving performance.
|
||||
* @author katelyn@google.com (Katelyn Mann)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.WorkspaceDragSurfaceSvg');
|
||||
|
||||
goog.require('Blockly.utils');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.math.Coordinate');
|
||||
|
||||
|
||||
/**
|
||||
* Blocks are moved into this SVG during a drag, improving performance.
|
||||
* The entire SVG is translated using css transforms instead of SVG so the
|
||||
* blocks are never repainted during drag improving performance.
|
||||
* @param {!Element} container Containing element.
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg = function(container) {
|
||||
this.container_ = container;
|
||||
this.createDom();
|
||||
};
|
||||
|
||||
/**
|
||||
* The SVG drag surface. Set once by Blockly.workspaceDragSurfaceSvg.createDom.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.SVG_ = null;
|
||||
|
||||
/**
|
||||
* SVG group inside the drag surface that holds blocks while a drag is in
|
||||
* progress. Blocks are moved here by the workspace at start of a drag and moved
|
||||
* back into the main SVG at the end of a drag.
|
||||
*
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.dragGroup_ = null;
|
||||
|
||||
/**
|
||||
* Containing HTML element; parent of the workspace and the drag surface.
|
||||
* @type {Element}
|
||||
* @private
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.container_ = null;
|
||||
|
||||
/**
|
||||
* Create the drag surface and inject it into the container.
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.createDom = function() {
|
||||
if (this.SVG_) {
|
||||
return; // Already created.
|
||||
}
|
||||
|
||||
/**
|
||||
* Dom structure when the workspace is being dragged. If there is no drag in
|
||||
* progress, the SVG is empty and display: none.
|
||||
* <svg class="blocklyWsDragSurface" style=transform:translate3d(...)>
|
||||
* <g class="blocklyBlockCanvas"></g>
|
||||
* <g class="blocklyBubbleCanvas">/g>
|
||||
* </svg>
|
||||
*/
|
||||
this.SVG_ = Blockly.utils.createSvgElement('svg', {
|
||||
'xmlns': Blockly.SVG_NS,
|
||||
'xmlns:html': Blockly.HTML_NS,
|
||||
'xmlns:xlink': 'http://www.w3.org/1999/xlink',
|
||||
'version': '1.1',
|
||||
'class': 'blocklyWsDragSurface'
|
||||
}, null);
|
||||
this.container_.appendChild(this.SVG_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Translate the entire drag surface during a drag.
|
||||
* We translate the drag surface instead of the blocks inside the surface
|
||||
* so that the browser avoids repainting the SVG.
|
||||
* Because of this, the drag coordinates must be adjusted by scale.
|
||||
* @param {number} x X translation for the entire surface
|
||||
* @param {number} y Y translation for the entire surface
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.translateSurface = function(x, y) {
|
||||
// This is a work-around to prevent a the blocks from rendering
|
||||
// fuzzy while they are being moved on the drag surface.
|
||||
x = x.toFixed(0);
|
||||
y = y.toFixed(0);
|
||||
|
||||
var transform =
|
||||
'transform: translate3d(' + x + 'px, ' + y + 'px, 0px); display: block;';
|
||||
this.SVG_.setAttribute('style', transform);
|
||||
};
|
||||
|
||||
/**
|
||||
* Reports the surface translation in scaled workspace coordinates.
|
||||
* Use this when finishing a drag to return blocks to the correct position.
|
||||
* @return {!goog.math.Coordinate} Current translation of the surface
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.getSurfaceTranslation = function() {
|
||||
return Blockly.utils.getRelativeXY(this.SVG_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the blockCanvas and bubbleCanvas out of the surface SVG and on to
|
||||
* newSurface.
|
||||
* @param {!SVGElement} newSurface The element to put the drag surface contents
|
||||
* into.
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.clearAndHide = function(newSurface) {
|
||||
var blockCanvas = this.SVG_.childNodes[0];
|
||||
var bubbleCanvas = this.SVG_.childNodes[1];
|
||||
if (!blockCanvas || !bubbleCanvas ||
|
||||
!Blockly.utils.hasClass(blockCanvas, 'blocklyBlockCanvas') ||
|
||||
!Blockly.utils.hasClass(bubbleCanvas, 'blocklyBubbleCanvas')) {
|
||||
throw 'Couldn\'t clear and hide the drag surface. A node was missing.';
|
||||
}
|
||||
|
||||
// If there is a previous sibling, put the blockCanvas back right afterwards,
|
||||
// otherwise insert it as the first child node in newSurface.
|
||||
if (this.previousSibling_ != null) {
|
||||
Blockly.utils.insertAfter_(blockCanvas, this.previousSibling_);
|
||||
} else {
|
||||
newSurface.insertBefore(blockCanvas, newSurface.firstChild);
|
||||
}
|
||||
|
||||
// Reattach the bubble canvas after the blockCanvas.
|
||||
Blockly.utils.insertAfter_(bubbleCanvas, blockCanvas);
|
||||
// Hide the drag surface.
|
||||
this.SVG_.style.display = 'none';
|
||||
goog.asserts.assert(this.SVG_.childNodes.length == 0,
|
||||
'Drag surface was not cleared.');
|
||||
this.SVG_.style.transform = '';
|
||||
this.previousSibling_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the SVG to have the block canvas and bubble canvas in it and then
|
||||
* show the surface.
|
||||
* @param {!Element} blockCanvas The block canvas <g> element from the workspace.
|
||||
* @param {!Element} bubbleCanvas The <g> element that contains the bubbles.
|
||||
* @param {?Element} previousSibling The element to insert the block canvas &
|
||||
bubble canvas after when it goes back in the dom at the end of a drag.
|
||||
* @param {number} width The width of the workspace svg element.
|
||||
* @param {number} height The height of the workspace svg element.
|
||||
* @param {number} scale The scale of the workspace being dragged.
|
||||
* @package
|
||||
*/
|
||||
Blockly.workspaceDragSurfaceSvg.prototype.setContentsAndShow = function(
|
||||
blockCanvas, bubbleCanvas, previousSibling, width, height, scale) {
|
||||
goog.asserts.assert(this.SVG_.childNodes.length == 0,
|
||||
'Already dragging a block.');
|
||||
this.previousSibling_ = previousSibling;
|
||||
// Make sure the blocks and bubble canvas are scaled appropriately.
|
||||
blockCanvas.setAttribute('transform', 'translate(0, 0) scale(' + scale + ')');
|
||||
bubbleCanvas.setAttribute('transform',
|
||||
'translate(0, 0) scale(' + scale + ')');
|
||||
this.SVG_.setAttribute('width', width);
|
||||
this.SVG_.setAttribute('height', height);
|
||||
this.SVG_.appendChild(blockCanvas);
|
||||
this.SVG_.appendChild(bubbleCanvas);
|
||||
this.SVG_.style.display = 'block';
|
||||
};
|
|
@ -40,6 +40,7 @@ goog.require('Blockly.Touch');
|
|||
goog.require('Blockly.Trashcan');
|
||||
//goog.require('Blockly.VerticalFlyout');
|
||||
goog.require('Blockly.Workspace');
|
||||
goog.require('Blockly.WorkspaceDragSurfaceSvg');
|
||||
goog.require('Blockly.Xml');
|
||||
goog.require('Blockly.ZoomControls');
|
||||
|
||||
|
@ -53,11 +54,14 @@ goog.require('goog.userAgent');
|
|||
* Class for a workspace. This is an onscreen area with optional trashcan,
|
||||
* scrollbars, bubbles, and dragging.
|
||||
* @param {!Blockly.Options} options Dictionary of options.
|
||||
* @param {Blockly.DragSurfaceSvg=} opt_dragSurface Drag surface for the workspace.
|
||||
* @param {Blockly.BlockDragSurfaceSvg=} opt_blockDragSurface Drag surface for
|
||||
* blocks.
|
||||
* @param {Blockly.workspaceDragSurfaceSvg=} opt_wsDragSurface Drag surface for
|
||||
* the workspace.
|
||||
* @extends {Blockly.Workspace}
|
||||
* @constructor
|
||||
*/
|
||||
Blockly.WorkspaceSvg = function(options, opt_dragSurface) {
|
||||
Blockly.WorkspaceSvg = function(options, opt_blockDragSurface, opt_wsDragSurface) {
|
||||
Blockly.WorkspaceSvg.superClass_.constructor.call(this, options);
|
||||
this.getMetrics =
|
||||
options.getMetrics || Blockly.WorkspaceSvg.getTopLevelWorkspaceMetrics_;
|
||||
|
@ -65,12 +69,17 @@ Blockly.WorkspaceSvg = function(options, opt_dragSurface) {
|
|||
options.setMetrics || Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_;
|
||||
|
||||
Blockly.ConnectionDB.init(this);
|
||||
|
||||
if (opt_dragSurface) {
|
||||
this.dragSurface = opt_dragSurface;
|
||||
if (opt_blockDragSurface) {
|
||||
this.blockDragSurface_ = opt_blockDragSurface;
|
||||
}
|
||||
|
||||
Blockly.ConnectionDB.init(this);
|
||||
if (opt_wsDragSurface) {
|
||||
this.workspaceDragSurface_ = opt_wsDragSurface;
|
||||
}
|
||||
|
||||
this.useWorkspaceDragSurface_ =
|
||||
this.workspaceDragSurface_ && Blockly.utils.is3dSupported();
|
||||
|
||||
/**
|
||||
* Database of pre-loaded sounds.
|
||||
* @private
|
||||
|
@ -78,7 +87,8 @@ Blockly.WorkspaceSvg = function(options, opt_dragSurface) {
|
|||
*/
|
||||
this.SOUNDS_ = Object.create(null);
|
||||
/**
|
||||
* List of currently highlighted blocks.
|
||||
* List of currently highlighted blocks. Block highlighting is often used to
|
||||
* visually mark blocks currently being executed.
|
||||
* @type !Array.<!Blockly.BlockSvg>
|
||||
* @private
|
||||
*/
|
||||
|
@ -87,14 +97,16 @@ Blockly.WorkspaceSvg = function(options, opt_dragSurface) {
|
|||
goog.inherits(Blockly.WorkspaceSvg, Blockly.Workspace);
|
||||
|
||||
/**
|
||||
* A wrapper function called when a resize event occurs. You can pass the result to `unbindEvent_`.
|
||||
* A wrapper function called when a resize event occurs.
|
||||
* You can pass the result to `unbindEvent_`.
|
||||
* @type {Array.<!Array>}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.resizeHandlerWrapper_ = null;
|
||||
|
||||
/**
|
||||
* The render status of an SVG workspace.
|
||||
* Returns `true` for visible workspaces and `false` for non-visible, or headless, workspaces.
|
||||
* Returns `true` for visible workspaces and `false` for non-visible,
|
||||
* or headless, workspaces.
|
||||
* @type {boolean}
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.rendered = true;
|
||||
|
@ -179,15 +191,40 @@ Blockly.WorkspaceSvg.prototype.trashcan = null;
|
|||
Blockly.WorkspaceSvg.prototype.scrollbar = null;
|
||||
|
||||
/**
|
||||
* This workspace's drag surface, if it exists.
|
||||
* @type {Blockly.DragSurfaceSvg}
|
||||
* This workspace's surface for dragging blocks, if it exists.
|
||||
* @type {Blockly.BlockDragSurfaceSvg}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.dragSurface = null;
|
||||
Blockly.WorkspaceSvg.prototype.blockDragSurface_ = null;
|
||||
|
||||
/**
|
||||
* Inverted screen CTM, for use in mouseToSvg.
|
||||
* @type {SVGMatrix}
|
||||
* This workspace's drag surface, if it exists.
|
||||
* @type {Blockly.WorkspaceDragSurfaceSvg}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.workspaceDragSurface_ = null;
|
||||
|
||||
/**
|
||||
* Whether to move workspace to the drag surface when it is dragged.
|
||||
* True if it should move, false if it should be translated directly.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.useWorkspaceDragSurface_ = false;
|
||||
|
||||
/**
|
||||
* Whether the drag surface is actively in use. When true, calls to
|
||||
* translate will translate the drag surface instead of the translating the
|
||||
* workspace directly.
|
||||
* This is set to true in setupDragSurface and to false in resetDragSurface.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isDragSurfaceActive_ = false;
|
||||
|
||||
/**
|
||||
* Time that the last sound was played.
|
||||
* @type {Date}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.inverseScreenCTM_ = null;
|
||||
|
@ -224,6 +261,14 @@ Blockly.WorkspaceSvg.prototype.setResizeHandlerWrapper = function(handler) {
|
|||
*/
|
||||
Blockly.WorkspaceSvg.prototype.lastRecordedPageScroll_ = null;
|
||||
|
||||
/**
|
||||
* Map from function names to callbacks, for deciding what to do when a button
|
||||
* is clicked.
|
||||
* @type {!Object<string, function(!Blockly.FlyoutButton)>}
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.flyoutButtonCallbacks_ = {};
|
||||
|
||||
/**
|
||||
* Inverted screen CTM, for use in mouseToSvg.
|
||||
* @type {SVGMatrix}
|
||||
|
@ -249,6 +294,38 @@ Blockly.WorkspaceSvg.prototype.updateInverseScreenCTM = function() {
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the absolute coordinates of the top-left corner of this element,
|
||||
* scales that after canvas SVG element, if it's a descendant.
|
||||
* The origin (0,0) is the top-left corner of the Blockly SVG.
|
||||
* @param {!Element} element Element to find the coordinates of.
|
||||
* @return {!goog.math.Coordinate} Object with .x and .y properties.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getSvgXY = function(element) {
|
||||
var x = 0;
|
||||
var y = 0;
|
||||
var scale = 1;
|
||||
if (goog.dom.contains(this.getCanvas(), element) ||
|
||||
goog.dom.contains(this.getBubbleCanvas(), element)) {
|
||||
// Before the SVG canvas, scale the coordinates.
|
||||
scale = this.scale;
|
||||
}
|
||||
do {
|
||||
// Loop through this block and every parent.
|
||||
var xy = Blockly.utils.getRelativeXY(element);
|
||||
if (element == this.getCanvas() ||
|
||||
element == this.getBubbleCanvas()) {
|
||||
// After the SVG canvas, don't scale the coordinates.
|
||||
scale = 1;
|
||||
}
|
||||
x += xy.x * scale;
|
||||
y += xy.y * scale;
|
||||
element = element.parentNode;
|
||||
} while (element && element != this.getParentSvg());
|
||||
return new goog.math.Coordinate(x, y);
|
||||
};
|
||||
|
||||
/**
|
||||
* Save resize handler data so we can delete it later in dispose.
|
||||
* @param {!Array.<!Array>} handler Data that can be passed to unbindEvent_.
|
||||
|
@ -274,11 +351,11 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
|||
* </g>
|
||||
* @type {SVGElement}
|
||||
*/
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyWorkspace'}, null);
|
||||
if (opt_backgroundClass) {
|
||||
/** @type {SVGElement} */
|
||||
this.svgBackground_ = Blockly.createSvgElement('rect',
|
||||
this.svgBackground_ = Blockly.utils.createSvgElement('rect',
|
||||
{'height': '100%', 'width': '100%', 'class': opt_backgroundClass},
|
||||
this.svgGroup_);
|
||||
if (opt_backgroundClass == 'blocklyMainBackground') {
|
||||
|
@ -287,10 +364,10 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
|||
}
|
||||
}
|
||||
/** @type {SVGElement} */
|
||||
this.svgBlockCanvas_ = Blockly.createSvgElement('g',
|
||||
this.svgBlockCanvas_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyBlockCanvas'}, this.svgGroup_, this);
|
||||
/** @type {SVGElement} */
|
||||
this.svgBubbleCanvas_ = Blockly.createSvgElement('g',
|
||||
this.svgBubbleCanvas_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyBubbleCanvas'}, this.svgGroup_, this);
|
||||
var bottom = Blockly.Scrollbar.scrollbarThickness;
|
||||
if (this.options.hasTrashcan) {
|
||||
|
@ -321,8 +398,6 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) {
|
|||
* @private
|
||||
*/
|
||||
this.toolbox_ = new Blockly.Toolbox(this);
|
||||
} else if (this.options.languageTree) {
|
||||
this.addFlyout_();
|
||||
}
|
||||
this.updateGridPattern_();
|
||||
this.updateStackGlowScale_();
|
||||
|
@ -416,10 +491,12 @@ Blockly.WorkspaceSvg.prototype.addZoomControls_ = function(bottom) {
|
|||
};
|
||||
|
||||
/**
|
||||
* Add a flyout.
|
||||
* Add a flyout element in an element with the given tag name.
|
||||
* @param {string} tagName What type of tag the flyout belongs in.
|
||||
* @return {!Element} The element containing the flyout dom.
|
||||
* @private
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.addFlyout_ = function() {
|
||||
Blockly.WorkspaceSvg.prototype.addFlyout_ = function(tagName) {
|
||||
var workspaceOptions = {
|
||||
disabledPatternId: this.options.disabledPatternId,
|
||||
parentWorkspace: this,
|
||||
|
@ -434,8 +511,28 @@ Blockly.WorkspaceSvg.prototype.addFlyout_ = function() {
|
|||
this.flyout_ = new Blockly.VerticalFlyout(workspaceOptions);
|
||||
}
|
||||
this.flyout_.autoClose = false;
|
||||
var svgFlyout = this.flyout_.createDom();
|
||||
this.svgGroup_.insertBefore(svgFlyout, this.svgBlockCanvas_);
|
||||
|
||||
// Return the element so that callers can place it in their desired
|
||||
// spot in the dom. For exmaple, mutator flyouts do not go in the same place
|
||||
// as main workspace flyouts.
|
||||
return this.flyout_.createDom(tagName);
|
||||
};
|
||||
|
||||
/**
|
||||
* Getter for the flyout associated with this workspace. This flyout may be
|
||||
* owned by either the toolbox or the workspace, depending on toolbox
|
||||
* configuration. It will be null if there is no flyout.
|
||||
* @return {Blockly.Flyout} The flyout on this workspace.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getFlyout_ = function() {
|
||||
if (this.flyout_) {
|
||||
return this.flyout_;
|
||||
}
|
||||
if (this.toolbox_) {
|
||||
return this.toolbox_.flyout_;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -455,7 +552,7 @@ Blockly.WorkspaceSvg.prototype.updateScreenCalculations_ = function() {
|
|||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.resizeContents = function() {
|
||||
if (!this.resizesEnabled_) {
|
||||
if (!this.resizesEnabled_ || !this.rendered) {
|
||||
return;
|
||||
}
|
||||
if (this.scrollbar) {
|
||||
|
@ -564,13 +661,65 @@ Blockly.WorkspaceSvg.prototype.getFlyout = function() {
|
|||
* @param {number} y Vertical translation.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.translate = function(x, y) {
|
||||
var translation = 'translate(' + x + ',' + y + ') ' +
|
||||
'scale(' + this.scale + ')';
|
||||
if (this.useWorkspaceDragSurface_ && this.isDragSurfaceActive_) {
|
||||
this.workspaceDragSurface_.translateSurface(x,y);
|
||||
} else {
|
||||
var translation = 'translate(' + x + ',' + y + ') ' +
|
||||
'scale(' + this.scale + ')';
|
||||
this.svgBlockCanvas_.setAttribute('transform', translation);
|
||||
this.svgBubbleCanvas_.setAttribute('transform', translation);
|
||||
}
|
||||
// Now update the block drag surface if we're using one.
|
||||
if (this.blockDragSurface_) {
|
||||
this.blockDragSurface_.translateAndScaleGroup(x, y, this.scale);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called at the end of a workspace drag to take the contents
|
||||
* out of the drag surface and put them back into the workspace svg.
|
||||
* Does nothing if the workspace drag surface is not enabled.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.resetDragSurface = function() {
|
||||
// Don't do anything if we aren't using a drag surface.
|
||||
if (!this.useWorkspaceDragSurface_) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragSurfaceActive_ = false;
|
||||
|
||||
var trans = this.workspaceDragSurface_.getSurfaceTranslation();
|
||||
this.workspaceDragSurface_.clearAndHide(this.svgGroup_);
|
||||
var translation = 'translate(' + trans.x + ',' + trans.y + ') ' +
|
||||
'scale(' + this.scale + ')';
|
||||
this.svgBlockCanvas_.setAttribute('transform', translation);
|
||||
this.svgBubbleCanvas_.setAttribute('transform', translation);
|
||||
if (this.dragSurface) {
|
||||
this.dragSurface.translateAndScaleGroup(x, y, this.scale);
|
||||
};
|
||||
|
||||
/**
|
||||
* Called at the beginning of a workspace drag to move contents of
|
||||
* the workspace to the drag surface.
|
||||
* Does nothing if the drag surface is not enabled.
|
||||
* @package
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.setupDragSurface = function() {
|
||||
// Don't do anything if we aren't using a drag surface.
|
||||
if (!this.useWorkspaceDragSurface_) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isDragSurfaceActive_ = true;
|
||||
|
||||
// Figure out where we want to put the canvas back. The order
|
||||
// in the is important because things are layered.
|
||||
var previousElement = this.svgBlockCanvas_.previousSibling;
|
||||
var width = this.getParentSvg().getAttribute("width");
|
||||
var height = this.getParentSvg().getAttribute("height");
|
||||
var coord = Blockly.utils.getRelativeXY(this.svgBlockCanvas_);
|
||||
this.workspaceDragSurface_.setContentsAndShow(this.svgBlockCanvas_,
|
||||
this.svgBubbleCanvas_, previousElement, width, height, this.scale);
|
||||
this.workspaceDragSurface_.translateSurface(coord.x, coord.y);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -589,6 +738,19 @@ Blockly.WorkspaceSvg.prototype.getWidth = function() {
|
|||
* @param {boolean} isVisible True if workspace should be visible.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) {
|
||||
|
||||
// Tell the scrollbar whether its container is visible so it can
|
||||
// tell when to hide itself.
|
||||
if (this.scrollbar) {
|
||||
this.scrollbar.setContainerVisible(isVisible);
|
||||
}
|
||||
|
||||
// Tell the flyout whether its container is visible so it can
|
||||
// tell when to hide itself.
|
||||
if (this.getFlyout_()) {
|
||||
this.getFlyout_().setContainerVisible(isVisible);
|
||||
}
|
||||
|
||||
this.getParentSvg().style.display = isVisible ? 'block' : 'none';
|
||||
if (this.toolbox_) {
|
||||
// Currently does not support toolboxes in mutators.
|
||||
|
@ -628,7 +790,8 @@ Blockly.WorkspaceSvg.prototype.traceOn = function() {
|
|||
};
|
||||
|
||||
/**
|
||||
* Highlight or unhighlight a block in the workspace.
|
||||
* Highlight or unhighlight a block in the workspace. Block highlighting is
|
||||
* often used to visually mark blocks currently being executed.
|
||||
* @param {?string} id ID of block to highlight/unhighlight,
|
||||
* or null for no block (used to unhighlight all blocks).
|
||||
* @param {boolean=} opt_state If undefined, highlight specified block and
|
||||
|
@ -814,26 +977,18 @@ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() {
|
|||
* Is the mouse event over a delete area (toolbox or non-closing flyout)?
|
||||
* Opens or closes the trashcan and sets the cursor as a side effect.
|
||||
* @param {!Event} e Mouse move event.
|
||||
* @return {boolean} True if event is in a delete area.
|
||||
* @return {?number} Null if not over a delete area, or an enum representing
|
||||
* which delete area the event is over.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
|
||||
var xy = new goog.math.Coordinate(e.clientX, e.clientY);
|
||||
if (this.deleteAreaTrash_) {
|
||||
if (this.deleteAreaTrash_.contains(xy)) {
|
||||
this.trashcan.setOpen_(true);
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
|
||||
return true;
|
||||
}
|
||||
this.trashcan.setOpen_(false);
|
||||
if (this.deleteAreaTrash_ && this.deleteAreaTrash_.contains(xy)) {
|
||||
return Blockly.DELETE_AREA_TRASH;
|
||||
}
|
||||
if (this.deleteAreaToolbox_) {
|
||||
if (this.deleteAreaToolbox_.contains(xy)) {
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.DELETE);
|
||||
return true;
|
||||
}
|
||||
if (this.deleteAreaToolbox_ && this.deleteAreaToolbox_.contains(xy)) {
|
||||
return Blockly.DELETE_AREA_TOOLBOX;
|
||||
}
|
||||
Blockly.Css.setCursor(Blockly.Css.Cursor.CLOSED);
|
||||
return false;
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -843,7 +998,7 @@ Blockly.WorkspaceSvg.prototype.isDeleteArea = function(e) {
|
|||
*/
|
||||
Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
|
||||
this.markFocused();
|
||||
if (Blockly.isTargetInput_(e)) {
|
||||
if (Blockly.utils.isTargetInput(e)) {
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
return;
|
||||
}
|
||||
|
@ -857,9 +1012,13 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
|
|||
// Clicking on the document clears the selection.
|
||||
Blockly.selected.unselect();
|
||||
}
|
||||
if (Blockly.isRightButton(e)) {
|
||||
if (Blockly.utils.isRightButton(e)) {
|
||||
// Right-click.
|
||||
this.showContextMenu_(e);
|
||||
// This is to handle the case where the event is pretending to be a right
|
||||
// click event but it was really a long press. In that case, we want to make
|
||||
// sure any in progress drags are stopped.
|
||||
Blockly.onMouseUp_(e);
|
||||
// Since this was a click, not a drag, end the gesture immediately.
|
||||
Blockly.Touch.clearTouchIdentifier();
|
||||
} else if (this.scrollbar) {
|
||||
|
@ -871,6 +1030,7 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
|
|||
this.startScrollX = this.scrollX;
|
||||
this.startScrollY = this.scrollY;
|
||||
|
||||
this.setupDragSurface();
|
||||
// If this is a touch event then bind to the mouseup so workspace drag mode
|
||||
// is turned off and double move events are not performed on a block.
|
||||
// See comment in inject.js Blockly.init_ as to why mouseup events are
|
||||
|
@ -901,7 +1061,7 @@ Blockly.WorkspaceSvg.prototype.onMouseDown_ = function(e) {
|
|||
*/
|
||||
Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) {
|
||||
// Record the starting offset between the bubble's location and the mouse.
|
||||
var point = Blockly.mouseToSvg(e, this.getParentSvg(),
|
||||
var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(),
|
||||
this.getInverseScreenCTM());
|
||||
// Fix scale of mouse event.
|
||||
point.x /= this.scale;
|
||||
|
@ -915,7 +1075,7 @@ Blockly.WorkspaceSvg.prototype.startDrag = function(e, xy) {
|
|||
* @return {!goog.math.Coordinate} New location of object.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.moveDrag = function(e) {
|
||||
var point = Blockly.mouseToSvg(e, this.getParentSvg(),
|
||||
var point = Blockly.utils.mouseToSvg(e, this.getParentSvg(),
|
||||
this.getInverseScreenCTM());
|
||||
// Fix scale of mouse event.
|
||||
point.x /= this.scale;
|
||||
|
@ -942,10 +1102,11 @@ Blockly.WorkspaceSvg.prototype.isDragging = function() {
|
|||
Blockly.WorkspaceSvg.prototype.onMouseWheel_ = function(e) {
|
||||
// TODO: Remove terminateDrag and compensate for coordinate skew during zoom.
|
||||
if (e.ctrlKey) {
|
||||
// Pinch-to-zoom in Chrome only
|
||||
Blockly.terminateDrag_();
|
||||
var delta = e.deltaY > 0 ? -1 : 1;
|
||||
var position = Blockly.mouseToSvg(e, this.getParentSvg(),
|
||||
// The vertical scroll distance that corresponds to a click of a zoom button.
|
||||
var PIXELS_PER_ZOOM_STEP = 50;
|
||||
var delta = -e.deltaY / PIXELS_PER_ZOOM_STEP;
|
||||
var position = Blockly.utils.mouseToSvg(e, this.getParentSvg(),
|
||||
this.getInverseScreenCTM());
|
||||
this.zoom(position.x, position.y, delta);
|
||||
} else {
|
||||
|
@ -1032,7 +1193,7 @@ Blockly.WorkspaceSvg.prototype.showContextMenu_ = function(e) {
|
|||
}
|
||||
var menuOptions = [];
|
||||
var topBlocks = this.getTopBlocks(true);
|
||||
var eventGroup = Blockly.genUid();
|
||||
var eventGroup = Blockly.utils.genUid();
|
||||
|
||||
// Options to undo/redo previous action.
|
||||
var undoOption = {};
|
||||
|
@ -1302,9 +1463,10 @@ Blockly.WorkspaceSvg.prototype.markFocused = function() {
|
|||
* Zooming the blocks centered in (x, y) coordinate with zooming in or out.
|
||||
* @param {number} x X coordinate of center.
|
||||
* @param {number} y Y coordinate of center.
|
||||
* @param {number} type Type of zooming (-1 zooming out and 1 zooming in).
|
||||
* @param {number} amount Amount of zooming
|
||||
* (negative zooms out and positive zooms in).
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.zoom = function(x, y, type) {
|
||||
Blockly.WorkspaceSvg.prototype.zoom = function(x, y, amount) {
|
||||
var speed = this.options.zoomOptions.scaleSpeed;
|
||||
var metrics = this.getMetrics();
|
||||
var center = this.getParentSvg().createSVGPoint();
|
||||
|
@ -1315,7 +1477,7 @@ Blockly.WorkspaceSvg.prototype.zoom = function(x, y, type) {
|
|||
y = center.y;
|
||||
var canvas = this.getCanvas();
|
||||
// Scale factor.
|
||||
var scaleChange = (type == 1) ? speed : 1 / speed;
|
||||
var scaleChange = Math.pow(speed, amount);
|
||||
// Clamp scale within valid range.
|
||||
var newScale = this.scale * scaleChange;
|
||||
if (newScale > this.options.zoomOptions.maxScale) {
|
||||
|
@ -1368,7 +1530,7 @@ Blockly.WorkspaceSvg.prototype.zoomToFit = function() {
|
|||
workspaceWidth -= this.flyout_.width_;
|
||||
}
|
||||
if (!this.scrollbar) {
|
||||
// Orgin point of 0,0 is fixed, blocks will not scroll to center.
|
||||
// Origin point of 0,0 is fixed, blocks will not scroll to center.
|
||||
blocksWidth += metrics.contentLeft;
|
||||
blocksHeight += metrics.contentTop;
|
||||
}
|
||||
|
@ -1617,8 +1779,9 @@ Blockly.WorkspaceSvg.setTopLevelWorkspaceMetrics_ = function(xyRatio) {
|
|||
if (this.options.gridPattern) {
|
||||
this.options.gridPattern.setAttribute('x', x);
|
||||
this.options.gridPattern.setAttribute('y', y);
|
||||
if (goog.userAgent.IE) {
|
||||
// IE doesn't notice that the x/y offsets have changed. Force an update.
|
||||
if (goog.userAgent.IE || goog.userAgent.EDGE) {
|
||||
// IE/Edge doesn't notice that the x/y offsets have changed.
|
||||
// Force an update.
|
||||
this.updateGridPattern_();
|
||||
}
|
||||
}
|
||||
|
@ -1649,6 +1812,32 @@ Blockly.WorkspaceSvg.prototype.clear = function() {
|
|||
this.setResizesEnabled(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a callback function associated with a given key, for clicks on
|
||||
* buttons and labels in the flyout.
|
||||
* For instance, a button specified by the XML
|
||||
* <button text="create variable" callbackKey="CREATE_VARIABLE"></button>
|
||||
* should be matched by a call to
|
||||
* registerButtonCallback("CREATE_VARIABLE", yourCallbackFunction).
|
||||
* @param {string} key The name to use to look up this function.
|
||||
* @param {function(!Blockly.FlyoutButton)} func The function to call when the
|
||||
* given button is clicked.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.registerButtonCallback = function(key, func) {
|
||||
this.flyoutButtonCallbacks_[key] = func;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the callback function associated with a given key, for clicks on buttons
|
||||
* and labels in the flyout.
|
||||
* @param {string} key The name to use to look up the function.
|
||||
* @return {function(!Blockly.FlyoutButton)} The function corresponding to the
|
||||
* given key for this workspace.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.getButtonCallback = function(key) {
|
||||
return this.flyoutButtonCallbacks_[key];
|
||||
};
|
||||
|
||||
// Export symbols that would otherwise be renamed by Closure compiler.
|
||||
Blockly.WorkspaceSvg.prototype['setVisible'] =
|
||||
Blockly.WorkspaceSvg.prototype.setVisible;
|
||||
|
|
|
@ -362,7 +362,7 @@ Blockly.Xml.domToBlock = function(xmlBlock, workspace) {
|
|||
for (var i = blocks.length - 1; i >= 0; i--) {
|
||||
blocks[i].render(false);
|
||||
}
|
||||
// Populating the connection database may be defered until after the
|
||||
// Populating the connection database may be deferred until after the
|
||||
// blocks have rendered.
|
||||
if (!workspace.isFlyout) {
|
||||
setTimeout(function() {
|
||||
|
@ -434,7 +434,7 @@ Blockly.Xml.domToBlockHeadless_ = function(xmlBlock, workspace) {
|
|||
if (block.domToMutation) {
|
||||
block.domToMutation(xmlChild);
|
||||
if (block.initSvg) {
|
||||
// Mutation may have added some elements that need initalizing.
|
||||
// Mutation may have added some elements that need initializing.
|
||||
block.initSvg();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,17 +113,17 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
|||
clip-path="url(#blocklyZoomresetClipPath837493)"></image>
|
||||
</g>
|
||||
*/
|
||||
this.svgGroup_ = Blockly.createSvgElement('g',
|
||||
this.svgGroup_ = Blockly.utils.createSvgElement('g',
|
||||
{'class': 'blocklyZoom'}, null);
|
||||
var rnd = String(Math.random()).substring(2);
|
||||
|
||||
var clip = Blockly.createSvgElement('clipPath',
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyZoomoutClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'width': 32, 'height': 32, 'y': 77},
|
||||
clip);
|
||||
var zoomoutSvg = Blockly.createSvgElement('image',
|
||||
var zoomoutSvg = Blockly.utils.createSvgElement('image',
|
||||
{'width': Blockly.SPRITE.width,
|
||||
'height': Blockly.SPRITE.height, 'x': -64,
|
||||
'y': -15,
|
||||
|
@ -132,13 +132,13 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
|||
zoomoutSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
|
||||
workspace.options.pathToMedia + Blockly.SPRITE.url);
|
||||
|
||||
var clip = Blockly.createSvgElement('clipPath',
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyZoominClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'width': 32, 'height': 32, 'y': 43},
|
||||
clip);
|
||||
var zoominSvg = Blockly.createSvgElement('image',
|
||||
var zoominSvg = Blockly.utils.createSvgElement('image',
|
||||
{'width': Blockly.SPRITE.width,
|
||||
'height': Blockly.SPRITE.height,
|
||||
'x': -32,
|
||||
|
@ -148,13 +148,13 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
|||
zoominSvg.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href',
|
||||
workspace.options.pathToMedia + Blockly.SPRITE.url);
|
||||
|
||||
var clip = Blockly.createSvgElement('clipPath',
|
||||
var clip = Blockly.utils.createSvgElement('clipPath',
|
||||
{'id': 'blocklyZoomresetClipPath' + rnd},
|
||||
this.svgGroup_);
|
||||
Blockly.createSvgElement('rect',
|
||||
Blockly.utils.createSvgElement('rect',
|
||||
{'width': 32, 'height': 32},
|
||||
clip);
|
||||
var zoomresetSvg = Blockly.createSvgElement('image',
|
||||
var zoomresetSvg = Blockly.utils.createSvgElement('image',
|
||||
{'width': Blockly.SPRITE.width,
|
||||
'height': Blockly.SPRITE.height, 'y': -92,
|
||||
'clip-path': 'url(#blocklyZoomresetClipPath' + rnd + ')'},
|
||||
|
@ -167,21 +167,21 @@ Blockly.ZoomControls.prototype.createDom = function() {
|
|||
workspace.markFocused();
|
||||
workspace.setScale(workspace.options.zoomOptions.startScale);
|
||||
workspace.scrollCenter();
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
e.stopPropagation(); // Don't start a workspace scroll.
|
||||
e.preventDefault(); // Stop double-clicking from selecting text.
|
||||
});
|
||||
Blockly.bindEventWithChecks_(zoominSvg, 'mousedown', null, function(e) {
|
||||
workspace.markFocused();
|
||||
workspace.zoomCenter(1);
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
e.stopPropagation(); // Don't start a workspace scroll.
|
||||
e.preventDefault(); // Stop double-clicking from selecting text.
|
||||
});
|
||||
Blockly.bindEventWithChecks_(zoomoutSvg, 'mousedown', null, function(e) {
|
||||
workspace.markFocused();
|
||||
workspace.zoomCenter(-1);
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
e.stopPropagation(); // Don't start a workspace scroll.
|
||||
e.preventDefault(); // Stop double-clicking from selecting text.
|
||||
});
|
||||
|
|
|
@ -40,7 +40,6 @@ Blockly.Msg.CONTROLS_FOREACH_TOOLTIP = "For each item in a list, set the variabl
|
|||
Blockly.Msg.CONTROLS_FOR_HELPURL = "https://github.com/google/blockly/wiki/Loops#count-with";
|
||||
Blockly.Msg.CONTROLS_FOR_TITLE = "count with %1 from %2 to %3 by %4";
|
||||
Blockly.Msg.CONTROLS_FOR_TOOLTIP = "Have the variable '%1' take on the values from the start number to the end number, counting by the specified interval, and do the specified blocks.";
|
||||
Blockly.Msg.CONTROLS_IFELSE_TITLE = "if %1 do %2 else %3";
|
||||
Blockly.Msg.CONTROLS_IF_ELSEIF_TOOLTIP = "Add a condition to the if block.";
|
||||
Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP = "Add a final, catch-all condition to the if block.";
|
||||
Blockly.Msg.CONTROLS_IF_HELPURL = "https://github.com/google/blockly/wiki/IfElse";
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"@metadata": {
|
||||
"author": "Ellen Spertus <ellen.spertus@gmail.com>",
|
||||
"lastupdated": "2017-01-02 12:17:41.695529",
|
||||
"lastupdated": "2017-02-01 21:01:32.200551",
|
||||
"locale": "en",
|
||||
"messagedocumentation" : "qqq"
|
||||
},
|
||||
|
@ -82,7 +82,6 @@
|
|||
"CONTROLS_IF_IF_TOOLTIP": "Add, remove, or reorder sections to reconfigure this if block.",
|
||||
"CONTROLS_IF_ELSEIF_TOOLTIP": "Add a condition to the if block.",
|
||||
"CONTROLS_IF_ELSE_TOOLTIP": "Add a final, catch-all condition to the if block.",
|
||||
"CONTROLS_IFELSE_TITLE": "if %1 do %2 else %3",
|
||||
"LOGIC_COMPARE_HELPURL": "https://en.wikipedia.org/wiki/Inequality_(mathematics)",
|
||||
"LOGIC_COMPARE_TOOLTIP_EQ": "Return true if both inputs equal each other.",
|
||||
"LOGIC_COMPARE_TOOLTIP_NEQ": "Return true if both inputs are not equal to each other.",
|
||||
|
|
|
@ -239,9 +239,6 @@ Blockly.Msg.CONTROLS_IF_ELSE_TITLE_ELSE = Blockly.Msg.CONTROLS_IF_MSG_ELSE;
|
|||
/// tooltip - Describes the 'else' subblock during [https://github.com/google/blockly/wiki/IfElse#block-modification if block modification].
|
||||
Blockly.Msg.CONTROLS_IF_ELSE_TOOLTIP = 'Add a final, catch-all condition to the if block.';
|
||||
|
||||
/// block text - See [https://github.com/google/blockly/wiki/IfElse https://github.com/google/blockly/wiki/IfElse]. The English word "otherwise" would probably be superior to "else", but the latter is used because it is traditional and shorter.
|
||||
Blockly.Msg.CONTROLS_IFELSE_TITLE = 'if %1 do %2 else %3';
|
||||
|
||||
/// url - Information about comparisons.
|
||||
Blockly.Msg.LOGIC_COMPARE_HELPURL = 'https://en.wikipedia.org/wiki/Inequality_(mathematics)';
|
||||
/// tooltip - Describes the equals (=) block.
|
||||
|
|
|
@ -1,140 +0,0 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function test_genUid() {
|
||||
var uuids = {};
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var uuid = Blockly.genUid();
|
||||
assertFalse('UUID different: ' + uuid, uuid in uuids);
|
||||
uuids[uuid] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function test_addClass() {
|
||||
var p = document.createElement('p');
|
||||
Blockly.addClass_(p, 'one');
|
||||
assertEquals('Adding "one"', 'one', p.className);
|
||||
Blockly.addClass_(p, 'one');
|
||||
assertEquals('Adding duplicate "one"', 'one', p.className);
|
||||
Blockly.addClass_(p, 'two');
|
||||
assertEquals('Adding "two"', 'one two', p.className);
|
||||
Blockly.addClass_(p, 'two');
|
||||
assertEquals('Adding duplicate "two"', 'one two', p.className);
|
||||
Blockly.addClass_(p, 'three');
|
||||
assertEquals('Adding "three"', 'one two three', p.className);
|
||||
}
|
||||
|
||||
function test_removeClass() {
|
||||
var p = document.createElement('p');
|
||||
p.className = ' one three two three ';
|
||||
Blockly.removeClass_(p, 'two');
|
||||
assertEquals('Removing "two"', 'one three three', p.className);
|
||||
Blockly.removeClass_(p, 'four');
|
||||
assertEquals('Removing "four"', 'one three three', p.className);
|
||||
Blockly.removeClass_(p, 'three');
|
||||
assertEquals('Removing "three"', 'one', p.className);
|
||||
Blockly.removeClass_(p, 'ne');
|
||||
assertEquals('Removing "ne"', 'one', p.className);
|
||||
Blockly.removeClass_(p, 'one');
|
||||
assertEquals('Removing "one"', '', p.className);
|
||||
Blockly.removeClass_(p, 'zero');
|
||||
assertEquals('Removing "zero"', '', p.className);
|
||||
}
|
||||
|
||||
function test_hasClass() {
|
||||
var p = document.createElement('p');
|
||||
p.className = ' one three two three ';
|
||||
assertTrue('Has "one"', Blockly.hasClass_(p, 'one'));
|
||||
assertTrue('Has "two"', Blockly.hasClass_(p, 'two'));
|
||||
assertTrue('Has "three"', Blockly.hasClass_(p, 'three'));
|
||||
assertFalse('Has no "four"', Blockly.hasClass_(p, 'four'));
|
||||
assertFalse('Has no "t"', Blockly.hasClass_(p, 't'));
|
||||
}
|
||||
|
||||
function test_shortestStringLength() {
|
||||
var len = Blockly.shortestStringLength('one,two,three,four,five'.split(','));
|
||||
assertEquals('Length of "one"', 3, len);
|
||||
len = Blockly.shortestStringLength('one,two,three,four,five,'.split(','));
|
||||
assertEquals('Length of ""', 0, len);
|
||||
len = Blockly.shortestStringLength(['Hello World']);
|
||||
assertEquals('List of one', 11, len);
|
||||
len = Blockly.shortestStringLength([]);
|
||||
assertEquals('Empty list', 0, len);
|
||||
}
|
||||
|
||||
function test_commonWordPrefix() {
|
||||
var len = Blockly.commonWordPrefix('one,two,three,four,five'.split(','));
|
||||
assertEquals('No prefix', 0, len);
|
||||
len = Blockly.commonWordPrefix('Xone,Xtwo,Xthree,Xfour,Xfive'.split(','));
|
||||
assertEquals('No word prefix', 0, len);
|
||||
len = Blockly.commonWordPrefix('abc de,abc de,abc de,abc de'.split(','));
|
||||
assertEquals('Full equality', 6, len);
|
||||
len = Blockly.commonWordPrefix('abc deX,abc deY'.split(','));
|
||||
assertEquals('One word prefix', 4, len);
|
||||
len = Blockly.commonWordPrefix('abc de,abc deY'.split(','));
|
||||
assertEquals('Overflow no', 4, len);
|
||||
len = Blockly.commonWordPrefix('abc de,abc de Y'.split(','));
|
||||
assertEquals('Overflow yes', 6, len);
|
||||
len = Blockly.commonWordPrefix(['Hello World']);
|
||||
assertEquals('List of one', 11, len);
|
||||
len = Blockly.commonWordPrefix([]);
|
||||
assertEquals('Empty list', 0, len);
|
||||
len = Blockly.commonWordPrefix('turn left,turn right'.split(','));
|
||||
assertEquals('No prefix due to &nbsp;', 0, len);
|
||||
len = Blockly.commonWordPrefix('turn\u00A0left,turn\u00A0right'.split(','));
|
||||
assertEquals('No prefix due to \\u00A0', 0, len);
|
||||
}
|
||||
|
||||
function test_commonWordSuffix() {
|
||||
var len = Blockly.commonWordSuffix('one,two,three,four,five'.split(','));
|
||||
assertEquals('No prefix', 0, len);
|
||||
len = Blockly.commonWordSuffix('oneX,twoX,threeX,fourX,fiveX'.split(','));
|
||||
assertEquals('No word prefix', 0, len);
|
||||
len = Blockly.commonWordSuffix('abc de,abc de,abc de,abc de'.split(','));
|
||||
assertEquals('Full equality', 6, len);
|
||||
len = Blockly.commonWordSuffix('Xabc de,Yabc de'.split(','));
|
||||
assertEquals('One word prefix', 3, len);
|
||||
len = Blockly.commonWordSuffix('abc de,Yabc de'.split(','));
|
||||
assertEquals('Overflow no', 3, len);
|
||||
len = Blockly.commonWordSuffix('abc de,Y abc de'.split(','));
|
||||
assertEquals('Overflow yes', 6, len);
|
||||
len = Blockly.commonWordSuffix(['Hello World']);
|
||||
assertEquals('List of one', 11, len);
|
||||
len = Blockly.commonWordSuffix([]);
|
||||
assertEquals('Empty list', 0, len);
|
||||
}
|
||||
|
||||
function test_tokenizeInterpolation() {
|
||||
var tokens = Blockly.utils.tokenizeInterpolation('');
|
||||
assertArrayEquals('Null interpolation', [], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello');
|
||||
assertArrayEquals('No interpolation', ['Hello'], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello%World');
|
||||
assertArrayEquals('Unescaped %.', ['Hello%World'], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello%%World');
|
||||
assertArrayEquals('Escaped %.', ['Hello%World'], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello %1 World');
|
||||
assertArrayEquals('Interpolation.', ['Hello ', 1, ' World'], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%123Hello%456World%789');
|
||||
assertArrayEquals('Interpolations.', [123, 'Hello', 456, 'World', 789], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%%%x%%0%00%01%');
|
||||
assertArrayEquals('Torture interpolations.', ['%%x%0', 0, 1, '%'], tokens);
|
||||
}
|
218
tests/jsunit/utils_test.js
Normal file
218
tests/jsunit/utils_test.js
Normal file
|
@ -0,0 +1,218 @@
|
|||
/**
|
||||
* @license
|
||||
* Visual Blocks Editor
|
||||
*
|
||||
* Copyright 2011 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.
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
function test_genUid() {
|
||||
var uuids = {};
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
var uuid = Blockly.utils.genUid();
|
||||
assertFalse('UUID different: ' + uuid, uuid in uuids);
|
||||
uuids[uuid] = true;
|
||||
}
|
||||
}
|
||||
|
||||
function test_addClass() {
|
||||
var p = document.createElement('p');
|
||||
Blockly.utils.addClass(p, 'one');
|
||||
assertEquals('Adding "one"', 'one', p.className);
|
||||
Blockly.utils.addClass(p, 'one');
|
||||
assertEquals('Adding duplicate "one"', 'one', p.className);
|
||||
Blockly.utils.addClass(p, 'two');
|
||||
assertEquals('Adding "two"', 'one two', p.className);
|
||||
Blockly.utils.addClass(p, 'two');
|
||||
assertEquals('Adding duplicate "two"', 'one two', p.className);
|
||||
Blockly.utils.addClass(p, 'three');
|
||||
assertEquals('Adding "three"', 'one two three', p.className);
|
||||
}
|
||||
|
||||
function test_hasClass() {
|
||||
var p = document.createElement('p');
|
||||
p.className = ' one three two three ';
|
||||
assertTrue('Has "one"', Blockly.utils.hasClass(p, 'one'));
|
||||
assertTrue('Has "two"', Blockly.utils.hasClass(p, 'two'));
|
||||
assertTrue('Has "three"', Blockly.utils.hasClass(p, 'three'));
|
||||
assertFalse('Has no "four"', Blockly.utils.hasClass(p, 'four'));
|
||||
assertFalse('Has no "t"', Blockly.utils.hasClass(p, 't'));
|
||||
}
|
||||
|
||||
function test_removeClass() {
|
||||
var p = document.createElement('p');
|
||||
p.className = ' one three two three ';
|
||||
Blockly.utils.removeClass(p, 'two');
|
||||
assertEquals('Removing "two"', 'one three three', p.className);
|
||||
Blockly.utils.removeClass(p, 'four');
|
||||
assertEquals('Removing "four"', 'one three three', p.className);
|
||||
Blockly.utils.removeClass(p, 'three');
|
||||
assertEquals('Removing "three"', 'one', p.className);
|
||||
Blockly.utils.removeClass(p, 'ne');
|
||||
assertEquals('Removing "ne"', 'one', p.className);
|
||||
Blockly.utils.removeClass(p, 'one');
|
||||
assertEquals('Removing "one"', '', p.className);
|
||||
Blockly.utils.removeClass(p, 'zero');
|
||||
assertEquals('Removing "zero"', '', p.className);
|
||||
}
|
||||
|
||||
function test_shortestStringLength() {
|
||||
var len = Blockly.utils.shortestStringLength('one,two,three,four,five'.split(','));
|
||||
assertEquals('Length of "one"', 3, len);
|
||||
len = Blockly.utils.shortestStringLength('one,two,three,four,five,'.split(','));
|
||||
assertEquals('Length of ""', 0, len);
|
||||
len = Blockly.utils.shortestStringLength(['Hello World']);
|
||||
assertEquals('List of one', 11, len);
|
||||
len = Blockly.utils.shortestStringLength([]);
|
||||
assertEquals('Empty list', 0, len);
|
||||
}
|
||||
|
||||
function test_commonWordPrefix() {
|
||||
var len = Blockly.utils.commonWordPrefix('one,two,three,four,five'.split(','));
|
||||
assertEquals('No prefix', 0, len);
|
||||
len = Blockly.utils.commonWordPrefix('Xone,Xtwo,Xthree,Xfour,Xfive'.split(','));
|
||||
assertEquals('No word prefix', 0, len);
|
||||
len = Blockly.utils.commonWordPrefix('abc de,abc de,abc de,abc de'.split(','));
|
||||
assertEquals('Full equality', 6, len);
|
||||
len = Blockly.utils.commonWordPrefix('abc deX,abc deY'.split(','));
|
||||
assertEquals('One word prefix', 4, len);
|
||||
len = Blockly.utils.commonWordPrefix('abc de,abc deY'.split(','));
|
||||
assertEquals('Overflow no', 4, len);
|
||||
len = Blockly.utils.commonWordPrefix('abc de,abc de Y'.split(','));
|
||||
assertEquals('Overflow yes', 6, len);
|
||||
len = Blockly.utils.commonWordPrefix(['Hello World']);
|
||||
assertEquals('List of one', 11, len);
|
||||
len = Blockly.utils.commonWordPrefix([]);
|
||||
assertEquals('Empty list', 0, len);
|
||||
len = Blockly.utils.commonWordPrefix('turn left,turn right'.split(','));
|
||||
assertEquals('No prefix due to &nbsp;', 0, len);
|
||||
len = Blockly.utils.commonWordPrefix('turn\u00A0left,turn\u00A0right'.split(','));
|
||||
assertEquals('No prefix due to \\u00A0', 0, len);
|
||||
}
|
||||
|
||||
function test_commonWordSuffix() {
|
||||
var len = Blockly.utils.commonWordSuffix('one,two,three,four,five'.split(','));
|
||||
assertEquals('No prefix', 0, len);
|
||||
len = Blockly.utils.commonWordSuffix('oneX,twoX,threeX,fourX,fiveX'.split(','));
|
||||
assertEquals('No word prefix', 0, len);
|
||||
len = Blockly.utils.commonWordSuffix('abc de,abc de,abc de,abc de'.split(','));
|
||||
assertEquals('Full equality', 6, len);
|
||||
len = Blockly.utils.commonWordSuffix('Xabc de,Yabc de'.split(','));
|
||||
assertEquals('One word prefix', 3, len);
|
||||
len = Blockly.utils.commonWordSuffix('abc de,Yabc de'.split(','));
|
||||
assertEquals('Overflow no', 3, len);
|
||||
len = Blockly.utils.commonWordSuffix('abc de,Y abc de'.split(','));
|
||||
assertEquals('Overflow yes', 6, len);
|
||||
len = Blockly.utils.commonWordSuffix(['Hello World']);
|
||||
assertEquals('List of one', 11, len);
|
||||
len = Blockly.utils.commonWordSuffix([]);
|
||||
assertEquals('Empty list', 0, len);
|
||||
}
|
||||
|
||||
function test_tokenizeInterpolation() {
|
||||
var tokens = Blockly.utils.tokenizeInterpolation('');
|
||||
assertArrayEquals('Null interpolation', [], tokens);
|
||||
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello');
|
||||
assertArrayEquals('No interpolation', ['Hello'], tokens);
|
||||
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello%World');
|
||||
assertArrayEquals('Unescaped %.', ['Hello%World'], tokens);
|
||||
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello%%World');
|
||||
assertArrayEquals('Escaped %.', ['Hello%World'], tokens);
|
||||
|
||||
tokens = Blockly.utils.tokenizeInterpolation('Hello %1 World');
|
||||
assertArrayEquals('Interpolation.', ['Hello ', 1, ' World'], tokens);
|
||||
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%123Hello%456World%789');
|
||||
assertArrayEquals('Interpolations.', [123, 'Hello', 456, 'World', 789], tokens);
|
||||
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%%%x%%0%00%01%');
|
||||
assertArrayEquals('Torture interpolations.', ['%%x%0', 0, 1, '%'], tokens);
|
||||
|
||||
Blockly.Msg = Blockly.Msg || {};
|
||||
|
||||
Blockly.Msg.STRING_REF = 'test string';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%{bky_string_ref}');
|
||||
assertArrayEquals('String table reference, lowercase', ['test string'], tokens);
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%{BKY_STRING_REF}');
|
||||
assertArrayEquals('String table reference, uppercase', ['test string'], tokens);
|
||||
|
||||
Blockly.Msg.WITH_PARAM = 'before %1 after';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%{bky_with_param}');
|
||||
assertArrayEquals('String table reference, with parameter', ['before ', 1, ' after'], tokens);
|
||||
|
||||
Blockly.Msg.RECURSE = 'before %{bky_string_ref} after';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%{bky_recurse}');
|
||||
assertArrayEquals('String table reference, with subreference', ['before test string after'], tokens);
|
||||
|
||||
// Error cases...
|
||||
tokens = Blockly.utils.tokenizeInterpolation('%{bky_undefined}');
|
||||
assertArrayEquals('Undefined string table reference', ['%{bky_undefined}'], tokens);
|
||||
|
||||
Blockly.Msg['1'] = 'Will not match';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('before %{1} after');
|
||||
assertArrayEquals('Invalid initial digit in string table reference', ['before %{1} after'], tokens);
|
||||
|
||||
Blockly.Msg['TWO WORDS'] = 'Will not match';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('before %{two words} after');
|
||||
assertArrayEquals('Invalid character in string table reference: space', ['before %{two words} after'], tokens);
|
||||
|
||||
Blockly.Msg['TWO-WORDS'] = 'Will not match';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('before %{two-words} after');
|
||||
assertArrayEquals('Invalid character in string table reference: dash', ['before %{two-words} after'], tokens);
|
||||
|
||||
Blockly.Msg['TWO.WORDS'] = 'Will not match';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('before %{two.words} after');
|
||||
assertArrayEquals('Invalid character in string table reference: period', ['before %{two.words} after'], tokens);
|
||||
|
||||
Blockly.Msg['AB&C'] = 'Will not match';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('before %{ab&c} after');
|
||||
assertArrayEquals('Invalid character in string table reference: &', ['before %{ab&c} after'], tokens);
|
||||
|
||||
Blockly.Msg['UNCLOSED'] = 'Will not match';
|
||||
tokens = Blockly.utils.tokenizeInterpolation('before %{unclosed');
|
||||
assertArrayEquals('String table reference, with parameter', ['before %{unclosed'], tokens);
|
||||
}
|
||||
|
||||
function test_replaceMessageReferences() {
|
||||
Blockly.Msg = Blockly.Msg || {};
|
||||
Blockly.Msg.STRING_REF = 'test string';
|
||||
|
||||
var resultString = Blockly.utils.replaceMessageReferences('');
|
||||
assertEquals('Empty string produces empty string', '', resultString);
|
||||
|
||||
resultString = Blockly.utils.replaceMessageReferences('%{bky_string_ref}');
|
||||
assertEquals('Message ref dereferenced.', 'test string', resultString);
|
||||
resultString = Blockly.utils.replaceMessageReferences('before %{bky_string_ref} after');
|
||||
assertEquals('Message ref dereferenced.', 'before test string after', resultString);
|
||||
|
||||
resultString = Blockly.utils.replaceMessageReferences('%1');
|
||||
assertEquals('Interpolation tokens ignored.', '%1', resultString);
|
||||
resultString = Blockly.utils.replaceMessageReferences('%1 %2');
|
||||
assertEquals('Interpolation tokens ignored.', '%1 %2', resultString);
|
||||
resultString = Blockly.utils.replaceMessageReferences('before %1 after');
|
||||
assertEquals('Interpolation tokens ignored.', 'before %1 after', resultString);
|
||||
|
||||
resultString = Blockly.utils.replaceMessageReferences('%%');
|
||||
assertEquals('Escaped %', '%', resultString);
|
||||
resultString = Blockly.utils.replaceMessageReferences('%%{bky_string_ref}');
|
||||
assertEquals('Escaped %', '%{bky_string_ref}', resultString);
|
||||
|
||||
resultString = Blockly.utils.replaceMessageReferences('%a');
|
||||
assertEquals('Unrecognized % escape code treated as literal', '%a', resultString);
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue