1304 lines
51 KiB
Plaintext
1304 lines
51 KiB
Plaintext
/**
|
|
* 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');
|
|
|
|
// Set up dependencies
|
|
this.dependencies = [
|
|
'persistence-manager',
|
|
'localization',
|
|
'tts-factory',
|
|
'audio-manager'
|
|
];
|
|
|
|
// Modal element
|
|
this.modal = null;
|
|
|
|
// UI elements
|
|
this.elements = {};
|
|
|
|
// Settings that require reload
|
|
this.reloadRequired = false;
|
|
|
|
// Bind methods
|
|
this.bindMethods([
|
|
'show',
|
|
'hide',
|
|
'createModal',
|
|
'populateTtsSystems',
|
|
'populateVoices',
|
|
'populateLanguages',
|
|
'loadPreferences',
|
|
'applySettings',
|
|
'handleTtsSystemChanged',
|
|
'showReloadNotice',
|
|
'toggle',
|
|
'setupEventListeners',
|
|
'saveCurrentSettings',
|
|
'setupApiUrlFields',
|
|
'setupInitialState',
|
|
// Helper methods
|
|
'createUIElement',
|
|
'populateDropdown',
|
|
'registerHandler',
|
|
'dispatchApiChangeEvent',
|
|
'getPreference',
|
|
'updatePreference'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Creates a UI element and optionally appends it to a parent
|
|
* @param {string} type - Element type ('div', 'button', etc.)
|
|
* @param {string} className - CSS class name
|
|
* @param {string|Object} textOrProps - Text content or properties object
|
|
* @param {HTMLElement} parent - Parent element to append to
|
|
* @returns {HTMLElement} - The created element
|
|
*/
|
|
createUIElement(type, className, textOrProps, parent) {
|
|
const element = document.createElement(type);
|
|
if (className) element.className = className;
|
|
|
|
if (typeof textOrProps === 'string') {
|
|
element.textContent = textOrProps;
|
|
} else if (textOrProps) {
|
|
Object.assign(element, textOrProps);
|
|
}
|
|
|
|
if (parent) parent.appendChild(element);
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* Populates a select dropdown with options
|
|
* @param {HTMLSelectElement} selectElement - The select element to populate
|
|
* @param {Array} items - Array of items to add as options
|
|
* @param {string|Function} valueKey - Property name or function to extract value
|
|
* @param {string|Function} textKey - Property name or function to extract text
|
|
* @param {string} selectedValue - Value to select
|
|
*/
|
|
populateDropdown(selectElement, items, valueKey, textKey, selectedValue) {
|
|
if (!selectElement) return;
|
|
|
|
// Clear existing options
|
|
selectElement.innerHTML = '';
|
|
|
|
// Add options
|
|
items.forEach(item => {
|
|
const option = document.createElement('option');
|
|
option.value = typeof valueKey === 'function' ? valueKey(item) : item[valueKey];
|
|
option.textContent = typeof textKey === 'function' ? textKey(item) : item[textKey];
|
|
selectElement.appendChild(option);
|
|
});
|
|
|
|
// Set selected value if provided and exists
|
|
if (selectedValue && selectElement.querySelector(`option[value="${selectedValue}"]`)) {
|
|
selectElement.value = selectedValue;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Registers an event handler on an element
|
|
* @param {string} elementName - Element name in this.elements
|
|
* @param {string} eventType - Event type to listen for
|
|
* @param {Function} handler - Event handler function
|
|
*/
|
|
registerHandler(elementName, eventType, handler) {
|
|
if (this.elements && this.elements[elementName]) {
|
|
this.elements[elementName].addEventListener(eventType, handler);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispatches an API change event
|
|
* @param {string} eventType - Event type (e.g. 'tts:api:keyChanged')
|
|
* @param {string} provider - Provider name (e.g. 'elevenlabs')
|
|
* @param {string} valueType - Value type (e.g. 'key', 'url')
|
|
* @param {string} value - The value
|
|
*/
|
|
dispatchApiChangeEvent(eventType, provider, valueType, value) {
|
|
const detail = { provider };
|
|
detail[valueType] = value;
|
|
document.dispatchEvent(new CustomEvent(eventType, { detail }));
|
|
}
|
|
|
|
/**
|
|
* Gets a preference from persistence manager
|
|
* @param {string} category - Preference category
|
|
* @param {string} key - Preference key
|
|
* @param {*} defaultValue - Default value if not found
|
|
* @returns {*} - Preference value
|
|
*/
|
|
getPreference(category, key, defaultValue = null) {
|
|
const persistenceManager = this.getModule('persistence-manager');
|
|
return persistenceManager.getPreference(category, key) || defaultValue;
|
|
}
|
|
|
|
/**
|
|
* Updates a preference in persistence manager
|
|
* @param {string} category - Preference category
|
|
* @param {string} key - Preference key
|
|
* @param {*} value - Value to set
|
|
*/
|
|
updatePreference(category, key, value) {
|
|
const persistenceManager = this.getModule('persistence-manager');
|
|
persistenceManager.updatePreference(category, key, value);
|
|
}
|
|
|
|
/**
|
|
* Initialize the Options UI module
|
|
* @returns {Promise<boolean>} - Promise resolves with initialization success
|
|
*/
|
|
async initialize() {
|
|
console.log('Options UI: Initializing');
|
|
|
|
// Create DOM elements
|
|
this.createModal();
|
|
|
|
// Set up event listeners
|
|
this.setupEventListeners();
|
|
|
|
// Initialize module
|
|
this.reportProgress(50, 'Initializing UI');
|
|
|
|
// Set up initial state
|
|
await this.setupInitialState();
|
|
|
|
// Set up API URL fields with correct defaults
|
|
this.setupApiUrlFields();
|
|
|
|
// Set up immediate save listeners for all input controls
|
|
this.setupImmediateSaveListeners();
|
|
|
|
this.reportProgress(100, 'Options UI initialized');
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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 = '×';
|
|
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);
|
|
|
|
// API TTS Provider Settings (ElevenLabs and OpenAI)
|
|
// Container for API settings that will be shown/hidden based on selected TTS system
|
|
const apiSettingsContainer = document.createElement('div');
|
|
apiSettingsContainer.id = 'api-tts-settings';
|
|
apiSettingsContainer.className = 'api-settings-container';
|
|
apiSettingsContainer.style.display = 'none';
|
|
|
|
// ElevenLabs API Key
|
|
const elevenLabsApiKeyContainer = document.createElement('div');
|
|
elevenLabsApiKeyContainer.className = 'options-row elevenlabs-setting';
|
|
elevenLabsApiKeyContainer.dataset.provider = 'elevenlabs';
|
|
|
|
const elevenLabsApiKeyLabel = document.createElement('label');
|
|
elevenLabsApiKeyLabel.textContent = 'ElevenLabs API Key:';
|
|
elevenLabsApiKeyContainer.appendChild(elevenLabsApiKeyLabel);
|
|
|
|
const elevenLabsApiKey = document.createElement('input');
|
|
elevenLabsApiKey.type = 'password';
|
|
elevenLabsApiKey.id = 'elevenlabs-api-key';
|
|
elevenLabsApiKey.placeholder = 'Enter your ElevenLabs API key';
|
|
elevenLabsApiKey.addEventListener('change', (e) => {
|
|
const persistenceManager = this.getModule('persistence-manager');
|
|
if (persistenceManager) {
|
|
persistenceManager.updatePreference('tts', 'elevenlabs_api_key', e.target.value);
|
|
|
|
// Notify TTS system that API key has changed
|
|
document.dispatchEvent(new CustomEvent('tts:api:keyChanged', {
|
|
detail: { provider: 'elevenlabs', key: e.target.value }
|
|
}));
|
|
}
|
|
});
|
|
elevenLabsApiKeyContainer.appendChild(elevenLabsApiKey);
|
|
|
|
apiSettingsContainer.appendChild(elevenLabsApiKeyContainer);
|
|
|
|
// ElevenLabs API Base URL
|
|
const elevenLabsApiUrlContainer = document.createElement('div');
|
|
elevenLabsApiUrlContainer.className = 'options-row elevenlabs-setting';
|
|
elevenLabsApiUrlContainer.dataset.provider = 'elevenlabs';
|
|
|
|
const elevenLabsApiUrlLabel = document.createElement('label');
|
|
elevenLabsApiUrlLabel.textContent = 'ElevenLabs API URL:';
|
|
elevenLabsApiUrlContainer.appendChild(elevenLabsApiUrlLabel);
|
|
|
|
const elevenLabsApiUrl = document.createElement('input');
|
|
elevenLabsApiUrl.type = 'text';
|
|
elevenLabsApiUrl.id = 'elevenlabs-api-url';
|
|
elevenLabsApiUrl.placeholder = 'https://api.elevenlabs.io/v1';
|
|
elevenLabsApiUrl.addEventListener('change', (e) => {
|
|
const persistenceManager = this.getModule('persistence-manager');
|
|
if (persistenceManager) {
|
|
persistenceManager.updatePreference('tts', 'elevenlabs_api_url', e.target.value);
|
|
|
|
// Notify TTS system that API URL has changed
|
|
document.dispatchEvent(new CustomEvent('tts:api:urlChanged', {
|
|
detail: { provider: 'elevenlabs', url: e.target.value }
|
|
}));
|
|
}
|
|
});
|
|
elevenLabsApiUrlContainer.appendChild(elevenLabsApiUrl);
|
|
|
|
apiSettingsContainer.appendChild(elevenLabsApiUrlContainer);
|
|
|
|
// OpenAI API Key
|
|
const openaiApiKeyContainer = document.createElement('div');
|
|
openaiApiKeyContainer.className = 'options-row openai-setting';
|
|
openaiApiKeyContainer.dataset.provider = 'openai';
|
|
|
|
const openaiApiKeyLabel = document.createElement('label');
|
|
openaiApiKeyLabel.textContent = 'OpenAI API Key:';
|
|
openaiApiKeyContainer.appendChild(openaiApiKeyLabel);
|
|
|
|
const openaiApiKey = document.createElement('input');
|
|
openaiApiKey.type = 'password';
|
|
openaiApiKey.id = 'openai-api-key';
|
|
openaiApiKey.placeholder = 'Enter your OpenAI API key';
|
|
openaiApiKey.addEventListener('change', (e) => {
|
|
const persistenceManager = this.getModule('persistence-manager');
|
|
if (persistenceManager) {
|
|
persistenceManager.updatePreference('tts', 'openai_api_key', e.target.value);
|
|
|
|
// Notify TTS system that API key has changed
|
|
document.dispatchEvent(new CustomEvent('tts:api:keyChanged', {
|
|
detail: { provider: 'openai', key: e.target.value }
|
|
}));
|
|
}
|
|
});
|
|
openaiApiKeyContainer.appendChild(openaiApiKey);
|
|
|
|
apiSettingsContainer.appendChild(openaiApiKeyContainer);
|
|
|
|
// OpenAI API Base URL
|
|
const openaiApiUrlContainer = document.createElement('div');
|
|
openaiApiUrlContainer.className = 'options-row openai-setting';
|
|
openaiApiUrlContainer.dataset.provider = 'openai';
|
|
|
|
const openaiApiUrlLabel = document.createElement('label');
|
|
openaiApiUrlLabel.textContent = 'OpenAI API URL:';
|
|
openaiApiUrlContainer.appendChild(openaiApiUrlLabel);
|
|
|
|
const openaiApiUrl = document.createElement('input');
|
|
openaiApiUrl.type = 'text';
|
|
openaiApiUrl.id = 'openai-api-url';
|
|
openaiApiUrl.placeholder = 'https://api.openai.com/v1';
|
|
openaiApiUrl.addEventListener('change', (e) => {
|
|
const persistenceManager = this.getModule('persistence-manager');
|
|
if (persistenceManager) {
|
|
persistenceManager.updatePreference('tts', 'openai_api_url', e.target.value);
|
|
|
|
// Notify TTS system that API URL has changed
|
|
document.dispatchEvent(new CustomEvent('tts:api:urlChanged', {
|
|
detail: { provider: 'openai', url: e.target.value }
|
|
}));
|
|
}
|
|
});
|
|
openaiApiUrlContainer.appendChild(openaiApiUrl);
|
|
|
|
apiSettingsContainer.appendChild(openaiApiUrlContainer);
|
|
|
|
ttsSection.appendChild(apiSettingsContainer);
|
|
|
|
// 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: ttsSystem,
|
|
ttsVoice: ttsVoice,
|
|
language: language,
|
|
textSpeed: textSpeed,
|
|
masterVolume: masterVolume,
|
|
speechVolume: speechVolume,
|
|
musicVolume: musicVolume,
|
|
effectsVolume: effectsVolume,
|
|
reloadNotice: reloadNotice,
|
|
speechRate: speedSlider,
|
|
ttsSpeechToggle: ttsSpeechToggle,
|
|
apiSettingsContainer: apiSettingsContainer,
|
|
elevenLabsApiKey: elevenLabsApiKey,
|
|
elevenLabsApiUrl: elevenLabsApiUrl,
|
|
openaiApiKey: openaiApiKey,
|
|
openaiApiUrl: openaiApiUrl
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Show the options modal
|
|
*/
|
|
show() {
|
|
// Show modal
|
|
if (this.modal) {
|
|
this.modal.style.display = 'flex';
|
|
|
|
// Refresh TTS dropdown
|
|
this.populateTtsSystems();
|
|
|
|
// Make sure the UI reflects the current voice
|
|
this.populateVoices();
|
|
|
|
// Update API settings visibility based on the current selection
|
|
this.updateApiSettingsVisibility();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.ttsSystem) return;
|
|
|
|
const ttsFactory = this.getModule('tts-factory');
|
|
|
|
// Get available TTS handlers
|
|
const handlers = ttsFactory.getAvailableHandlers();
|
|
|
|
// Format for dropdown
|
|
const items = handlers.map(handler => ({
|
|
id: handler.id,
|
|
name: this.getTtsSystemName(handler.id)
|
|
}));
|
|
|
|
// Get current handler
|
|
const currentHandler = ttsFactory.getActiveHandler();
|
|
const currentHandlerId = currentHandler ? currentHandler.id : 'none';
|
|
|
|
// Populate dropdown
|
|
this.populateDropdown(
|
|
this.elements.ttsSystem,
|
|
items,
|
|
'id',
|
|
'name',
|
|
currentHandlerId
|
|
);
|
|
|
|
// Update API settings visibility
|
|
this.updateApiSettingsVisibility();
|
|
}
|
|
|
|
/**
|
|
* Update visibility of API settings based on selected TTS system
|
|
*/
|
|
updateApiSettingsVisibility() {
|
|
if (!this.elements.ttsSystem || !this.elements.apiSettingsContainer) return;
|
|
|
|
const selectedSystem = this.elements.ttsSystem.value;
|
|
const isApiSystem = selectedSystem === 'elevenlabs' || selectedSystem === 'openai';
|
|
|
|
// Show or hide API settings
|
|
this.elements.apiSettingsContainer.style.display = isApiSystem ? 'block' : 'none';
|
|
|
|
// Show/hide specific provider settings
|
|
if (this.elements.elevenLabsApiKey && this.elements.elevenLabsApiUrl) {
|
|
const isElevenLabs = selectedSystem === 'elevenlabs';
|
|
this.elements.elevenLabsApiKey.parentNode.style.display = isElevenLabs ? 'block' : 'none';
|
|
this.elements.elevenLabsApiUrl.parentNode.style.display = isElevenLabs ? 'block' : 'none';
|
|
}
|
|
|
|
if (this.elements.openaiApiKey && this.elements.openaiApiUrl) {
|
|
const isOpenAI = selectedSystem === 'openai';
|
|
this.elements.openaiApiKey.parentNode.style.display = isOpenAI ? 'block' : 'none';
|
|
this.elements.openaiApiUrl.parentNode.style.display = isOpenAI ? 'block' : 'none';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 'Web Browser';
|
|
case 'elevenlabs':
|
|
return 'ElevenLabs';
|
|
case 'openai':
|
|
return 'OpenAI';
|
|
case 'kokoro':
|
|
return 'Kokoro (Local) ';
|
|
default:
|
|
return id.charAt(0).toUpperCase() + id.slice(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Populate voices dropdown for the current TTS system
|
|
*/
|
|
populateVoices() {
|
|
if (!this.elements.ttsVoice) return;
|
|
|
|
const ttsFactory = this.getModule('tts-factory');
|
|
const localization = this.getModule('localization');
|
|
|
|
// Get current handler and voices
|
|
const currentHandler = ttsFactory.getActiveHandler();
|
|
const currentLocale = localization.getLocale() || 'en-US';
|
|
|
|
// If we have a handler with voices, populate the dropdown
|
|
if (currentHandler && currentHandler.getVoices) {
|
|
const voices = currentHandler.getVoices(currentLocale);
|
|
|
|
if (voices && voices.length > 0) {
|
|
// Format for dropdown
|
|
const items = voices.map(voice => ({
|
|
id: voice.id || voice.name,
|
|
name: voice.name
|
|
}));
|
|
|
|
// Get current voice
|
|
const currentVoice = this.getPreference('tts', 'voice');
|
|
|
|
// Populate dropdown
|
|
this.populateDropdown(
|
|
this.elements.ttsVoice,
|
|
items,
|
|
'id',
|
|
'name',
|
|
currentVoice
|
|
);
|
|
} else {
|
|
// No voices available, add a placeholder
|
|
this.elements.ttsVoice.innerHTML = '';
|
|
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.language) return;
|
|
|
|
const localization = this.getModule('localization');
|
|
|
|
// Get available locales
|
|
const availableLocales = localization.getAvailableLocales();
|
|
|
|
// Format for dropdown
|
|
const items = availableLocales.map(localeCode => ({
|
|
id: localeCode,
|
|
name: localization.getLanguageName(localeCode)
|
|
}));
|
|
|
|
// Get current locale
|
|
const currentLocale = localization.getLocale();
|
|
|
|
// Populate dropdown
|
|
this.populateDropdown(
|
|
this.elements.language,
|
|
items,
|
|
'id',
|
|
'name',
|
|
currentLocale
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Load current preferences into the UI
|
|
*/
|
|
loadPreferences() {
|
|
console.log('Options UI: Loading preferences');
|
|
|
|
// Get current preferences
|
|
const ttsSpeechEnabled = this.getPreference('tts', 'enabled', false);
|
|
const ttsSpeed = this.getPreference('tts', 'speed', 0.5);
|
|
const currentLocale = this.getPreference('app', 'locale', 'en-US');
|
|
|
|
// Set TTS speech toggle
|
|
if (this.elements.ttsSpeechToggle) {
|
|
this.elements.ttsSpeechToggle.checked = ttsSpeechEnabled;
|
|
}
|
|
|
|
// Set speech rate
|
|
if (this.elements.speechRate) {
|
|
// Convert from 0-1 to 0-100 for slider
|
|
this.elements.speechRate.value = Math.round(ttsSpeed * 100);
|
|
}
|
|
|
|
// Set language
|
|
if (this.elements.language) {
|
|
// Check if the locale is available in the dropdown
|
|
if (this.elements.language.querySelector(`option[value="${currentLocale}"]`)) {
|
|
this.elements.language.value = currentLocale;
|
|
}
|
|
}
|
|
|
|
// Set API keys
|
|
if (this.elements.elevenLabsApiKey) {
|
|
const elevenLabsApiKey = this.getPreference('tts', 'elevenlabs_api_key', '');
|
|
this.elements.elevenLabsApiKey.value = elevenLabsApiKey;
|
|
}
|
|
|
|
if (this.elements.openaiApiKey) {
|
|
const openaiApiKey = this.getPreference('tts', 'openai_api_key', '');
|
|
this.elements.openaiApiKey.value = openaiApiKey;
|
|
}
|
|
|
|
// Set API URLs - these are handled in setupApiUrlFields
|
|
}
|
|
|
|
/**
|
|
* Apply settings to the game
|
|
*/
|
|
applySettings() {
|
|
if (!this.persistenceManager) return;
|
|
|
|
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() {
|
|
const ttsFactory = this.getModule('tts-factory');
|
|
if (!ttsFactory) return;
|
|
|
|
// Repopulate the systems dropdown to reflect the current state
|
|
this.populateTtsSystems();
|
|
|
|
// Populate voices for the new system
|
|
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);
|
|
|
|
// Save ElevenLabs API Key
|
|
const elevenLabsApiKey = this.elements.elevenLabsApiKey.value;
|
|
this.persistenceManager.updatePreference('tts', 'elevenlabs_api_key', elevenLabsApiKey);
|
|
|
|
// Save ElevenLabs API URL
|
|
const elevenLabsApiUrl = this.elements.elevenLabsApiUrl.value;
|
|
this.persistenceManager.updatePreference('tts', 'elevenlabs_api_url', elevenLabsApiUrl);
|
|
|
|
// Save OpenAI API Key
|
|
const openaiApiKey = this.elements.openaiApiKey.value;
|
|
this.persistenceManager.updatePreference('tts', 'openai_api_key', openaiApiKey);
|
|
|
|
// Save OpenAI API URL
|
|
const openaiApiUrl = this.elements.openaiApiUrl.value;
|
|
this.persistenceManager.updatePreference('tts', 'openai_api_url', openaiApiUrl);
|
|
}
|
|
|
|
setupEventListeners() {
|
|
try {
|
|
// Listen for language change events
|
|
document.addEventListener('localization:languageChanged', () => {
|
|
try {
|
|
this.populateLanguages();
|
|
this.populateVoices();
|
|
} catch (error) {
|
|
console.error('Options UI: Error handling language change event', error);
|
|
}
|
|
});
|
|
|
|
// Listen for TTS state changes
|
|
document.addEventListener('tts:stateChange', (event) => {
|
|
try {
|
|
if (this.elements && this.elements.ttsSpeechToggle) {
|
|
this.elements.ttsSpeechToggle.checked = event.detail.enabled;
|
|
|
|
// Save preference immediately
|
|
if (this.persistenceManager) {
|
|
this.persistenceManager.updatePreference('tts', 'enabled', event.detail.enabled);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Options UI: Error handling TTS state change event', error);
|
|
}
|
|
});
|
|
|
|
// Listen for TTS handler changes
|
|
document.addEventListener('tts:handler:changed', (event) => {
|
|
try {
|
|
if (event.detail && event.detail.handler) {
|
|
console.log(`Options UI: TTS handler changed to ${event.detail.handler}`);
|
|
this.handleTtsSystemChanged();
|
|
|
|
// Save preference immediately
|
|
if (this.persistenceManager) {
|
|
this.persistenceManager.updatePreference('tts', 'preferred_handler', event.detail.handler);
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Options UI: Error handling TTS handler change event', error);
|
|
}
|
|
});
|
|
|
|
// Listen for TTS availability changes
|
|
document.addEventListener('tts:availability', (event) => {
|
|
try {
|
|
if (event.detail && typeof event.detail.available === 'boolean' && this.elements) {
|
|
const available = event.detail.available;
|
|
console.log(`Options UI: TTS availability changed to ${available}`);
|
|
|
|
// Update UI to reflect TTS availability
|
|
if (this.elements.ttsSection) {
|
|
this.elements.ttsSection.classList.toggle('tts-unavailable', !available);
|
|
// Add status message if not available
|
|
if (!available && !this.elements.ttsUnavailableMessage) {
|
|
const statusDiv = document.createElement('div');
|
|
statusDiv.className = 'tts-status-message';
|
|
statusDiv.innerHTML = '<strong>TTS Unavailable</strong>: Check logs for details. You can still configure API keys below.';
|
|
statusDiv.style.color = '#ca3c3c';
|
|
statusDiv.style.padding = '5px 0';
|
|
statusDiv.style.marginBottom = '10px';
|
|
this.elements.ttsUnavailableMessage = statusDiv;
|
|
// Insert at the top of the TTS section
|
|
this.elements.ttsSection.insertBefore(statusDiv, this.elements.ttsSection.firstChild);
|
|
} else if (available && this.elements.ttsUnavailableMessage) {
|
|
// Remove the message if TTS becomes available
|
|
this.elements.ttsUnavailableMessage.remove();
|
|
this.elements.ttsUnavailableMessage = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the TTS system dropdown
|
|
this.populateTtsSystems();
|
|
} catch (error) {
|
|
console.error('Options UI: Error handling TTS availability event', error);
|
|
}
|
|
});
|
|
|
|
// Listen for Kokoro voice updates
|
|
document.addEventListener('kokoro:voices-updated', () => {
|
|
try {
|
|
// Repopulate the voices dropdown when Kokoro voices become available
|
|
this.populateVoices();
|
|
} catch (error) {
|
|
console.error('Options UI: Error handling Kokoro voices update event', error);
|
|
}
|
|
});
|
|
|
|
// Browser window resize event
|
|
window.addEventListener('resize', () => {
|
|
try {
|
|
// Update modal positioning
|
|
if (this.modal && this.modal.style.display === 'block') {
|
|
this.positionModal();
|
|
}
|
|
} catch (error) {
|
|
console.error('Options UI: Error handling window resize event', error);
|
|
}
|
|
});
|
|
|
|
console.log('Options UI: Event listeners set up successfully');
|
|
} catch (error) {
|
|
console.error('Options UI: Error setting up event listeners', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up immediate save listeners for all input elements
|
|
*/
|
|
setupImmediateSaveListeners() {
|
|
try {
|
|
if (!this.elements || !this.persistenceManager) {
|
|
console.warn('Options UI: Cannot set up immediate save listeners - elements or persistence manager missing');
|
|
return;
|
|
}
|
|
|
|
// Ensure we have the required elements before setting up listeners
|
|
const elementsToSetup = [
|
|
'ttsSystem', 'ttsVoice', 'language',
|
|
'ttsSpeechToggle', 'speechRate',
|
|
'elevenLabsApiKey', 'elevenLabsApiUrl',
|
|
'openaiApiKey', 'openaiApiUrl'
|
|
];
|
|
|
|
// Add change listeners for immediate save on each input
|
|
elementsToSetup.forEach(elementName => {
|
|
this.registerHandler(elementName, 'change', () => {
|
|
console.log(`Options UI: Change detected on ${elementName}, saving settings`);
|
|
this.saveCurrentSettings();
|
|
});
|
|
});
|
|
|
|
// For range inputs, also add input event to update during dragging
|
|
this.registerHandler('speechRate', 'input', () => {
|
|
// Update TTS speech rate immediately on slider movement
|
|
if (this.elements.speechRate) {
|
|
const ttsFactory = this.getModule('tts-factory');
|
|
const speechRate = parseFloat(this.elements.speechRate.value);
|
|
ttsFactory.setSpeed(speechRate);
|
|
}
|
|
});
|
|
} catch (error) {
|
|
console.error('Options UI: Error setting up immediate save listeners', error);
|
|
}
|
|
}
|
|
|
|
setupApiUrlFields() {
|
|
if (!this.elements) return;
|
|
|
|
// Set up ElevenLabs API URL
|
|
if (this.elements.elevenLabsApiUrl) {
|
|
const savedUrl = this.getPreference('tts', 'elevenlabs_api_url');
|
|
const defaultUrl = 'https://api.elevenlabs.io/v1';
|
|
|
|
// Always set the input value to the saved or default URL
|
|
this.elements.elevenLabsApiUrl.value = savedUrl || defaultUrl;
|
|
|
|
// Save default to persistence if not already set
|
|
if (!savedUrl) {
|
|
console.log('Options UI: Setting default ElevenLabs API URL:', defaultUrl);
|
|
this.updatePreference('tts', 'elevenlabs_api_url', defaultUrl);
|
|
}
|
|
}
|
|
|
|
// Set up OpenAI API URL
|
|
if (this.elements.openaiApiUrl) {
|
|
const savedUrl = this.getPreference('tts', 'openai_api_url');
|
|
const defaultUrl = 'https://api.openai.com/v1';
|
|
|
|
// Always set the input value to the saved or default URL
|
|
this.elements.openaiApiUrl.value = savedUrl || defaultUrl;
|
|
|
|
// Save default to persistence only if not already set
|
|
if (!savedUrl) {
|
|
console.log('Options UI: Setting default OpenAI API URL:', defaultUrl);
|
|
this.updatePreference('tts', 'openai_api_url', defaultUrl);
|
|
}
|
|
}
|
|
|
|
// Make sure API keys are initialized if not already set
|
|
if (!this.getPreference('tts', 'elevenlabs_api_key')) {
|
|
this.updatePreference('tts', 'elevenlabs_api_key', '');
|
|
}
|
|
|
|
if (!this.getPreference('tts', 'openai_api_key')) {
|
|
this.updatePreference('tts', 'openai_api_key', '');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set up the initial state of the Options UI
|
|
* @returns {Promise<boolean>} - Promise resolves when setup is complete
|
|
*/
|
|
async setupInitialState() {
|
|
try {
|
|
console.log('Options UI: Setting up initial state');
|
|
|
|
// Add event listener for toggling options UI
|
|
document.addEventListener('ui:options:toggle', () => this.toggle());
|
|
|
|
// Set up key bindings
|
|
document.addEventListener('keydown', (e) => {
|
|
if (e.key === 'Escape' && this.modal && this.modal.style.display === 'flex') {
|
|
this.saveCurrentSettings();
|
|
this.hide();
|
|
}
|
|
});
|
|
|
|
// Populate TTS systems
|
|
await this.populateTtsSystems();
|
|
|
|
// Populate languages
|
|
await this.populateLanguages();
|
|
|
|
// Populate voices based on current TTS system
|
|
await this.populateVoices();
|
|
|
|
// Load current preferences
|
|
this.loadPreferences();
|
|
|
|
// 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 language change listener
|
|
document.addEventListener('locale:changed', async () => {
|
|
this.updateUIText();
|
|
await this.populateLanguages();
|
|
});
|
|
|
|
// Register event listeners for TTS availability and voiceId changes
|
|
document.addEventListener('tts:engine:change', async (event) => {
|
|
console.log('Options UI: Received TTS engine change event:', event.detail);
|
|
await this.populateVoices();
|
|
await this.populateLanguages();
|
|
this.updateApiSettingsVisibility();
|
|
});
|
|
|
|
console.log('Options UI: Initial state setup complete');
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Options UI: Error setting up initial state', error);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Create the singleton instance
|
|
const OptionsUI = new OptionsUIModule();
|
|
|
|
// Register with the module registry
|
|
moduleRegistry.register(OptionsUI);
|
|
|
|
// Export the module
|
|
export { OptionsUI }; |