Files
blockly/core/theme_manager.js
Rachel Fenichel 075385c87c chore: move remaining functions out of utils.js (#5714)
* chore: move arrayRemove to a new utils.array namespace

* chore: move getBlockTypeCounts out of utils.js

* chore: remove last functions from utils.js

* chore: reorder imports

* chore: add re-export for runAfterPageLoad
2021-11-15 18:12:45 -08:00

208 lines
5.8 KiB
JavaScript

/**
* @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<Workspace>}
* @private
*/
this.subscribedWorkspaces_ = [];
/**
* A map of subscribed UI components, keyed by component name.
* @type {!Object<string, !Array<!ThemeManager.Component>>}
* @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;