diff --git a/src/components/DictionaryManageDialog.vue b/src/components/DictionaryManageDialog.vue index a2cdc1ab2d..e7fe221b2a 100644 --- a/src/components/DictionaryManageDialog.vue +++ b/src/components/DictionaryManageDialog.vue @@ -25,10 +25,17 @@ -
+
-
読み込み中・・・
+
+ + +
@@ -263,8 +270,6 @@ export default defineComponent({ const store = useStore(); const $q = useQuasar(); - const engineIdComputed = computed(() => store.state.engineIds[0]); // TODO: 複数エンジン対応 - const dictionaryManageDialogOpenedComputed = computed({ get: () => props.modelValue, set: (val) => emit("update:modelValue", val), @@ -273,7 +278,7 @@ export default defineComponent({ const nowGenerating = ref(false); const nowPlaying = ref(false); - const loadingDict = ref(false); + const loadingDictState = ref("loading"); const userDict = ref>({}); const createUILockAction = function (action: Promise) { @@ -284,16 +289,13 @@ export default defineComponent({ }; const loadingDictProcess = async () => { - const engineId = engineIdComputed.value; - if (engineId === undefined) - throw new Error(`assert engineId !== undefined`); + if (store.state.engineIds.length === 0) + throw new Error(`assert engineId.length > 0`); - loadingDict.value = true; + loadingDictState.value = "loading"; try { userDict.value = await createUILockAction( - store.dispatch("LOAD_USER_DICT", { - engineId, - }) + store.dispatch("LOAD_ALL_USER_DICT") ); } catch { $q.dialog({ @@ -308,7 +310,21 @@ export default defineComponent({ dictionaryManageDialogOpenedComputed.value = false; }); } - loadingDict.value = false; + loadingDictState.value = "synchronizing"; + try { + await createUILockAction(store.dispatch("SYNC_ALL_USER_DICT")); + } catch { + $q.dialog({ + title: "辞書の同期に失敗しました", + message: "エンジンの再起動をお試しください。", + ok: { + label: "閉じる", + flat: true, + textColor: "display", + }, + }); + } + loadingDictState.value = null; }; watch(dictionaryManageDialogOpenedComputed, async (newValue) => { if (newValue) { @@ -345,6 +361,14 @@ export default defineComponent({ return store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas.styles[0] .styleId; }); + const engineIdComputed = computed(() => { + if (store.state.engineIds.length === 0) + throw new Error("assert engineId.length > 0"); + if (!store.getters.USER_ORDERED_CHARACTER_INFOS) + throw new Error("assert USER_ORDERED_CHARACTER_INFOS"); + return store.getters.USER_ORDERED_CHARACTER_INFOS[0].metas.styles[0] + .engineId; + }); const kanaRegex = createKanaRegex(); const isOnlyHiraOrKana = ref(true); @@ -710,7 +734,7 @@ export default defineComponent({ nowGenerating, nowPlaying, userDict, - loadingDict, + loadingDictState, wordEditing, surfaceInput, yomiInput, diff --git a/src/store/dictionary.ts b/src/store/dictionary.ts index 4305491f6f..d5a5a84310 100644 --- a/src/store/dictionary.ts +++ b/src/store/dictionary.ts @@ -1,4 +1,4 @@ -import { UserDictWord } from "@/openapi"; +import { UserDictWord, UserDictWordToJSON } from "@/openapi"; import { DictionaryStoreState, DictionaryStoreTypes } from "@/store/type"; import { createPartialStore } from "./vuex"; @@ -30,12 +30,42 @@ export const dictionaryStore = createPartialStore({ }, }, + LOAD_ALL_USER_DICT: { + async action({ dispatch, state }) { + const allDict = await Promise.all( + state.engineIds.map((engineId) => { + return dispatch("LOAD_USER_DICT", { engineId }); + }) + ); + const mergedDictMap = new Map(); + for (const dict of allDict) { + for (const [id, dictItem] of Object.entries(dict)) { + mergedDictMap.set(`${dictItem.yomi}-${dictItem.surface}`, [ + id, + dictItem, + ]); + } + } + const mergedDict = [...mergedDictMap.values()]; + mergedDict.sort((a, b) => { + if (a[1].yomi > b[1].yomi) { + return 1; + } else { + return -1; + } + }); + return Object.fromEntries(mergedDict); + }, + }, + ADD_WORD: { async action( { state, dispatch }, { surface, pronunciation, accentType, priority } ) { - const engineId: string | undefined = state.engineIds[0]; // TODO: 複数エンジン対応 + // 同じ単語IDで登録するために、1つのエンジンで登録したあと全エンジンに同期する。 + const engineId: string | undefined = state.engineIds[0]; + if (engineId === undefined) throw new Error(`No such engine registered: index == 0`); await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { @@ -48,6 +78,8 @@ export const dictionaryStore = createPartialStore({ priority, }) ); + + await dispatch("SYNC_ALL_USER_DICT"); }, }, @@ -56,35 +88,95 @@ export const dictionaryStore = createPartialStore({ { state, dispatch }, { wordUuid, surface, pronunciation, accentType, priority } ) { - const engineId: string | undefined = state.engineIds[0]; // TODO: 複数エンジン対応 - if (engineId === undefined) - throw new Error(`No such engine registered: index == 0`); - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then((instance) => - instance.invoke("rewriteUserDictWordUserDictWordWordUuidPut")({ - wordUuid, - surface, - pronunciation, - accentType, - priority, - }) - ); + if (state.engineIds.length === 0) + throw new Error(`At least one engine must be registered`); + for (const engineId of state.engineIds) { + await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { + engineId, + }).then((instance) => + instance.invoke("rewriteUserDictWordUserDictWordWordUuidPut")({ + wordUuid, + surface, + pronunciation, + accentType, + priority, + }) + ); + } }, }, DELETE_WORD: { async action({ state, dispatch }, { wordUuid }) { - const engineId: string | undefined = state.engineIds[0]; // TODO: 複数エンジン対応 - if (engineId === undefined) - throw new Error(`No such engine registered: index == 0`); - await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { - engineId, - }).then((instance) => - instance.invoke("deleteUserDictWordUserDictWordWordUuidDelete")({ - wordUuid, - }) - ); + if (state.engineIds.length === 0) + throw new Error(`At least one engine must be registered`); + for (const engineId of state.engineIds) { + await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { + engineId, + }).then((instance) => + instance.invoke("deleteUserDictWordUserDictWordWordUuidDelete")({ + wordUuid, + }) + ); + } + }, + }, + + SYNC_ALL_USER_DICT: { + async action({ dispatch, state }) { + const mergedDict = await dispatch("LOAD_ALL_USER_DICT"); + for (const engineId of state.engineIds) { + // エンジンの辞書のIDリストを取得する。 + const dictIdSet = await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { + engineId, + }).then( + async (instance) => + new Set( + Object.keys( + await instance.invoke("getUserDictWordsUserDictGet")({}) + ) + ) + ); + if (Object.keys(mergedDict).some((id) => !dictIdSet.has(id))) { + await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { + engineId, + }).then((instance) => + // マージした辞書をエンジンにインポートする。 + instance.invoke("importUserDictWordsImportUserDictPost")({ + override: true, + requestBody: Object.fromEntries( + Object.entries(mergedDict).map(([k, v]) => [ + k, + UserDictWordToJSON(v), + ]) + ), + }) + ); + } + const removedDictIdSet = new Set(dictIdSet); + // マージされた辞書にあるIDを削除する。 + // これにより、マージ処理で削除された項目のIDが残る。 + for (const id of Object.keys(mergedDict)) { + if (removedDictIdSet.has(id)) { + removedDictIdSet.delete(id); + } + } + + await dispatch("INSTANTIATE_ENGINE_CONNECTOR", { engineId }).then( + (instance) => { + // マージ処理で削除された項目をエンジンから削除する。 + Promise.all( + [...removedDictIdSet].map((id) => + instance.invoke("deleteUserDictWordUserDictWordWordUuidDelete")( + { + wordUuid: id, + } + ) + ) + ); + } + ); + } }, }, }); diff --git a/src/store/type.ts b/src/store/type.ts index ebc8b07eb5..d37be3cb3c 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1144,6 +1144,9 @@ export type DictionaryStoreTypes = { engineId: string; }): Promise>; }; + LOAD_ALL_USER_DICT: { + action(): Promise>; + }; ADD_WORD: { action(payload: { surface: string; @@ -1164,6 +1167,9 @@ export type DictionaryStoreTypes = { DELETE_WORD: { action(payload: { wordUuid: string }): Promise; }; + SYNC_ALL_USER_DICT: { + action(): Promise; + }; }; /* diff --git a/src/views/EditorHome.vue b/src/views/EditorHome.vue index 6253342ab2..669830f62d 100644 --- a/src/views/EditorHome.vue +++ b/src/views/EditorHome.vue @@ -477,6 +477,9 @@ export default defineComponent({ await store.dispatch("LOAD_USER_CHARACTER_ORDER"); await store.dispatch("LOAD_DEFAULT_STYLE_IDS"); + // 辞書を同期 + await store.dispatch("SYNC_ALL_USER_DICT"); + // 新キャラが追加されている場合はキャラ並び替えダイアログを表示 const newCharacters = await store.dispatch("GET_NEW_CHARACTERS"); isCharacterOrderDialogOpenComputed.value = newCharacters.length > 0;