diff --git a/block-lexical-variables/README.md b/block-lexical-variables/README.md index 5333e3f..3c8ff2a 100644 --- a/block-lexical-variables/README.md +++ b/block-lexical-variables/README.md @@ -71,6 +71,12 @@ and while hovering over the variable name: ![A picture of a global variable block with getter and setter blocks](readme-media/globalvar-with-flydown.png "Global variable with flydown") +**Block type: 'initialize_global' and 'global_declaration_entry'** - Provide a more structured way to initialize global variables by grouping multiple initializations together. + +![A picture of a global variable initialization block](readme-media/initialize-global-block.png "Initialize global variable") + +Note: It is recommended not to mix the two different ways of initializing global variables. + ## Variable/Parameter setters and getters ### Setter **Block type: 'lexical_variable_set'** - Note that despite the block type name, the diff --git a/block-lexical-variables/readme-media/initialize-global-block.png b/block-lexical-variables/readme-media/initialize-global-block.png new file mode 100644 index 0000000..a2ae7ec Binary files /dev/null and b/block-lexical-variables/readme-media/initialize-global-block.png differ diff --git a/block-lexical-variables/src/blocks/lexical-variables.js b/block-lexical-variables/src/blocks/lexical-variables.js index 747b79b..dfa6904 100644 --- a/block-lexical-variables/src/blocks/lexical-variables.js +++ b/block-lexical-variables/src/blocks/lexical-variables.js @@ -137,8 +137,120 @@ Blockly.Blocks['global_declaration'] = { }, }; +Blockly.Blocks['global_declaration_entry'] = { + category: 'Variables', + helpUrl: Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_HELPURL, + init: function() { + this.setStyle('variable_blocks'); + this.appendValueInput('VALUE') + .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TITLE_INIT) + .appendField(new FieldGlobalFlydown( + Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_NAME, + FieldFlydown.DISPLAY_BELOW), 'NAME') + .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TO); + this.setPreviousStatement(true, ['global_declaration_entry', 'initialize_global']); + this.setNextStatement(true, ['global_declaration_entry']); + this.setTooltip(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TOOLTIP); + this.setOnChange(this.checkPlacement_); + }, + getDeclaredVars: Blockly.Blocks.global_declaration.getDeclaredVars, + getGlobalNames: Blockly.Blocks.global_declaration.getGlobalNames, + renameVar: Blockly.Blocks.global_declaration.renameVar, + checkPlacement_: function() { + if (this.isInFlyout) return; + + const REASON = Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_WARNING; + const parent = this.getSurroundParent(); + + if (!parent || parent.type !== 'initialize_global') { + this.setWarningText(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_WARNING, 'global_declaration_entry'); + if (this.workspace.disableInvalidBlocks) { + this.setDisabledReason(true, REASON); + } + } else { + this.setWarningText(null, 'global_declaration_entry'); + if (this.workspace.disableInvalidBlocks) { + this.setDisabledReason(false, REASON); + } + } + } +}; + +Blockly.Blocks['initialize_global'] = { + category: 'Variables', + helpUrl: Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_HELPURL, + init: function () { + this.setStyle('variable_blocks'); + this.appendDummyInput() + .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TITLE_INIT) + this.appendStatementInput('DO') + .appendField(Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_TO_DO); + this.setTooltip(Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_TOOLTIP); + this.setOnChange(this.checkChildren_) + queueMicrotask(this.checkChildren_.bind(this)); + }, + getDeclaredVarFieldNames: function () { + return ['VAR']; + }, + getScopedInputName: function () { + return 'DO'; + }, + getGlobalNames: function (block) { + const names = [] + let childBlock = this.getInputTargetBlock('DO') + while (childBlock) { + if (childBlock.type === 'global_declaration_entry' && block !== childBlock) { + names.push(...childBlock.getGlobalNames()) + } + childBlock = childBlock.getNextBlock() + } + return names + }, + checkChildren_: function(event) { + if (this.isInFlyout) return; + if (event && event.type !== Blockly.Events.BLOCK_MOVE && event.type !== Blockly.Events.BLOCK_DRAG) return; + + const REASON = Blockly.Msg.LANG_VARIABLES_GLOBAL_DECLARATION_BLOCK_CHECK; + const inStack = new Set(); + + // Validate child blocks are global_declaration_entry types and mark them + let childBlock = this.getInputTargetBlock('DO'); + while (childBlock) { + inStack.add(childBlock.id); + if (childBlock.type !== 'global_declaration_entry') { + childBlock.setWarningText(REASON, 'initialize_global'); + if (this.workspace.disableInvalidBlocks) { + childBlock.setDisabledReason(true, REASON); + } + childBlock.__disabledByInitGlobal = true; + } else { + childBlock.setWarningText(null, 'initialize_global'); + if (this.workspace.disableInvalidBlocks) { + childBlock.setDisabledReason(false, REASON); + } + childBlock.__disabledByInitGlobal = false; + } + childBlock = childBlock.getNextBlock(); + } + + // If a block was moved OUT, clear our disable flag/state + if (event && event.blockId) { + let moved = this.workspace.getBlockById(event.blockId); + while (moved) { + if (moved && moved.__disabledByInitGlobal && !inStack.has(moved.id)) { + moved.setWarningText(null, 'initialize_global'); + if (this.workspace.disableInvalidBlocks) { + moved.setDisabledReason(false, REASON); + } + moved.__disabledByInitGlobal = false; + } + moved = moved.getNextBlock(); + } + } + } +} + Blockly.Blocks['simple_local_declaration_statement'] = { - // For each loop. category: 'Variables', helpUrl: Blockly.Msg.LANG_VARIABLES_LOCAL_DECLARATION_HELPURL, init: function () { diff --git a/block-lexical-variables/src/blocks/procedures.js b/block-lexical-variables/src/blocks/procedures.js index 765cad7..1819368 100644 --- a/block-lexical-variables/src/blocks/procedures.js +++ b/block-lexical-variables/src/blocks/procedures.js @@ -160,9 +160,9 @@ Blockly.Blocks['procedures_defnoreturn'] = { hash['arg_' + this.arguments_[x].toLowerCase()] = true; } if (badArg) { - this.setWarningText(Blockly.Msg['LANG_PROCEDURES_DEF_DUPLICATE_WARNING']); + this.setWarningText(Blockly.Msg['LANG_PROCEDURES_DEF_DUPLICATE_WARNING'], 'procedures_defnoreturn'); } else { - this.setWarningText(null); + this.setWarningText(null, 'procedures_defnoreturn'); } const procName = this.getFieldValue('NAME'); diff --git a/block-lexical-variables/src/fields/field_lexical_variable.js b/block-lexical-variables/src/fields/field_lexical_variable.js index 261715c..77714fe 100644 --- a/block-lexical-variables/src/fields/field_lexical_variable.js +++ b/block-lexical-variables/src/fields/field_lexical_variable.js @@ -164,8 +164,8 @@ FieldLexicalVariable.getGlobalNames = function(optExcludedBlock) { for (let i = 0; i < blocks.length; i++) { const block = blocks[i]; if ((block.getGlobalNames) && - (block != optExcludedBlock)) { - globals.push(...block.getGlobalNames()); + (block != optExcludedBlock) && block.isEnabled()) { + globals.push(...block.getGlobalNames(optExcludedBlock)); } } } diff --git a/block-lexical-variables/src/generators/lexical-variables.js b/block-lexical-variables/src/generators/lexical-variables.js index 7b72387..80caf2d 100644 --- a/block-lexical-variables/src/generators/lexical-variables.js +++ b/block-lexical-variables/src/generators/lexical-variables.js @@ -52,6 +52,13 @@ if (pkg) { return 'var ' + genBasicSetterCode(block, 'NAME', generator); }; + javascriptGenerator.forBlock['global_declaration_entry'] = javascriptGenerator.forBlock['global_declaration']; + + javascriptGenerator.forBlock['initialize_global'] = function (block, generator) { + // Global variable declaration + return generator.statementToCode(block, 'DO'); + }; + function generateDeclarations(block, generator) { let code = '{\n let '; for (let i = 0; block.getFieldValue('VAR' + i); i++) { diff --git a/block-lexical-variables/src/msg.js b/block-lexical-variables/src/msg.js index cdaa364..72e9d6f 100644 --- a/block-lexical-variables/src/msg.js +++ b/block-lexical-variables/src/msg.js @@ -148,3 +148,9 @@ Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_COLLAPSED_TEXT'] = 'do/result'; Blockly.Msg['LANG_CONTROLS_DO_THEN_RETURN_TITLE'] = 'do result'; Blockly.Msg['PROCEDURES_DEFNORETURN_PROCEDURE'] = Blockly.Msg['LANG_PROCEDURES_DEFNORETURN_PROCEDURE']; Blockly.Msg['PROCEDURES_DEFRETURN_PROCEDURE'] = Blockly.Msg['LANG_PROCEDURES_DEFRETURN_PROCEDURE']; +Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_WARNING'] = 'Global declarations must be inside of a initialize global block.' +Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_TITLE_INIT'] = 'initialize global variables'; +Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_TO_DO'] = 'to'; +Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_TOOLTIP'] = + 'Allows you to create global variables that are accessible everywhere'; +Blockly.Msg['LANG_VARIABLES_GLOBAL_DECLARATION_BLOCK_CHECK'] = 'Only global_declaration_entry blocks are allowed here.'; \ No newline at end of file diff --git a/block-lexical-variables/src/warningHandler.js b/block-lexical-variables/src/warningHandler.js index 38c9add..be6d585 100644 --- a/block-lexical-variables/src/warningHandler.js +++ b/block-lexical-variables/src/warningHandler.js @@ -74,17 +74,17 @@ export default class WarningHandler { } // remove the warning icon, if there is one - block.setWarningText(null); + block.setWarningText(null, 'warningHandler'); if (block.hasWarning) { block.hasWarning = false; } }; setError(block, message) { - block.setWarningText(message); + block.setWarningText(message, 'warningHandler'); } setWarning(block, message) { - block.setWarningText(message); + block.setWarningText(message, 'warningHandler'); } } diff --git a/block-lexical-variables/test/field_lexical_var.mocha.js b/block-lexical-variables/test/field_lexical_var.mocha.js index ce96e51..0429d44 100644 --- a/block-lexical-variables/test/field_lexical_var.mocha.js +++ b/block-lexical-variables/test/field_lexical_var.mocha.js @@ -39,6 +39,7 @@ import {NameSet} from "../src/nameSet"; suite ('FieldLexical', function() { setup(function() { this.workspace = new Blockly.Workspace(); + this.workspace.disableInvalidBlocks = true; Blockly.common.setMainWorkspace(this.workspace); this.createBlock = function (type) { @@ -90,6 +91,52 @@ suite ('FieldLexical', function() { const vars = FieldLexicalVariable.getGlobalNames(); chai.assert.sameOrderedMembers(vars, ['global', 'global2', 'global3']); }); + test('initialize globals mixed', function () { + const xml = Blockly.utils.xml.textToDom('' + + ' ' + + ' name' + + ' ' + + ' ' + + ' name2' + + ' ' + + ' ' + + ' ' + + ' ' + + ' a' + + ' ' + + ' ' + + ' b' + + ' ' + + ' ' + + ' ' + + ' ' + + ' ' + + ''); + Blockly.Xml.domToWorkspace(xml, this.workspace); + const vars = FieldLexicalVariable.getGlobalNames(); + chai.assert.sameOrderedMembers(vars, ['name', 'name2', 'a', 'b']); + }) + test('global_declaration_entry disabled if outside of initialize_global', async function () { + const xml = Blockly.utils.xml.textToDom('' + + ' ' + + ' ' + + ' ' + + ' a' + + ' ' + + ' ' + + ' ' + + ' ' + + ' b' + + ' ' + + ''); + Blockly.Xml.domToWorkspace(xml, this.workspace); + const block = this.workspace.getBlockById('b') + // Trigger placement check manually since it's only automatically called after BlockCreate + block.checkPlacement_(); + const vars = FieldLexicalVariable.getGlobalNames(); + chai.assert.sameOrderedMembers(vars, ['a']); + chai.assert.equal(block.isEnabled(), false) + }) }); suite('getLexicalNamesInScope', function() { setup(function() { diff --git a/block-lexical-variables/test/index.js b/block-lexical-variables/test/index.js index 95d4cd8..d0d93cb 100644 --- a/block-lexical-variables/test/index.js +++ b/block-lexical-variables/test/index.js @@ -52,6 +52,8 @@ document.addEventListener('DOMContentLoaded', function() { + +