mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-24 23:35:22 -07:00
Cleanup UX
This commit is contained in:
parent
4c34c61ce5
commit
00823d8417
2 changed files with 70 additions and 19 deletions
|
@ -1,9 +1,12 @@
|
||||||
<template>
|
<template>
|
||||||
<span class="v-btn__content">
|
<div v-if="ttsSupported" class="v-btn__content">
|
||||||
<i v-if="!playing" aria-hidden="true" class="v-icon notranslate mdi mdi-play theme--dark" @click.stop="play"></i>
|
<i v-if="canPlay" aria-hidden="true" class="v-icon notranslate mdi mdi-play theme--dark" @click.stop="play"></i>
|
||||||
<i v-if="playing" aria-hidden="true" class="v-icon notranslate mdi mdi-pause theme--dark" @click.stop="pause"></i>
|
<i v-if="canPause" aria-hidden="true" class="v-icon notranslate mdi mdi-pause theme--dark" @click.stop="pause"></i>
|
||||||
<i v-if="playing" aria-hidden="true" class="v-icon notranslate mdi mdi-pause theme--dark" @click.stop="resume"></i>
|
<i v-if="canResume" aria-hidden="true" class="v-icon notranslate mdi mdi-play theme--dark" @click.stop="resume"></i>
|
||||||
</span>
|
<i v-if="canStartAgain" aria-hidden="true" class="v-icon notranslate mdi mdi-restart theme--dark" @click.stop="play"></i>
|
||||||
|
|
||||||
|
<small @click.stop="pauseOrResume">{{ currentSentence }}</small>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
@ -20,7 +23,9 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
speechSynthesis.stop();
|
if (speechSynthesis) {
|
||||||
|
speechSynthesis.cancel();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
@ -28,19 +33,57 @@ export default defineComponent({
|
||||||
playing: false,
|
playing: false,
|
||||||
played: false,
|
played: false,
|
||||||
paused: false,
|
paused: false,
|
||||||
utterance: null,
|
utterance: SpeechSynthesisUtterance,
|
||||||
currentIndex: 0
|
currentIndex: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
ttsSupported() {
|
||||||
|
return !!speechSynthesis;
|
||||||
|
},
|
||||||
|
canPlay() {
|
||||||
|
return !this.playing && !this.paused;
|
||||||
|
},
|
||||||
|
canPause() {
|
||||||
|
return this.playing && !this.paused;
|
||||||
|
},
|
||||||
|
canResume() {
|
||||||
|
return !this.playing && this.paused;
|
||||||
|
},
|
||||||
|
canStartAgain() {
|
||||||
|
return this.playing || this.paused;
|
||||||
|
},
|
||||||
|
nextIndex() {
|
||||||
|
// TODO: i18n stopwords in sync with the browser's implementation? Assumes . and ! are boundaries, may not be true for all languages.
|
||||||
|
const match = this.step.text.slice(this.currentIndex).search(/[\.\!]/)
|
||||||
|
if (match === -1) {
|
||||||
|
return this.currentIndex;
|
||||||
|
}
|
||||||
|
return this.currentIndex + match;
|
||||||
|
},
|
||||||
|
currentSentence() {
|
||||||
|
if (this.playing || this.paused) {
|
||||||
|
return this.step.text.slice(this.currentIndex, this.nextIndex);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
pauseOrResume() {
|
||||||
|
if (this.paused) {
|
||||||
|
this.resume();
|
||||||
|
} else {
|
||||||
|
this.pause();
|
||||||
|
}
|
||||||
|
},
|
||||||
play() {
|
play() {
|
||||||
const self = this;
|
const self = this;
|
||||||
if (this.utterance && this.playing) {
|
if (this.utterance && this.playing) {
|
||||||
speechSynthesis.cancel();
|
speechSynthesis.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.utterance = new SpeechSynthesisUtterance(this.step.text);
|
const utterance = new SpeechSynthesisUtterance(this.step.text);
|
||||||
this.utterance.onstart = (event) => {
|
utterance.onstart = (event) => {
|
||||||
self.playing = true;
|
self.playing = true;
|
||||||
self.played = false;
|
self.played = false;
|
||||||
self.paused = false;
|
self.paused = false;
|
||||||
|
@ -48,26 +91,26 @@ export default defineComponent({
|
||||||
|
|
||||||
console.debug("Now playing: " + this.step.text);
|
console.debug("Now playing: " + this.step.text);
|
||||||
};
|
};
|
||||||
this.utterance.onend = () => {
|
utterance.onend = () => {
|
||||||
self.playing = false;
|
self.playing = false;
|
||||||
self.played = true;
|
self.played = true;
|
||||||
self.paused = false;
|
self.paused = false;
|
||||||
// self.$emit("playing", false);
|
self.$emit("ttscompleted", true);
|
||||||
|
|
||||||
console.debug("Playback complete");
|
console.debug("Playback complete");
|
||||||
};
|
};
|
||||||
|
|
||||||
this.utterance.onpause = () => {
|
utterance.onpause = () => {
|
||||||
self.playing = false;
|
self.playing = false;
|
||||||
self.paused = true;
|
self.paused = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.utterance.onresume = () => {
|
utterance.onresume = () => {
|
||||||
self.playing = true;
|
self.playing = true;
|
||||||
self.paused = false;
|
self.paused = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
this.utterance.onboundary = (event) => {
|
utterance.onboundary = (event) => {
|
||||||
// Update the start of the current sentence.
|
// Update the start of the current sentence.
|
||||||
if (event.name === "sentence") {
|
if (event.name === "sentence") {
|
||||||
self.currentIndex = event.charIndex;
|
self.currentIndex = event.charIndex;
|
||||||
|
@ -78,13 +121,20 @@ export default defineComponent({
|
||||||
// console.log("mark")
|
// console.log("mark")
|
||||||
// console.log(event)
|
// console.log(event)
|
||||||
// };
|
// };
|
||||||
this.utterance.onerror = (event) => {
|
utterance.onerror = (event) => {
|
||||||
|
if (event.error == "interrupted") {
|
||||||
|
this.playing = false;
|
||||||
|
this.played = false;
|
||||||
|
this.paused = false;
|
||||||
|
} else {
|
||||||
console.error("Error in playback")
|
console.error("Error in playback")
|
||||||
console.debug(event)
|
console.debug(event);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
speechSynthesis.speak(this.utterance);
|
speechSynthesis.speak(utterance);
|
||||||
return true;
|
|
||||||
|
this.utterance = utterance;
|
||||||
},
|
},
|
||||||
pause() {
|
pause() {
|
||||||
speechSynthesis.pause();
|
speechSynthesis.pause();
|
||||||
|
|
|
@ -122,6 +122,7 @@
|
||||||
:ripple="false"
|
:ripple="false"
|
||||||
@click="toggleDisabled(index)"
|
@click="toggleDisabled(index)"
|
||||||
>
|
>
|
||||||
|
<!-- TODO: Should a ttscompleted event mark the step 'done'? Or auto scroll? -->
|
||||||
<v-card-title :class="{ 'pb-0': !isChecked(index) }">
|
<v-card-title :class="{ 'pb-0': !isChecked(index) }">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
v-if="isEditForm"
|
v-if="isEditForm"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue