browser-captions/static/js/settings.js
bunker-admin c7becf330c Initial commit: Live Captions web application
Real-time speech-to-text using OpenAI Whisper (faster-whisper).
Features browser audio capture, WebSocket streaming, and customizable display settings.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 08:53:40 -07:00

260 lines
9.0 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'),
// 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'),
// 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'),
// Behavior settings
maxWords: document.getElementById('max-words'),
maxWordsValue: document.getElementById('max-words-value'),
// Caption display
captionContainer: document.getElementById('caption-container'),
};
},
/**
* 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());
// Live preview on input change
const inputs = [
'fontFamily', 'fontSize', 'fontWeight', 'textColor', 'textAlign',
'backgroundColor', 'backgroundOpacity', 'borderRadius', 'padding',
'maxWords'
];
inputs.forEach(name => {
const element = this.elements[name];
if (element) {
element.addEventListener('input', () => this.updatePreview());
}
});
// Update 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;
});
},
/**
* 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 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 caption container
this.updatePreview();
},
/**
* Update live preview of 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);
},
/**
* Get current form values as settings object
*/
getFormValues() {
return {
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),
};
},
/**
* 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);
}
}
};