/** * @license * Copyright 2019 Google LLC * SPDX-License-Identifier: Apache-2.0 */ /** * @fileoverview Object in charge of storing and updating a workspace theme * and UI components. * @author aschmiedt@google.com (Abby Schmiedt) * @author samelh@google.com (Sam El-Husseini) */ 'use strict'; goog.provide('Blockly.ThemeManager'); goog.require('Blockly.Theme'); /** * Class for storing and updating a workspace's theme and UI components. * @param {!Blockly.WorkspaceSvg} workspace The main workspace. * @param {!Blockly.Theme} theme The workspace theme. * @constructor * @package */ Blockly.ThemeManager = function(workspace, theme) { /** * The main workspace. * @type {!Blockly.WorkspaceSvg} * @private */ this.workspace_ = workspace; /** * The Blockly theme to use. * @type {!Blockly.Theme} * @private */ this.theme_ = theme; /** * A list of workspaces that are subscribed to this theme. * @type {!Array.} * @private */ this.subscribedWorkspaces_ = []; /** * A map of subscribed UI components, keyed by component name. * @type {!Object.>} * @private */ this.componentDB_ = Object.create(null); }; /** * A Blockly UI component type. * @typedef {{ * element:!Element, * propertyName:string * }} */ Blockly.ThemeManager.Component; /** * Get the workspace theme. * @return {!Blockly.Theme} The workspace theme. * @package */ Blockly.ThemeManager.prototype.getTheme = function() { return this.theme_; }; /** * Set the workspace theme, and refresh the workspace and all components. * @param {!Blockly.Theme} theme The workspace theme. * @package */ Blockly.ThemeManager.prototype.setTheme = function(theme) { var prevTheme = this.theme_; this.theme_ = theme; // Set the theme name onto the injection div. var injectionDiv = this.workspace_.getInjectionDiv(); if (injectionDiv) { if (prevTheme) { Blockly.utils.dom.removeClass(injectionDiv, prevTheme.name + '-theme'); } Blockly.utils.dom.addClass(injectionDiv, this.theme_.name + '-theme'); } // Refresh all subscribed workspaces. for (var i = 0, workspace; (workspace = this.subscribedWorkspaces_[i]); i++) { workspace.refreshTheme(); // Re-render if workspace is visible if (workspace.isVisible()) { workspace.setVisible(true); } } // Refresh all registered Blockly UI components. for (var i = 0, keys = Object.keys(this.componentDB_), key; (key = keys[i]); i++) { for (var j = 0, component; (component = this.componentDB_[key][j]); j++) { var element = component.element; var propertyName = component.propertyName; var style = this.theme_ && this.theme_.getComponentStyle(key); element.style[propertyName] = style || ''; } } Blockly.hideChaff(); }; /** * Subscribe a workspace to changes to the selected theme. If a new theme is * set, the workspace is called to refresh its blocks. * @param {!Blockly.Workspace} workspace The workspace to subscribe. * @package */ Blockly.ThemeManager.prototype.subscribeWorkspace = function(workspace) { this.subscribedWorkspaces_.push(workspace); }; /** * Unsubscribe a workspace to changes to the selected theme. * @param {!Blockly.Workspace} workspace The workspace to unsubscribe. * @package */ Blockly.ThemeManager.prototype.unsubscribeWorkspace = function(workspace) { var index = this.subscribedWorkspaces_.indexOf(workspace); if (index < 0) { throw Error('Cannot unsubscribe a workspace that hasn\'t been subscribed.'); } this.subscribedWorkspaces_.splice(index, 1); }; /** * Subscribe an element to changes to the selected theme. If a new theme is * selected, the element's style is refreshed with the new theme's style. * @param {!Element} element The element to subscribe. * @param {string} componentName The name used to identify the component. This * must be the same name used to configure the style in the Theme object. * @param {string} propertyName The inline style property name to update. * @package */ Blockly.ThemeManager.prototype.subscribe = function(element, componentName, propertyName) { if (!this.componentDB_[componentName]) { this.componentDB_[componentName] = []; } // Add the element to our component map. this.componentDB_[componentName].push({ element: element, propertyName: propertyName }); // Initialize the element with its corresponding theme style. var style = this.theme_ && this.theme_.getComponentStyle(componentName); element.style[propertyName] = style || ''; }; /** * Unsubscribe an element to changes to the selected theme. * @param {Element} element The element to unsubscribe. * @package */ Blockly.ThemeManager.prototype.unsubscribe = function(element) { if (!element) { return; } // Go through all component, and remove any references to this element. var componentNames = Object.keys(this.componentDB_); for (var c = 0, componentName; (componentName = componentNames[c]); c++) { var elements = this.componentDB_[componentName]; for (var i = elements.length - 1; i >= 0; i--) { if (elements[i].element === element) { elements.splice(i, 1); } } // Clean up the component map entry if the list is empty. if (!this.componentDB_[componentName].length) { delete this.componentDB_[componentName]; } } }; /** * Dispose of this theme manager. * @package * @suppress {checkTypes} */ Blockly.ThemeManager.prototype.dispose = function() { this.owner_ = null; this.theme_ = null; this.subscribedWorkspaces_ = null; this.componentDB_ = null; };