From 659cdf86cc1dc8b95b126b2ff8b396b3419d5c2f Mon Sep 17 00:00:00 2001 From: jdkfx Date: Mon, 30 Sep 2024 23:09:52 +0900 Subject: [PATCH 01/10] =?UTF-8?q?=E3=83=80=E3=82=A4=E3=82=A2=E3=83=AD?= =?UTF-8?q?=E3=82=B0=E3=81=AE=E8=A4=87=E8=A3=BD=E3=81=A8=E3=83=86=E3=83=B3?= =?UTF-8?q?=E3=83=97=E3=83=AC=E3=83=BC=E3=83=88=E9=83=A8=E5=88=86=E3=81=AE?= =?UTF-8?q?=E5=88=87=E3=82=8A=E5=87=BA=E3=81=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dialog/DictionaryEditWordDialog.vue | 677 ++++++++++++++++++ .../Dialog/DictionaryManageDialog.vue | 267 +------ 2 files changed, 679 insertions(+), 265 deletions(-) create mode 100644 src/components/Dialog/DictionaryEditWordDialog.vue diff --git a/src/components/Dialog/DictionaryEditWordDialog.vue b/src/components/Dialog/DictionaryEditWordDialog.vue new file mode 100644 index 0000000000..6b86ca9b45 --- /dev/null +++ b/src/components/Dialog/DictionaryEditWordDialog.vue @@ -0,0 +1,677 @@ + + + + + diff --git a/src/components/Dialog/DictionaryManageDialog.vue b/src/components/Dialog/DictionaryManageDialog.vue index 2d4c1dcfb0..3b4c4dd55b 100644 --- a/src/components/Dialog/DictionaryManageDialog.vue +++ b/src/components/Dialog/DictionaryManageDialog.vue @@ -116,169 +116,7 @@ - -
-
-
単語
- - - -
-
-
読み
- - - - -
-
アクセント調整
-
- 語尾のアクセントを考慮するため、「が」が自動で挿入されます。 -
-
-
- - -
-
-
- - -
-
-
-
単語優先度
-
- 単語を登録しても反映されない場合は優先度を高くしてください。 -
-
- -
-
- - リセット - キャンセル - 保存 -
-
+ @@ -288,9 +126,7 @@ From 4298f647278bcdcdae8b4273d714290a85567a2f Mon Sep 17 00:00:00 2001 From: jdkfx Date: Tue, 3 Dec 2024 23:54:18 +0900 Subject: [PATCH 02/10] =?UTF-8?q?=E9=9F=B3=E5=A3=B0=E5=86=8D=E7=94=9F?= =?UTF-8?q?=E6=A9=9F=E6=A7=8B=E3=81=AE=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dialog/DictionaryEditWordDialog.vue | 366 +----------------- .../Dialog/DictionaryManageDialog.vue | 41 -- 2 files changed, 7 insertions(+), 400 deletions(-) diff --git a/src/components/Dialog/DictionaryEditWordDialog.vue b/src/components/Dialog/DictionaryEditWordDialog.vue index 1e95935b6c..cc6452f8f1 100644 --- a/src/components/Dialog/DictionaryEditWordDialog.vue +++ b/src/components/Dialog/DictionaryEditWordDialog.vue @@ -175,186 +175,15 @@ import { createKanaRegex, } from "@/domain/japanese"; -const defaultDictPriority = 5; - -const props = defineProps<{ - modelValue: boolean; -}>(); -const emit = defineEmits<{ - (e: "update:modelValue", v: boolean): void; -}>(); - -const store = useStore(); - -const dictionaryManageDialogOpenedComputed = computed({ - get: () => props.modelValue, - set: (val) => emit("update:modelValue", val), -}); -const uiLocked = ref(false); // ダイアログ内でstore.getters.UI_LOCKEDは常にtrueなので独自に管理 +// 音声再生機構 const nowGenerating = ref(false); const nowPlaying = ref(false); -// word-list の要素のうち、どの要素がホバーされているか -const hoveredKey = ref(undefined); - -const loadingDictState = ref("loading"); -const userDict = ref>({}); - -const createUILockAction = function (action: Promise) { - uiLocked.value = true; - return action.finally(() => { - uiLocked.value = false; - }); -}; - -const loadingDictProcess = async () => { - if (store.state.engineIds.length === 0) - throw new Error(`assert engineId.length > 0`); - - loadingDictState.value = "loading"; - try { - userDict.value = await createUILockAction( - store.dispatch("LOAD_ALL_USER_DICT"), - ); - } catch { - const result = await store.dispatch("SHOW_ALERT_DIALOG", { - title: "辞書の取得に失敗しました", - message: "エンジンの再起動をお試しください。", - }); - if (result === "OK") { - dictionaryManageDialogOpenedComputed.value = false; - } - } - loadingDictState.value = "synchronizing"; - try { - await createUILockAction(store.dispatch("SYNC_ALL_USER_DICT")); - } catch { - await store.dispatch("SHOW_ALERT_DIALOG", { - title: "辞書の同期に失敗しました", - message: "エンジンの再起動をお試しください。", - }); - } - loadingDictState.value = null; -}; -watch(dictionaryManageDialogOpenedComputed, async (newValue) => { - if (newValue) { - await loadingDictProcess(); - toInitialState(); - } -}); - -const wordEditing = ref(false); - -const surfaceInput = ref(); -const yomiInput = ref(); -const yomiFocus = (event?: KeyboardEvent) => { - if (event && event.isComposing) return; - yomiInput.value?.focus(); -}; -const setYomiWhenEnter = (event?: KeyboardEvent) => { - if (event && event.isComposing) return; - void setYomi(yomi.value); -}; - -const selectedId = ref(""); -const surface = ref(""); -const yomi = ref(""); - -const voiceComputed = computed(() => { - const userOrderedCharacterInfos = - store.getters.USER_ORDERED_CHARACTER_INFOS("talk"); - if (userOrderedCharacterInfos == undefined) - throw new Error("assert USER_ORDERED_CHARACTER_INFOS"); - if (store.state.engineIds.length === 0) - throw new Error("assert engineId.length > 0"); - const characterInfo = userOrderedCharacterInfos[0].metas; - const speakerId = characterInfo.speakerUuid; - const { engineId, styleId } = characterInfo.styles[0]; - return { engineId, speakerId, styleId }; -}); - -const kanaRegex = createKanaRegex(); -const isOnlyHiraOrKana = ref(true); -const accentPhrase = ref(); -const accentPhraseTable = ref(); - -const convertHankakuToZenkaku = (text: string) => { - // " "などの目に見えない文字をまとめて全角スペース(0x3000)に置き換える - text = text.replace(/\p{Z}/gu, () => String.fromCharCode(0x3000)); - - // "!"から"~"までの範囲の文字(数字やアルファベット)を全角に置き換える - return text.replace(/[\u0021-\u007e]/g, (s) => { - return String.fromCharCode(s.charCodeAt(0) + 0xfee0); - }); -}; -const setSurface = (text: string) => { - // surfaceを全角化する - // 入力は半角でも問題ないが、登録時に全角に変換され、isWordChangedの判断がおかしくなることがあるので、 - // 入力後に自動で変換するようにする - surface.value = convertHankakuToZenkaku(text); -}; -const setYomi = async (text: string, changeWord?: boolean) => { - const { engineId, styleId } = voiceComputed.value; - - // テキスト長が0の時にエラー表示にならないように、テキスト長を考慮する - isOnlyHiraOrKana.value = !text.length || kanaRegex.test(text); - // 読みが変更されていない場合は、アクセントフレーズに変更を加えない - // ただし、読みが同じで違う単語が存在する場合が考えられるので、changeWordフラグを考慮する - // 「ガ」が自動挿入されるので、それを考慮してsliceしている - if ( - text == - accentPhrase.value?.moras - .map((v) => v.text) - .join("") - .slice(0, -1) && - !changeWord - ) { - return; - } - if (isOnlyHiraOrKana.value && text.length) { - text = convertHiraToKana(text); - text = convertLongVowel(text); - accentPhrase.value = ( - await createUILockAction( - store.dispatch("FETCH_ACCENT_PHRASES", { - text: text + "ガ'", - engineId, - styleId, - isKana: true, - }), - ) - )[0]; - if (selectedId.value && userDict.value[selectedId.value].yomi === text) { - accentPhrase.value.accent = computeDisplayAccent(); - } - } else { - accentPhrase.value = undefined; - } - yomi.value = text; -}; - -const changeAccent = async (_: number, accent: number) => { - const { engineId, styleId } = voiceComputed.value; - - if (accentPhrase.value) { - accentPhrase.value.accent = accent; - accentPhrase.value = ( - await createUILockAction( - store.dispatch("FETCH_MORA_DATA", { - accentPhrases: [accentPhrase.value], - engineId, - styleId, - }), - ) - )[0]; - } -}; - const play = async () => { if (!accentPhrase.value) return; nowGenerating.value = true; - const audioItem = await store.dispatch("GENERATE_AUDIO_ITEM", { + const audioItem = await store.actions.GENERATE_AUDIO_ITEM({ text: yomi.value, voice: voiceComputed.value, }); @@ -366,13 +195,13 @@ const play = async () => { let fetchAudioResult: FetchAudioResult; try { - fetchAudioResult = await store.dispatch("FETCH_AUDIO_FROM_AUDIO_ITEM", { + fetchAudioResult = await store.actions.FETCH_AUDIO_FROM_AUDIO_ITEM({ audioItem, }); } catch (e) { window.backend.logError(e); nowGenerating.value = false; - void store.dispatch("SHOW_ALERT_DIALOG", { + void store.actions.SHOW_ALERT_DIALOG({ title: "生成に失敗しました", message: "エンジンの再起動をお試しください。", }); @@ -382,193 +211,12 @@ const play = async () => { const { blob } = fetchAudioResult; nowGenerating.value = false; nowPlaying.value = true; - await store.dispatch("PLAY_AUDIO_BLOB", { audioBlob: blob }); + await store.actions.PLAY_AUDIO_BLOB({ audioBlob: blob }); nowPlaying.value = false; }; -const stop = () => { - void store.dispatch("STOP_AUDIO"); -}; - -// accent phraseにあるaccentと実際に登録するアクセントには差が生まれる -// アクセントが自動追加される「ガ」に指定されている場合、 -// 実際に登録するaccentの値は0となるので、そうなるように処理する -const computeRegisteredAccent = () => { - if (!accentPhrase.value) throw new Error(); - let accent = accentPhrase.value.accent; - accent = accent === accentPhrase.value.moras.length ? 0 : accent; - return accent; -}; -// computeの逆 -// 辞書から得たaccentが0の場合に、自動で追加される「ガ」の位置にアクセントを表示させるように処理する -const computeDisplayAccent = () => { - if (!accentPhrase.value || !selectedId.value) throw new Error(); - let accent = userDict.value[selectedId.value].accentType; - accent = accent === 0 ? accentPhrase.value.moras.length : accent; - return accent; -}; - -const wordPriority = ref(defaultDictPriority); -const wordPriorityLabels = { - 0: "最低", - 3: "低", - 5: "標準", - 7: "高", - 10: "最高", -}; - -// 操作(ステートの移動) -const isWordChanged = computed(() => { - if (selectedId.value === "") { - return surface.value && yomi.value && accentPhrase.value; - } - // 一旦代入することで、userDictそのものが更新された時もcomputedするようにする - const dict = userDict.value; - const dictData = dict[selectedId.value]; - return ( - dictData && - (dictData.surface !== surface.value || - dictData.yomi !== yomi.value || - dictData.accentType !== computeRegisteredAccent() || - dictData.priority !== wordPriority.value) - ); -}); -const saveWord = async () => { - if (!accentPhrase.value) throw new Error(`accentPhrase === undefined`); - const accent = computeRegisteredAccent(); - if (selectedId.value) { - try { - await store.dispatch("REWRITE_WORD", { - wordUuid: selectedId.value, - surface: surface.value, - pronunciation: yomi.value, - accentType: accent, - priority: wordPriority.value, - }); - } catch { - void store.dispatch("SHOW_ALERT_DIALOG", { - title: "単語の更新に失敗しました", - message: "エンジンの再起動をお試しください。", - }); - return; - } - } else { - try { - await createUILockAction( - store.dispatch("ADD_WORD", { - surface: surface.value, - pronunciation: yomi.value, - accentType: accent, - priority: wordPriority.value, - }), - ); - } catch { - void store.dispatch("SHOW_ALERT_DIALOG", { - title: "単語の登録に失敗しました", - message: "エンジンの再起動をお試しください。", - }); - return; - } - } - await loadingDictProcess(); - toInitialState(); -}; -const deleteWord = async () => { - const result = await store.dispatch("SHOW_WARNING_DIALOG", { - title: "登録された単語を削除しますか?", - message: "削除された単語は元に戻せません。", - actionName: "削除", - }); - if (result === "OK") { - try { - await createUILockAction( - store.dispatch("DELETE_WORD", { - wordUuid: selectedId.value, - }), - ); - } catch { - void store.dispatch("SHOW_ALERT_DIALOG", { - title: "単語の削除に失敗しました", - message: "エンジンの再起動をお試しください。", - }); - return; - } - await loadingDictProcess(); - toInitialState(); - } -}; -const resetWord = async (id: string) => { - const result = await store.dispatch("SHOW_WARNING_DIALOG", { - title: "単語の変更をリセットしますか?", - message: "単語の変更は破棄されてリセットされます。", - actionName: "リセット", - }); - if (result === "OK") { - selectedId.value = id; - surface.value = userDict.value[id].surface; - void setYomi(userDict.value[id].yomi, true); - wordPriority.value = userDict.value[id].priority; - toWordEditingState(); - } -}; -const discardOrNotDialog = async (okCallback: () => void) => { - if (isWordChanged.value) { - const result = await store.dispatch("SHOW_WARNING_DIALOG", { - title: "単語の追加・変更を破棄しますか?", - message: "破棄すると、単語の追加・変更はリセットされます。", - actionName: "破棄", - }); - if (result === "OK") { - okCallback(); - } - } else { - okCallback(); - } -}; -const newWord = () => { - selectedId.value = ""; - surface.value = ""; - void setYomi(""); - wordPriority.value = defaultDictPriority; - editWord(); -}; -const editWord = () => { - toWordEditingState(); -}; -const selectWord = (id: string) => { - selectedId.value = id; - surface.value = userDict.value[id].surface; - void setYomi(userDict.value[id].yomi, true); - wordPriority.value = userDict.value[id].priority; - toWordSelectedState(); -}; -const cancel = () => { - toInitialState(); -}; -const closeDialog = () => { - toDialogClosedState(); -}; -// ステートの移動 -// 初期状態 -const toInitialState = () => { - wordEditing.value = false; - selectedId.value = ""; - surface.value = ""; - void setYomi(""); - wordPriority.value = defaultDictPriority; -}; -// 単語が選択されているだけの状態 -const toWordSelectedState = () => { - wordEditing.value = false; -}; -// 単語が編集されている状態 -const toWordEditingState = () => { - wordEditing.value = true; - surfaceInput.value?.focus(); -}; -// ダイアログが閉じている状態 -const toDialogClosedState = () => { - dictionaryManageDialogOpenedComputed.value = false; +const stop = () => { + void store.actions.STOP_AUDIO(); }; const surfaceContextMenu = ref>(); diff --git a/src/components/Dialog/DictionaryManageDialog.vue b/src/components/Dialog/DictionaryManageDialog.vue index 239b9fbdce..391ed5715c 100644 --- a/src/components/Dialog/DictionaryManageDialog.vue +++ b/src/components/Dialog/DictionaryManageDialog.vue @@ -152,8 +152,6 @@ const dictionaryManageDialogOpenedComputed = computed({ set: (val) => emit("update:modelValue", val), }); const uiLocked = ref(false); // ダイアログ内でstore.getters.UI_LOCKEDは常にtrueなので独自に管理 -const nowGenerating = ref(false); -const nowPlaying = ref(false); // word-list の要素のうち、どの要素がホバーされているか const hoveredKey = ref(undefined); @@ -311,45 +309,6 @@ const changeAccent = async (_: number, accent: number) => { } }; -const play = async () => { - if (!accentPhrase.value) return; - - nowGenerating.value = true; - const audioItem = await store.actions.GENERATE_AUDIO_ITEM({ - text: yomi.value, - voice: voiceComputed.value, - }); - - if (audioItem.query == undefined) - throw new Error(`assert audioItem.query !== undefined`); - - audioItem.query.accentPhrases = [accentPhrase.value]; - - let fetchAudioResult: FetchAudioResult; - try { - fetchAudioResult = await store.actions.FETCH_AUDIO_FROM_AUDIO_ITEM({ - audioItem, - }); - } catch (e) { - window.backend.logError(e); - nowGenerating.value = false; - void store.actions.SHOW_ALERT_DIALOG({ - title: "生成に失敗しました", - message: "エンジンの再起動をお試しください。", - }); - return; - } - - const { blob } = fetchAudioResult; - nowGenerating.value = false; - nowPlaying.value = true; - await store.actions.PLAY_AUDIO_BLOB({ audioBlob: blob }); - nowPlaying.value = false; -}; -const stop = () => { - void store.actions.STOP_AUDIO(); -}; - // accent phraseにあるaccentと実際に登録するアクセントには差が生まれる // アクセントが自動追加される「ガ」に指定されている場合、 // 実際に登録するaccentの値は0となるので、そうなるように処理する From 53d2e0bee65c386220cf06b0aad3c55bf079ff6b Mon Sep 17 00:00:00 2001 From: jdkfx Date: Tue, 3 Dec 2024 23:59:33 +0900 Subject: [PATCH 03/10] =?UTF-8?q?=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC?= =?UTF-8?q?=E3=80=81=E3=82=A2=E3=82=AF=E3=82=BB=E3=83=B3=E3=83=88=E9=96=A2?= =?UTF-8?q?=E9=80=A3=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Dialog/DictionaryEditWordDialog.vue | 32 +++++++++++++++---- .../Dialog/DictionaryManageDialog.vue | 21 ------------ 2 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/components/Dialog/DictionaryEditWordDialog.vue b/src/components/Dialog/DictionaryEditWordDialog.vue index cc6452f8f1..b57676d13c 100644 --- a/src/components/Dialog/DictionaryEditWordDialog.vue +++ b/src/components/Dialog/DictionaryEditWordDialog.vue @@ -161,19 +161,15 @@