/** * Settings Panel Module * Handles user settings UI and persistence */ const Settings = { // Current settings state current: {}, // DOM elements elements: {}, /** * Initialize the settings module */ init() { this.cacheElements(); this.bindEvents(); }, /** * Cache DOM element references */ cacheElements() { this.elements = { panel: document.getElementById('settings-panel'), overlay: document.getElementById('overlay'), btnSettings: document.getElementById('btn-settings'), btnClose: document.getElementById('btn-close-settings'), btnSave: document.getElementById('btn-save-settings'), btnReset: document.getElementById('btn-reset-settings'), // Mic text settings fontFamily: document.getElementById('font-family'), fontSize: document.getElementById('font-size'), fontSizeValue: document.getElementById('font-size-value'), fontWeight: document.getElementById('font-weight'), textColor: document.getElementById('text-color'), textAlign: document.getElementById('text-align'), // Mic background settings backgroundColor: document.getElementById('background-color'), backgroundOpacity: document.getElementById('background-opacity'), opacityValue: document.getElementById('opacity-value'), borderRadius: document.getElementById('border-radius'), radiusValue: document.getElementById('radius-value'), padding: document.getElementById('padding'), paddingValue: document.getElementById('padding-value'), // Mic behavior settings maxWords: document.getElementById('max-words'), maxWordsValue: document.getElementById('max-words-value'), // Desktop text settings desktopFontFamily: document.getElementById('desktop-font-family'), desktopFontSize: document.getElementById('desktop-font-size'), desktopFontSizeValue: document.getElementById('desktop-font-size-value'), desktopFontWeight: document.getElementById('desktop-font-weight'), desktopTextColor: document.getElementById('desktop-text-color'), desktopTextAlign: document.getElementById('desktop-text-align'), // Desktop background settings desktopBackgroundColor: document.getElementById('desktop-background-color'), desktopBackgroundOpacity: document.getElementById('desktop-background-opacity'), desktopOpacityValue: document.getElementById('desktop-opacity-value'), desktopBorderRadius: document.getElementById('desktop-border-radius'), desktopRadiusValue: document.getElementById('desktop-radius-value'), desktopPadding: document.getElementById('desktop-padding'), desktopPaddingValue: document.getElementById('desktop-padding-value'), // Desktop behavior settings desktopMaxWords: document.getElementById('desktop-max-words'), desktopMaxWordsValue: document.getElementById('desktop-max-words-value'), // Caption displays captionContainer: document.getElementById('caption-container'), desktopCaptionContainer: document.getElementById('desktop-caption-container'), // Section toggles sectionToggles: document.querySelectorAll('.section-toggle'), }; }, /** * Bind event listeners */ bindEvents() { // Panel open/close this.elements.btnSettings.addEventListener('click', () => this.openPanel()); this.elements.btnClose.addEventListener('click', () => this.closePanel()); this.elements.overlay.addEventListener('click', () => this.closePanel()); // Save/Reset this.elements.btnSave.addEventListener('click', () => this.saveSettings()); this.elements.btnReset.addEventListener('click', () => this.resetSettings()); // Section toggle collapse/expand this.elements.sectionToggles.forEach(toggle => { toggle.addEventListener('click', () => this.toggleSection(toggle)); }); // Mic live preview on input change const micInputs = [ 'fontFamily', 'fontSize', 'fontWeight', 'textColor', 'textAlign', 'backgroundColor', 'backgroundOpacity', 'borderRadius', 'padding', 'maxWords' ]; micInputs.forEach(name => { const element = this.elements[name]; if (element) { element.addEventListener('input', () => this.updatePreview()); } }); // Desktop live preview on input change const desktopInputs = [ 'desktopFontFamily', 'desktopFontSize', 'desktopFontWeight', 'desktopTextColor', 'desktopTextAlign', 'desktopBackgroundColor', 'desktopBackgroundOpacity', 'desktopBorderRadius', 'desktopPadding', 'desktopMaxWords' ]; desktopInputs.forEach(name => { const element = this.elements[name]; if (element) { element.addEventListener('input', () => this.updateDesktopPreview()); } }); // Update mic value displays for range inputs this.elements.fontSize.addEventListener('input', (e) => { this.elements.fontSizeValue.textContent = e.target.value; }); this.elements.backgroundOpacity.addEventListener('input', (e) => { this.elements.opacityValue.textContent = e.target.value; }); this.elements.borderRadius.addEventListener('input', (e) => { this.elements.radiusValue.textContent = e.target.value; }); this.elements.padding.addEventListener('input', (e) => { this.elements.paddingValue.textContent = e.target.value; }); this.elements.maxWords.addEventListener('input', (e) => { this.elements.maxWordsValue.textContent = e.target.value; }); // Update desktop value displays for range inputs this.elements.desktopFontSize.addEventListener('input', (e) => { this.elements.desktopFontSizeValue.textContent = e.target.value; }); this.elements.desktopBackgroundOpacity.addEventListener('input', (e) => { this.elements.desktopOpacityValue.textContent = e.target.value; }); this.elements.desktopBorderRadius.addEventListener('input', (e) => { this.elements.desktopRadiusValue.textContent = e.target.value; }); this.elements.desktopPadding.addEventListener('input', (e) => { this.elements.desktopPaddingValue.textContent = e.target.value; }); this.elements.desktopMaxWords.addEventListener('input', (e) => { this.elements.desktopMaxWordsValue.textContent = e.target.value; }); }, /** * Toggle settings section collapse/expand */ toggleSection(toggle) { const sectionId = toggle.dataset.section; const section = document.getElementById(sectionId); const icon = toggle.querySelector('.toggle-icon'); section.classList.toggle('hidden'); toggle.classList.toggle('active'); icon.textContent = section.classList.contains('hidden') ? '\u25B6' : '\u25BC'; }, /** * Open settings panel */ openPanel() { this.elements.panel.classList.remove('hidden'); this.elements.overlay.classList.remove('hidden'); }, /** * Close settings panel */ closePanel() { this.elements.panel.classList.add('hidden'); this.elements.overlay.classList.add('hidden'); }, /** * Apply settings to the UI */ applySettings(settings) { this.current = settings; // Update mic form values this.elements.fontFamily.value = settings.font_family; this.elements.fontSize.value = settings.font_size; this.elements.fontSizeValue.textContent = settings.font_size; this.elements.fontWeight.value = settings.font_weight; this.elements.textColor.value = settings.text_color; this.elements.textAlign.value = settings.text_align; this.elements.backgroundColor.value = settings.background_color; this.elements.backgroundOpacity.value = Math.round(settings.background_opacity * 100); this.elements.opacityValue.textContent = Math.round(settings.background_opacity * 100); this.elements.borderRadius.value = settings.border_radius; this.elements.radiusValue.textContent = settings.border_radius; this.elements.padding.value = settings.padding; this.elements.paddingValue.textContent = settings.padding; this.elements.maxWords.value = settings.max_words || 30; this.elements.maxWordsValue.textContent = settings.max_words || 30; // Apply to mic caption container this.updatePreview(); // Apply desktop settings if present if (settings.desktop_font_family !== undefined) { this.applyDesktopSettings(settings); } }, /** * Apply desktop settings to the UI */ applyDesktopSettings(settings) { // Update desktop form values this.elements.desktopFontFamily.value = settings.desktop_font_family; this.elements.desktopFontSize.value = settings.desktop_font_size; this.elements.desktopFontSizeValue.textContent = settings.desktop_font_size; this.elements.desktopFontWeight.value = settings.desktop_font_weight; this.elements.desktopTextColor.value = settings.desktop_text_color; this.elements.desktopTextAlign.value = settings.desktop_text_align; this.elements.desktopBackgroundColor.value = settings.desktop_background_color; this.elements.desktopBackgroundOpacity.value = Math.round(settings.desktop_background_opacity * 100); this.elements.desktopOpacityValue.textContent = Math.round(settings.desktop_background_opacity * 100); this.elements.desktopBorderRadius.value = settings.desktop_border_radius; this.elements.desktopRadiusValue.textContent = settings.desktop_border_radius; this.elements.desktopPadding.value = settings.desktop_padding; this.elements.desktopPaddingValue.textContent = settings.desktop_padding; this.elements.desktopMaxWords.value = settings.desktop_max_words || 30; this.elements.desktopMaxWordsValue.textContent = settings.desktop_max_words || 30; // Store desktop max words this.current.desktop_max_words = settings.desktop_max_words || 30; // Apply to desktop caption container this.updateDesktopPreview(); }, /** * Update live preview of mic caption styling */ updatePreview() { const container = this.elements.captionContainer; const opacity = this.elements.backgroundOpacity.value / 100; // Parse background color and apply opacity const bgColor = this.elements.backgroundColor.value; const r = parseInt(bgColor.slice(1, 3), 16); const g = parseInt(bgColor.slice(3, 5), 16); const b = parseInt(bgColor.slice(5, 7), 16); container.style.fontFamily = this.elements.fontFamily.value; container.style.fontSize = `${this.elements.fontSize.value}px`; container.style.fontWeight = this.elements.fontWeight.value; container.style.color = this.elements.textColor.value; container.style.textAlign = this.elements.textAlign.value; container.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${opacity})`; container.style.borderRadius = `${this.elements.borderRadius.value}px`; container.style.padding = `${this.elements.padding.value}px`; // Store max words for caption management this.current.max_words = parseInt(this.elements.maxWords.value); }, /** * Update live preview of desktop caption styling */ updateDesktopPreview() { const container = this.elements.desktopCaptionContainer; if (!container) return; const opacity = this.elements.desktopBackgroundOpacity.value / 100; // Parse background color and apply opacity const bgColor = this.elements.desktopBackgroundColor.value; const r = parseInt(bgColor.slice(1, 3), 16); const g = parseInt(bgColor.slice(3, 5), 16); const b = parseInt(bgColor.slice(5, 7), 16); container.style.fontFamily = this.elements.desktopFontFamily.value; container.style.fontSize = `${this.elements.desktopFontSize.value}px`; container.style.fontWeight = this.elements.desktopFontWeight.value; container.style.color = this.elements.desktopTextColor.value; container.style.textAlign = this.elements.desktopTextAlign.value; container.style.backgroundColor = `rgba(${r}, ${g}, ${b}, ${opacity})`; container.style.borderRadius = `${this.elements.desktopBorderRadius.value}px`; container.style.padding = `${this.elements.desktopPadding.value}px`; // Store desktop max words for caption management this.current.desktop_max_words = parseInt(this.elements.desktopMaxWords.value); }, /** * Get current form values as settings object */ getFormValues() { return { // Mic settings font_family: this.elements.fontFamily.value, font_size: parseInt(this.elements.fontSize.value), font_weight: this.elements.fontWeight.value, text_color: this.elements.textColor.value, text_align: this.elements.textAlign.value, background_color: this.elements.backgroundColor.value, background_opacity: this.elements.backgroundOpacity.value / 100, border_radius: parseInt(this.elements.borderRadius.value), padding: parseInt(this.elements.padding.value), max_words: parseInt(this.elements.maxWords.value), // Desktop settings desktop_font_family: this.elements.desktopFontFamily.value, desktop_font_size: parseInt(this.elements.desktopFontSize.value), desktop_font_weight: this.elements.desktopFontWeight.value, desktop_text_color: this.elements.desktopTextColor.value, desktop_text_align: this.elements.desktopTextAlign.value, desktop_background_color: this.elements.desktopBackgroundColor.value, desktop_background_opacity: this.elements.desktopBackgroundOpacity.value / 100, desktop_border_radius: parseInt(this.elements.desktopBorderRadius.value), desktop_padding: parseInt(this.elements.desktopPadding.value), desktop_max_words: parseInt(this.elements.desktopMaxWords.value), }; }, /** * Save settings to server */ async saveSettings() { const settings = this.getFormValues(); try { const response = await fetch('/api/settings', { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(settings), }); if (response.ok) { this.current = await response.json(); this.closePanel(); console.log('Settings saved'); } else { console.error('Failed to save settings'); } } catch (error) { console.error('Error saving settings:', error); } }, /** * Reset settings to defaults */ async resetSettings() { if (!confirm('Reset all settings to defaults?')) { return; } try { const response = await fetch('/api/settings/reset', { method: 'POST', }); if (response.ok) { const settings = await response.json(); this.applySettings(settings); console.log('Settings reset to defaults'); } else { console.error('Failed to reset settings'); } } catch (error) { console.error('Error resetting settings:', error); } }, /** * Fetch settings from server */ async fetchSettings() { try { const response = await fetch('/api/settings'); if (response.ok) { const settings = await response.json(); this.applySettings(settings); } } catch (error) { console.error('Error fetching settings:', error); } } };