diff --git a/src/components/CharacterTryListenCard.vue b/src/components/CharacterTryListenCard.vue index 307b2c7b73..f78738b0f7 100644 --- a/src/components/CharacterTryListenCard.vue +++ b/src/components/CharacterTryListenCard.vue @@ -105,6 +105,8 @@ const props = speakerUuid: SpeakerId; styleId: StyleId; index: number; + // playingには上記の要素が必須、それ以外の要素があっても可 + [key: string]: unknown; }; togglePlayOrStop: ( speakerUuid: SpeakerId, diff --git a/src/components/LibraryManageDialog.vue b/src/components/LibraryManageDialog.vue new file mode 100644 index 0000000000..d2e820bb47 --- /dev/null +++ b/src/components/LibraryManageDialog.vue @@ -0,0 +1,620 @@ + + + + + + + 音声ライブラリの管理 + + + + + + + + + + + + + + + + + + + + + {{ engineManifests[engineId].name }} + + + 取得に失敗しました。 + + + + + + + + {{ library.name }} + + + + {{ + isLatest(engineId, library) + ? "最新版です" + : installedLibraries[engineId].find( + (installedLibrary) => + installedLibrary.uuid === library.uuid + ) + ? "アップデート" + : "インストール" + }} + + + アンインストール + + + + + + selectLibraryAndSpeaker(library.uuid, speakerUuid) + " + /> + + + + + + + + + + + + + + + diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index a85adc7511..dfce8ef628 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -233,6 +233,9 @@ const closeAllDialog = () => { store.dispatch("SET_DIALOG_OPEN", { isDefaultStyleSelectDialogOpen: false, }); + store.dispatch("SET_DIALOG_OPEN", { + isLibraryManageDialogOpen: false, + }); }; const openHelpDialog = () => { @@ -528,6 +531,22 @@ async function updateEngines() { disableWhenUiLocked: false, }); } + if ( + Object.values(engineManifests.value).some( + (e) => e.supportedFeatures?.manageLibrary + ) + ) { + engineMenu.subMenu.push({ + type: "button", + label: "音声ライブラリの管理", + onClick: () => { + store.dispatch("SET_DIALOG_OPEN", { + isLibraryManageDialogOpen: true, + }); + }, + disableWhenUiLocked: true, + }); + } } // engineInfos、engineManifests、enableMultiEngineを見て動的に更新できるようにする // FIXME: computedにする diff --git a/src/store/type.ts b/src/store/type.ts index 6f8d130360..766b313b21 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1139,6 +1139,7 @@ export type UiStoreState = { isAcceptTermsDialogOpen: boolean; isDictionaryManageDialogOpen: boolean; isEngineManageDialogOpen: boolean; + isLibraryManageDialogOpen: boolean; isMaximized: boolean; isPinned: boolean; isFullscreen: boolean; @@ -1199,6 +1200,7 @@ export type UiStoreTypes = { isToolbarSettingDialogOpen?: boolean; isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; + isLibraryManageDialogOpen?: boolean; }; action(payload: { isDefaultStyleSelectDialogOpen?: boolean; @@ -1211,6 +1213,7 @@ export type UiStoreTypes = { isToolbarSettingDialogOpen?: boolean; isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; + isLibraryManageDialogOpen?: boolean; }): void; }; diff --git a/src/store/ui.ts b/src/store/ui.ts index 9ee1c03d07..4e96fe0188 100644 --- a/src/store/ui.ts +++ b/src/store/ui.ts @@ -48,6 +48,7 @@ export const uiStoreState: UiStoreState = { isAcceptTermsDialogOpen: false, isDictionaryManageDialogOpen: false, isEngineManageDialogOpen: false, + isLibraryManageDialogOpen: false, isMaximized: false, isPinned: false, isFullscreen: false, @@ -145,6 +146,7 @@ export const uiStore = createPartialStore({ isToolbarSettingDialogOpen?: boolean; isCharacterOrderDialogOpen?: boolean; isEngineManageDialogOpen?: boolean; + isLibraryManageDialogOpen?: boolean; } ) { for (const [key, value] of Object.entries(dialogState)) { diff --git a/src/type/preload.ts b/src/type/preload.ts index d735e67220..4290751df7 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -24,6 +24,10 @@ export const audioKeySchema = z.string().brand<"AudioKey">(); export type AudioKey = z.infer; export const AudioKey = (id: string): AudioKey => audioKeySchema.parse(id); +export const libraryIdSchema = z.string().brand<"LibraryId">(); +export type LibraryId = z.infer; +export const LibraryId = (id: string): LibraryId => libraryIdSchema.parse(id); + export const presetKeySchema = z.string().brand<"PresetKey">(); export type PresetKey = z.infer; export const PresetKey = (id: string): PresetKey => presetKeySchema.parse(id); diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index 1a65d7b821..4f7f94fcae 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -160,6 +160,7 @@ /> + @@ -190,6 +191,8 @@ import AcceptTermsDialog from "@/components/AcceptTermsDialog.vue"; import DictionaryManageDialog from "@/components/DictionaryManageDialog.vue"; import EngineManageDialog from "@/components/EngineManageDialog.vue"; import ProgressDialog from "@/components/ProgressDialog.vue"; +import LibraryManageDialog from "@/components/LibraryManageDialog.vue"; + import { AudioItem, EngineState } from "@/store/type"; import { AudioKey, @@ -756,6 +759,15 @@ const isDictionaryManageDialogOpenComputed = computed({ }), }); +// 音声ライブラリのダウンロード +const isLibraryManageDialogOpenComputed = computed({ + get: () => store.state.isLibraryManageDialogOpen, + set: (val) => + store.dispatch("SET_DIALOG_OPEN", { + isLibraryManageDialogOpen: val, + }), +}); + const isAcceptRetrieveTelemetryDialogOpenComputed = computed({ get: () => !store.state.isAcceptTermsDialogOpen && diff --git a/tests/unit/store/Vuex.spec.ts b/tests/unit/store/Vuex.spec.ts index 55b464f5fd..292961ceb2 100644 --- a/tests/unit/store/Vuex.spec.ts +++ b/tests/unit/store/Vuex.spec.ts @@ -51,6 +51,7 @@ describe("store/vuex.js test", () => { isEngineManageDialogOpen: false, isAcceptRetrieveTelemetryDialogOpen: false, isAcceptTermsDialogOpen: false, + isLibraryManageDialogOpen: false, isMaximized: false, isMultiEngineOffMode: false, savedLastCommandUnixMillisec: null, @@ -118,6 +119,7 @@ describe("store/vuex.js test", () => { adjustVolumeScale: true, interrogativeUpspeak: true, synthesisMorphing: true, + manageLibrary: true, }, }, },