mirror of
https://github.com/google/blockly.git
synced 2026-05-03 10:30:13 +02:00
Toolbox Rewrite (#4223)
Rewrite the toolbox in order to get rid of old closure code and make it easier to extend. Co-authored-by: Maribeth Bottorff <maribethb@google.com>
This commit is contained in:
+12
-8
@@ -31,11 +31,8 @@ goog.addDependency('../../core/blockly.js', ['Blockly'], ['Blockly.Events', 'Blo
|
||||
goog.addDependency('../../core/blocks.js', ['Blockly.Blocks'], [], {});
|
||||
goog.addDependency('../../core/bubble.js', ['Blockly.Bubble'], ['Blockly.Scrollbar', 'Blockly.Touch', 'Blockly.Workspace', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/bubble_dragger.js', ['Blockly.BubbleDragger'], ['Blockly.Bubble', 'Blockly.Events', 'Blockly.Events.CommentMove', 'Blockly.utils', 'Blockly.utils.Coordinate'], {});
|
||||
goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/comment.js', ['Blockly.Comment'], ['Blockly.Bubble', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Events.Ui', 'Blockly.Icon', 'Blockly.Warning', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/components/component.js', ['Blockly.Component', 'Blockly.Component.Error'], ['Blockly.utils.IdGenerator', 'Blockly.utils.dom', 'Blockly.utils.style'], {});
|
||||
goog.addDependency('../../core/components/tree/basenode.js', ['Blockly.tree.BaseNode'], ['Blockly.Component', 'Blockly.utils.KeyCodes', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style'], {});
|
||||
goog.addDependency('../../core/components/tree/treecontrol.js', ['Blockly.tree.TreeControl'], ['Blockly.tree.BaseNode', 'Blockly.tree.TreeNode', 'Blockly.utils.aria', 'Blockly.utils.object', 'Blockly.utils.style'], {});
|
||||
goog.addDependency('../../core/components/tree/treenode.js', ['Blockly.tree.TreeNode'], ['Blockly.tree.BaseNode', 'Blockly.utils.KeyCodes', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/connection.js', ['Blockly.Connection'], ['Blockly.Events', 'Blockly.Events.BlockMove', 'Blockly.Xml', 'Blockly.utils.deprecation'], {});
|
||||
goog.addDependency('../../core/connection_checker.js', ['Blockly.ConnectionChecker'], ['Blockly.registry'], {});
|
||||
goog.addDependency('../../core/connection_db.js', ['Blockly.ConnectionDB'], ['Blockly.RenderedConnection'], {});
|
||||
@@ -48,7 +45,7 @@ goog.addDependency('../../core/dropdowndiv.js', ['Blockly.DropDownDiv'], ['Block
|
||||
goog.addDependency('../../core/events.js', ['Blockly.Events'], ['Blockly.registry', 'Blockly.utils'], {});
|
||||
goog.addDependency('../../core/events_abstract.js', ['Blockly.Events.Abstract'], ['Blockly.Events'], {});
|
||||
goog.addDependency('../../core/extensions.js', ['Blockly.Extensions'], ['Blockly.utils'], {});
|
||||
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/field.js', ['Blockly.Field'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.Size', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.style', 'Blockly.utils.userAgent'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/field_angle.js', ['Blockly.FieldAngle'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.FieldTextInput', 'Blockly.fieldRegistry', 'Blockly.utils.dom', 'Blockly.utils.math', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/field_checkbox.js', ['Blockly.FieldCheckbox'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.utils.Size', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/field_colour.js', ['Blockly.FieldColour'], ['Blockly.Css', 'Blockly.DropDownDiv', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.fieldRegistry', 'Blockly.navigation', 'Blockly.utils.IdGenerator', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
|
||||
@@ -61,7 +58,7 @@ goog.addDependency('../../core/field_number.js', ['Blockly.FieldNumber'], ['Bloc
|
||||
goog.addDependency('../../core/field_registry.js', ['Blockly.fieldRegistry'], ['Blockly.registry'], {});
|
||||
goog.addDependency('../../core/field_textinput.js', ['Blockly.FieldTextInput'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.KeyCodes', 'Blockly.utils.Size', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/field_variable.js', ['Blockly.FieldVariable'], ['Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.FieldDropdown', 'Blockly.Msg', 'Blockly.VariableModel', 'Blockly.Variables', 'Blockly.Xml', 'Blockly.fieldRegistry', 'Blockly.utils', 'Blockly.utils.Size', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutCursor', 'Blockly.Gesture', 'Blockly.Marker', 'Blockly.Scrollbar', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {});
|
||||
goog.addDependency('../../core/flyout_base.js', ['Blockly.Flyout'], ['Blockly.Block', 'Blockly.Events', 'Blockly.Events.BlockCreate', 'Blockly.Events.VarCreate', 'Blockly.FlyoutCursor', 'Blockly.Gesture', 'Blockly.Marker', 'Blockly.Scrollbar', 'Blockly.Tooltip', 'Blockly.Touch', 'Blockly.WorkspaceSvg', 'Blockly.Xml', 'Blockly.blockRendering', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {});
|
||||
goog.addDependency('../../core/flyout_button.js', ['Blockly.FlyoutButton'], ['Blockly.Css', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/flyout_dragger.js', ['Blockly.FlyoutDragger'], ['Blockly.WorkspaceDragger', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/flyout_horizontal.js', ['Blockly.HorizontalFlyout'], ['Blockly.Block', 'Blockly.Flyout', 'Blockly.Scrollbar', 'Blockly.WidgetDiv', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.object'], {});
|
||||
@@ -75,7 +72,9 @@ goog.addDependency('../../core/input.js', ['Blockly.Input'], ['Blockly.Connectio
|
||||
goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.Events', 'Blockly.blockAnimations'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/interfaces/i_accessibility.js', ['Blockly.IASTNodeLocation', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IBlocklyActionable'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_bounded_element.js', ['Blockly.IBoundedElement'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_bubble.js', ['Blockly.IBubble'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_connection_checker.js', ['Blockly.IConnectionChecker'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_contextmenu.js', ['Blockly.IContextMenu'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_copyable.js', ['Blockly.ICopyable'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_deletable.js', ['Blockly.IDeletable'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_deletearea.js', ['Blockly.IDeleteArea'], [], {});
|
||||
@@ -85,6 +84,7 @@ goog.addDependency('../../core/interfaces/i_registrable.js', ['Blockly.IRegistra
|
||||
goog.addDependency('../../core/interfaces/i_selectable.js', ['Blockly.ISelectable'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_styleable.js', ['Blockly.IStyleable'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_toolbox.js', ['Blockly.IToolbox'], [], {});
|
||||
goog.addDependency('../../core/interfaces/i_toolbox_item.js', ['Blockly.ICollapsibleToolboxItem', 'Blockly.ISelectableToolboxItem', 'Blockly.IToolboxItem'], [], {});
|
||||
goog.addDependency('../../core/keyboard_nav/action.js', ['Blockly.Action'], [], {});
|
||||
goog.addDependency('../../core/keyboard_nav/ast_node.js', ['Blockly.ASTNode'], ['Blockly.utils.Coordinate'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/keyboard_nav/basic_cursor.js', ['Blockly.BasicCursor'], ['Blockly.ASTNode', 'Blockly.Cursor'], {'lang': 'es5'});
|
||||
@@ -103,7 +103,7 @@ goog.addDependency('../../core/names.js', ['Blockly.Names'], ['Blockly.Msg'], {}
|
||||
goog.addDependency('../../core/options.js', ['Blockly.Options'], ['Blockly.Theme', 'Blockly.Themes.Classic', 'Blockly.Xml', 'Blockly.registry', 'Blockly.user.keyMap', 'Blockly.utils.IdGenerator', 'Blockly.utils.Metrics', 'Blockly.utils.toolbox', 'Blockly.utils.userAgent'], {});
|
||||
goog.addDependency('../../core/procedures.js', ['Blockly.Procedures'], ['Blockly.Blocks', 'Blockly.Events', 'Blockly.Events.BlockChange', 'Blockly.Field', 'Blockly.Msg', 'Blockly.Names', 'Blockly.Workspace', 'Blockly.Xml', 'Blockly.constants', 'Blockly.utils.xml'], {});
|
||||
goog.addDependency('../../core/registry.js', ['Blockly.registry'], [], {});
|
||||
goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/rendered_connection.js', ['Blockly.RenderedConnection'], ['Blockly.Connection', 'Blockly.Events', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.deprecation', 'Blockly.utils.dom', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/renderers/common/block_rendering.js', ['Blockly.blockRendering'], ['Blockly.registry', 'Blockly.utils.object'], {});
|
||||
goog.addDependency('../../core/renderers/common/constants.js', ['Blockly.blockRendering.ConstantProvider'], ['Blockly.utils', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.svgPaths', 'Blockly.utils.userAgent'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/renderers/common/debugger.js', ['Blockly.blockRendering.Debug'], ['Blockly.blockRendering.BottomRow', 'Blockly.blockRendering.InputRow', 'Blockly.blockRendering.Measurable', 'Blockly.blockRendering.RenderInfo', 'Blockly.blockRendering.Row', 'Blockly.blockRendering.SpacerRow', 'Blockly.blockRendering.TopRow', 'Blockly.blockRendering.Types'], {'lang': 'es5'});
|
||||
@@ -153,7 +153,11 @@ goog.addDependency('../../core/theme/modern.js', ['Blockly.Themes.Modern'], ['Bl
|
||||
goog.addDependency('../../core/theme/tritanopia.js', ['Blockly.Themes.Tritanopia'], ['Blockly.Theme'], {});
|
||||
goog.addDependency('../../core/theme/zelos.js', ['Blockly.Themes.Zelos'], ['Blockly.Theme'], {});
|
||||
goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Blockly.Theme'], {});
|
||||
goog.addDependency('../../core/toolbox.js', ['Blockly.Toolbox'], ['Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.Touch', 'Blockly.navigation', 'Blockly.registry', 'Blockly.tree.TreeControl', 'Blockly.tree.TreeNode', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.colour', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
|
||||
goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/toolbox/collapsible_category.js', ['Blockly.CollapsibleToolboxCategory'], ['Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox'], {});
|
||||
goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.ToolboxItem', 'Blockly.registry', 'Blockly.utils.dom'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.Ui', 'Blockly.ToolboxCategory', 'Blockly.ToolboxSeparator', 'Blockly.Touch', 'Blockly.navigation', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom'], {'lang': 'es5'});
|
||||
goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], [], {});
|
||||
goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.utils.string'], {});
|
||||
goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string'], {});
|
||||
goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object'], {});
|
||||
|
||||
@@ -1,895 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Blockly.tree.BaseNode class.
|
||||
* This class is similar to Closure's goog.ui.tree.BaseNode class.
|
||||
* @author samelh@google.com (Sam El-Husseini)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.tree.BaseNode');
|
||||
|
||||
goog.require('Blockly.Component');
|
||||
goog.require('Blockly.utils.aria');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.KeyCodes');
|
||||
goog.require('Blockly.utils.style');
|
||||
|
||||
|
||||
/**
|
||||
* An abstract base class for a node in the tree.
|
||||
* Similar to goog.ui.tree.BaseNode
|
||||
*
|
||||
* @param {string} content The content of the node label treated as
|
||||
* plain-text and will be HTML escaped.
|
||||
* @param {!Blockly.tree.BaseNode.Config} config The configuration for the tree.
|
||||
* @constructor
|
||||
* @extends {Blockly.Component}
|
||||
*/
|
||||
Blockly.tree.BaseNode = function(content, config) {
|
||||
Blockly.Component.call(this);
|
||||
|
||||
/**
|
||||
* Text content of the node label.
|
||||
* @type {string}
|
||||
* @package
|
||||
*/
|
||||
this.content = content;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @package
|
||||
*/
|
||||
this.iconClass;
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
* @package
|
||||
*/
|
||||
this.expandedIconClass;
|
||||
|
||||
/**
|
||||
* The configuration for the tree.
|
||||
* @type {!Blockly.tree.BaseNode.Config}
|
||||
* @protected
|
||||
*/
|
||||
this.config_ = config;
|
||||
|
||||
/**
|
||||
* @type {Blockly.tree.TreeControl}
|
||||
* @protected
|
||||
*/
|
||||
this.tree;
|
||||
|
||||
/**
|
||||
* @type {Blockly.tree.BaseNode}
|
||||
* @private
|
||||
*/
|
||||
this.previousSibling_;
|
||||
|
||||
/**
|
||||
* @type {Blockly.tree.BaseNode}
|
||||
* @private
|
||||
*/
|
||||
this.nextSibling_;
|
||||
|
||||
/**
|
||||
* Whether the tree item is selected.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.selected_ = false;
|
||||
|
||||
/**
|
||||
* Whether the tree node is expanded.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.expanded_ = false;
|
||||
|
||||
/**
|
||||
* Nesting depth of this node; cached result of getDepth.
|
||||
* -1 if value has not been cached.
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.depth_ = -1;
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.tree.BaseNode, Blockly.Component);
|
||||
|
||||
|
||||
/**
|
||||
* The config type for the tree.
|
||||
* @typedef {{
|
||||
* indentWidth:number,
|
||||
* cssRoot:string,
|
||||
* cssHideRoot:string,
|
||||
* cssTreeRow:string,
|
||||
* cssItemLabel:string,
|
||||
* cssTreeIcon:string,
|
||||
* cssExpandedFolderIcon:string,
|
||||
* cssCollapsedFolderIcon:string,
|
||||
* cssFileIcon:string,
|
||||
* cssSelectedRow:string
|
||||
* }}
|
||||
*/
|
||||
Blockly.tree.BaseNode.Config;
|
||||
|
||||
/**
|
||||
* Map of nodes in existence. Needed to route events to the appropriate nodes.
|
||||
* Nodes are added to the map at {@link #enterDocument} time and removed at
|
||||
* {@link #exitDocument} time.
|
||||
* @type {Object}
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.allNodes = {};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.BaseNode.prototype.disposeInternal = function() {
|
||||
Blockly.tree.BaseNode.superClass_.disposeInternal.call(this);
|
||||
if (this.tree) {
|
||||
this.tree = null;
|
||||
}
|
||||
this.setElementInternal(null);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Adds roles and states.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.initAccessibility = function() {
|
||||
var el = this.getElement();
|
||||
if (el) {
|
||||
// Set an id for the label
|
||||
var label = this.getLabelElement();
|
||||
if (label && !label.id) {
|
||||
label.id = this.getId() + '.label';
|
||||
}
|
||||
|
||||
Blockly.utils.aria.setRole(el, Blockly.utils.aria.Role.TREEITEM);
|
||||
Blockly.utils.aria.setState(el, Blockly.utils.aria.State.SELECTED, false);
|
||||
Blockly.utils.aria.setState(el,
|
||||
Blockly.utils.aria.State.LEVEL, this.getDepth());
|
||||
if (label) {
|
||||
Blockly.utils.aria.setState(el,
|
||||
Blockly.utils.aria.State.LABELLEDBY, label.id);
|
||||
}
|
||||
|
||||
var img = this.getIconElement();
|
||||
if (img) {
|
||||
Blockly.utils.aria.setRole(img, Blockly.utils.aria.Role.PRESENTATION);
|
||||
}
|
||||
|
||||
var ce = this.getChildrenElement();
|
||||
if (ce) {
|
||||
Blockly.utils.aria.setRole(ce, Blockly.utils.aria.Role.GROUP);
|
||||
|
||||
// In case the children will be created lazily.
|
||||
if (ce.hasChildNodes()) {
|
||||
// Only set aria-expanded if the node has children (can be expanded).
|
||||
Blockly.utils.aria.setState(el, Blockly.utils.aria.State.EXPANDED, false);
|
||||
|
||||
// do setsize for each child
|
||||
var count = this.getChildCount();
|
||||
for (var i = 1; i <= count; i++) {
|
||||
var child = /** @type {!Element} */ (this.getChildAt(i - 1).getElement());
|
||||
Blockly.utils.aria.setState(child,
|
||||
Blockly.utils.aria.State.SETSIZE, count);
|
||||
Blockly.utils.aria.setState(child,
|
||||
Blockly.utils.aria.State.POSINSET, i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.BaseNode.prototype.createDom = function() {
|
||||
var element = document.createElement('div');
|
||||
element.appendChild(this.toDom());
|
||||
this.setElementInternal(/** @type {!HTMLElement} */ (element));
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.BaseNode.prototype.enterDocument = function() {
|
||||
Blockly.tree.BaseNode.superClass_.enterDocument.call(this);
|
||||
Blockly.tree.BaseNode.allNodes[this.getId()] = this;
|
||||
this.initAccessibility();
|
||||
};
|
||||
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.BaseNode.prototype.exitDocument = function() {
|
||||
Blockly.tree.BaseNode.superClass_.exitDocument.call(this);
|
||||
delete Blockly.tree.BaseNode.allNodes[this.getId()];
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The method assumes that the child doesn't have parent node yet.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.addChildAt = function(child, index) {
|
||||
child = /** @type {Blockly.tree.BaseNode} */ (child);
|
||||
var prevNode = this.getChildAt(index - 1);
|
||||
var nextNode = this.getChildAt(index);
|
||||
|
||||
Blockly.tree.BaseNode.superClass_.addChildAt.call(this, child, index);
|
||||
|
||||
child.previousSibling_ = prevNode;
|
||||
child.nextSibling_ = nextNode;
|
||||
|
||||
if (prevNode) {
|
||||
prevNode.nextSibling_ = child;
|
||||
}
|
||||
if (nextNode) {
|
||||
nextNode.previousSibling_ = child;
|
||||
}
|
||||
|
||||
var tree = this.getTree();
|
||||
if (tree) {
|
||||
child.setTreeInternal(tree);
|
||||
}
|
||||
|
||||
child.setDepth_(this.getDepth() + 1);
|
||||
|
||||
var el = this.getElement();
|
||||
if (el) {
|
||||
this.updateExpandIcon();
|
||||
Blockly.utils.aria.setState(
|
||||
el, Blockly.utils.aria.State.EXPANDED, this.expanded_);
|
||||
if (this.expanded_) {
|
||||
var childrenEl = this.getChildrenElement();
|
||||
if (!child.getElement()) {
|
||||
child.createDom();
|
||||
}
|
||||
var childElement = child.getElement();
|
||||
var nextElement = nextNode && nextNode.getElement();
|
||||
childrenEl.insertBefore(childElement, nextElement);
|
||||
|
||||
if (this.isInDocument()) {
|
||||
child.enterDocument();
|
||||
}
|
||||
|
||||
if (!nextNode) {
|
||||
if (prevNode) {
|
||||
prevNode.updateExpandIcon();
|
||||
} else {
|
||||
Blockly.utils.style.setElementShown(childrenEl, true);
|
||||
this.setExpanded(this.expanded_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends a node as a child to the current node.
|
||||
* @param {Blockly.tree.BaseNode} child The child to add.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.add = function(child) {
|
||||
if (child.getParent()) {
|
||||
throw Error(Blockly.Component.Error.PARENT_UNABLE_TO_BE_SET);
|
||||
}
|
||||
this.addChildAt(child, this.getChildCount());
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the tree.
|
||||
* @return {?Blockly.tree.TreeControl} tree
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getTree = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the depth of the node in the tree. Should not be overridden.
|
||||
* @return {number} The non-negative depth of this node (the root is zero).
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getDepth = function() {
|
||||
var depth = this.depth_;
|
||||
if (depth < 0) {
|
||||
var parent = this.getParent();
|
||||
if (parent) {
|
||||
depth = parent.getDepth() + 1;
|
||||
} else {
|
||||
depth = 0;
|
||||
}
|
||||
this.setDepth_(depth);
|
||||
}
|
||||
return depth;
|
||||
};
|
||||
|
||||
/**
|
||||
* Changes the depth of a node (and all its descendants).
|
||||
* @param {number} depth The new nesting depth; must be non-negative.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.setDepth_ = function(depth) {
|
||||
if (depth != this.depth_) {
|
||||
this.depth_ = depth;
|
||||
var row = this.getRowElement();
|
||||
if (row) {
|
||||
var indent = this.getPixelIndent_() + 'px';
|
||||
if (this.rightToLeft_) {
|
||||
row.style.paddingRight = indent;
|
||||
} else {
|
||||
row.style.paddingLeft = indent;
|
||||
}
|
||||
}
|
||||
this.forEachChild(function(child) { child.setDepth_(depth + 1); });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns true if the node is a descendant of this node.
|
||||
* @param {Blockly.Component} node The node to check.
|
||||
* @return {boolean} True if the node is a descendant of this node, false
|
||||
* otherwise.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.contains = function(node) {
|
||||
while (node) {
|
||||
if (node == this) {
|
||||
return true;
|
||||
}
|
||||
node = node.getParent();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* This is re-defined here to indicate to the Closure Compiler the correct
|
||||
* child return type.
|
||||
* @param {number} index 0-based index.
|
||||
* @return {Blockly.tree.BaseNode} The child at the given index; null if none.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getChildAt;
|
||||
|
||||
/**
|
||||
* Returns the children of this node.
|
||||
* @return {!Array.<!Blockly.tree.BaseNode>} The children.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getChildren = function() {
|
||||
var children = [];
|
||||
this.forEachChild(function(child) { children.push(child); });
|
||||
return children;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the node's parent, if any.
|
||||
* @return {?Blockly.tree.BaseNode} The parent node.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getParent = function() {
|
||||
return /** @type {Blockly.tree.BaseNode} */ (
|
||||
Blockly.tree.BaseNode.superClass_.getParent.call(this));
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Blockly.tree.BaseNode} The previous sibling of this node.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getPreviousSibling = function() {
|
||||
return this.previousSibling_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Blockly.tree.BaseNode} The next sibling of this node.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getNextSibling = function() {
|
||||
return this.nextSibling_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the node is the last sibling.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.isLastSibling = function() {
|
||||
return !this.nextSibling_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether the node is selected.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.isSelected = function() {
|
||||
return this.selected_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the node.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.select = function() {
|
||||
var tree = this.getTree();
|
||||
if (tree) {
|
||||
tree.setSelectedItem(this);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Called from the tree to instruct the node change its selection state.
|
||||
* @param {boolean} selected The new selection state.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.setSelected = function(selected) {
|
||||
if (this.selected_ == selected) {
|
||||
return;
|
||||
}
|
||||
this.selected_ = selected;
|
||||
|
||||
this.updateRow();
|
||||
|
||||
var el = this.getElement();
|
||||
if (el) {
|
||||
Blockly.utils.aria.setState(el, Blockly.utils.aria.State.SELECTED, selected);
|
||||
if (selected) {
|
||||
var treeElement = /** @type {!Element} */ (this.getTree().getElement());
|
||||
Blockly.utils.aria.setState(treeElement,
|
||||
Blockly.utils.aria.State.ACTIVEDESCENDANT, this.getId());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the node to be expanded.
|
||||
* @param {boolean} expanded Whether to expand or close the node.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.setExpanded = function(expanded) {
|
||||
var isStateChange = expanded != this.expanded_;
|
||||
var ce;
|
||||
this.expanded_ = expanded;
|
||||
var tree = this.getTree();
|
||||
var el = this.getElement();
|
||||
|
||||
if (this.hasChildren()) {
|
||||
if (!expanded && tree && this.contains(tree.getSelectedItem())) {
|
||||
this.select();
|
||||
}
|
||||
|
||||
if (el) {
|
||||
ce = this.getChildrenElement();
|
||||
if (ce) {
|
||||
Blockly.utils.style.setElementShown(ce, expanded);
|
||||
Blockly.utils.aria.setState(el, Blockly.utils.aria.State.EXPANDED, expanded);
|
||||
|
||||
// Make sure we have the HTML for the children here.
|
||||
if (expanded && this.isInDocument() && !ce.hasChildNodes()) {
|
||||
this.forEachChild(function(child) {
|
||||
ce.appendChild(child.toDom());
|
||||
});
|
||||
this.forEachChild(function(child) { child.enterDocument(); });
|
||||
}
|
||||
}
|
||||
this.updateExpandIcon();
|
||||
}
|
||||
} else {
|
||||
ce = this.getChildrenElement();
|
||||
if (ce) {
|
||||
Blockly.utils.style.setElementShown(ce, false);
|
||||
}
|
||||
}
|
||||
if (el) {
|
||||
this.updateIcon_();
|
||||
}
|
||||
|
||||
if (isStateChange) {
|
||||
if (expanded) {
|
||||
this.doNodeExpanded();
|
||||
} else {
|
||||
this.doNodeCollapsed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to notify a node of that we have expanded it.
|
||||
* Can be overridden by subclasses, see Blockly.tree.TreeNode.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.doNodeExpanded = function() {
|
||||
// NOP
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to notify a node that we have collapsed it.
|
||||
* Can be overridden by subclasses, see Blockly.tree.TreeNode.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.doNodeCollapsed = function() {
|
||||
// NOP
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles the expanded state of the node.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.toggle = function() {
|
||||
this.setExpanded(!this.expanded_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates HTML Element for the node.
|
||||
* @return {!Element} HTML element
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.toDom = function() {
|
||||
var nonEmptyAndExpanded = this.expanded_ && this.hasChildren();
|
||||
|
||||
var children = document.createElement('div');
|
||||
children.style.backgroundPosition = this.getBackgroundPosition();
|
||||
if (!nonEmptyAndExpanded) {
|
||||
children.style.display = 'none';
|
||||
}
|
||||
|
||||
if (nonEmptyAndExpanded) {
|
||||
// children
|
||||
this.forEachChild(function(child) { children.appendChild(child.toDom()); });
|
||||
}
|
||||
|
||||
var node = document.createElement('div');
|
||||
node.id = this.getId();
|
||||
|
||||
node.appendChild(this.getRowDom());
|
||||
node.appendChild(children);
|
||||
|
||||
return node;
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates correct padding for each row. Nested categories are indented more.
|
||||
* @return {number} The pixel indent of the row.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getPixelIndent_ = function() {
|
||||
return Math.max(0, (this.getDepth() - 1) * this.config_.indentWidth);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates row with icon and label dom.
|
||||
* @return {!Element} The HTML element for the row.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getRowDom = function() {
|
||||
var row = document.createElement('div');
|
||||
row.className = this.getRowClassName();
|
||||
row.style['padding-' + (this.rightToLeft_ ? 'right' : 'left')] =
|
||||
this.getPixelIndent_() + 'px';
|
||||
|
||||
row.appendChild(this.getIconDom());
|
||||
row.appendChild(this.getLabelDom());
|
||||
|
||||
return row;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the selected class name to the default row class name if node is
|
||||
* selected.
|
||||
* @return {string} The class name for the row.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getRowClassName = function() {
|
||||
var selectedClass = '';
|
||||
if (this.isSelected()) {
|
||||
selectedClass = ' ' + (this.config_.cssSelectedRow || '');
|
||||
}
|
||||
return this.config_.cssTreeRow + selectedClass;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {!Element} The HTML element for the label.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getLabelDom = function() {
|
||||
var label = document.createElement('span');
|
||||
label.className = this.config_.cssItemLabel || '';
|
||||
label.textContent = this.content;
|
||||
return label;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {!Element} The HTML for the icon.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getIconDom = function() {
|
||||
var icon = document.createElement('span');
|
||||
icon.style.display = 'inline-block';
|
||||
icon.className = this.getCalculatedIconClass();
|
||||
return icon;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the calculated icon class.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getCalculatedIconClass = function() {
|
||||
throw Error(Blockly.Component.Error.ABSTRACT_METHOD);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a string containing the x and y position of the node's background.
|
||||
* @return {string} The background position style value.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getBackgroundPosition = function() {
|
||||
return (this.isLastSibling() ? '-100' : (this.getDepth() - 1) *
|
||||
this.config_.indentWidth) + 'px 0';
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {HTMLElement} The element for the tree node.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getElement = function() {
|
||||
var el = Blockly.tree.BaseNode.superClass_.getElement.call(this);
|
||||
if (!el) {
|
||||
el = document.getElementById(this.getId());
|
||||
this.setElementInternal(el);
|
||||
}
|
||||
return /** @type {!HTMLElement} */ (el);
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Element} The row is the div that is used to draw the node without
|
||||
* the children.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getRowElement = function() {
|
||||
var el = this.getElement();
|
||||
return el ? /** @type {Element} */ (el.firstChild) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Element} The icon element.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getIconElement = function() {
|
||||
var el = this.getRowElement();
|
||||
return el ? /** @type {Element} */ (el.firstChild) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Element} The label element.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getLabelElement = function() {
|
||||
var el = this.getRowElement();
|
||||
return el && el.lastChild ?
|
||||
/** @type {Element} */ (el.lastChild.previousSibling) :
|
||||
null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Element} The div containing the children.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getChildrenElement = function() {
|
||||
var el = this.getElement();
|
||||
return el ? /** @type {Element} */ (el.lastChild) : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the row styles.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.updateRow = function() {
|
||||
var rowEl = this.getRowElement();
|
||||
if (rowEl) {
|
||||
rowEl.className = this.getRowClassName();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the expand icon of the node.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.updateExpandIcon = function() {
|
||||
var cel = this.getChildrenElement();
|
||||
if (cel) {
|
||||
cel.style.backgroundPosition = this.getBackgroundPosition();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the icon of the node. Assumes that this.getElement() is created.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.updateIcon_ = function() {
|
||||
this.getIconElement().className = this.getCalculatedIconClass();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a click event.
|
||||
* @param {!Event} e The browser event.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.onClick_ = function(e) {
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles a key down event.
|
||||
* @param {!Event} e The browser event.
|
||||
* @return {boolean} The handled value.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.onKeyDown = function(e) {
|
||||
var handled = true;
|
||||
switch (e.keyCode) {
|
||||
case Blockly.utils.KeyCodes.RIGHT:
|
||||
handled = this.selectChild();
|
||||
break;
|
||||
|
||||
case Blockly.utils.KeyCodes.LEFT:
|
||||
handled = this.selectParent();
|
||||
break;
|
||||
|
||||
case Blockly.utils.KeyCodes.DOWN:
|
||||
handled = this.selectNext();
|
||||
break;
|
||||
|
||||
case Blockly.utils.KeyCodes.UP:
|
||||
handled = this.selectPrevious();
|
||||
break;
|
||||
|
||||
case Blockly.utils.KeyCodes.ENTER:
|
||||
case Blockly.utils.KeyCodes.SPACE:
|
||||
this.toggle();
|
||||
handled = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
handled = false;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return handled;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Select the next node.
|
||||
* @return {boolean} True if the action has been handled, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.selectNext = function() {
|
||||
var nextNode = this.getNextShownNode();
|
||||
if (nextNode) {
|
||||
nextNode.select();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the previous node.
|
||||
* @return {boolean} True if the action has been handled, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.selectPrevious = function() {
|
||||
var previousNode = this.getPreviousShownNode();
|
||||
if (previousNode) {
|
||||
previousNode.select();
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the parent node or collapse the current node.
|
||||
* @return {boolean} True if the action has been handled, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.selectParent = function() {
|
||||
if (this.hasChildren() && this.expanded_) {
|
||||
this.setExpanded(false);
|
||||
} else {
|
||||
var parent = this.getParent();
|
||||
var tree = this.getTree();
|
||||
// don't go to root if hidden
|
||||
if (parent && (parent != tree)) {
|
||||
parent.select();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand the current node if it's not already expanded, or select the
|
||||
* child node.
|
||||
* @return {boolean} True if the action has been handled, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.selectChild = function() {
|
||||
if (this.hasChildren()) {
|
||||
if (!this.expanded_) {
|
||||
this.setExpanded(true);
|
||||
} else {
|
||||
this.getChildAt(0).select();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Blockly.tree.BaseNode} The last shown descendant.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getLastShownDescendant = function() {
|
||||
if (!this.expanded_ || !this.hasChildren()) {
|
||||
return this;
|
||||
}
|
||||
// we know there is at least 1 child
|
||||
return this.getChildAt(this.getChildCount() - 1).getLastShownDescendant();
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Blockly.tree.BaseNode} The next node to show or null if there isn't
|
||||
* a next node to show.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getNextShownNode = function() {
|
||||
if (this.hasChildren() && this.expanded_) {
|
||||
return this.getChildAt(0);
|
||||
}
|
||||
var parent = this;
|
||||
var next;
|
||||
while (parent != this.getTree()) {
|
||||
next = parent.getNextSibling();
|
||||
if (next != null) {
|
||||
return next;
|
||||
}
|
||||
parent = parent.getParent();
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {Blockly.tree.BaseNode} The previous node to show.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.getPreviousShownNode = function() {
|
||||
var ps = this.getPreviousSibling();
|
||||
if (ps != null) {
|
||||
return ps.getLastShownDescendant();
|
||||
}
|
||||
var parent = this.getParent();
|
||||
var tree = this.getTree();
|
||||
if (parent == tree) {
|
||||
return null;
|
||||
}
|
||||
// The root is the first node.
|
||||
if (this == tree) {
|
||||
return null;
|
||||
}
|
||||
return /** @type {Blockly.tree.BaseNode} */ (parent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Internal method that is used to set the tree control on the node.
|
||||
* @param {Blockly.tree.TreeControl} tree The tree control.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.tree.BaseNode.prototype.setTreeInternal = function(tree) {
|
||||
if (this.tree != tree) {
|
||||
this.tree = tree;
|
||||
this.forEachChild(function(child) { child.setTreeInternal(tree); });
|
||||
}
|
||||
};
|
||||
@@ -1,332 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Blockly.tree.TreeControl class.
|
||||
* This class is similar to Closure's goog.ui.tree.TreeControl class.
|
||||
* @author samelh@google.com (Sam El-Husseini)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.tree.TreeControl');
|
||||
|
||||
goog.require('Blockly.tree.TreeNode');
|
||||
goog.require('Blockly.tree.BaseNode');
|
||||
goog.require('Blockly.utils.aria');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.style');
|
||||
|
||||
|
||||
/**
|
||||
* An extension of the TreeControl object in closure that provides
|
||||
* a way to view a hierarchical set of data.
|
||||
* Similar to Closure's goog.ui.tree.TreeControl
|
||||
*
|
||||
* @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
|
||||
* @param {!Blockly.tree.BaseNode.Config} config The configuration for the tree.
|
||||
* @constructor
|
||||
* @extends {Blockly.tree.BaseNode}
|
||||
*/
|
||||
Blockly.tree.TreeControl = function(toolbox, config) {
|
||||
this.toolbox_ = toolbox;
|
||||
|
||||
/**
|
||||
* Click event data.
|
||||
* @type {?Blockly.EventData}
|
||||
* @private
|
||||
*/
|
||||
this.onClickWrapper_ = null;
|
||||
|
||||
/**
|
||||
* Key down event data.
|
||||
* @type {?Blockly.EventData}
|
||||
* @private
|
||||
*/
|
||||
this.onKeydownWrapper_ = null;
|
||||
|
||||
Blockly.tree.BaseNode.call(this, '', config);
|
||||
|
||||
// The root is open and selected by default.
|
||||
this.expanded_ = true;
|
||||
this.selected_ = true;
|
||||
|
||||
/**
|
||||
* Currently selected item.
|
||||
* @type {Blockly.tree.BaseNode}
|
||||
* @private
|
||||
*/
|
||||
this.selectedItem_ = this;
|
||||
|
||||
/**
|
||||
* A handler that's triggered before a node is selected.
|
||||
* @type {?function(Blockly.tree.BaseNode):boolean}
|
||||
* @private
|
||||
*/
|
||||
this.onBeforeSelected_ = null;
|
||||
|
||||
/**
|
||||
* A handler that's triggered before a node is selected.
|
||||
* @type {?function(Blockly.tree.BaseNode, Blockly.tree.BaseNode):?}
|
||||
* @private
|
||||
*/
|
||||
this.onAfterSelected_ = null;
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.tree.TreeControl, Blockly.tree.BaseNode);
|
||||
|
||||
/**
|
||||
* Returns the tree.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.getTree = function() {
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the associated toolbox.
|
||||
* @return {Blockly.Toolbox} The toolbox.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.getToolbox = function() {
|
||||
return this.toolbox_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Return node depth.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.getDepth = function() {
|
||||
return 0;
|
||||
};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.TreeControl.prototype.setExpanded = function(expanded) {
|
||||
this.expanded_ = expanded;
|
||||
};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.TreeControl.prototype.getIconElement = function() {
|
||||
var el = this.getRowElement();
|
||||
return el ? /** @type {Element} */ (el.firstChild) : null;
|
||||
};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.TreeControl.prototype.updateExpandIcon = function() {
|
||||
// no expand icon
|
||||
};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.TreeControl.prototype.getRowClassName = function() {
|
||||
return Blockly.tree.TreeControl.superClass_.getRowClassName.call(this) +
|
||||
' ' + this.config_.cssHideRoot;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the source for the icon.
|
||||
* @return {string} Src for the icon.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.getCalculatedIconClass = function() {
|
||||
var expanded = this.expanded_;
|
||||
if (expanded && this.expandedIconClass) {
|
||||
return this.expandedIconClass;
|
||||
}
|
||||
var iconClass = this.iconClass;
|
||||
if (!expanded && iconClass) {
|
||||
return iconClass;
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the selected item.
|
||||
* @param {Blockly.tree.BaseNode} node The item to select.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.setSelectedItem = function(node) {
|
||||
if (node == this.selectedItem_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.onBeforeSelected_ &&
|
||||
!this.onBeforeSelected_.call(this.toolbox_, node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var oldNode = this.getSelectedItem();
|
||||
|
||||
if (this.selectedItem_) {
|
||||
this.selectedItem_.setSelected(false);
|
||||
}
|
||||
|
||||
this.selectedItem_ = node;
|
||||
|
||||
if (node) {
|
||||
node.setSelected(true);
|
||||
}
|
||||
|
||||
if (this.onAfterSelected_) {
|
||||
this.onAfterSelected_.call(this.toolbox_, oldNode, node);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the handler that's triggered before a node is selected.
|
||||
* @param {function(Blockly.tree.BaseNode):boolean} fn The handler
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.onBeforeSelected = function(fn) {
|
||||
this.onBeforeSelected_ = fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the handler that's triggered after a node is selected.
|
||||
* @param {function(
|
||||
* Blockly.tree.BaseNode, Blockly.tree.BaseNode):?} fn The handler
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.onAfterSelected = function(fn) {
|
||||
this.onAfterSelected_ = fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the selected item.
|
||||
* @return {Blockly.tree.BaseNode} The currently selected item.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.getSelectedItem = function() {
|
||||
return this.selectedItem_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add roles and states.
|
||||
* @protected
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.initAccessibility = function() {
|
||||
Blockly.tree.TreeControl.superClass_.initAccessibility.call(this);
|
||||
|
||||
var el = /** @type {!Element} */ (this.getElement());
|
||||
Blockly.utils.aria.setRole(el, Blockly.utils.aria.Role.TREE);
|
||||
Blockly.utils.aria.setState(el,
|
||||
Blockly.utils.aria.State.LABELLEDBY, this.getLabelElement().id);
|
||||
};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.TreeControl.prototype.enterDocument = function() {
|
||||
Blockly.tree.TreeControl.superClass_.enterDocument.call(this);
|
||||
var el = this.getElement();
|
||||
el.className = this.config_.cssRoot;
|
||||
el.setAttribute('hideFocus', 'true');
|
||||
this.attachEvents_();
|
||||
this.initAccessibility();
|
||||
};
|
||||
|
||||
/** @override */
|
||||
Blockly.tree.TreeControl.prototype.exitDocument = function() {
|
||||
Blockly.tree.TreeControl.superClass_.exitDocument.call(this);
|
||||
this.detachEvents_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the event listeners to the tree.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.attachEvents_ = function() {
|
||||
var el = this.getElement();
|
||||
el.tabIndex = 0;
|
||||
|
||||
this.onClickWrapper_ = Blockly.bindEventWithChecks_(el,
|
||||
'click', this, this.handleMouseEvent_);
|
||||
this.onKeydownWrapper_ = Blockly.bindEvent_(el,
|
||||
'keydown', this, this.handleKeyEvent_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes the event listeners from the tree.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.detachEvents_ = function() {
|
||||
if (this.onClickWrapper_) {
|
||||
Blockly.unbindEvent_(this.onClickWrapper_);
|
||||
this.onClickWrapper_ = null;
|
||||
}
|
||||
if (this.onKeydownWrapper_) {
|
||||
Blockly.unbindEvent_(this.onKeydownWrapper_);
|
||||
this.onKeydownWrapper_ = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles mouse events.
|
||||
* @param {!Event} e The browser event.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.handleMouseEvent_ = function(e) {
|
||||
var node = this.getNodeFromEvent_(e);
|
||||
if (node && e.type == 'click') {
|
||||
node.onClick_(e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles key down on the tree.
|
||||
* @param {!Event} e The browser event.
|
||||
* @return {boolean} The handled value.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.handleKeyEvent_ = function(e) {
|
||||
// Handle navigation keystrokes.
|
||||
var handled = !!(this.selectedItem_ && this.selectedItem_.onKeyDown(e));
|
||||
|
||||
if (handled) {
|
||||
Blockly.utils.style.scrollIntoContainerView(
|
||||
/** @type {!Element} */ (this.selectedItem_.getElement()),
|
||||
/** @type {!Element} */ (this.getElement().parentNode));
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
return handled;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finds the containing node given an event.
|
||||
* @param {!Event} e The browser event.
|
||||
* @return {Blockly.tree.BaseNode} The containing node or null if no node is
|
||||
* found.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.getNodeFromEvent_ = function(e) {
|
||||
// find the right node
|
||||
var node = null;
|
||||
var target = e.target;
|
||||
while (target) {
|
||||
var id = target.id;
|
||||
node = Blockly.tree.BaseNode.allNodes[id];
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
if (target == this.getElement()) {
|
||||
break;
|
||||
}
|
||||
// Don't bubble if we hit a group. See issue #714.
|
||||
if (target.getAttribute('role') == Blockly.utils.aria.Role.GROUP) {
|
||||
return null;
|
||||
}
|
||||
target = target.parentNode;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new tree node using the same config as the root.
|
||||
* @param {string=} opt_content The content of the node label.
|
||||
* @return {!Blockly.tree.TreeNode} The new item.
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeControl.prototype.createNode = function(opt_content) {
|
||||
return new Blockly.tree.TreeNode(
|
||||
this.toolbox_, opt_content || '', this.config_);
|
||||
};
|
||||
@@ -1,172 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2019 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Definition of the Blockly.tree.TreeNode class.
|
||||
* This class is similar to Closure's goog.ui.tree.TreeNode class.
|
||||
* @author samelh@google.com (Sam El-Husseini)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.tree.TreeNode');
|
||||
|
||||
goog.require('Blockly.tree.BaseNode');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.KeyCodes');
|
||||
|
||||
|
||||
/**
|
||||
* A single node in the tree, customized for Blockly's UI.
|
||||
* Similar to Closure's goog.ui.tree.TreeNode
|
||||
*
|
||||
* @param {Blockly.Toolbox} toolbox The parent toolbox for this tree.
|
||||
* @param {string} content The content of the node label treated as
|
||||
* plain-text and will be HTML escaped.
|
||||
* @param {!Blockly.tree.BaseNode.Config} config The configuration for the tree.
|
||||
* @constructor
|
||||
* @extends {Blockly.tree.BaseNode}
|
||||
*/
|
||||
Blockly.tree.TreeNode = function(toolbox, content, config) {
|
||||
this.toolbox_ = toolbox;
|
||||
Blockly.tree.BaseNode.call(this, content, config);
|
||||
|
||||
/**
|
||||
* A handler that's triggered when the size of node has changed.
|
||||
* @type {?function():?}
|
||||
* @private
|
||||
*/
|
||||
this.onSizeChanged_ = null;
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.tree.TreeNode, Blockly.tree.BaseNode);
|
||||
|
||||
/**
|
||||
* Returns the tree.
|
||||
* @return {?Blockly.tree.TreeControl} The tree.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.getTree = function() {
|
||||
if (this.tree) {
|
||||
return this.tree;
|
||||
}
|
||||
var parent = this.getParent();
|
||||
if (parent) {
|
||||
var tree = parent.getTree();
|
||||
if (tree) {
|
||||
this.setTreeInternal(tree);
|
||||
return tree;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the source for the icon.
|
||||
* @return {string} Src for the icon.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.getCalculatedIconClass = function() {
|
||||
var expanded = this.expanded_;
|
||||
if (expanded && this.expandedIconClass) {
|
||||
return this.expandedIconClass;
|
||||
}
|
||||
var iconClass = this.iconClass;
|
||||
if (!expanded && iconClass) {
|
||||
return iconClass;
|
||||
}
|
||||
|
||||
// fall back on default icons
|
||||
var config = this.config_;
|
||||
if (this.hasChildren()) {
|
||||
if (expanded && config.cssExpandedFolderIcon) {
|
||||
return config.cssTreeIcon + ' ' + config.cssExpandedFolderIcon;
|
||||
} else if (!expanded && config.cssCollapsedFolderIcon) {
|
||||
return config.cssTreeIcon + ' ' + config.cssCollapsedFolderIcon;
|
||||
}
|
||||
} else {
|
||||
if (config.cssFileIcon) {
|
||||
return config.cssTreeIcon + ' ' + config.cssFileIcon;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand or collapse the node on mouse click.
|
||||
* @param {!Event} _e The browser event.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.onClick_ = function(_e) {
|
||||
// Expand icon.
|
||||
if (this.hasChildren()) {
|
||||
this.toggle();
|
||||
this.select();
|
||||
} else if (this.isSelected()) {
|
||||
this.getTree().setSelectedItem(null);
|
||||
} else {
|
||||
this.select();
|
||||
}
|
||||
this.updateRow();
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remap event.keyCode in horizontalLayout so that arrow
|
||||
* keys work properly and call original onKeyDown handler.
|
||||
* @param {!Event} e The browser event.
|
||||
* @return {boolean} The handled value.
|
||||
* @override
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.onKeyDown = function(e) {
|
||||
if (this.tree.toolbox_.horizontalLayout_) {
|
||||
var map = {};
|
||||
var next = Blockly.utils.KeyCodes.DOWN;
|
||||
var prev = Blockly.utils.KeyCodes.UP;
|
||||
map[Blockly.utils.KeyCodes.RIGHT] = this.rightToLeft_ ? prev : next;
|
||||
map[Blockly.utils.KeyCodes.LEFT] = this.rightToLeft_ ? next : prev;
|
||||
map[Blockly.utils.KeyCodes.UP] = Blockly.utils.KeyCodes.LEFT;
|
||||
map[Blockly.utils.KeyCodes.DOWN] = Blockly.utils.KeyCodes.RIGHT;
|
||||
|
||||
var newKeyCode = map[e.keyCode];
|
||||
Object.defineProperties(e, {
|
||||
keyCode: {value: newKeyCode || e.keyCode}
|
||||
});
|
||||
}
|
||||
return Blockly.tree.TreeNode.superClass_.onKeyDown.call(this, e);
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the handler that's triggered when the size of node has changed.
|
||||
* @param {function():?} fn The handler
|
||||
* @package
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.onSizeChanged = function(fn) {
|
||||
this.onSizeChanged_ = fn;
|
||||
};
|
||||
|
||||
/**
|
||||
* Trigger a size changed event if a handler exists.
|
||||
* @private
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.resizeToolbox_ = function() {
|
||||
if (this.onSizeChanged_) {
|
||||
this.onSizeChanged_.call(this.toolbox_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Resize the toolbox when a node is expanded.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.doNodeExpanded =
|
||||
Blockly.tree.TreeNode.prototype.resizeToolbox_;
|
||||
|
||||
/**
|
||||
* Resize the toolbox when a node is collapsed.
|
||||
* @override
|
||||
*/
|
||||
Blockly.tree.TreeNode.prototype.doNodeCollapsed =
|
||||
Blockly.tree.TreeNode.prototype.resizeToolbox_;
|
||||
+53
-30
@@ -26,6 +26,7 @@ goog.require('Blockly.Touch');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.utils.Coordinate');
|
||||
goog.require('Blockly.utils.dom');
|
||||
goog.require('Blockly.utils.toolbox');
|
||||
goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('Blockly.Xml');
|
||||
|
||||
@@ -457,10 +458,9 @@ Blockly.Flyout.prototype.hide = function() {
|
||||
|
||||
/**
|
||||
* Show and populate the flyout.
|
||||
* @param {!Blockly.utils.toolbox.ToolboxDefinition|string} flyoutDef
|
||||
* List of contents to display in the flyout as an array of xml an
|
||||
* array of Nodes, a NodeList or a string with the name of the dynamic category.
|
||||
* Variables and procedures have a custom set of blocks.
|
||||
* @param {!Blockly.utils.toolbox.FlyoutDefinition|string} flyoutDef Contents to display
|
||||
* in the flyout. This is either an array of Nodes, a NodeList, a
|
||||
* toolbox definition, or a string with the name of the dynamic category.
|
||||
*/
|
||||
Blockly.Flyout.prototype.show = function(flyoutDef) {
|
||||
this.workspace_.setResizesEnabled(false);
|
||||
@@ -468,24 +468,13 @@ Blockly.Flyout.prototype.show = function(flyoutDef) {
|
||||
this.clearOldBlocks_();
|
||||
|
||||
// Handle dynamic categories, represented by a name instead of a list.
|
||||
// Look up the correct category generation function and call that to get a
|
||||
// valid XML list.
|
||||
if (typeof flyoutDef == 'string') {
|
||||
var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback(
|
||||
flyoutDef);
|
||||
if (typeof fnToApply != 'function') {
|
||||
throw TypeError('Couldn\'t find a callback function when opening' +
|
||||
' a toolbox category.');
|
||||
}
|
||||
flyoutDef = fnToApply(this.workspace_.targetWorkspace);
|
||||
if (!Array.isArray(flyoutDef)) {
|
||||
throw TypeError('Result of toolbox category callback must be an array.');
|
||||
}
|
||||
flyoutDef = this.getDynamicCategoryContents_(flyoutDef);
|
||||
}
|
||||
this.setVisible(true);
|
||||
// Parse the Array or NodeList passed in into an Array of
|
||||
// Blockly.utils.toolbox.Toolbox.
|
||||
var parsedContent = Blockly.utils.toolbox.convertToolboxToJSON(flyoutDef);
|
||||
|
||||
// Parse the Array, Node or NodeList into a a list of flyout items.
|
||||
var parsedContent = Blockly.utils.toolbox.convertFlyoutDefToJsonArray(flyoutDef);
|
||||
var flyoutInfo =
|
||||
/** @type {{contents:!Array.<!Object>, gaps:!Array.<number>}} */ (
|
||||
this.createFlyoutInfo_(parsedContent));
|
||||
@@ -524,10 +513,10 @@ Blockly.Flyout.prototype.show = function(flyoutDef) {
|
||||
/**
|
||||
* Create the contents array and gaps array necessary to create the layout for
|
||||
* the flyout.
|
||||
* @param {Array.<Blockly.utils.toolbox.Toolbox>} parsedContent The array
|
||||
* of objects to show in the flyout.
|
||||
* @param {!Blockly.utils.toolbox.FlyoutItemInfoArray} parsedContent The array
|
||||
* of objects to show in the flyout.
|
||||
* @return {{contents:Array.<Object>, gaps:Array.<number>}} The list of contents
|
||||
* and gaps needed to lay out the flyout.
|
||||
* and gaps needed to lay out the flyout.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.createFlyoutInfo_ = function(parsedContent) {
|
||||
@@ -536,9 +525,20 @@ Blockly.Flyout.prototype.createFlyoutInfo_ = function(parsedContent) {
|
||||
this.permanentlyDisabled_.length = 0;
|
||||
var defaultGap = this.horizontalLayout ? this.GAP_X : this.GAP_Y;
|
||||
for (var i = 0, contentInfo; (contentInfo = parsedContent[i]); i++) {
|
||||
|
||||
if (contentInfo['custom']) {
|
||||
var customInfo = /** @type {!Blockly.utils.toolbox.DynamicCategoryInfo} */ (contentInfo);
|
||||
var categoryName = customInfo['custom'];
|
||||
var flyoutDef = this.getDynamicCategoryContents_(categoryName);
|
||||
var parsedDynamicContent = /** @type {!Blockly.utils.toolbox.FlyoutItemInfoArray} */
|
||||
(Blockly.utils.toolbox.convertFlyoutDefToJsonArray(flyoutDef));
|
||||
parsedContent.splice.apply(parsedContent, [i, 1].concat(parsedDynamicContent));
|
||||
contentInfo = parsedContent[i];
|
||||
}
|
||||
|
||||
switch (contentInfo['kind'].toUpperCase()) {
|
||||
case 'BLOCK':
|
||||
var blockInfo = /** @type {Blockly.utils.toolbox.Block} */ (contentInfo);
|
||||
var blockInfo = /** @type {!Blockly.utils.toolbox.BlockInfo} */ (contentInfo);
|
||||
var blockXml = this.getBlockXml_(blockInfo);
|
||||
var block = this.createBlock_(blockXml);
|
||||
// This is a deprecated method for adding gap to a block.
|
||||
@@ -548,19 +548,18 @@ Blockly.Flyout.prototype.createFlyoutInfo_ = function(parsedContent) {
|
||||
contents.push({type: 'block', block: block});
|
||||
break;
|
||||
case 'SEP':
|
||||
var sepInfo = /** @type {Blockly.utils.toolbox.Separator} */ (contentInfo);
|
||||
var sepInfo = /** @type {!Blockly.utils.toolbox.SeparatorInfo} */ (contentInfo);
|
||||
this.addSeparatorGap_(sepInfo, gaps, defaultGap);
|
||||
break;
|
||||
case 'LABEL':
|
||||
var labelInfo = /** @type {Blockly.utils.toolbox.Label} */ (contentInfo);
|
||||
var labelInfo = /** @type {!Blockly.utils.toolbox.LabelInfo} */ (contentInfo);
|
||||
// A label is a button with different styling.
|
||||
// Rename this function.
|
||||
var label = this.createButton_(labelInfo, /** isLabel */ true);
|
||||
contents.push({type: 'button', button: label});
|
||||
gaps.push(defaultGap);
|
||||
break;
|
||||
case 'BUTTON':
|
||||
var buttonInfo = /** @type {Blockly.utils.toolbox.Button} */ (contentInfo);
|
||||
var buttonInfo = /** @type {!Blockly.utils.toolbox.ButtonInfo} */ (contentInfo);
|
||||
var button = this.createButton_(buttonInfo, /** isLabel */ false);
|
||||
contents.push({type: 'button', button: button});
|
||||
gaps.push(defaultGap);
|
||||
@@ -570,9 +569,31 @@ Blockly.Flyout.prototype.createFlyoutInfo_ = function(parsedContent) {
|
||||
return {contents: contents, gaps: gaps};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the flyout definition for the dynamic category.
|
||||
* @param {string} categoryName The name of the dynamic category.
|
||||
* @return {!Array.<!Element>} The array of flyout items.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Flyout.prototype.getDynamicCategoryContents_ = function(categoryName) {
|
||||
// Look up the correct category generation function and call that to get a
|
||||
// valid XML list.
|
||||
var fnToApply = this.workspace_.targetWorkspace.getToolboxCategoryCallback(
|
||||
categoryName);
|
||||
if (typeof fnToApply != 'function') {
|
||||
throw TypeError('Couldn\'t find a callback function when opening' +
|
||||
' a toolbox category.');
|
||||
}
|
||||
var flyoutDef = fnToApply(this.workspace_.targetWorkspace);
|
||||
if (!Array.isArray(flyoutDef)) {
|
||||
throw new TypeError('Result of toolbox category callback must be an array.');
|
||||
}
|
||||
return flyoutDef;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a flyout button or a flyout label.
|
||||
* @param {!Blockly.utils.toolbox.Button|!Blockly.utils.toolbox.Label} btnInfo
|
||||
* @param {!Blockly.utils.toolbox.ButtonOrLabelInfo} btnInfo
|
||||
* The object holding information about a button or a label.
|
||||
* @param {boolean} isLabel True if the button is a label, false otherwise.
|
||||
* @return {!Blockly.FlyoutButton} The object used to display the button in the
|
||||
@@ -609,7 +630,7 @@ Blockly.Flyout.prototype.createBlock_ = function(blockXml) {
|
||||
|
||||
/**
|
||||
* Get the xml from the block info object.
|
||||
* @param {!Blockly.utils.toolbox.Block} blockInfo The object holding
|
||||
* @param {!Blockly.utils.toolbox.BlockInfo} blockInfo The object holding
|
||||
* information about a block.
|
||||
* @return {!Element} The xml for the block.
|
||||
* @throws {Error} if the xml is not a valid block definition.
|
||||
@@ -623,10 +644,12 @@ Blockly.Flyout.prototype.getBlockXml_ = function(blockInfo) {
|
||||
blockElement = blockXml;
|
||||
} else if (blockXml && typeof blockXml == 'string') {
|
||||
blockElement = Blockly.Xml.textToDom(blockXml);
|
||||
blockInfo['blockxml'] = blockElement;
|
||||
} else if (blockInfo['type']) {
|
||||
blockElement = Blockly.utils.xml.createElement('xml');
|
||||
blockElement.setAttribute('type', blockInfo['type']);
|
||||
blockElement.setAttribute('disabled', blockInfo['disabled']);
|
||||
blockInfo['blockxml'] = blockElement;
|
||||
}
|
||||
|
||||
if (!blockElement) {
|
||||
@@ -637,7 +660,7 @@ Blockly.Flyout.prototype.getBlockXml_ = function(blockInfo) {
|
||||
|
||||
/**
|
||||
* Add the necessary gap in the flyout for a separator.
|
||||
* @param {!Blockly.utils.toolbox.Separator} sepInfo The object holding
|
||||
* @param {!Blockly.utils.toolbox.SeparatorInfo} sepInfo The object holding
|
||||
* information about a separator.
|
||||
* @param {!Array.<number>} gaps The list gaps between items in the flyout.
|
||||
* @param {number} defaultGap The default gap between the button and next element.
|
||||
|
||||
+16
-2
@@ -23,7 +23,7 @@ goog.require('Blockly.utils.dom');
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to place this
|
||||
* button.
|
||||
* @param {!Blockly.WorkspaceSvg} targetWorkspace The flyout's target workspace.
|
||||
* @param {!Blockly.utils.toolbox.Button|!Blockly.utils.toolbox.Label} json
|
||||
* @param {!Blockly.utils.toolbox.ButtonOrLabelInfo} json
|
||||
* The JSON specifying the label/button.
|
||||
* @param {boolean} isLabel Whether this button should be styled as a label.
|
||||
* @constructor
|
||||
@@ -88,7 +88,7 @@ Blockly.FlyoutButton = function(workspace, targetWorkspace, json, isLabel) {
|
||||
|
||||
/**
|
||||
* The JSON specifying the label / button.
|
||||
* @type {!Blockly.utils.toolbox.Button|!Blockly.utils.toolbox.Label}
|
||||
* @type {!Blockly.utils.toolbox.ButtonOrLabelInfo}
|
||||
*/
|
||||
this.info = json;
|
||||
};
|
||||
@@ -227,6 +227,13 @@ Blockly.FlyoutButton.prototype.moveTo = function(x, y) {
|
||||
this.updateTransform_();
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {boolean} Whether or not the button is a label.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.isLabel = function() {
|
||||
return this.isLabel_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Location of the button.
|
||||
* @return {!Blockly.utils.Coordinate} x, y coordinates.
|
||||
@@ -236,6 +243,13 @@ Blockly.FlyoutButton.prototype.getPosition = function() {
|
||||
return this.position_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @return {string} Text of the button.
|
||||
*/
|
||||
Blockly.FlyoutButton.prototype.getButtonText = function() {
|
||||
return this.text_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the button's target workspace.
|
||||
* @return {!Blockly.WorkspaceSvg} The target workspace of the flyout where this
|
||||
|
||||
@@ -13,7 +13,12 @@
|
||||
|
||||
goog.provide('Blockly.IFlyout');
|
||||
|
||||
goog.requireType('Blockly.BlockSvg');
|
||||
goog.requireType('Blockly.IRegistrable');
|
||||
goog.requireType('Blockly.utils.dom');
|
||||
goog.requireType('Blockly.utils.Coordinate');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
goog.requireType('Blockly.WorkspaceSvg');
|
||||
|
||||
|
||||
/**
|
||||
@@ -130,10 +135,9 @@ Blockly.IFlyout.prototype.hide;
|
||||
|
||||
/**
|
||||
* Show and populate the flyout.
|
||||
* @param {!Blockly.utils.toolbox.ToolboxDefinition|string} flyoutDef
|
||||
* List of contents to display in the flyout as an array of xml an
|
||||
* array of Nodes, a NodeList or a string with the name of the dynamic category.
|
||||
* Variables and procedures have a custom set of blocks.
|
||||
* @param {!Blockly.utils.toolbox.FlyoutDefinition|string} flyoutDef Contents to
|
||||
* display in the flyout. This is either an array of Nodes, a NodeList, a
|
||||
* toolbox definition, or a string with the name of the dynamic category.
|
||||
*/
|
||||
Blockly.IFlyout.prototype.show;
|
||||
|
||||
|
||||
@@ -15,6 +15,9 @@ goog.provide('Blockly.IToolbox');
|
||||
|
||||
goog.requireType('Blockly.IFlyout');
|
||||
goog.requireType('Blockly.IRegistrable');
|
||||
goog.requireType('Blockly.IToolboxItem');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
goog.requireType('Blockly.WorkspaceSvg');
|
||||
|
||||
|
||||
/**
|
||||
@@ -31,44 +34,58 @@ Blockly.IToolbox = function() {};
|
||||
Blockly.IToolbox.prototype.init;
|
||||
|
||||
/**
|
||||
* Fill the toolbox with categories and blocks.
|
||||
* @param {Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef Array holding objects
|
||||
* containing information on the contents of the toolbox.
|
||||
* Fills the toolbox with new toolbox items and removes any old contents.
|
||||
* @param {!Blockly.utils.toolbox.ToolboxInfo} toolboxDef Object holding information
|
||||
* for creating a toolbox.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.render;
|
||||
|
||||
/**
|
||||
* Dispose of this toolbox.
|
||||
* @return {void}
|
||||
*/
|
||||
Blockly.IToolbox.prototype.dispose;
|
||||
|
||||
/**
|
||||
* Get the width of the toolbox.
|
||||
* Gets the width of the toolbox.
|
||||
* @return {number} The width of the toolbox.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.getWidth;
|
||||
|
||||
/**
|
||||
* Get the height of the toolbox.
|
||||
* Gets the height of the toolbox.
|
||||
* @return {number} The width of the toolbox.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.getHeight;
|
||||
|
||||
/**
|
||||
* Get the toolbox flyout.
|
||||
* @return {Blockly.IFlyout} The toolbox flyout.
|
||||
* Gets the toolbox flyout.
|
||||
* @return {?Blockly.IFlyout} The toolbox flyout.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.getFlyout;
|
||||
|
||||
/**
|
||||
* Move the toolbox to the edge.
|
||||
* Gets the workspace for the toolbox.
|
||||
* @return {!Blockly.WorkspaceSvg} The parent workspace for the toolbox.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.getWorkspace;
|
||||
|
||||
/**
|
||||
* Gets whether or not the toolbox is horizontal.
|
||||
* @return {boolean} True if the toolbox is horizontal, false if the toolbox is
|
||||
* vertical.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.isHorizontal;
|
||||
|
||||
/**
|
||||
* Positions the toolbox based on whether it is a horizontal toolbox and whether
|
||||
* the workspace is in rtl.
|
||||
* @return {void}
|
||||
*/
|
||||
Blockly.IToolbox.prototype.position;
|
||||
|
||||
/**
|
||||
* Unhighlight any previously specified option.
|
||||
* Handles resizing the toolbox when a toolbox item resizes.
|
||||
* @return {void}
|
||||
*/
|
||||
Blockly.IToolbox.prototype.handleToolboxItemResize;
|
||||
|
||||
/**
|
||||
* Unhighlights any previously selected item.
|
||||
* @return {void}
|
||||
*/
|
||||
Blockly.IToolbox.prototype.clearSelection;
|
||||
@@ -80,7 +97,7 @@ Blockly.IToolbox.prototype.clearSelection;
|
||||
Blockly.IToolbox.prototype.refreshTheme;
|
||||
|
||||
/**
|
||||
* Update the flyout's contents without closing it. Should be used in response
|
||||
* Updates the flyout's content without closing it. Should be used in response
|
||||
* to a change in one of the dynamic categories, such as variables or
|
||||
* procedures.
|
||||
* @return {void}
|
||||
@@ -88,13 +105,27 @@ Blockly.IToolbox.prototype.refreshTheme;
|
||||
Blockly.IToolbox.prototype.refreshSelection;
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the toolbox.
|
||||
* @param {boolean} isVisible True if the toolbox should be visible.
|
||||
* Sets the visibility of the toolbox.
|
||||
* @param {boolean} isVisible True if toolbox should be visible.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.setVisible;
|
||||
|
||||
/**
|
||||
* Select the first toolbox category if no category is selected.
|
||||
* Selects the toolbox item by it's position in the list of toolbox items.
|
||||
* @param {number} position The position of the item to select.
|
||||
* @return {void}
|
||||
*/
|
||||
Blockly.IToolbox.prototype.selectFirstCategory;
|
||||
Blockly.IToolbox.prototype.selectItemByPosition;
|
||||
|
||||
/**
|
||||
* Gets the selected item.
|
||||
* @return {?Blockly.IToolboxItem} The selected item, or null if no item is
|
||||
* currently selected.
|
||||
*/
|
||||
Blockly.IToolbox.prototype.getSelectedItem;
|
||||
|
||||
/**
|
||||
* Disposes of this toolbox.
|
||||
* @return {void}
|
||||
*/
|
||||
Blockly.IToolbox.prototype.dispose;
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview The interface for a toolbox item.
|
||||
* @author aschmiedt@google.com (Abby Schmiedt)
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.ICollapsibleToolboxItem');
|
||||
goog.provide('Blockly.ISelectableToolboxItem');
|
||||
goog.provide('Blockly.IToolboxItem');
|
||||
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
|
||||
|
||||
/**
|
||||
* Interface for an item in the toolbox.
|
||||
* @interface
|
||||
*/
|
||||
Blockly.IToolboxItem = function() {};
|
||||
|
||||
/**
|
||||
* Initializes the toolbox item.
|
||||
* This includes creating the dom and updating the state of any items based
|
||||
* on the info object.
|
||||
* @return {void}
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.init;
|
||||
|
||||
/**
|
||||
* Gets the div for the toolbox item.
|
||||
* @return {?Element} The div for the toolbox item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.getDiv;
|
||||
|
||||
/**
|
||||
* Gets a unique identifier for this toolbox item.
|
||||
* @return {string} The id for the toolbox item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.getId;
|
||||
|
||||
/**
|
||||
* Gets the parent if the toolbox item is nested.
|
||||
* @return {?Blockly.IToolboxItem} The parent toolbox item, or null if
|
||||
* this toolbox item is not nested.
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.getParent;
|
||||
|
||||
/**
|
||||
* Gets the nested level of the category.
|
||||
* @return {number} The nested level of the category.
|
||||
* @package
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.getLevel;
|
||||
|
||||
/**
|
||||
* Whether the toolbox item is selectable.
|
||||
* @return {boolean} True if the toolbox item can be selected.
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.isSelectable;
|
||||
|
||||
/**
|
||||
* Whether the toolbox item is collapsible.
|
||||
* @return {boolean} True if the toolbox item is collapsible.
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.isCollapsible;
|
||||
|
||||
/**
|
||||
* Dispose of this toolbox item. No-op by default.
|
||||
* @public
|
||||
*/
|
||||
Blockly.IToolboxItem.prototype.dispose;
|
||||
|
||||
/**
|
||||
* Interface for an item in the toolbox that can be selected.
|
||||
* @extends {Blockly.IToolboxItem}
|
||||
* @interface
|
||||
*/
|
||||
Blockly.ISelectableToolboxItem = function() {};
|
||||
|
||||
/**
|
||||
* Gets the name of the toolbox item. Used for emitting events.
|
||||
* @return {string} The name of the toolbox item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ISelectableToolboxItem.prototype.getName;
|
||||
|
||||
/**
|
||||
* Gets the contents of the toolbox item. These are items that are meant to be
|
||||
* displayed in the flyout.
|
||||
* @return {!Blockly.utils.toolbox.FlyoutItemInfoArray|string} The definition
|
||||
* of items to be displayed in the flyout.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ISelectableToolboxItem.prototype.getContents;
|
||||
|
||||
/**
|
||||
* Sets the current toolbox item as selected.
|
||||
* @param {boolean} _isSelected True if this category is selected, false
|
||||
* otherwise.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ISelectableToolboxItem.prototype.setSelected;
|
||||
|
||||
/**
|
||||
* Handles when the toolbox item is clicked.
|
||||
* @param {!Event} _e Click event to handle.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ISelectableToolboxItem.prototype.onClick;
|
||||
|
||||
/**
|
||||
* Interface for an item in the toolbox that can be collapsed.
|
||||
* @extends {Blockly.ISelectableToolboxItem}
|
||||
* @interface
|
||||
*/
|
||||
Blockly.ICollapsibleToolboxItem = function() {};
|
||||
|
||||
/**
|
||||
* Gets any children toolbox items. (ex. Gets the subcategories)
|
||||
* @return {!Array<!Blockly.IToolboxItem>} The child toolbox items.
|
||||
*/
|
||||
Blockly.ICollapsibleToolboxItem.prototype.getChildToolboxItems;
|
||||
|
||||
/**
|
||||
* Whether the toolbox item is expanded to show its child subcategories.
|
||||
* @return {boolean} True if the toolbox item shows its children, false if it
|
||||
* is collapsed.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ICollapsibleToolboxItem.prototype.isExpanded;
|
||||
|
||||
/**
|
||||
* Toggles whether or not the toolbox item is expanded.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ICollapsibleToolboxItem.prototype.toggleExpanded;
|
||||
@@ -131,7 +131,9 @@ Blockly.navigation.focusToolbox_ = function() {
|
||||
if (!Blockly.navigation.getMarker().getCurNode()) {
|
||||
Blockly.navigation.markAtCursor_();
|
||||
}
|
||||
toolbox.selectFirstCategory();
|
||||
if (!toolbox.getSelectedItem()) {
|
||||
toolbox.selectItemByPosition(0);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-1
@@ -28,6 +28,7 @@ goog.require('Blockly.WorkspaceSvg');
|
||||
goog.require('Blockly.Xml');
|
||||
|
||||
goog.requireType('Blockly.utils.Metrics');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
|
||||
|
||||
/**
|
||||
@@ -173,7 +174,7 @@ Blockly.Mutator.prototype.createEditor_ = function() {
|
||||
var hasFlyout = !!quarkXml;
|
||||
if (hasFlyout) {
|
||||
workspaceOptions.languageTree =
|
||||
Blockly.utils.toolbox.convertToolboxToJSON(quarkXml);
|
||||
Blockly.utils.toolbox.convertToolboxDefToJson(quarkXml);
|
||||
workspaceOptions.getMetrics = this.getFlyoutMetrics_.bind(this);
|
||||
}
|
||||
this.workspace_ = new Blockly.WorkspaceSvg(workspaceOptions);
|
||||
|
||||
+5
-40
@@ -33,7 +33,7 @@ goog.require('Blockly.Xml');
|
||||
Blockly.Options = function(options) {
|
||||
var readOnly = !!options['readOnly'];
|
||||
if (readOnly) {
|
||||
var toolboxContents = null;
|
||||
var toolboxJsonDef = null;
|
||||
var hasCategories = false;
|
||||
var hasTrashcan = false;
|
||||
var hasCollapse = false;
|
||||
@@ -41,12 +41,8 @@ Blockly.Options = function(options) {
|
||||
var hasDisable = false;
|
||||
var hasSounds = false;
|
||||
} else {
|
||||
var toolboxDef = options['toolbox'];
|
||||
if (!Array.isArray(toolboxDef)) {
|
||||
toolboxDef = Blockly.Options.parseToolboxTree(toolboxDef || null);
|
||||
}
|
||||
var toolboxContents = Blockly.utils.toolbox.convertToolboxToJSON(toolboxDef);
|
||||
var hasCategories = Blockly.utils.toolbox.hasCategories(toolboxContents);
|
||||
var toolboxJsonDef = Blockly.utils.toolbox.convertToolboxDefToJson(options['toolbox']);
|
||||
var hasCategories = Blockly.utils.toolbox.hasCategories(toolboxJsonDef);
|
||||
var hasTrashcan = options['trashcan'];
|
||||
if (hasTrashcan === undefined) {
|
||||
hasTrashcan = hasCategories;
|
||||
@@ -148,8 +144,8 @@ Blockly.Options = function(options) {
|
||||
this.hasCss = hasCss;
|
||||
/** @type {boolean} */
|
||||
this.horizontalLayout = horizontalLayout;
|
||||
/** @type {Array.<Blockly.utils.toolbox.Toolbox>} */
|
||||
this.languageTree = toolboxContents;
|
||||
/** @type {?Blockly.utils.toolbox.ToolboxInfo} */
|
||||
this.languageTree = toolboxJsonDef;
|
||||
/** @type {!Blockly.Options.GridOptions} */
|
||||
this.gridOptions = Blockly.Options.parseGridOptions_(options);
|
||||
/** @type {!Blockly.Options.ZoomOptions} */
|
||||
@@ -362,34 +358,3 @@ Blockly.Options.parseThemeOptions_ = function(options) {
|
||||
return Blockly.Theme.defineTheme(theme.name ||
|
||||
('builtin' + Blockly.utils.IdGenerator.getNextUniqueId()), theme);
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the provided toolbox tree into a consistent DOM format.
|
||||
* @param {Node|NodeList|?string} tree DOM tree of blocks, or text representation
|
||||
* of same.
|
||||
* @return {Node} DOM tree of blocks, or null.
|
||||
*/
|
||||
Blockly.Options.parseToolboxTree = function(tree) {
|
||||
if (tree) {
|
||||
if (typeof tree != 'string') {
|
||||
if (Blockly.utils.userAgent.IE && tree.outerHTML) {
|
||||
// In this case the tree will not have been properly built by the
|
||||
// browser. The HTML will be contained in the element, but it will
|
||||
// not have the proper DOM structure since the browser doesn't support
|
||||
// XSLTProcessor (XML -> HTML).
|
||||
tree = tree.outerHTML;
|
||||
} else if (!(tree instanceof Element)) {
|
||||
tree = null;
|
||||
}
|
||||
}
|
||||
if (typeof tree == 'string') {
|
||||
tree = Blockly.Xml.textToDom(tree);
|
||||
if (tree.nodeName.toLowerCase() != 'xml') {
|
||||
throw TypeError('Toolbox should be an <xml> document.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
tree = null;
|
||||
}
|
||||
return tree;
|
||||
};
|
||||
|
||||
+35
-11
@@ -20,7 +20,6 @@ goog.requireType('Blockly.IConnectionChecker');
|
||||
goog.requireType('Blockly.IFlyout');
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
goog.requireType('Blockly.Theme');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
|
||||
|
||||
/**
|
||||
@@ -80,6 +79,9 @@ Blockly.registry.Type.TOOLBOX = new Blockly.registry.Type('toolbox');
|
||||
/** @type {!Blockly.registry.Type<Blockly.Theme>} */
|
||||
Blockly.registry.Type.THEME = new Blockly.registry.Type('theme');
|
||||
|
||||
/** @type {!Blockly.registry.Type<Blockly.ToolboxItem>} */
|
||||
Blockly.registry.Type.TOOLBOX_ITEM = new Blockly.registry.Type('toolboxItem');
|
||||
|
||||
/** @type {!Blockly.registry.Type<Blockly.IFlyout>} */
|
||||
Blockly.registry.Type.FLYOUTS_VERTICAL_TOOLBOX =
|
||||
new Blockly.registry.Type('flyoutsVerticalToolbox');
|
||||
@@ -90,16 +92,18 @@ Blockly.registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX =
|
||||
|
||||
/**
|
||||
* Registers a class based on a type and name.
|
||||
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* @param {string|!Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* (e.g. Field, Renderer)
|
||||
* @param {string} name The plugin's name. (Ex. field_angle, geras)
|
||||
* @param {?function(new:T, ...?)|Object} registryItem The class or object to
|
||||
* register.
|
||||
* @param {boolean=} opt_quiet True to prevent an error when overriding an
|
||||
* already registered item.
|
||||
* @throws {Error} if the type or name is empty, a name with the given type has
|
||||
* already been registered, or if the given class or object is not valid for it's type.
|
||||
* @template T
|
||||
*/
|
||||
Blockly.registry.register = function(type, name, registryItem) {
|
||||
Blockly.registry.register = function(type, name, registryItem, opt_quiet) {
|
||||
if ((!(type instanceof Blockly.registry.Type) && typeof type != 'string') || String(type).trim() == '') {
|
||||
throw Error('Invalid type "' + type + '". The type must be a' +
|
||||
' non-empty string or a Blockly.registry.Type.');
|
||||
@@ -123,8 +127,8 @@ Blockly.registry.register = function(type, name, registryItem) {
|
||||
// Validate that the given class has all the required properties.
|
||||
Blockly.registry.validate_(type, registryItem);
|
||||
|
||||
// If the name already exists throw an error.
|
||||
if (typeRegistry[name]) {
|
||||
// Don't throw an error if opt_quiet is true.
|
||||
if (!opt_quiet && typeRegistry[name]) {
|
||||
throw Error('Name "' + name + '" with type "' + type + '" already registered.');
|
||||
}
|
||||
typeRegistry[name] = registryItem;
|
||||
@@ -150,7 +154,7 @@ Blockly.registry.validate_ = function(type, registryItem) {
|
||||
|
||||
/**
|
||||
* Unregisters the registry item with the given type and name.
|
||||
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* @param {string|!Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* (e.g. Field, Renderer)
|
||||
* @param {string} name The plugin's name. (Ex. field_angle, geras)
|
||||
* @template T
|
||||
@@ -172,8 +176,8 @@ Blockly.registry.unregister = function(type, name) {
|
||||
|
||||
/**
|
||||
* Gets the registry item for the given name and type. This can be either a
|
||||
* class or an object.l
|
||||
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* class or an object.
|
||||
* @param {string|!Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* (e.g. Field, Renderer)
|
||||
* @param {string} name The plugin's name. (Ex. field_angle, geras)
|
||||
* @return {?function(new:T, ...?)|Object} The class or object with the given
|
||||
@@ -195,9 +199,29 @@ Blockly.registry.getItem_ = function(type, name) {
|
||||
return typeRegistry[name];
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether or not the registry contains an item with the given type and
|
||||
* name.
|
||||
* @param {string|!Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* (e.g. Field, Renderer)
|
||||
* @param {string} name The plugin's name. (Ex. field_angle, geras)
|
||||
* @return {boolean} True if the registry has an item with the given type and
|
||||
* name, false otherwise.
|
||||
* @template T
|
||||
*/
|
||||
Blockly.registry.hasItem = function(type, name) {
|
||||
type = String(type).toLowerCase();
|
||||
name = name.toLowerCase();
|
||||
var typeRegistry = Blockly.registry.typeMap_[type];
|
||||
if (!typeRegistry) {
|
||||
return false;
|
||||
}
|
||||
return !!(typeRegistry[name]);
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the class for the given name and type.
|
||||
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* @param {string|!Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* (e.g. Field, Renderer)
|
||||
* @param {string} name The plugin's name. (Ex. field_angle, geras)
|
||||
* @return {?function(new:T, ...?)} The class with the given name and type or
|
||||
@@ -210,7 +234,7 @@ Blockly.registry.getClass = function(type, name) {
|
||||
|
||||
/**
|
||||
* Gets the object for the given name and type.
|
||||
* @param {string|Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* @param {string|!Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* (e.g. Category)
|
||||
* @param {string} name The plugin's name. (Ex. logic_category)
|
||||
* @returns {T} The object with the given name and type or null if none exists.
|
||||
@@ -223,7 +247,7 @@ Blockly.registry.getObject = function(type, name) {
|
||||
/**
|
||||
* Gets the class from Blockly options for the given type.
|
||||
* This is used for plugins that override a built in feature. (e.g. Toolbox)
|
||||
* @param {Blockly.registry.Type<T>} type The type of the plugin.
|
||||
* @param {!Blockly.registry.Type<T>} 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.
|
||||
|
||||
-943
@@ -1,943 +0,0 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2011 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Toolbox from whence to create blocks.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Toolbox');
|
||||
|
||||
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');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.utils.aria');
|
||||
goog.require('Blockly.utils.colour');
|
||||
goog.require('Blockly.utils.dom');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.Rect');
|
||||
goog.require('Blockly.utils.toolbox');
|
||||
|
||||
goog.requireType('Blockly.IBlocklyActionable');
|
||||
goog.requireType('Blockly.IDeleteArea');
|
||||
goog.requireType('Blockly.IFlyout');
|
||||
goog.requireType('Blockly.IStyleable');
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a Toolbox.
|
||||
* Creates the toolbox's DOM.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to create new
|
||||
* blocks.
|
||||
* @constructor
|
||||
* @implements {Blockly.IBlocklyActionable}
|
||||
* @implements {Blockly.IDeleteArea}
|
||||
* @implements {Blockly.IStyleable}
|
||||
* @implements {Blockly.IToolbox}
|
||||
*/
|
||||
Blockly.Toolbox = function(workspace) {
|
||||
/**
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @private
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* Is RTL vs LTR.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.RTL = workspace.options.RTL;
|
||||
|
||||
/**
|
||||
* Whether the toolbox should be laid out horizontally.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = workspace.options.horizontalLayout;
|
||||
|
||||
/**
|
||||
* Position of the toolbox and flyout relative to the workspace.
|
||||
* @type {number}
|
||||
*/
|
||||
this.toolboxPosition = workspace.options.toolboxPosition;
|
||||
|
||||
/**
|
||||
* Configuration constants for Closure's tree UI.
|
||||
* @type {!Object.<string,*>}
|
||||
* @private
|
||||
*/
|
||||
this.config_ = {
|
||||
indentWidth: 19,
|
||||
cssRoot: 'blocklyTreeRoot',
|
||||
cssHideRoot: 'blocklyHidden',
|
||||
cssTreeRow: 'blocklyTreeRow',
|
||||
cssItemLabel: 'blocklyTreeLabel',
|
||||
cssTreeIcon: 'blocklyTreeIcon',
|
||||
cssExpandedFolderIcon: 'blocklyTreeIconOpen',
|
||||
cssFileIcon: 'blocklyTreeIconNone',
|
||||
cssSelectedRow: 'blocklyTreeSelected'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Configuration constants for tree separator.
|
||||
* @type {!Object.<string,*>}
|
||||
* @private
|
||||
*/
|
||||
this.treeSeparatorConfig_ = {
|
||||
cssTreeRow: 'blocklyTreeSeparator'
|
||||
};
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
this.config_['cssTreeRow'] =
|
||||
this.config_['cssTreeRow'] +
|
||||
(workspace.RTL ?
|
||||
' blocklyHorizontalTreeRtl' : ' blocklyHorizontalTree');
|
||||
|
||||
this.treeSeparatorConfig_['cssTreeRow'] =
|
||||
'blocklyTreeSeparatorHorizontal ' +
|
||||
(workspace.RTL ?
|
||||
'blocklyHorizontalTreeRtl' : 'blocklyHorizontalTree');
|
||||
this.config_['cssTreeIcon'] = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The toolbox flyout.
|
||||
* @type {Blockly.IFlyout}
|
||||
* @private
|
||||
*/
|
||||
this.flyout_ = null;
|
||||
|
||||
/**
|
||||
* Width of the toolbox, which changes only in vertical layout.
|
||||
* @type {number}
|
||||
*/
|
||||
this.width = 0;
|
||||
|
||||
/**
|
||||
* Height of the toolbox, which changes only in horizontal layout.
|
||||
* @type {number}
|
||||
*/
|
||||
this.height = 0;
|
||||
|
||||
/**
|
||||
* The TreeNode most recently selected.
|
||||
* @type {Blockly.tree.BaseNode}
|
||||
* @private
|
||||
*/
|
||||
this.lastCategory_ = null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the toolbox.
|
||||
* @throws {Error} If missing a require for both `Blockly.HorizontalFlyout` and
|
||||
* `Blockly.VerticalFlyout`.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.init = function() {
|
||||
var workspace = this.workspace_;
|
||||
var svg = this.workspace_.getParentSvg();
|
||||
|
||||
/**
|
||||
* HTML container for the Toolbox menu.
|
||||
* @type {Element}
|
||||
*/
|
||||
this.HtmlDiv = document.createElement('div');
|
||||
this.HtmlDiv.className = 'blocklyToolboxDiv blocklyNonSelectable';
|
||||
this.HtmlDiv.setAttribute('dir', workspace.RTL ? 'RTL' : 'LTR');
|
||||
svg.parentNode.insertBefore(this.HtmlDiv, svg);
|
||||
var themeManager = workspace.getThemeManager();
|
||||
themeManager.subscribe(this.HtmlDiv, 'toolboxBackgroundColour',
|
||||
'background-color');
|
||||
themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color');
|
||||
|
||||
// Clicking on toolbox closes popups.
|
||||
Blockly.bindEventWithChecks_(this.HtmlDiv, 'mousedown', this,
|
||||
function(e) {
|
||||
if (Blockly.utils.isRightButton(e) || e.target == this.HtmlDiv) {
|
||||
// Close flyout.
|
||||
Blockly.hideChaff(false);
|
||||
} else {
|
||||
// Just close popups.
|
||||
Blockly.hideChaff(true);
|
||||
}
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
}, /* opt_noCaptureIdentifier */ false, /* opt_noPreventDefault */ true);
|
||||
var workspaceOptions = new Blockly.Options(
|
||||
/** @type {!Blockly.BlocklyOptions} */
|
||||
({
|
||||
'parentWorkspace': workspace,
|
||||
'rtl': workspace.RTL,
|
||||
'oneBasedIndex': workspace.options.oneBasedIndex,
|
||||
'horizontalLayout': workspace.horizontalLayout,
|
||||
'renderer': workspace.options.renderer,
|
||||
'rendererOverrides': workspace.options.rendererOverrides
|
||||
}));
|
||||
workspaceOptions.toolboxPosition = workspace.options.toolboxPosition;
|
||||
|
||||
var FlyoutClass = null;
|
||||
if (workspace.horizontalLayout) {
|
||||
FlyoutClass = Blockly.registry.getClassFromOptions(
|
||||
Blockly.registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, workspace.options);
|
||||
} else {
|
||||
FlyoutClass = Blockly.registry.getClassFromOptions(
|
||||
Blockly.registry.Type.FLYOUTS_VERTICAL_TOOLBOX, workspace.options);
|
||||
}
|
||||
|
||||
if (!FlyoutClass) {
|
||||
throw Error('Blockly.VerticalFlyout, Blockly.HorizontalFlyout or your own' +
|
||||
' custom flyout must be required.');
|
||||
}
|
||||
|
||||
this.flyout_ = new FlyoutClass(workspaceOptions);
|
||||
|
||||
// Insert the flyout after the workspace.
|
||||
Blockly.utils.dom.insertAfter(
|
||||
this.flyout_.createDom(Blockly.utils.dom.SvgElementType.SVG), svg);
|
||||
this.flyout_.init(workspace);
|
||||
|
||||
this.config_['cssCollapsedFolderIcon'] =
|
||||
'blocklyTreeIconClosed' + (workspace.RTL ? 'Rtl' : 'Ltr');
|
||||
this.render(workspace.options.languageTree);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fill the toolbox with categories and blocks.
|
||||
* @param {Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef Array holding objects
|
||||
* containing information on the contents of the toolbox.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.render = function(toolboxDef) {
|
||||
if (this.tree_) {
|
||||
this.tree_.dispose(); // Delete any existing content.
|
||||
this.lastCategory_ = null;
|
||||
}
|
||||
var tree = new Blockly.tree.TreeControl(this,
|
||||
/** @type {!Blockly.tree.BaseNode.Config} */ (this.config_));
|
||||
this.tree_ = tree;
|
||||
tree.setSelectedItem(null);
|
||||
tree.onBeforeSelected(this.handleBeforeTreeSelected_);
|
||||
tree.onAfterSelected(this.handleAfterTreeSelected_);
|
||||
var openNode = null;
|
||||
if (toolboxDef) {
|
||||
this.tree_.contents = [];
|
||||
this.hasColours_ = false;
|
||||
openNode = this.createTree_(toolboxDef, this.tree_);
|
||||
|
||||
if (this.tree_.contents.length) {
|
||||
throw Error('Toolbox cannot have both blocks and categories ' +
|
||||
'in the root level.');
|
||||
}
|
||||
// Fire a resize event since the toolbox may have changed width and height.
|
||||
this.workspace_.resizeContents();
|
||||
}
|
||||
tree.render(this.HtmlDiv);
|
||||
if (openNode) {
|
||||
tree.setSelectedItem(openNode);
|
||||
}
|
||||
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(
|
||||
/** @type {!Element} */ (this.tree_.getElement()),
|
||||
Blockly.utils.aria.State.ORIENTATION, 'horizontal');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the toolbox tree.
|
||||
* @param {Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef List of objects
|
||||
* holding information on toolbox contents.
|
||||
* @param {!Blockly.tree.BaseNode} treeOut The output tree for the toolbox. Due
|
||||
* to the recursive nature of this function, treeOut can be either the root of
|
||||
* the tree (Blockly.tree.TreeControl) or a child node of the tree
|
||||
* (Blockly.tree.TreeNode). These nodes are built from the toolboxDef.
|
||||
* @return {Blockly.tree.BaseNode} The TreeNode to expand when the toolbox is
|
||||
* first loaded (or null).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.createTree_ = function(toolboxDef, treeOut) {
|
||||
var openNode = null;
|
||||
var lastElement = null;
|
||||
if (!toolboxDef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (var i = 0, childIn; (childIn = toolboxDef[i]); i++) {
|
||||
switch (childIn['kind'].toUpperCase()) {
|
||||
case 'CATEGORY':
|
||||
var categoryInfo = /** @type {Blockly.utils.toolbox.Category} */ (childIn);
|
||||
openNode = this.addCategory_(categoryInfo, treeOut) || openNode;
|
||||
lastElement = childIn;
|
||||
break;
|
||||
case 'SEP':
|
||||
var separatorInfo = /** @type {Blockly.utils.toolbox.Separator} */ (childIn);
|
||||
lastElement = this.addSeparator_(separatorInfo, treeOut, lastElement) || lastElement;
|
||||
break;
|
||||
case 'BLOCK':
|
||||
case 'SHADOW':
|
||||
case 'LABEL':
|
||||
case 'BUTTON':
|
||||
treeOut.contents.push(childIn);
|
||||
lastElement = childIn;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return openNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a category to the toolbox tree.
|
||||
* @param {!Blockly.utils.toolbox.Category} categoryInfo The object holding
|
||||
* information on the category.
|
||||
* @param {!Blockly.tree.BaseNode} treeOut The TreeControl or TreeNode
|
||||
* object built from the childNodes.
|
||||
* @return {Blockly.tree.BaseNode} TreeNode to open at startup (or null).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addCategory_ = function(categoryInfo, treeOut) {
|
||||
var openNode = null;
|
||||
// Decode the category name for any potential message references
|
||||
// (eg. `%{BKY_CATEGORY_NAME_LOGIC}`).
|
||||
var categoryName = Blockly.utils.replaceMessageReferences(categoryInfo['name']);
|
||||
|
||||
// Create and add the tree node for the category.
|
||||
var childOut = this.tree_.createNode(categoryName);
|
||||
childOut.onSizeChanged(this.handleNodeSizeChanged_);
|
||||
childOut.contents = [];
|
||||
treeOut.add(childOut);
|
||||
|
||||
var custom = categoryInfo['custom'];
|
||||
|
||||
if (custom) {
|
||||
// Variables and procedures are special dynamic categories.
|
||||
childOut.contents = custom;
|
||||
} else {
|
||||
openNode = this.createTree_(categoryInfo['contents'], childOut) || openNode;
|
||||
}
|
||||
this.setColourOrStyle_(categoryInfo, childOut, categoryName);
|
||||
openNode = this.setExpanded_(categoryInfo, childOut) || openNode;
|
||||
return openNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Add either the colour or the style for a category.
|
||||
* @param {!Blockly.utils.toolbox.Category} categoryInfo The object holding
|
||||
* information on the category.
|
||||
* @param {!Blockly.tree.TreeNode} childOut The TreeNode for a category.
|
||||
* @param {string} categoryName The name of the category.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.setColourOrStyle_ = function(
|
||||
categoryInfo, childOut, categoryName) {
|
||||
var styleName = categoryInfo['categorystyle'];
|
||||
var colour = categoryInfo['colour'];
|
||||
|
||||
if (colour && styleName) {
|
||||
childOut.hexColour = '';
|
||||
console.warn('Toolbox category "' + categoryName +
|
||||
'" must not have both a style and a colour');
|
||||
} else if (styleName) {
|
||||
this.setColourFromStyle_(styleName, childOut, categoryName);
|
||||
} else {
|
||||
this.setColour_(colour, childOut, categoryName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Add a separator to the toolbox tree if it is between categories. Otherwise,
|
||||
* add the separator to the list of contents.
|
||||
* @param {!Blockly.utils.toolbox.Separator} separatorInfo The object holding
|
||||
* information on the separator.
|
||||
* @param {!Blockly.tree.BaseNode} treeOut The TreeControl or TreeNode
|
||||
* object built from the childNodes.
|
||||
* @param {Object} lastElement The last element to be added to the tree.
|
||||
* @return {Object} The last element to be added to the tree, or
|
||||
* null.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addSeparator_ = function(
|
||||
separatorInfo, treeOut, lastElement) {
|
||||
if (lastElement && lastElement['kind'].toUpperCase() == 'CATEGORY') {
|
||||
// Separator between two categories.
|
||||
// <sep></sep>
|
||||
treeOut.add(new Blockly.Toolbox.TreeSeparator(
|
||||
/** @type {!Blockly.tree.BaseNode.Config} */
|
||||
(this.treeSeparatorConfig_)));
|
||||
} else {
|
||||
// Otherwise add to contents array.
|
||||
treeOut.contents.push(separatorInfo);
|
||||
return separatorInfo;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks whether a node should be expanded, and expands if necessary.
|
||||
* @param {!Blockly.utils.toolbox.Category} categoryInfo The child to expand.
|
||||
* @param {!Blockly.tree.TreeNode} childOut The TreeNode created from childIn.
|
||||
* @return {Blockly.tree.BaseNode} TreeNode to open at startup (or null).
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.setExpanded_ = function(categoryInfo, childOut) {
|
||||
var openNode = null;
|
||||
if (categoryInfo['expanded'] == 'true') {
|
||||
if (childOut.contents.length) {
|
||||
// This is a category that directly contains blocks.
|
||||
// After the tree is rendered, open this category and show flyout.
|
||||
openNode = childOut;
|
||||
}
|
||||
childOut.setExpanded(true);
|
||||
} else {
|
||||
childOut.setExpanded(false);
|
||||
}
|
||||
return openNode;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handle the before tree item selected action.
|
||||
* @param {Blockly.tree.BaseNode} node The newly selected node.
|
||||
* @return {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.contents && newNode.contents.length) {
|
||||
this.flyout_.show(newNode.contents);
|
||||
// Scroll the flyout to the top if the category has changed.
|
||||
if (this.lastCategory_ != newNode &&
|
||||
typeof this.flyout_.scrollToStart == 'function') {
|
||||
this.flyout_.scrollToStart();
|
||||
}
|
||||
if (this.workspace_.keyboardAccessibilityMode) {
|
||||
Blockly.navigation.setState(Blockly.navigation.STATE_TOOLBOX);
|
||||
}
|
||||
} else {
|
||||
// Hide the flyout.
|
||||
this.flyout_.hide();
|
||||
if (this.workspace_.keyboardAccessibilityMode &&
|
||||
!(newNode instanceof Blockly.Toolbox.TreeSeparator)) {
|
||||
Blockly.navigation.setState(Blockly.navigation.STATE_WS);
|
||||
}
|
||||
}
|
||||
if (oldNode != newNode && oldNode != this) {
|
||||
var event = new Blockly.Events.Ui(null, 'category',
|
||||
oldNode && oldNode.content, newNode && newNode.content);
|
||||
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() {
|
||||
// Reposition the workspace so that (0,0) is in the correct position relative
|
||||
// to the new absolute edge (ie toolbox edge).
|
||||
var workspace = this.workspace_;
|
||||
var rect = this.HtmlDiv.getBoundingClientRect();
|
||||
var newX = this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ?
|
||||
workspace.scrollX + rect.width : 0;
|
||||
var newY = this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ?
|
||||
workspace.scrollY + rect.height : 0;
|
||||
workspace.translate(newX, newY);
|
||||
|
||||
// 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(workspace);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the given Blockly action on a toolbox.
|
||||
* This is only triggered when keyboard accessibility mode is enabled.
|
||||
* @param {!Blockly.Action} action The action to be handled.
|
||||
* @return {boolean} True if the field handled the action, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.onBlocklyAction = function(action) {
|
||||
var selected = this.tree_.getSelectedItem();
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
switch (action.name) {
|
||||
case Blockly.navigation.actionNames.PREVIOUS:
|
||||
return selected.selectPrevious();
|
||||
case Blockly.navigation.actionNames.OUT:
|
||||
return selected.selectParent();
|
||||
case Blockly.navigation.actionNames.NEXT:
|
||||
return selected.selectNext();
|
||||
case Blockly.navigation.actionNames.IN:
|
||||
return selected.selectChild();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this toolbox.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.dispose = function() {
|
||||
this.flyout_.dispose();
|
||||
this.tree_.dispose();
|
||||
this.workspace_.getThemeManager().unsubscribe(this.HtmlDiv);
|
||||
Blockly.utils.dom.removeNode(this.HtmlDiv);
|
||||
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.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getWidth = function() {
|
||||
return this.width;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the height of the toolbox.
|
||||
* @return {number} The width of the toolbox.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getHeight = function() {
|
||||
return this.height;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the toolbox flyout.
|
||||
* @return {Blockly.IFlyout} The toolbox flyout.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getFlyout = function() {
|
||||
return this.flyout_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Move the toolbox to the edge.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.position = function() {
|
||||
var treeDiv = this.HtmlDiv;
|
||||
if (!treeDiv) {
|
||||
// Not initialized yet.
|
||||
return;
|
||||
}
|
||||
var svgSize = Blockly.svgSize(this.workspace_.getParentSvg());
|
||||
if (this.horizontalLayout_) {
|
||||
treeDiv.style.left = '0';
|
||||
treeDiv.style.height = 'auto';
|
||||
treeDiv.style.width = svgSize.width + 'px';
|
||||
this.height = treeDiv.offsetHeight;
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top
|
||||
treeDiv.style.top = '0';
|
||||
} else { // Bottom
|
||||
treeDiv.style.bottom = '0';
|
||||
}
|
||||
} else {
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right
|
||||
treeDiv.style.right = '0';
|
||||
} else { // Left
|
||||
treeDiv.style.left = '0';
|
||||
}
|
||||
treeDiv.style.height = svgSize.height + 'px';
|
||||
this.width = treeDiv.offsetWidth;
|
||||
}
|
||||
this.flyout_.position();
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.tree.TreeNode} childOut The child to set the hexColour on.
|
||||
* @param {string} categoryName Name of the toolbox category.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.setColour_ = function(colourValue, childOut,
|
||||
categoryName) {
|
||||
// Decode the colour for any potential message references
|
||||
// (eg. `%{BKY_MATH_HUE}`).
|
||||
var colour = Blockly.utils.replaceMessageReferences(colourValue);
|
||||
if (colour == null || colour === '') {
|
||||
// No attribute. No colour.
|
||||
childOut.hexColour = '';
|
||||
} else {
|
||||
var hue = Number(colour);
|
||||
if (!isNaN(hue)) {
|
||||
childOut.hexColour = Blockly.hueToHex(hue);
|
||||
this.hasColours_ = true;
|
||||
} else {
|
||||
var hex = Blockly.utils.colour.parse(colour);
|
||||
if (hex) {
|
||||
childOut.hexColour = hex;
|
||||
this.hasColours_ = true;
|
||||
} else {
|
||||
childOut.hexColour = '';
|
||||
console.warn('Toolbox category "' + categoryName +
|
||||
'" has unrecognized colour attribute: ' + colour);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.tree.TreeNode} childOut The child to set the hexColour on.
|
||||
* @param {string} categoryName Name of the toolbox category.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.setColourFromStyle_ = function(
|
||||
styleName, childOut, categoryName) {
|
||||
childOut.styleName = styleName;
|
||||
var theme = this.workspace_.getTheme();
|
||||
if (styleName && theme) {
|
||||
var style = theme.categoryStyles[styleName];
|
||||
if (style && style.colour) {
|
||||
this.setColour_(style.colour, childOut, categoryName);
|
||||
} else {
|
||||
console.warn('Style "' + styleName +
|
||||
'" must exist and contain a colour value');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Recursively updates all the category colours using the category style name.
|
||||
* @param {Blockly.tree.BaseNode=} opt_tree Starting point of tree.
|
||||
* Defaults to the root node.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.updateColourFromTheme_ = function(opt_tree) {
|
||||
var tree = opt_tree || this.tree_;
|
||||
if (tree) {
|
||||
var children = tree.getChildren(false);
|
||||
for (var i = 0, child; (child = children[i]); i++) {
|
||||
if (child.styleName) {
|
||||
this.setColourFromStyle_(child.styleName, child, '');
|
||||
this.addColour_();
|
||||
}
|
||||
this.updateColourFromTheme_(child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the category colours and background colour of selected categories.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.refreshTheme = function() {
|
||||
var tree = this.tree_;
|
||||
if (tree) {
|
||||
this.updateColourFromTheme_(tree);
|
||||
this.updateSelectedItemColour_(tree);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the background colour of the selected category.
|
||||
* @param {!Blockly.tree.BaseNode} tree Starting point of tree.
|
||||
* Defaults to the root node.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.updateSelectedItemColour_ = function(tree) {
|
||||
var selectedItem = tree.getSelectedItem();
|
||||
if (selectedItem) {
|
||||
var hexColour = selectedItem.hexColour || '#57e';
|
||||
selectedItem.getRowElement().style.backgroundColor = hexColour;
|
||||
this.addColour_(selectedItem);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Recursively add colours to this toolbox.
|
||||
* @param {Blockly.tree.BaseNode=} opt_tree Starting point of tree.
|
||||
* Defaults to the root node.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addColour_ = function(opt_tree) {
|
||||
var tree = opt_tree || this.tree_;
|
||||
var children = tree.getChildren(false);
|
||||
for (var i = 0, child; (child = children[i]); i++) {
|
||||
var element = child.getRowElement();
|
||||
if (element) {
|
||||
if (this.hasColours_) {
|
||||
var border = '8px solid ' + (child.hexColour || '#ddd');
|
||||
} else {
|
||||
var border = 'none';
|
||||
}
|
||||
if (this.workspace_.RTL) {
|
||||
element.style.borderRight = border;
|
||||
} else {
|
||||
element.style.borderLeft = border;
|
||||
}
|
||||
}
|
||||
this.addColour_(child);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Unhighlight any previously specified option.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.clearSelection = function() {
|
||||
this.tree_.setSelectedItem(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a style on the toolbox. Usually used to change the cursor.
|
||||
* @param {string} style The name of the class to add.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addStyle = function(style) {
|
||||
Blockly.utils.dom.addClass(/** @type {!Element} */ (this.HtmlDiv), style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a style from the toolbox. Usually used to change the cursor.
|
||||
* @param {string} style The name of the class to remove.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.removeStyle = function(style) {
|
||||
Blockly.utils.dom.removeClass(/** @type {!Element} */ (this.HtmlDiv), style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the deletion rectangle for this toolbox.
|
||||
* @return {Blockly.utils.Rect} Rectangle in which to delete.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getClientRect = function() {
|
||||
if (!this.HtmlDiv) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox
|
||||
// area are still deleted. Must be smaller than Infinity, but larger than
|
||||
// the largest screen size.
|
||||
var BIG_NUM = 10000000;
|
||||
var toolboxRect = this.HtmlDiv.getBoundingClientRect();
|
||||
|
||||
var top = toolboxRect.top;
|
||||
var bottom = top + toolboxRect.height;
|
||||
var left = toolboxRect.left;
|
||||
var right = left + toolboxRect.width;
|
||||
|
||||
// Assumes that the toolbox is on the SVG edge. If this changes
|
||||
// (e.g. toolboxes in mutators) then this code will need to be more complex.
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
|
||||
return new Blockly.utils.Rect(-BIG_NUM, bottom, -BIG_NUM, BIG_NUM);
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
return new Blockly.utils.Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM);
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
return new Blockly.utils.Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, right);
|
||||
} else { // Right
|
||||
return new Blockly.utils.Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
Blockly.Toolbox.prototype.refreshSelection = function() {
|
||||
var selectedItem = this.tree_.getSelectedItem();
|
||||
if (selectedItem && selectedItem.contents) {
|
||||
this.flyout_.show(selectedItem.contents);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Select the first toolbox category if no category is selected.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectFirstCategory = function() {
|
||||
var selectedItem = this.tree_.getSelectedItem();
|
||||
if (!selectedItem) {
|
||||
this.tree_.selectChild();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A blank separator node in the tree.
|
||||
* @param {!Blockly.tree.BaseNode.Config} config The configuration for the tree.
|
||||
* @constructor
|
||||
* @extends {Blockly.tree.TreeNode}
|
||||
*/
|
||||
Blockly.Toolbox.TreeSeparator = function(config) {
|
||||
Blockly.tree.TreeNode.call(this, null, '', config);
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.Toolbox.TreeSeparator,
|
||||
Blockly.tree.TreeNode);
|
||||
|
||||
/**
|
||||
* CSS for Toolbox. See css.js for use.
|
||||
*/
|
||||
Blockly.Css.register([
|
||||
/* eslint-disable indent */
|
||||
'.blocklyToolboxDelete {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxGrab {',
|
||||
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
|
||||
'cursor: grabbing;',
|
||||
'cursor: -webkit-grabbing;',
|
||||
'}',
|
||||
|
||||
/* Category tree in Toolbox. */
|
||||
'.blocklyToolboxDiv {',
|
||||
'background-color: #ddd;',
|
||||
'overflow-x: visible;',
|
||||
'overflow-y: auto;',
|
||||
'position: absolute;',
|
||||
'z-index: 70;', /* so blocks go under toolbox when dragging */
|
||||
'-webkit-tap-highlight-color: transparent;', /* issue #1345 */
|
||||
'}',
|
||||
|
||||
'.blocklyTreeRoot {',
|
||||
'padding: 4px 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeRoot:focus {',
|
||||
'outline: none;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeRow {',
|
||||
'height: 22px;',
|
||||
'line-height: 22px;',
|
||||
'margin-bottom: 3px;',
|
||||
'padding-right: 8px;',
|
||||
'white-space: nowrap;',
|
||||
'}',
|
||||
|
||||
'.blocklyHorizontalTree {',
|
||||
'float: left;',
|
||||
'margin: 1px 5px 8px 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyHorizontalTreeRtl {',
|
||||
'float: right;',
|
||||
'margin: 1px 0 8px 5px;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',
|
||||
'margin-left: 8px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeRow:not(.blocklyTreeSelected):hover {',
|
||||
'background-color: rgba(255, 255, 255, 0.2);',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSeparator {',
|
||||
'border-bottom: solid #e5e5e5 1px;',
|
||||
'height: 0;',
|
||||
'margin: 5px 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSeparatorHorizontal {',
|
||||
'border-right: solid #e5e5e5 1px;',
|
||||
'width: 0;',
|
||||
'padding: 5px 0;',
|
||||
'margin: 0 5px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIcon {',
|
||||
'background-image: url(<<<PATH>>>/sprites.png);',
|
||||
'height: 16px;',
|
||||
'vertical-align: middle;',
|
||||
'width: 16px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIconClosedLtr {',
|
||||
'background-position: -32px -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIconClosedRtl {',
|
||||
'background-position: 0 -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIconOpen {',
|
||||
'background-position: -16px -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected>.blocklyTreeIconClosedLtr {',
|
||||
'background-position: -32px -17px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected>.blocklyTreeIconClosedRtl {',
|
||||
'background-position: 0 -17px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected>.blocklyTreeIconOpen {',
|
||||
'background-position: -16px -17px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIconNone,',
|
||||
'.blocklyTreeSelected>.blocklyTreeIconNone {',
|
||||
'background-position: -48px -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeLabel {',
|
||||
'cursor: default;',
|
||||
'font: 16px sans-serif;',
|
||||
'padding: 0 3px;',
|
||||
'vertical-align: middle;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDelete .blocklyTreeLabel {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected .blocklyTreeLabel {',
|
||||
'color: #fff;',
|
||||
'}'
|
||||
/* eslint-enable indent */
|
||||
]);
|
||||
|
||||
Blockly.registry.register(Blockly.registry.Type.TOOLBOX,
|
||||
Blockly.registry.DEFAULT, Blockly.Toolbox);
|
||||
@@ -0,0 +1,690 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A toolbox category used to organize blocks in the toolbox.
|
||||
* @author aschmiedt@google.com (Abby Schmiedt)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.ToolboxCategory');
|
||||
|
||||
goog.require('Blockly.registry');
|
||||
goog.require('Blockly.utils');
|
||||
goog.require('Blockly.utils.aria');
|
||||
goog.require('Blockly.utils.dom');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.toolbox');
|
||||
goog.require('Blockly.ToolboxItem');
|
||||
|
||||
goog.requireType('Blockly.ICollapsibleToolboxItem');
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
goog.requireType('Blockly.IToolboxItem');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a category in a toolbox.
|
||||
* @param {!Blockly.utils.toolbox.CategoryInfo} categoryDef The information needed
|
||||
* to create a category in the toolbox.
|
||||
* @param {!Blockly.IToolbox} toolbox The parent toolbox for the category.
|
||||
* @param {Blockly.ICollapsibleToolboxItem=} opt_parent The parent category or null if
|
||||
* the category does not have a parent.
|
||||
* @constructor
|
||||
* @extends {Blockly.ToolboxItem}
|
||||
* @implements {Blockly.ISelectableToolboxItem}
|
||||
*/
|
||||
Blockly.ToolboxCategory = function(categoryDef, toolbox, opt_parent) {
|
||||
Blockly.ToolboxCategory.superClass_.constructor.call(
|
||||
this, categoryDef, toolbox, opt_parent);
|
||||
|
||||
/**
|
||||
* The name that will be displayed on the category.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
this.name_ = Blockly.utils.replaceMessageReferences(categoryDef['name']);
|
||||
|
||||
/**
|
||||
* The colour of the category.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
this.colour_ = this.getColour_(categoryDef);
|
||||
|
||||
/**
|
||||
* The html container for the category.
|
||||
* @type {?Element}
|
||||
* @protected
|
||||
*/
|
||||
this.htmlDiv_ = null;
|
||||
|
||||
/**
|
||||
* The html element for the category row.
|
||||
* @type {?Element}
|
||||
* @protected
|
||||
*/
|
||||
this.rowDiv_ = null;
|
||||
|
||||
/**
|
||||
* The html element that holds children elements of the category row.
|
||||
* @type {?Element}
|
||||
* @protected
|
||||
*/
|
||||
this.rowContents_ = null;
|
||||
|
||||
/**
|
||||
* The html element for the toolbox icon.
|
||||
* @type {?Element}
|
||||
* @protected
|
||||
*/
|
||||
this.iconDom_ = null;
|
||||
|
||||
/**
|
||||
* All the css class names that are used to create a category.
|
||||
* @type {!Blockly.ToolboxCategory.CssConfig}
|
||||
* @protected
|
||||
*/
|
||||
this.cssConfig_ = this.makeDefaultCssConfig_();
|
||||
|
||||
var cssConfig = categoryDef['cssconfig'] || categoryDef['cssConfig'];
|
||||
Blockly.utils.object.mixin(this.cssConfig_, cssConfig);
|
||||
|
||||
/**
|
||||
* True if the category is meant to be hidden, false otherwise.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.isHidden_ = false;
|
||||
|
||||
/**
|
||||
* True if this category is disabled, false otherwise.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.isDisabled_ = false;
|
||||
|
||||
/**
|
||||
* The flyout items for this category.
|
||||
* @type {string|!Blockly.utils.toolbox.FlyoutItemInfoArray}
|
||||
* @protected
|
||||
*/
|
||||
this.flyoutItems_ = [];
|
||||
|
||||
this.parseContents_(categoryDef);
|
||||
};
|
||||
|
||||
Blockly.utils.object.inherits(Blockly.ToolboxCategory, Blockly.ToolboxItem);
|
||||
|
||||
/**
|
||||
* All the css class names that are used to create a category.
|
||||
* @typedef {{
|
||||
* container:?string,
|
||||
* row:?string,
|
||||
* icon:?string,
|
||||
* label:?string,
|
||||
* selected:?string,
|
||||
* openIcon:?string,
|
||||
* closedIcon:?string,
|
||||
* }}
|
||||
*/
|
||||
Blockly.ToolboxCategory.CssConfig;
|
||||
|
||||
/**
|
||||
* Name used for registering a toolbox category.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.ToolboxCategory.registrationName = 'category';
|
||||
|
||||
/**
|
||||
* The number of pixels to move the category over at each nested level.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.ToolboxCategory.nestedPadding = 19;
|
||||
|
||||
/**
|
||||
* The width in pixels of the strip of colour next to each category.
|
||||
* @type {number}
|
||||
*/
|
||||
Blockly.ToolboxCategory.borderWidth = 8;
|
||||
|
||||
/**
|
||||
* The default colour of the category. This is used as the background colour of
|
||||
* the category when it is selected.
|
||||
* @type {string}
|
||||
*/
|
||||
Blockly.ToolboxCategory.defaultBackgroundColour = '#57e';
|
||||
|
||||
/**
|
||||
* Creates an object holding the default classes for a category.
|
||||
* @return {!Blockly.ToolboxCategory.CssConfig} The configuration object holding
|
||||
* all the CSS classes for a category.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.makeDefaultCssConfig_ = function() {
|
||||
return {
|
||||
'container': 'blocklyToolboxCategory',
|
||||
'row': 'blocklyTreeRow',
|
||||
'rowContentContainer': 'blocklyTreeRowContentContainer',
|
||||
'icon': 'blocklyTreeIcon',
|
||||
'label': 'blocklyTreeLabel',
|
||||
'contents': 'blocklyToolboxContents',
|
||||
'selected': 'blocklyTreeSelected',
|
||||
'openIcon': 'blocklyTreeIconOpen',
|
||||
'closedIcon': 'blocklyTreeIconClosed',
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the contents array depending on if the category is a dynamic category,
|
||||
* or if its contents are meant to be shown in the flyout.
|
||||
* @param {!Blockly.utils.toolbox.CategoryInfo} categoryDef The information needed
|
||||
* to create a category.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.parseContents_ = function(categoryDef) {
|
||||
var contents = categoryDef['contents'];
|
||||
|
||||
if (categoryDef['custom']) {
|
||||
this.flyoutItems_ = categoryDef['custom'];
|
||||
} else if (contents) {
|
||||
for (var i = 0, itemDef; (itemDef = contents[i]); i++) {
|
||||
var flyoutItem = /** @type {Blockly.utils.toolbox.FlyoutItemInfo} */ (itemDef);
|
||||
this.flyoutItems_.push(flyoutItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.init = function() {
|
||||
this.createDom_();
|
||||
if (this.toolboxItemDef_['hidden'] == 'true') {
|
||||
this.hide();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the dom for the category.
|
||||
* @return {!Element} The parent element for the category.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.createDom_ = function() {
|
||||
this.htmlDiv_ = this.createContainer_();
|
||||
Blockly.utils.aria.setRole(this.htmlDiv_, Blockly.utils.aria.Role.TREEITEM);
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.htmlDiv_),
|
||||
Blockly.utils.aria.State.SELECTED,false);
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.htmlDiv_),
|
||||
Blockly.utils.aria.State.LEVEL, this.level_);
|
||||
|
||||
this.rowDiv_ = this.createRowContainer_();
|
||||
this.rowDiv_.setAttribute('id', this.id_);
|
||||
this.rowDiv_.style.pointerEvents = 'auto';
|
||||
this.htmlDiv_.appendChild(this.rowDiv_);
|
||||
|
||||
this.rowContents_ = this.createRowContentsContainer_();
|
||||
this.rowContents_.style.pointerEvents = 'none';
|
||||
this.rowDiv_.appendChild(this.rowContents_);
|
||||
|
||||
this.iconDom_ = this.createIconDom_();
|
||||
Blockly.utils.aria.setRole(this.iconDom_, Blockly.utils.aria.Role.PRESENTATION);
|
||||
this.rowContents_.appendChild(this.iconDom_);
|
||||
|
||||
var labelDom = this.createLabelDom_(this.name_);
|
||||
this.rowContents_.appendChild(labelDom);
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.htmlDiv_),
|
||||
Blockly.utils.aria.State.LABELLEDBY, labelDom.getAttribute('id'));
|
||||
|
||||
this.addColourBorder_(this.colour_);
|
||||
|
||||
return this.htmlDiv_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the container that holds the row and any subcategories.
|
||||
* @return {!Element} The div that holds the icon and the label.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.createContainer_ = function() {
|
||||
var container = document.createElement('div');
|
||||
Blockly.utils.dom.addClass(container, this.cssConfig_['container']);
|
||||
return container;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the parent of the contents container. All clicks will happen on this
|
||||
* div.
|
||||
* @return {!Element} The div that holds the contents container.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.createRowContainer_ = function() {
|
||||
var rowDiv = document.createElement('div');
|
||||
Blockly.utils.dom.addClass(rowDiv, this.cssConfig_['row']);
|
||||
var nestedPadding = Blockly.ToolboxCategory.nestedPadding * this.getLevel();
|
||||
nestedPadding = nestedPadding.toString() + 'px';
|
||||
this.workspace_.RTL ? rowDiv.style.paddingRight = nestedPadding :
|
||||
rowDiv.style.paddingLeft = nestedPadding;
|
||||
return rowDiv;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the container for the label and icon.
|
||||
* This is necessary so we can set all subcategory pointer events to none.
|
||||
* @return {!Element} The div that holds the icon and the label.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.createRowContentsContainer_ = function() {
|
||||
var contentsContainer = document.createElement('div');
|
||||
Blockly.utils.dom.addClass(contentsContainer, this.cssConfig_['rowContentContainer']);
|
||||
return contentsContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the span that holds the category icon.
|
||||
* @return {!Element} The span that holds the category icon.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.createIconDom_ = function() {
|
||||
var toolboxIcon = document.createElement('span');
|
||||
if (!this.parentToolbox_.isHorizontal()) {
|
||||
Blockly.utils.dom.addClass(toolboxIcon, this.cssConfig_['icon']);
|
||||
}
|
||||
|
||||
toolboxIcon.style.display = 'inline-block';
|
||||
return toolboxIcon;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the span that holds the category label.
|
||||
* This should have an id for accessibility purposes.
|
||||
* @param {string} name The name of the category.
|
||||
* @return {!Element} The span that holds the category label.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.createLabelDom_ = function(name) {
|
||||
var toolboxLabel = document.createElement('span');
|
||||
toolboxLabel.setAttribute('id', this.getId() + '.label');
|
||||
toolboxLabel.textContent = name;
|
||||
Blockly.utils.dom.addClass(toolboxLabel, this.cssConfig_['label']);
|
||||
return toolboxLabel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the colour for this category.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.refreshTheme = function() {
|
||||
this.colour_ = this.getColour_(/** @type {Blockly.utils.toolbox.CategoryInfo} **/
|
||||
(this.toolboxItemDef_));
|
||||
this.addColourBorder_(this.colour_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Add the strip of colour to the toolbox category.
|
||||
* @param {string} colour The category colour.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.addColourBorder_ = function(colour) {
|
||||
if (colour) {
|
||||
var border = Blockly.ToolboxCategory.borderWidth + 'px solid ' +
|
||||
(colour || '#ddd');
|
||||
if (this.workspace_.RTL) {
|
||||
this.rowDiv_.style.borderRight = border;
|
||||
} else {
|
||||
this.rowDiv_.style.borderLeft = border;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets either the colour or the style for a category.
|
||||
* @param {!Blockly.utils.toolbox.CategoryInfo} categoryDef The object holding
|
||||
* information on the category.
|
||||
* @return {string} The hex colour for the category.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.getColour_ = function(categoryDef) {
|
||||
var styleName = categoryDef['categorystyle'] || categoryDef['categoryStyle'];
|
||||
var colour = categoryDef['colour'];
|
||||
|
||||
if (colour && styleName) {
|
||||
console.warn('Toolbox category "' + this.name_ +
|
||||
'" must not have both a style and a colour');
|
||||
} else if (styleName) {
|
||||
return this.getColourfromStyle_(styleName);
|
||||
} else {
|
||||
return this.parseColour_(colour);
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the colour for the category using the style name and returns the new
|
||||
* colour as a hex string.
|
||||
* @param {string} styleName Name of the style.
|
||||
* @return {string} The hex colour for the category.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.getColourfromStyle_ = function(styleName) {
|
||||
var theme = this.workspace_.getTheme();
|
||||
if (styleName && theme) {
|
||||
var style = theme.categoryStyles[styleName];
|
||||
if (style && style.colour) {
|
||||
return this.parseColour_(style.colour);
|
||||
} else {
|
||||
console.warn('Style "' + styleName +
|
||||
'" must exist and contain a colour value');
|
||||
}
|
||||
}
|
||||
return '';
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses 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.
|
||||
* @return {string} The hex colour for the category.
|
||||
* @private
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.parseColour_ = function(colourValue) {
|
||||
// Decode the colour for any potential message references
|
||||
// (eg. `%{BKY_MATH_HUE}`).
|
||||
var colour = Blockly.utils.replaceMessageReferences(colourValue);
|
||||
if (colour == null || colour === '') {
|
||||
// No attribute. No colour.
|
||||
return '';
|
||||
} else {
|
||||
var hue = Number(colour);
|
||||
if (!isNaN(hue)) {
|
||||
return Blockly.hueToHex(hue);
|
||||
} else {
|
||||
var hex = Blockly.utils.colour.parse(colour);
|
||||
if (hex) {
|
||||
return hex;
|
||||
} else {
|
||||
console.warn('Toolbox category "' + this.name_ +
|
||||
'" has unrecognized colour attribute: ' + colour);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds appropriate classes to display an open icon.
|
||||
* @param {?Element} iconDiv The div that holds the icon.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.openIcon_ = function(iconDiv) {
|
||||
if (!iconDiv) {
|
||||
return;
|
||||
}
|
||||
Blockly.utils.dom.removeClasses(iconDiv, this.cssConfig_['closedIcon']);
|
||||
Blockly.utils.dom.addClass(iconDiv, this.cssConfig_['openIcon']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds appropriate classes to display a closed icon.
|
||||
* @param {?Element} iconDiv The div that holds the icon.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.closeIcon_ = function(iconDiv) {
|
||||
if (!iconDiv) {
|
||||
return;
|
||||
}
|
||||
Blockly.utils.dom.removeClasses(iconDiv, this.cssConfig_['openIcon']);
|
||||
Blockly.utils.dom.addClass(iconDiv, this.cssConfig_['closedIcon']);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets whether the category is visible or not.
|
||||
* For a category to be visible its parent category must also be expanded.
|
||||
* @param {boolean} isVisible True if category should be visible.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.setVisible_ = function(isVisible) {
|
||||
this.htmlDiv_.style.display = isVisible ? 'block' : 'none';
|
||||
this.isHidden_ = !isVisible;
|
||||
|
||||
if (this.parentToolbox_.getSelectedItem() == this) {
|
||||
this.parentToolbox_.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Hide the category.
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.hide = function() {
|
||||
this.setVisible_(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* Show the category. Category will only appear if its parent category is also
|
||||
* expanded.
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.show = function() {
|
||||
this.setVisible_(true);
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the category is visible.
|
||||
* A category is only visible if all of its ancestors are expanded and isHidden_ is false.
|
||||
* @return {boolean} True if the category is visible, false otherwise.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.isVisible = function() {
|
||||
return !this.isHidden_ && this.allAncestorsExpanded_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether all ancestors of a category (parent and parent's parent, etc.) are expanded.
|
||||
* @return {boolean} True only if every ancestor is expanded
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.allAncestorsExpanded_ = function() {
|
||||
var category = this;
|
||||
while (category.getParent()) {
|
||||
category = category.getParent();
|
||||
if (!category.isExpanded()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.isSelectable = function() {
|
||||
return this.isVisible() && !this.isDisabled_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles when the toolbox item is clicked.
|
||||
* @param {!Event} _e Click event to handle.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.onClick = function(_e) {
|
||||
// No-op
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the current category as selected.
|
||||
* @param {boolean} isSelected True if this category is selected, false
|
||||
* otherwise.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.setSelected = function(isSelected) {
|
||||
if (isSelected) {
|
||||
var defaultColour = this.parseColour_(
|
||||
Blockly.ToolboxCategory.defaultBackgroundColour);
|
||||
this.rowDiv_.style.backgroundColor = this.colour_ || defaultColour;
|
||||
Blockly.utils.dom.addClass(this.rowDiv_, this.cssConfig_['selected']);
|
||||
} else {
|
||||
this.rowDiv_.style.backgroundColor = '';
|
||||
Blockly.utils.dom.removeClass(this.rowDiv_, this.cssConfig_['selected']);
|
||||
}
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.htmlDiv_),
|
||||
Blockly.utils.aria.State.SELECTED, isSelected);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets whether the category is disabled.
|
||||
* @param {boolean} isDisabled True to disable the category, false otherwise.
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.setDisabled = function(isDisabled) {
|
||||
this.isDisabled_ = isDisabled;
|
||||
this.getDiv().setAttribute('disabled', isDisabled);
|
||||
isDisabled ? this.getDiv().setAttribute('disabled', 'true') :
|
||||
this.getDiv().removeAttribute('disabled');
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the name of the category. Used for emitting events.
|
||||
* @return {string} The name of the toolbox item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.getName = function() {
|
||||
return this.name_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.getParent = function() {
|
||||
return this.parent_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.getDiv = function() {
|
||||
return this.htmlDiv_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the contents of the category. These are items that are meant to be
|
||||
* displayed in the flyout.
|
||||
* @return {!Blockly.utils.toolbox.FlyoutItemInfoArray|string} The definition
|
||||
* of items to be displayed in the flyout.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.getContents = function() {
|
||||
return this.flyoutItems_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the contents to be displayed in the flyout.
|
||||
* If the flyout is open when the contents are updated, refreshSelection on the
|
||||
* toolbox must also be called.
|
||||
* @param {!Blockly.utils.toolbox.FlyoutDefinition|string} contents The contents
|
||||
* to be displayed in the flyout. A string can be supplied to create a
|
||||
* dynamic category.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.updateFlyoutContents = function(contents) {
|
||||
this.flyoutItems_ = [];
|
||||
|
||||
if (typeof contents == 'string') {
|
||||
this.toolboxItemDef_['custom'] = contents;
|
||||
} else {
|
||||
// Removes old custom field when contents is updated.
|
||||
delete this.toolboxItemDef_['custom'];
|
||||
this.toolboxItemDef_['contents'] =
|
||||
Blockly.utils.toolbox.convertFlyoutDefToJsonArray(contents);
|
||||
}
|
||||
this.parseContents_(
|
||||
/** @type {Blockly.utils.toolbox.CategoryInfo} */ (this.toolboxItemDef_));
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxCategory.prototype.dispose = function() {
|
||||
Blockly.utils.dom.removeNode(this.htmlDiv_);
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS for Toolbox. See css.js for use.
|
||||
*/
|
||||
Blockly.Css.register([
|
||||
/* eslint-disable indent */
|
||||
'.blocklyTreeRow:not(.blocklyTreeSelected):hover {',
|
||||
'background-color: rgba(255, 255, 255, 0.2);',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[layout="h"] .blocklyToolboxCategory {',
|
||||
'margin: 1px 5px 1px 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[dir="RTL"][layout="h"] .blocklyToolboxCategory {',
|
||||
'margin: 1px 0 1px 5px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeRow {',
|
||||
'height: 22px;',
|
||||
'line-height: 22px;',
|
||||
'margin-bottom: 3px;',
|
||||
'padding-right: 8px;',
|
||||
'white-space: nowrap;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeRow {',
|
||||
'margin-left: 8px;',
|
||||
'padding-right: 0px',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIcon {',
|
||||
'background-image: url(<<<PATH>>>/sprites.png);',
|
||||
'height: 16px;',
|
||||
'vertical-align: middle;',
|
||||
'visibility: hidden;',
|
||||
'width: 16px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIconClosed {',
|
||||
'background-position: -32px -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeIconClosed {',
|
||||
'background-position: 0 -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected>.blocklyTreeIconClosed {',
|
||||
'background-position: -32px -17px;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[dir="RTL"] .blocklyTreeSelected>.blocklyTreeIconClosed {',
|
||||
'background-position: 0 -17px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeIconOpen {',
|
||||
'background-position: -16px -1px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected>.blocklyTreeIconOpen {',
|
||||
'background-position: -16px -17px;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeLabel {',
|
||||
'cursor: default;',
|
||||
'font: 16px sans-serif;',
|
||||
'padding: 0 3px;',
|
||||
'vertical-align: middle;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDelete .blocklyTreeLabel {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyTreeSelected .blocklyTreeLabel {',
|
||||
'color: #fff;',
|
||||
'}'
|
||||
/* eslint-enable indent */
|
||||
]);
|
||||
|
||||
Blockly.registry.register(Blockly.registry.Type.TOOLBOX_ITEM,
|
||||
Blockly.ToolboxCategory.registrationName, Blockly.ToolboxCategory);
|
||||
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A toolbox category used to organize blocks in the toolbox.
|
||||
* @author aschmiedt@google.com (Abby Schmiedt)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.CollapsibleToolboxCategory');
|
||||
|
||||
goog.require('Blockly.registry');
|
||||
goog.require('Blockly.ToolboxCategory');
|
||||
goog.require('Blockly.ToolboxSeparator');
|
||||
goog.require('Blockly.utils.aria');
|
||||
goog.require('Blockly.utils.dom');
|
||||
goog.require('Blockly.utils.object');
|
||||
goog.require('Blockly.utils.toolbox');
|
||||
goog.require('Blockly.ToolboxItem');
|
||||
|
||||
goog.requireType('Blockly.ICollapsibleToolboxItem');
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
goog.requireType('Blockly.IToolboxItem');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a category in a toolbox that can be collapsed.
|
||||
* @param {!Blockly.utils.toolbox.CategoryInfo} categoryDef The information needed
|
||||
* to create a category in the toolbox.
|
||||
* @param {!Blockly.IToolbox} toolbox The parent toolbox for the category.
|
||||
* @param {Blockly.ICollapsibleToolboxItem=} opt_parent The parent category or null if
|
||||
* the category does not have a parent.
|
||||
* @constructor
|
||||
* @extends {Blockly.ToolboxCategory}
|
||||
* @implements {Blockly.ICollapsibleToolboxItem}
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory = function(categoryDef, toolbox, opt_parent) {
|
||||
/**
|
||||
* Container for any child categories.
|
||||
* @type {?Element}
|
||||
* @protected
|
||||
*/
|
||||
this.subcategoriesDiv_ = null;
|
||||
|
||||
/**
|
||||
* Whether or not the category should display its subcategories.
|
||||
* @type {boolean}
|
||||
* @protected
|
||||
*/
|
||||
this.expanded_ = false;
|
||||
|
||||
/**
|
||||
* The child toolbox items for this category.
|
||||
* @type {!Array<!Blockly.ToolboxItem>}
|
||||
* @protected
|
||||
*/
|
||||
this.toolboxItems_ = [];
|
||||
|
||||
Blockly.CollapsibleToolboxCategory.superClass_.constructor.call(
|
||||
this, categoryDef, toolbox, opt_parent);
|
||||
};
|
||||
|
||||
Blockly.utils.object.inherits(Blockly.CollapsibleToolboxCategory, Blockly.ToolboxCategory);
|
||||
|
||||
/**
|
||||
* All the css class names that are used to create a collapsible
|
||||
* category. This is all the properties from the regular category plus contents.
|
||||
* @typedef {{
|
||||
* container:?string,
|
||||
* row:?string,
|
||||
* icon:?string,
|
||||
* label:?string,
|
||||
* selected:?string,
|
||||
* openIcon:?string,
|
||||
* closedIcon:?string,
|
||||
* contents:?string,
|
||||
* }}
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.CssConfig;
|
||||
|
||||
/**
|
||||
* Name used for registering a collapsible toolbox category.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.registrationName = 'collapsibleCategory';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.makeDefaultCssConfig_ = function() {
|
||||
var cssConfig = Blockly.CollapsibleToolboxCategory.superClass_.makeDefaultCssConfig_.call(this);
|
||||
cssConfig['contents'] = 'blocklyToolboxContents';
|
||||
return cssConfig;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.parseContents_ = function(categoryDef) {
|
||||
var contents = categoryDef['contents'];
|
||||
var prevIsFlyoutItem = true;
|
||||
|
||||
if (categoryDef['custom']) {
|
||||
this.flyoutItems_ = categoryDef['custom'];
|
||||
} else if (contents) {
|
||||
for (var i = 0, itemDef; (itemDef = contents[i]); i++) {
|
||||
// Separators can exist as either a flyout item or a toolbox item so
|
||||
// decide where it goes based on the type of the previous item.
|
||||
if (!Blockly.registry.hasItem(Blockly.registry.Type.TOOLBOX_ITEM, itemDef['kind']) ||
|
||||
(itemDef['kind'].toLowerCase() == Blockly.ToolboxSeparator.registrationName &&
|
||||
prevIsFlyoutItem)) {
|
||||
var flyoutItem = /** @type {Blockly.utils.toolbox.FlyoutItemInfo} */ (itemDef);
|
||||
this.flyoutItems_.push(flyoutItem);
|
||||
prevIsFlyoutItem = true;
|
||||
} else {
|
||||
this.createToolboxItem_(itemDef);
|
||||
prevIsFlyoutItem = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a toolbox item and adds it to the list of toolbox items.
|
||||
* @param {!Blockly.utils.toolbox.ToolboxItemInfo} itemDef The information needed
|
||||
* to create a toolbox item.
|
||||
* @private
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.createToolboxItem_ = function(itemDef) {
|
||||
var registryName = itemDef['kind'];
|
||||
var categoryDef = /** @type {!Blockly.utils.toolbox.CategoryInfo} */ (itemDef);
|
||||
|
||||
// Categories that are collapsible are created using a class registered under
|
||||
// a diffferent name.
|
||||
if (registryName.toUpperCase() == 'CATEGORY' &&
|
||||
Blockly.utils.toolbox.isCategoryCollapsible(categoryDef)) {
|
||||
registryName = Blockly.CollapsibleToolboxCategory.registrationName;
|
||||
}
|
||||
var ToolboxItemClass = Blockly.registry.getClass(
|
||||
Blockly.registry.Type.TOOLBOX_ITEM, registryName);
|
||||
var toolboxItem = new ToolboxItemClass(itemDef, this.parentToolbox_, this);
|
||||
this.toolboxItems_.push(toolboxItem);
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.init = function() {
|
||||
Blockly.CollapsibleToolboxCategory.superClass_.init.call(this);
|
||||
|
||||
this.setExpanded(this.toolboxItemDef_['expanded'] == 'true' ||
|
||||
this.toolboxItemDef_['expanded']);
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.createDom_ = function() {
|
||||
Blockly.CollapsibleToolboxCategory.superClass_.createDom_.call(this);
|
||||
|
||||
var subCategories = this.getChildToolboxItems();
|
||||
this.subcategoriesDiv_ = this.createSubCategoriesDom_(subCategories);
|
||||
Blockly.utils.aria.setRole(this.subcategoriesDiv_,
|
||||
Blockly.utils.aria.Role.GROUP);
|
||||
this.htmlDiv_.appendChild(this.subcategoriesDiv_);
|
||||
|
||||
return this.htmlDiv_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.createIconDom_ = function() {
|
||||
var toolboxIcon = document.createElement('span');
|
||||
if (!this.parentToolbox_.isHorizontal()) {
|
||||
Blockly.utils.dom.addClass(toolboxIcon, this.cssConfig_['icon']);
|
||||
toolboxIcon.style.visibility = 'visible';
|
||||
}
|
||||
|
||||
toolboxIcon.style.display = 'inline-block';
|
||||
return toolboxIcon;
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the dom for all subcategories.
|
||||
* @param {!Array<!Blockly.ToolboxItem>} subcategories The subcategories.
|
||||
* @return {!Element} The div holding all the subcategories.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.createSubCategoriesDom_ = function(subcategories) {
|
||||
var contentsContainer = document.createElement('div');
|
||||
Blockly.utils.dom.addClass(contentsContainer, this.cssConfig_['contents']);
|
||||
|
||||
for (var i = 0; i < subcategories.length; i++) {
|
||||
var newCategory = subcategories[i];
|
||||
newCategory.init();
|
||||
var newCategoryDiv = newCategory.getDiv();
|
||||
contentsContainer.appendChild(newCategoryDiv);
|
||||
}
|
||||
return contentsContainer;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Opens or closes the current category.
|
||||
* @param {boolean} isExpanded True to expand the category, false to close.
|
||||
* @public
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.setExpanded = function(isExpanded) {
|
||||
if (this.expanded_ == isExpanded) {
|
||||
return;
|
||||
}
|
||||
this.expanded_ = isExpanded;
|
||||
if (isExpanded) {
|
||||
this.subcategoriesDiv_.style.display = 'block';
|
||||
this.openIcon_(this.iconDom_);
|
||||
} else {
|
||||
this.subcategoriesDiv_.style.display = 'none';
|
||||
this.closeIcon_(this.iconDom_);
|
||||
}
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.htmlDiv_),
|
||||
Blockly.utils.aria.State.EXPANDED, isExpanded);
|
||||
|
||||
this.parentToolbox_.handleToolboxItemResize();
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.setVisible_ = function(isVisible) {
|
||||
this.htmlDiv_.style.display = isVisible ? 'block' : 'none';
|
||||
for (var i = 0, child; (child = this.getChildToolboxItems()[i]); i++) {
|
||||
child.setVisible_(isVisible);
|
||||
}
|
||||
this.isHidden_ = !isVisible;
|
||||
|
||||
if (this.parentToolbox_.getSelectedItem() == this) {
|
||||
this.parentToolbox_.clearSelection();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the category is expanded to show its child subcategories.
|
||||
* @return {boolean} True if the toolbox item shows its children, false if it
|
||||
* is collapsed.
|
||||
* @public
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.isExpanded = function() {
|
||||
return this.expanded_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.isCollapsible = function() {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.onClick = function(_e) {
|
||||
this.toggleExpanded();
|
||||
};
|
||||
|
||||
/**
|
||||
* Toggles whether or not the category is expanded.
|
||||
* @public
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.toggleExpanded = function() {
|
||||
this.setExpanded(!this.expanded_);
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.getDiv = function() {
|
||||
return this.htmlDiv_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets any children toolbox items. (ex. Gets the subcategories)
|
||||
* @return {!Array<!Blockly.IToolboxItem>} The child toolbox items.
|
||||
*/
|
||||
Blockly.CollapsibleToolboxCategory.prototype.getChildToolboxItems = function() {
|
||||
return this.toolboxItems_;
|
||||
};
|
||||
|
||||
|
||||
Blockly.registry.register(Blockly.registry.Type.TOOLBOX_ITEM,
|
||||
Blockly.CollapsibleToolboxCategory.registrationName, Blockly.CollapsibleToolboxCategory);
|
||||
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview A separator used for separating toolbox categories.
|
||||
* @author aschmiedt@google.com (Abby Schmiedt)
|
||||
* @author maribethb@google.com (Maribeth Bottorff)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.ToolboxSeparator');
|
||||
|
||||
goog.require('Blockly.registry');
|
||||
goog.require('Blockly.ToolboxItem');
|
||||
goog.require('Blockly.utils.dom');
|
||||
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
goog.requireType('Blockly.IToolboxItem');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a toolbox separator. This is the thin visual line that appears on
|
||||
* the toolbox. This item is not interactable.
|
||||
* @param {!Blockly.utils.toolbox.SeparatorInfo} separatorDef The information
|
||||
* needed to create a separator.
|
||||
* @param {!Blockly.IToolbox} toolbox The parent toolbox for the separator.
|
||||
* @constructor
|
||||
* @extends {Blockly.ToolboxItem}
|
||||
* @implements {Blockly.IToolboxItem}
|
||||
*/
|
||||
Blockly.ToolboxSeparator = function(separatorDef, toolbox) {
|
||||
|
||||
Blockly.ToolboxSeparator.superClass_.constructor.call(
|
||||
this, separatorDef, toolbox);
|
||||
/**
|
||||
* All the css class names that are used to create a separator.
|
||||
* @type {!Blockly.ToolboxSeparator.CssConfig}
|
||||
* @protected
|
||||
*/
|
||||
this.cssConfig_ = {
|
||||
'container': 'blocklyTreeSeparator'
|
||||
};
|
||||
|
||||
var cssConfig = separatorDef['cssconfig'] || separatorDef['cssConfig'];
|
||||
Blockly.utils.object.mixin(this.cssConfig_, cssConfig);
|
||||
};
|
||||
Blockly.utils.object.inherits(Blockly.ToolboxSeparator, Blockly.ToolboxItem);
|
||||
|
||||
/**
|
||||
* All the css class names that are used to create a separator.
|
||||
* @typedef {{
|
||||
* container:?string,
|
||||
* }}
|
||||
*/
|
||||
Blockly.ToolboxSeparator.CssConfig;
|
||||
|
||||
/**
|
||||
* Name used for registering a toolbox separator.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.ToolboxSeparator.registrationName = 'sep';
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxSeparator.prototype.init = function() {
|
||||
this.createDom_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the dom for a separator.
|
||||
* @return {!Element} The parent element for the separator.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.ToolboxSeparator.prototype.createDom_ = function() {
|
||||
var container = document.createElement('div');
|
||||
Blockly.utils.dom.addClass(container, this.cssConfig_['container']);
|
||||
this.htmlDiv_ = container;
|
||||
return container;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxSeparator.prototype.getDiv = function() {
|
||||
return this.htmlDiv_;
|
||||
};
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
Blockly.ToolboxSeparator.prototype.dispose = function() {
|
||||
Blockly.utils.dom.removeNode(this.htmlDiv_);
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS for Toolbox. See css.js for use.
|
||||
*/
|
||||
Blockly.Css.register([
|
||||
/* eslint-disable indent */
|
||||
'.blocklyTreeSeparator {',
|
||||
'border-bottom: solid #e5e5e5 1px;',
|
||||
'height: 0;',
|
||||
'margin: 5px 0;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxDiv[layout="h"] .blocklyTreeSeparator {',
|
||||
'border-right: solid #e5e5e5 1px;',
|
||||
'border-bottom: none;',
|
||||
'height: auto;',
|
||||
'margin: 0 5px 0 5px;',
|
||||
'padding: 5px 0;',
|
||||
'width: 0;',
|
||||
'}',
|
||||
/* eslint-enable indent */
|
||||
]);
|
||||
|
||||
Blockly.registry.register(Blockly.registry.Type.TOOLBOX_ITEM,
|
||||
Blockly.ToolboxSeparator.registrationName, Blockly.ToolboxSeparator);
|
||||
@@ -0,0 +1,985 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview Toolbox from whence to create blocks.
|
||||
* @author fraser@google.com (Neil Fraser)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.Toolbox');
|
||||
|
||||
goog.require('Blockly.CollapsibleToolboxCategory');
|
||||
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.utils');
|
||||
goog.require('Blockly.utils.aria');
|
||||
goog.require('Blockly.utils.dom');
|
||||
goog.require('Blockly.utils.Rect');
|
||||
|
||||
goog.requireType('Blockly.Action');
|
||||
goog.requireType('Blockly.IBlocklyActionable');
|
||||
goog.requireType('Blockly.ICollapsibleToolboxItem');
|
||||
goog.requireType('Blockly.IDeleteArea');
|
||||
goog.requireType('Blockly.IFlyout');
|
||||
goog.requireType('Blockly.ISelectableToolboxItem');
|
||||
goog.requireType('Blockly.IStyleable');
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
goog.requireType('Blockly.IToolboxItem');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
goog.requireType('Blockly.WorkspaceSvg');
|
||||
|
||||
|
||||
/**
|
||||
* Class for a Toolbox.
|
||||
* Creates the toolbox's DOM.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace in which to create new
|
||||
* blocks.
|
||||
* @constructor
|
||||
* @implements {Blockly.IBlocklyActionable}
|
||||
* @implements {Blockly.IDeleteArea}
|
||||
* @implements {Blockly.IStyleable}
|
||||
* @implements {Blockly.IToolbox}
|
||||
*/
|
||||
Blockly.Toolbox = function(workspace) {
|
||||
/**
|
||||
* The workspace this toolbox is on.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @protected
|
||||
*/
|
||||
this.workspace_ = workspace;
|
||||
|
||||
/**
|
||||
* The JSON describing the contents of this toolbox.
|
||||
* @type {!Blockly.utils.toolbox.ToolboxInfo}
|
||||
* @protected
|
||||
*/
|
||||
this.toolboxDef_ = workspace.options.languageTree || {'contents': []};
|
||||
|
||||
/**
|
||||
* Whether the toolbox should be laid out horizontally.
|
||||
* @type {boolean}
|
||||
* @private
|
||||
*/
|
||||
this.horizontalLayout_ = workspace.options.horizontalLayout;
|
||||
|
||||
/**
|
||||
* The html container for the toolbox.
|
||||
* @type {?Element}
|
||||
*/
|
||||
this.HtmlDiv = null;
|
||||
|
||||
/**
|
||||
* The html container for the contents of a toolbox.
|
||||
* @type {?Element}
|
||||
* @protected
|
||||
*/
|
||||
this.contentsDiv_ = null;
|
||||
|
||||
/**
|
||||
* The list of items in the toolbox.
|
||||
* @type {!Array<!Blockly.IToolboxItem>}
|
||||
* @protected
|
||||
*/
|
||||
this.contents_ = [];
|
||||
|
||||
/**
|
||||
* The width of the toolbox.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
this.width_ = 0;
|
||||
|
||||
/**
|
||||
* The height of the toolbox.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
this.height_ = 0;
|
||||
|
||||
/**
|
||||
* Is RTL vs LTR.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.RTL = workspace.options.RTL;
|
||||
|
||||
/**
|
||||
* The flyout for the toolbox.
|
||||
* @type {?Blockly.IFlyout}
|
||||
* @private
|
||||
*/
|
||||
this.flyout_ = null;
|
||||
|
||||
/**
|
||||
* A map from toolbox item IDs to toolbox items.
|
||||
* @type {!Object<string, Blockly.IToolboxItem>}
|
||||
* @protected
|
||||
*/
|
||||
this.contentMap_ = {};
|
||||
|
||||
/**
|
||||
* Position of the toolbox and flyout relative to the workspace.
|
||||
* TODO (#4246): Add an enum for toolbox types.
|
||||
* @type {number}
|
||||
*/
|
||||
this.toolboxPosition = workspace.options.toolboxPosition;
|
||||
|
||||
/**
|
||||
* The currently selected item.
|
||||
* @type {?Blockly.ISelectableToolboxItem}
|
||||
* @protected
|
||||
*/
|
||||
this.selectedItem_ = null;
|
||||
|
||||
/**
|
||||
* The previously selected item.
|
||||
* @type {?Blockly.ISelectableToolboxItem}
|
||||
* @protected
|
||||
*/
|
||||
this.previouslySelectedItem_ = null;
|
||||
|
||||
/**
|
||||
* Array holding info needed to unbind event handlers.
|
||||
* Used for disposing.
|
||||
* Ex: [[node, name, func], [node, name, func]].
|
||||
* @type {!Array<!Blockly.EventData>}
|
||||
* @protected
|
||||
*/
|
||||
this.boundEvents_ = [];
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the toolbox
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.init = function() {
|
||||
var workspace = this.workspace_;
|
||||
var svg = workspace.getParentSvg();
|
||||
|
||||
this.flyout_ = this.createFlyout_();
|
||||
|
||||
this.HtmlDiv = this.createDom_(this.workspace_);
|
||||
Blockly.utils.dom.insertAfter(this.flyout_.createDom('svg'), svg);
|
||||
this.flyout_.init(workspace);
|
||||
|
||||
this.render(this.toolboxDef_);
|
||||
var themeManager = workspace.getThemeManager();
|
||||
themeManager.subscribe(this.HtmlDiv, 'toolboxBackgroundColour',
|
||||
'background-color');
|
||||
themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color');
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the dom for the toolbox.
|
||||
* @param {!Blockly.WorkspaceSvg} workspace The workspace this toolbox is on.
|
||||
* @return {!Element} The html container for the toolbox.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.createDom_ = function(workspace) {
|
||||
var svg = workspace.getParentSvg();
|
||||
|
||||
var container = this.createContainer_();
|
||||
|
||||
this.contentsDiv_ = this.createContentsContainer_();
|
||||
this.contentsDiv_.tabIndex = 0;
|
||||
Blockly.utils.aria.setRole(this.contentsDiv_, Blockly.utils.aria.Role.TREE);
|
||||
container.appendChild(this.contentsDiv_);
|
||||
|
||||
svg.parentNode.insertBefore(container, svg);
|
||||
|
||||
this.attachEvents_(container, this.contentsDiv_);
|
||||
return container;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the container div for the toolbox.
|
||||
* @return {!Element} The html container for the toolbox.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.createContainer_ = function() {
|
||||
var toolboxContainer = document.createElement('div');
|
||||
toolboxContainer.setAttribute('layout', this.isHorizontal() ? 'h' : 'v');
|
||||
Blockly.utils.dom.addClass(toolboxContainer, 'blocklyToolboxDiv');
|
||||
Blockly.utils.dom.addClass(toolboxContainer, 'blocklyNonSelectable');
|
||||
toolboxContainer.setAttribute('dir', this.RTL ? 'RTL' : 'LTR');
|
||||
return toolboxContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the container for all the contents in the toolbox.
|
||||
* @return {!Element} The html container for the toolbox contents.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.createContentsContainer_ = function() {
|
||||
var contentsContainer = document.createElement('div');
|
||||
Blockly.utils.dom.addClass(contentsContainer, 'blocklyToolboxContents');
|
||||
if (this.isHorizontal()) {
|
||||
contentsContainer.style.flexDirection = 'row';
|
||||
}
|
||||
return contentsContainer;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds event listeners to the toolbox container div.
|
||||
* @param {!Element} container The html container for the toolbox.
|
||||
* @param {!Element} contentsContainer The html container for the contents
|
||||
* of the toolbox.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.attachEvents_ = function(container,
|
||||
contentsContainer) {
|
||||
// Clicking on toolbox closes popups.
|
||||
var clickEvent = Blockly.bindEventWithChecks_(container, 'mousedown', this,
|
||||
this.onClick_, /* opt_noCaptureIdentifier */ false,
|
||||
/* opt_noPreventDefault */ true);
|
||||
this.boundEvents_.push(clickEvent);
|
||||
|
||||
var keyDownEvent = Blockly.bindEventWithChecks_(contentsContainer, 'keydown',
|
||||
this, this.onKeyDown_, /* opt_noCaptureIdentifier */ false,
|
||||
/* opt_noPreventDefault */ true);
|
||||
this.boundEvents_.push(keyDownEvent);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles on click events for when the toolbox or toolbox items are clicked.
|
||||
* @param {!Event} e Click event to handle.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.onClick_ = function(e) {
|
||||
if (Blockly.utils.isRightButton(e) || e.target == this.HtmlDiv) {
|
||||
// Close flyout.
|
||||
Blockly.hideChaff(false);
|
||||
} else {
|
||||
var srcElement = e.srcElement;
|
||||
var itemId = srcElement.getAttribute('id');
|
||||
if (itemId) {
|
||||
var item = this.getToolboxItemById(itemId);
|
||||
if (item.isSelectable()) {
|
||||
this.setSelectedItem(item);
|
||||
item.onClick(e);
|
||||
}
|
||||
}
|
||||
// Just close popups.
|
||||
Blockly.hideChaff(true);
|
||||
}
|
||||
Blockly.Touch.clearTouchIdentifier(); // Don't block future drags.
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles key down events for the toolbox.
|
||||
* @param {!KeyboardEvent} e The key down event.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.onKeyDown_ = function(e) {
|
||||
var handled = false;
|
||||
switch (e.keyCode) {
|
||||
case Blockly.utils.KeyCodes.DOWN:
|
||||
handled = this.selectNext_();
|
||||
break;
|
||||
case Blockly.utils.KeyCodes.UP:
|
||||
handled = this.selectPrevious_();
|
||||
break;
|
||||
case Blockly.utils.KeyCodes.LEFT:
|
||||
handled = this.selectParent_();
|
||||
break;
|
||||
case Blockly.utils.KeyCodes.RIGHT:
|
||||
handled = this.selectChild_();
|
||||
break;
|
||||
case Blockly.utils.KeyCodes.ENTER:
|
||||
case Blockly.utils.KeyCodes.SPACE:
|
||||
if (this.selectedItem_ && this.selectedItem_.isCollapsible()) {
|
||||
var collapsibleItem = /** @type {!Blockly.ICollapsibleToolboxItem} */ (this.selectedItem_);
|
||||
collapsibleItem.toggleExpanded();
|
||||
handled = true;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (handled) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates the flyout based on the toolbox layout.
|
||||
* @return {!Blockly.IFlyout} The flyout for the toolbox.
|
||||
* @throws {Error} If missing a require for `Blockly.HorizontalFlyout`,
|
||||
* `Blockly.VerticalFlyout`, and no flyout plugin is specified.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.createFlyout_ = function() {
|
||||
var workspace = this.workspace_;
|
||||
// TODO (#4247): Look into adding a makeFlyout method to Blockly Options.
|
||||
var workspaceOptions = new Blockly.Options(
|
||||
/** @type {!Blockly.BlocklyOptions} */
|
||||
({
|
||||
'parentWorkspace': workspace,
|
||||
'rtl': workspace.RTL,
|
||||
'oneBasedIndex': workspace.options.oneBasedIndex,
|
||||
'horizontalLayout': workspace.horizontalLayout,
|
||||
'renderer': workspace.options.renderer,
|
||||
'rendererOverrides': workspace.options.rendererOverrides
|
||||
}));
|
||||
// Options takes in either 'end' or 'start'. This has already been parsed to
|
||||
// be either 0 or 1, so set it after.
|
||||
workspaceOptions.toolboxPosition = workspace.options.toolboxPosition;
|
||||
var FlyoutClass = null;
|
||||
if (workspace.horizontalLayout) {
|
||||
FlyoutClass = Blockly.registry.getClassFromOptions(
|
||||
Blockly.registry.Type.FLYOUTS_HORIZONTAL_TOOLBOX, workspace.options);
|
||||
} else {
|
||||
FlyoutClass = Blockly.registry.getClassFromOptions(
|
||||
Blockly.registry.Type.FLYOUTS_VERTICAL_TOOLBOX, workspace.options);
|
||||
}
|
||||
|
||||
if (!FlyoutClass) {
|
||||
throw new Error('Blockly.VerticalFlyout, Blockly.HorizontalFlyout or your own' +
|
||||
' custom flyout must be required.');
|
||||
}
|
||||
return new FlyoutClass(workspaceOptions);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fills the toolbox with new toolbox items and removes any old contents.
|
||||
* @param {!Blockly.utils.toolbox.ToolboxInfo} toolboxDef Object holding information
|
||||
* for creating a toolbox.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.render = function(toolboxDef) {
|
||||
this.toolboxDef_ = toolboxDef;
|
||||
for (var i = 0; i < this.contents_.length; i++) {
|
||||
var toolboxItem = this.contents_[i];
|
||||
if (toolboxItem) {
|
||||
toolboxItem.dispose();
|
||||
}
|
||||
}
|
||||
this.contents_ = [];
|
||||
this.contentMap_ = {};
|
||||
this.renderContents_(toolboxDef['contents']);
|
||||
this.position();
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds all the toolbox items to the toolbox.
|
||||
* @param {!Array<!Blockly.utils.toolbox.ToolboxItemInfo>} toolboxDef Array
|
||||
* holding objects containing information on the contents of the toolbox.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.renderContents_ = function(toolboxDef) {
|
||||
// This is for performance reasons. By using document fragment we only have to
|
||||
// add to the dom once.
|
||||
var fragment = document.createDocumentFragment();
|
||||
for (var i = 0, toolboxItemDef; (toolboxItemDef = toolboxDef[i]); i++) {
|
||||
this.createToolboxItem_(toolboxItemDef, fragment);
|
||||
}
|
||||
this.contentsDiv_.appendChild(fragment);
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates and renders the toolbox item.
|
||||
* @param {Blockly.utils.toolbox.ToolboxItemInfo} toolboxItemDef Any information
|
||||
* that can be used to create an item in the toolbox.
|
||||
* @param {!DocumentFragment} fragment The document fragment to add the child
|
||||
* toolbox elements to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.createToolboxItem_ = function(toolboxItemDef, fragment) {
|
||||
var registryName = toolboxItemDef['kind'];
|
||||
|
||||
// Categories that are collapsible are created using a class registered under
|
||||
// a diffferent name.
|
||||
if (registryName.toUpperCase() == 'CATEGORY' &&
|
||||
Blockly.utils.toolbox.isCategoryCollapsible(
|
||||
/** @type {!Blockly.utils.toolbox.CategoryInfo} */(toolboxItemDef))) {
|
||||
registryName = Blockly.CollapsibleToolboxCategory.registrationName;
|
||||
}
|
||||
|
||||
var ToolboxItemClass = Blockly.registry.getClass(
|
||||
Blockly.registry.Type.TOOLBOX_ITEM, registryName.toLowerCase());
|
||||
if (ToolboxItemClass) {
|
||||
var toolboxItem = new ToolboxItemClass(toolboxItemDef, this);
|
||||
this.addToolboxItem_(toolboxItem);
|
||||
toolboxItem.init();
|
||||
var toolboxItemDom = toolboxItem.getDiv();
|
||||
if (toolboxItemDom) {
|
||||
fragment.appendChild(toolboxItemDom);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds an item to the toolbox.
|
||||
* @param {!Blockly.IToolboxItem} toolboxItem The item in the toolbox.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addToolboxItem_ = function(toolboxItem) {
|
||||
this.contents_.push(toolboxItem);
|
||||
this.contentMap_[toolboxItem.getId()] = toolboxItem;
|
||||
if (toolboxItem.isCollapsible()) {
|
||||
var collapsibleItem = /** @type {Blockly.ICollapsibleToolboxItem} */
|
||||
(toolboxItem);
|
||||
for (var i = 0, child; (child = collapsibleItem.getChildToolboxItems()[i]); i++) {
|
||||
this.addToolboxItem_(child);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the items in the toolbox.
|
||||
* @return {!Array<!Blockly.IToolboxItem>} The list of items in the toolbox.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getToolboxItems = function() {
|
||||
return this.contents_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a style on the toolbox. Usually used to change the cursor.
|
||||
* @param {string} style The name of the class to add.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.addStyle = function(style) {
|
||||
Blockly.utils.dom.addClass(/** @type {!Element} */ (this.HtmlDiv), style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes a style from the toolbox. Usually used to change the cursor.
|
||||
* @param {string} style The name of the class to remove.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.removeStyle = function(style) {
|
||||
Blockly.utils.dom.removeClass(/** @type {!Element} */ (this.HtmlDiv), style);
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the deletion rectangle for this toolbox.
|
||||
* @return {?Blockly.utils.Rect} Rectangle in which to delete.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getClientRect = function() {
|
||||
if (!this.HtmlDiv) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// BIG_NUM is offscreen padding so that blocks dragged beyond the toolbox
|
||||
// area are still deleted. Must be smaller than Infinity, but larger than
|
||||
// the largest screen size.
|
||||
var BIG_NUM = 10000000;
|
||||
var toolboxRect = this.HtmlDiv.getBoundingClientRect();
|
||||
|
||||
var top = toolboxRect.top;
|
||||
var bottom = top + toolboxRect.height;
|
||||
var left = toolboxRect.left;
|
||||
var right = left + toolboxRect.width;
|
||||
|
||||
// Assumes that the toolbox is on the SVG edge. If this changes
|
||||
// (e.g. toolboxes in mutators) then this code will need to be more complex.
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) {
|
||||
return new Blockly.utils.Rect(-BIG_NUM, bottom, -BIG_NUM, BIG_NUM);
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_BOTTOM) {
|
||||
return new Blockly.utils.Rect(top, BIG_NUM, -BIG_NUM, BIG_NUM);
|
||||
} else if (this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT) {
|
||||
return new Blockly.utils.Rect(-BIG_NUM, BIG_NUM, -BIG_NUM, right);
|
||||
} else { // Right
|
||||
return new Blockly.utils.Rect(-BIG_NUM, BIG_NUM, left, BIG_NUM);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the toolbox item with the given id.
|
||||
* @param {string} id The id of the toolbox item.
|
||||
* @return {?Blockly.IToolboxItem} The toolbox item with the given id, or null if
|
||||
* no item exists.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getToolboxItemById = function(id) {
|
||||
return this.contentMap_[id];
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the width of the toolbox.
|
||||
* @return {number} The width of the toolbox.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getWidth = function() {
|
||||
return this.width_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the height of the toolbox.
|
||||
* @return {number} The width of the toolbox.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getHeight = function() {
|
||||
return this.height_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the toolbox flyout.
|
||||
* @return {?Blockly.IFlyout} The toolbox flyout.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getFlyout = function() {
|
||||
return this.flyout_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the workspace for the toolbox.
|
||||
* @return {!Blockly.WorkspaceSvg} The parent workspace for the toolbox.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getWorkspace = function() {
|
||||
return this.workspace_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the selected item.
|
||||
* @return {?Blockly.ISelectableToolboxItem} The selected item, or null if no item is
|
||||
* currently selected.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getSelectedItem = function() {
|
||||
return this.selectedItem_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the previously selected item.
|
||||
* @return {?Blockly.ISelectableToolboxItem} The previously selected item, or null if no
|
||||
* item was previously selected.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.getPreviouslySelectedItem = function() {
|
||||
return this.previouslySelectedItem_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets whether or not the toolbox is horizontal.
|
||||
* @return {boolean} True if the toolbox is horizontal, false if the toolbox is
|
||||
* vertical.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.isHorizontal = function() {
|
||||
return this.horizontalLayout_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Positions the toolbox based on whether it is a horizontal toolbox and whether
|
||||
* the workspace is in rtl.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.position = function() {
|
||||
var toolboxDiv = this.HtmlDiv;
|
||||
if (!toolboxDiv) {
|
||||
// Not initialized yet.
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.horizontalLayout_) {
|
||||
toolboxDiv.style.left = '0';
|
||||
toolboxDiv.style.height = 'auto';
|
||||
toolboxDiv.style.width = '100%';
|
||||
this.height_ = toolboxDiv.offsetHeight;
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_TOP) { // Top
|
||||
toolboxDiv.style.top = '0';
|
||||
} else { // Bottom
|
||||
toolboxDiv.style.bottom = '0';
|
||||
}
|
||||
} else {
|
||||
if (this.toolboxPosition == Blockly.TOOLBOX_AT_RIGHT) { // Right
|
||||
toolboxDiv.style.right = '0';
|
||||
} else { // Left
|
||||
toolboxDiv.style.left = '0';
|
||||
}
|
||||
toolboxDiv.style.height = '100%';
|
||||
this.width_ = toolboxDiv.offsetWidth;
|
||||
}
|
||||
this.flyout_.position();
|
||||
};
|
||||
/**
|
||||
* Handles resizing the toolbox when a toolbox item resizes.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.handleToolboxItemResize = function() {
|
||||
// Reposition the workspace so that (0,0) is in the correct position relative
|
||||
// to the new absolute edge (ie toolbox edge).
|
||||
var workspace = this.workspace_;
|
||||
var rect = this.HtmlDiv.getBoundingClientRect();
|
||||
var newX = this.toolboxPosition == Blockly.TOOLBOX_AT_LEFT ?
|
||||
workspace.scrollX + rect.width : 0;
|
||||
var newY = this.toolboxPosition == Blockly.TOOLBOX_AT_TOP ?
|
||||
workspace.scrollY + rect.height : 0;
|
||||
workspace.translate(newX, newY);
|
||||
|
||||
// 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(workspace);
|
||||
};
|
||||
|
||||
/**
|
||||
* Unhighlights any previously selected item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.clearSelection = function() {
|
||||
this.setSelectedItem(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the category colours and background colour of selected categories.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.refreshTheme = function() {
|
||||
for (var i = 0; i < this.contents_.length; i++) {
|
||||
var child = this.contents_[i];
|
||||
if (child.refreshTheme) {
|
||||
child.refreshTheme();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Updates the flyout's content without closing it. Should be used in response
|
||||
* to a change in one of the dynamic categories, such as variables or
|
||||
* procedures.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.refreshSelection = function() {
|
||||
if (this.selectedItem_ && this.selectedItem_.isSelectable() &&
|
||||
this.selectedItem_.getContents().length) {
|
||||
this.flyout_.show(this.selectedItem_.getContents());
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Shows or hides the toolbox.
|
||||
* @param {boolean} isVisible True if toolbox should be visible.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.setVisible = function(isVisible) {
|
||||
this.HtmlDiv.style.display = isVisible ? 'block' : 'none';
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the given item as selected.
|
||||
* No-op if the item is not selectable.
|
||||
* @param {?Blockly.IToolboxItem} newItem The toolbox item to select.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.setSelectedItem = function(newItem) {
|
||||
var oldItem = this.selectedItem_;
|
||||
|
||||
if ((!newItem && !oldItem) || (newItem && !newItem.isSelectable())) {
|
||||
return;
|
||||
}
|
||||
newItem = /** @type {Blockly.ISelectableToolboxItem} */ (newItem);
|
||||
|
||||
if (this.shouldDeselectItem_(oldItem, newItem) && oldItem != null) {
|
||||
this.deselectItem_(oldItem);
|
||||
}
|
||||
|
||||
if (this.shouldSelectItem_(oldItem, newItem) && newItem != null) {
|
||||
this.selectItem_(oldItem, newItem);
|
||||
}
|
||||
|
||||
this.updateFlyout_(oldItem, newItem);
|
||||
this.fireSelectEvent_(oldItem, newItem);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decides whether the old item should be deselected.
|
||||
* @param {?Blockly.ISelectableToolboxItem} oldItem The previously selected
|
||||
* toolbox item.
|
||||
* @param {?Blockly.ISelectableToolboxItem} newItem The newly selected toolbox
|
||||
* item.
|
||||
* @return {boolean} True if the old item should be deselected, false otherwise.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.shouldDeselectItem_ = function(oldItem, newItem) {
|
||||
// Deselect the old item unless the old item is collapsible and has been
|
||||
// previously clicked on.
|
||||
return oldItem != null && (!oldItem.isCollapsible() || oldItem != newItem);
|
||||
};
|
||||
|
||||
/**
|
||||
* Decides whether the new item should be selected.
|
||||
* @param {?Blockly.ISelectableToolboxItem} oldItem The previously selected
|
||||
* toolbox item.
|
||||
* @param {?Blockly.ISelectableToolboxItem} newItem The newly selected toolbox
|
||||
* item.
|
||||
* @return {boolean} True if the new item should be selected, false otherwise.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.shouldSelectItem_ = function(oldItem, newItem) {
|
||||
// Select the new item unless the old item equals the new item.
|
||||
return newItem != null && newItem != oldItem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Deselects the given item, marks it as unselected, and updates aria state.
|
||||
* @param {!Blockly.ISelectableToolboxItem} item The previously selected
|
||||
* toolbox item which should be deselected.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.deselectItem_ = function(item) {
|
||||
this.selectedItem_ = null;
|
||||
this.previouslySelectedItem_ = item;
|
||||
item.setSelected(false);
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.contentsDiv_),
|
||||
Blockly.utils.aria.State.ACTIVEDESCENDANT, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the given item, marks it selected, and updates aria state.
|
||||
* @param {?Blockly.ISelectableToolboxItem} oldItem The previously selected
|
||||
* toolbox item.
|
||||
* @param {!Blockly.ISelectableToolboxItem} newItem The newly selected toolbox
|
||||
* item.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectItem_ = function(oldItem, newItem) {
|
||||
this.selectedItem_ = newItem;
|
||||
this.previouslySelectedItem_ = oldItem;
|
||||
newItem.setSelected(true);
|
||||
Blockly.utils.aria.setState(/** @type {!Element} */ (this.contentsDiv_),
|
||||
Blockly.utils.aria.State.ACTIVEDESCENDANT, newItem.getId());
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the toolbox item by its position in the list of toolbox items.
|
||||
* @param {number} position The position of the item to select.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectItemByPosition = function(position) {
|
||||
if (position > -1 && position < this.contents_.length) {
|
||||
var item = this.contents_[position];
|
||||
if (item.isSelectable()) {
|
||||
this.setSelectedItem(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Decides whether to hide or show the flyout depending on the selected item.
|
||||
* @param {?Blockly.ISelectableToolboxItem} oldItem The previously selected toolbox item.
|
||||
* @param {?Blockly.ISelectableToolboxItem} newItem The newly selected toolbox item.
|
||||
* @protected
|
||||
*/
|
||||
Blockly.Toolbox.prototype.updateFlyout_ = function(oldItem, newItem) {
|
||||
if ((oldItem == newItem && !newItem.isCollapsible()) || !newItem ||
|
||||
!newItem.getContents().length) {
|
||||
this.flyout_.hide();
|
||||
} else {
|
||||
this.flyout_.show(newItem.getContents());
|
||||
this.flyout_.scrollToStart();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Emits an event when a new toolbox item is selected.
|
||||
* @param {?Blockly.ISelectableToolboxItem} oldItem The previously selected
|
||||
* toolbox item.
|
||||
* @param {?Blockly.ISelectableToolboxItem} newItem The newly selected toolbox
|
||||
* item.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.fireSelectEvent_ = function(oldItem, newItem) {
|
||||
var oldElement = oldItem && oldItem.getName();
|
||||
var newElement = newItem && newItem.getName();
|
||||
// In this case the toolbox closes, so the newElement should be null.
|
||||
if (oldItem == newItem) {
|
||||
newElement = null;
|
||||
}
|
||||
// TODO (#4187): Update Toolbox Events.
|
||||
var event = new Blockly.Events.Ui(null, 'category',
|
||||
oldElement, newElement);
|
||||
event.workspaceId = this.workspace_.id;
|
||||
Blockly.Events.fire(event);
|
||||
};
|
||||
|
||||
/**
|
||||
* Handles the given Blockly action on a toolbox.
|
||||
* This is only triggered when keyboard accessibility mode is enabled.
|
||||
* @param {!Blockly.Action} action The action to be handled.
|
||||
* @return {boolean} True if the field handled the action, false otherwise.
|
||||
* @package
|
||||
*/
|
||||
Blockly.Toolbox.prototype.onBlocklyAction = function(action) {
|
||||
var selected = this.selectedItem_;
|
||||
if (!selected) {
|
||||
return false;
|
||||
}
|
||||
switch (action.name) {
|
||||
case Blockly.navigation.actionNames.PREVIOUS:
|
||||
return this.selectPrevious_();
|
||||
case Blockly.navigation.actionNames.OUT:
|
||||
return this.selectParent_();
|
||||
case Blockly.navigation.actionNames.NEXT:
|
||||
return this.selectNext_();
|
||||
case Blockly.navigation.actionNames.IN:
|
||||
return this.selectChild_();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Closes the current item if it is expanded, or selects the parent.
|
||||
* @return {boolean} True if a parent category was selected, false otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectParent_ = function() {
|
||||
if (!this.selectedItem_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.selectedItem_.isCollapsible() && this.selectedItem_.isExpanded()) {
|
||||
var collapsibleItem = /** @type {!Blockly.ICollapsibleToolboxItem} */ (this.selectedItem_);
|
||||
collapsibleItem.setExpanded(false);
|
||||
return true;
|
||||
} else if (this.selectedItem_.getParent() &&
|
||||
this.selectedItem_.getParent().isSelectable()) {
|
||||
this.setSelectedItem(this.selectedItem_.getParent());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the first child of the currently selected item, or nothing if the
|
||||
* toolbox item has no children.
|
||||
* @return {boolean} True if a child category was selected, false otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectChild_ = function() {
|
||||
if (!this.selectedItem_ || !this.selectedItem_.isCollapsible()) {
|
||||
return false;
|
||||
}
|
||||
var collapsibleItem = /** @type {Blockly.ICollapsibleToolboxItem} */
|
||||
(this.selectedItem_);
|
||||
if (!collapsibleItem.isExpanded()) {
|
||||
collapsibleItem.setExpanded(true);
|
||||
return true;
|
||||
} else {
|
||||
this.selectNext_();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the next visible toolbox item.
|
||||
* @return {boolean} True if a next category was selected, false otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectNext_ = function() {
|
||||
if (!this.selectedItem_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var nextItemIdx = this.contents_.indexOf(this.selectedItem_) + 1;
|
||||
if (nextItemIdx > -1 && nextItemIdx < this.contents_.length) {
|
||||
var nextItem = this.contents_[nextItemIdx];
|
||||
while (nextItem && !nextItem.isSelectable()) {
|
||||
nextItem = this.contents_[++nextItemIdx];
|
||||
}
|
||||
if (nextItem && nextItem.isSelectable()) {
|
||||
this.setSelectedItem(nextItem);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Selects the previous visible toolbox item.
|
||||
* @return {boolean} True if a previous category was selected, false otherwise.
|
||||
* @private
|
||||
*/
|
||||
Blockly.Toolbox.prototype.selectPrevious_ = function() {
|
||||
if (!this.selectedItem_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var prevItemIdx = this.contents_.indexOf(this.selectedItem_) - 1;
|
||||
if (prevItemIdx > -1 && prevItemIdx < this.contents_.length) {
|
||||
var prevItem = this.contents_[prevItemIdx];
|
||||
while (prevItem && !prevItem.isSelectable()) {
|
||||
prevItem = this.contents_[--prevItemIdx];
|
||||
}
|
||||
if (prevItem && prevItem.isSelectable()) {
|
||||
this.setSelectedItem(prevItem);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Disposes of this toolbox.
|
||||
* @public
|
||||
*/
|
||||
Blockly.Toolbox.prototype.dispose = function() {
|
||||
this.flyout_.dispose();
|
||||
for (var i = 0; i < this.contents_.length; i++) {
|
||||
var toolboxItem = this.contents_[i];
|
||||
toolboxItem.dispose();
|
||||
}
|
||||
|
||||
for (var j = 0; j < this.boundEvents_.length; j++) {
|
||||
Blockly.unbindEvent_(this.boundEvents_[j]);
|
||||
}
|
||||
this.boundEvents_ = [];
|
||||
this.contents_ = [];
|
||||
|
||||
this.workspace_.getThemeManager().unsubscribe(this.HtmlDiv);
|
||||
Blockly.utils.dom.removeNode(this.HtmlDiv);
|
||||
};
|
||||
|
||||
/**
|
||||
* CSS for Toolbox. See css.js for use.
|
||||
*/
|
||||
Blockly.Css.register([
|
||||
/* eslint-disable indent */
|
||||
'.blocklyToolboxDelete {',
|
||||
'cursor: url("<<<PATH>>>/handdelete.cur"), auto;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxGrab {',
|
||||
'cursor: url("<<<PATH>>>/handclosed.cur"), auto;',
|
||||
'cursor: grabbing;',
|
||||
'cursor: -webkit-grabbing;',
|
||||
'}',
|
||||
|
||||
/* Category tree in Toolbox. */
|
||||
'.blocklyToolboxDiv {',
|
||||
'background-color: #ddd;',
|
||||
'overflow-x: visible;',
|
||||
'overflow-y: auto;',
|
||||
'padding: 4px 0 4px 0;',
|
||||
'position: absolute;',
|
||||
'z-index: 70;', /* so blocks go under toolbox when dragging */
|
||||
'-webkit-tap-highlight-color: transparent;', /* issue #1345 */
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxContents {',
|
||||
'display: flex;',
|
||||
'flex-wrap: wrap;',
|
||||
'flex-direction: column;',
|
||||
'}',
|
||||
|
||||
'.blocklyToolboxContents:focus {',
|
||||
'outline: none;',
|
||||
'}',
|
||||
/* eslint-enable indent */
|
||||
]);
|
||||
|
||||
Blockly.registry.register(Blockly.registry.Type.TOOLBOX,
|
||||
Blockly.registry.DEFAULT, Blockly.Toolbox);
|
||||
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @license
|
||||
* Copyright 2020 Google LLC
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* @fileoverview An item in the toolbox.
|
||||
* @author aschmiedt@google.com (Abby Schmiedt)
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
goog.provide('Blockly.ToolboxItem');
|
||||
|
||||
goog.requireType('Blockly.IToolbox');
|
||||
goog.requireType('Blockly.IToolboxItem');
|
||||
goog.requireType('Blockly.utils.toolbox');
|
||||
goog.requireType('Blockly.WorkspaceSvg');
|
||||
|
||||
/**
|
||||
* Class for an item in the toolbox.
|
||||
* @param {!Blockly.utils.toolbox.ToolboxItemInfo} toolboxItemDef The JSON defining the
|
||||
* toolbox item.
|
||||
* @param {!Blockly.IToolbox} toolbox The toolbox that holds the toolbox item.
|
||||
* @param {Blockly.ICollapsibleToolboxItem=} opt_parent The parent toolbox item
|
||||
* or null if the category does not have a parent.
|
||||
* @constructor
|
||||
* @implements {Blockly.IToolboxItem}
|
||||
*/
|
||||
Blockly.ToolboxItem = function(toolboxItemDef, toolbox, opt_parent) {
|
||||
|
||||
/**
|
||||
* The id for the category.
|
||||
* @type {string}
|
||||
* @protected
|
||||
*/
|
||||
this.id_ = toolboxItemDef['id'] || Blockly.utils.IdGenerator.getNextUniqueId();
|
||||
|
||||
/**
|
||||
* The parent of the category.
|
||||
* @type {?Blockly.ICollapsibleToolboxItem}
|
||||
* @protected
|
||||
*/
|
||||
this.parent_ = opt_parent || null;
|
||||
|
||||
/**
|
||||
* The level that the category is nested at.
|
||||
* @type {number}
|
||||
* @protected
|
||||
*/
|
||||
this.level_ = this.parent_ ? this.parent_.getLevel() + 1 : 0;
|
||||
|
||||
/**
|
||||
* The JSON definition of the toolbox item.
|
||||
* @type {!Blockly.utils.toolbox.ToolboxItemInfo}
|
||||
* @protected
|
||||
*/
|
||||
this.toolboxItemDef_ = toolboxItemDef;
|
||||
|
||||
/**
|
||||
* The toolbox this category belongs to.
|
||||
* @type {!Blockly.IToolbox}
|
||||
* @protected
|
||||
*/
|
||||
this.parentToolbox_ = toolbox;
|
||||
|
||||
/**
|
||||
* The workspace of the parent toolbox.
|
||||
* @type {!Blockly.WorkspaceSvg}
|
||||
* @protected
|
||||
*/
|
||||
this.workspace_ = this.parentToolbox_.getWorkspace();
|
||||
};
|
||||
|
||||
/**
|
||||
* Initializes the toolbox item.
|
||||
* This includes creating the dom and updating the state of any items based
|
||||
* on the info object.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.init = function() {
|
||||
// No-op by default.
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the div for the toolbox item.
|
||||
* @return {?Element} The div for the toolbox item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.getDiv = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a unique identifier for this toolbox item.
|
||||
* @return {string} The id for the toolbox item.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.getId = function() {
|
||||
return this.id_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the parent if the toolbox item is nested.
|
||||
* @return {?Blockly.IToolboxItem} The parent toolbox item, or null if
|
||||
* this toolbox item is not nested.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.getParent = function() {
|
||||
return null;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the nested level of the category.
|
||||
* @return {number} The nested level of the category.
|
||||
* @package
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.getLevel = function() {
|
||||
return this.level_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the toolbox item is selectable.
|
||||
* @return {boolean} True if the toolbox item can be selected.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.isSelectable = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether the toolbox item is collapsible.
|
||||
* @return {boolean} True if the toolbox item is collapsible.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.isCollapsible = function() {
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Dispose of this toolbox item. No-op by default.
|
||||
* @public
|
||||
*/
|
||||
Blockly.ToolboxItem.prototype.dispose = function() {
|
||||
};
|
||||
@@ -235,6 +235,20 @@ Blockly.utils.dom.addClass = function(element, className) {
|
||||
return true;
|
||||
};
|
||||
|
||||
/**
|
||||
* Removes multiple calsses from an element.
|
||||
* @param {!Element} element DOM element to remove classes from.
|
||||
* @param {string} classNames A string of one or multiple class names for an
|
||||
* element.
|
||||
*/
|
||||
Blockly.utils.dom.removeClasses = function(element, classNames) {
|
||||
var classList = classNames.split(' ');
|
||||
for (var i = 0; i < classList.length; i++) {
|
||||
var cssName = classList[i];
|
||||
Blockly.utils.dom.removeClass(element, cssName);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove a CSS class from a element.
|
||||
* Similar to Closure's goog.dom.classes.remove, except it handles SVG elements.
|
||||
|
||||
+274
-53
@@ -13,6 +13,9 @@
|
||||
goog.provide('Blockly.utils.toolbox');
|
||||
|
||||
|
||||
goog.requireType('Blockly.ToolboxCategory');
|
||||
goog.requireType('Blockly.ToolboxSeparator');
|
||||
|
||||
/**
|
||||
* The information needed to create a block in the toolbox.
|
||||
* @typedef {{
|
||||
@@ -23,16 +26,18 @@ goog.provide('Blockly.utils.toolbox');
|
||||
* disabled: (?string|?boolean)
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.Block;
|
||||
Blockly.utils.toolbox.BlockInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create a separator in the toolbox.
|
||||
* @typedef {{
|
||||
* kind:string,
|
||||
* gap:?number
|
||||
* id:?string,
|
||||
* gap:?number,
|
||||
* cssconfig:?Blockly.ToolboxSeparator.CssConfig
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.Separator;
|
||||
Blockly.utils.toolbox.SeparatorInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create a button in the toolbox.
|
||||
@@ -42,83 +47,269 @@ Blockly.utils.toolbox.Separator;
|
||||
* callbackkey:string
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.Button;
|
||||
Blockly.utils.toolbox.ButtonInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create a label in the toolbox.
|
||||
* @typedef {{
|
||||
* kind:string,
|
||||
* id:?string,
|
||||
* text:string
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.Label;
|
||||
Blockly.utils.toolbox.LabelInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create either a button or a label in the flyout.
|
||||
* @typedef {Blockly.utils.toolbox.ButtonInfo|
|
||||
* Blockly.utils.toolbox.LabelInfo}
|
||||
*/
|
||||
Blockly.utils.toolbox.ButtonOrLabelInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create a category in the toolbox.
|
||||
* @typedef {{
|
||||
* kind:string,
|
||||
* name:string,
|
||||
* id:?string,
|
||||
* categorystyle:?string,
|
||||
* colour:?string,
|
||||
* contents:Array.<Blockly.utils.toolbox.Toolbox>
|
||||
* cssconfig:?Blockly.ToolboxCategory.CssConfig,
|
||||
* contents:!Array<Blockly.utils.toolbox.ToolboxItemInfo>,
|
||||
* hidden:?string
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.Category;
|
||||
Blockly.utils.toolbox.StaticCategoryInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create a custom category.
|
||||
* @typedef {{
|
||||
* kind:string,
|
||||
* custom:string,
|
||||
* id:?string,
|
||||
* categorystyle:?string,
|
||||
* colour:?string,
|
||||
* cssconfig:?Blockly.ToolboxCategory.CssConfig,
|
||||
* hidden:?string
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.DynamicCategoryInfo;
|
||||
|
||||
/**
|
||||
* The information needed to create either a dynamic or static category.
|
||||
* @typedef {Blockly.utils.toolbox.StaticCategoryInfo|
|
||||
* Blockly.utils.toolbox.DynamicCategoryInfo}
|
||||
*/
|
||||
Blockly.utils.toolbox.CategoryInfo;
|
||||
|
||||
/**
|
||||
* Any information that can be used to create an item in the toolbox.
|
||||
* @typedef {Blockly.utils.toolbox.Block|
|
||||
* Blockly.utils.toolbox.Separator|
|
||||
* Blockly.utils.toolbox.Button|
|
||||
* Blockly.utils.toolbox.Label|
|
||||
* Blockly.utils.toolbox.Category}
|
||||
* @typedef {Blockly.utils.toolbox.FlyoutItemInfo|
|
||||
* Blockly.utils.toolbox.StaticCategoryInfo}
|
||||
*/
|
||||
Blockly.utils.toolbox.Toolbox;
|
||||
Blockly.utils.toolbox.ToolboxItemInfo;
|
||||
|
||||
/**
|
||||
* All the different types that can be displayed in a flyout.
|
||||
* @typedef {Blockly.utils.toolbox.BlockInfo|
|
||||
* Blockly.utils.toolbox.SeparatorInfo|
|
||||
* Blockly.utils.toolbox.ButtonInfo|
|
||||
* Blockly.utils.toolbox.LabelInfo|
|
||||
* Blockly.utils.toolbox.DynamicCategoryInfo}
|
||||
*/
|
||||
Blockly.utils.toolbox.FlyoutItemInfo;
|
||||
|
||||
/**
|
||||
* The JSON definition of a toolbox.
|
||||
* @typedef {{
|
||||
* contents:!Array<Blockly.utils.toolbox.ToolboxItemInfo>
|
||||
* }}
|
||||
*/
|
||||
Blockly.utils.toolbox.ToolboxInfo;
|
||||
|
||||
/**
|
||||
* An array holding flyout items.
|
||||
* @typedef {
|
||||
* Array<!Blockly.utils.toolbox.FlyoutItemInfo>
|
||||
* }
|
||||
*/
|
||||
Blockly.utils.toolbox.FlyoutItemInfoArray;
|
||||
|
||||
/**
|
||||
* All of the different types that can create a toolbox.
|
||||
* @typedef {Node|
|
||||
* NodeList|
|
||||
* Array.<Blockly.utils.toolbox.Toolbox>|
|
||||
* Array.<Node>}
|
||||
* Blockly.utils.toolbox.ToolboxInfo|
|
||||
* string}
|
||||
*/
|
||||
Blockly.utils.toolbox.ToolboxDefinition;
|
||||
|
||||
/**
|
||||
* All of the different types that can be used to show items in a flyout.
|
||||
* @typedef {Blockly.utils.toolbox.FlyoutItemInfoArray|
|
||||
* NodeList|
|
||||
* Blockly.utils.toolbox.ToolboxInfo|
|
||||
* Array<!Node>}
|
||||
*/
|
||||
Blockly.utils.toolbox.FlyoutDefinition;
|
||||
|
||||
/**
|
||||
* Parse the provided toolbox definition into a consistent format.
|
||||
* @param {Blockly.utils.toolbox.ToolboxDefinition} toolboxDef The definition of the
|
||||
* toolbox in one of its many forms.
|
||||
* @return {Array.<Blockly.utils.toolbox.Toolbox>} Array of JSON holding
|
||||
* information on toolbox contents.
|
||||
* The name used to identify a toolbox that has category like items.
|
||||
* This only needs to be used if a toolbox wants to be treated like a category
|
||||
* toolbox but does not actually contain any toolbox items with the kind
|
||||
* 'category'.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.utils.toolbox.CATEGORY_TOOLBOX_KIND = 'categoryToolbox';
|
||||
|
||||
/**
|
||||
* The name used to identify a toolbox that has no categories and is displayed
|
||||
* as a simple flyout displaying blocks, buttons, or labels.
|
||||
* @const {string}
|
||||
*/
|
||||
Blockly.utils.toolbox.FLYOUT_TOOLBOX_KIND = 'flyoutToolbox';
|
||||
|
||||
/**
|
||||
* Converts the toolbox definition into toolbox JSON.
|
||||
* @param {?Blockly.utils.toolbox.ToolboxDefinition} toolboxDef The definition
|
||||
* of the toolbox in one of its many forms.
|
||||
* @return {?Blockly.utils.toolbox.ToolboxInfo} Object holding information
|
||||
* for creating a toolbox.
|
||||
* @package
|
||||
*/
|
||||
Blockly.utils.toolbox.convertToolboxToJSON = function(toolboxDef) {
|
||||
Blockly.utils.toolbox.convertToolboxDefToJson = function(toolboxDef) {
|
||||
if (!toolboxDef) {
|
||||
return null;
|
||||
}
|
||||
// If it is an array of JSON, then it is already in the correct format.
|
||||
if (Array.isArray(toolboxDef) && toolboxDef.length && !(toolboxDef[0].nodeType)) {
|
||||
if (Blockly.utils.toolbox.hasCategories(toolboxDef)) {
|
||||
// TODO: Remove after #3985 has been looked into.
|
||||
console.warn('Due to some performance issues, defining a toolbox using' +
|
||||
' JSON is not ready yet. Please define your toolbox using xml.');
|
||||
}
|
||||
return /** @type {!Array.<Blockly.utils.toolbox.Toolbox>} */ (toolboxDef);
|
||||
|
||||
if (toolboxDef instanceof Element || typeof toolboxDef == 'string') {
|
||||
toolboxDef = Blockly.utils.toolbox.parseToolboxTree(toolboxDef);
|
||||
toolboxDef = Blockly.utils.toolbox.convertToToolboxJson_(toolboxDef);
|
||||
}
|
||||
|
||||
return Blockly.utils.toolbox.toolboxXmlToJson_(toolboxDef);
|
||||
var toolboxJson = /** @type {Blockly.utils.toolbox.ToolboxInfo} */ (toolboxDef);
|
||||
Blockly.utils.toolbox.validateToolbox_(toolboxJson);
|
||||
return toolboxJson;
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert the xml for a toolbox to JSON.
|
||||
* @param {!NodeList|!Node|!Array.<Node>} toolboxDef The
|
||||
* definition of the toolbox in one of its many forms.
|
||||
* @return {!Array.<Blockly.utils.toolbox.Toolbox>} A list of objects in the
|
||||
* toolbox.
|
||||
* Validates the toolbox JSON fields have been set correctly.
|
||||
* @param {Blockly.utils.toolbox.ToolboxInfo} toolboxJson Object holding
|
||||
* information for creating a toolbox.
|
||||
* @throws {Error} if the toolbox is not the correct format.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.toolbox.toolboxXmlToJson_ = function(toolboxDef) {
|
||||
Blockly.utils.toolbox.validateToolbox_ = function(toolboxJson) {
|
||||
var toolboxKind = toolboxJson['kind'];
|
||||
var toolboxContents = toolboxJson['contents'];
|
||||
|
||||
if (toolboxKind) {
|
||||
if (toolboxKind != Blockly.utils.toolbox.FLYOUT_TOOLBOX_KIND &&
|
||||
toolboxKind != Blockly.utils.toolbox.CATEGORY_TOOLBOX_KIND) {
|
||||
throw Error('Invalid toolbox kind ' + toolboxKind + '.' +
|
||||
' Please supply either ' +
|
||||
Blockly.utils.toolbox.FLYOUT_TOOLBOX_KIND + ' or ' +
|
||||
Blockly.utils.toolbox.CATEGORY_TOOLBOX_KIND);
|
||||
}
|
||||
}
|
||||
if (!toolboxContents) {
|
||||
throw Error('Toolbox must have a contents attribute.');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the flyout definition into a list of flyout items.
|
||||
* @param {?Blockly.utils.toolbox.FlyoutDefinition} flyoutDef The definition of
|
||||
* the flyout in one of its many forms.
|
||||
* @return {!Blockly.utils.toolbox.FlyoutItemInfoArray} A list of flyout items.
|
||||
* @package
|
||||
*/
|
||||
Blockly.utils.toolbox.convertFlyoutDefToJsonArray = function(flyoutDef) {
|
||||
if (!flyoutDef) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (flyoutDef['contents']) {
|
||||
return flyoutDef['contents'];
|
||||
}
|
||||
|
||||
// If it is already in the correct format return the flyoutDef.
|
||||
if (Array.isArray(flyoutDef) && flyoutDef.length > 0 &&
|
||||
!flyoutDef[0].nodeType) {
|
||||
return flyoutDef;
|
||||
}
|
||||
|
||||
return Blockly.utils.toolbox.xmlToJsonArray_(
|
||||
/** @type {!Array<Node>|!NodeList} */ (flyoutDef));
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not the toolbox definition has categories.
|
||||
* @param {?Blockly.utils.toolbox.ToolboxInfo} toolboxJson Object holding
|
||||
* information for creating a toolbox.
|
||||
* @return {boolean} True if the toolbox has categories.
|
||||
* @package
|
||||
*/
|
||||
Blockly.utils.toolbox.hasCategories = function(toolboxJson) {
|
||||
if (!toolboxJson) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var toolboxKind = toolboxJson['kind'];
|
||||
if (toolboxKind) {
|
||||
return toolboxKind == Blockly.utils.toolbox.CATEGORY_TOOLBOX_KIND;
|
||||
}
|
||||
|
||||
var categories = toolboxJson['contents'].filter(function(item) {
|
||||
return item['kind'].toUpperCase() == 'CATEGORY';
|
||||
});
|
||||
return !!categories.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not the category is collapsible.
|
||||
* @param {!Blockly.utils.toolbox.CategoryInfo} categoryInfo Object holing
|
||||
* information for creating a category.
|
||||
* @return {boolean} True if the category has subcategories.
|
||||
* @package
|
||||
*/
|
||||
Blockly.utils.toolbox.isCategoryCollapsible = function(categoryInfo) {
|
||||
if (!categoryInfo || !categoryInfo['contents']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var categories = categoryInfo['contents'].filter(function(item) {
|
||||
return item['kind'].toUpperCase() == 'CATEGORY';
|
||||
});
|
||||
return !!categories.length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses the provided toolbox definition into a consistent format.
|
||||
* @param {Node} toolboxDef The definition of the toolbox in one of its many forms.
|
||||
* @return {!Blockly.utils.toolbox.ToolboxInfo} Object holding information
|
||||
* for creating a toolbox.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.toolbox.convertToToolboxJson_ = function(toolboxDef) {
|
||||
var contents = Blockly.utils.toolbox.xmlToJsonArray_(
|
||||
/** @type {!Node|!Array<Node>} */ (toolboxDef));
|
||||
var toolboxJson = {'contents': contents};
|
||||
if (toolboxDef instanceof Node) {
|
||||
Blockly.utils.toolbox.addAttributes_(toolboxDef, toolboxJson);
|
||||
}
|
||||
return toolboxJson;
|
||||
};
|
||||
|
||||
/**
|
||||
* Converts the xml for a toolbox to JSON.
|
||||
* @param {!Node|!Array<Node>|!NodeList} toolboxDef The
|
||||
* definition of the toolbox in one of its many forms.
|
||||
* @return {!Blockly.utils.toolbox.FlyoutItemInfoArray|
|
||||
* !Array<Blockly.utils.toolbox.ToolboxItemInfo>} A list of objects in
|
||||
* the toolbox.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.toolbox.xmlToJsonArray_ = function(toolboxDef) {
|
||||
var arr = [];
|
||||
// If it is a node it will have children.
|
||||
var childNodes = toolboxDef.childNodes;
|
||||
@@ -139,31 +330,61 @@ Blockly.utils.toolbox.toolboxXmlToJson_ = function(toolboxDef) {
|
||||
obj['blockxml'] = child;
|
||||
} else if (tagName == 'CATEGORY') {
|
||||
// Get the contents of a category
|
||||
obj['contents'] = Blockly.utils.toolbox.toolboxXmlToJson_(child);
|
||||
obj['contents'] = Blockly.utils.toolbox.xmlToJsonArray_(child);
|
||||
}
|
||||
|
||||
// Add xml attributes to object
|
||||
for (var j = 0; j < child.attributes.length; j++) {
|
||||
var attr = child.attributes[j];
|
||||
obj[attr.nodeName] = attr.value;
|
||||
}
|
||||
Blockly.utils.toolbox.addAttributes_(child, obj);
|
||||
arr.push(obj);
|
||||
}
|
||||
return arr;
|
||||
};
|
||||
|
||||
/**
|
||||
* Whether or not the toolbox definition has categories or not.
|
||||
* @param {Node|Array.<Blockly.utils.toolbox.Toolbox>} toolboxDef The definition
|
||||
* of the toolbox. Either in xml or JSON.
|
||||
* @return {boolean} True if the toolbox has categories.
|
||||
* @package
|
||||
* Adds the attributes on the node to the given object.
|
||||
* @param {!Node} node The node to copy the attributes from.
|
||||
* @param {!Object} obj The object to copy the attributes to.
|
||||
* @private
|
||||
*/
|
||||
Blockly.utils.toolbox.hasCategories = function(toolboxDef) {
|
||||
if (Array.isArray(toolboxDef)) {
|
||||
// Search for categories
|
||||
return !!(toolboxDef.length && toolboxDef[0]['kind'].toUpperCase() == 'CATEGORY');
|
||||
} else {
|
||||
return !!(toolboxDef && toolboxDef.getElementsByTagName('category').length);
|
||||
Blockly.utils.toolbox.addAttributes_ = function(node, obj) {
|
||||
for (var j = 0; j < node.attributes.length; j++) {
|
||||
var attr = node.attributes[j];
|
||||
if (attr.nodeName.indexOf('css-') > -1) {
|
||||
obj['cssconfig'] = obj['cssconfig'] || {};
|
||||
obj['cssconfig'][attr.nodeName.replace('css-', '')] = attr.value;
|
||||
} else {
|
||||
obj[attr.nodeName] = attr.value;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse the provided toolbox tree into a consistent DOM format.
|
||||
* @param {?Node|?string} toolboxDef DOM tree of blocks, or text representation
|
||||
* of same.
|
||||
* @return {?Node} DOM tree of blocks, or null.
|
||||
*/
|
||||
Blockly.utils.toolbox.parseToolboxTree = function(toolboxDef) {
|
||||
if (toolboxDef) {
|
||||
if (typeof toolboxDef != 'string') {
|
||||
if (Blockly.utils.userAgent.IE && toolboxDef.outerHTML) {
|
||||
// In this case the tree will not have been properly built by the
|
||||
// browser. The HTML will be contained in the element, but it will
|
||||
// not have the proper DOM structure since the browser doesn't support
|
||||
// XSLTProcessor (XML -> HTML).
|
||||
toolboxDef = toolboxDef.outerHTML;
|
||||
} else if (!(toolboxDef instanceof Element)) {
|
||||
toolboxDef = null;
|
||||
}
|
||||
}
|
||||
if (typeof toolboxDef == 'string') {
|
||||
toolboxDef = Blockly.Xml.textToDom(toolboxDef);
|
||||
if (toolboxDef.nodeName.toLowerCase() != 'xml') {
|
||||
throw TypeError('Toolbox should be an <xml> document.');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
toolboxDef = null;
|
||||
}
|
||||
return toolboxDef;
|
||||
};
|
||||
|
||||
+12
-13
@@ -1755,16 +1755,14 @@ Blockly.WorkspaceSvg.prototype.showContextMenu = function(e) {
|
||||
|
||||
/**
|
||||
* Modify the block tree on the existing toolbox.
|
||||
* @param {Blockly.utils.toolbox.ToolboxDefinition|string} toolboxDef
|
||||
* DOM tree of toolbox contents, string of toolbox contents, or array of JSON
|
||||
* representing toolbox contents.
|
||||
* @param {?Blockly.utils.toolbox.ToolboxDefinition} toolboxDef
|
||||
* DOM tree of toolbox contents, string of toolbox contents, or JSON
|
||||
* representing toolbox definition.
|
||||
*/
|
||||
Blockly.WorkspaceSvg.prototype.updateToolbox = function(toolboxDef) {
|
||||
if (!Array.isArray(toolboxDef)) {
|
||||
toolboxDef = Blockly.Options.parseToolboxTree(toolboxDef);
|
||||
}
|
||||
toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(toolboxDef);
|
||||
if (!toolboxDef) {
|
||||
var parsedToolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(toolboxDef);
|
||||
|
||||
if (!parsedToolboxDef) {
|
||||
if (this.options.languageTree) {
|
||||
throw Error('Can\'t nullify an existing toolbox.');
|
||||
}
|
||||
@@ -1773,18 +1771,19 @@ Blockly.WorkspaceSvg.prototype.updateToolbox = function(toolboxDef) {
|
||||
if (!this.options.languageTree) {
|
||||
throw Error('Existing toolbox is null. Can\'t create new toolbox.');
|
||||
}
|
||||
if (Blockly.utils.toolbox.hasCategories(toolboxDef)) {
|
||||
|
||||
if (Blockly.utils.toolbox.hasCategories(parsedToolboxDef)) {
|
||||
if (!this.toolbox_) {
|
||||
throw Error('Existing toolbox has no categories. Can\'t change mode.');
|
||||
}
|
||||
this.options.languageTree = toolboxDef;
|
||||
this.toolbox_.render(toolboxDef);
|
||||
this.options.languageTree = parsedToolboxDef;
|
||||
this.toolbox_.render(parsedToolboxDef);
|
||||
} else {
|
||||
if (!this.flyout_) {
|
||||
throw Error('Existing toolbox has categories. Can\'t change mode.');
|
||||
}
|
||||
this.options.languageTree = toolboxDef;
|
||||
this.flyout_.show(toolboxDef);
|
||||
this.options.languageTree = parsedToolboxDef;
|
||||
this.flyout_.show(parsedToolboxDef);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@ function typings() {
|
||||
const blocklySrcs = [
|
||||
"core/",
|
||||
"core/components",
|
||||
"core/components/tree",
|
||||
"core/keyboard_nav",
|
||||
"core/renderers/common",
|
||||
"core/renderers/measurables",
|
||||
"core/theme",
|
||||
"core/toolbox",
|
||||
"core/interfaces",
|
||||
"core/utils",
|
||||
"msg/"
|
||||
|
||||
@@ -28,7 +28,14 @@
|
||||
"dispatchPointerEvent": true,
|
||||
"createFireChangeListenerSpy": true,
|
||||
"createGenUidStubWithReturns": true,
|
||||
"getBasicToolbox": true,
|
||||
"getCategoryJSON": true,
|
||||
"getChildItem": true,
|
||||
"getCollapsibleItem": true,
|
||||
"getDeeplyNestedJSON": true,
|
||||
"getInjectedToolbox": true,
|
||||
"getNonCollapsibleItem": true,
|
||||
"getSeparator": true,
|
||||
"getSimpleJSON": true,
|
||||
"getXmlArray": true,
|
||||
"sharedTestSetup": true,
|
||||
|
||||
+9
-11
@@ -120,7 +120,7 @@
|
||||
</xml>
|
||||
|
||||
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-categories" style="display: none">
|
||||
<category name="First">
|
||||
<category name="First" css-container="something">
|
||||
<block type="basic_block">
|
||||
<field name="TEXT">FirstCategory-FirstBlock</field>
|
||||
</block>
|
||||
@@ -139,20 +139,18 @@
|
||||
<category name="First" expanded="true" categorystyle="logic_category">
|
||||
<sep gap="-1"></sep>
|
||||
<button text="insert" callbackkey="insertConnectionRows"></button>
|
||||
<block type="basic_block">
|
||||
<field name="TEXT">FirstCategory-FirstBlock</field>
|
||||
</block>
|
||||
<block type="basic_block">
|
||||
<field name="TEXT">FirstCategory-SecondBlock</field>
|
||||
</block>
|
||||
<block type="stack_block"></block>
|
||||
<block type="stack_block"></block>
|
||||
</category>
|
||||
<category name="Second">
|
||||
<block type="basic_block">
|
||||
<field name="TEXT">SecondCategory-FirstBlock</field>
|
||||
</block>
|
||||
<block type="stack_block"></block>
|
||||
</category>
|
||||
<sep gap="-1"></sep>
|
||||
<sep id="separator" gap="-1"></sep>
|
||||
<category name="Variables" custom="VARIABLE"></category>
|
||||
<category name="NestedCategory" >
|
||||
<category id="nestedCategory" name="NestedItemOne"></category>
|
||||
</category>
|
||||
<category name="lastItem"></category>
|
||||
</xml>
|
||||
<xml xmlns="https://developers.google.com/blockly/xml" id="toolbox-connections" style="display: none">
|
||||
<block type="stack_block"></block>
|
||||
|
||||
@@ -51,9 +51,6 @@ suite('Navigation', function() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
this.firstCategory_ = this.workspace.getToolbox().tree_.getChildAt(0);
|
||||
this.secondCategory_ = this.firstCategory_.getNextShownNode();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
@@ -61,60 +58,26 @@ suite('Navigation', function() {
|
||||
delete Blockly.Blocks['basic_block'];
|
||||
});
|
||||
|
||||
test('Next', function() {
|
||||
this.mockEvent.keyCode = Blockly.utils.KeyCodes.S;
|
||||
chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent));
|
||||
chai.assert.equal(Blockly.navigation.currentState_,
|
||||
Blockly.navigation.STATE_TOOLBOX);
|
||||
chai.assert.equal(this.workspace.getToolbox().tree_.getSelectedItem(),
|
||||
this.secondCategory_);
|
||||
});
|
||||
function testToolboxSelectMethodCalled(ws, mockEvent, keyCode, selectMethodName) {
|
||||
mockEvent.keyCode = keyCode;
|
||||
var toolbox = ws.getToolbox();
|
||||
toolbox.selectedItem_ = toolbox.contents_[0];
|
||||
var selectNextStub = sinon.stub(toolbox, selectMethodName);
|
||||
Blockly.navigation.onKeyPress(mockEvent);
|
||||
sinon.assert.called(selectNextStub);
|
||||
}
|
||||
|
||||
// Should be a no-op.
|
||||
test('Next at end', function() {
|
||||
this.workspace.getToolbox().tree_.getSelectedItem().selectNext();
|
||||
this.mockEvent.keyCode = Blockly.utils.KeyCodes.S;
|
||||
// Go forward one so that we can go back one.
|
||||
Blockly.navigation.onKeyPress(this.mockEvent);
|
||||
var startCategory = this.workspace.getToolbox().tree_.getSelectedItem();
|
||||
chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent));
|
||||
chai.assert.equal(Blockly.navigation.currentState_,
|
||||
Blockly.navigation.STATE_TOOLBOX);
|
||||
chai.assert.equal(this.workspace.getToolbox().tree_.getSelectedItem(),
|
||||
startCategory);
|
||||
test('Calls toolbox selectNext_', function() {
|
||||
testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.S, 'selectNext_');
|
||||
});
|
||||
|
||||
test('Previous', function() {
|
||||
// Go forward one so that we can go back one:
|
||||
this.workspace.getToolbox().tree_.getSelectedItem().selectNext();
|
||||
this.mockEvent.keyCode = Blockly.utils.KeyCodes.W;
|
||||
chai.assert.equal(this.workspace.getToolbox().tree_.getSelectedItem(),
|
||||
this.secondCategory_);
|
||||
chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent));
|
||||
chai.assert.equal(Blockly.navigation.currentState_,
|
||||
Blockly.navigation.STATE_TOOLBOX);
|
||||
chai.assert.equal(this.workspace.getToolbox().tree_.getSelectedItem(),
|
||||
this.firstCategory_);
|
||||
test('Calls toolbox selectPrevious_', function() {
|
||||
testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.W, 'selectPrevious_');
|
||||
});
|
||||
|
||||
// Should be a no-op.
|
||||
test('Previous at start', function() {
|
||||
var startCategory = this.workspace.getToolbox().tree_.getSelectedItem();
|
||||
this.mockEvent.keyCode = Blockly.utils.KeyCodes.W;
|
||||
chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent));
|
||||
chai.assert.equal(Blockly.navigation.currentState_,
|
||||
Blockly.navigation.STATE_TOOLBOX);
|
||||
chai.assert.equal(this.workspace.getToolbox().tree_.getSelectedItem(),
|
||||
startCategory);
|
||||
test('Calls toolbox selectParent_', function() {
|
||||
testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.D, 'selectChild_');
|
||||
});
|
||||
|
||||
test('Out', function() {
|
||||
this.mockEvent.keyCode = Blockly.utils.KeyCodes.A;
|
||||
chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent));
|
||||
// TODO (fenichel/aschmiedt): Decide whether out should go to the
|
||||
// workspace.
|
||||
chai.assert.equal(Blockly.navigation.currentState_,
|
||||
Blockly.navigation.STATE_TOOLBOX);
|
||||
test('Calls toolbox selectChild_', function() {
|
||||
testToolboxSelectMethodCalled(this.workspace, this.mockEvent, Blockly.utils.KeyCodes.A, 'selectParent_');
|
||||
});
|
||||
|
||||
test('Go to flyout', function() {
|
||||
@@ -255,7 +218,7 @@ suite('Navigation', function() {
|
||||
}]);
|
||||
this.workspace = createNavigationWorkspace(true);
|
||||
this.basicBlock = this.workspace.newBlock('basic_block');
|
||||
this.firstCategory_ = this.workspace.getToolbox().tree_.getChildAt(0);
|
||||
this.firstCategory_ = this.workspace.getToolbox().contents_[0];
|
||||
this.mockEvent = {
|
||||
getModifierState: function() {
|
||||
return false;
|
||||
@@ -337,7 +300,7 @@ suite('Navigation', function() {
|
||||
test('Toolbox', function() {
|
||||
this.mockEvent.keyCode = Blockly.utils.KeyCodes.T;
|
||||
chai.assert.isTrue(Blockly.navigation.onKeyPress(this.mockEvent));
|
||||
chai.assert.equal(this.workspace.getToolbox().tree_.getSelectedItem(), this.firstCategory_);
|
||||
chai.assert.equal(this.workspace.getToolbox().getSelectedItem(), this.firstCategory_);
|
||||
chai.assert.equal(Blockly.navigation.currentState_,
|
||||
Blockly.navigation.STATE_TOOLBOX);
|
||||
});
|
||||
|
||||
@@ -6,13 +6,16 @@
|
||||
|
||||
/**
|
||||
* Get JSON for a toolbox that contains categories.
|
||||
* @return {Array.<Blockly.utils.toolbox.Toolbox>} The array holding information
|
||||
* @return {Blockly.utils.toolbox.ToolboxJson} The array holding information
|
||||
* for a toolbox.
|
||||
*/
|
||||
function getCategoryJSON() {
|
||||
return [
|
||||
return {"contents": [
|
||||
{
|
||||
"kind": "CATEGORY",
|
||||
"cssconfig": {
|
||||
"container": "something"
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"kind": "BLOCK",
|
||||
@@ -34,16 +37,16 @@ function getCategoryJSON() {
|
||||
}
|
||||
],
|
||||
"name": "Second"
|
||||
}];
|
||||
}]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON for a simple toolbox.
|
||||
* @return {Array.<Blockly.utils.toolbox.Toolbox>} The array holding information
|
||||
* @return {Blockly.utils.toolbox.ToolboxJson} The array holding information
|
||||
* for a simple toolbox.
|
||||
*/
|
||||
function getSimpleJSON() {
|
||||
return [
|
||||
return {"contents":[
|
||||
{
|
||||
"kind":"BLOCK",
|
||||
"blockxml": "<block type=\"logic_operation\"></block>",
|
||||
@@ -62,12 +65,56 @@ function getSimpleJSON() {
|
||||
"kind":"LABEL",
|
||||
"text":"tooltips"
|
||||
}
|
||||
];
|
||||
]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get JSON for a toolbox that contains categories that contain categories.
|
||||
* @return {Blockly.utils.toolbox.ToolboxJson} The array holding information
|
||||
* for a toolbox.
|
||||
*/
|
||||
function getDeeplyNestedJSON() {
|
||||
return {"contents": [
|
||||
{
|
||||
"kind": "CATEGORY",
|
||||
"cssconfig": {
|
||||
"container": "something"
|
||||
},
|
||||
"contents": [{
|
||||
"kind": "CATEGORY",
|
||||
"contents": [{
|
||||
"kind": "CATEGORY",
|
||||
"contents": [
|
||||
{
|
||||
"kind": "BLOCK",
|
||||
"blockxml": '<block type="basic_block"><field name="TEXT">NestedCategory-FirstBlock</field></block>'
|
||||
},
|
||||
{
|
||||
"kind": "BLOCK",
|
||||
"blockxml": '<block type="basic_block"><field name="TEXT">NestedCategory-SecondBlock</field></block>'
|
||||
}
|
||||
],
|
||||
"name": "NestedCategoryInner"
|
||||
}],
|
||||
"name": "NestedCategoryMiddle",
|
||||
}],
|
||||
"name": "NestedCategoryOuter"
|
||||
},
|
||||
{
|
||||
"kind": "CATEGORY",
|
||||
"contents": [
|
||||
{
|
||||
"kind": "BLOCK",
|
||||
"blockxml": '<block type="basic_block"><field name="TEXT">SecondCategory-FirstBlock</field></block>'
|
||||
}
|
||||
],
|
||||
"name": "Second"
|
||||
}]};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array filled with xml elements.
|
||||
* @return {Array.<Nodde>} Array holding xml elements for a toolbox.
|
||||
* @return {Array<Node>} Array holding xml elements for a toolbox.
|
||||
*/
|
||||
function getXmlArray() {
|
||||
// Need to use HTMLElement instead of Element so parser output is
|
||||
@@ -79,3 +126,60 @@ function getXmlArray() {
|
||||
var label = Blockly.Xml.textToDom('<label text="tooltips"></label>');
|
||||
return [block, separator, button, label];
|
||||
}
|
||||
|
||||
function getInjectedToolbox() {
|
||||
/**
|
||||
* Category: First
|
||||
* sep
|
||||
* basic_block
|
||||
* basic_block
|
||||
* Category: second
|
||||
* basic_block
|
||||
* Category: Variables
|
||||
* custom: VARIABLE
|
||||
* Category: NestedCategory
|
||||
* Category: NestedItemOne
|
||||
*/
|
||||
var toolboxXml = document.getElementById('toolbox-test');
|
||||
var workspace = Blockly.inject('blocklyDiv',
|
||||
{
|
||||
toolbox: toolboxXml
|
||||
});
|
||||
return workspace.getToolbox();
|
||||
}
|
||||
|
||||
function getBasicToolbox() {
|
||||
var workspace = new Blockly.WorkspaceSvg(new Blockly.Options({}));
|
||||
var toolbox = new Blockly.Toolbox(workspace);
|
||||
toolbox.HtmlDiv = document.createElement('div');
|
||||
toolbox.flyout_ = sinon.createStubInstance(Blockly.VerticalFlyout);
|
||||
return toolbox;
|
||||
}
|
||||
|
||||
function getCollapsibleItem(toolbox) {
|
||||
var contents = toolbox.contents_;
|
||||
for (var i = 0; i < contents.length; i++) {
|
||||
var item = contents[i];
|
||||
if (item.isCollapsible()) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getNonCollapsibleItem(toolbox) {
|
||||
var contents = toolbox.contents_;
|
||||
for (var i = 0; i < contents.length; i++) {
|
||||
var item = contents[i];
|
||||
if (!item.isCollapsible()) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getChildItem(toolbox) {
|
||||
return toolbox.getToolboxItemById('nestedCategory');
|
||||
}
|
||||
|
||||
function getSeparator(toolbox) {
|
||||
return toolbox.getToolboxItemById('separator');
|
||||
}
|
||||
|
||||
+479
-212
@@ -5,112 +5,92 @@
|
||||
*/
|
||||
|
||||
suite('Toolbox', function() {
|
||||
|
||||
setup(function() {
|
||||
sharedTestSetup.call(this);
|
||||
Blockly.defineBlocksWithJsonArray([{
|
||||
"type": "basic_block",
|
||||
"message0": "%1",
|
||||
"args0": [
|
||||
{
|
||||
"type": "field_input",
|
||||
"name": "TEXT",
|
||||
"text": "default"
|
||||
}
|
||||
]
|
||||
}]);
|
||||
this.toolboxXml = document.getElementById('toolbox-test');
|
||||
this.workspace = Blockly.inject('blocklyDiv',
|
||||
{
|
||||
toolbox: this.toolboxXml
|
||||
});
|
||||
this.toolbox = this.workspace.getToolbox();
|
||||
defineStackBlock();
|
||||
});
|
||||
|
||||
teardown(function() {
|
||||
sharedTestTeardown.call(this);
|
||||
delete Blockly.Blocks['basic_block'];
|
||||
delete Blockly.Blocks['row_block'];
|
||||
});
|
||||
|
||||
suite('init', function() {
|
||||
setup(function() {
|
||||
this.toolbox.init();
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
test('HtmlDiv is created', function() {
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
|
||||
test('Init called -> HtmlDiv is created', function() {
|
||||
chai.assert.isDefined(this.toolbox.HtmlDiv);
|
||||
});
|
||||
test('HtmlDiv is inserted before parent node', function() {
|
||||
test('Init called -> HtmlDiv is inserted before parent node', function() {
|
||||
var toolboxDiv = Blockly.getMainWorkspace().getInjectionDiv().childNodes[0];
|
||||
chai.assert.equal(toolboxDiv.className, 'blocklyToolboxDiv blocklyNonSelectable');
|
||||
chai.assert.equal(toolboxDiv.className,
|
||||
'blocklyToolboxDiv blocklyNonSelectable');
|
||||
});
|
||||
test('hideChaff is called when the toolbox is clicked', function() {
|
||||
var hideChaffStub = sinon.stub(Blockly, "hideChaff");
|
||||
var evt = new MouseEvent('pointerdown', {});
|
||||
this.toolbox.HtmlDiv.dispatchEvent(evt);
|
||||
sinon.assert.calledOnce(hideChaffStub);
|
||||
test('Init called -> Toolbox is subscribed to background and foreground colour', function() {
|
||||
var themeManager = this.toolbox.workspace_.getThemeManager();
|
||||
var themeManagerSpy = sinon.spy(themeManager, 'subscribe');
|
||||
this.toolbox.init();
|
||||
sinon.assert.calledWith(themeManagerSpy, this.toolbox.HtmlDiv,
|
||||
'toolboxBackgroundColour', 'background-color');
|
||||
sinon.assert.calledWith(themeManagerSpy, this.toolbox.HtmlDiv,
|
||||
'toolboxForegroundColour', 'color');
|
||||
});
|
||||
test('Flyout is initialized', function() {
|
||||
test('Init called -> Render is called', function() {
|
||||
var renderSpy = sinon.spy(this.toolbox, 'render');
|
||||
this.toolbox.init();
|
||||
sinon.assert.calledOnce(renderSpy);
|
||||
});
|
||||
test('Init called -> Flyout is initialized', function() {
|
||||
this.toolbox.init();
|
||||
chai.assert.isDefined(this.toolbox.flyout_);
|
||||
});
|
||||
});
|
||||
|
||||
suite('render', function() {
|
||||
setup(function() {
|
||||
this.toolboxXml = Blockly.utils.toolbox.convertToolboxToJSON(this.toolboxXml);
|
||||
this.toolbox.selectFirstCategory();
|
||||
this.firstChild = this.toolbox.tree_.getChildAt(0);
|
||||
this.secondChild = this.toolbox.tree_.getChildAt(1);
|
||||
this.toolbox.handleBeforeTreeSelected_(this.secondChild);
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
test('Tree is created and set', function() {
|
||||
this.toolbox.render(this.toolboxXml);
|
||||
chai.assert.isDefined(this.toolbox.tree_);
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
test('Throws error if a toolbox has both blocks and categories at root level', function() {
|
||||
test('Render called with valid toolboxDef -> Contents are created', function() {
|
||||
var positionStub = sinon.stub(this.toolbox, 'position');
|
||||
this.toolbox.render({'contents': [
|
||||
{'kind': 'category', 'contents': []},
|
||||
{'kind': 'category', 'contents': []}
|
||||
]});
|
||||
chai.assert.lengthOf(this.toolbox.contents_, 2);
|
||||
sinon.assert.called(positionStub);
|
||||
});
|
||||
// TODO: Uncomment once implemented.
|
||||
test.skip('Toolbox definition with both blocks and categories -> Should throw an error', function() {
|
||||
var toolbox = this.toolbox;
|
||||
var badToolboxDef = [
|
||||
{
|
||||
"kind": "block",
|
||||
"blockxml": "<block type='controls_if'></block>"
|
||||
"kind": "block"
|
||||
},
|
||||
{
|
||||
"kind": "category",
|
||||
"name": "loops",
|
||||
"categorystyle": "math_category",
|
||||
"contents": [
|
||||
{
|
||||
"kind": "block",
|
||||
"blockxml": "<block type='controls_if'></block>"
|
||||
},
|
||||
{
|
||||
"kind": "button",
|
||||
"text": "insert",
|
||||
"callbackkey":"insertConnectionRows"
|
||||
},
|
||||
{
|
||||
"kind": "label",
|
||||
"text": "Something"
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
chai.assert.throws(function() {
|
||||
toolbox.render(badToolboxDef);
|
||||
toolbox.render({'contents' : badToolboxDef});
|
||||
}, 'Toolbox cannot have both blocks and categories in the root level.');
|
||||
});
|
||||
test('Select any open nodes', function() {
|
||||
// TODO: Uncomment once implemented.
|
||||
test.skip('Expanded set to true for a non collapsible toolbox item -> Should open flyout', function() {
|
||||
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.render(this.toolboxXml);
|
||||
var orientationAttribute = this.toolbox.tree_.getElement()
|
||||
.getAttribute('aria-orientation');
|
||||
chai.assert.equal(orientationAttribute, 'horizontal');
|
||||
});
|
||||
test('Create a toolbox from JSON', function() {
|
||||
var jsonDef = [
|
||||
test('JSON toolbox definition -> Should create toolbox with contents', function() {
|
||||
var jsonDef = {'contents' : [
|
||||
{
|
||||
"kind": "category",
|
||||
"contents": [
|
||||
@@ -135,88 +115,335 @@ suite('Toolbox', function() {
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
]};
|
||||
this.toolbox.render(jsonDef);
|
||||
chai.assert.lengthOf(this.toolbox.tree_.children_, 1);
|
||||
chai.assert.lengthOf(this.toolbox.contents_, 1);
|
||||
});
|
||||
});
|
||||
|
||||
suite('handleBeforeTreeSelected_', function() {
|
||||
suite('onClick_', function() {
|
||||
setup(function() {
|
||||
this.toolbox.selectFirstCategory();
|
||||
this.firstChild = this.toolbox.tree_.getChildAt(0);
|
||||
this.secondChild = this.toolbox.tree_.getChildAt(1);
|
||||
this.toolbox.handleBeforeTreeSelected_(this.secondChild);
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
test('Clear the previously selected category', function() {
|
||||
chai.assert.equal(this.firstChild.getRowElement().style.backgroundColor, '');
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
test('Set color for new selected category', function() {
|
||||
chai.assert.equal(this.secondChild.getRowElement().style.backgroundColor,
|
||||
'rgb(85, 119, 238)');
|
||||
|
||||
test('Toolbox clicked -> Should close flyout', function() {
|
||||
var hideChaffStub = sinon.stub(Blockly, "hideChaff");
|
||||
var evt = new MouseEvent('pointerdown', {});
|
||||
this.toolbox.HtmlDiv.dispatchEvent(evt);
|
||||
sinon.assert.calledOnce(hideChaffStub);
|
||||
});
|
||||
test('Category clicked -> Should select category', function() {
|
||||
var categoryXml = document.getElementsByClassName('blocklyTreeRow')[0];
|
||||
var evt = {
|
||||
'srcElement': categoryXml
|
||||
};
|
||||
var item = this.toolbox.contentMap_[categoryXml.getAttribute('id')];
|
||||
var setSelectedSpy = sinon.spy(this.toolbox, 'setSelectedItem');
|
||||
var onClickSpy = sinon.spy(item, 'onClick');
|
||||
this.toolbox.onClick_(evt);
|
||||
sinon.assert.calledOnce(setSelectedSpy);
|
||||
sinon.assert.calledOnce(onClickSpy);
|
||||
});
|
||||
});
|
||||
|
||||
suite('handleAfterTreeSelected_', function() {
|
||||
suite('onKeyDown_', function() {
|
||||
setup(function() {
|
||||
this.toolbox.selectFirstCategory();
|
||||
this.firstChild = this.toolbox.tree_.getChildAt(0);
|
||||
this.secondChild = this.toolbox.tree_.getChildAt(1);
|
||||
this.showStub = sinon.stub(this.toolbox.flyout_, "show");
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
test('Show the new set of blocks in the flyout', function() {
|
||||
this.toolbox.handleAfterTreeSelected_(this.firstChild, this.secondChild);
|
||||
sinon.assert.calledWith(this.showStub, this.secondChild.contents);
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
test('Opening the previous selected category does not scroll', function() {
|
||||
var scrollStub = sinon.stub(this.toolbox.flyout_, "scrollToStart");
|
||||
this.toolbox.handleAfterTreeSelected_(null, this.firstChild);
|
||||
sinon.assert.notCalled(scrollStub);
|
||||
|
||||
function createKeyDownMock(keyCode) {
|
||||
return {
|
||||
'keyCode': keyCode,
|
||||
'preventDefault': function() {}
|
||||
};
|
||||
}
|
||||
|
||||
function testCorrectFunctionCalled(toolbox, keyCode, funcName) {
|
||||
var event = createKeyDownMock(keyCode);
|
||||
var preventDefaultEvent = sinon.stub(event, 'preventDefault');
|
||||
var selectMethodStub = sinon.stub(toolbox, funcName);
|
||||
selectMethodStub.returns(true);
|
||||
toolbox.onKeyDown_(event);
|
||||
sinon.assert.called(selectMethodStub);
|
||||
sinon.assert.called(preventDefaultEvent);
|
||||
}
|
||||
|
||||
test('Down button is pushed -> Should call selectNext_', function() {
|
||||
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.DOWN, 'selectNext_', true);
|
||||
});
|
||||
test('Opening new category scrolls to top', function() {
|
||||
var scrollStub = sinon.stub(this.toolbox.flyout_, "scrollToStart");
|
||||
this.toolbox.handleAfterTreeSelected_(null, this.secondChild);
|
||||
sinon.assert.calledOnce(scrollStub);
|
||||
test('Up button is pushed -> Should call selectPrevious_', function() {
|
||||
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.UP, 'selectPrevious_', true);
|
||||
});
|
||||
test('Clicking selected category closes flyout', function() {
|
||||
var flyoutHideStub = sinon.stub(this.toolbox.flyout_, "hide");
|
||||
this.toolbox.handleAfterTreeSelected_(this.firstChild);
|
||||
sinon.assert.calledOnce(flyoutHideStub);
|
||||
test('Left button is pushed -> Should call selectParent_', function() {
|
||||
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.LEFT, 'selectParent_', true);
|
||||
});
|
||||
test('UI Event is fired when new category is selected', function() {
|
||||
this.eventsFireStub.resetHistory();
|
||||
this.toolbox.handleAfterTreeSelected_(this.firstChild);
|
||||
sinon.assert.calledOnce(this.eventsFireStub);
|
||||
test('Right button is pushed -> Should call selectChild_', function() {
|
||||
testCorrectFunctionCalled(this.toolbox, Blockly.utils.KeyCodes.RIGHT, 'selectChild_', true);
|
||||
});
|
||||
test('Last category is updated when there is a new node', function() {
|
||||
this.toolbox.handleAfterTreeSelected_(this.firstChild, this.secondChild);
|
||||
chai.assert.equal(this.toolbox.lastCategory_, this.secondChild);
|
||||
test('Enter button is pushed -> Should toggle expandedd', function() {
|
||||
this.toolbox.selectedItem_ = getCollapsibleItem(this.toolbox);
|
||||
var toggleExpandedStub = sinon.stub(this.toolbox.selectedItem_, 'toggleExpanded');
|
||||
var event = createKeyDownMock(Blockly.utils.KeyCodes.ENTER);
|
||||
var preventDefaultEvent = sinon.stub(event, 'preventDefault');
|
||||
this.toolbox.onKeyDown_(event);
|
||||
sinon.assert.called(toggleExpandedStub);
|
||||
sinon.assert.called(preventDefaultEvent);
|
||||
});
|
||||
test('Enter button is pushed when no item is selected -> Should not call prevent default', function() {
|
||||
this.toolbox.selectedItem_ = null;
|
||||
var event = createKeyDownMock(Blockly.utils.KeyCodes.ENTER);
|
||||
var preventDefaultEvent = sinon.stub(event, 'preventDefault');
|
||||
this.toolbox.onKeyDown_(event);
|
||||
sinon.assert.notCalled(preventDefaultEvent);
|
||||
});
|
||||
});
|
||||
|
||||
suite('Select Methods', function() {
|
||||
setup(function() {
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
|
||||
suite('selectChild_', function() {
|
||||
test('No item is selected -> Should not handle event', function() {
|
||||
this.toolbox.selectedItem_ = null;
|
||||
var handled = this.toolbox.selectChild_();
|
||||
chai.assert.isFalse(handled);
|
||||
});
|
||||
test('Selected item is not collapsible -> Should not handle event', function() {
|
||||
this.toolbox.selectedItem_ = getNonCollapsibleItem(this.toolbox);
|
||||
var handled = this.toolbox.selectChild_();
|
||||
chai.assert.isFalse(handled);
|
||||
});
|
||||
test('Selected item is collapsible -> Should expand', function() {
|
||||
var collapsibleItem = getCollapsibleItem(this.toolbox);
|
||||
this.toolbox.selectedItem_ = collapsibleItem;
|
||||
var handled = this.toolbox.selectChild_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.isTrue(collapsibleItem.isExpanded());
|
||||
chai.assert.equal(this.toolbox.selectedItem_, collapsibleItem);
|
||||
});
|
||||
|
||||
test('Selected item is expanded -> Should select child', function() {
|
||||
var collapsibleItem = getCollapsibleItem(this.toolbox);
|
||||
collapsibleItem.expanded_ = true;
|
||||
var selectNextStub = sinon.stub(this.toolbox, 'selectNext_');
|
||||
this.toolbox.selectedItem_ = collapsibleItem;
|
||||
var handled = this.toolbox.selectChild_();
|
||||
chai.assert.isTrue(handled);
|
||||
sinon.assert.called(selectNextStub);
|
||||
});
|
||||
});
|
||||
|
||||
suite('selectParent_', function() {
|
||||
test('No item selected -> Should not handle event', function() {
|
||||
this.toolbox.selectedItem_ = null;
|
||||
var handled = this.toolbox.selectParent_();
|
||||
chai.assert.isFalse(handled);
|
||||
});
|
||||
test('Selected item is expanded -> Should collapse', function() {
|
||||
var collapsibleItem = getCollapsibleItem(this.toolbox);
|
||||
collapsibleItem.expanded_ = true;
|
||||
this.toolbox.selectedItem_ = collapsibleItem;
|
||||
var handled = this.toolbox.selectParent_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.isFalse(collapsibleItem.isExpanded());
|
||||
chai.assert.equal(this.toolbox.selectedItem_, collapsibleItem);
|
||||
});
|
||||
test('Selected item is not expanded -> Should get parent', function() {
|
||||
var childItem = getChildItem(this.toolbox);
|
||||
this.toolbox.selectedItem_ = childItem;
|
||||
var handled = this.toolbox.selectParent_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.equal(this.toolbox.selectedItem_, childItem.getParent());
|
||||
});
|
||||
});
|
||||
|
||||
suite('selectNext_', function() {
|
||||
test('No item is selected -> Should not handle event', function() {
|
||||
this.toolbox.selectedItem_ = null;
|
||||
var handled = this.toolbox.selectNext_();
|
||||
chai.assert.isFalse(handled);
|
||||
});
|
||||
test('Next item is selectable -> Should select next item', function() {
|
||||
var item = this.toolbox.contents_[0];
|
||||
this.toolbox.selectedItem_ = item;
|
||||
var handled = this.toolbox.selectNext_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.equal(this.toolbox.selectedItem_, this.toolbox.contents_[1]);
|
||||
});
|
||||
test('Selected item is last item -> Should not handle event', function() {
|
||||
var item = this.toolbox.contents_[this.toolbox.contents_.length - 1];
|
||||
this.toolbox.selectedItem_ = item;
|
||||
var handled = this.toolbox.selectNext_();
|
||||
chai.assert.isFalse(handled);
|
||||
chai.assert.equal(this.toolbox.selectedItem_, item);
|
||||
});
|
||||
test('Selected item is collapsed -> Should skip over its children', function() {
|
||||
var item = getCollapsibleItem(this.toolbox);
|
||||
var childItem = item.flyoutItems_[0];
|
||||
item.expanded_ = false;
|
||||
this.toolbox.selectedItem_ = item;
|
||||
var handled = this.toolbox.selectNext_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.notEqual(this.toolbox.selectedItem_, childItem);
|
||||
});
|
||||
});
|
||||
|
||||
suite('selectPrevious', function() {
|
||||
test('No item is selected -> Should not handle event', function() {
|
||||
this.toolbox.selectedItem_ = null;
|
||||
var handled = this.toolbox.selectPrevious_();
|
||||
chai.assert.isFalse(handled);
|
||||
});
|
||||
test('Selected item is first item -> Should not handle event', function() {
|
||||
var item = this.toolbox.contents_[0];
|
||||
this.toolbox.selectedItem_ = item;
|
||||
var handled = this.toolbox.selectPrevious_();
|
||||
chai.assert.isFalse(handled);
|
||||
chai.assert.equal(this.toolbox.selectedItem_, item);
|
||||
});
|
||||
test('Previous item is selectable -> Should select previous item', function() {
|
||||
var item = this.toolbox.contents_[1];
|
||||
var prevItem = this.toolbox.contents_[0];
|
||||
this.toolbox.selectedItem_ = item;
|
||||
var handled = this.toolbox.selectPrevious_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.equal(this.toolbox.selectedItem_, prevItem);
|
||||
});
|
||||
test('Previous item is collapsed -> Should skip over children of the previous item', function() {
|
||||
var childItem = getChildItem(this.toolbox);
|
||||
var parentItem = childItem.getParent();
|
||||
var parentIdx = this.toolbox.contents_.indexOf(parentItem);
|
||||
// Gets the item after the parent.
|
||||
var item = this.toolbox.contents_[parentIdx + 1];
|
||||
this.toolbox.selectedItem_ = item;
|
||||
var handled = this.toolbox.selectPrevious_();
|
||||
chai.assert.isTrue(handled);
|
||||
chai.assert.notEqual(this.toolbox.selectedItem_, childItem);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
suite('setSelectedItem', function() {
|
||||
setup(function() {
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
|
||||
function setupSetSelected(toolbox, oldItem, newItem) {
|
||||
toolbox.selectedItem_ = oldItem;
|
||||
var newItemStub = sinon.stub(newItem, 'setSelected');
|
||||
toolbox.setSelectedItem(newItem);
|
||||
return newItemStub;
|
||||
}
|
||||
|
||||
test('Selected item and new item are null -> Should not update the flyout', function() {
|
||||
this.selectedItem_ = null;
|
||||
this.toolbox.setSelectedItem(null);
|
||||
var updateFlyoutStub = sinon.stub(this.toolbox, 'updateFlyout_');
|
||||
sinon.assert.notCalled(updateFlyoutStub);
|
||||
});
|
||||
test('New item is not selectable -> Should not update the flyout', function() {
|
||||
var separator = getSeparator(this.toolbox);
|
||||
this.toolbox.setSelectedItem(separator);
|
||||
var updateFlyoutStub = sinon.stub(this.toolbox, 'updateFlyout_');
|
||||
sinon.assert.notCalled(updateFlyoutStub);
|
||||
});
|
||||
test('Select an item with no children -> Should select item', function() {
|
||||
var oldItem = getCollapsibleItem(this.toolbox);
|
||||
var oldItemStub = sinon.stub(oldItem, 'setSelected');
|
||||
var newItem = getNonCollapsibleItem(this.toolbox);
|
||||
var newItemStub = setupSetSelected(this.toolbox, oldItem, newItem);
|
||||
sinon.assert.calledWith(oldItemStub, false);
|
||||
sinon.assert.calledWith(newItemStub, true);
|
||||
});
|
||||
test('Select previously selected item with no children -> Should deselect', function() {
|
||||
var newItem = getNonCollapsibleItem(this.toolbox);
|
||||
var newItemStub = setupSetSelected(this.toolbox, newItem, newItem);
|
||||
sinon.assert.calledWith(newItemStub, false);
|
||||
});
|
||||
test('Select collapsible item -> Should select item', function() {
|
||||
var newItem = getCollapsibleItem(this.toolbox);
|
||||
var newItemStub = setupSetSelected(this.toolbox, null, newItem);
|
||||
sinon.assert.calledWith(newItemStub, true);
|
||||
});
|
||||
test('Select previously selected collapsible item -> Should not deselect', function() {
|
||||
var newItem = getCollapsibleItem(this.toolbox);
|
||||
var newItemStub = setupSetSelected(this.toolbox, newItem, newItem);
|
||||
sinon.assert.notCalled(newItemStub);
|
||||
});
|
||||
});
|
||||
|
||||
suite('updateFlyout_', function() {
|
||||
setup(function() {
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
|
||||
function testHideFlyout(toolbox, oldItem, newItem) {
|
||||
var updateFlyoutStub = sinon.stub(toolbox.flyout_, 'hide');
|
||||
var newItem = getNonCollapsibleItem(toolbox);
|
||||
toolbox.updateFlyout_(oldItem, newItem);
|
||||
sinon.assert.called(updateFlyoutStub);
|
||||
}
|
||||
|
||||
test('Select previously selected item -> Should close flyout', function() {
|
||||
var newItem = getNonCollapsibleItem(this.toolbox);
|
||||
testHideFlyout(this.toolbox, newItem, newItem);
|
||||
});
|
||||
test('No new item -> Should close flyout', function() {
|
||||
testHideFlyout(this.toolbox, null, null);
|
||||
});
|
||||
test('Select collapsible item -> Should close flyout', function() {
|
||||
var newItem = getCollapsibleItem(this.toolbox);
|
||||
testHideFlyout(this.toolbox,null, newItem);
|
||||
});
|
||||
test('Select selectable item -> Should open flyout', function() {
|
||||
var showFlyoutstub = sinon.stub(this.toolbox.flyout_, 'show');
|
||||
var scrollToStartFlyout = sinon.stub(this.toolbox.flyout_, 'scrollToStart');
|
||||
var newItem = getNonCollapsibleItem(this.toolbox);
|
||||
this.toolbox.updateFlyout_(null, newItem);
|
||||
sinon.assert.called(showFlyoutstub);
|
||||
sinon.assert.called(scrollToStartFlyout);
|
||||
});
|
||||
});
|
||||
|
||||
suite('position', function() {
|
||||
setup(function() {
|
||||
this.toolbox.init();
|
||||
this.toolbox = getBasicToolbox();
|
||||
});
|
||||
|
||||
function checkHorizontalToolbox(toolbox) {
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.left, '0px', 'Check left position');
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.height, 'auto', 'Check height');
|
||||
var svgSize = Blockly.svgSize(toolbox.workspace_.getParentSvg());
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.width, svgSize.width + 'px', 'Check width');
|
||||
chai.assert.equal(toolbox.height, toolbox.HtmlDiv.offsetHeight, 'Check height');
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.width, '100%', 'Check width');
|
||||
chai.assert.equal(toolbox.height_, toolbox.HtmlDiv.offsetHeight, 'Check height');
|
||||
}
|
||||
function checkVerticalToolbox(toolbox) {
|
||||
var svgSize = Blockly.svgSize(toolbox.workspace_.getParentSvg());
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.height, svgSize.height + 'px', 'Check height');
|
||||
chai.assert.equal(toolbox.width, toolbox.HtmlDiv.offsetWidth, 'Check width');
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.height, '100%', 'Check height');
|
||||
chai.assert.equal(toolbox.width_, toolbox.HtmlDiv.offsetWidth, 'Check width');
|
||||
}
|
||||
test('Return if tree is not yet initialized', function() {
|
||||
this.toolbox.HtmlDiv = null;
|
||||
this.toolbox.horizontalLayout_ = true;
|
||||
this.toolbox.position();
|
||||
chai.assert.equal(this.toolbox.height, '');
|
||||
test('HtmlDiv is not created -> Should not resize', function() {
|
||||
var toolbox = this.toolbox;
|
||||
toolbox.HtmlDiv = null;
|
||||
toolbox.horizontalLayout_ = true;
|
||||
toolbox.position();
|
||||
chai.assert.equal(toolbox.height_, 0);
|
||||
});
|
||||
test('Position horizontal at top', function() {
|
||||
test('Horizontal toolbox at top -> Should anchor horizontal toolbox to top', function() {
|
||||
var toolbox = this.toolbox;
|
||||
toolbox.toolboxPosition = Blockly.TOOLBOX_AT_TOP;
|
||||
toolbox.horizontalLayout_ = true;
|
||||
@@ -224,7 +451,7 @@ suite('Toolbox', function() {
|
||||
checkHorizontalToolbox(toolbox);
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.top, '0px', 'Check top');
|
||||
});
|
||||
test('Position horizontal at bottom', function() {
|
||||
test('Horizontal toolbox at bottom -> Should anchor horizontal toolbox to bottom', function() {
|
||||
var toolbox = this.toolbox;
|
||||
toolbox.toolboxPosition = Blockly.TOOLBOX_AT_BOTTOM;
|
||||
toolbox.horizontalLayout_ = true;
|
||||
@@ -232,7 +459,7 @@ suite('Toolbox', function() {
|
||||
checkHorizontalToolbox(toolbox);
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.bottom, '0px', 'Check bottom');
|
||||
});
|
||||
test('Position Vertical at right', function() {
|
||||
test('Vertical toolbox at right -> Should anchor to right', function() {
|
||||
var toolbox = this.toolbox;
|
||||
toolbox.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT;
|
||||
toolbox.horizontalLayout_ = false;
|
||||
@@ -240,9 +467,9 @@ suite('Toolbox', function() {
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.right, '0px', 'Check right');
|
||||
checkVerticalToolbox(toolbox);
|
||||
});
|
||||
test('Position Vertical at left ', function() {
|
||||
test('Vertical toolbox at left -> Should anchor to left', function() {
|
||||
var toolbox = this.toolbox;
|
||||
toolbox.toolboxPosition = Blockly.TOOLBOX_AT_RIGHT;
|
||||
toolbox.toolboxPosition = Blockly.TOOLBOX_AT_LEFT;
|
||||
toolbox.horizontalLayout_ = false;
|
||||
toolbox.position();
|
||||
chai.assert.equal(toolbox.HtmlDiv.style.left, '0px', 'Check left');
|
||||
@@ -250,53 +477,7 @@ suite('Toolbox', function() {
|
||||
});
|
||||
});
|
||||
|
||||
suite('createTree_', function() {
|
||||
setup(function() {
|
||||
this.tree = new Blockly.tree.TreeControl(this.toolbox, this.toolbox.config_);
|
||||
this.tree.contents = [];
|
||||
this.toolboxXml = document.getElementById('toolbox-test');
|
||||
this.separatorIdx = 0;
|
||||
this.buttonIdx = 1;
|
||||
this.dynamicCategoryIdx = 3;
|
||||
this.categorySeparatorIdx = 2;
|
||||
this.toolboxXml = Blockly.utils.toolbox.convertToolboxToJSON(this.toolboxXml);
|
||||
});
|
||||
test('Having a dynamic category', function() {
|
||||
this.toolbox.createTree_(this.toolboxXml, this.tree);
|
||||
chai.assert.equal(this.tree.children_[this.dynamicCategoryIdx].contents, 'VARIABLE');
|
||||
});
|
||||
test('Node is expanded', function() {
|
||||
var openNode = this.toolbox.createTree_(this.toolboxXml, this.tree);
|
||||
chai.assert.exists(openNode);
|
||||
});
|
||||
test('Having a tree separator', function() {
|
||||
this.toolbox.createTree_(this.toolboxXml, this.tree);
|
||||
var sepObj = this.tree.children_[0].contents[this.separatorIdx];
|
||||
chai.assert.isNotNull(sepObj);
|
||||
chai.assert.equal(sepObj['gap'], -1);
|
||||
});
|
||||
test('Separator between two categories', function() {
|
||||
this.toolbox.createTree_(this.toolboxXml, this.tree);
|
||||
chai.assert.instanceOf(this.tree.children_[this.categorySeparatorIdx],
|
||||
Blockly.Toolbox.TreeSeparator);
|
||||
});
|
||||
test('Having a button', function() {
|
||||
this.toolbox.createTree_(this.toolboxXml, this.tree);
|
||||
var btnObj = this.tree.children_[0].contents[this.buttonIdx];
|
||||
chai.assert.isNotNull(btnObj);
|
||||
chai.assert.equal(btnObj['text'], 'insert');
|
||||
chai.assert.equal(btnObj['callbackkey'], 'insertConnectionRows');
|
||||
});
|
||||
test('Colours are set using correct method', function() {
|
||||
var setColourFromStyleStub = sinon.stub(this.toolbox, "setColourFromStyle_");
|
||||
var setColourStub = sinon.stub(this.toolbox, "setColour_");
|
||||
this.toolbox.createTree_(this.toolboxXml, this.tree);
|
||||
sinon.assert.calledOnce(setColourFromStyleStub);
|
||||
sinon.assert.called(setColourStub);
|
||||
});
|
||||
});
|
||||
|
||||
suite('parseToolbox', function() {
|
||||
suite('parseMethods', function() {
|
||||
setup(function() {
|
||||
this.categoryToolboxJSON = getCategoryJSON();
|
||||
this.simpleToolboxJSON = getSimpleJSON();
|
||||
@@ -305,72 +486,158 @@ suite('Toolbox', function() {
|
||||
function checkValue(actual, expected, value) {
|
||||
var actualVal = actual[value];
|
||||
var expectedVal = expected[value];
|
||||
chai.assert.equal(actualVal.toUpperCase(), expectedVal.toUpperCase(), 'Checknig value for: ' + value);
|
||||
chai.assert.equal(actualVal.toUpperCase(), expectedVal.toUpperCase(), 'Checking value for: ' + value);
|
||||
}
|
||||
function checkContents(actualContents, expectedContents) {
|
||||
chai.assert.equal(actualContents.length, expectedContents.length);
|
||||
for (var i = 0; i < actualContents.length; i++) {
|
||||
// TODO: Check the values as well as all the keys.
|
||||
chai.assert.containsAllKeys(actualContents[i], Object.keys(expectedContents[i]));
|
||||
}
|
||||
}
|
||||
function checkCategory(actual, expected) {
|
||||
checkValue(actual, expected, 'kind');
|
||||
checkValue(actual, expected, 'name');
|
||||
chai.assert.deepEqual(actual['cssconfig'], expected['cssconfig']);
|
||||
checkContents(actual.contents, expected.contents);
|
||||
}
|
||||
function checkCategoryToolbox(actual, expected) {
|
||||
chai.assert.equal(actual.length, expected.length);
|
||||
var actualContents = actual['contents'];
|
||||
var expectedContents = expected['contents'];
|
||||
chai.assert.equal(actualContents.length, expectedContents.length);
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
checkCategory(actual[i], expected[i]);
|
||||
checkCategory(actualContents[i], expected[i]);
|
||||
}
|
||||
}
|
||||
function checkSimpleToolbox(actual, expected) {
|
||||
checkContents(actual, expected);
|
||||
checkContents(actual['contents'], expected['contents']);
|
||||
}
|
||||
|
||||
test('Simple Toolbox: Array with xml', function() {
|
||||
var xmlList = getXmlArray();
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(xmlList);
|
||||
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
|
||||
suite('parseToolbox', function() {
|
||||
test('Category Toolbox: JSON', function() {
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(this.categoryToolboxJSON);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
|
||||
});
|
||||
test('Simple Toolbox: JSON', function() {
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(this.simpleToolboxJSON);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
|
||||
});
|
||||
test('Category Toolbox: xml', function() {
|
||||
var toolboxXml = document.getElementById('toolbox-categories');
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(toolboxXml);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
|
||||
});
|
||||
test('Simple Toolbox: xml', function() {
|
||||
var toolboxXml = document.getElementById('toolbox-simple');
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(toolboxXml);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
|
||||
});
|
||||
test('Simple Toolbox: string', function() {
|
||||
var toolbox = '<xml>';
|
||||
toolbox += ' <block type="controls_if"></block>';
|
||||
toolbox += ' <block type="controls_whileUntil"></block>';
|
||||
toolbox += '</xml>';
|
||||
|
||||
var toolboxJson = {
|
||||
'contents': [
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'controls_if'
|
||||
},
|
||||
{
|
||||
'kind': 'block',
|
||||
'type': 'controls_if'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(toolbox);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkSimpleToolbox(toolboxDef, toolboxJson);
|
||||
});
|
||||
test('Category Toolbox: string', function() {
|
||||
var toolbox = '<xml>';
|
||||
toolbox += ' <category name="a"></category>';
|
||||
toolbox += ' <category name="b"></category>';
|
||||
toolbox += '</xml>';
|
||||
|
||||
var toolboxJson = {
|
||||
'contents': [
|
||||
{
|
||||
'kind': 'category',
|
||||
'name': 'a'
|
||||
},
|
||||
{
|
||||
'kind': 'category',
|
||||
'name': 'b'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxDefToJson(toolbox);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkSimpleToolbox(toolboxDef, toolboxJson);
|
||||
});
|
||||
});
|
||||
test('Category Toolbox: Array with xml', function() {
|
||||
var categoryOne = Blockly.Xml.textToDom('<category name="First"><block type="basic_block"><field name="TEXT">FirstCategory-FirstBlock</field></block><block type="basic_block"><field name="TEXT">FirstCategory-SecondBlock</field></block></category>');
|
||||
var categoryTwo = Blockly.Xml.textToDom('<category name="Second"><block type="basic_block"><field name="TEXT">SecondCategory-FirstBlock</field></block></category>');
|
||||
var xmlList = [categoryOne, categoryTwo];
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(xmlList);
|
||||
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
|
||||
suite('parseFlyout', function() {
|
||||
test('Array of Nodes', function() {
|
||||
var xmlList = getXmlArray();
|
||||
var flyoutDef = Blockly.utils.toolbox.convertFlyoutDefToJsonArray(xmlList);
|
||||
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
|
||||
});
|
||||
test('NodeList', function() {
|
||||
var nodeList = document.getElementById('toolbox-simple').childNodes;
|
||||
var flyoutDef = Blockly.utils.toolbox.convertFlyoutDefToJsonArray(nodeList);
|
||||
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
|
||||
});
|
||||
test('List of json', function() {
|
||||
var jsonList = this.simpleToolboxJSON['contents'];
|
||||
var flyoutDef = Blockly.utils.toolbox.convertFlyoutDefToJsonArray(jsonList);
|
||||
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
|
||||
});
|
||||
test('Json', function() {
|
||||
var flyoutDef = Blockly.utils.toolbox.convertFlyoutDefToJsonArray(this.simpleToolboxJSON);
|
||||
checkContents(flyoutDef, this.simpleToolboxJSON['contents']);
|
||||
});
|
||||
});
|
||||
test('Category Toolbox: Array with JSON', function() {
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(this.categoryToolboxJSON);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
|
||||
});
|
||||
suite('Nested Categories', function() {
|
||||
setup(function() {
|
||||
this.toolbox = getInjectedToolbox();
|
||||
});
|
||||
test('Simple Toolbox: Array with JSON', function() {
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(this.simpleToolboxJSON);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
|
||||
teardown(function() {
|
||||
this.toolbox.dispose();
|
||||
});
|
||||
test('Category Toolbox: NodeList', function() {
|
||||
var nodeList = document.getElementById('toolbox-categories').childNodes;
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(nodeList);
|
||||
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
|
||||
test('Child categories visible if all ancestors expanded', function() {
|
||||
this.toolbox.render(getDeeplyNestedJSON());
|
||||
var outerCategory = this.toolbox.contents_[0];
|
||||
var middleCategory = this.toolbox.contents_[1];
|
||||
var innerCategory = this.toolbox.contents_[2];
|
||||
|
||||
outerCategory.toggleExpanded();
|
||||
middleCategory.toggleExpanded();
|
||||
innerCategory.show();
|
||||
|
||||
chai.assert.isTrue(innerCategory.isVisible(),
|
||||
'All ancestors are expanded, so category should be visible');
|
||||
});
|
||||
test('Simple Toolbox: NodeList', function() {
|
||||
var nodeList = document.getElementById('toolbox-simple').childNodes;
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(nodeList);
|
||||
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
|
||||
});
|
||||
test('Category Toolbox: xml', function() {
|
||||
var toolboxXml = document.getElementById('toolbox-categories');
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(toolboxXml);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkCategoryToolbox(toolboxDef, this.categoryToolboxJSON);
|
||||
});
|
||||
test('Simple Toolbox: xml', function() {
|
||||
var toolboxXml = document.getElementById('toolbox-simple');
|
||||
var toolboxDef = Blockly.utils.toolbox.convertToolboxToJSON(toolboxXml);
|
||||
chai.assert.isNotNull(toolboxDef);
|
||||
checkSimpleToolbox(toolboxDef, this.simpleToolboxJSON);
|
||||
test('Child categories not visible if any ancestor not expanded', function() {
|
||||
this.toolbox.render(getDeeplyNestedJSON());
|
||||
var middleCategory = this.toolbox.contents_[1];
|
||||
var innerCategory = this.toolbox.contents_[2];
|
||||
|
||||
// Don't expand the outermost category
|
||||
// Even though the direct parent of inner is expanded, it shouldn't be visible
|
||||
// because all ancestor categories need to be visible, not just parent
|
||||
middleCategory.toggleExpanded();
|
||||
innerCategory.show();
|
||||
|
||||
chai.assert.isFalse(innerCategory.isVisible(),
|
||||
'Not all ancestors are expanded, so category should not be visible');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -97,25 +97,25 @@ suite('WorkspaceSvg', function() {
|
||||
test('Passes in toolbox def when current toolbox is null', function() {
|
||||
this.workspace.options.languageTree = null;
|
||||
chai.assert.throws(function() {
|
||||
this.workspace.updateToolbox([]);
|
||||
this.workspace.updateToolbox({'contents': []});
|
||||
}.bind(this), 'Existing toolbox is null. Can\'t create new toolbox.');
|
||||
});
|
||||
test('Existing toolbox has no categories', function() {
|
||||
sinon.stub(Blockly.utils.toolbox, 'hasCategories').returns(true);
|
||||
this.workspace.toolbox_ = null;
|
||||
chai.assert.throws(function() {
|
||||
this.workspace.updateToolbox([]);
|
||||
this.workspace.updateToolbox({'contents': []});
|
||||
}.bind(this), 'Existing toolbox has no categories. Can\'t change mode.');
|
||||
});
|
||||
test('Existing toolbox has categories', function() {
|
||||
sinon.stub(Blockly.utils.toolbox, 'hasCategories').returns(false);
|
||||
this.workspace.flyout_ = null;
|
||||
chai.assert.throws(function() {
|
||||
this.workspace.updateToolbox([]);
|
||||
this.workspace.updateToolbox({'contents': []});
|
||||
}.bind(this), 'Existing toolbox has categories. Can\'t change mode.');
|
||||
});
|
||||
test('Passing in string as toolboxdef', function() {
|
||||
var parseToolboxFake = sinon.spy(Blockly.Options, 'parseToolboxTree');
|
||||
var parseToolboxFake = sinon.spy(Blockly.utils.toolbox, 'parseToolboxTree');
|
||||
this.workspace.updateToolbox('<xml><category name="something"></category></xml>');
|
||||
sinon.assert.calledOnce(parseToolboxFake);
|
||||
});
|
||||
|
||||
@@ -16,6 +16,11 @@
|
||||
var alignCategory = {
|
||||
"kind": "CATEGORY",
|
||||
"name": "Align",
|
||||
"classConfig": {
|
||||
"container": "containerSomething",
|
||||
"row": "rowSomething",
|
||||
"icon": "iconSomething"
|
||||
},
|
||||
"contents": [
|
||||
{
|
||||
"kind": "BLOCK",
|
||||
|
||||
Reference in New Issue
Block a user