No closure toolbox (#2819)

* Update the toolbox to not rely on closure and instead use the built in Blockly.tree component
This commit is contained in:
Sam El-Husseini
2019-08-14 13:48:21 -07:00
committed by GitHub
parent d662410694
commit f118d6de88
9 changed files with 1731 additions and 674 deletions

View File

@@ -1210,6 +1210,9 @@ Blockly.tree.BaseNode.prototype.getPreviousShownNode = function() {
}
var parent = this.getParent();
var tree = this.getTree();
if (parent == tree) {
return null;
}
// The root is the first node.
if (this == tree) {
return null;

View File

@@ -27,6 +27,7 @@
goog.provide('Blockly.inject');
goog.require('Blockly.BlockDragSurfaceSvg');
goog.require('Blockly.Component');
goog.require('Blockly.Css');
goog.require('Blockly.DropDownDiv');
goog.require('Blockly.Events');
@@ -96,8 +97,11 @@ Blockly.createDom_ = function(container, options) {
// out content in RTL mode. Therefore Blockly forces the use of LTR,
// then manually positions content in RTL as needed.
container.setAttribute('dir', 'LTR');
// TODO(samelh): Remove this once we no longer need goog.ui.Component
// Closure can be trusted to create HTML widgets with the proper direction.
goog.ui.Component.setDefaultRightToLeft(options.RTL);
// Set the default direction for Components to use.
Blockly.Component.setDefaultRightToLeft(options.RTL);
// Load CSS.
Blockly.Css.inject(options.hasCss, options.pathToMedia);

View File

@@ -41,7 +41,7 @@ Blockly.navigation.marker_ = null;
/**
* The current selected category if the toolbox is open or
* last selected category if focus is on a different element.
* @type {goog.ui.tree.BaseNode}
* @type {Blockly.tree.BaseNode}
* @private
*/
Blockly.navigation.currentCategory_ = null;
@@ -225,10 +225,10 @@ Blockly.navigation.outCategory = function() {
} else {
var parent = curCategory.getParent();
var tree = curCategory.getTree();
if (parent && (tree.getShowRootNode() || parent != tree)) {
if (parent && parent != tree) {
parent.select();
Blockly.navigation.currentCategory_ = /** @type {goog.ui.tree.BaseNode} */
Blockly.navigation.currentCategory_ = /** @type {Blockly.tree.BaseNode} */
(parent);
}
}

View File

@@ -31,19 +31,15 @@ goog.require('Blockly.Events.Ui');
goog.require('Blockly.Flyout');
goog.require('Blockly.HorizontalFlyout');
goog.require('Blockly.Touch');
goog.require('Blockly.tree.TreeControl');
goog.require('Blockly.tree.TreeNode');
goog.require('Blockly.utils');
goog.require('Blockly.utils.aria');
goog.require('Blockly.utils.colour');
goog.require('Blockly.utils.dom');
goog.require('Blockly.utils.Rect');
goog.require('Blockly.VerticalFlyout');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.html.SafeHtml');
goog.require('goog.ui.tree.BaseNode');
goog.require('goog.ui.tree.TreeControl');
goog.require('goog.ui.tree.TreeNode');
/**
* Class for a Toolbox.
@@ -141,7 +137,7 @@ Blockly.Toolbox.prototype.selectedOption_ = null;
/**
* The tree node most recently selected.
* @type {goog.ui.tree.BaseNode}
* @type {Blockly.tree.BaseNode}
* @private
*/
Blockly.Toolbox.prototype.lastCategory_ = null;
@@ -200,12 +196,11 @@ Blockly.Toolbox.prototype.init = function() {
this.config_['cleardotPath'] = workspace.options.pathToMedia + '1x1.gif';
this.config_['cssCollapsedFolderIcon'] =
'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr');
var tree = new Blockly.Toolbox.TreeControl(this, this.config_);
var tree = new Blockly.tree.TreeControl(this, this.config_);
this.tree_ = tree;
tree.setShowRootNode(false);
tree.setShowLines(false);
tree.setShowExpandIcons(false);
tree.setSelectedItem(null);
tree.onBeforeSelected(this.handleBeforeTreeSelected_);
tree.onAfterSelected(this.handleAfterTreeSelected_);
var openNode = this.populate_(workspace.options.languageTree);
tree.render(this.HtmlDiv);
if (openNode) {
@@ -213,6 +208,74 @@ Blockly.Toolbox.prototype.init = function() {
}
this.addColour_();
this.position();
// Trees have an implicit orientation of vertical, so we only need to set this
// when the toolbox is in horizontal mode.
if (this.horizontalLayout_) {
Blockly.utils.aria.setState(this.tree_.getElement(),
Blockly.utils.aria.State.ORIENTATION, 'horizontal');
}
};
/**
* Handle the before tree item selected action.
* @param {Blockly.tree.BaseNode} node The newly selected node.
* @returns {boolean} Whether or not to cancel selecting the node.
* @private
*/
Blockly.Toolbox.prototype.handleBeforeTreeSelected_ = function(node) {
if (node == this.tree_) {
return false;
}
if (this.lastCategory_) {
this.lastCategory_.getRowElement().style.backgroundColor = '';
}
if (node) {
var hexColour = node.hexColour || '#57e';
node.getRowElement().style.backgroundColor = hexColour;
// Add colours to child nodes which may have been collapsed and thus
// not rendered.
this.addColour_(node);
}
return true;
};
/**
* Handle the after tree item selected action.
* @param {Blockly.tree.BaseNode} oldNode The previously selected node.
* @param {Blockly.tree.BaseNode} newNode The newly selected node.
* @private
*/
Blockly.Toolbox.prototype.handleAfterTreeSelected_ = function(oldNode, newNode) {
if (newNode && newNode.blocks && newNode.blocks.length) {
this.flyout_.show(newNode.blocks);
// Scroll the flyout to the top if the category has changed.
if (this.lastCategory_ != newNode) {
this.flyout_.scrollToStart();
}
} else {
// Hide the flyout.
this.flyout_.hide();
}
if (oldNode != newNode && oldNode != this) {
var event = new Blockly.Events.Ui(null, 'category',
oldNode && oldNode.getText(), newNode && newNode.getText());
event.workspaceId = this.workspace_.id;
Blockly.Events.fire(event);
}
if (newNode) {
this.lastCategory_ = newNode;
}
};
/**
* Handle a node sized changed event.
* @private
*/
Blockly.Toolbox.prototype.handleNodeSizeChanged_ = function() {
// Even though the div hasn't changed size, the visible workspace
// surface of the workspace has, so we may need to reposition everything.
Blockly.svgResize(this.workspace_);
};
/**
@@ -301,8 +364,8 @@ Blockly.Toolbox.prototype.populate_ = function(newTree) {
/**
* Sync trees of the toolbox.
* @param {!Node} treeIn DOM tree of blocks.
* @param {!Blockly.Toolbox.TreeControl} treeOut The TreeContorol object built
* from treeIn.
* @param {!Blockly.tree.BaseNode} treeOut The TreeControl or TreeNode
* object built from treeIn.
* @param {string} pathToMedia The path to the Blockly media directory.
* @return {Node} Tree node to open at startup (or null).
* @private
@@ -322,6 +385,7 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
var categoryName = Blockly.utils.replaceMessageReferences(
childIn.getAttribute('name'));
var childOut = this.tree_.createNode(categoryName);
childOut.onSizeChanged(this.handleNodeSizeChanged_);
childOut.blocks = [];
treeOut.add(childOut);
var custom = childIn.getAttribute('custom');
@@ -385,7 +449,7 @@ Blockly.Toolbox.prototype.syncTrees_ = function(treeIn, treeOut, pathToMedia) {
* Sets the colour on the category.
* @param {number|string} colourValue HSV hue value (0 to 360), #RRGGBB string,
* or a message reference string pointing to one of those two values.
* @param {Blockly.Toolbox.TreeNode} childOut The child to set the hexColour on.
* @param {Blockly.tree.TreeNode} childOut The child to set the hexColour on.
* @param {string} categoryName Name of the toolbox category.
* @private
*/
@@ -420,7 +484,7 @@ Blockly.Toolbox.prototype.setColour_ = function(colourValue, childOut,
* Retrieves and sets the colour for the category using the style name.
* The category colour is set from the colour style attribute.
* @param {string} styleName Name of the style.
* @param {!Blockly.Toolbox.TreeNode} childOut The child to set the hexColour on.
* @param {!Blockly.tree.TreeNode} childOut The child to set the hexColour on.
* @param {string} categoryName Name of the toolbox category.
*/
Blockly.Toolbox.prototype.setColourFromStyle_ = function(
@@ -439,7 +503,7 @@ Blockly.Toolbox.prototype.setColourFromStyle_ = function(
/**
* Recursively updates all the category colours using the category style name.
* @param {Blockly.Toolbox.TreeNode=} opt_tree Starting point of tree.
* @param {Blockly.tree.BaseNode=} opt_tree Starting point of tree.
* Defaults to the root node.
* @private
*/
@@ -470,23 +534,23 @@ Blockly.Toolbox.prototype.updateColourFromTheme = function() {
/**
* Updates the background colour of the selected category.
* @param {!Blockly.Toolbox.TreeNode} tree Starting point of tree.
* @param {!Blockly.tree.BaseNode} tree Starting point of tree.
* Defaults to the root node.
* @private
*/
Blockly.Toolbox.prototype.updateSelectedItemColour_ = function(tree) {
var selectedItem = tree.selectedItem_;
var selectedItem = tree.getSelectedItem();
if (selectedItem) {
var hexColour = selectedItem.hexColour || '#57e';
selectedItem.getRowElement().style.backgroundColor = hexColour;
tree.toolbox_.addColour_(selectedItem);
this.addColour_(selectedItem);
}
};
/**
* Recursively add colours to this toolbox.
* @param {Blockly.Toolbox.TreeNode=} opt_tree Starting point of tree.
* @param {Blockly.tree.BaseNode=} opt_tree Starting point of tree.
* Defaults to the root node.
* @private
*/
@@ -581,215 +645,13 @@ Blockly.Toolbox.prototype.refreshSelection = function() {
}
};
// Extending Closure's Tree UI.
/**
* Extension of a TreeControl object that uses a custom tree node.
* @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
* @param {Object} config The configuration for the tree. See
* goog.ui.tree.TreeControl.DefaultConfig.
* @constructor
* @extends {goog.ui.tree.TreeControl}
*/
Blockly.Toolbox.TreeControl = function(toolbox, config) {
this.toolbox_ = toolbox;
goog.ui.tree.TreeControl.call(this, goog.html.SafeHtml.EMPTY, config);
};
goog.inherits(Blockly.Toolbox.TreeControl, goog.ui.tree.TreeControl);
/**
* Adds touch handling to TreeControl.
* @override
*/
Blockly.Toolbox.TreeControl.prototype.enterDocument = function() {
Blockly.Toolbox.TreeControl.superClass_.enterDocument.call(this);
// Add touch handler.
if (Blockly.Touch.TOUCH_ENABLED) {
var el = this.getElement();
Blockly.bindEventWithChecks_(el, goog.events.EventType.TOUCHEND, this,
this.handleTouchEvent_);
}
};
/**
* Handles touch events.
* @param {!goog.events.BrowserEvent} e The browser event.
* @private
*/
Blockly.Toolbox.TreeControl.prototype.handleTouchEvent_ = function(e) {
var node = this.getNodeFromEvent_(e);
if (node && e.type === goog.events.EventType.TOUCHEND) {
// Fire asynchronously since onMouseDown takes long enough that the browser
// would fire the default mouse event before this method returns.
setTimeout(function() {
node.onClick_(e); // Same behaviour for click and touch.
}, 1);
}
};
/**
* Creates a new tree node using a custom tree node.
* @param {string=} opt_html The HTML content of the node label.
* @return {!goog.ui.tree.TreeNode} The new item.
* @override
*/
Blockly.Toolbox.TreeControl.prototype.createNode = function(opt_html) {
var html = opt_html ?
goog.html.SafeHtml.htmlEscape(opt_html) : goog.html.SafeHtml.EMPTY;
return new Blockly.Toolbox.TreeNode(this.toolbox_, html, this.getConfig());
};
/**
* Display/hide the flyout when an item is selected.
* @param {goog.ui.tree.BaseNode} node The item to select.
* @override
*/
Blockly.Toolbox.TreeControl.prototype.setSelectedItem = function(node) {
var toolbox = this.toolbox_;
if (node == this.selectedItem_ || node == toolbox.tree_) {
return;
}
if (toolbox.lastCategory_) {
toolbox.lastCategory_.getRowElement().style.backgroundColor = '';
}
if (node) {
var hexColour = node.hexColour || '#57e';
node.getRowElement().style.backgroundColor = hexColour;
// Add colours to child nodes which may have been collapsed and thus
// not rendered.
toolbox.addColour_(node);
}
var oldNode = this.getSelectedItem();
goog.ui.tree.TreeControl.prototype.setSelectedItem.call(this, node);
if (node && node.blocks && node.blocks.length) {
toolbox.flyout_.show(node.blocks);
// Scroll the flyout to the top if the category has changed.
if (toolbox.lastCategory_ != node) {
toolbox.flyout_.scrollToStart();
}
} else {
// Hide the flyout.
toolbox.flyout_.hide();
}
if (oldNode != node && oldNode != this) {
var event = new Blockly.Events.Ui(null, 'category',
oldNode && oldNode.getHtml(), node && node.getHtml());
event.workspaceId = toolbox.workspace_.id;
Blockly.Events.fire(event);
}
if (node) {
toolbox.lastCategory_ = node;
}
};
/**
* A single node in the tree, customized for Blockly's UI.
* @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
* @param {!goog.html.SafeHtml} html The HTML content of the node label.
* @param {Object|undefined} config The configuration for the tree.
* See goog.ui.tree.TreeControl.DefaultConfig.
* If not specified, a default config will be used.
* @constructor
* @extends {goog.ui.tree.TreeNode}
*/
Blockly.Toolbox.TreeNode = function(toolbox, html, config) {
goog.ui.tree.TreeNode.call(this, html, config);
if (toolbox) {
var resize = function() {
// Even though the div hasn't changed size, the visible workspace
// surface of the workspace has, so we may need to reposition everything.
Blockly.svgResize(toolbox.workspace_);
};
// Fire a resize event since the toolbox may have changed width.
goog.events.listen(toolbox.tree_,
goog.ui.tree.BaseNode.EventType.EXPAND, resize);
goog.events.listen(toolbox.tree_,
goog.ui.tree.BaseNode.EventType.COLLAPSE, resize);
}
};
goog.inherits(Blockly.Toolbox.TreeNode, goog.ui.tree.TreeNode);
/**
* Suppress population of the +/- icon.
* @return {!goog.html.SafeHtml} The source for the icon.
* @override
*/
Blockly.Toolbox.TreeNode.prototype.getExpandIconSafeHtml = function() {
return goog.html.SafeHtml.create('span');
};
/**
* Expand or collapse the node on mouse click.
* @param {!goog.events.BrowserEvent} _e The browser event.
* @override
*/
Blockly.Toolbox.TreeNode.prototype.onClick_ = function(_e) {
// Expand icon.
if (this.hasChildren() && this.isUserCollapsible_) {
this.toggle();
this.select();
} else if (this.isSelected()) {
this.getTree().setSelectedItem(null);
} else {
this.select();
}
this.updateRow();
};
/**
* Suppress the inherited mouse down behaviour.
* @param {!goog.events.BrowserEvent} _e The browser event.
* @override
* @private
*/
Blockly.Toolbox.TreeNode.prototype.onMouseDown = function(_e) {
// NOPE.
};
/**
* Suppress the inherited double-click behaviour.
* @param {!goog.events.BrowserEvent} _e The browser event.
* @override
* @private
*/
Blockly.Toolbox.TreeNode.prototype.onDoubleClick_ = function(_e) {
// NOP.
};
/**
* Remap event.keyCode in horizontalLayout so that arrow
* keys work properly and call original onKeyDown handler.
* @param {!goog.events.BrowserEvent} e The browser event.
* @return {boolean} The handled value.
* @override
* @private
*/
Blockly.Toolbox.TreeNode.prototype.onKeyDown = function(e) {
if (this.tree.toolbox_.horizontalLayout_) {
var map = {};
var next = goog.events.KeyCodes.DOWN;
var prev = goog.events.KeyCodes.UP;
map[goog.events.KeyCodes.RIGHT] = this.rightToLeft_ ? prev : next;
map[goog.events.KeyCodes.LEFT] = this.rightToLeft_ ? next : prev;
map[goog.events.KeyCodes.UP] = goog.events.KeyCodes.LEFT;
map[goog.events.KeyCodes.DOWN] = goog.events.KeyCodes.RIGHT;
var newKeyCode = map[e.keyCode];
e.keyCode = newKeyCode || e.keyCode;
}
return Blockly.Toolbox.TreeNode.superClass_.onKeyDown.call(this, e);
};
/**
* A blank separator node in the tree.
* @param {Object|undefined} config The configuration for the tree.
* See goog.ui.tree.TreeControl.DefaultConfig
* If not specified, a default config will be used.
* @param {Blockly.tree.BaseNode.Config} config The configuration for the tree.
* @constructor
* @extends {Blockly.Toolbox.TreeNode}
* @extends {Blockly.tree.TreeNode}
*/
Blockly.Toolbox.TreeSeparator = function(config) {
Blockly.Toolbox.TreeNode.call(this, null, goog.html.SafeHtml.EMPTY, config);
Blockly.tree.TreeNode.call(this, null, '', config);
};
goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.Toolbox.TreeNode);
goog.inherits(Blockly.Toolbox.TreeSeparator, Blockly.tree.TreeNode);