Files
MoviePilot-Frontend/src/utils/themeManager.ts

213 lines
5.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 主题管理器 - 动态加载主题CSS
export interface ThemeConfig {
name: string
cssPath: string
isLoaded: boolean
}
class ThemeManager {
private themes: Map<string, ThemeConfig> = new Map()
private currentTheme: string = 'default'
private loadedLinks: Map<string, HTMLLinkElement> = new Map()
constructor() {
// 注册所有可用主题
this.registerTheme('default', '')
this.registerTheme('light', '')
this.registerTheme('dark', '')
this.registerTheme('purple', '')
this.registerTheme('auto', '')
// 只有透明主题有特定的CSS文件
this.registerTheme('transparent', './src/styles/themes/transparent.css')
}
/**
* 注册主题
*/
registerTheme(name: string, cssPath: string): void {
this.themes.set(name, {
name,
cssPath,
isLoaded: false,
})
}
/**
* 获取当前主题
*/
getCurrentTheme(): string {
return this.currentTheme
}
/**
* 设置主题
*/
async setTheme(themeName: string): Promise<void> {
if (!this.themes.has(themeName)) {
console.warn(`Theme "${themeName}" not found`)
return
}
const theme = this.themes.get(themeName)!
// 清理其他主题的CSS除了当前要设置的主题
this.unloadOtherThemes()
// 如果主题有CSS文件则加载CSS
if (theme.cssPath) {
try {
await this.loadThemeCSS(themeName, theme.cssPath)
} catch (error) {
console.error(`Failed to load CSS for theme "${themeName}":`, error)
// 即使CSS加载失败也继续应用主题使用默认样式
}
}
// 应用主题无论是否有CSS文件
this.applyTheme(themeName)
}
/**
* 加载主题CSS文件
*/
private async loadThemeCSS(themeName: string, cssPath: string): Promise<void> {
// 如果已经加载过,直接返回
if (this.loadedLinks.has(themeName)) {
return
}
try {
// 动态导入CSS模块
if (themeName === 'transparent') {
await import('@/styles/themes/transparent.scss')
this.themes.get(themeName)!.isLoaded = true
return
}
// 对于其他主题使用传统的link方式
const link = document.createElement('link')
link.rel = 'stylesheet'
link.type = 'text/css'
link.href = cssPath
link.id = `theme-${themeName}`
// 等待CSS加载完成
await new Promise<void>((resolve, reject) => {
link.onload = () => {
this.loadedLinks.set(themeName, link)
this.themes.get(themeName)!.isLoaded = true
resolve()
}
link.onerror = () => {
reject(new Error(`Failed to load theme CSS: ${cssPath}`))
}
})
// 添加到head
document.head.appendChild(link)
} catch (error) {
console.error(`Error loading theme "${themeName}":`, error)
throw error
}
}
/**
* 应用主题到DOM
*/
private applyTheme(themeName: string): void {
// 移除之前的主题属性
document.documentElement.removeAttribute('data-theme')
// 设置新主题除了default主题
if (themeName !== 'default') {
document.documentElement.setAttribute('data-theme', themeName)
}
this.currentTheme = themeName
// 触发主题变更事件
this.dispatchThemeChangeEvent(themeName)
}
/**
* 卸载主题CSS
*/
unloadTheme(themeName: string): void {
const theme = this.themes.get(themeName)
if (!theme) return
// 对于动态导入的CSS我们无法直接卸载但可以标记为未加载
if (themeName === 'transparent') {
theme.isLoaded = false
return
}
// 对于传统link方式加载的CSS
const link = this.loadedLinks.get(themeName)
if (link) {
link.remove()
this.loadedLinks.delete(themeName)
theme.isLoaded = false
}
}
/**
* 卸载所有主题CSS除了当前主题
*/
unloadOtherThemes(): void {
for (const [themeName] of this.themes) {
if (themeName !== this.currentTheme && this.themes.get(themeName)?.isLoaded) {
this.unloadTheme(themeName)
}
}
}
/**
* 获取已注册的主题列表
*/
getAvailableThemes(): string[] {
return Array.from(this.themes.keys())
}
/**
* 检查主题是否已加载
*/
isThemeLoaded(themeName: string): boolean {
return this.themes.get(themeName)?.isLoaded || false
}
/**
* 触发主题变更事件
*/
private dispatchThemeChangeEvent(themeName: string): void {
const event = new CustomEvent('themechange', {
detail: { theme: themeName },
})
document.dispatchEvent(event)
}
/**
* 监听主题变更事件
*/
onThemeChange(callback: (theme: string) => void): void {
document.addEventListener('themechange', (event: any) => {
callback(event.detail.theme)
})
}
/**
* 移除主题变更监听器
*/
offThemeChange(callback: (theme: string) => void): void {
document.removeEventListener('themechange', (event: any) => {
callback(event.detail.theme)
})
}
}
// 创建单例实例
export const themeManager = new ThemeManager()
// 导出类型
export type { ThemeManager }