Files
ai.interactive.fiction/public/js/options-ui.js
T
2025-04-05 11:29:30 +00:00

956 lines
36 KiB
JavaScript

/**
* Options UI Module
* Provides the options UI for the game
*/
import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
class OptionsUIModule extends BaseModule {
/**
* Create a new options UI module
*/
constructor() {
super('options-ui', 'Options UI');
// Modal element
this.modal = null;
// UI elements
this.elements = null;
// Settings that require reload
this.reloadRequired = false;
// Bind methods
this.bindMethods([
'show',
'hide',
'createModal',
'populateTtsSystems',
'populateVoices',
'populateLanguages',
'loadPreferences',
'applySettings',
'handleTtsSystemChanged',
'showReloadNotice',
'toggle',
'setupEventListeners',
'saveCurrentSettings'
]);
}
/**
* Initialize the options UI
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize() {
try {
console.log('Initializing Options UI Module');
// Set up dependencies
this.dependencies = [
'persistence-manager',
'localization',
'tts-factory',
'audio-manager'
];
// Create the options modal
this.createModal();
// Set up event listeners
this.setupEventListeners();
// Add event listener for showing options UI
document.addEventListener('ui:showOptions', () => this.show());
// Add event listener for toggling options UI
document.addEventListener('ui:options:toggle', () => this.toggle());
// Wait for dependencies and populate UI with delay to ensure TTS handlers are registered
this.waitForDependencies().then(() => {
console.log('Options UI: Dependencies loaded, initializing UI with delay');
// Add a delay to ensure all TTS handlers are registered and initialized
setTimeout(() => {
// Populate TTS systems
this.populateTtsSystems();
// Populate languages
this.populateLanguages();
// Load current preferences
this.loadPreferences();
// Apply settings
this.applySettings();
console.log('Options UI: Initialization complete');
}, 1000); // 1 second delay
});
// Register for TTS events to update voices when they change
document.addEventListener('tts:voices:updated', () => {
console.log('Options UI: Received tts:voices:updated event, updating voice dropdown');
this.populateVoices();
});
// Set up key bindings
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape' && this.modal.style.display === 'flex') {
this.saveCurrentSettings();
this.hide();
}
});
return true;
} catch (error) {
console.error("Options UI: Error initializing", error);
return false;
}
}
/**
* Wait for dependencies to be available
* @returns {Promise} - Resolves when dependencies are available
*/
waitForDependencies() {
return new Promise((resolve) => {
const checkDependencies = () => {
const persistenceManager = this.getModule('persistence-manager');
const localization = this.getModule('localization');
const ttsFactory = this.getModule('tts-factory');
const audioManager = this.getModule('audio-manager');
if (persistenceManager && localization && ttsFactory && audioManager) {
this.persistenceManager = persistenceManager;
this.localization = localization;
this.ttsFactory = ttsFactory;
this.audioManager = audioManager;
resolve();
} else {
setTimeout(checkDependencies, 100);
}
};
checkDependencies();
});
}
/**
* Create the options modal
*/
createModal() {
if (this.modal) return;
// Create modal container
this.modal = document.createElement('div');
this.modal.id = 'options-modal';
this.modal.className = 'options-modal';
this.modal.style.display = 'none';
// Create modal content
const content = document.createElement('div');
content.className = 'options-content';
// Create header
const header = document.createElement('div');
header.className = 'options-header';
const title = document.createElement('h2');
title.textContent = 'Options';
header.appendChild(title);
const closeButton = document.createElement('button');
closeButton.className = 'options-close';
closeButton.innerHTML = '&times;';
closeButton.addEventListener('click', () => {
// Save all current settings when closing
this.saveCurrentSettings();
this.hide();
});
header.appendChild(closeButton);
content.appendChild(header);
// Create settings container
const settings = document.createElement('div');
settings.className = 'options-settings';
// TTS Settings
const ttsSection = document.createElement('div');
ttsSection.className = 'options-section';
const ttsTitle = document.createElement('h3');
ttsTitle.textContent = 'Text-to-Speech';
ttsSection.appendChild(ttsTitle);
// TTS Toggle
const ttsSpeechToggleContainer = document.createElement('div');
ttsSpeechToggleContainer.className = 'options-row';
const ttsSpeechToggleLabel = document.createElement('label');
ttsSpeechToggleLabel.textContent = 'Enable Speech:';
ttsSpeechToggleContainer.appendChild(ttsSpeechToggleLabel);
const ttsSpeechToggle = document.createElement('input');
ttsSpeechToggle.type = 'checkbox';
ttsSpeechToggle.id = 'tts-speech-toggle';
ttsSpeechToggle.addEventListener('change', (e) => {
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
const enabled = e.target.checked;
persistenceManager.updatePreference('tts', 'enabled', enabled);
// Dispatch event for TTS state change
document.dispatchEvent(new CustomEvent('tts:stateChange', {
detail: { enabled: enabled }
}));
}
});
ttsSpeechToggleContainer.appendChild(ttsSpeechToggle);
ttsSection.appendChild(ttsSpeechToggleContainer);
// TTS System
const ttsSystemContainer = document.createElement('div');
ttsSystemContainer.className = 'options-row';
const ttsSystemLabel = document.createElement('label');
ttsSystemLabel.textContent = 'TTS System:';
ttsSystemContainer.appendChild(ttsSystemLabel);
const ttsSystem = document.createElement('select');
ttsSystem.id = 'tts-system';
ttsSystem.addEventListener('change', (e) => {
const persistenceManager = this.getModule('persistence-manager');
const ttsFactory = this.getModule('tts-factory');
if (persistenceManager && ttsFactory) {
const provider = e.target.value;
persistenceManager.updatePreference('tts', 'provider', provider);
ttsFactory.setActiveHandler(provider);
// Update TTS enabled state based on provider
const enabled = provider !== 'none';
persistenceManager.updatePreference('tts', 'enabled', enabled);
// Dispatch event for TTS state change
document.dispatchEvent(new CustomEvent('tts:stateChange', {
detail: { enabled: enabled }
}));
this.populateVoices();
}
});
ttsSystemContainer.appendChild(ttsSystem);
ttsSection.appendChild(ttsSystemContainer);
// TTS Voice
const ttsVoiceContainer = document.createElement('div');
ttsVoiceContainer.className = 'options-row';
const ttsVoiceLabel = document.createElement('label');
ttsVoiceLabel.textContent = 'Voice:';
ttsVoiceContainer.appendChild(ttsVoiceLabel);
const ttsVoice = document.createElement('select');
ttsVoice.id = 'tts-voice';
ttsVoice.addEventListener('change', (e) => {
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
persistenceManager.updatePreference('tts', 'voice', e.target.value);
}
});
ttsVoiceContainer.appendChild(ttsVoice);
ttsSection.appendChild(ttsVoiceContainer);
// Speed controls
const speedContainer = document.createElement('div');
speedContainer.className = 'options-row';
const speedLabel = document.createElement('label');
speedLabel.textContent = 'Speed:';
speedContainer.appendChild(speedLabel);
const speedSlider = document.createElement('input');
speedSlider.type = 'range';
speedSlider.min = '0';
speedSlider.max = '100';
speedSlider.value = '50'; // Default to 0.5 speed (50 out of 100)
speedSlider.id = 'speech-rate';
speedSlider.addEventListener('input', (e) => {
const persistenceManager = this.getModule('persistence-manager');
const ttsFactory = this.getModule('tts-factory');
if (persistenceManager && ttsFactory) {
// Convert to normalized speed (0-1 range)
const speed = parseInt(e.target.value) / 100;
// Update persistence manager
persistenceManager.updatePreference('tts', 'speed', speed);
// Configure the TTS factory
ttsFactory.configure({ speed: speed });
// Broadcast the speed change event for other components
document.dispatchEvent(new CustomEvent('tts:speed:change', {
detail: { speed: speed }
}));
}
});
speedContainer.appendChild(speedSlider);
ttsSection.appendChild(speedContainer);
// Language
const languageContainer = document.createElement('div');
languageContainer.className = 'options-row';
const languageLabel = document.createElement('label');
languageLabel.textContent = 'Language:';
languageContainer.appendChild(languageLabel);
const language = document.createElement('select');
language.id = 'language';
language.addEventListener('change', (e) => {
const persistenceManager = this.getModule('persistence-manager');
const localization = this.getModule('localization');
if (persistenceManager && localization) {
persistenceManager.updatePreference('app', 'locale', e.target.value);
persistenceManager.updatePreference('tts', 'language', e.target.value);
localization.setLocale(e.target.value);
this.showReloadNotice();
}
});
languageContainer.appendChild(language);
ttsSection.appendChild(languageContainer);
// Text Speed
const textSpeedContainer = document.createElement('div');
textSpeedContainer.className = 'options-row';
const textSpeedLabel = document.createElement('label');
textSpeedLabel.textContent = 'Text Speed:';
textSpeedContainer.appendChild(textSpeedLabel);
const textSpeed = document.createElement('input');
textSpeed.type = 'range';
textSpeed.min = '0';
textSpeed.max = '100';
textSpeed.value = '50';
textSpeed.id = 'text-speed';
textSpeed.addEventListener('input', (e) => {
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
persistenceManager.updatePreference('animation', 'speed', parseInt(e.target.value));
}
});
textSpeedContainer.appendChild(textSpeed);
ttsSection.appendChild(textSpeedContainer);
settings.appendChild(ttsSection);
// Audio Settings
const audioSection = document.createElement('div');
audioSection.className = 'options-section';
const audioTitle = document.createElement('h3');
audioTitle.textContent = 'Audio';
audioSection.appendChild(audioTitle);
// Master Volume
const masterVolumeContainer = document.createElement('div');
masterVolumeContainer.className = 'options-row';
const masterVolumeLabel = document.createElement('label');
masterVolumeLabel.textContent = 'Master Volume:';
masterVolumeContainer.appendChild(masterVolumeLabel);
const masterVolume = document.createElement('input');
masterVolume.type = 'range';
masterVolume.min = '0';
masterVolume.max = '100';
masterVolume.value = '100';
masterVolume.id = 'master-volume';
masterVolume.addEventListener('input', (e) => {
const persistenceManager = this.getModule('persistence-manager');
const audioManager = this.getModule('audio-manager');
if (persistenceManager && audioManager) {
const volume = parseInt(e.target.value) / 100;
persistenceManager.updatePreference('audio', 'masterVolume', volume);
audioManager.setMasterVolume(volume);
}
});
masterVolumeContainer.appendChild(masterVolume);
audioSection.appendChild(masterVolumeContainer);
// Speech Volume
const speechVolumeContainer = document.createElement('div');
speechVolumeContainer.className = 'options-row';
const speechVolumeLabel = document.createElement('label');
speechVolumeLabel.textContent = 'Speech Volume:';
speechVolumeContainer.appendChild(speechVolumeLabel);
const speechVolume = document.createElement('input');
speechVolume.type = 'range';
speechVolume.min = '0';
speechVolume.max = '100';
speechVolume.value = '100';
speechVolume.id = 'speech-volume';
speechVolume.addEventListener('input', (e) => {
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
const volume = parseInt(e.target.value) / 100;
persistenceManager.updatePreference('tts', 'volume', volume);
}
});
speechVolumeContainer.appendChild(speechVolume);
audioSection.appendChild(speechVolumeContainer);
// Music Volume
const musicVolumeContainer = document.createElement('div');
musicVolumeContainer.className = 'options-row';
const musicVolumeLabel = document.createElement('label');
musicVolumeLabel.textContent = 'Music Volume:';
musicVolumeContainer.appendChild(musicVolumeLabel);
const musicVolume = document.createElement('input');
musicVolume.type = 'range';
musicVolume.min = '0';
musicVolume.max = '100';
musicVolume.value = '70';
musicVolume.id = 'music-volume';
musicVolume.addEventListener('input', (e) => {
const persistenceManager = this.getModule('persistence-manager');
const audioManager = this.getModule('audio-manager');
if (persistenceManager && audioManager) {
const volume = parseInt(e.target.value) / 100;
persistenceManager.updatePreference('audio', 'musicVolume', volume);
audioManager.setMusicVolume(volume);
}
});
musicVolumeContainer.appendChild(musicVolume);
audioSection.appendChild(musicVolumeContainer);
// Effects Volume
const effectsVolumeContainer = document.createElement('div');
effectsVolumeContainer.className = 'options-row';
const effectsVolumeLabel = document.createElement('label');
effectsVolumeLabel.textContent = 'Effects Volume:';
effectsVolumeContainer.appendChild(effectsVolumeLabel);
const effectsVolume = document.createElement('input');
effectsVolume.type = 'range';
effectsVolume.min = '0';
effectsVolume.max = '100';
effectsVolume.value = '100';
effectsVolume.id = 'effects-volume';
effectsVolume.addEventListener('input', (e) => {
const persistenceManager = this.getModule('persistence-manager');
const audioManager = this.getModule('audio-manager');
if (persistenceManager && audioManager) {
const volume = parseInt(e.target.value) / 100;
persistenceManager.updatePreference('audio', 'sfxVolume', volume);
audioManager.setSfxVolume(volume);
}
});
effectsVolumeContainer.appendChild(effectsVolume);
audioSection.appendChild(effectsVolumeContainer);
settings.appendChild(audioSection);
// Reload notice
const reloadNotice = document.createElement('div');
reloadNotice.id = 'reload-notice';
reloadNotice.className = 'reload-notice';
reloadNotice.style.display = 'none';
reloadNotice.innerHTML = '<span>* Changes to language or speech system require a page reload to take full effect.</span>';
settings.appendChild(reloadNotice);
content.appendChild(settings);
this.modal.appendChild(content);
document.body.appendChild(this.modal);
// Store references to elements
this.elements = {
ttsSystem,
ttsVoice,
language,
textSpeed,
masterVolume,
speechVolume,
musicVolume,
effectsVolume,
reloadNotice,
speechRate: speedSlider,
ttsSpeechToggle
};
}
/**
* Show the options modal
*/
show() {
if (!this.modal) return;
// Reload preferences before showing
this.loadPreferences();
// Show modal
this.modal.style.display = 'flex';
}
/**
* Hide the options modal
*/
hide() {
if (!this.modal) return;
this.modal.style.display = 'none';
}
/**
* Toggle the options modal
*/
toggle() {
if (this.modal.style.display === 'flex') {
this.hide();
} else {
this.show();
}
}
/**
* Populate TTS systems dropdown
*/
populateTtsSystems() {
if (!this.elements || !this.elements.ttsSystem) return;
const ttsFactory = this.getModule('tts-factory');
if (!ttsFactory) return;
// Clear existing options
this.elements.ttsSystem.innerHTML = '';
// Add "None" option first
const noneOption = document.createElement('option');
noneOption.value = 'none';
noneOption.textContent = 'None (Disable TTS)';
this.elements.ttsSystem.appendChild(noneOption);
// Get available handlers
const handlers = ttsFactory.getAvailableHandlers();
// Add all registered handlers
for (const id in handlers) {
const option = document.createElement('option');
option.value = id;
option.textContent = this.getTtsSystemName(id);
this.elements.ttsSystem.appendChild(option);
}
// If no handlers available, add a disabled option
if (this.elements.ttsSystem.options.length === 1) {
const option = document.createElement('option');
option.value = '';
option.textContent = 'No TTS systems available';
option.disabled = true;
this.elements.ttsSystem.appendChild(option);
}
// Set the current provider value in the dropdown
if (this.persistenceManager) {
const provider = this.persistenceManager.getPreference('tts', 'provider');
if (provider) {
const option = Array.from(this.elements.ttsSystem.options).find(opt => opt.value === provider);
if (option) {
this.elements.ttsSystem.value = provider;
}
}
}
}
/**
* Get a user-friendly name for a TTS system
* @param {string} id - TTS system ID
* @returns {string} - User-friendly name
*/
getTtsSystemName(id) {
switch (id) {
case 'browser': return 'Browser TTS';
case 'api': return 'API TTS';
case 'kokoro': return 'Kokoro TTS';
default: return id;
}
}
/**
* Populate voices dropdown for the current TTS system
*/
populateVoices() {
if (!this.elements || !this.elements.ttsVoice) {
console.log('Options UI: Cannot populate voices - elements not initialized');
return;
}
const ttsFactory = this.getModule('tts-factory');
const localization = this.getModule('localization');
if (!ttsFactory || !localization) {
console.log('Options UI: Cannot populate voices - required modules not available');
return;
}
// Clear existing options
this.elements.ttsVoice.innerHTML = '';
// Get current locale
const currentLocale = localization.getLocale();
console.log(`Options UI: Current locale from localization module: ${currentLocale}`);
// Get active TTS handler
const activeHandler = ttsFactory.getActiveHandler();
const handlerId = activeHandler ? activeHandler.getId() : 'none';
console.log(`Options UI: Populating voices for locale: ${currentLocale}, handler: ${handlerId}`);
// Get voices from active handler
const voices = ttsFactory.getVoices();
console.log(`Options UI: Got ${voices ? voices.length : 0} voices from TTS factory`);
// Add available voices to dropdown
if (voices && voices.length > 0) {
// Add options for each voice
voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice.id || voice.name;
option.textContent = voice.name;
if (voice.lang) {
option.textContent += ` (${voice.lang})`;
}
this.elements.ttsVoice.appendChild(option);
});
console.log(`Options UI: Added ${voices.length} voice options to the dropdown`);
} else {
// No voices available
const option = document.createElement('option');
option.value = '';
option.textContent = `No voices available for ${currentLocale}`;
option.disabled = true;
this.elements.ttsVoice.appendChild(option);
console.log(`Options UI: No voices available for ${currentLocale}, added placeholder option`);
}
}
/**
* Populate languages dropdown
*/
populateLanguages() {
if (!this.elements || !this.elements.language) return;
const localization = this.getModule('localization');
if (!localization) return;
// Clear existing options
this.elements.language.innerHTML = '';
// Get available locales from the localization module
const availableLocales = localization.getAvailableLocales();
// Add options for each language
availableLocales.forEach(localeCode => {
const option = document.createElement('option');
option.value = localeCode;
option.textContent = localization.getLanguageName(localeCode);
this.elements.language.appendChild(option);
});
// Set current locale as selected
const currentLocale = localization.getLocale();
if (currentLocale && this.elements.language.querySelector(`option[value="${currentLocale}"]`)) {
this.elements.language.value = currentLocale;
}
}
/**
* Load current preferences into the UI
*/
loadPreferences() {
if (!this.persistenceManager || !this.elements) return;
// Wait for dependencies
this.waitForDependencies().then(() => {
const prefs = this.persistenceManager.getAllPreferences();
// TTS System
if (this.elements.ttsSystem) {
const provider = prefs.tts.provider;
if (provider) {
// Check if the option exists
const option = Array.from(this.elements.ttsSystem.options).find(opt => opt.value === provider);
if (option) {
this.elements.ttsSystem.value = provider;
}
}
}
// TTS Voice
if (this.elements.ttsVoice) {
const voice = prefs.tts.voice;
if (voice) {
// Check if the option exists
const option = Array.from(this.elements.ttsVoice.options).find(opt => opt.value === voice);
if (option) {
this.elements.ttsVoice.value = voice;
}
}
}
// Language
if (this.elements.language) {
const locale = prefs.app.locale;
if (locale) {
// Check if the option exists
const option = Array.from(this.elements.language.options).find(opt => opt.value === locale);
if (option) {
this.elements.language.value = locale;
}
}
}
// Text Speed
if (this.elements.textSpeed) {
this.elements.textSpeed.value = prefs.animation.speed;
}
// Master Volume
if (this.elements.masterVolume) {
this.elements.masterVolume.value = Math.round(prefs.audio.masterVolume * 100);
}
// Speech Volume
if (this.elements.speechVolume) {
this.elements.speechVolume.value = Math.round(prefs.tts.volume * 100);
}
// Music Volume
if (this.elements.musicVolume) {
this.elements.musicVolume.value = Math.round(prefs.audio.musicVolume * 100);
}
// Effects Volume
if (this.elements.effectsVolume) {
this.elements.effectsVolume.value = Math.round(prefs.audio.sfxVolume * 100);
}
// Speech Rate
if (this.elements.speechRate) {
this.elements.speechRate.value = Math.round(prefs.tts.speed * 100);
}
// TTS Speech Toggle
if (this.elements.ttsSpeechToggle) {
this.elements.ttsSpeechToggle.checked = prefs.tts.enabled;
}
});
}
/**
* Apply settings to the game
*/
applySettings() {
if (!this.persistenceManager) return;
this.waitForDependencies().then(() => {
const prefs = this.persistenceManager.getAllPreferences();
// Apply TTS settings
const ttsFactory = this.getModule('tts-factory');
if (ttsFactory) {
// Set active handler
const provider = this.elements.ttsSystem.value;
ttsFactory.setActiveHandler(provider);
// Update TTS state
const enabled = provider !== 'none';
document.dispatchEvent(new CustomEvent('tts:stateChange', {
detail: { enabled: enabled }
}));
// Update persistence
this.persistenceManager.updatePreference('tts', 'provider', provider);
this.persistenceManager.updatePreference('tts', 'enabled', enabled);
}
// Apply language settings
const localization = this.getModule('localization');
if (localization && this.elements.language) {
const currentLocale = localization.getLocale();
// Update the UI to match the current locale
if (currentLocale && this.elements.language.value !== currentLocale) {
this.elements.language.value = currentLocale;
}
}
// Apply audio settings
const audioManager = this.getModule('audio-manager');
if (audioManager) {
audioManager.setMasterVolume(prefs.audio.masterVolume);
audioManager.setMusicVolume(prefs.audio.musicVolume);
audioManager.setSfxVolume(prefs.audio.sfxVolume);
}
});
}
/**
* Handle TTS system changed event
*/
handleTtsSystemChanged() {
this.populateVoices();
}
/**
* Show reload notice
*/
showReloadNotice() {
if (!this.elements || !this.elements.reloadNotice) return;
this.elements.reloadNotice.style.display = 'block';
this.reloadRequired = true;
}
/**
* Save current settings
*/
saveCurrentSettings() {
if (!this.persistenceManager) return;
// Save TTS settings
const ttsFactory = this.getModule('tts-factory');
if (ttsFactory) {
const provider = this.elements.ttsSystem.value;
const voice = this.elements.ttsVoice.value;
const speed = parseInt(this.elements.speechRate.value) / 100;
const enabled = this.elements.ttsSpeechToggle.checked;
this.persistenceManager.updatePreference('tts', 'provider', provider);
this.persistenceManager.updatePreference('tts', 'voice', voice);
this.persistenceManager.updatePreference('tts', 'speed', speed);
this.persistenceManager.updatePreference('tts', 'enabled', enabled);
}
// Save language settings
const localization = this.getModule('localization');
if (localization && this.elements.language) {
const locale = this.elements.language.value;
this.persistenceManager.updatePreference('app', 'locale', locale);
this.persistenceManager.updatePreference('tts', 'language', locale);
}
// Save audio settings
const audioManager = this.getModule('audio-manager');
if (audioManager) {
const masterVolume = parseInt(this.elements.masterVolume.value) / 100;
const musicVolume = parseInt(this.elements.musicVolume.value) / 100;
const sfxVolume = parseInt(this.elements.effectsVolume.value) / 100;
const speechVolume = parseInt(this.elements.speechVolume.value) / 100;
this.persistenceManager.updatePreference('audio', 'masterVolume', masterVolume);
this.persistenceManager.updatePreference('audio', 'musicVolume', musicVolume);
this.persistenceManager.updatePreference('audio', 'sfxVolume', sfxVolume);
this.persistenceManager.updatePreference('tts', 'volume', speechVolume);
}
// Save text speed setting
const textSpeed = parseInt(this.elements.textSpeed.value);
this.persistenceManager.updatePreference('animation', 'speed', textSpeed);
}
setupEventListeners() {
// Listen for language change events
document.addEventListener('localization:languageChanged', () => {
this.populateLanguages();
this.populateVoices();
});
// Listen for TTS state changes
document.addEventListener('tts:stateChange', (event) => {
if (this.elements && this.elements.ttsSpeechToggle) {
this.elements.ttsSpeechToggle.checked = event.detail.enabled;
// Update persistence manager
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
persistenceManager.updatePreference('tts', 'enabled', event.detail.enabled);
}
}
});
// Listen for TTS handler changes
document.addEventListener('tts:handlerChanged', (event) => {
if (this.elements && this.elements.ttsSystem) {
// Update the dropdown to match the active handler
const handlerId = event.detail.handlerId;
if (handlerId && handlerId !== 'none') {
this.elements.ttsSystem.value = handlerId;
// Update persistence manager
const persistenceManager = this.getModule('persistence-manager');
if (persistenceManager) {
persistenceManager.updatePreference('tts', 'provider', handlerId);
}
}
// Refresh voices when handler changes
this.populateVoices();
}
});
// Listen for TTS availability events
document.addEventListener('tts:availability', (event) => {
if (!this.elements) return;
const available = event.detail?.available || false;
// Update the TTS options visibility
if (this.elements.ttsSection) {
this.elements.ttsSection.style.display = available ? 'block' : 'none';
}
// Update the TTS system dropdown
this.populateTtsSystems();
});
// Listen for Kokoro voice updates
document.addEventListener('kokoro:voices-updated', () => {
// Repopulate the voices dropdown when Kokoro voices become available
this.populateVoices();
});
// Browser window resize event
window.addEventListener('resize', () => {
// Update modal positioning
if (this.modal && this.modal.style.display === 'block') {
this.positionModal();
}
});
}
}
// Create the singleton instance
const OptionsUI = new OptionsUIModule();
// Register with the module registry
moduleRegistry.register(OptionsUI);
// Export the module
export { OptionsUI };