From 21032f7d6fa75ca50f7f8463b94a2bb262932941 Mon Sep 17 00:00:00 2001 From: Sverre <59171289+sverben@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:46:16 +0100 Subject: [PATCH] Feat: type inference + text operators (#43) --- blocks/extensions.js | 69 ----------- blocks/logic.js | 2 +- blocks/text.js | 247 +++++++------------------------------ generators/arduino/all.js | 66 ++++++++-- generators/arduino/text.js | 31 +++++ msg/js/en.js | 7 +- msg/js/nl.js | 7 +- msg/messages.js | 4 + package.json | 2 +- 9 files changed, 146 insertions(+), 289 deletions(-) diff --git a/blocks/extensions.js b/blocks/extensions.js index 05beaf4..3b9ee8a 100644 --- a/blocks/extensions.js +++ b/blocks/extensions.js @@ -367,74 +367,6 @@ const WHILE_UNTIL_TOOLTIPS = { UNTIL: "%{BKY_CONTROLS_WHILEUNTIL_TOOLTIP_UNTIL}", }; -/** - * Adds dynamic type validation for the left and right sides of a logic_compare - * block. - * @mixin - * @augments Block - * @readonly - */ -const LOGIC_COMPARE_ONCHANGE_MIXIN = { - /** - * Called whenever anything on the workspace changes. - * Prevent mismatched types from being compared. - * @param {!AbstractEvent} e Change event. - * @this {Block} - */ - onchange: function (e) { - if (!this.prevBlocks_) { - this.prevBlocks_ = [null, null]; - } - - const blockA = this.getInputTargetBlock("A"); - const blockB = this.getInputTargetBlock("B"); - // Disconnect blocks that existed prior to this change if they don't match. - if ( - blockA && - blockB && - !this.workspace.connectionChecker.doTypeChecks( - blockA.outputConnection, - blockB.outputConnection, - ) - ) { - // Mismatch between two inputs. Revert the block connections, - // bumping away the newly connected block(s). - Blockly.Events.setGroup(e.group); - const prevA = this.prevBlocks_[0]; - if (prevA !== blockA) { - blockA.unplug(); - if (prevA && !prevA.isDisposed() && !prevA.isShadow()) { - // The shadow block is automatically replaced during unplug(). - this.getInput("A").connection.connect(prevA.outputConnection); - } - } - const prevB = this.prevBlocks_[1]; - if (prevB !== blockB) { - blockB.unplug(); - if (prevB && !prevB.isDisposed() && !prevB.isShadow()) { - // The shadow block is automatically replaced during unplug(). - this.getInput("B").connection.connect(prevB.outputConnection); - } - } - this.bumpNeighbours(); - Blockly.Events.setGroup(false); - } - this.prevBlocks_[0] = this.getInputTargetBlock("A"); - this.prevBlocks_[1] = this.getInputTargetBlock("B"); - }, -}; - -/** - * "logic_compare" extension function. Adds type left and right side type - * checking to "logic_compare" blocks. - * @this {Block} - * @readonly - */ -const LOGIC_COMPARE_EXTENSION = function () { - // Add onchange handler to ensure types are compatible. - this.mixin(LOGIC_COMPARE_ONCHANGE_MIXIN); -}; - /** * Tooltip text, keyed by block OP value. Used by logic_compare and * logic_operation blocks. @@ -741,5 +673,4 @@ export { CONTROLS_IF_MUTATOR_MIXIN, CONTROLS_IF_TOOLTIP_EXTENSION, WHILE_UNTIL_TOOLTIPS, - LOGIC_COMPARE_EXTENSION, }; diff --git a/blocks/logic.js b/blocks/logic.js index 1c4c2f2..3374eee 100644 --- a/blocks/logic.js +++ b/blocks/logic.js @@ -125,7 +125,7 @@ const blocks = [ output: "Boolean", style: "numbers_blocks", helpUrl: "%{BKY_LOGIC_COMPARE_HELPURL}", - extensions: ["logic_compare", "logic_op_tooltip"], + extensions: ["logic_op_tooltip"], }, // Block for logical operations: 'and', 'or'. { diff --git a/blocks/text.js b/blocks/text.js index ced919e..37b032e 100644 --- a/blocks/text.js +++ b/blocks/text.js @@ -18,7 +18,7 @@ const blocks = [ }, ], output: "String", - style: "text_blocks", + style: "numbers_blocks", helpUrl: "%{BKY_TEXT_TEXT_HELPURL}", tooltip: "%{BKY_TEXT_TEXT_TOOLTIP}", extensions: ["text_quotes", "parent_tooltip_when_inline"], @@ -50,44 +50,29 @@ const blocks = [ }, ], output: "String", - style: "text_blocks", + style: "numbers_blocks", helpUrl: "%{BKY_TEXT_TEXT_HELPURL}", tooltip: "%{BKY_TEXT_TEXT_TOOLTIP}", extensions: ["parent_tooltip_when_inline"], }, { type: "text_join", - message0: "", - output: "String", - style: "text_blocks", - helpUrl: "%{BKY_TEXT_JOIN_HELPURL}", - tooltip: "%{BKY_TEXT_JOIN_TOOLTIP}", - mutator: "text_join_mutator", - }, - { - type: "text_create_join_container", - message0: "%{BKY_TEXT_CREATE_JOIN_TITLE_JOIN} %1 %2", + message0: "%{BKY_TEXT_JOIN_TITLE_CREATEWITH}", args0: [ { - type: "input_dummy", + type: "input_value", + name: "ADD0", + check: ["String", "Number", "Boolean"], }, { - type: "input_statement", - name: "STACK", + type: "input_value", + name: "ADD1", + check: ["String", "Number", "Boolean"], }, ], - style: "text_blocks", - tooltip: "%{BKY_TEXT_CREATE_JOIN_TOOLTIP}", - enableContextMenu: false, - }, - { - type: "text_create_join_item", - message0: "%{BKY_TEXT_CREATE_JOIN_ITEM_TITLE_ITEM}", - previousStatement: null, - nextStatement: null, - style: "text_blocks", - tooltip: "%{BKY_TEXT_CREATE_JOIN_ITEM_TOOLTIP}", - enableContextMenu: false, + inputsInline: true, + output: "String", + style: "numbers_blocks", }, { type: "text_append", @@ -105,7 +90,7 @@ const blocks = [ ], previousStatement: null, nextStatement: null, - style: "text_blocks", + style: "numbers_blocks", extensions: ["text_append_tooltip"], }, { @@ -115,11 +100,11 @@ const blocks = [ { type: "input_value", name: "VALUE", - check: ["String", "Array"], + check: ["String"], }, ], output: "Number", - style: "text_blocks", + style: "numbers_blocks", tooltip: "%{BKY_TEXT_LENGTH_TOOLTIP}", helpUrl: "%{BKY_TEXT_LENGTH_HELPURL}", }, @@ -134,10 +119,29 @@ const blocks = [ }, ], output: "Boolean", - style: "text_blocks", + style: "numbers_blocks", tooltip: "%{BKY_TEXT_ISEMPTY_TOOLTIP}", helpUrl: "%{BKY_TEXT_ISEMPTY_HELPURL}", }, + { + type: "text_includes", + message0: "%{BKY_TEXT_INCLUDES_TITLE}", + args0: [ + { + type: "input_value", + name: "VALUE", + check: ["String"], + }, + { + type: "input_value", + name: "CHECK", + check: ["String"], + }, + ], + inputsInline: true, + output: "Boolean", + style: "numbers_blocks", + }, { type: "text_indexOf", message0: "%{BKY_TEXT_INDEXOF_TITLE}", @@ -162,7 +166,7 @@ const blocks = [ }, ], output: "Number", - style: "text_blocks", + style: "numbers_blocks", helpUrl: "%{BKY_TEXT_INDEXOF_HELPURL}", inputsInline: true, extensions: ["text_indexOf_tooltip"], @@ -173,26 +177,19 @@ const blocks = [ args0: [ { type: "input_value", - name: "VALUE", - check: "String", + name: "AT", + check: "Number", }, { - type: "field_dropdown", - name: "WHERE", - options: [ - ["%{BKY_TEXT_CHARAT_FROM_START}", "FROM_START"], - ["%{BKY_TEXT_CHARAT_FROM_END}", "FROM_END"], - ["%{BKY_TEXT_CHARAT_FIRST}", "FIRST"], - ["%{BKY_TEXT_CHARAT_LAST}", "LAST"], - ["%{BKY_TEXT_CHARAT_RANDOM}", "RANDOM"], - ], + type: "input_value", + name: "VALUE", + check: "String", }, ], output: "String", - style: "text_blocks", + style: "numbers_blocks", helpUrl: "%{BKY_TEXT_CHARAT_HELPURL}", inputsInline: true, - mutator: "text_charAt_mutator", }, ]; @@ -579,166 +576,6 @@ blocks["text_reverse"] = { }, }; -/** - * Mixin for mutator functions in the 'text_join_mutator' extension. - * @mixin - * @augments Block - * @package - */ -const TEXT_JOIN_MUTATOR_MIXIN = { - /** - * Create XML to represent number of text inputs. - * Backwards compatible serialization implementation. - * @return {!Element} XML storage element. - * @this {Block} - */ - mutationToDom: function () { - const container = xmlUtils.createElement("mutation"); - container.setAttribute("items", this.itemCount_); - return container; - }, - /** - * Parse XML to restore the text inputs. - * Backwards compatible serialization implementation. - * @param {!Element} xmlElement XML storage element. - * @this {Block} - */ - domToMutation: function (xmlElement) { - this.itemCount_ = parseInt(xmlElement.getAttribute("items"), 10); - this.updateShape_(); - }, - /** - * Returns the state of this block as a JSON serializable object. - * @return {{itemCount: number}} The state of this block, ie the item count. - */ - saveExtraState: function () { - return { - itemCount: this.itemCount_, - }; - }, - /** - * Applies the given state to this block. - * @param {*} state The state to apply to this block, ie the item count. - */ - loadExtraState: function (state) { - this.itemCount_ = state["itemCount"]; - this.updateShape_(); - }, - /** - * Populate the mutator's dialog with this block's components. - * @param {!Workspace} workspace Mutator's workspace. - * @return {!Block} Root block in mutator. - * @this {Block} - */ - decompose: function (workspace) { - const containerBlock = workspace.newBlock("text_create_join_container"); - containerBlock.initSvg(); - let connection = containerBlock.getInput("STACK").connection; - for (let i = 0; i < this.itemCount_; i++) { - const itemBlock = workspace.newBlock("text_create_join_item"); - itemBlock.initSvg(); - connection.connect(itemBlock.previousConnection); - connection = itemBlock.nextConnection; - } - return containerBlock; - }, - /** - * Reconfigure this block based on the mutator dialog's components. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} - */ - compose: function (containerBlock) { - let itemBlock = containerBlock.getInputTargetBlock("STACK"); - // Count number of inputs. - const connections = []; - while (itemBlock) { - if (itemBlock.isInsertionMarker()) { - itemBlock = itemBlock.getNextBlock(); - continue; - } - connections.push(itemBlock.valueConnection_); - itemBlock = itemBlock.getNextBlock(); - } - // Disconnect any children that don't belong. - for (let i = 0; i < this.itemCount_; i++) { - const 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 (let i = 0; i < this.itemCount_; i++) { - connections[i].reconnect(this, "ADD" + i); - } - }, - /** - * Store pointers to any connected child blocks. - * @param {!Block} containerBlock Root block in mutator. - * @this {Block} - */ - saveConnections: function (containerBlock) { - let itemBlock = containerBlock.getInputTargetBlock("STACK"); - let i = 0; - while (itemBlock) { - if (itemBlock.isInsertionMarker()) { - itemBlock = itemBlock.getNextBlock(); - continue; - } - const input = this.getInput("ADD" + i); - itemBlock.valueConnection_ = input && input.connection.targetConnection; - itemBlock = itemBlock.getNextBlock(); - i++; - } - }, - /** - * Modify this block to have the correct number of inputs. - * @private - * @this {Block} - */ - updateShape_: function () { - if (this.itemCount_ && this.getInput("EMPTY")) { - this.removeInput("EMPTY"); - } else if (!this.itemCount_ && !this.getInput("EMPTY")) { - this.appendDummyInput("EMPTY") - .appendField(this.newQuote_(true)) - .appendField(this.newQuote_(false)); - } - // Add new inputs. - for (let i = 0; i < this.itemCount_; i++) { - if (!this.getInput("ADD" + i)) { - const input = this.appendValueInput("ADD" + i).setAlign( - Blockly.inputs.Align.RIGHT, - ); - if (i === 0) { - input.appendField(Msg["TEXT_JOIN_TITLE_CREATEWITH"]); - } - } - } - // Remove deleted inputs. - for (let i = this.itemCount_; this.getInput("ADD" + i); i++) { - this.removeInput("ADD" + i); - } - }, -}; - -/** - * Performs final setup of a text_join block. - * @this {Block} - */ -const TEXT_JOIN_EXTENSION = function () { - // Add the quote mixin for the itemCount_ = 0 case. - this.mixin(QUOTE_IMAGE_MIXIN); - // Initialize the mutator values. - this.itemCount_ = 2; - this.updateShape_(); - // Configure the mutator UI. - this.setMutator( - new Blockly.icons.MutatorIcon(["text_create_join_item"], this), - ); -}; - /** * Update the tooltip of 'text_append' block to reference the variable. * @this {Block} diff --git a/generators/arduino/all.js b/generators/arduino/all.js index aef9199..81862b0 100644 --- a/generators/arduino/all.js +++ b/generators/arduino/all.js @@ -98,6 +98,18 @@ Arduino.ORDER_OVERRIDES = [ */ Arduino.DEF_FUNC_NAME = Arduino.FUNCTION_NAME_PLACEHOLDER_; +const TYPES = { + Number: "double", + Boolean: "boolean", + String: "String", +}; + +const DEFAULTS = { + Number: "0", + Boolean: "false", + String: '""', +}; + /** * Initialises the database of global definitions, the setup function, function * names, and variable names. @@ -140,19 +152,59 @@ Arduino.init = function (workspace) { // Add user Blockly.Variables, but only ones that are being used. const variables = Blockly.Variables.allUsedVarModels(workspace); + const variableSetters = workspace.getBlocksByType("variables_set"); + const variableGetters = workspace.getBlocksByType("variables_get"); for (let i = 0; i < variables.length; i++) { - defvars.push( - this.nameDB_.getName( - variables[i].getId(), - Blockly.Names.NameType.VARIABLE, - ), + const setters = variableSetters.filter( + (block) => block.getFieldValue("VAR") === variables[i].getId(), ); + const types = setters.map((block) => { + const output = block.getChildren(true)[0]; + return { + block, + type: + output && output.outputConnection + ? output.outputConnection.getCheck()[0] + : undefined, + }; + }); + // check for mismatch + if ( + types.some(({ type }) => type !== types[0].type && type !== undefined) + ) { + types.forEach(({ block }) => { + block.setWarningText( + `Variable has conflicting types: ${types + .map(({ type }) => type) + .join(", ")}`, + ); + }); + } else { + types.forEach(({ block }) => { + block.setWarningText(null); + }); + } + + const type = types[0].type || "Number"; + variableGetters.forEach((block) => { + if (block.getFieldValue("VAR") === variables[i].getId()) { + block.outputConnection.setCheck(type); + } + }); + + const arduinoType = TYPES[type]; + const defaultValue = DEFAULTS[type]; + const name = this.nameDB_.getName( + variables[i].getId(), + Blockly.Names.NameType.VARIABLE, + ); + + defvars.push(`${arduinoType} ${name} = ${defaultValue}`); } // Declare all of the variables. if (defvars.length) { - this.definitions_["variables"] = - "double " + defvars.join(" = 0, ") + " = 0;\n"; + this.definitions_["variables"] = defvars.join(";\n") + ";\n"; } // Create a dictionary of definitions to be printed at the top of the sketch diff --git a/generators/arduino/text.js b/generators/arduino/text.js index 8186923..f8c04c8 100644 --- a/generators/arduino/text.js +++ b/generators/arduino/text.js @@ -9,6 +9,37 @@ function getCodeGenerators(Arduino) { var code = Arduino.quote_(block.getFieldValue("TEXT")); return [code, Arduino.ORDER_ATOMIC]; }; + + Arduino.forBlock["text_join"] = function (block) { + const ADD0 = Arduino.valueToCode(block, "ADD0", Arduino.ORDER_NONE); + const ADD1 = Arduino.valueToCode(block, "ADD1", Arduino.ORDER_NONE); + const code = `String(${ADD0}) + String(${ADD1})`; + + return [code, Arduino.ORDER_ATOMIC]; + }; + + Arduino.forBlock["text_charAt"] = function (block) { + const at = Arduino.valueToCode(block, "AT", Arduino.ORDER_NONE); + const value = Arduino.valueToCode(block, "VALUE", Arduino.ORDER_NONE); + const code = `String(${value}[${at}])`; + + return [code, Arduino.ORDER_ATOMIC]; + }; + + Arduino.forBlock["text_length"] = function (block) { + const value = Arduino.valueToCode(block, "VALUE", Arduino.ORDER_NONE); + const code = `String(${value}).length()`; + + return [code, Arduino.ORDER_ATOMIC]; + }; + + Arduino.forBlock["text_includes"] = function (block) { + const value = Arduino.valueToCode(block, "VALUE", Arduino.ORDER_NONE); + const check = Arduino.valueToCode(block, "CHECK", Arduino.ORDER_NONE); + const code = `String(${value}).indexOf(${check}) != -1`; + + return [code, Arduino.ORDER_ATOMIC]; + }; } export default getCodeGenerators; diff --git a/msg/js/en.js b/msg/js/en.js index 32cc4bd..80c7b30 100644 --- a/msg/js/en.js +++ b/msg/js/en.js @@ -140,7 +140,7 @@ Blockly.Msg["LEAPHY_MOTOR_RIGHT"] = "Right"; Blockly.Msg["LEAPHY_MOTOR_RIGHT_DROPDOWN"] = "Motor_R"; Blockly.Msg["LEAPHY_MOTOR_SPEED"] = "Speed"; Blockly.Msg["LEAPHY_MOTOR_TYPE"] = "Type"; -Blockly.Msg["LEAPHY_NUMBERS_CATEGORY"] = "Numbers"; +Blockly.Msg["LEAPHY_NUMBERS_CATEGORY"] = "Operators"; Blockly.Msg["LEAPHY_ORIGINAL_CATEGORY"] = "Leaphy Original"; Blockly.Msg["LEAPHY_READ_HAND"] = "Read Hand sensor"; Blockly.Msg["LEAPHY_READ_STOMACH"] = "Read Belly sensor"; @@ -411,7 +411,7 @@ Blockly.Msg["TEXT_CHARAT_HELPURL"] = "https://github.com/google/blockly/wiki/Tex Blockly.Msg["TEXT_CHARAT_LAST"] = "get last letter"; Blockly.Msg["TEXT_CHARAT_RANDOM"] = "get random letter"; Blockly.Msg["TEXT_CHARAT_TAIL"] = ""; -Blockly.Msg["TEXT_CHARAT_TITLE"] = "in text %1 %2"; +Blockly.Msg["TEXT_CHARAT_TITLE"] = "letter %1 of %2"; Blockly.Msg["TEXT_CHARAT_TOOLTIP"] = "Returns the letter at the specified position."; Blockly.Msg["TEXT_COUNT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#counting-substrings"; Blockly.Msg["TEXT_COUNT_MESSAGE0"] = "count %1 in %2"; @@ -438,11 +438,12 @@ Blockly.Msg["TEXT_ISEMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Te Blockly.Msg["TEXT_ISEMPTY_TITLE"] = "%1 is empty"; Blockly.Msg["TEXT_ISEMPTY_TOOLTIP"] = "Returns true if the provided text is empty."; Blockly.Msg["TEXT_JOIN_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-creation"; -Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"] = "create text with"; +Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"] = "join %1 %2"; Blockly.Msg["TEXT_JOIN_TOOLTIP"] = "Create a piece of text by joining together any number of items."; Blockly.Msg["TEXT_LENGTH_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-modification"; Blockly.Msg["TEXT_LENGTH_TITLE"] = "length of %1"; Blockly.Msg["TEXT_LENGTH_TOOLTIP"] = "Returns the number of letters (including spaces) in the provided text."; +Blockly.Msg["TEXT_INCLUDES_TITLE"] = "%1 contains %2 ?"; Blockly.Msg["TEXT_PRINT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#printing-text"; Blockly.Msg["TEXT_PRINT_TITLE"] = "print %1"; Blockly.Msg["TEXT_PRINT_TOOLTIP"] = "Print the specified text, number or other value."; diff --git a/msg/js/nl.js b/msg/js/nl.js index 33e0857..917ed2c 100644 --- a/msg/js/nl.js +++ b/msg/js/nl.js @@ -138,7 +138,7 @@ Blockly.Msg["LEAPHY_MOTOR_RIGHT"] = "Rechts"; Blockly.Msg["LEAPHY_MOTOR_RIGHT_DROPDOWN"] = "Motor_R"; // untranslated Blockly.Msg["LEAPHY_MOTOR_SPEED"] = "Snelheid"; Blockly.Msg["LEAPHY_MOTOR_TYPE"] = "Zet"; -Blockly.Msg["LEAPHY_NUMBERS_CATEGORY"] = "Getal blokken"; +Blockly.Msg["LEAPHY_NUMBERS_CATEGORY"] = "Functies"; Blockly.Msg["LEAPHY_ORIGINAL_CATEGORY"] = "Leaphy Original"; // untranslated Blockly.Msg["LEAPHY_READ_HAND"] = "Lees handsensor"; Blockly.Msg["LEAPHY_READ_STOMACH"] = "Lees buiksensor"; @@ -409,7 +409,7 @@ Blockly.Msg["TEXT_CHARAT_HELPURL"] = "https://github.com/google/blockly/wiki/Tex Blockly.Msg["TEXT_CHARAT_LAST"] = "haal laatste letter op"; Blockly.Msg["TEXT_CHARAT_RANDOM"] = "haal willekeurige letter op"; Blockly.Msg["TEXT_CHARAT_TAIL"] = ""; // untranslated -Blockly.Msg["TEXT_CHARAT_TITLE"] = "in tekst %1 %2"; +Blockly.Msg["TEXT_CHARAT_TITLE"] = "letter %1 van %2"; Blockly.Msg["TEXT_CHARAT_TOOLTIP"] = "Geeft de letter op de opgegeven positie terug."; Blockly.Msg["TEXT_COUNT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#counting-substrings"; // untranslated Blockly.Msg["TEXT_COUNT_MESSAGE0"] = "%1 in %2 tellen"; @@ -436,11 +436,12 @@ Blockly.Msg["TEXT_ISEMPTY_HELPURL"] = "https://github.com/google/blockly/wiki/Te Blockly.Msg["TEXT_ISEMPTY_TITLE"] = "%1 is leeg"; Blockly.Msg["TEXT_ISEMPTY_TOOLTIP"] = "Geeft \"waar\" terug, als de opgegeven tekst leeg is."; Blockly.Msg["TEXT_JOIN_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-creation"; // untranslated -Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"] = "maak tekst met"; +Blockly.Msg["TEXT_JOIN_TITLE_CREATEWITH"] = "voeg %1 en %2 samen"; Blockly.Msg["TEXT_JOIN_TOOLTIP"] = "Maakt een stuk tekst door één of meer items samen te voegen."; Blockly.Msg["TEXT_LENGTH_HELPURL"] = "https://github.com/google/blockly/wiki/Text#text-modification"; // untranslated Blockly.Msg["TEXT_LENGTH_TITLE"] = "lengte van %1"; Blockly.Msg["TEXT_LENGTH_TOOLTIP"] = "Geeft het aantal tekens terug (inclusief spaties) in de opgegeven tekst."; +Blockly.Msg["TEXT_INCLUDES_TITLE"] = "%1 bevat %2 ?"; Blockly.Msg["TEXT_PRINT_HELPURL"] = "https://github.com/google/blockly/wiki/Text#printing-text"; // untranslated Blockly.Msg["TEXT_PRINT_TITLE"] = "tekst weergeven: %1"; Blockly.Msg["TEXT_PRINT_TOOLTIP"] = "Drukt de opgegeven tekst, getal of een andere waarde af."; diff --git a/msg/messages.js b/msg/messages.js index 0755ee3..fdc5bc7 100644 --- a/msg/messages.js +++ b/msg/messages.js @@ -1840,3 +1840,7 @@ Blockly.Msg.COPY_ALLBLOCKS_TO_BACKPACK = 'Copy All Blocks to Backpack'; /** @type {string} */ /// copy all from backpack - This copies all blocks from the backpack to the workspace Blockly.Msg.COPY_ALL_FROM_BACKPACK = 'Copy All Blocks from Backpack'; + +/** @type {string} */ +/// text includes - This is used to check if a string contains another string +Blockly.Msg.TEXT_INCLUDES_TITLE = '%1 contains %2 ?'; diff --git a/package.json b/package.json index ddd445a..40154c9 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "msg", "theme" ], - "version": "1.8.1", + "version": "1.9.0", "description": "Leaphy custom Blockly blocks and arduino code generator", "name": "@leaphy-robotics/leaphy-blocks" }