* @license
* Copyright 2019 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
* Object in charge of storing and updating a workspace theme
* and UI components.
*
* @class
*/
import type {Theme} from './theme.js';
import * as arrayUtils from './utils/array.js';
import * as dom from './utils/dom.js';
import type {Workspace} from './workspace.js';
import type {WorkspaceSvg} from './workspace_svg.js';
* Class for storing and updating a workspace's theme and UI components.
*/
export class ThemeManager {
private subscribedWorkspaces_: Workspace[] = [];
private componentDB = new Map<string, Component[]>();
* @param workspace The main workspace.
* @param theme The workspace theme.
* @internal
*/
constructor(
private readonly workspace: WorkspaceSvg,
private theme: Theme,
) {}
* Get the workspace theme.
*
* @returns The workspace theme.
* @internal
*/
getTheme(): Theme {
return this.theme;
}
* Set the workspace theme, and refresh the workspace and all components.
*
* @param theme The workspace theme.
* @internal
*/
setTheme(theme: Theme) {
const prevTheme = this.theme;
this.theme = theme;
const injectionDiv = this.workspace.getInjectionDiv();
if (injectionDiv) {
if (prevTheme) {
const oldClassName = prevTheme.getClassName();
if (oldClassName) {
dom.removeClass(injectionDiv, oldClassName);
}
}
const newClassName = this.theme.getClassName();
if (newClassName) {
dom.addClass(injectionDiv, newClassName);
}
}
for (
let i = 0, workspace;
(workspace = this.subscribedWorkspaces_[i]);
i++
) {
(workspace as WorkspaceSvg).refreshTheme();
}
for (const [key, componentList] of this.componentDB) {
for (const component of componentList) {
const element = component.element;
const propertyName = component.propertyName;
const style = this.theme && this.theme.getComponentStyle(key);
element.style.setProperty(propertyName, style || '');
}
}
for (const workspace of this.subscribedWorkspaces_) {
(workspace as WorkspaceSvg).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 The workspace to subscribe.
* @internal
*/
subscribeWorkspace(workspace: Workspace) {
this.subscribedWorkspaces_.push(workspace);
}
* Unsubscribe a workspace to changes to the selected theme.
*
* @param workspace The workspace to unsubscribe.
* @internal
*/
unsubscribeWorkspace(workspace: 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 The element to subscribe.
* @param componentName The name used to identify the component. This must be
* the same name used to configure the style in the Theme object.
* @param propertyName The inline style property name to update.
* @internal
*/
subscribe(
element: HTMLElement | SVGElement,
componentName: string,
propertyName: string,
) {
if (!this.componentDB.has(componentName)) {
this.componentDB.set(componentName, []);
}
this.componentDB.get(componentName)!.push({element, propertyName});
const style = this.theme && this.theme.getComponentStyle(componentName);
element.style.setProperty(propertyName, style || '');
}
* Unsubscribe an element to changes to the selected theme.
*
* @param element The element to unsubscribe.
* @internal
*/
unsubscribe(element: HTMLElement | SVGElement) {
if (!element) {
return;
}
for (const [componentName, elements] of this.componentDB) {
for (let i = elements.length - 1; i >= 0; i--) {
if (elements[i].element === element) {
elements.splice(i, 1);
}
}
if (!elements.length) {
this.componentDB.delete(componentName);
}
}
}
* Dispose of this theme manager.
*
* @internal
*/
dispose() {
this.subscribedWorkspaces_.length = 0;
this.componentDB.clear();
}
}
export namespace ThemeManager {
export interface Component {
element: HTMLElement | SVGElement;
propertyName: string;
}
}
export type Component = ThemeManager.Component;