/** * @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. */ 'use strict'; /** * Object in charge of storing and updating a workspace theme * and UI components. * @class */ goog.module('Blockly.ThemeManager'); const arrayUtils = goog.require('Blockly.utils.array'); const dom = goog.require('Blockly.utils.dom'); /* eslint-disable-next-line no-unused-vars */ const {Theme} = goog.requireType('Blockly.Theme'); /* eslint-disable-next-line no-unused-vars */ const {WorkspaceSvg} = goog.requireType('Blockly.WorkspaceSvg'); /* eslint-disable-next-line no-unused-vars */ const {Workspace} = goog.requireType('Blockly.Workspace'); /** * Class for storing and updating a workspace's theme and UI components. * @param {!WorkspaceSvg} workspace The main workspace. * @param {!Theme} theme The workspace theme. * @constructor * @package * @alias Blockly.ThemeManager */ const ThemeManager = function(workspace, theme) { /** * The main workspace. * @type {!WorkspaceSvg} * @private */ this.workspace_ = workspace; /** * The Blockly theme to use. * @type {!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 * }} */ ThemeManager.Component; /** * Get the workspace theme. * @return {!Theme} The workspace theme. * @package */ ThemeManager.prototype.getTheme = function() { return this.theme_; }; /** * Set the workspace theme, and refresh the workspace and all components. * @param {!Theme} theme The workspace theme. * @package */ ThemeManager.prototype.setTheme = function(theme) { const prevTheme = this.theme_; this.theme_ = theme; // Set the theme name onto the injection div. const injectionDiv = this.workspace_.getInjectionDiv(); if (injectionDiv) { if (prevTheme) { dom.removeClass(injectionDiv, prevTheme.getClassName()); } dom.addClass(injectionDiv, this.theme_.getClassName()); } // Refresh all subscribed workspaces. for (let i = 0, workspace; (workspace = this.subscribedWorkspaces_[i]); i++) { workspace.refreshTheme(); } // Refresh all registered Blockly UI components. for (let i = 0, keys = Object.keys(this.componentDB_), key; (key = keys[i]); i++) { for (let j = 0, component; (component = this.componentDB_[key][j]); j++) { const element = component.element; const propertyName = component.propertyName; const style = this.theme_ && this.theme_.getComponentStyle(key); element.style[propertyName] = style || ''; } } for (const workspace of this.subscribedWorkspaces_) { workspace.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 {!Workspace} workspace The workspace to subscribe. * @package */ ThemeManager.prototype.subscribeWorkspace = function(workspace) { this.subscribedWorkspaces_.push(workspace); }; /** * Unsubscribe a workspace to changes to the selected theme. * @param {!Workspace} workspace The workspace to unsubscribe. * @package */ ThemeManager.prototype.unsubscribeWorkspace = function(workspace) { if (!arrayUtils.removeElem(this.subscribedWorkspaces_, workspace)) { throw Error('Cannot unsubscribe a workspace that hasn\'t been subscribed.'); } }; /** * 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 */ 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. const 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 */ ThemeManager.prototype.unsubscribe = function(element) { if (!element) { return; } // Go through all component, and remove any references to this element. const componentNames = Object.keys(this.componentDB_); for (let c = 0, componentName; (componentName = componentNames[c]); c++) { const elements = this.componentDB_[componentName]; for (let 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} */ ThemeManager.prototype.dispose = function() { this.owner_ = null; this.theme_ = null; this.subscribedWorkspaces_ = null; this.componentDB_ = null; }; exports.ThemeManager = ThemeManager;