diff --git a/blocks_vertical/control.js b/blocks_vertical/control.js index 3418745882..b20c90122b 100644 --- a/blocks_vertical/control.js +++ b/blocks_vertical/control.js @@ -530,3 +530,52 @@ Blockly.Blocks['control_all_at_once'] = { }); } }; + +Blockly.Blocks['control_foreach_in_range'] = { + /** + * Block for each number from range. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROL_FOREACHINRANGE, + "message1": "%1", // Statement + "args0": [ + { + "type": "input_value", + "name": "ITEM" + }, + { + "type": "input_value", + "name": "FROM", + }, + { + "type": "input_value", + "name": "TO", + }, + ], + "args1": [ + { + "type": "input_statement", + "name": "SUBSTACK" + } + ], + "category": Blockly.Categories.json, + "extensions": ["colours_control", "shape_statement"], + }); + } +}; + +Blockly.Blocks['control_foreach_in_range_item'] = { + /** + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.CONTROL_FOREACHINRANGE_ITEM, + "category": Blockly.Categories.json, + "duplicateOnDrag": true, + "extensions": ["colours_control", "output_number"] + }); + } +}; \ No newline at end of file diff --git a/blocks_vertical/data.js b/blocks_vertical/data.js index 73e1d0b66d..fcb53dc10e 100644 --- a/blocks_vertical/data.js +++ b/blocks_vertical/data.js @@ -455,6 +455,53 @@ Blockly.Blocks['data_listcontainsitem'] = { } }; +Blockly.Blocks['data_listasarray'] = { + /** + * Block for turning list into array. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.DATA_LISTASARRAY, + "args0": [ + { + "type": "field_variable", + "name": "LIST", + "variableTypes": [Blockly.LIST_VARIABLE_TYPE] + } + ], + "category": Blockly.Categories.dataLists, + "extensions": ["colours_data_lists", "output_array"] + }); + } +}; + +Blockly.Blocks['data_setlistarray'] = { + /** + * Block to set a list to array. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.DATA_SETLISTARRAY, + "args0": [ + { + "type": "field_variable", + "name": "LIST", + "variableTypes": [Blockly.LIST_VARIABLE_TYPE] + }, + { + "type": "input_value", + "name": "ARRAY", + "check": "Array" + }, + ], + "category": Blockly.Categories.dataLists, + "extensions": ["colours_data_lists", "shape_statement"] + }); + } +}; + Blockly.Blocks['data_showlist'] = { /** * Block to show a list. diff --git a/blocks_vertical/default_toolbox.js b/blocks_vertical/default_toolbox.js index dd0c67a690..dbdf4f8d42 100644 --- a/blocks_vertical/default_toolbox.js +++ b/blocks_vertical/default_toolbox.js @@ -295,6 +295,22 @@ Blockly.Blocks.defaultToolbox = '' + + '' + + '' + + '' + + '' + + '' + + '' + + '1' + + '' + + '' + + '' + + '' + + '10' + + '' + + '' + + '' + '' + '' + '' + @@ -531,6 +547,8 @@ Blockly.Blocks.defaultToolbox = '' + + '' + '' + '' + '' + @@ -629,6 +647,8 @@ Blockly.Blocks.defaultToolbox = '' + '' + + '' + + '' + '' + '' + '' + @@ -636,6 +656,16 @@ Blockly.Blocks.defaultToolbox = '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + '' + '' + '' + @@ -719,6 +749,12 @@ Blockly.Blocks.defaultToolbox = '' + + ' ' + + ' ' + + ' ' + + ' ' + + '' + '' + '' + '' + diff --git a/blocks_vertical/extensions.js b/blocks_vertical/extensions.js index 7c737e8b6c..aeb18b4c86 100644 --- a/blocks_vertical/extensions.js +++ b/blocks_vertical/extensions.js @@ -109,6 +109,33 @@ Blockly.Blocks['extension_checkbox_test_legacy_json2'] = { } }; +Blockly.Blocks['extension_input'] = { + init: function () { + this.jsonInit({ + "message0": 'input %1', + "args0": [ + { + "type": "input_value", + "name": "VALUE" + } + ], + "category": Blockly.Categories.more, + "extensions": ["colours_more", "shape_statement"] + }); + } +}; + +Blockly.Blocks['extension_blockduplicateondrag'] = { + init: function () { + this.jsonInit({ + "message0": 'duplicate on drag', + "duplicateOnDrag": true, + "category": Blockly.Categories.more, + "extensions": ["colours_more", "output_string"] + }); + } +}; + Blockly.Blocks['extension_pen_down'] = { /** * @this Blockly.Block diff --git a/blocks_vertical/json.js b/blocks_vertical/json.js index 2bc1893c38..1d3e98859f 100644 --- a/blocks_vertical/json.js +++ b/blocks_vertical/json.js @@ -483,3 +483,69 @@ Blockly.Blocks['json_reverse_array'] = { }); } }; + +Blockly.Blocks['json_foreach'] = { + /** + * Block for each item and index in array. + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.JSON_FOREACH, + "message1": "%1", // Statement + "args0": [ + { + "type": "input_value", + "name": "VALUE" + }, + { + "type": "input_value", + "name": "INDEX" + }, + { + "type": "input_value", + "name": "ARRAY", + "check": "Array" + }, + ], + "args1": [ + { + "type": "input_statement", + "name": "SUBSTACK" + } + ], + "category": Blockly.Categories.json, + "extensions": ["colours_json", "shape_statement"], + }); + } +}; + +Blockly.Blocks['json_foreach_value'] = { + /** + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.JSON_FOREACH_VALUE, + "output": null, + "outputShape": Blockly.OUTPUT_SHAPE_ROUND, + "category": Blockly.Categories.json, + "duplicateOnDrag": true, + "extensions": ["colours_json"], + }); + } +}; + +Blockly.Blocks['json_foreach_index'] = { + /** + * @this Blockly.Block + */ + init: function() { + this.jsonInit({ + "message0": Blockly.Msg.JSON_FOREACH_INDEX, + "category": Blockly.Categories.json, + "duplicateOnDrag": true, + "extensions": ["colours_json", "output_number"] + }); + } +}; \ No newline at end of file diff --git a/blocks_vertical/operators.js b/blocks_vertical/operators.js index 939c94c4ad..88e46fbae0 100644 --- a/blocks_vertical/operators.js +++ b/blocks_vertical/operators.js @@ -489,8 +489,8 @@ Blockly.Blocks['operator_cast'] = { [Blockly.Msg.OPERATORS_CAST_STRING, "string"], [Blockly.Msg.OPERATORS_CAST_NUMBER, "number"], [Blockly.Msg.OPERATORS_CAST_BOOLEAN, "boolean"], - [Blockly.Msg.OPERATORS_CAST_ARRAY, "array"], - [Blockly.Msg.OPERATORS_CAST_OBJECT, "object"] + [Blockly.Msg.OPERATORS_CAST_OBJECT, "object"], + [Blockly.Msg.OPERATORS_CAST_ARRAY, "array"] ] } ], @@ -501,3 +501,23 @@ Blockly.Blocks['operator_cast'] = { }); } }; + +Blockly.Blocks['operator_typeof'] = { + /** + * Get the specific type of a value. + * @this Blockly.Block + */ + init: function () { + this.jsonInit({ + message0: Blockly.Msg.OPERATORS_TYPEOF, + args0: [ + { + type: "input_value", + name: "VALUE" + } + ], + category: Blockly.Categories.operators, + extensions: ["colours_operators", "output_string"] + }); + } +}; diff --git a/core/block.js b/core/block.js index 79491231ad..a57a990483 100644 --- a/core/block.js +++ b/core/block.js @@ -142,6 +142,12 @@ Blockly.Block = function(workspace, prototypeName, opt_id) { */ this.outputShape_ = null; + /** + * @type {boolean} + * @private + */ + this.duplicateOnDrag_ = false; + /** * @type {?string} * @private @@ -1325,6 +1331,9 @@ Blockly.Block.prototype.jsonInit = function(json) { var localizedValue = Blockly.utils.replaceMessageReferences(rawValue); this.setHelpUrl(localizedValue); } + if (json['duplicateOnDrag'] !== undefined) { + this.setDuplicateOnDrag(json['duplicateOnDrag']); + } if (goog.isString(json['extensions'])) { console.warn('JSON attribute \'extensions\' should be an array of ' + 'strings. Found raw string in JSON for \'' + json['type'] + '\' block.'); @@ -1839,3 +1848,19 @@ Blockly.Block.prototype.toDevString = function() { } return msg; }; + +/** + * Set whether this block can duplicate on drag and if it's a shadow block. + * @param {boolean} value True if this block should duplicate on drag. + */ +Blockly.Block.prototype.setDuplicateOnDrag = function(value) { + this.duplicateOnDrag_ = value; +}; + +/** + * Get whether this block can duplicate on drag and if it's a shadow block. + * @return {boolean} True if this block can duplicate on drag. + */ +Blockly.Block.prototype.canDuplicateOnDrag = function() { + return this.duplicateOnDrag_ && this.isShadow(); +}; \ No newline at end of file diff --git a/core/block_render_svg_vertical.js b/core/block_render_svg_vertical.js index 87a82c81f2..212cf9e5e3 100644 --- a/core/block_render_svg_vertical.js +++ b/core/block_render_svg_vertical.js @@ -533,8 +533,7 @@ Blockly.BlockSvg.DEFINE_BLOCK_PADDING_RIGHT = 2 * Blockly.BlockSvg.GRID_UNIT; */ Blockly.BlockSvg.prototype.updateColour = function() { var strokeColour = this.getColourTertiary(); - var renderShadowed = this.isShadow() && - !Blockly.scratchBlocksUtils.isShadowArgumentReporter(this); + var renderShadowed = !this.canDuplicateOnDrag() && this.isShadow() && !Blockly.scratchBlocksUtils.isShadowArgumentReporter(this); if (renderShadowed && this.parentBlock_) { // Pull shadow block stroke colour from parent block's tertiary if possible. @@ -989,7 +988,7 @@ Blockly.BlockSvg.prototype.computeInputWidth_ = function(input) { Blockly.BlockSvg.prototype.computeInputHeight_ = function(input, row, previousRow) { if (this.inputList.length === 1 && this.outputConnection && - (this.isShadow() && + (!this.canDuplicateOnDrag() && this.isShadow() && !Blockly.scratchBlocksUtils.isShadowArgumentReporter(this))) { // "Lone" field blocks are smaller. return Blockly.BlockSvg.MIN_BLOCK_Y_SINGLE_FIELD_OUTPUT; @@ -1049,7 +1048,7 @@ Blockly.BlockSvg.prototype.computeRightEdge_ = function(curEdge, hasStatement) { // Blocks with notches edge = Math.max(edge, Blockly.BlockSvg.MIN_BLOCK_X); } else if (this.outputConnection) { - if (this.isShadow() && + if (!this.canDuplicateOnDrag() && this.isShadow() && !Blockly.scratchBlocksUtils.isShadowArgumentReporter(this)) { // Single-fields edge = Math.max(edge, Blockly.BlockSvg.MIN_BLOCK_X_SHADOW_OUTPUT); @@ -1080,7 +1079,7 @@ Blockly.BlockSvg.prototype.computeRightEdge_ = function(curEdge, hasStatement) { Blockly.BlockSvg.prototype.computeOutputPadding_ = function(inputRows) { // Only apply to blocks with outputs and not single fields (shadows). if (!this.getOutputShape() || !this.outputConnection || - (this.isShadow() && + (!this.canDuplicateOnDrag() && this.isShadow() && !Blockly.scratchBlocksUtils.isShadowArgumentReporter(this))) { return; } diff --git a/core/connection.js b/core/connection.js index 66ce750cee..1bda443b32 100644 --- a/core/connection.js +++ b/core/connection.js @@ -298,6 +298,7 @@ Blockly.Connection.prototype.canConnectWithReason_ = function(target) { if (!target) { return Blockly.Connection.REASON_TARGET_NULL; } + if (this.isSuperior()) { var blockA = this.sourceBlock_; var blockB = target.getSourceBlock(); @@ -307,6 +308,7 @@ Blockly.Connection.prototype.canConnectWithReason_ = function(target) { var blockA = target.getSourceBlock(); var superiorConn = target; } + if (blockA && blockA == blockB) { return Blockly.Connection.REASON_SELF_CONNECTION; } else if (target.type != Blockly.OPPOSITE_TYPE[this.type]) { @@ -328,6 +330,15 @@ Blockly.Connection.prototype.canConnectWithReason_ = function(target) { // defnoreturn block to a prototype block. return Blockly.Connection.REASON_CUSTOM_PROCEDURE; } + + const thisConnection = this.targetConnection && this.targetConnection.sourceBlock_; + const targetConnection = target.targetConnection && target.targetConnection.sourceBlock_; + if ( + (thisConnection && (Blockly.scratchBlocksUtils.isShadowArgumentReporter(thisConnection) || thisConnection.canDuplicateOnDrag())) || + (targetConnection && (Blockly.scratchBlocksUtils.isShadowArgumentReporter(targetConnection) || targetConnection.canDuplicateOnDrag())) + ) { + return Blockly.Connection.REASON_CUSTOM_PROCEDURE; + } return Blockly.Connection.CAN_CONNECT; }; diff --git a/core/data_category.js b/core/data_category.js index 64f1d53687..89b3f59ce8 100644 --- a/core/data_category.js +++ b/core/data_category.js @@ -85,6 +85,8 @@ Blockly.DataCategory = function(workspace) { Blockly.DataCategory.addLengthOfList(xmlList, firstVariable); Blockly.DataCategory.addListContainsItem(xmlList, firstVariable); Blockly.DataCategory.addSep(xmlList); + Blockly.DataCategory.addListAsArray(xmlList, firstVariable); + Blockly.DataCategory.addSetListArray(xmlList, firstVariable); Blockly.DataCategory.addShowList(xmlList, firstVariable); Blockly.DataCategory.addHideList(xmlList, firstVariable); } @@ -382,6 +384,30 @@ Blockly.DataCategory.addListContainsItem = function(xmlList, variable) { 'LIST', [['ITEM', 'text', Blockly.Msg.DEFAULT_LIST_ITEM]]); }; +/** + * Construct and add a data_lengthoflist block to xmlList. + * @param {!Array.} xmlList Array of XML block elements. + * @param {?Blockly.VariableModel} variable Variable to select in the field. + */ +Blockly.DataCategory.addListAsArray = function(xmlList, variable) { + // + // variablename + // + Blockly.DataCategory.addBlock(xmlList, variable, 'data_listasarray', 'LIST'); +}; + +/** + * Construct and add a data_lengthoflist block to xmlList. + * @param {!Array.} xmlList Array of XML block elements. + * @param {?Blockly.VariableModel} variable Variable to select in the field. + */ +Blockly.DataCategory.addSetListArray = function(xmlList, variable) { + // + // variablename + // + Blockly.DataCategory.addBlock(xmlList, variable, 'data_setlistarray', 'LIST'); +}; + /** * Construct and add a data_showlist block to xmlList. * @param {!Array.} xmlList Array of XML block elements. diff --git a/core/gesture.js b/core/gesture.js index 41a6e2ed5c..5e180ac841 100644 --- a/core/gesture.js +++ b/core/gesture.js @@ -862,7 +862,7 @@ Blockly.Gesture.prototype.setStartBlock = function(block) { if (!this.startBlock_ && !this.startBubble_) { this.startBlock_ = block; this.shouldDuplicateOnDrag_ = - Blockly.scratchBlocksUtils.isShadowArgumentReporter(block); + Blockly.scratchBlocksUtils.isShadowArgumentReporter(block) || block.canDuplicateOnDrag(); if (block.isInFlyout && block != block.getRootBlock()) { this.setTargetBlock_(block.getRootBlock()); } else { diff --git a/media/icons/arrow_straight.svg b/media/icons/arrow_straight.svg new file mode 100644 index 0000000000..b7c1b1cac0 --- /dev/null +++ b/media/icons/arrow_straight.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/msg/messages.js b/msg/messages.js index 1a7d784ba3..bb966e40b4 100644 --- a/msg/messages.js +++ b/msg/messages.js @@ -55,6 +55,8 @@ Blockly.Msg.CONTROL_COUNTER = 'counter'; Blockly.Msg.CONTROL_INCRCOUNTER = 'increment counter'; Blockly.Msg.CONTROL_CLEARCOUNTER = 'clear counter'; Blockly.Msg.CONTROL_ALLATONCE = 'all at once'; +Blockly.Msg.CONTROL_FOREACHINRANGE = 'for each %1 in range %2 to %3'; +Blockly.Msg.CONTROL_FOREACHINRANGE_ITEM = 'i'; // Data blocks Blockly.Msg.DATA_SETVARIABLETO = 'set %1 to %2'; @@ -70,6 +72,8 @@ Blockly.Msg.DATA_ITEMOFLIST = 'item %1 of %2'; Blockly.Msg.DATA_ITEMNUMOFLIST = 'item # of %1 in %2'; Blockly.Msg.DATA_LENGTHOFLIST = 'length of %1'; Blockly.Msg.DATA_LISTCONTAINSITEM = '%1 contains %2?'; +Blockly.Msg.DATA_LISTASARRAY = '%1 as array'; +Blockly.Msg.DATA_SETLISTARRAY = 'set list %1 to %2'; Blockly.Msg.DATA_SHOWLIST = 'show list %1'; Blockly.Msg.DATA_HIDELIST = 'hide list %1'; Blockly.Msg.DATA_INDEX_ALL = 'all'; @@ -115,8 +119,9 @@ Blockly.Msg.JSON_MERGE = 'merge %1 %2'; Blockly.Msg.JSON_ARRAY_LENGTH = 'length of %1'; Blockly.Msg.JSON_SLICE_ARRAY = 'items %1 to %2 in %3'; Blockly.Msg.JSON_REVERSE_ARRAY = 'reverse %1'; -Blockly.Msg.JSON_OBJECT = '{"key":"value"}'; -Blockly.Msg.JSON_ARRAY = '["foo","bar"]'; +Blockly.Msg.JSON_FOREACH_VALUE = 'value'; +Blockly.Msg.JSON_FOREACH_INDEX = 'index'; +Blockly.Msg.JSON_FOREACH = 'for each %1 %2 in %3'; Blockly.Msg.JSON_KEY = 'key'; Blockly.Msg.JSON_BAR = 'bar'; Blockly.Msg.JSON_BAZ = 'baz'; @@ -267,6 +272,7 @@ Blockly.Msg.OPERATORS_CAST_NUMBER = 'number'; Blockly.Msg.OPERATORS_CAST_BOOLEAN = 'boolean'; Blockly.Msg.OPERATORS_CAST_ARRAY = 'array'; Blockly.Msg.OPERATORS_CAST_OBJECT = 'object'; +Blockly.Msg.OPERATORS_TYPEOF = 'type of %1'; // Procedures blocks Blockly.Msg.PROCEDURES_DEFINITION = 'define %1';