Document markup and improve choice tags

This commit is contained in:
2026-05-17 15:52:41 +02:00
parent c2fb27b6b8
commit 2c54498ee2
52 changed files with 3485 additions and 377 deletions
+110 -128
View File
@@ -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}%`;
}
}
}