diff --git a/core/field.js b/core/field.js index 961f07451..6e7e76883 100644 --- a/core/field.js +++ b/core/field.js @@ -27,6 +27,7 @@ goog.requireType('Blockly.blockRendering.ConstantProvider'); goog.requireType('Blockly.IASTNodeLocationSvg'); goog.requireType('Blockly.IASTNodeLocationWithBlock'); goog.requireType('Blockly.IBlocklyActionable'); +goog.requireType('Blockly.IRegistrable'); /** @@ -42,6 +43,7 @@ goog.requireType('Blockly.IBlocklyActionable'); * @implements {Blockly.IASTNodeLocationSvg} * @implements {Blockly.IASTNodeLocationWithBlock} * @implements {Blockly.IBlocklyActionable} + * @implements {Blockly.IRegistrable} */ Blockly.Field = function(value, opt_validator, opt_config) { /** diff --git a/core/flyout_base.js b/core/flyout_base.js index c38fc5ef5..d2405984a 100644 --- a/core/flyout_base.js +++ b/core/flyout_base.js @@ -30,6 +30,7 @@ goog.require('Blockly.WorkspaceSvg'); goog.require('Blockly.Xml'); goog.requireType('Blockly.IBlocklyActionable'); +goog.requireType('Blockly.IDeleteArea'); goog.requireType('Blockly.utils.Metrics'); @@ -40,6 +41,7 @@ goog.requireType('Blockly.utils.Metrics'); * @constructor * @abstract * @implements {Blockly.IBlocklyActionable} + * @implements {Blockly.IDeleteArea} */ Blockly.Flyout = function(workspaceOptions) { workspaceOptions.getMetrics = diff --git a/core/interfaces/i_deletearea.js b/core/interfaces/i_deletearea.js new file mode 100644 index 000000000..3ca0f083d --- /dev/null +++ b/core/interfaces/i_deletearea.js @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The interface for a component that can delete a block that is + * dropped on top of it. + * @author aschmiedt@google.com (Abby Schmiedt) + */ + +'use strict'; + +goog.provide('Blockly.IDeleteArea'); + + +/** + * Interface for a component that can delete a block that is dropped on top of it. + * @interface + */ +Blockly.IDeleteArea = function() {}; + +/** + * Return the deletion rectangle. + * @return {Blockly.utils.Rect} Rectangle in which to delete. + */ +Blockly.IDeleteArea.prototype.getClientRect; diff --git a/core/interfaces/i_registrable.js b/core/interfaces/i_registrable.js new file mode 100644 index 000000000..e2803182b --- /dev/null +++ b/core/interfaces/i_registrable.js @@ -0,0 +1,19 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The interface for a Blockly component that can be registered. + * (Ex. Toolbox, Fields, Renderers) + * @author aschmiedt@google.com (Abby Schmiedt) + */ + +'use strict'; + +goog.provide('Blockly.IRegistrable'); + + +/** @interface */ +Blockly.IRegistrable = function() {}; diff --git a/core/interfaces/i_toolbox.js b/core/interfaces/i_toolbox.js new file mode 100644 index 000000000..96164f062 --- /dev/null +++ b/core/interfaces/i_toolbox.js @@ -0,0 +1,111 @@ +/** + * @license + * Copyright 2020 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The interface for a toolbox. + * @author aschmiedt@google.com (Abby Schmiedt) + */ + +'use strict'; + +goog.provide('Blockly.IToolbox'); + +goog.requireType('Blockly.IRegistrable'); + + +/** + * Interface for a toolbox. + * @extends {Blockly.IRegistrable} + * @interface + */ +Blockly.IToolbox = function() {}; + +/** + * Initializes the toolbox. + * @return {void} + */ +Blockly.IToolbox.prototype.init; + +/** + * Fill the toolbox with categories and blocks. + * @param {Array.} toolboxDef Array holding objects + * containing information on the contents of the toolbox. + */ +Blockly.IToolbox.prototype.render; + +/** + * Dispose of this toolbox. + * @return {void} + */ +Blockly.IToolbox.prototype.dispose; + +/** + * Get the width of the toolbox. + * @return {number} The width of the toolbox. + */ +Blockly.IToolbox.prototype.getWidth; + +/** + * Get the height of the toolbox. + * @return {number} The width of the toolbox. + */ +Blockly.IToolbox.prototype.getHeight; + +/** + * Get the toolbox flyout. + * @return {Blockly.Flyout} The toolbox flyout. + */ +Blockly.IToolbox.prototype.getFlyout; + +/** + * Move the toolbox to the edge. + * @return {void} + */ +Blockly.IToolbox.prototype.position; + +/** + * Unhighlight any previously specified option. + * @return {void} + */ +Blockly.IToolbox.prototype.clearSelection; + +/** + * Updates the category colours and background colour of selected categories. + * @return {void} + */ +Blockly.IToolbox.prototype.updateColourFromTheme; + +/** + * Adds a style on the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to add. + */ +Blockly.IToolbox.prototype.addStyle; + +/** + * Removes a style from the toolbox. Usually used to change the cursor. + * @param {string} style The name of the class to remove. + */ +Blockly.IToolbox.prototype.removeStyle; + +/** + * Update the flyout's contents without closing it. Should be used in response + * to a change in one of the dynamic categories, such as variables or + * procedures. + * @return {void} + */ +Blockly.IToolbox.prototype.refreshSelection; + +/** + * Toggles the visibility of the toolbox. + * @param {boolean} isVisible True if the toolbox should be visible. + */ +Blockly.IToolbox.prototype.setVisible; + +/** + * Select the first toolbox category if no category is selected. + * @return {void} + */ +Blockly.IToolbox.prototype.selectFirstCategory; diff --git a/core/keyboard_nav/navigation.js b/core/keyboard_nav/navigation.js index 6364c5556..d342a125c 100644 --- a/core/keyboard_nav/navigation.js +++ b/core/keyboard_nav/navigation.js @@ -855,7 +855,8 @@ Blockly.navigation.flyoutOnAction_ = function(action) { Blockly.navigation.toolboxOnAction_ = function(action) { var workspace = Blockly.navigation.getNavigationWorkspace(); var toolbox = workspace.getToolbox(); - var handled = toolbox ? toolbox.onBlocklyAction(action) : false; + var handled = toolbox && typeof toolbox.onBlocklyAction == 'function' ? + toolbox.onBlocklyAction(action) : false; if (handled) { return true; diff --git a/core/renderers/common/renderer.js b/core/renderers/common/renderer.js index 74b9f6f3d..05885d18c 100644 --- a/core/renderers/common/renderer.js +++ b/core/renderers/common/renderer.js @@ -21,6 +21,7 @@ goog.require('Blockly.blockRendering.RenderInfo'); goog.require('Blockly.InsertionMarkerManager'); goog.requireType('Blockly.blockRendering.Debug'); +goog.requireType('Blockly.IRegistrable'); /** @@ -28,6 +29,7 @@ goog.requireType('Blockly.blockRendering.Debug'); * @param {string} name The renderer name. * @package * @constructor + * @implements {Blockly.IRegistrable} */ Blockly.blockRendering.Renderer = function(name) { diff --git a/core/toolbox.js b/core/toolbox.js index 892eafc28..63a34d8a5 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -28,6 +28,8 @@ goog.require('Blockly.utils.Rect'); goog.require('Blockly.utils.toolbox'); goog.requireType('Blockly.IBlocklyActionable'); +goog.requireType('Blockly.IDeleteArea'); +goog.requireType('Blockly.IToolbox'); /** @@ -37,6 +39,8 @@ goog.requireType('Blockly.IBlocklyActionable'); * blocks. * @constructor * @implements {Blockly.IBlocklyActionable} + * @implements {Blockly.IDeleteArea} + * @implements {Blockly.IToolbox} */ Blockly.Toolbox = function(workspace) { /** @@ -206,7 +210,7 @@ Blockly.Toolbox.prototype.init = function() { this.config_['cssCollapsedFolderIcon'] = 'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr'); - this.renderTree(workspace.options.languageTree); + this.render(workspace.options.languageTree); }; /** @@ -215,7 +219,7 @@ Blockly.Toolbox.prototype.init = function() { * containing information on the contents of the toolbox. * @package */ -Blockly.Toolbox.prototype.renderTree = function(toolboxDef) { +Blockly.Toolbox.prototype.render = function(toolboxDef) { if (this.tree_) { this.tree_.dispose(); // Delete any existing content. this.lastCategory_ = null; @@ -506,6 +510,14 @@ Blockly.Toolbox.prototype.dispose = function() { this.lastCategory_ = null; }; +/** + * Toggles the visibility of the toolbox. + * @param {boolean} isVisible True if toolbox should be visible. + */ +Blockly.Toolbox.prototype.setVisible = function(isVisible) { + this.HtmlDiv.style.display = isVisible ? 'block' : 'none'; +}; + /** * Get the width of the toolbox. * @return {number} The width of the toolbox. diff --git a/core/trashcan.js b/core/trashcan.js index 5c45cf82b..107b9d602 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -17,11 +17,14 @@ goog.require('Blockly.utils.dom'); goog.require('Blockly.utils.Rect'); goog.require('Blockly.Xml'); +goog.requireType('Blockly.IDeleteArea'); + /** * Class for a trash can. * @param {!Blockly.WorkspaceSvg} workspace The workspace to sit in. * @constructor + * @implements {Blockly.IDeleteArea} */ Blockly.Trashcan = function(workspace) { /** diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 819ef2407..bff6581ed 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -1187,7 +1187,7 @@ Blockly.WorkspaceSvg.prototype.setVisible = function(isVisible) { this.getParentSvg().style.display = isVisible ? 'block' : 'none'; if (this.toolbox_) { // Currently does not support toolboxes in mutators. - this.toolbox_.HtmlDiv.style.display = isVisible ? 'block' : 'none'; + this.toolbox_.setVisible(isVisible); } if (isVisible) { var blocks = this.getAllBlocks(false); @@ -1455,7 +1455,7 @@ Blockly.WorkspaceSvg.prototype.recordDeleteAreas = function() { } if (this.flyout_) { this.deleteAreaToolbox_ = this.flyout_.getClientRect(); - } else if (this.toolbox_) { + } else if (this.toolbox_ && typeof this.toolbox_.getClientRect == 'function') { this.deleteAreaToolbox_ = this.toolbox_.getClientRect(); } else { this.deleteAreaToolbox_ = null; @@ -1849,7 +1849,7 @@ Blockly.WorkspaceSvg.prototype.updateToolbox = function(toolboxDef) { throw Error('Existing toolbox has no categories. Can\'t change mode.'); } this.options.languageTree = toolboxDef; - this.toolbox_.renderTree(toolboxDef); + this.toolbox_.render(toolboxDef); } else { if (!this.flyout_) { throw Error('Existing toolbox has categories. Can\'t change mode.'); diff --git a/tests/mocha/toolbox_test.js b/tests/mocha/toolbox_test.js index d4a9546e7..16d340229 100644 --- a/tests/mocha/toolbox_test.js +++ b/tests/mocha/toolbox_test.js @@ -53,7 +53,7 @@ suite('Toolbox', function() { }); }); - suite('renderTree', function() { + suite('render', function() { setup(function() { this.toolboxXml = Blockly.utils.toolbox.convertToolboxToJSON(this.toolboxXml); this.toolbox.selectFirstCategory(); @@ -62,7 +62,7 @@ suite('Toolbox', function() { this.toolbox.handleBeforeTreeSelected_(this.secondChild); }); test('Tree is created and set', function() { - this.toolbox.renderTree(this.toolboxXml); + this.toolbox.render(this.toolboxXml); chai.assert.isDefined(this.toolbox.tree_); }); test('Throws error if a toolbox has both blocks and categories at root level', function() { @@ -94,17 +94,17 @@ suite('Toolbox', function() { } ]; chai.assert.throws(function() { - toolbox.renderTree(badToolboxDef); + toolbox.render(badToolboxDef); }, 'Toolbox cannot have both blocks and categories in the root level.'); }); test('Select any open nodes', function() { - this.toolbox.renderTree(this.toolboxXml); + this.toolbox.render(this.toolboxXml); var selectedNode = this.toolbox.tree_.children_[0]; chai.assert.isTrue(selectedNode.selected_); }); test('Set the state for horizontal layout ', function() { this.toolbox.horizontalLayout_ = true; - this.toolbox.renderTree(this.toolboxXml); + this.toolbox.render(this.toolboxXml); var orientationAttribute = this.toolbox.tree_.getElement() .getAttribute('aria-orientation'); chai.assert.equal(orientationAttribute, 'horizontal'); @@ -136,7 +136,7 @@ suite('Toolbox', function() { ] } ]; - this.toolbox.renderTree(jsonDef); + this.toolbox.render(jsonDef); chai.assert.lengthOf(this.toolbox.tree_.children_, 1); }); });