Skip to content

Commit

Permalink
[ソング] Improve: エンジン起動前の画面を良い感じにする (#1790)
Browse files Browse the repository at this point in the history
* Add: エンジン起動中のポップアップを追加

* Refactor: 共通化

* Change: slotを使わないように

* Update: スタイルを移動

* Add: スケルトンを追加

* Fix: 細かいところを修正

* Fix: スタイルが消えてたので修正

* Change: EngineStartupPopup -> EngineStartupOverlay

* Change: コンポーネントを分割

* Change: ToolBarは無効化で表現するように

* Fix: スクロールバーが消えてたのを修正

* Delete: 未使用のスタイルを削除

* Delete: 未使用のスタイルを削除

---------

Co-authored-by: Hiroshiba <hihokaruta@gmail.com>
  • Loading branch information
sevenc-nanashi and Hiroshiba authored Feb 13, 2024
1 parent e159b3c commit 33167d8
Show file tree
Hide file tree
Showing 6 changed files with 286 additions and 230 deletions.
126 changes: 126 additions & 0 deletions src/components/EngineStartupOverlay.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<template>
<!-- TODO: 複数エンジン対応 -->
<!-- TODO: allEngineStateが "ERROR" のときエラーになったエンジンを探してトーストで案内 -->
<div v-if="allEngineState === 'FAILED_STARTING'" class="waiting-engine">
<div>エンジンの起動に失敗しました。エンジンの再起動をお試しください。</div>
</div>
<div
v-else-if="
!props.isCompletedInitialStartup || allEngineState === 'STARTING'
"
class="waiting-engine"
>
<div>
<q-spinner color="primary" size="2.5rem" />
<div class="q-mt-xs">
{{
allEngineState === "STARTING"
? "エンジン起動中・・・"
: "データ準備中・・・"
}}
</div>

<template v-if="isEngineWaitingLong">
<q-separator spaced />
エンジン起動に時間がかかっています。<br />
<q-btn
v-if="isMultipleEngine"
outline
:disable="reloadingLocked"
@click="reloadAppWithMultiEngineOffMode"
>
マルチエンジンをオフにして再読み込みする</q-btn
>
<q-btn v-else outline @click="openQa">Q&Aを見る</q-btn>
</template>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, watch } from "vue";
import { useStore } from "@/store";
import { EngineState } from "@/store/type";
const store = useStore();
const props =
defineProps<{
isCompletedInitialStartup: boolean;
}>();
const reloadingLocked = computed(() => store.state.reloadingLock);
const isMultipleEngine = computed(() => store.state.engineIds.length > 1);
// エンジン待機
// TODO: 個別のエンジンの状態をUIで確認できるようにする
const allEngineState = computed(() => {
const engineStates = store.state.engineStates;
let lastEngineState: EngineState | undefined = undefined;
// 登録されているすべてのエンジンについて状態を確認する
for (const engineId of store.state.engineIds) {
const engineState: EngineState | undefined = engineStates[engineId];
if (engineState == undefined)
throw new Error(`No such engineState set: engineId == ${engineId}`);
// FIXME: 1つでも接続テストに成功していないエンジンがあれば、暫定的に起動中とする
if (engineState === "STARTING") {
return engineState;
}
lastEngineState = engineState;
}
return lastEngineState; // FIXME: 暫定的に1つのエンジンの状態を返す
});
const isEngineWaitingLong = ref<boolean>(false);
let engineTimer: number | undefined = undefined;
watch(allEngineState, (newEngineState) => {
if (engineTimer != undefined) {
clearTimeout(engineTimer);
engineTimer = undefined;
}
if (newEngineState === "STARTING") {
isEngineWaitingLong.value = false;
engineTimer = window.setTimeout(() => {
isEngineWaitingLong.value = true;
}, 30000);
} else {
isEngineWaitingLong.value = false;
}
});
const reloadAppWithMultiEngineOffMode = () => {
store.dispatch("CHECK_EDITED_AND_NOT_SAVE", {
closeOrReload: "reload",
isMultiEngineOffMode: true,
});
};
const openQa = () => {
window.open("https://voicevox.hiroshiba.jp/qa/", "_blank");
};
</script>

