848 lines
31 KiB
JavaScript
848 lines
31 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'
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Initialize the options UI
|
|
* @returns {boolean} - True if initialization was successful
|
|
*/
|
|
initialize() {
|
|
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
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 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 = '×';
|
|
closeButton.addEventListener('click', () => 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);
|
|
|
|
// Debug log for troubleshooting
|
|
console.log('Options UI: Populating TTS systems');
|
|
|
|
// Get available handlers
|
|
const handlers = ttsFactory.getAvailableHandlers();
|
|
console.log('Options UI: Available handlers:', handlers);
|
|
|
|
// Add all registered handlers
|
|
for (const id in handlers) {
|
|
// Always add the handler, even if not initialized yet
|
|
console.log(`Options UI: Adding TTS option for ${id}`);
|
|
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) {
|
|
console.log('Options UI: No TTS systems available, adding disabled option');
|
|
const option = document.createElement('option');
|
|
option.value = '';
|
|
option.textContent = 'No TTS systems available';
|
|
option.disabled = true;
|
|
this.elements.ttsSystem.appendChild(option);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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) return;
|
|
|
|
const ttsFactory = this.getModule('tts-factory');
|
|
const localization = this.getModule('localization');
|
|
if (!ttsFactory || !localization) return;
|
|
|
|
// Clear existing options
|
|
this.elements.ttsVoice.innerHTML = '';
|
|
|
|
// Get current locale
|
|
const currentLocale = localization.getLocale();
|
|
const languageCode = currentLocale.split('-')[0].toLowerCase();
|
|
|
|
// Get voices from active handler
|
|
const allVoices = ttsFactory.getVoices();
|
|
|
|
// Filter voices by current locale
|
|
const filteredVoices = allVoices.filter(voice => {
|
|
if (!voice.lang) return true; // Include voices without language info
|
|
const voiceLang = voice.lang.toLowerCase();
|
|
return voiceLang.startsWith(languageCode) || languageCode.startsWith(voiceLang.split('-')[0]);
|
|
});
|
|
|
|
if (filteredVoices && filteredVoices.length > 0) {
|
|
// Add options for each voice
|
|
filteredVoices.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);
|
|
});
|
|
} else {
|
|
// No voices available for current locale
|
|
const option = document.createElement('option');
|
|
option.value = '';
|
|
option.textContent = `No voices available for ${currentLocale}`;
|
|
option.disabled = true;
|
|
this.elements.ttsVoice.appendChild(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;
|
|
}
|
|
|
|
setupEventListeners() {
|
|
// Listen for language change events
|
|
document.addEventListener('localization:languageChanged', () => {
|
|
// Update the language selection in options panel
|
|
const localization = this.getModule('localization');
|
|
if (localization && this.elements && this.elements.language) {
|
|
const currentLocale = localization.getLocale();
|
|
if (currentLocale && this.elements.language.value !== currentLocale) {
|
|
this.elements.language.value = currentLocale;
|
|
}
|
|
}
|
|
|
|
// Re-populate TTS voices for new language
|
|
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 }; |