ribbit/src/ts/theme-manager.ts

116 lines
3.4 KiB
TypeScript
Raw Normal View History

/*
* theme-manager.ts manages theme registration and activation for a Ribbit instance.
*/
import type { RibbitTheme } from './types';
import { HopDown } from './hopdown';
export class ThemeManager {
private registered: Map<string, RibbitTheme>;
private disabled: Set<string>;
private active: RibbitTheme;
private themeLink: HTMLLinkElement | null;
private themesPath: string;
private onSwitch: (theme: RibbitTheme, previous: RibbitTheme) => void;
constructor(initial: RibbitTheme, themesPath: string, onSwitch: (theme: RibbitTheme, previous: RibbitTheme) => void) {
this.registered = new Map();
this.disabled = new Set();
this.themeLink = null;
this.themesPath = themesPath;
this.onSwitch = onSwitch;
this.active = initial;
this.add(initial);
}
/**
* Register a theme. Themes must be added before they can be enabled.
*/
add(theme: RibbitTheme): void {
this.registered.set(theme.name, theme);
}
/**
* Unregister a theme by name. Cannot remove the active theme.
*/
remove(name: string): void {
if (this.active.name === name) {
throw new Error(`Cannot remove the active theme "${name}".`);
}
this.registered.delete(name);
}
/**
* Return the names of all registered and enabled themes.
*/
list(): string[] {
return Array.from(this.registered.keys()).filter(name => !this.disabled.has(name));
}
/**
* Get a registered theme by name, or undefined if not found.
*/
get(name: string): RibbitTheme | undefined {
return this.registered.get(name);
}
/**
* Return the currently active theme.
*/
current(): RibbitTheme {
return this.active;
}
/**
* Switch to a registered theme by name. The theme must be
* registered and enabled. Loads the theme's CSS and notifies
* the editor to rebuild its converter.
*/
set(name: string): void {
const theme = this.registered.get(name);
if (!theme) {
throw new Error(`Theme "${name}" is not registered. Call add() first.`);
}
if (this.disabled.has(name)) {
throw new Error(`Theme "${name}" is disabled. Call enable() first.`);
}
const previous = this.active;
this.active = theme;
this.loadCSS(name);
this.onSwitch(theme, previous);
}
/**
* Mark a theme as available for selection via set().
* Themes are enabled by default when added.
*/
enable(name: string): void {
if (!this.registered.has(name)) {
throw new Error(`Theme "${name}" is not registered. Call add() first.`);
}
this.disabled.delete(name);
}
/**
* Mark a theme as unavailable for selection via set().
* Does not affect the current theme if it is already active.
*/
disable(name: string): void {
if (!this.registered.has(name)) {
throw new Error(`Theme "${name}" is not registered.`);
}
this.disabled.add(name);
}
private loadCSS(name: string): void {
if (this.themeLink) {
this.themeLink.remove();
}
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `${this.themesPath}/${name}/theme.css`;
document.head.appendChild(link);
this.themeLink = link;
}
}