<style scoped lang="scss">
@use '@/styles/colors' as colors;
@use '@/styles/variables' as vars;
.waiting-engine {
background-color: rgba(colors.$display-rgb, 0.15);
position: absolute;
inset: 0;
z-index: 10;
display: flex;
text-align: center;
align-items: center;
justify-content: center;
> div {
color: colors.$display;
background: colors.$surface;
border-radius: 6px;
padding: 14px;
}
}
</style>
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
<template>
<q-btn flat class="q-pa-none">
<slot></slot>
<q-btn flat class="q-pa-none" :disable="uiLocked">
<selected-character
:show-skeleton="showSkeleton"
:selected-character-info="selectedCharacterInfo"
:selected-singer="selectedSinger"
/>
<q-menu
class="character-menu"
transition-show="none"
Expand Down Expand Up @@ -134,15 +138,22 @@
</q-btn>
</template>

<script lang="ts">
export default {
name: "CharacterMenuButton",
};
</script>
<script setup lang="ts">
import { computed, ref } from "vue";
import { debounce } from "quasar";
import SelectedCharacter from "./SelectedCharacter.vue";
import { useStore } from "@/store";
import { base64ImageToUri } from "@/helpers/imageHelper";
import { SpeakerId, StyleId } from "@/type/preload";
import { getStyleDescription } from "@/sing/viewHelper";
const store = useStore();
const uiLocked = computed(() => store.getters.UI_LOCKED);
const userOrderedCharacterInfos = computed(() => {
return store.getters.USER_ORDERED_CHARACTER_INFOS("singerLike");
Expand All @@ -160,6 +171,7 @@ const reassignSubMenuOpen = debounce((idx: number) => {
arr[idx] = true;
subMenuOpenFlags.value = arr;
}, 100);
const showSkeleton = computed(() => selectedCharacterInfo.value == undefined);
const changeStyleId = (speakerUuid: SpeakerId, styleId: StyleId) => {
const engineId = store.state.engineIds.find((_engineId) =>
Expand Down Expand Up @@ -205,6 +217,10 @@ const selectedCharacterInfo = computed(() => {
return store.getters.CHARACTER_INFO(singer.engineId, singer.styleId);
});
const selectedSinger = computed(() => {
return store.getters.SELECTED_TRACK.singer;
});
const selectedSpeakerUuid = computed(() => {
return selectedCharacterInfo.value?.metas.speakerUuid;
});
Expand Down Expand Up @@ -235,20 +251,6 @@ const engineIcons = computed(() =>
@use '@/styles/variables' as vars;
@use '@/styles/colors' as colors;
.character-name {
position: absolute;
top: 0px;
left: 0px;
padding: 1px 24px 1px 8px;
background-image: linear-gradient(
90deg,
rgba(colors.$background-rgb, 0.5) 0%,
rgba(colors.$background-rgb, 0.5) 75%,
transparent 100%
);
overflow-wrap: anywhere;
}
.character-menu {
.q-item {
color: colors.$display;
Expand All @@ -261,10 +263,6 @@ const engineIcons = computed(() =>
background-color: rgba(colors.$primary-rgb, 0.1);
}
}
.selected-character-item,
.opened-character-item {
background-color: rgba(colors.$primary-rgb, 0.2);
}
.engine-icon {
position: absolute;
width: 13px;
Expand Down
132 changes: 132 additions & 0 deletions src/components/Sing/CharacterMenuButton/SelectedCharacter.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
<template>
<div v-if="props.showSkeleton" class="selected-character">
<q-skeleton class="character-avatar" type="QAvatar" size="52px" />
<div class="character-info">
<q-skeleton
class="character-name skeleton"
type="rect"
width="65px"
height="15px"
/>
<q-skeleton
class="character-style"
type="rect"
width="110px"
height="12px"
/>
</div>
</div>
<div v-else class="selected-character">
<q-avatar
v-if="selectedStyleIconPath"
class="character-avatar"
size="3.5rem"
>
<img :src="selectedStyleIconPath" class="character-avatar-icon" />
</q-avatar>
<div class="character-info">
<div class="character-name">
{{ selectedCharacterName }}
</div>
<div class="character-style">
{{ selectedCharacterStyleDescription }}
</div>
</div>
<q-icon
name="arrow_drop_down"
size="sm"
class="character-menu-dropdown-icon"
/>
</div>
</template>

<script setup lang="ts">
import { computed } from "vue";
import { Singer } from "@/store/type";
import { CharacterInfo } from "@/type/preload";
import { getStyleDescription } from "@/sing/viewHelper";
const props =
defineProps<{
showSkeleton: boolean;
selectedCharacterInfo: CharacterInfo | undefined;
selectedSinger: Singer | undefined;
}>();
const selectedCharacterName = computed(() => {
return props.selectedCharacterInfo?.metas.speakerName;
});
const selectedCharacterStyleDescription = computed(() => {
const style = props.selectedCharacterInfo?.metas.styles.find((style) => {
return (
style.styleId === props.selectedSinger?.styleId &&
style.engineId === props.selectedSinger?.engineId
);
});
return style != undefined ? getStyleDescription(style) : "";
});
const selectedStyleIconPath = computed(() => {
if (!props.selectedCharacterInfo || !props.selectedSinger) {
return;
}
const styles = props.selectedCharacterInfo.metas.styles;
return styles?.find((style) => {
return (
style.styleId === props.selectedSinger?.styleId &&
style.engineId === props.selectedSinger?.engineId
);
})?.iconPath;
});
</script>

<style scoped lang="scss">
@use '@/styles/variables' as vars;
@use '@/styles/colors' as colors;
.selected-character {
align-items: center;
display: flex;
padding: 0.25rem 0.5rem 0.25rem 0.25rem;
position: relative;
.character-avatar-icon {
display: block;
height: 100%;
object-fit: cover;
width: 100%;
}
.character-info {
align-items: start;
display: flex;
flex-direction: column;
margin-left: 0.5rem;
text-align: left;
justify-content: center;
white-space: nowrap;
}
.character-name {
font-size: 0.875rem;
font-weight: bold;
line-height: 1rem;
padding-top: 0.5rem;
&.skeleton {
margin-top: 0.4rem;
margin-bottom: 0.2rem;
}
}
.character-style {
color: rgba(colors.$display-rgb, 0.6);
font-size: 0.75rem;
font-weight: bold;
line-height: 1rem;
}
.character-menu-dropdown-icon {
color: rgba(colors.$display-rgb, 0.8);
margin-left: 0.25rem;
}
}
</style>
3 changes: 3 additions & 0 deletions src/components/Sing/SingEditor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<menu-bar />
<tool-bar />
<div class="sing-main">
<engine-startup-overlay :is-completed-initial-startup="isEnginesReady" />
<div v-if="nowAudioExporting" class="exporting-dialog">
<div>
<q-spinner color="primary" size="2.5rem" />
Expand Down Expand Up @@ -34,6 +35,7 @@ import {
DEFAULT_BPM,
DEFAULT_TPQN,
} from "@/sing/storeHelper";
import EngineStartupOverlay from "@/components/EngineStartupOverlay.vue";
import { useStore } from "@/store";
const props = withDefaults(
Expand Down Expand Up @@ -121,6 +123,7 @@ const unwatchIsEnginesReady = watch(
.sing-main {
display: flex;
overflow: hidden;
position: relative;
}
.exporting-dialog {
Expand Down
Loading

0 comments on commit 33167d8

Please sign in to comment.