Spaces:
Runtime error
Runtime error
File size: 6,088 Bytes
05b45a5 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
export class VoiceSelector {
constructor(voiceService) {
this.voiceService = voiceService;
this.elements = {
voiceSearch: document.getElementById('voice-search'),
voiceDropdown: document.getElementById('voice-dropdown'),
voiceOptions: document.getElementById('voice-options'),
selectedVoices: document.getElementById('selected-voices')
};
this.setupEventListeners();
}
setupEventListeners() {
// Voice search focus
this.elements.voiceSearch.addEventListener('focus', () => {
this.elements.voiceDropdown.classList.add('show');
});
// Voice search
this.elements.voiceSearch.addEventListener('input', (e) => {
const filteredVoices = this.voiceService.filterVoices(e.target.value);
this.renderVoiceOptions(filteredVoices);
});
// Voice selection - handle clicks on the entire voice option
this.elements.voiceOptions.addEventListener('mousedown', (e) => {
e.preventDefault(); // Prevent blur on search input
const voiceOption = e.target.closest('.voice-option');
if (!voiceOption) return;
const voice = voiceOption.dataset.voice;
if (!voice) return;
const isSelected = voiceOption.classList.contains('selected');
if (!isSelected) {
this.voiceService.addVoice(voice);
} else {
this.voiceService.removeVoice(voice);
}
voiceOption.classList.toggle('selected');
this.updateSelectedVoicesDisplay();
// Keep focus on search input
requestAnimationFrame(() => {
this.elements.voiceSearch.focus();
});
});
// Weight adjustment
this.elements.selectedVoices.addEventListener('input', (e) => {
if (e.target.type === 'number') {
const voice = e.target.dataset.voice;
let weight = parseFloat(e.target.value);
// Ensure weight is between 0.1 and 10
weight = Math.max(0.1, Math.min(10, weight));
e.target.value = weight;
this.voiceService.updateWeight(voice, weight);
}
});
// Remove selected voice
this.elements.selectedVoices.addEventListener('click', (e) => {
if (e.target.classList.contains('remove-voice')) {
e.preventDefault();
e.stopPropagation();
const voice = e.target.dataset.voice;
this.voiceService.removeVoice(voice);
this.updateVoiceOptionState(voice, false);
this.updateSelectedVoicesDisplay();
}
});
// Handle clicks outside to close dropdown
document.addEventListener('mousedown', (e) => {
// Don't handle clicks in selected voices area
if (this.elements.selectedVoices.contains(e.target)) {
return;
}
// Don't close if clicking in search or dropdown
if (this.elements.voiceSearch.contains(e.target) ||
this.elements.voiceDropdown.contains(e.target)) {
return;
}
this.elements.voiceDropdown.classList.remove('show');
this.elements.voiceSearch.blur();
});
this.elements.voiceSearch.addEventListener('blur', () => {
if (!this.elements.voiceSearch.value) {
this.updateSearchPlaceholder();
}
});
}
renderVoiceOptions(voices) {
this.elements.voiceOptions.innerHTML = voices
.map(voice => `
<div class="voice-option ${this.voiceService.getSelectedVoices().includes(voice) ? 'selected' : ''}"
data-voice="${voice}">
${voice}
</div>
`)
.join('');
}
updateSelectedVoicesDisplay() {
const selectedVoices = this.voiceService.getSelectedVoiceWeights();
this.elements.selectedVoices.innerHTML = selectedVoices
.map(({voice, weight}) => `
<span class="selected-voice-tag">
<span class="voice-name">${voice}</span>
<span class="voice-weight">
<input type="number"
value="${weight}"
min="0.1"
max="10"
step="0.1"
data-voice="${voice}"
class="weight-input"
title="Voice weight (0.1 to 10)">
</span>
<span class="remove-voice" data-voice="${voice}" title="Remove voice">×</span>
</span>
`)
.join('');
this.updateSearchPlaceholder();
}
updateSearchPlaceholder() {
const hasSelected = this.voiceService.hasSelectedVoices();
this.elements.voiceSearch.placeholder = hasSelected ?
'Search voices...' :
'Search and select voices...';
}
updateVoiceOptionState(voice, selected) {
const voiceOption = this.elements.voiceOptions
.querySelector(`[data-voice="${voice}"]`);
if (voiceOption) {
voiceOption.classList.toggle('selected', selected);
}
}
async initialize() {
try {
await this.voiceService.loadVoices();
this.renderVoiceOptions(this.voiceService.getAvailableVoices());
this.updateSelectedVoicesDisplay();
return true;
} catch (error) {
console.error('Failed to initialize voice selector:', error);
return false;
}
}
}
export default VoiceSelector; |