Files
ai.interactive.fiction/public/js/options-ui.js
T

947 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Options UI Module for AI Interactive Fiction
* Provides a user interface for adjusting game settings, TTS options, etc.
*/
import { BaseModule } from './base-module.js';
import { moduleRegistry } from './module-registry.js';
class OptionsUIModule extends BaseModule {
/**
* Create new options UI
*/
constructor() {
super('options-ui', 'Options UI');
this.persistenceManager = null;
this.ttsPlayer = null;
this.audioManager = null;
this.ttsFactory = null;
this.modal = null;
this.isOpen = false;
// Configuration
this.config = {
modalClass: 'options-modal',
modalContentClass: 'options-content',
backdrop: true
};
// Bound event handlers for proper this context
this.handleTtsSystemChanged = this.handleTtsSystemChanged.bind(this);
}
/**
* Initialize the module
* @returns {Promise<boolean>} - Resolves with success status
*/
async initialize() {
try {
// Set up event listeners
window.addEventListener('tts-system-changed', this.handleTtsSystemChanged);
// The option modal will be created on demand
this.reportProgress(100, "Options UI ready");
return true;
} catch (error) {
console.error("Error initializing options UI:", error);
return false;
}
}
/**
* Handle TTS system changes
* @param {CustomEvent} event - The event containing TTS system change details
*/
handleTtsSystemChanged(event) {
console.log("TTS system changed:", event.detail);
if (this.isOpen) {
// Refresh the voices list if the options UI is currently open
this.populateVoices();
}
}
/**
* Wait for dependencies to be ready
* @returns {Promise<boolean>} - Resolves when dependencies are ready
*/
async waitForDependencies() {
try {
// Wait for the persistence manager if available
this.persistenceManager = moduleRegistry.getModule('persistence-manager');
this.ttsPlayer = moduleRegistry.getModule('tts');
// These dependencies are optional - UI will adapt if not available
this.audioManager = moduleRegistry.getModule('audio-manager');
return true;
} catch (error) {
console.error("Error waiting for options UI dependencies:", error);
return true; // Non-critical, can continue
}
}
/**
* Create the options UI elements
*/
createModal() {
if (this.modal) return;
// Create modal container
this.modal = document.createElement('div');
this.modal.className = this.config.modalClass;
this.modal.style.display = 'none';
// Create backdrop if enabled
if (this.config.backdrop) {
this.backdrop = document.createElement('div');
this.backdrop.className = 'modal-backdrop';
this.backdrop.addEventListener('click', () => this.hide());
this.modal.appendChild(this.backdrop);
}
// Create content container
const content = document.createElement('div');
content.className = this.config.modalContentClass;
// Add header with title and close button
const header = document.createElement('div');
header.className = 'options-header';
const title = document.createElement('h2');
title.textContent = 'Options';
header.appendChild(title);
const closeBtn = document.createElement('button');
closeBtn.className = 'close-button';
closeBtn.textContent = '×';
closeBtn.setAttribute('aria-label', 'Close options');
closeBtn.addEventListener('click', () => this.hide());
header.appendChild(closeBtn);
content.appendChild(header);
// Create tabs
const tabContainer = document.createElement('div');
tabContainer.className = 'tabs-container';
const tabs = document.createElement('div');
tabs.className = 'tabs';
const tabGeneral = document.createElement('button');
tabGeneral.className = 'tab active';
tabGeneral.textContent = 'General';
tabGeneral.dataset.tab = 'general';
const tabVoice = document.createElement('button');
tabVoice.className = 'tab';
tabVoice.textContent = 'Voice';
tabVoice.dataset.tab = 'voice';
const tabAudio = document.createElement('button');
tabAudio.className = 'tab';
tabAudio.textContent = 'Audio';
tabAudio.dataset.tab = 'audio';
const tabAccess = document.createElement('button');
tabAccess.className = 'tab';
tabAccess.textContent = 'Accessibility';
tabAccess.dataset.tab = 'accessibility';
tabs.appendChild(tabGeneral);
tabs.appendChild(tabVoice);
tabs.appendChild(tabAudio);
tabs.appendChild(tabAccess);
tabContainer.appendChild(tabs);
content.appendChild(tabContainer);
// Create tab content sections
const tabContent = document.createElement('div');
tabContent.className = 'tab-content';
// General tab content
const generalContent = document.createElement('div');
generalContent.className = 'tab-pane active';
generalContent.dataset.tab = 'general';
const animSpeedSection = document.createElement('div');
animSpeedSection.className = 'option-section';
const animSpeedLabel = document.createElement('label');
animSpeedLabel.textContent = 'Animation Speed';
animSpeedLabel.htmlFor = 'option-anim-speed';
const animSpeedSlider = document.createElement('input');
animSpeedSlider.type = 'range';
animSpeedSlider.id = 'option-anim-speed';
animSpeedSlider.min = '0';
animSpeedSlider.max = '100';
animSpeedSlider.value = '50'; // Will be updated from preferences
const animSpeedValue = document.createElement('span');
animSpeedValue.className = 'range-value';
animSpeedValue.textContent = '50%';
animSpeedSlider.addEventListener('input', () => {
const val = animSpeedSlider.value;
animSpeedValue.textContent = `${val}%`;
if (this.persistenceManager) {
this.persistenceManager.updatePreference('animation', 'speed', parseInt(val, 10));
}
// Update animation queue speed if available
const animQueue = moduleRegistry.getModule('animation-queue');
if (animQueue) {
const speed = Math.pow(100.0 - val, 3) / 10000 * 10 + 0.01;
animQueue.setSpeed(speed);
}
});
animSpeedSection.appendChild(animSpeedLabel);
animSpeedSection.appendChild(animSpeedSlider);
animSpeedSection.appendChild(animSpeedValue);
generalContent.appendChild(animSpeedSection);
// Voice tab content
const voiceContent = document.createElement('div');
voiceContent.className = 'tab-pane';
voiceContent.dataset.tab = 'voice';
const ttsSysSection = document.createElement('div');
ttsSysSection.className = 'option-section';
const ttsSysLabel = document.createElement('label');
ttsSysLabel.textContent = 'TTS System';
ttsSysLabel.htmlFor = 'option-tts-system';
const ttsSysSelect = document.createElement('select');
ttsSysSelect.id = 'option-tts-system';
// Will populate systems dynamically later
ttsSysSection.appendChild(ttsSysLabel);
ttsSysSection.appendChild(ttsSysSelect);
voiceContent.appendChild(ttsSysSection);
// Voice selection section
const voiceSection = document.createElement('div');
voiceSection.className = 'option-section';
const voiceLabel = document.createElement('label');
voiceLabel.textContent = 'Voice';
voiceLabel.htmlFor = 'option-voice';
const voiceSelect = document.createElement('select');
voiceSelect.id = 'option-voice';
// Will populate voices dynamically later
voiceSection.appendChild(voiceLabel);
voiceSection.appendChild(voiceSelect);
voiceContent.appendChild(voiceSection);
// Voice rate section
const rateSection = document.createElement('div');
rateSection.className = 'option-section';
const rateLabel = document.createElement('label');
rateLabel.textContent = 'Speech Rate';
rateLabel.htmlFor = 'option-speech-rate';
const rateSlider = document.createElement('input');
rateSlider.type = 'range';
rateSlider.id = 'option-speech-rate';
rateSlider.min = '50';
rateSlider.max = '200';
rateSlider.value = '100'; // Will be updated from preferences
const rateValue = document.createElement('span');
rateValue.className = 'range-value';
rateValue.textContent = '1.0x';
rateSlider.addEventListener('input', () => {
const val = rateSlider.value;
const rate = val / 100;
rateValue.textContent = `${rate.toFixed(1)}x`;
if (this.ttsPlayer) {
this.ttsPlayer.setSpeed(rate);
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('tts', 'rate', rate);
}
});
rateSection.appendChild(rateLabel);
rateSection.appendChild(rateSlider);
rateSection.appendChild(rateValue);
voiceContent.appendChild(rateSection);
// Audio tab content
const audioContent = document.createElement('div');
audioContent.className = 'tab-pane';
audioContent.dataset.tab = 'audio';
// Master volume section
const masterVolSection = document.createElement('div');
masterVolSection.className = 'option-section';
const masterVolLabel = document.createElement('label');
masterVolLabel.textContent = 'Master Volume';
masterVolLabel.htmlFor = 'option-master-vol';
const masterVolSlider = document.createElement('input');
masterVolSlider.type = 'range';
masterVolSlider.id = 'option-master-vol';
masterVolSlider.min = '0';
masterVolSlider.max = '100';
masterVolSlider.value = '100'; // Will be updated from preferences
const masterVolValue = document.createElement('span');
masterVolValue.className = 'range-value';
masterVolValue.textContent = '100%';
masterVolSlider.addEventListener('input', () => {
const val = masterVolSlider.value;
masterVolValue.textContent = `${val}%`;
if (this.audioManager) {
this.audioManager.setMasterVolume(val / 100);
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('audio', 'masterVolume', val / 100);
}
});
masterVolSection.appendChild(masterVolLabel);
masterVolSection.appendChild(masterVolSlider);
masterVolSection.appendChild(masterVolValue);
audioContent.appendChild(masterVolSection);
// TTS volume section
const ttsVolSection = document.createElement('div');
ttsVolSection.className = 'option-section';
const ttsVolLabel = document.createElement('label');
ttsVolLabel.textContent = 'Speech Volume';
ttsVolLabel.htmlFor = 'option-tts-vol';
const ttsVolSlider = document.createElement('input');
ttsVolSlider.type = 'range';
ttsVolSlider.id = 'option-tts-vol';
ttsVolSlider.min = '0';
ttsVolSlider.max = '100';
ttsVolSlider.value = '100'; // Will be updated from preferences
const ttsVolValue = document.createElement('span');
ttsVolValue.className = 'range-value';
ttsVolValue.textContent = '100%';
ttsVolSlider.addEventListener('input', () => {
const val = ttsVolSlider.value;
ttsVolValue.textContent = `${val}%`;
if (this.ttsPlayer) {
this.ttsPlayer.setVolume(val / 100);
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('tts', 'volume', val / 100);
}
});
ttsVolSection.appendChild(ttsVolLabel);
ttsVolSection.appendChild(ttsVolSlider);
ttsVolSection.appendChild(ttsVolValue);
audioContent.appendChild(ttsVolSection);
// Music volume section (for future use)
const musicVolSection = document.createElement('div');
musicVolSection.className = 'option-section';
const musicVolLabel = document.createElement('label');
musicVolLabel.textContent = 'Music Volume';
musicVolLabel.htmlFor = 'option-music-vol';
const musicVolSlider = document.createElement('input');
musicVolSlider.type = 'range';
musicVolSlider.id = 'option-music-vol';
musicVolSlider.min = '0';
musicVolSlider.max = '100';
musicVolSlider.value = '70'; // Will be updated from preferences
const musicVolValue = document.createElement('span');
musicVolValue.className = 'range-value';
musicVolValue.textContent = '70%';
musicVolSlider.addEventListener('input', () => {
const val = musicVolSlider.value;
musicVolValue.textContent = `${val}%`;
if (this.audioManager) {
this.audioManager.setMusicVolume(val / 100);
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('audio', 'musicVolume', val / 100);
}
});
musicVolSection.appendChild(musicVolLabel);
musicVolSection.appendChild(musicVolSlider);
musicVolSection.appendChild(musicVolValue);
audioContent.appendChild(musicVolSection);
// SFX volume section (for future use)
const sfxVolSection = document.createElement('div');
sfxVolSection.className = 'option-section';
const sfxVolLabel = document.createElement('label');
sfxVolLabel.textContent = 'Effects Volume';
sfxVolLabel.htmlFor = 'option-sfx-vol';
const sfxVolSlider = document.createElement('input');
sfxVolSlider.type = 'range';
sfxVolSlider.id = 'option-sfx-vol';
sfxVolSlider.min = '0';
sfxVolSlider.max = '100';
sfxVolSlider.value = '100'; // Will be updated from preferences
const sfxVolValue = document.createElement('span');
sfxVolValue.className = 'range-value';
sfxVolValue.textContent = '100%';
sfxVolSlider.addEventListener('input', () => {
const val = sfxVolSlider.value;
sfxVolValue.textContent = `${val}%`;
if (this.audioManager) {
this.audioManager.setSfxVolume(val / 100);
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('audio', 'sfxVolume', val / 100);
}
});
sfxVolSection.appendChild(sfxVolLabel);
sfxVolSection.appendChild(sfxVolSlider);
sfxVolSection.appendChild(sfxVolValue);
audioContent.appendChild(sfxVolSection);
// Accessibility tab content
const accessContent = document.createElement('div');
accessContent.className = 'tab-pane';
accessContent.dataset.tab = 'accessibility';
// High contrast toggle
const contrastSection = document.createElement('div');
contrastSection.className = 'option-section checkbox-section';
const contrastCheckbox = document.createElement('input');
contrastCheckbox.type = 'checkbox';
contrastCheckbox.id = 'option-high-contrast';
const contrastLabel = document.createElement('label');
contrastLabel.textContent = 'High Contrast Mode';
contrastLabel.htmlFor = 'option-high-contrast';
contrastCheckbox.addEventListener('change', () => {
const isEnabled = contrastCheckbox.checked;
// Apply high contrast class to body
if (isEnabled) {
document.body.classList.add('high-contrast');
} else {
document.body.classList.remove('high-contrast');
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('accessibility', 'highContrast', isEnabled);
}
});
contrastSection.appendChild(contrastCheckbox);
contrastSection.appendChild(contrastLabel);
accessContent.appendChild(contrastSection);
// Larger text toggle
const largerTextSection = document.createElement('div');
largerTextSection.className = 'option-section checkbox-section';
const largerTextCheckbox = document.createElement('input');
largerTextCheckbox.type = 'checkbox';
largerTextCheckbox.id = 'option-larger-text';
const largerTextLabel = document.createElement('label');
largerTextLabel.textContent = 'Larger Text';
largerTextLabel.htmlFor = 'option-larger-text';
largerTextCheckbox.addEventListener('change', () => {
const isEnabled = largerTextCheckbox.checked;
// Apply larger text class to body
if (isEnabled) {
document.body.classList.add('larger-text');
} else {
document.body.classList.remove('larger-text');
}
if (this.persistenceManager) {
this.persistenceManager.updatePreference('accessibility', 'largerText', isEnabled);
}
});
largerTextSection.appendChild(largerTextCheckbox);
largerTextSection.appendChild(largerTextLabel);
accessContent.appendChild(largerTextSection);
// Add tab content to container
tabContent.appendChild(generalContent);
tabContent.appendChild(voiceContent);
tabContent.appendChild(audioContent);
tabContent.appendChild(accessContent);
content.appendChild(tabContent);
// Add buttons at the bottom
const buttons = document.createElement('div');
buttons.className = 'options-buttons';
const resetButton = document.createElement('button');
resetButton.textContent = 'Reset to Defaults';
resetButton.className = 'reset-button';
resetButton.addEventListener('click', () => this.resetToDefaults());
const saveButton = document.createElement('button');
saveButton.textContent = 'Save & Close';
saveButton.className = 'save-button';
saveButton.addEventListener('click', () => this.saveAndClose());
buttons.appendChild(resetButton);
buttons.appendChild(saveButton);
content.appendChild(buttons);
// Set up tab switching
tabs.addEventListener('click', (e) => {
if (e.target.classList.contains('tab')) {
// Deactivate all tabs and tab panes
Array.from(tabs.querySelectorAll('.tab')).forEach(tab => {
tab.classList.remove('active');
});
Array.from(tabContent.querySelectorAll('.tab-pane')).forEach(pane => {
pane.classList.remove('active');
});
// Activate clicked tab and corresponding pane
e.target.classList.add('active');
const tabName = e.target.dataset.tab;
const pane = tabContent.querySelector(`.tab-pane[data-tab="${tabName}"]`);
if (pane) {
pane.classList.add('active');
}
// If switching to voice tab, ensure voices are updated
if (tabName === 'voice') {
this.populateTtsSystems();
this.populateVoices();
}
}
});
this.modal.appendChild(content);
document.body.appendChild(this.modal);
// Store references to UI elements for later use
this.elements = {
animSpeed: animSpeedSlider,
animSpeedValue: animSpeedValue,
ttsSystem: ttsSysSelect,
voiceSelect: voiceSelect,
speechRate: rateSlider,
speechRateValue: rateValue,
masterVolume: masterVolSlider,
masterVolumeValue: masterVolValue,
ttsVolume: ttsVolSlider,
ttsVolumeValue: ttsVolValue,
musicVolume: musicVolSlider,
musicVolumeValue: musicVolValue,
sfxVolume: sfxVolSlider,
sfxVolumeValue: sfxVolValue,
highContrast: contrastCheckbox,
largerText: largerTextCheckbox
};
}
/**
* Show the options UI
*/
show() {
if (!this.modal) {
this.createModal();
}
// Load current preferences
this.loadPreferences();
// Populate TTS systems and voices
this.populateTtsSystems();
this.populateVoices();
// Show the modal
this.modal.style.display = 'flex';
this.isOpen = true;
}
/**
* Hide the options UI
*/
hide() {
if (this.modal) {
this.modal.style.display = 'none';
this.isOpen = false;
}
}
/**
* Toggle the options UI visibility
*/
toggle() {
if (this.isOpen) {
this.hide();
} else {
this.show();
}
}
/**
* Load current preferences into UI
*/
loadPreferences() {
if (!this.persistenceManager || !this.elements) return;
const prefs = this.persistenceManager.getAllPreferences();
// Animation speed
const animSpeed = this.persistenceManager.getPreference('animation', 'speed', 50);
this.elements.animSpeed.value = animSpeed;
this.elements.animSpeedValue.textContent = `${animSpeed}%`;
// TTS settings
const ttsEnabled = this.persistenceManager.getPreference('tts', 'enabled', false);
const ttsProvider = this.persistenceManager.getPreference('tts', 'provider', 'browser');
const ttsVoice = this.persistenceManager.getPreference('tts', 'voice', '');
const ttsVolume = this.persistenceManager.getPreference('tts', 'volume', 1.0);
const ttsRate = this.persistenceManager.getPreference('tts', 'rate', 1.0);
// TTS rate slider
this.elements.speechRate.value = Math.round(ttsRate * 100);
this.elements.speechRateValue.textContent = `${ttsRate.toFixed(1)}x`;
// TTS volume slider
this.elements.ttsVolume.value = Math.round(ttsVolume * 100);
this.elements.ttsVolumeValue.textContent = `${Math.round(ttsVolume * 100)}%`;
// Audio volumes
const masterVolume = this.persistenceManager.getPreference('audio', 'masterVolume', 1.0);
const musicVolume = this.persistenceManager.getPreference('audio', 'musicVolume', 0.7);
const sfxVolume = this.persistenceManager.getPreference('audio', 'sfxVolume', 1.0);
this.elements.masterVolume.value = Math.round(masterVolume * 100);
this.elements.masterVolumeValue.textContent = `${Math.round(masterVolume * 100)}%`;
this.elements.musicVolume.value = Math.round(musicVolume * 100);
this.elements.musicVolumeValue.textContent = `${Math.round(musicVolume * 100)}%`;
this.elements.sfxVolume.value = Math.round(sfxVolume * 100);
this.elements.sfxVolumeValue.textContent = `${Math.round(sfxVolume * 100)}%`;
// Accessibility settings
const highContrast = this.persistenceManager.getPreference('accessibility', 'highContrast', false);
const largerText = this.persistenceManager.getPreference('accessibility', 'largerText', false);
this.elements.highContrast.checked = highContrast;
this.elements.largerText.checked = largerText;
}
/**
* Populate TTS systems dropdown
*/
populateTtsSystems() {
if (!this.ttsPlayer || !this.elements) return;
const systems = this.ttsPlayer.getAvailableSystems();
const select = this.elements.ttsSystem;
// Clear existing options and listeners
select.innerHTML = '';
const newSelect = select.cloneNode(false);
select.parentNode.replaceChild(newSelect, select);
this.elements.ttsSystem = newSelect;
select = newSelect;
// Get current TTS info
const currentInfo = this.ttsPlayer.getTTSInfo();
const currentId = currentInfo.type || '';
// Create an option for each available system
systems.forEach(id => {
const option = document.createElement('option');
option.value = id;
switch (id) {
case 'browser':
option.textContent = 'Browser Built-in TTS';
break;
case 'kokoro':
option.textContent = 'Kokoro Neural TTS';
break;
case 'api':
option.textContent = 'API-based TTS';
break;
default:
option.textContent = id.charAt(0).toUpperCase() + id.slice(1);
}
if (id === currentId) {
option.selected = true;
}
select.appendChild(option);
});
// Add change listener
select.addEventListener('change', () => {
const selectedSystem = select.value;
if (this.ttsPlayer) {
this.ttsPlayer.switchTTS(selectedSystem);
// Update persistence
if (this.persistenceManager) {
this.persistenceManager.updatePreference('tts', 'provider', selectedSystem);
}
}
});
}
/**
* Populate voices dropdown for current TTS system
*/
async populateVoices() {
if (!this.ttsPlayer || !this.elements || !this.ttsPlayer.getVoices) return;
try {
const voices = await this.ttsPlayer.getVoices();
const select = this.elements.voiceSelect;
// Clear existing options and listeners
select.innerHTML = '';
const newSelect = select.cloneNode(false);
select.parentNode.replaceChild(newSelect, select);
this.elements.voiceSelect = newSelect;
select = newSelect;
if (!voices || voices.length === 0) {
const option = document.createElement('option');
option.value = '';
option.textContent = 'No voices available';
select.appendChild(option);
select.disabled = true;
return;
}
select.disabled = false;
// Get current preference
let currentVoice = '';
if (this.persistenceManager) {
currentVoice = this.persistenceManager.getPreference('tts', 'voice', '');
}
// Add voices to dropdown
voices.forEach(voice => {
const option = document.createElement('option');
option.value = voice.id || voice.name;
option.textContent = voice.name;
if (voice.id === currentVoice || voice.name === currentVoice) {
option.selected = true;
}
select.appendChild(option);
});
// Add change listener
select.addEventListener('change', () => {
const selectedVoice = select.value;
// Update TTS
if (this.ttsPlayer) {
this.ttsPlayer.setVoice(selectedVoice);
}
// Update persistence
if (this.persistenceManager) {
this.persistenceManager.updatePreference('tts', 'voice', selectedVoice);
}
});
console.log(`Voices populated for current TTS system. Selected: ${select.value}`);
} catch (error) {
console.error("Error populating voices:", error);
const select = this.elements.voiceSelect;
select.innerHTML = '';
const option = document.createElement('option');
option.value = '';
option.textContent = 'Error loading voices';
select.appendChild(option);
select.disabled = true;
}
}
/**
* Reset all options to defaults
*/
resetToDefaults() {
if (!this.persistenceManager) return;
const confirmed = confirm('Reset all options to default values?');
if (confirmed) {
// Reset preferences
this.persistenceManager.resetPreferences();
// Update UI
this.loadPreferences();
// Apply changes
this.applySettings();
// Refresh voice list
this.populateVoices();
}
}
/**
* Save settings and close modal
*/
saveAndClose() {
if (this.persistenceManager && this.elements) {
// Save preferences - already saved as they change
// Apply settings
this.applySettings();
}
this.hide();
}
/**
* Apply current settings to the app
*/
applySettings() {
if (!this.persistenceManager) return;
// Apply animation speed
const animSpeed = this.persistenceManager.getPreference('animation', 'speed', 50);
const animQueue = moduleRegistry.getModule('animation-queue');
if (animQueue) {
const speed = Math.pow(100.0 - animSpeed, 3) / 10000 * 10 + 0.01;
animQueue.setSpeed(speed);
}
// Apply TTS settings
const ttsEnabled = this.persistenceManager.getPreference('tts', 'enabled', false);
const ttsProvider = this.persistenceManager.getPreference('tts', 'provider', 'browser');
const ttsVoice = this.persistenceManager.getPreference('tts', 'voice', '');
const ttsVolume = this.persistenceManager.getPreference('tts', 'volume', 1.0);
const ttsRate = this.persistenceManager.getPreference('tts', 'rate', 1.0);
if (this.ttsPlayer) {
// Set TTS system
if (ttsProvider) {
this.ttsPlayer.switchTTS(ttsProvider);
}
// Apply voice options
this.ttsPlayer.setVoiceOptions({
voice: ttsVoice,
volume: ttsVolume,
rate: ttsRate
});
}
// Apply audio volume settings
const masterVolume = this.persistenceManager.getPreference('audio', 'masterVolume', 1.0);
const musicVolume = this.persistenceManager.getPreference('audio', 'musicVolume', 0.7);
const sfxVolume = this.persistenceManager.getPreference('audio', 'sfxVolume', 1.0);
if (this.audioManager) {
this.audioManager.setMasterVolume(masterVolume);
this.audioManager.setMusicVolume(musicVolume);
this.audioManager.setSfxVolume(sfxVolume);
}
// Apply accessibility settings
const highContrast = this.persistenceManager.getPreference('accessibility', 'highContrast', false);
const largerText = this.persistenceManager.getPreference('accessibility', 'largerText', false);
if (highContrast) {
document.body.classList.add('high-contrast');
} else {
document.body.classList.remove('high-contrast');
}
if (largerText) {
document.body.classList.add('larger-text');
} else {
document.body.classList.remove('larger-text');
}
}
/**
* Set the TTS factory reference
* @param {Object} factory - The TTS factory instance
*/
setTtsFactory(factory) {
this.ttsFactory = factory;
}
/**
* Update available TTS systems info
* @param {Object} systemsInfo - Information about available TTS systems
*/
updateAvailableSystems(systemsInfo) {
// Will repopulate next time UI is opened
console.log("TTS systems info updated:", systemsInfo);
// If the options UI is currently open, update it
if (this.isOpen) {
this.populateTtsSystems();
this.populateVoices();
}
}
/**
* Clean up when module is disposed
*/
dispose() {
// Remove event listeners
window.removeEventListener('tts-system-changed', this.handleTtsSystemChanged);
}
}
// Create the singleton instance
const OptionsUI = new OptionsUIModule();
// Register with the module registry
moduleRegistry.register(OptionsUI);
// Export the module
export { OptionsUI };
// Keep a reference in window for loader system
window.OptionsUI = OptionsUI;