mirror of
https://github.com/google/blockly.git
synced 2026-01-09 01:50:11 +01:00
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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
316
core/toolbox.js
316
core/toolbox.js
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user