From 5344ad6c219ffacee08a416cf0fbe213eea749cf Mon Sep 17 00:00:00 2001 From: Monica Kozbial <6621618+moniika@users.noreply.github.com> Date: Thu, 27 May 2021 17:01:11 -0700 Subject: [PATCH] Add support for IAutoHideable (#4855) --- blockly_uncompressed.js | 5 +++-- core/blockly.js | 27 ++++++++++--------------- core/component_manager.js | 7 +++++++ core/interfaces/i_autohideable.js | 33 +++++++++++++++++++++++++++++++ core/toolbox/toolbox.js | 22 +++++++++++++++++++++ core/trashcan.js | 25 ++++++++++++++++++++++- core/workspace_svg.js | 14 +------------ core/zoom_controls.js | 6 ++++++ 8 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 core/interfaces/i_autohideable.js diff --git a/blockly_uncompressed.js b/blockly_uncompressed.js index e2e7f96ac..48244f0f0 100644 --- a/blockly_uncompressed.js +++ b/blockly_uncompressed.js @@ -86,6 +86,7 @@ goog.addDependency('../../core/input.js', ['Blockly.Input'], ['Blockly.Connectio goog.addDependency('../../core/input_types.js', ['Blockly.inputTypes'], ['Blockly.connectionTypes']); goog.addDependency('../../core/insertion_marker_manager.js', ['Blockly.InsertionMarkerManager'], ['Blockly.Events', 'Blockly.blockAnimations', 'Blockly.connectionTypes', 'Blockly.constants'], {'lang': 'es5'}); goog.addDependency('../../core/interfaces/i_accessibility.js', ['Blockly.IASTNodeLocation', 'Blockly.IASTNodeLocationSvg', 'Blockly.IASTNodeLocationWithBlock', 'Blockly.IKeyboardAccessible'], []); +goog.addDependency('../../core/interfaces/i_autohideable.js', ['Blockly.IAutoHideable'], ['Blockly.IComponent']); goog.addDependency('../../core/interfaces/i_bounded_element.js', ['Blockly.IBoundedElement'], []); goog.addDependency('../../core/interfaces/i_bubble.js', ['Blockly.IBubble'], ['Blockly.IContextMenu', 'Blockly.IDeletable']); goog.addDependency('../../core/interfaces/i_component.js', ['Blockly.IComponent'], []); @@ -173,12 +174,12 @@ goog.addDependency('../../core/theme_manager.js', ['Blockly.ThemeManager'], ['Bl goog.addDependency('../../core/toolbox/category.js', ['Blockly.ToolboxCategory'], ['Blockly.ISelectableToolboxItem', '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.ICollapsibleToolboxItem', 'Blockly.ToolboxCategory', 'Blockly.ToolboxItem', 'Blockly.ToolboxSeparator', 'Blockly.registry', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.object', 'Blockly.utils.toolbox']); goog.addDependency('../../core/toolbox/separator.js', ['Blockly.ToolboxSeparator'], ['Blockly.IToolboxItem', '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.ToolboxItemSelect', 'Blockly.IDeleteArea', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); +goog.addDependency('../../core/toolbox/toolbox.js', ['Blockly.Toolbox'], ['Blockly.CollapsibleToolboxCategory', 'Blockly.Css', 'Blockly.Events', 'Blockly.Events.ToolboxItemSelect', 'Blockly.IAutoHideable', 'Blockly.IDeleteArea', 'Blockly.IKeyboardAccessible', 'Blockly.IStyleable', 'Blockly.IToolbox', 'Blockly.Options', 'Blockly.Touch', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.utils', 'Blockly.utils.Rect', 'Blockly.utils.aria', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/toolbox/toolbox_item.js', ['Blockly.ToolboxItem'], ['Blockly.IToolboxItem']); goog.addDependency('../../core/tooltip.js', ['Blockly.Tooltip'], ['Blockly.browserEvents', 'Blockly.utils.string']); goog.addDependency('../../core/touch.js', ['Blockly.Touch'], ['Blockly.constants', 'Blockly.utils', 'Blockly.utils.global', 'Blockly.utils.string']); goog.addDependency('../../core/touch_gesture.js', ['Blockly.TouchGesture'], ['Blockly.Gesture', 'Blockly.browserEvents', 'Blockly.utils', 'Blockly.utils.Coordinate', 'Blockly.utils.object']); -goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.IDeleteArea', 'Blockly.IPositionable', 'Blockly.Options', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); +goog.addDependency('../../core/trashcan.js', ['Blockly.Trashcan'], ['Blockly.Events', 'Blockly.Events.TrashcanOpen', 'Blockly.IAutoHideable', 'Blockly.IDeleteArea', 'Blockly.IPositionable', 'Blockly.Options', 'Blockly.Xml', 'Blockly.browserEvents', 'Blockly.constants', 'Blockly.registry', 'Blockly.uiPosition', 'Blockly.utils.Rect', 'Blockly.utils.Svg', 'Blockly.utils.dom', 'Blockly.utils.toolbox'], {'lang': 'es5'}); goog.addDependency('../../core/utils.js', ['Blockly.utils'], ['Blockly.Msg', 'Blockly.constants', 'Blockly.utils.Coordinate', 'Blockly.utils.Rect', 'Blockly.utils.colour', 'Blockly.utils.global', 'Blockly.utils.string', 'Blockly.utils.style', 'Blockly.utils.userAgent']); goog.addDependency('../../core/utils/aria.js', ['Blockly.utils.aria'], []); goog.addDependency('../../core/utils/colour.js', ['Blockly.utils.colour'], []); diff --git a/core/blockly.js b/core/blockly.js index cc13900b7..4ed797022 100644 --- a/core/blockly.js +++ b/core/blockly.js @@ -294,27 +294,20 @@ Blockly.onContextMenu_ = function(e) { /** * Close tooltips, context menus, dropdown selections, etc. - * @param {boolean=} opt_allowToolbox If true, don't close the toolbox. + * @param {boolean=} opt_onlyClosePopups Whether only popups should be closed. */ -Blockly.hideChaff = function(opt_allowToolbox) { +Blockly.hideChaff = function(opt_onlyClosePopups) { Blockly.Tooltip.hide(); Blockly.WidgetDiv.hide(); Blockly.DropDownDiv.hideWithoutAnimation(); - if (!opt_allowToolbox) { - var workspace = Blockly.getMainWorkspace(); - // For now the trashcan flyout always autocloses because it overlays the - // trashcan UI (no trashcan to click to close it). - if (workspace.trashcan && - workspace.trashcan.flyout) { - workspace.trashcan.closeFlyout(); - } - var toolbox = workspace.getToolbox(); - if (toolbox && - toolbox.getFlyout() && - toolbox.getFlyout().autoClose) { - toolbox.clearSelection(); - } - } + + var onlyClosePopups = !!opt_onlyClosePopups; + var workspace = Blockly.getMainWorkspace(); + var autoHideables = workspace.getComponentManager().getComponents( + Blockly.ComponentManager.Capability.AUTOHIDEABLE, true); + autoHideables.forEach(function(autoHideable) { + autoHideable.autoHide(onlyClosePopups); + }); }; /** diff --git a/core/component_manager.js b/core/component_manager.js index 691b573bd..825473186 100644 --- a/core/component_manager.js +++ b/core/component_manager.js @@ -13,6 +13,10 @@ goog.provide('Blockly.ComponentManager'); +goog.requireType('Blockly.IAutoHideable'); +goog.requireType('Blockly.IComponent'); +goog.requireType('Blockly.IPositionable'); + /** * Manager for all items registered with the workspace. @@ -136,3 +140,6 @@ Blockly.ComponentManager.Capability.prototype.toString = function() { /** @type {!Blockly.ComponentManager.Capability} */ Blockly.ComponentManager.Capability.POSITIONABLE = new Blockly.ComponentManager.Capability('positionable'); +/** @type {!Blockly.ComponentManager.Capability} */ +Blockly.ComponentManager.Capability.AUTOHIDEABLE = + new Blockly.ComponentManager.Capability('autohideable'); diff --git a/core/interfaces/i_autohideable.js b/core/interfaces/i_autohideable.js new file mode 100644 index 000000000..542919286 --- /dev/null +++ b/core/interfaces/i_autohideable.js @@ -0,0 +1,33 @@ +/** + * @license + * Copyright 2021 Google LLC + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @fileoverview The interface for a component that is automatically hidden + * when Blockly.hideChaff is called. + * @author kozbial@google.com (Monica Kozbial) + */ + +'use strict'; + +goog.provide('Blockly.IAutoHideable'); + + +goog.require('Blockly.IComponent'); + + +/** + * Interface for a component that can be automatically hidden. + * @extends {Blockly.IComponent} + * @interface + */ +Blockly.IAutoHideable = function() {}; + +/** + * Hides the component. Called in Blockly.hideChaff. + * @param {boolean} onlyClosePopups Whether only popups should be closed. + * Flyouts should not be closed if this is true. + */ +Blockly.IAutoHideable.prototype.autoHide; diff --git a/core/toolbox/toolbox.js b/core/toolbox/toolbox.js index ba6db95fa..9769b96c5 100644 --- a/core/toolbox/toolbox.js +++ b/core/toolbox/toolbox.js @@ -20,6 +20,7 @@ goog.require('Blockly.Css'); goog.require('Blockly.Events'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.ToolboxItemSelect'); +goog.require('Blockly.IAutoHideable'); goog.require('Blockly.IDeleteArea'); goog.require('Blockly.IKeyboardAccessible'); goog.require('Blockly.IStyleable'); @@ -47,6 +48,7 @@ goog.requireType('Blockly.WorkspaceSvg'); * @param {!Blockly.WorkspaceSvg} workspace The workspace in which to create new * blocks. * @constructor + * @implements {Blockly.IAutoHideable} * @implements {Blockly.IKeyboardAccessible} * @implements {Blockly.IDeleteArea} * @implements {Blockly.IStyleable} @@ -187,6 +189,15 @@ Blockly.Toolbox.prototype.init = function() { themeManager.subscribe(this.HtmlDiv, 'toolboxBackgroundColour', 'background-color'); themeManager.subscribe(this.HtmlDiv, 'toolboxForegroundColour', 'color'); + + this.workspace_.getComponentManager().addComponent({ + id: 'toolbox', + component: this, + weight: 1, + capabilities: [ + Blockly.ComponentManager.Capability.AUTOHIDEABLE + ] + }); }; /** @@ -696,6 +707,17 @@ Blockly.Toolbox.prototype.setVisible = function(isVisible) { this.HtmlDiv.style.display = isVisible ? 'block' : 'none'; }; +/** + * Hides the component. Called in Blockly.hideChaff. + * @param {boolean} onlyClosePopups Whether only popups should be closed. + * Flyouts should not be closed if this is true. + */ +Blockly.Toolbox.prototype.autoHide = function(onlyClosePopups) { + if (!onlyClosePopups && this.flyout_ && this.flyout_.autoClose) { + this.clearSelection(); + } +}; + /** * Sets the given item as selected. * No-op if the item is not selectable. diff --git a/core/trashcan.js b/core/trashcan.js index c825aa358..b402fd6f9 100644 --- a/core/trashcan.js +++ b/core/trashcan.js @@ -18,6 +18,7 @@ goog.require('Blockly.constants'); goog.require('Blockly.Events'); /** @suppress {extraRequire} */ goog.require('Blockly.Events.TrashcanOpen'); +goog.require('Blockly.IAutoHideable'); goog.require('Blockly.IDeleteArea'); goog.require('Blockly.IPositionable'); goog.require('Blockly.Options'); @@ -38,6 +39,7 @@ goog.requireType('Blockly.WorkspaceSvg'); * Class for a trash can. * @param {!Blockly.WorkspaceSvg} workspace The workspace to sit in. * @constructor + * @implements {Blockly.IAutoHideable} * @implements {Blockly.IDeleteArea} * @implements {Blockly.IPositionable} */ @@ -357,7 +359,15 @@ Blockly.Trashcan.prototype.init = function() { this.workspace_.getParentSvg()); this.flyout.init(this.workspace_); } - + this.workspace_.getComponentManager().addComponent({ + id: 'trashcan', + component: this, + weight: 1, + capabilities: [ + Blockly.ComponentManager.Capability.POSITIONABLE, + Blockly.ComponentManager.Capability.AUTOHIDEABLE + ] + }); this.initialized_ = true; this.setLidOpen(false); }; @@ -422,6 +432,19 @@ Blockly.Trashcan.prototype.closeFlyout = function() { this.fireUiEvent_(false); }; +/** + * Hides the component. Called in Blockly.hideChaff. + * @param {boolean} onlyClosePopups Whether only popups should be closed. + * Flyouts should not be closed if this is true. + */ +Blockly.Trashcan.prototype.autoHide = function(onlyClosePopups) { + // For now the trashcan flyout always autocloses because it overlays the + // trashcan UI (no trashcan to click to close it). + if (!onlyClosePopups && this.flyout) { + this.closeFlyout(); + } +}; + /** * Empties the trashcan's contents. If the contents-flyout is currently open * it will be closed. diff --git a/core/workspace_svg.js b/core/workspace_svg.js index 1946f8bfc..32df698d6 100644 --- a/core/workspace_svg.js +++ b/core/workspace_svg.js @@ -1003,12 +1003,6 @@ Blockly.WorkspaceSvg.prototype.addTrashcan = function() { this.trashcan = new Blockly.Trashcan(this); var svgTrashcan = this.trashcan.createDom(); this.svgGroup_.insertBefore(svgTrashcan, this.svgBlockCanvas_); - this.componentManager_.addComponent({ - id: 'trashcan', - component: this.trashcan, - weight: 1, - capabilities: [Blockly.ComponentManager.Capability.POSITIONABLE] - }); }; /** @@ -1023,12 +1017,6 @@ Blockly.WorkspaceSvg.prototype.addZoomControls = function() { this.zoomControls_ = new Blockly.ZoomControls(this); var svgZoomControls = this.zoomControls_.createDom(); this.svgGroup_.appendChild(svgZoomControls); - this.componentManager_.addComponent({ - id: 'zoomControls', - component: this.zoomControls_, - weight: 2, - capabilities: [Blockly.ComponentManager.Capability.POSITIONABLE] - }); }; /** @@ -2261,7 +2249,7 @@ Blockly.WorkspaceSvg.prototype.getScale = function() { * @package */ Blockly.WorkspaceSvg.prototype.scroll = function(x, y) { - Blockly.hideChaff(/* opt_allowToolbox */ true); + Blockly.hideChaff(/* opt_onlyClosePopups */ true); // Keep scrolling within the bounds of the content. var metrics = this.getMetrics(); diff --git a/core/zoom_controls.js b/core/zoom_controls.js index 84e84febb..530a24142 100644 --- a/core/zoom_controls.js +++ b/core/zoom_controls.js @@ -189,6 +189,12 @@ Blockly.ZoomControls.prototype.createDom = function() { * Initializes the zoom controls. */ Blockly.ZoomControls.prototype.init = function() { + this.workspace_.getComponentManager().addComponent({ + id: 'zoomControls', + component: this, + weight: 2, + capabilities: [Blockly.ComponentManager.Capability.POSITIONABLE] + }); this.initialized_ = true; };