409 lines
16 KiB
JavaScript

/**
* 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);
}
}
};