Document markup and improve choice tags
This commit is contained in:
+110
-128
@@ -17,7 +17,8 @@ class OptionsUIModule extends BaseModule {
|
||||
'persistence-manager',
|
||||
'localization',
|
||||
'tts-factory',
|
||||
'audio-manager'
|
||||
'audio-manager',
|
||||
'game-config'
|
||||
];
|
||||
|
||||
// Modal element
|
||||
@@ -38,6 +39,9 @@ class OptionsUIModule extends BaseModule {
|
||||
'populateVoices',
|
||||
'populateLanguages',
|
||||
'loadPreferences',
|
||||
'createVolumeControl',
|
||||
'updateVolumeToggleButtons',
|
||||
'updateVolumeToggleButton',
|
||||
'showReloadNotice',
|
||||
'toggle',
|
||||
'setupEventListeners',
|
||||
@@ -170,6 +174,7 @@ class OptionsUIModule extends BaseModule {
|
||||
// Create body
|
||||
const body = document.createElement('div');
|
||||
body.className = 'modal-body';
|
||||
const localization = this.getModule('localization');
|
||||
|
||||
// Create sections
|
||||
// App Settings Section (Language and Speed)
|
||||
@@ -193,6 +198,23 @@ class OptionsUIModule extends BaseModule {
|
||||
}, null, languageContainer);
|
||||
|
||||
appSettingsSection.appendChild(languageContainer);
|
||||
|
||||
const gameLanguageContainer = document.createElement('div');
|
||||
gameLanguageContainer.className = 'option-item';
|
||||
|
||||
const gameLanguageLabel = document.createElement('label');
|
||||
gameLanguageLabel.textContent = this.t('options.gameLanguage') + ':';
|
||||
gameLanguageContainer.appendChild(gameLanguageLabel);
|
||||
|
||||
const gameLanguageValue = document.createElement('span');
|
||||
gameLanguageValue.className = 'game-language-value';
|
||||
const gameConfig = this.getModule('game-config');
|
||||
const gameLocale = gameConfig?.getLocale?.() || 'en_US';
|
||||
gameLanguageValue.textContent = localization?.getLanguageName?.(gameLocale) || gameLocale;
|
||||
this.elements.gameLanguage = gameLanguageValue;
|
||||
gameLanguageContainer.appendChild(gameLanguageValue);
|
||||
|
||||
appSettingsSection.appendChild(gameLanguageContainer);
|
||||
|
||||
// Speed
|
||||
const speedContainer = document.createElement('div');
|
||||
@@ -296,125 +318,11 @@ class OptionsUIModule extends BaseModule {
|
||||
audioTitle.textContent = this.t('options.audio');
|
||||
audioSection.appendChild(audioTitle);
|
||||
|
||||
// Master Volume
|
||||
const masterVolumeContainer = document.createElement('div');
|
||||
masterVolumeContainer.className = 'option-item';
|
||||
|
||||
const masterVolumeLabel = document.createElement('label');
|
||||
masterVolumeLabel.textContent = this.t('options.masterVolume') + ':';
|
||||
masterVolumeContainer.appendChild(masterVolumeLabel);
|
||||
|
||||
const masterVolumeValue = document.createElement('span');
|
||||
masterVolumeValue.className = 'slider-value';
|
||||
masterVolumeValue.textContent = '100%';
|
||||
this.elements.masterVolumeValue = masterVolumeValue;
|
||||
masterVolumeContainer.appendChild(masterVolumeValue);
|
||||
|
||||
this.elements.masterVolume = createUIElement('input', {
|
||||
type: 'range',
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: 100,
|
||||
'data-pref-bind': 'audio.masterVolume',
|
||||
'data-pref-transform': 'range:0,1'
|
||||
}, null, masterVolumeContainer);
|
||||
|
||||
// Update displayed value when slider changes
|
||||
this.elements.masterVolume.addEventListener('input', () => {
|
||||
this.elements.masterVolumeValue.textContent = `${this.elements.masterVolume.value}%`;
|
||||
});
|
||||
|
||||
audioSection.appendChild(masterVolumeContainer);
|
||||
|
||||
// Speech Volume
|
||||
const ttsVolumeContainer = document.createElement('div');
|
||||
ttsVolumeContainer.className = 'option-item';
|
||||
|
||||
const ttsVolumeLabel = document.createElement('label');
|
||||
ttsVolumeLabel.textContent = this.t('options.speechVolume') + ':';
|
||||
ttsVolumeContainer.appendChild(ttsVolumeLabel);
|
||||
|
||||
const ttsVolumeValue = document.createElement('span');
|
||||
ttsVolumeValue.className = 'slider-value';
|
||||
ttsVolumeValue.textContent = '100%';
|
||||
this.elements.ttsVolumeValue = ttsVolumeValue;
|
||||
ttsVolumeContainer.appendChild(ttsVolumeValue);
|
||||
|
||||
this.elements.ttsVolume = createUIElement('input', {
|
||||
type: 'range',
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: 100,
|
||||
'data-pref-bind': 'audio.ttsVolume',
|
||||
'data-pref-transform': 'range:0,1'
|
||||
}, null, ttsVolumeContainer);
|
||||
|
||||
// Update displayed value when slider changes
|
||||
this.elements.ttsVolume.addEventListener('input', () => {
|
||||
this.elements.ttsVolumeValue.textContent = `${this.elements.ttsVolume.value}%`;
|
||||
});
|
||||
|
||||
audioSection.appendChild(ttsVolumeContainer);
|
||||
|
||||
// Music Volume
|
||||
const musicVolumeContainer = document.createElement('div');
|
||||
musicVolumeContainer.className = 'option-item';
|
||||
|
||||
const musicVolumeLabel = document.createElement('label');
|
||||
musicVolumeLabel.textContent = this.t('options.musicVolume') + ':';
|
||||
musicVolumeContainer.appendChild(musicVolumeLabel);
|
||||
|
||||
const musicVolumeValue = document.createElement('span');
|
||||
musicVolumeValue.className = 'slider-value';
|
||||
musicVolumeValue.textContent = '100%';
|
||||
this.elements.musicVolumeValue = musicVolumeValue;
|
||||
musicVolumeContainer.appendChild(musicVolumeValue);
|
||||
|
||||
this.elements.musicVolume = createUIElement('input', {
|
||||
type: 'range',
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: 70,
|
||||
'data-pref-bind': 'audio.musicVolume',
|
||||
'data-pref-transform': 'range:0,1'
|
||||
}, null, musicVolumeContainer);
|
||||
|
||||
// Update displayed value when slider changes
|
||||
this.elements.musicVolume.addEventListener('input', () => {
|
||||
this.elements.musicVolumeValue.textContent = `${this.elements.musicVolume.value}%`;
|
||||
});
|
||||
|
||||
audioSection.appendChild(musicVolumeContainer);
|
||||
|
||||
// SFX Volume
|
||||
const sfxVolumeContainer = document.createElement('div');
|
||||
sfxVolumeContainer.className = 'option-item';
|
||||
|
||||
const sfxVolumeLabel = document.createElement('label');
|
||||
sfxVolumeLabel.textContent = this.t('options.sfxVolume') + ':';
|
||||
sfxVolumeContainer.appendChild(sfxVolumeLabel);
|
||||
|
||||
const sfxVolumeValue = document.createElement('span');
|
||||
sfxVolumeValue.className = 'slider-value';
|
||||
sfxVolumeValue.textContent = '100%';
|
||||
this.elements.sfxVolumeValue = sfxVolumeValue;
|
||||
sfxVolumeContainer.appendChild(sfxVolumeValue);
|
||||
|
||||
this.elements.sfxVolume = createUIElement('input', {
|
||||
type: 'range',
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: 100,
|
||||
'data-pref-bind': 'audio.sfxVolume',
|
||||
'data-pref-transform': 'range:0,1'
|
||||
}, null, sfxVolumeContainer);
|
||||
|
||||
// Update displayed value when slider changes
|
||||
this.elements.sfxVolume.addEventListener('input', () => {
|
||||
this.elements.sfxVolumeValue.textContent = `${this.elements.sfxVolume.value}%`;
|
||||
});
|
||||
|
||||
audioSection.appendChild(sfxVolumeContainer);
|
||||
audioSection.appendChild(this.createVolumeControl('masterVolume', 'masterVolumeEnabled', 'options.masterVolume', 'options.muteMasterVolume', 'options.unmuteMasterVolume', 100));
|
||||
audioSection.appendChild(this.createVolumeControl('ttsVolume', 'ttsVolumeEnabled', 'options.speechVolume', 'options.muteSpeechVolume', 'options.unmuteSpeechVolume', 100));
|
||||
audioSection.appendChild(this.createVolumeControl('musicVolume', 'musicVolumeEnabled', 'options.musicVolume', 'options.muteMusicVolume', 'options.unmuteMusicVolume', 70));
|
||||
audioSection.appendChild(this.createVolumeControl('sfxVolume', 'sfxVolumeEnabled', 'options.sfxVolume', 'options.muteSfxVolume', 'options.unmuteSfxVolume', 100));
|
||||
audioSection.appendChild(this.createVolumeControl('musicDuckingAmount', 'musicDuckingEnabled', 'options.musicDucking', 'options.disableMusicDucking', 'options.enableMusicDucking', 30));
|
||||
|
||||
body.appendChild(audioSection);
|
||||
|
||||
@@ -437,6 +345,69 @@ class OptionsUIModule extends BaseModule {
|
||||
// Add modal to document
|
||||
document.body.appendChild(this.modal);
|
||||
}
|
||||
|
||||
createVolumeControl(valueKey, enabledKey, labelKey, muteTitleKey, unmuteTitleKey, defaultPercent) {
|
||||
const container = document.createElement('div');
|
||||
container.className = 'option-item volume-option';
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.textContent = this.t(labelKey) + ':';
|
||||
container.appendChild(label);
|
||||
|
||||
const toggle = document.createElement('button');
|
||||
toggle.type = 'button';
|
||||
toggle.className = 'volume-toggle';
|
||||
toggle.dataset.prefCategory = 'audio';
|
||||
toggle.dataset.prefKey = enabledKey;
|
||||
toggle.dataset.muteTitleKey = muteTitleKey;
|
||||
toggle.dataset.unmuteTitleKey = unmuteTitleKey;
|
||||
toggle.addEventListener('click', () => {
|
||||
const current = this.getPreference('audio', enabledKey, true) !== false;
|
||||
this.updatePreference('audio', enabledKey, !current);
|
||||
this.updateVolumeToggleButton(toggle);
|
||||
});
|
||||
container.appendChild(toggle);
|
||||
|
||||
const value = document.createElement('span');
|
||||
value.className = 'slider-value';
|
||||
value.textContent = `${defaultPercent}%`;
|
||||
this.elements[`${valueKey}Value`] = value;
|
||||
container.appendChild(value);
|
||||
|
||||
const slider = createUIElement('input', {
|
||||
type: 'range',
|
||||
min: 0,
|
||||
max: 100,
|
||||
value: defaultPercent,
|
||||
'data-pref-bind': `audio.${valueKey}`,
|
||||
'data-pref-transform': 'range:0,1'
|
||||
}, null, container);
|
||||
this.elements[valueKey] = slider;
|
||||
slider.addEventListener('input', () => {
|
||||
value.textContent = `${slider.value}%`;
|
||||
});
|
||||
|
||||
this.updateVolumeToggleButton(toggle);
|
||||
return container;
|
||||
}
|
||||
|
||||
updateVolumeToggleButtons() {
|
||||
if (!this.modal) return;
|
||||
this.modal.querySelectorAll('.volume-toggle').forEach(button => {
|
||||
this.updateVolumeToggleButton(button);
|
||||
});
|
||||
}
|
||||
|
||||
updateVolumeToggleButton(button) {
|
||||
if (!button) return;
|
||||
const enabled = this.getPreference(button.dataset.prefCategory, button.dataset.prefKey, true) !== false;
|
||||
button.classList.toggle('is-muted', !enabled);
|
||||
button.innerHTML = enabled ? '🔊' : '🔇';
|
||||
const titleKey = enabled ? button.dataset.muteTitleKey : button.dataset.unmuteTitleKey;
|
||||
const title = this.t(titleKey);
|
||||
button.title = title;
|
||||
button.setAttribute('aria-label', title);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create API settings controls
|
||||
@@ -707,7 +678,7 @@ class OptionsUIModule extends BaseModule {
|
||||
languageOptions,
|
||||
'code',
|
||||
'name',
|
||||
this.getPreference('app', 'locale', 'en-us')
|
||||
this.getPreference('app', 'locale', localization.getLocale?.() || 'en_US')
|
||||
);
|
||||
}
|
||||
|
||||
@@ -883,7 +854,22 @@ class OptionsUIModule extends BaseModule {
|
||||
audioManager.setSfxVolume(value);
|
||||
} else if (key === 'ttsVolume') {
|
||||
audioManager.setTtsVolume(value);
|
||||
} else if (key === 'masterVolumeEnabled') {
|
||||
audioManager.setVolumeEnabled('master', value);
|
||||
} else if (key === 'musicVolumeEnabled') {
|
||||
audioManager.setVolumeEnabled('music', value);
|
||||
} else if (key === 'sfxVolumeEnabled') {
|
||||
audioManager.setVolumeEnabled('sfx', value);
|
||||
} else if (key === 'ttsVolumeEnabled') {
|
||||
audioManager.setVolumeEnabled('tts', value);
|
||||
} else if (key === 'musicDuckingAmount') {
|
||||
audioManager.setMusicDuckingAmount(value);
|
||||
} else if (key === 'musicDuckingEnabled') {
|
||||
audioManager.setMusicDuckingEnabled(value);
|
||||
}
|
||||
|
||||
this.updateVolumeDisplays();
|
||||
this.updateVolumeToggleButtons();
|
||||
}
|
||||
|
||||
// Handle TTS settings side effects
|
||||
@@ -905,8 +891,6 @@ class OptionsUIModule extends BaseModule {
|
||||
ttsFactory.configure({ voice: value });
|
||||
} else if (key === 'speed') {
|
||||
ttsFactory.configure({ speed: value });
|
||||
} else if (key === 'language') {
|
||||
ttsFactory.configure({ language: value });
|
||||
} else if (key === 'enabled') {
|
||||
if (!value) {
|
||||
ttsFactory.disableAfterCurrentPlayback();
|
||||
@@ -939,11 +923,6 @@ class OptionsUIModule extends BaseModule {
|
||||
if (localization) {
|
||||
localization.setLocale(value);
|
||||
}
|
||||
const ttsFactory = this.getModule('tts-factory');
|
||||
if (ttsFactory) {
|
||||
ttsFactory.configure({ language: value });
|
||||
}
|
||||
this.updatePreference('tts', 'language', value);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -969,6 +948,9 @@ class OptionsUIModule extends BaseModule {
|
||||
if (this.elements.sfxVolume && this.elements.sfxVolumeValue) {
|
||||
this.elements.sfxVolumeValue.textContent = `${this.elements.sfxVolume.value}%`;
|
||||
}
|
||||
if (this.elements.musicDuckingAmount && this.elements.musicDuckingAmountValue) {
|
||||
this.elements.musicDuckingAmountValue.textContent = `${this.elements.musicDuckingAmount.value}%`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user