964 lines
34 KiB
JavaScript
964 lines
34 KiB
JavaScript
/**
|
||
* Options UI Module
|
||
* Provides the options UI for the game
|
||
*/
|
||
import { BaseModule } from './base-module.js';
|
||
import { createUIElement, populateDropdown, registerHandler, createPreferenceBinding } from './ui-helper.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',
|
||
'dispatchApiChangeEvent',
|
||
'getPreference',
|
||
'updatePreference'
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* Dispatches an API change event
|
||
* @param {string} eventType - Event type (e.g. 'api:key:change')
|
||
* @param {string} provider - Provider name (e.g. 'elevenlabs')
|
||
* @param {string} valueType - Value type (e.g. 'key', 'url')
|
||
* @param {string} value - Value to dispatch
|
||
*/
|
||
dispatchApiChangeEvent(eventType, provider, valueType, value) {
|
||
const eventName = `tts:${eventType}`;
|
||
console.log(`Options UI: Dispatching event ${eventName} for provider ${provider}`);
|
||
document.dispatchEvent(new CustomEvent(eventName, {
|
||
detail: { provider, [valueType]: value }
|
||
}));
|
||
}
|
||
|
||
/**
|
||
* Gets a preference from the persistence manager
|
||
* @param {string} category - Preference category
|
||
* @param {string} key - Preference key
|
||
* @param {*} defaultValue - Default value if preference doesn't exist
|
||
* @returns {*} - Preference value
|
||
*/
|
||
getPreference(category, key, defaultValue) {
|
||
const persistenceManager = this.getModule('persistence-manager');
|
||
return persistenceManager.getPreference(category, key, defaultValue);
|
||
}
|
||
|
||
/**
|
||
* Updates a preference in the 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();
|
||
|
||
// Set up API URL fields
|
||
this.setupApiUrlFields();
|
||
|
||
// Set up initial state
|
||
await this.setupInitialState();
|
||
|
||
// Set up immediate save listeners
|
||
this.setupImmediateSaveListeners();
|
||
|
||
this.reportProgress(100, 'Options UI initialized');
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* Create the options modal
|
||
*/
|
||
createModal() {
|
||
if (this.modal) return;
|
||
|
||
const body = document.body;
|
||
|
||
// Create modal container
|
||
this.modal = createUIElement('div', { className: 'options-modal', id: 'options-modal' }, null, body);
|
||
|
||
// Create modal content
|
||
const modalContent = createUIElement('div', { className: 'options-content' }, null, this.modal);
|
||
|
||
// Create header
|
||
const header = createUIElement('div', { className: 'options-header' }, null, modalContent);
|
||
createUIElement('h2', {}, 'Options', header);
|
||
this.elements.closeButton = createUIElement('button', { className: 'options-close', 'aria-label': 'Close' }, '×', header);
|
||
|
||
// Create settings container
|
||
const settings = createUIElement('div', { className: 'options-settings' }, null, modalContent);
|
||
|
||
// Language Section
|
||
const languageSection = createUIElement('div', { className: 'options-section' }, null, settings);
|
||
createUIElement('h3', {}, 'Language Settings', languageSection);
|
||
|
||
// Language selection
|
||
const languageContainer = createUIElement('div', { className: 'options-row' }, null, languageSection);
|
||
createUIElement('label', {}, 'Language:', languageContainer);
|
||
this.elements.language = createUIElement('select', { id: 'app-language' }, null, languageContainer);
|
||
|
||
// TTS Settings
|
||
const ttsSection = createUIElement('div', { className: 'options-section' }, null, settings);
|
||
createUIElement('h3', {}, 'Text-to-Speech', ttsSection);
|
||
|
||
// TTS Toggle
|
||
const ttsSpeechToggleContainer = createUIElement('div', { className: 'options-row' }, null, ttsSection);
|
||
createUIElement('label', {}, 'Enable Text-to-Speech:', ttsSpeechToggleContainer);
|
||
this.elements.ttsEnabled = createUIElement('input', { type: 'checkbox', id: 'tts-enabled' }, null, ttsSpeechToggleContainer);
|
||
|
||
// TTS System
|
||
const ttsSystemContainer = createUIElement('div', { className: 'options-row' }, null, ttsSection);
|
||
createUIElement('label', {}, 'TTS System:', ttsSystemContainer);
|
||
this.elements.ttsSystem = createUIElement('select', { id: 'tts-system' }, null, ttsSystemContainer);
|
||
|
||
// TTS Voice
|
||
const ttsVoiceContainer = createUIElement('div', { className: 'options-row' }, null, ttsSection);
|
||
createUIElement('label', {}, 'Voice:', ttsVoiceContainer);
|
||
this.elements.ttsVoice = createUIElement('select', { id: 'tts-voice' }, null, ttsVoiceContainer);
|
||
|
||
// TTS Speed
|
||
const speedContainer = createUIElement('div', { className: 'options-row' }, null, ttsSection);
|
||
createUIElement('label', {}, 'TTS Speed:', speedContainer);
|
||
this.elements.ttsSpeed = createUIElement('input', {
|
||
type: 'range',
|
||
id: 'tts-speed',
|
||
min: '0',
|
||
max: '100'
|
||
}, null, speedContainer);
|
||
|
||
// Create API settings for each provider
|
||
const apiSettings = this.createApiSettings(ttsSection);
|
||
|
||
// Audio Settings Section
|
||
const audioSection = createUIElement('div', { className: 'options-section' }, null, settings);
|
||
createUIElement('h3', {}, 'Audio', audioSection);
|
||
|
||
// Master Volume
|
||
const masterVolumeContainer = createUIElement('div', { className: 'options-row' }, null, audioSection);
|
||
createUIElement('label', {}, 'Master Volume:', masterVolumeContainer);
|
||
this.elements.masterVolume = createUIElement('input', {
|
||
type: 'range',
|
||
id: 'master-volume',
|
||
min: '0',
|
||
max: '100'
|
||
}, null, masterVolumeContainer);
|
||
|
||
// Music Volume
|
||
const musicVolumeContainer = createUIElement('div', { className: 'options-row' }, null, audioSection);
|
||
createUIElement('label', {}, 'Music Volume:', musicVolumeContainer);
|
||
this.elements.musicVolume = createUIElement('input', {
|
||
type: 'range',
|
||
id: 'music-volume',
|
||
min: '0',
|
||
max: '100'
|
||
}, null, musicVolumeContainer);
|
||
|
||
// SFX Volume
|
||
const sfxVolumeContainer = createUIElement('div', { className: 'options-row' }, null, audioSection);
|
||
createUIElement('label', {}, 'Sound Effects Volume:', sfxVolumeContainer);
|
||
this.elements.sfxVolume = createUIElement('input', {
|
||
type: 'range',
|
||
id: 'sfx-volume',
|
||
min: '0',
|
||
max: '100'
|
||
}, null, sfxVolumeContainer);
|
||
|
||
// Ambience Volume
|
||
const ambienceVolumeContainer = createUIElement('div', { className: 'options-row' }, null, audioSection);
|
||
createUIElement('label', {}, 'Ambience Volume:', ambienceVolumeContainer);
|
||
this.elements.ambienceVolume = createUIElement('input', {
|
||
type: 'range',
|
||
id: 'ambience-volume',
|
||
min: '0',
|
||
max: '100'
|
||
}, null, ambienceVolumeContainer);
|
||
|
||
// Initialize with display: none
|
||
this.modal.style.display = 'none';
|
||
|
||
// Add event handlers
|
||
this.elements.closeButton.addEventListener('click', () => {
|
||
this.saveCurrentSettings();
|
||
this.hide();
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Create API settings for TTS providers
|
||
* @param {HTMLElement} parentSection - Parent section for API settings
|
||
* @returns {Object} - Object with API settings elements
|
||
*/
|
||
createApiSettings(parentSection) {
|
||
// ElevenLabs settings
|
||
// API Key
|
||
const elevenLabsApiKeyContainer = createUIElement('div', {
|
||
className: 'options-row elevenlabs-tts-setting',
|
||
'data-provider': 'elevenlabs-tts'
|
||
}, null, parentSection);
|
||
|
||
createUIElement('label', {}, 'ElevenLabs API Key:', elevenLabsApiKeyContainer);
|
||
this.elements.elevenLabsApiKey = createUIElement('input', {
|
||
type: 'password',
|
||
placeholder: 'Enter your ElevenLabs API key'
|
||
}, null, elevenLabsApiKeyContainer);
|
||
|
||
// API URL
|
||
const elevenLabsApiUrlContainer = createUIElement('div', {
|
||
className: 'options-row elevenlabs-tts-setting',
|
||
'data-provider': 'elevenlabs-tts'
|
||
}, null, parentSection);
|
||
|
||
createUIElement('label', {}, 'ElevenLabs API URL:', elevenLabsApiUrlContainer);
|
||
this.elements.elevenLabsApiUrl = createUIElement('input', {
|
||
type: 'text',
|
||
placeholder: 'https://api.elevenlabs.io/v1'
|
||
}, null, elevenLabsApiUrlContainer);
|
||
|
||
// OpenAI settings
|
||
// API Key
|
||
const openaiApiKeyContainer = createUIElement('div', {
|
||
className: 'options-row openai-tts-setting',
|
||
'data-provider': 'openai-tts'
|
||
}, null, parentSection);
|
||
|
||
createUIElement('label', {}, 'OpenAI API Key:', openaiApiKeyContainer);
|
||
this.elements.openaiApiKey = createUIElement('input', {
|
||
type: 'password',
|
||
placeholder: 'Enter your OpenAI API key'
|
||
}, null, openaiApiKeyContainer);
|
||
|
||
// API URL
|
||
const openaiApiUrlContainer = createUIElement('div', {
|
||
className: 'options-row openai-tts-setting',
|
||
'data-provider': 'openai-tts'
|
||
}, null, parentSection);
|
||
|
||
createUIElement('label', {}, 'OpenAI API URL:', openaiApiUrlContainer);
|
||
this.elements.openaiApiUrl = createUIElement('input', {
|
||
type: 'text',
|
||
placeholder: 'https://api.openai.com/v1'
|
||
}, null, openaiApiUrlContainer);
|
||
|
||
// Initially hide API settings
|
||
const apiSettings = document.querySelectorAll('.elevenlabs-tts-setting, .openai-tts-setting');
|
||
apiSettings.forEach(setting => {
|
||
setting.style.display = 'none';
|
||
});
|
||
|
||
return { elevenLabsApiKeyContainer, elevenLabsApiUrlContainer, openaiApiKeyContainer, openaiApiUrlContainer };
|
||
}
|
||
|
||
/**
|
||
* Set up event listeners for UI elements
|
||
*/
|
||
setupEventListeners() {
|
||
// TTS System change event
|
||
if (this.elements.ttsSystem) {
|
||
this.elements.ttsSystem.addEventListener('change', this.handleTtsSystemChanged);
|
||
}
|
||
|
||
// TTS Enable toggle event
|
||
if (this.elements.ttsEnabled) {
|
||
this.elements.ttsEnabled.addEventListener('change', (event) => {
|
||
const enabled = event.target.checked;
|
||
console.log('Options UI: TTS enabled changed to', enabled);
|
||
|
||
// Save setting
|
||
this.updatePreference('tts', 'enabled', enabled);
|
||
|
||
// Update TTS Factory
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (ttsFactory) {
|
||
ttsFactory.configure({ enabled });
|
||
}
|
||
});
|
||
}
|
||
|
||
// Voice change event
|
||
if (this.elements.ttsVoice) {
|
||
this.elements.ttsVoice.addEventListener('change', (event) => {
|
||
const voice = event.target.value;
|
||
console.log('Options UI: TTS voice changed to', voice);
|
||
|
||
// Save setting
|
||
this.updatePreference('tts', 'voice', voice);
|
||
|
||
// Update TTS Factory
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (ttsFactory) {
|
||
ttsFactory.configure({ voice });
|
||
}
|
||
});
|
||
}
|
||
|
||
// TTS Speed change event
|
||
if (this.elements.ttsSpeed) {
|
||
this.elements.ttsSpeed.addEventListener('input', (event) => {
|
||
const speed = parseInt(event.target.value) / 100;
|
||
console.log('Options UI: TTS speed changed to', speed);
|
||
|
||
// Save setting
|
||
this.updatePreference('tts', 'speed', speed);
|
||
|
||
// Update TTS Factory
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (ttsFactory) {
|
||
ttsFactory.configure({ speed });
|
||
}
|
||
});
|
||
}
|
||
|
||
// Language change event
|
||
if (this.elements.language) {
|
||
this.elements.language.addEventListener('change', (event) => {
|
||
const locale = event.target.value;
|
||
console.log('Options UI: Language changed to', locale);
|
||
|
||
// Save settings
|
||
this.updatePreference('app', 'locale', locale);
|
||
|
||
// Update Localization module
|
||
const localization = this.getModule('localization');
|
||
if (localization) {
|
||
localization.setLocale(locale);
|
||
}
|
||
|
||
// Show reload notice
|
||
this.showReloadNotice();
|
||
});
|
||
}
|
||
|
||
// Audio Settings
|
||
// Master Volume
|
||
if (this.elements.masterVolume) {
|
||
this.elements.masterVolume.addEventListener('input', (event) => {
|
||
const volume = parseInt(event.target.value) / 100;
|
||
console.log('Options UI: Master volume changed to', volume);
|
||
|
||
// Save setting
|
||
this.updatePreference('audio', 'masterVolume', volume);
|
||
|
||
// Update Audio Manager
|
||
const audioManager = this.getModule('audio-manager');
|
||
if (audioManager) {
|
||
audioManager.setMasterVolume(volume);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Music Volume
|
||
if (this.elements.musicVolume) {
|
||
this.elements.musicVolume.addEventListener('input', (event) => {
|
||
const volume = parseInt(event.target.value) / 100;
|
||
console.log('Options UI: Music volume changed to', volume);
|
||
|
||
// Save setting
|
||
this.updatePreference('audio', 'musicVolume', volume);
|
||
|
||
// Update Audio Manager
|
||
const audioManager = this.getModule('audio-manager');
|
||
if (audioManager) {
|
||
audioManager.setMusicVolume(volume);
|
||
}
|
||
});
|
||
}
|
||
|
||
// SFX Volume
|
||
if (this.elements.sfxVolume) {
|
||
this.elements.sfxVolume.addEventListener('input', (event) => {
|
||
const volume = parseInt(event.target.value) / 100;
|
||
console.log('Options UI: SFX volume changed to', volume);
|
||
|
||
// Save setting
|
||
this.updatePreference('audio', 'sfxVolume', volume);
|
||
|
||
// Update Audio Manager
|
||
const audioManager = this.getModule('audio-manager');
|
||
if (audioManager) {
|
||
audioManager.setSfxVolume(volume);
|
||
}
|
||
});
|
||
}
|
||
|
||
}
|
||
|
||
/**
|
||
* Handle TTS system change
|
||
* @param {Event} event - Change event
|
||
*/
|
||
async handleTtsSystemChanged(event) {
|
||
const selectedSystem = event.target.value;
|
||
console.log('Options UI: TTS system changed to', selectedSystem);
|
||
|
||
// Update API settings visibility
|
||
this.updateApiSettingsVisibility(selectedSystem);
|
||
|
||
// Save setting
|
||
this.updatePreference('tts', 'preferred_handler', selectedSystem);
|
||
|
||
// Notify TTSFactory of handler change
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (ttsFactory) {
|
||
await ttsFactory.setActiveHandler(selectedSystem);
|
||
|
||
// Now that the handler has changed, update voices for the selected system
|
||
await this.populateVoices();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Update API settings visibility based on selected TTS system
|
||
* @param {string} selectedSystem - Selected TTS system
|
||
*/
|
||
updateApiSettingsVisibility(selectedSystem) {
|
||
const elevenLabsSettings = document.querySelectorAll('.elevenlabs-tts-setting');
|
||
const openaiSettings = document.querySelectorAll('.openai-tts-setting');
|
||
|
||
elevenLabsSettings.forEach(setting => {
|
||
setting.style.display = selectedSystem === 'elevenlabs-tts' ? 'flex' : 'none';
|
||
});
|
||
|
||
openaiSettings.forEach(setting => {
|
||
setting.style.display = selectedSystem === 'openai-tts' ? 'flex' : 'none';
|
||
});
|
||
}
|
||
|
||
/**
|
||
* Show the options UI
|
||
*/
|
||
show() {
|
||
if (this.modal) {
|
||
this.modal.style.display = 'flex';
|
||
document.body.classList.add('modal-open');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Hide the options UI
|
||
*/
|
||
hide() {
|
||
if (this.modal) {
|
||
this.modal.style.display = 'none';
|
||
document.body.classList.remove('modal-open');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Toggle the options UI visibility
|
||
*/
|
||
toggle() {
|
||
if (this.modal) {
|
||
if (this.modal.style.display === 'flex') {
|
||
this.hide();
|
||
} else {
|
||
this.show();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Populate the TTS systems dropdown
|
||
*/
|
||
async populateTtsSystems() {
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (!ttsFactory || !this.elements.ttsSystem) return;
|
||
|
||
// Get available TTS systems
|
||
const handlers = ttsFactory.getAvailableHandlers();
|
||
console.log('Options UI: Available TTS handlers:', handlers);
|
||
|
||
// Format for display
|
||
const systems = handlers.map(handler => ({
|
||
id: handler.id,
|
||
name: handler.displayName || handler.id
|
||
}));
|
||
|
||
// Populate dropdown
|
||
populateDropdown(
|
||
this.elements.ttsSystem,
|
||
systems,
|
||
'id',
|
||
'name',
|
||
this.getPreference('tts', 'preferred_handler', 'none')
|
||
);
|
||
|
||
// Update API settings visibility
|
||
this.updateApiSettingsVisibility(this.elements.ttsSystem.value);
|
||
}
|
||
|
||
/**
|
||
* Populate the voices dropdown
|
||
*/
|
||
async populateVoices() {
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (!ttsFactory || !this.elements.ttsVoice) return;
|
||
|
||
// Get voices for current TTS system
|
||
const voices = await ttsFactory.getVoices() || [];
|
||
console.log('Options UI: TTS voices:', voices);
|
||
|
||
// Populate dropdown
|
||
populateDropdown(
|
||
this.elements.ttsVoice,
|
||
voices,
|
||
'id',
|
||
'name',
|
||
this.getPreference('tts', 'voice', '')
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Populate the languages dropdown
|
||
*/
|
||
async populateLanguages() {
|
||
const localization = this.getModule('localization');
|
||
if (!localization || !this.elements.language) return;
|
||
|
||
// Get available languages
|
||
const languages = localization.getAvailableLocales() || [];
|
||
console.log('Options UI: Available languages:', languages);
|
||
|
||
// Format languages with their names
|
||
const languageOptions = languages.map(code => ({
|
||
code,
|
||
name: localization.getLanguageName(code)
|
||
}));
|
||
|
||
// Populate dropdown
|
||
populateDropdown(
|
||
this.elements.language,
|
||
languageOptions,
|
||
'code',
|
||
'name',
|
||
this.getPreference('app', 'locale', 'en-us')
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Load user preferences from the persistence manager
|
||
*/
|
||
loadPreferences() {
|
||
const persistenceManager = this.getModule('persistence-manager');
|
||
if (!persistenceManager) return;
|
||
|
||
console.log('Options UI: Loading preferences');
|
||
|
||
// TTS Settings
|
||
// TTS Enable
|
||
if (this.elements.ttsEnabled) {
|
||
this.elements.ttsEnabled.checked = this.getPreference('tts', 'enabled', true);
|
||
}
|
||
|
||
// TTS System
|
||
if (this.elements.ttsSystem) {
|
||
const preferredHandler = this.getPreference('tts', 'preferred_handler', 'none');
|
||
if (this.elements.ttsSystem.querySelector(`option[value="${preferredHandler}"]`)) {
|
||
this.elements.ttsSystem.value = preferredHandler;
|
||
}
|
||
}
|
||
|
||
// TTS Speed
|
||
if (this.elements.ttsSpeed) {
|
||
const speed = this.getPreference('tts', 'speed', 1);
|
||
this.elements.ttsSpeed.value = Math.round(speed * 100);
|
||
}
|
||
|
||
// API Keys and URLs
|
||
// ElevenLabs API Key
|
||
if (this.elements.elevenLabsApiKey) {
|
||
this.elements.elevenLabsApiKey.value = this.getPreference('tts', 'elevenlabs-tts_api_key', '');
|
||
}
|
||
|
||
// ElevenLabs API URL
|
||
if (this.elements.elevenLabsApiUrl) {
|
||
this.elements.elevenLabsApiUrl.value = this.getPreference('tts', 'elevenlabs-tts_api_url', 'https://api.elevenlabs.io/v1');
|
||
}
|
||
|
||
// OpenAI API Key
|
||
if (this.elements.openaiApiKey) {
|
||
this.elements.openaiApiKey.value = this.getPreference('tts', 'openai-tts_api_key', '');
|
||
}
|
||
|
||
// OpenAI API URL
|
||
if (this.elements.openaiApiUrl) {
|
||
this.elements.openaiApiUrl.value = this.getPreference('tts', 'openai-tts_api_url', 'https://api.openai.com/v1');
|
||
}
|
||
|
||
// Audio Settings
|
||
// Master Volume
|
||
if (this.elements.masterVolume) {
|
||
const masterVolume = this.getPreference('audio', 'masterVolume', 1);
|
||
this.elements.masterVolume.value = Math.round(masterVolume * 100);
|
||
}
|
||
|
||
// Music Volume
|
||
if (this.elements.musicVolume) {
|
||
const musicVolume = this.getPreference('audio', 'musicVolume', 1);
|
||
this.elements.musicVolume.value = Math.round(musicVolume * 100);
|
||
}
|
||
|
||
// SFX Volume
|
||
if (this.elements.sfxVolume) {
|
||
const sfxVolume = this.getPreference('audio', 'sfxVolume', 1);
|
||
this.elements.sfxVolume.value = Math.round(sfxVolume * 100);
|
||
}
|
||
|
||
// Ambience Volume
|
||
if (this.elements.ambienceVolume) {
|
||
const ambienceVolume = this.getPreference('audio', 'ambienceVolume', 1);
|
||
this.elements.ambienceVolume.value = Math.round(ambienceVolume * 100);
|
||
}
|
||
|
||
// Language
|
||
if (this.elements.language) {
|
||
const locale = this.getPreference('app', 'locale', 'en');
|
||
if (this.elements.language.querySelector(`option[value="${locale}"]`)) {
|
||
this.elements.language.value = locale;
|
||
}
|
||
}
|
||
|
||
// Update API settings visibility
|
||
if (this.elements.ttsSystem) {
|
||
this.updateApiSettingsVisibility(this.elements.ttsSystem.value);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set up two-way binding for TTS Enabled
|
||
* @param {HTMLElement} element - UI element
|
||
* @param {Object} persistenceManager - Persistence Manager module
|
||
* @param {string} category - Preference category
|
||
* @param {string} key - Preference key
|
||
* @param {*} defaultValue - Default value if preference doesn't exist
|
||
* @param {Function} [transform] - Optional transform function
|
||
*/
|
||
setupTtsEnabledBinding(element, persistenceManager, category, key, defaultValue, transform) {
|
||
createPreferenceBinding(
|
||
element,
|
||
persistenceManager,
|
||
category,
|
||
key,
|
||
defaultValue,
|
||
transform
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Set up two-way binding for TTS Voice
|
||
* @param {HTMLElement} element - UI element
|
||
* @param {Object} persistenceManager - Persistence Manager module
|
||
* @param {string} category - Preference category
|
||
* @param {string} key - Preference key
|
||
* @param {*} defaultValue - Default value if preference doesn't exist
|
||
* @param {Function} [transform] - Optional transform function
|
||
*/
|
||
setupTtsVoiceBinding(element, persistenceManager, category, key, defaultValue, transform) {
|
||
createPreferenceBinding(
|
||
element,
|
||
persistenceManager,
|
||
category,
|
||
key,
|
||
defaultValue,
|
||
transform
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Set up two-way binding for App Language
|
||
* @param {HTMLElement} element - UI element
|
||
* @param {Object} persistenceManager - Persistence Manager module
|
||
* @param {string} category - Preference category
|
||
* @param {string} key - Preference key
|
||
* @param {*} defaultValue - Default value if preference doesn't exist
|
||
* @param {Function} [transform] - Optional transform function
|
||
*/
|
||
setupLanguageBinding(element, persistenceManager, category, key, defaultValue, transform) {
|
||
createPreferenceBinding(
|
||
element,
|
||
persistenceManager,
|
||
category,
|
||
key,
|
||
defaultValue,
|
||
transform
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Set up two-way binding for API settings
|
||
* @param {Object} persistenceManager - Persistence Manager module
|
||
*/
|
||
setupApiPreferenceBindings(persistenceManager) {
|
||
// ElevenLabs API Key
|
||
createPreferenceBinding(
|
||
this.elements.elevenLabsApiKey,
|
||
persistenceManager,
|
||
'tts',
|
||
'elevenlabs-tts_api_key',
|
||
null,
|
||
(value) => {
|
||
this.dispatchApiChangeEvent('api:keyChanged', 'elevenlabs-tts', 'key', value);
|
||
return value;
|
||
}
|
||
);
|
||
|
||
// ElevenLabs API URL
|
||
createPreferenceBinding(
|
||
this.elements.elevenLabsApiUrl,
|
||
persistenceManager,
|
||
'tts',
|
||
'elevenlabs-tts_api_url',
|
||
null,
|
||
(value) => {
|
||
this.dispatchApiChangeEvent('api:urlChanged', 'elevenlabs-tts', 'url', value);
|
||
return value;
|
||
}
|
||
);
|
||
|
||
// OpenAI API Key
|
||
createPreferenceBinding(
|
||
this.elements.openaiApiKey,
|
||
persistenceManager,
|
||
'tts',
|
||
'openai-tts_api_key',
|
||
null,
|
||
(value) => {
|
||
this.dispatchApiChangeEvent('api:keyChanged', 'openai-tts', 'key', value);
|
||
return value;
|
||
}
|
||
);
|
||
|
||
// OpenAI API URL
|
||
createPreferenceBinding(
|
||
this.elements.openaiApiUrl,
|
||
persistenceManager,
|
||
'tts',
|
||
'openai-tts_api_url',
|
||
null,
|
||
(value) => {
|
||
this.dispatchApiChangeEvent('api:urlChanged', 'openai-tts', 'url', value);
|
||
return value;
|
||
}
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Save current settings
|
||
*/
|
||
saveCurrentSettings() {
|
||
// With two-way binding, settings are saved automatically as they change
|
||
console.log('Options UI: Settings saved');
|
||
}
|
||
|
||
/**
|
||
* Apply settings
|
||
*/
|
||
applySettings() {
|
||
const ttsFactory = this.getModule('tts-factory');
|
||
if (ttsFactory) {
|
||
// Apply TTS settings
|
||
const enabled = this.getPreference('tts', 'enabled', false);
|
||
const preferredHandler = this.getPreference('tts', 'preferred_handler', 'none');
|
||
|
||
ttsFactory.configure({ enabled });
|
||
ttsFactory.setActiveHandler(preferredHandler);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Show a reload notice
|
||
* @param {string} message - Message to show
|
||
*/
|
||
showReloadNotice(message) {
|
||
console.log('Options UI: Reload required -', message);
|
||
this.reloadRequired = true;
|
||
}
|
||
|
||
/**
|
||
* Set up listeners for settings that should save immediately
|
||
*/
|
||
setupImmediateSaveListeners() {
|
||
// Settings are saved immediately with two-way binding
|
||
}
|
||
|
||
/**
|
||
* Update UI text based on current language
|
||
*/
|
||
updateUIText() {
|
||
// Update UI text based on current language
|
||
const localization = this.getModule('localization');
|
||
if (!localization) return;
|
||
|
||
// Update modal title
|
||
const modalTitle = this.modal.querySelector('h2');
|
||
if (modalTitle) {
|
||
modalTitle.textContent = localization.translate('options.title', 'Options');
|
||
}
|
||
|
||
// Update section titles
|
||
const ttsSectionTitle = this.modal.querySelector('.options-section h3:first-child');
|
||
if (ttsSectionTitle) {
|
||
ttsSectionTitle.textContent = localization.translate('options.tts.title', 'Text-to-Speech');
|
||
}
|
||
|
||
const langSectionTitle = this.modal.querySelector('.options-section:nth-child(2) h3');
|
||
if (langSectionTitle) {
|
||
langSectionTitle.textContent = localization.translate('options.language.title', 'Language Settings');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Set up API URL fields with default values
|
||
*/
|
||
setupApiUrlFields() {
|
||
// Set up ElevenLabs API URL
|
||
if (this.elements.elevenLabsApiUrl) {
|
||
const savedUrl = this.getPreference('tts', 'elevenlabs-tts_api_url');
|
||
const defaultUrl = 'https://api.elevenlabs.io/v1';
|
||
|
||
// If no saved URL, set the default
|
||
if (!savedUrl) {
|
||
console.log('Options UI: Setting default ElevenLabs API URL:', defaultUrl);
|
||
this.updatePreference('tts', 'elevenlabs-tts_api_url', defaultUrl);
|
||
}
|
||
}
|
||
|
||
// Set up OpenAI API URL
|
||
if (this.elements.openaiApiUrl) {
|
||
const savedUrl = this.getPreference('tts', 'openai-tts_api_url');
|
||
const defaultUrl = 'https://api.openai.com/v1';
|
||
|
||
// If no saved URL, set the default
|
||
if (!savedUrl) {
|
||
console.log('Options UI: Setting default OpenAI API URL:', defaultUrl);
|
||
this.updatePreference('tts', 'openai-tts_api_url', defaultUrl);
|
||
}
|
||
}
|
||
|
||
// Make sure API keys are initialized if not already set
|
||
if (!this.getPreference('tts', 'elevenlabs-tts_api_key')) {
|
||
this.updatePreference('tts', 'elevenlabs-tts_api_key', '');
|
||
}
|
||
|
||
if (!this.getPreference('tts', 'openai-tts_api_key')) {
|
||
this.updatePreference('tts', 'openai-tts_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();
|
||
this.updateApiSettingsVisibility(this.elements.ttsSystem.value);
|
||
});
|
||
|
||
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 };
|