From 00df97a59dc460c16fa7e9a5fdfaef5002ed71d8 Mon Sep 17 00:00:00 2001 From: alschmiedt Date: Fri, 29 May 2020 13:22:25 -0700 Subject: [PATCH] Add plugin options (#3922) * Adds a plugin through options * Fix requires * Fix pr comments --- core/options.js | 8 ++++++++ core/registry.js | 33 +++++++++++++++++++++++++++++++++ core/toolbox.js | 4 ++++ core/workspace_svg.js | 11 +++++++---- tests/mocha/registry_test.js | 36 ++++++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) diff --git a/core/options.js b/core/options.js index 6a8853e49..a9e76ff8c 100644 --- a/core/options.js +++ b/core/options.js @@ -110,6 +110,8 @@ Blockly.Options = function(options) { var renderer = options['renderer'] || 'geras'; + var plugins = options['plugins'] || {}; + /** @type {boolean} */ this.RTL = rtl; /** @type {boolean} */ @@ -174,6 +176,12 @@ Blockly.Options = function(options) { * @type {Blockly.Workspace} */ this.parentWorkspace = options['parentWorkspace']; + + /** + * Map of plugin type to name of registered plugin or plugin class. + * @type {!Object.} + */ + this.plugins = plugins; }; /** diff --git a/core/registry.js b/core/registry.js index cda739079..b5d533d25 100644 --- a/core/registry.js +++ b/core/registry.js @@ -13,6 +13,10 @@ goog.provide('Blockly.registry'); +goog.requireType('Blockly.blockRendering.Renderer'); +goog.requireType('Blockly.Field'); +goog.requireType('Blockly.IToolbox'); + /** * A map of maps. With the keys being the type and name of the class we are @@ -23,6 +27,12 @@ goog.provide('Blockly.registry'); */ Blockly.registry.typeMap_ = {}; +/** + * The string used to register the default class for a type of plugin. + * @type {string} + */ +Blockly.registry.DEFAULT = 'default'; + /** * A name with the type of the element stored in the generic. * @param {string} name The name of the registry type. @@ -49,6 +59,9 @@ Blockly.registry.Type.RENDERER = new Blockly.registry.Type('renderer'); /** @type {!Blockly.registry.Type} */ Blockly.registry.Type.FIELD = new Blockly.registry.Type('field'); +/** @type {!Blockly.registry.Type} */ +Blockly.registry.Type.TOOLBOX = new Blockly.registry.Type('toolbox'); + /** * Registers a class based on a type and name. * @param {string|Blockly.registry.Type} type The type of the plugin. @@ -152,3 +165,23 @@ Blockly.registry.getClass = function(type, name) { } return typeRegistry[name]; }; + +/** + * Gets the class name or the class from Blockly options for the given type. + * This is used for plugins that override a built in feature. (Ex: Toolbox) + * @param {Blockly.registry.Type} type The type of the plugin. + * @param {!Blockly.Options} options The option object to check for the given + * plugin. + * @return {?function(new:T, ...?)} The class for the plugin. + * @template T + */ +Blockly.registry.getClassFromOptions = function(type, options) { + var typeName = type.toString(); + var plugin = options.plugins[typeName] || Blockly.registry.DEFAULT; + + // If the user passed in a plugin class instead of a registered plugin name. + if (typeof plugin == 'function') { + return plugin; + } + return Blockly.registry.getClass(type, plugin); +}; diff --git a/core/toolbox.js b/core/toolbox.js index 790be801d..6d8fb86f6 100644 --- a/core/toolbox.js +++ b/core/toolbox.js @@ -16,6 +16,7 @@ goog.require('Blockly.Css'); goog.require('Blockly.Events'); goog.require('Blockly.Events.Ui'); goog.require('Blockly.navigation'); +goog.require('Blockly.registry'); goog.require('Blockly.Touch'); goog.require('Blockly.tree.TreeControl'); goog.require('Blockly.tree.TreeNode'); @@ -917,3 +918,6 @@ Blockly.Css.register([ '}' /* eslint-enable indent */ ]); + +Blockly.registry.register(Blockly.registry.Type.TOOLBOX, + Blockly.registry.DEFAULT, Blockly.Toolbox); diff --git a/core/workspace_svg.js b/core/workspace_svg.js index bff6581ed..e0e8a0450 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -24,6 +24,7 @@ goog.require('Blockly.MarkerManager'); goog.require('Blockly.Msg'); goog.require('Blockly.navigation'); goog.require('Blockly.Options'); +goog.require('Blockly.registry'); goog.require('Blockly.ThemeManager'); goog.require('Blockly.Themes.Classic'); goog.require('Blockly.TouchGesture'); @@ -341,7 +342,7 @@ Blockly.WorkspaceSvg.prototype.flyout_ = null; /** * Category-based toolbox providing blocks which may be dragged into this * workspace. - * @type {Blockly.Toolbox} + * @type {Blockly.IToolbox} * @private */ Blockly.WorkspaceSvg.prototype.toolbox_ = null; @@ -749,7 +750,9 @@ Blockly.WorkspaceSvg.prototype.createDom = function(opt_backgroundClass) { if (!Blockly.Toolbox) { throw Error('Missing require for Blockly.Toolbox'); } - this.toolbox_ = new Blockly.Toolbox(this); + var ToolboxClass = Blockly.registry.getClassFromOptions( + Blockly.registry.Type.TOOLBOX, this.options); + this.toolbox_ = new ToolboxClass(this); } if (this.grid_) { this.grid_.update(this.scale); @@ -948,7 +951,7 @@ Blockly.WorkspaceSvg.prototype.getFlyout = function(opt_own) { /** * Getter for the toolbox associated with this workspace, if one exists. - * @return {Blockly.Toolbox} The toolbox on this workspace. + * @return {Blockly.IToolbox} The toolbox on this workspace. * @package */ Blockly.WorkspaceSvg.prototype.getToolbox = function() { @@ -2232,7 +2235,7 @@ Blockly.WorkspaceSvg.prototype.scroll = function(x, y) { /** * Get the dimensions of the given workspace component, in pixels. - * @param {Blockly.Toolbox|Blockly.Flyout} elem The element to get the + * @param {Blockly.IToolbox|Blockly.Flyout} elem The element to get the * dimensions of, or null. It should be a toolbox or flyout, and should * implement getWidth() and getHeight(). * @return {!Blockly.utils.Size} An object containing width and height diff --git a/tests/mocha/registry_test.js b/tests/mocha/registry_test.js index b3f2d8a4e..1076b595e 100644 --- a/tests/mocha/registry_test.js +++ b/tests/mocha/registry_test.js @@ -48,4 +48,40 @@ suite('Registry', function() { }, 'Can not register a null value'); }); }); + suite('getClassFromOptions', function() { + setup(function() { + this.defaultClass = function() {}; + this.defaultClass.prototype.testMethod = function() { + return 'default'; + }; + this.options = { + 'plugins': { + 'test' : 'test_name' + } + }; + Blockly.registry.typeMap_['test'] = { + 'test_name': TestClass, + 'default': this.defaultClass + }; + }); + test('Simple - Plugin name given', function() { + var testClass = Blockly.registry.getClassFromOptions('test', this.options); + chai.assert.instanceOf(new testClass(), TestClass); + }); + test('Simple - Plugin class given', function() { + this.options.plugins['test'] = TestClass; + var testClass = Blockly.registry.getClassFromOptions('test', this.options); + chai.assert.instanceOf(new testClass(), TestClass); + }); + test('No Plugin Name Given', function() { + delete this.options['plugins']['test']; + var testClass = Blockly.registry.getClassFromOptions('test', this.options); + chai.assert.instanceOf(new testClass(), this.defaultClass); + }); + test('Incorrect Plugin Name', function() { + this.options['plugins']['test'] = 'random'; + var testClass = Blockly.registry.getClassFromOptions('test', this.options); + chai.assert.isNull(testClass); + }); + }); });