ui(tts): "Im Gerät" ausgrauen statt ausblenden, wenn keine Sprachstimmen
- refreshDevicePreset(): graut das "Im Gerät"-Preset aus (disabled + opacity + Hinweis "keine Sprachstimmen im Browser"), wenn getVoices() leer ist; entgraut automatisch bei voiceschanged (+ 1,5s-Backstop). - Kein Aufzwingen: device wird nur (wieder) gewählt, wenn es die gespeicherte Präferenz ist; ohne Stimmen wird die aktive Wahl auf piper gesetzt (Präferenz bleibt erhalten). - Server-Fallback bleibt für fehlende Einzelsprachen. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
53ec2d7ec7
commit
b8b2bcddd3
2 changed files with 29 additions and 10 deletions
|
|
@ -60,7 +60,9 @@ let _voices = [];
|
||||||
function loadVoices() { _voices = TTS_SUPPORTED ? speechSynthesis.getVoices() : []; }
|
function loadVoices() { _voices = TTS_SUPPORTED ? speechSynthesis.getVoices() : []; }
|
||||||
if (TTS_SUPPORTED) {
|
if (TTS_SUPPORTED) {
|
||||||
loadVoices();
|
loadVoices();
|
||||||
speechSynthesis.addEventListener("voiceschanged", loadVoices);
|
// Stimmen laden asynchron -> bei Eintreffen Preset-Zustand neu bewerten (entgrauen).
|
||||||
|
speechSynthesis.addEventListener("voiceschanged", () => { loadVoices(); refreshDevicePreset(); });
|
||||||
|
setTimeout(() => refreshDevicePreset(), 1500); // Backstop, falls kein voiceschanged kommt
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickVoice(bcp) {
|
function pickVoice(bcp) {
|
||||||
|
|
@ -139,13 +141,6 @@ const PLAYBACK_KEY = "va-playback"; // geräte-lokal: "device" wenn Geräte-TTS
|
||||||
// Vorrang: 1. lokale Wahl (localStorage), 2. Server-Pref (tts_provider), 3. Mobil-Default = Gerät.
|
// Vorrang: 1. lokale Wahl (localStorage), 2. Server-Pref (tts_provider), 3. Mobil-Default = Gerät.
|
||||||
function applyPlaybackChoice(serverProvider) {
|
function applyPlaybackChoice(serverProvider) {
|
||||||
if (!ttsSel) return;
|
if (!ttsSel) return;
|
||||||
// "Im Gerät"-Preset ausblenden, wenn der Browser keine Web Speech API hat.
|
|
||||||
if (!TTS_SUPPORTED) {
|
|
||||||
const opt = ttsSel.querySelector('option[value="device"]');
|
|
||||||
if (opt) opt.remove();
|
|
||||||
const btn = document.querySelector('.tts-preset[data-tts="device"]');
|
|
||||||
if (btn) btn.classList.add("hidden");
|
|
||||||
}
|
|
||||||
const local = localStorage.getItem(PLAYBACK_KEY);
|
const local = localStorage.getItem(PLAYBACK_KEY);
|
||||||
if (local === "device" && TTS_SUPPORTED) {
|
if (local === "device" && TTS_SUPPORTED) {
|
||||||
ttsSel.value = "device";
|
ttsSel.value = "device";
|
||||||
|
|
@ -157,6 +152,30 @@ function applyPlaybackChoice(serverProvider) {
|
||||||
} else if (!ttsSel.value) {
|
} else if (!ttsSel.value) {
|
||||||
ttsSel.value = "piper"; // Fallback: Server
|
ttsSel.value = "piper"; // Fallback: Server
|
||||||
}
|
}
|
||||||
|
refreshDevicePreset(); // graut „Im Gerät" aus, wenn keine Stimmen + syncPresetUI
|
||||||
|
}
|
||||||
|
|
||||||
|
// Graut „Im Gerät" aus, wenn keine Sprachstimmen vorhanden sind; entgraut automatisch,
|
||||||
|
// sobald welche da sind. Kein Aufzwingen: device wird nur (wieder) gewählt, wenn es die
|
||||||
|
// gespeicherte Präferenz ist. Ohne Stimmen NICHT auf der deaktivierten Option sitzen bleiben.
|
||||||
|
function refreshDevicePreset() {
|
||||||
|
const btn = document.querySelector('.tts-preset[data-tts="device"]');
|
||||||
|
if (!btn || !ttsSel) return;
|
||||||
|
const ready = deviceVoicesReady();
|
||||||
|
btn.disabled = !ready;
|
||||||
|
btn.classList.toggle("opacity-50", !ready);
|
||||||
|
btn.classList.toggle("cursor-not-allowed", !ready);
|
||||||
|
const sub = btn.querySelector(".tts-sub");
|
||||||
|
if (sub) sub.textContent = ready
|
||||||
|
? "Das Gerät liest vor · spart Daten"
|
||||||
|
: "keine Sprachstimmen im Browser";
|
||||||
|
if (ready) {
|
||||||
|
if (localStorage.getItem(PLAYBACK_KEY) === "device" && ttsSel.value !== "device") {
|
||||||
|
ttsSel.value = "device"; // gespeicherte Präferenz wieder aktivieren
|
||||||
|
}
|
||||||
|
} else if (ttsSel.value === "device") {
|
||||||
|
ttsSel.value = "piper"; // Server statt deaktivierter Option (Präferenz bleibt)
|
||||||
|
}
|
||||||
syncPresetUI();
|
syncPresetUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -261,7 +261,7 @@
|
||||||
class="tts-preset w-full flex items-center gap-3 rounded-lg border border-slate-200 dark:border-slate-700 px-3 py-2 text-left hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors">
|
class="tts-preset w-full flex items-center gap-3 rounded-lg border border-slate-200 dark:border-slate-700 px-3 py-2 text-left hover:bg-slate-50 dark:hover:bg-slate-700/50 transition-colors">
|
||||||
<span class="text-lg shrink-0">📱</span>
|
<span class="text-lg shrink-0">📱</span>
|
||||||
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Im Gerät</span>
|
<span class="flex-1 min-w-0"><span class="block text-sm font-medium">Im Gerät</span>
|
||||||
<span class="block text-xs text-slate-500 dark:text-slate-400">Das Gerät liest vor · spart Daten</span></span>
|
<span class="tts-sub block text-xs text-slate-500 dark:text-slate-400">Das Gerät liest vor · spart Daten</span></span>
|
||||||
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0">✓</span>
|
<span class="check text-blue-600 dark:text-blue-400 font-bold opacity-0">✓</span>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-tts="piper"
|
<button type="button" data-tts="piper"
|
||||||
|
|
@ -326,6 +326,6 @@
|
||||||
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
<div id="status" class="w-full max-w-3xl mx-auto mt-1 min-h-[1rem] text-xs text-slate-500 dark:text-slate-400"></div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="/app.js?v=41"></script>
|
<script src="/app.js?v=42"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue