diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.html b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.html index ba38a310..ed6e5044 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.html +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/panel/settings-panel/settings-panel.component.html @@ -82,6 +82,15 @@ +
+ + + routine +
{{ 'PANEL.SETTINGS.THEME' | translate }}
+ +
+
+
+ + + + + @for (theme of predefinedThemes; track theme) { + + } + +
+ `, + styleUrl: './theme-selector.component.scss' +}) +export class ThemeSelectorComponent { + protected predefinedThemes: OpenViduThemeMode[] = Object.values(OpenViduThemeMode); + constructor(private themeService: OpenViduThemeService) {} + + get currentTheme() { + return this.themeService.getCurrentTheme(); + } + + setTheme(theme: OpenViduThemeMode) { + this.themeService.setTheme(theme); + } +} diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.scss b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.scss index 1827fe57..6c423858 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.scss +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/components/settings/video-devices/video-devices.component.scss @@ -1,4 +1,4 @@ -@use '../device-selector-shared' as shared; +@use '../selector-shared' as shared; :host { display: flex; diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json index 47b83028..985a2364 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/cn.json @@ -106,7 +106,8 @@ "CAPTIONS": "字幕", "DISABLED_AUDIO": "没有音频设备", "DISABLED_VIDEO": "没有视频设备", - "CAPTIONS_LANG_TEXT": "选择房间参与者将使用的语言。字幕将以该语言显示。" + "CAPTIONS_LANG_TEXT": "选择房间参与者将使用的语言。字幕将以该语言显示。", + "THEME": "主题" }, "BACKGROUND": { "TITLE": "背景效果", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json index 5ad0e75a..3d5ff1e7 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/de.json @@ -106,7 +106,8 @@ "CAPTIONS": "Untertitel", "DISABLED_AUDIO": "Audio deaktiviert", "DISABLED_VIDEO": "Video deaktiviert", - "CAPTIONS_LANG_TEXT": "Wählen Sie die Sprache, die die Teilnehmer der Raum verwenden. Die Untertitel werden in dieser Sprache angezeigt." + "CAPTIONS_LANG_TEXT": "Wählen Sie die Sprache, die die Teilnehmer der Raum verwenden. Die Untertitel werden in dieser Sprache angezeigt.", + "THEME": "Thema" }, "BACKGROUND": { "TITLE": "Hintergrund-Effekte", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json index 2bd1f0b0..76ed85af 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/en.json @@ -106,7 +106,8 @@ "CAPTIONS": "Captions", "DISABLED_AUDIO": "Audio disabled", "DISABLED_VIDEO": "Video disabled", - "CAPTIONS_LANG_TEXT": "Select the language that the participants of the room will use. The captions will appear in that language." + "CAPTIONS_LANG_TEXT": "Select the language that the participants of the room will use. The captions will appear in that language.", + "THEME": "Theme" }, "BACKGROUND": { "TITLE": "Background effects", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json index da4189aa..f15c7d53 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/es.json @@ -106,7 +106,8 @@ "CAPTIONS": "Subtítulos", "DISABLED_AUDIO": "Audio desactivado", "DISABLED_VIDEO": "Video desactivado", - "CAPTIONS_LANG_TEXT": "Selecciona el idioma que usarán los participantes de la sala. Los subtítulos aparecerán en ese idioma." + "CAPTIONS_LANG_TEXT": "Selecciona el idioma que usarán los participantes de la sala. Los subtítulos aparecerán en ese idioma.", + "THEME": "Tema" }, "BACKGROUND": { "TITLE": "Efectos de fondo", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json index d5b6e0f5..d9ef66aa 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/fr.json @@ -106,7 +106,8 @@ "CAPTIONS": "Les sous-titres", "DISABLED_AUDIO": "Désactiver l'audio", "DISABLED_VIDEO": "Désactiver la vidéo", - "CAPTIONS_LANG_TEXT": "Sélectionnez la langue que les participants de la salle utiliseront. Les sous-titres apparaîtront dans cette langue." + "CAPTIONS_LANG_TEXT": "Sélectionnez la langue que les participants de la salle utiliseront. Les sous-titres apparaîtront dans cette langue.", + "THEME": "Thème" }, "BACKGROUND": { "TITLE": "Effets de fond", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json index 6498ec0e..4aa2ab1c 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/hi.json @@ -106,7 +106,8 @@ "CAPTIONS": "उपशीर्षक", "DISABLED_AUDIO": "ऑडियो अक्षम", "DISABLED_VIDEO": "वीडियो अक्षम", - "CAPTIONS_LANG_TEXT": "उस भाषा का चयन करें जिसका उपयोग कमरा के प्रतिभागी करेंगे। उपशीर्षक उस भाषा में दिखाई देंगे।" + "CAPTIONS_LANG_TEXT": "उस भाषा का चयन करें जिसका उपयोग कमरा के प्रतिभागी करेंगे। उपशीर्षक उस भाषा में दिखाई देंगे।", + "THEME": "थीम" }, "BACKGROUND": { "TITLE": "पृष्ठभूमि प्रभाव", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json index 258743ff..ce162e07 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/it.json @@ -106,7 +106,8 @@ "CAPTIONS": "Sottotitoli", "DISABLED_AUDIO": "Disattiva l'audio", "DISABLED_VIDEO": "Disattiva il video", - "CAPTIONS_LANG_TEXT": "Seleziona la lingua che i partecipanti della stanza useranno. I sottotitoli appariranno in quella lingua." + "CAPTIONS_LANG_TEXT": "Seleziona la lingua che i partecipanti della stanza useranno. I sottotitoli appariranno in quella lingua.", + "THEME": "Tema" }, "BACKGROUND": { "TITLE": "Effetti di sfondo", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json index 06a38bf7..adea633f 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/ja.json @@ -106,7 +106,8 @@ "CAPTIONS": "字幕", "DISABLED_AUDIO": "オーディオを無効にする", "DISABLED_VIDEO": "ビデオを無効にする", - "CAPTIONS_LANG_TEXT": "ルームの参加者が使用する言語を選択します。キャプションはその言語で表示されます。" + "CAPTIONS_LANG_TEXT": "ルームの参加者が使用する言語を選択します。キャプションはその言語で表示されます。", + "THEME": "テーマ" }, "BACKGROUND": { "TITLE": "背景効果", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json index 2ff2676b..68245e92 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/nl.json @@ -106,7 +106,8 @@ "CAPTIONS": "Ondertitels", "DISABLED_AUDIO": "Geen audio", "DISABLED_VIDEO": "Geen video", - "CAPTIONS_LANG_TEXT": "Selecteer de taal die de deelnemers van de kamer zullen gebruiken. De ondertiteling zal in die taal verschijnen." + "CAPTIONS_LANG_TEXT": "Selecteer de taal die de deelnemers van de kamer zullen gebruiken. De ondertiteling zal in die taal verschijnen.", + "THEME": "Thema" }, "BACKGROUND": { "TITLE": "Achtergrondeffecten", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json index dfffae04..d9eaf395 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/lang/pt.json @@ -106,7 +106,8 @@ "CAPTIONS": "Legendas", "DISABLED_AUDIO": "Áudio desativado", "DISABLED_VIDEO": "Vídeo desativado", - "CAPTIONS_LANG_TEXT": "Selecione o idioma que os participantes da sala utilizarão. Os legendas aparecerão nesse idioma." + "CAPTIONS_LANG_TEXT": "Selecione o idioma que os participantes da sala utilizarão. Os legendas aparecerão nesse idioma.", + "THEME": "Tema" }, "BACKGROUND": { "TITLE": "Efeitos de fundo", diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/storage.model.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/storage.model.ts index 677b650d..d630b0bd 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/storage.model.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/storage.model.ts @@ -10,6 +10,7 @@ export enum StorageKeys { LANG = 'lang', CAPTION_LANG = 'captionLang', BACKGROUND = 'virtualBg', + THEME = 'theme', TAB_ID = 'tabId', ACTIVE_TABS = 'activeTabs' } diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts index 8d056f4a..18d80282 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/models/theme.model.ts @@ -5,8 +5,7 @@ export enum OpenViduThemeMode { Light = 'light', Dark = 'dark', - None = 'none', - Auto = 'auto' + CLASSIC = 'classic' } /** diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/openvidu-components-angular-ui.module.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/openvidu-components-angular-ui.module.ts index d62aafed..eaf85a66 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/openvidu-components-angular-ui.module.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/openvidu-components-angular-ui.module.ts @@ -46,6 +46,7 @@ import { VideoDevicesComponent } from './components/settings/video-devices/video import { ApiDirectiveModule } from './directives/api/api.directive.module'; import { OpenViduComponentsDirectiveModule } from './directives/template/openvidu-components-angular.directive.module'; import { AppMaterialModule } from './openvidu-components-angular.material.module'; +import { ThemeSelectorComponent } from './components/settings/theme-selector/theme-selector.component'; const publicComponents = [ AdminDashboardComponent, @@ -79,7 +80,8 @@ const privateComponents = [ ParticipantNameInputComponent, LangSelectorComponent, ToolbarMediaButtonsComponent, - ToolbarPanelButtonsComponent + ToolbarPanelButtonsComponent, + ThemeSelectorComponent ]; @NgModule({ diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts index cd974429..c3f891fb 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/storage/storage.service.ts @@ -10,6 +10,7 @@ import { } from '../../models/storage.model'; import { LoggerService } from '../logger/logger.service'; import { CustomDevice } from '../../models/device.model'; +import { OpenViduThemeMode } from '../../models/theme.model'; /** * @internal @@ -230,7 +231,7 @@ export class StorageService implements OnDestroy { if (!this.isStorageAvailable) return; // Use batch removal for better performance - const keysToRemove = TAB_SPECIFIC_KEYS.map(key => `${this.PREFIX_KEY}${tabId}_${key}`); + const keysToRemove = TAB_SPECIFIC_KEYS.map((key) => `${this.PREFIX_KEY}${tabId}_${key}`); for (const storageKey of keysToRemove) { try { @@ -339,6 +340,18 @@ export class StorageService implements OnDestroy { this.remove(StorageKeys.BACKGROUND); } + setTheme(theme: OpenViduThemeMode): void { + this.set(StorageKeys.THEME, theme); + } + + getTheme(): OpenViduThemeMode | null { + return this.get(StorageKeys.THEME); + } + + removeTheme(): void { + this.remove(StorageKeys.THEME); + } + // Core storage methods with improved error handling and caching protected set(key: string, item: any): void { if (!this.isStorageAvailable) { @@ -403,9 +416,7 @@ export class StorageService implements OnDestroy { private setLocalValue(key: string, item: any, useCache: boolean = true): void { if (!this.isStorageAvailable) return; - const storageKey = this.shouldUseTabSpecificKey(key) - ? `${this.PREFIX_KEY}${this.tabId}_${key}` - : `${this.PREFIX_KEY}${key}`; + const storageKey = this.shouldUseTabSpecificKey(key) ? `${this.PREFIX_KEY}${this.tabId}_${key}` : `${this.PREFIX_KEY}${key}`; try { // Optimize serialization for primitive types @@ -436,9 +447,7 @@ export class StorageService implements OnDestroy { private getLocalValue(key: string, useCache: boolean = true): any { if (!this.isStorageAvailable) return null; - const storageKey = this.shouldUseTabSpecificKey(key) - ? `${this.PREFIX_KEY}${this.tabId}_${key}` - : `${this.PREFIX_KEY}${key}`; + const storageKey = this.shouldUseTabSpecificKey(key) ? `${this.PREFIX_KEY}${this.tabId}_${key}` : `${this.PREFIX_KEY}${key}`; // Check cache first if (useCache && this.cache.has(storageKey)) { @@ -484,9 +493,7 @@ export class StorageService implements OnDestroy { private removeLocalValue(key: string): void { if (!this.isStorageAvailable) return; - const storageKey = this.shouldUseTabSpecificKey(key) - ? `${this.PREFIX_KEY}${this.tabId}_${key}` - : `${this.PREFIX_KEY}${key}`; + const storageKey = this.shouldUseTabSpecificKey(key) ? `${this.PREFIX_KEY}${this.tabId}_${key}` : `${this.PREFIX_KEY}${key}`; try { this.localStorage.removeItem(storageKey); diff --git a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts index 898821ee..9e21a024 100644 --- a/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts +++ b/openvidu-components-angular/projects/openvidu-components-angular/src/lib/services/theme/theme.service.ts @@ -2,46 +2,25 @@ import { Injectable, Inject } from '@angular/core'; import { DOCUMENT } from '@angular/common'; import { BehaviorSubject, Observable } from 'rxjs'; import { OPENVIDU_DARK_THEME, OPENVIDU_LIGHT_THEME, OpenViduThemeMode, OpenViduThemeVariables } from '../../models/theme.model'; +import { StorageService } from '../storage/storage.service'; /** * Service for managing OpenVidu component themes dynamically * * This service allows you to: - * - Switch between light, dark, and auto themes + * - Switch between light, dark and classic themes * - Apply custom theme variables * - Listen to theme changes * - Integrate with Angular Material themes * - * @example - * ```typescript - * // Inject the service - * constructor(private themeService: OpenViduThemeService) {} - * - * // Switch to dark theme - * this.themeService.setTheme('dark'); - * - * // Apply custom variables - * this.themeService.updateThemeVariables({ - * '--ov-primary-action-color': '#ff5722', - * '--ov-accent-action-color': '#4caf50' - * }); - * - * // Listen to theme changes - * this.themeService.currentTheme$.subscribe(theme => { - * console.log('Current theme:', theme); - * }); - * ``` - * * @internal */ @Injectable({ providedIn: 'root' }) export class OpenViduThemeService { - private readonly THEME_STORAGE_KEY = 'openvidu-theme'; private readonly THEME_ATTRIBUTE = 'data-ov-theme'; - - private currentThemeSubject = new BehaviorSubject(OpenViduThemeMode.None); + private currentThemeSubject = new BehaviorSubject(OpenViduThemeMode.CLASSIC); private currentVariablesSubject = new BehaviorSubject({}); /** @@ -54,9 +33,11 @@ export class OpenViduThemeService { */ public readonly currentVariables$: Observable = this.currentVariablesSubject.asObservable(); - constructor(@Inject(DOCUMENT) private document: Document) { + constructor( + @Inject(DOCUMENT) private document: Document, + protected storageService: StorageService + ) { this.initializeTheme(); - this.setupSystemThemeListener(); } /** @@ -78,9 +59,9 @@ export class OpenViduThemeService { * @param theme The theme mode to apply */ setTheme(theme: OpenViduThemeMode): void { - this.currentThemeSubject.next(theme); this.applyTheme(theme); - this.saveThemeToStorage(theme); + this.currentThemeSubject.next(theme); + this.storageService.setTheme(theme); } /** @@ -152,17 +133,19 @@ export class OpenViduThemeService { } private initializeTheme(): void { - const savedTheme = this.getThemeFromStorage(); - const initialTheme = savedTheme || OpenViduThemeMode.None; + const savedTheme = this.storageService.getTheme(); + const initialTheme = savedTheme || OpenViduThemeMode.CLASSIC; this.applyTheme(initialTheme); this.currentThemeSubject.next(initialTheme); } private applyTheme(theme: OpenViduThemeMode): void { const documentElement = this.document.documentElement; - - if (theme === OpenViduThemeMode.Auto || theme === OpenViduThemeMode.None) { + const currentTheme = this.getCurrentTheme(); + if (theme === OpenViduThemeMode.CLASSIC) { documentElement.removeAttribute(this.THEME_ATTRIBUTE); + const currentVariables = this.getDefaultVariablesForTheme(currentTheme); + this.removeCSSVariables(currentVariables); } else { documentElement.setAttribute(this.THEME_ATTRIBUTE, theme); } @@ -182,57 +165,22 @@ export class OpenViduThemeService { }); } + private removeCSSVariables(variables: OpenViduThemeVariables): void { + const documentElement = this.document.documentElement; + + Object.keys(variables).forEach((property) => { + documentElement.style.removeProperty(property); + }); + } + private getDefaultVariablesForTheme(theme: OpenViduThemeMode): OpenViduThemeVariables { switch (theme) { case OpenViduThemeMode.Light: return OPENVIDU_LIGHT_THEME; case OpenViduThemeMode.Dark: return OPENVIDU_DARK_THEME; - case OpenViduThemeMode.None: - return {}; - case OpenViduThemeMode.Auto: - // Auto theme - use system preference - return this.prefersDarkMode() ? OPENVIDU_DARK_THEME : OPENVIDU_LIGHT_THEME; default: return {}; } } - - private setupSystemThemeListener(): void { - if (window.matchMedia) { - const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); - - const handleSystemThemeChange = (event: MediaQueryListEvent) => { - if (this.getCurrentTheme() === OpenViduThemeMode.Auto) { - const defaultVariables = this.getDefaultVariablesForTheme(OpenViduThemeMode.Auto); - this.applyCSSVariables(defaultVariables); - } - }; - - // Use the newer addEventListener if available, otherwise use the deprecated addListener - if (mediaQuery.addEventListener) { - mediaQuery.addEventListener('change', handleSystemThemeChange); - } - } - } - - private saveThemeToStorage(theme: OpenViduThemeMode): void { - try { - localStorage.setItem(this.THEME_STORAGE_KEY, theme); - } catch (error) { - console.warn('Failed to save theme to localStorage:', error); - } - } - - private getThemeFromStorage(): OpenViduThemeMode | null { - try { - const saved = localStorage.getItem(this.THEME_STORAGE_KEY) as OpenViduThemeMode; - if (saved && [OpenViduThemeMode.Light, OpenViduThemeMode.Dark, OpenViduThemeMode.Auto].includes(saved)) { - return saved; - } - } catch (error) { - console.warn('Failed to read theme from localStorage:', error); - } - return null; - } }