Skip to content

Commit

Permalink
複数エンジン対応:複数の辞書に対する操作を追加 (#980)
Browse files Browse the repository at this point in the history
* Add: 同期機能を追加

* Add: ソートを追加

* Fix: 同じ単語があるときの挙動を修正

* Add: 削除を実装

* Add: 編集を実装

* Add: 起動時の辞書同期を追加

* Add: コメントを追加

* Add: エンジンを選択できるように

* Revert: エンジンの選択機能を削除

* Refactor: loadingTypeとloadingDictをloadingDictStateにまとめる

* Refactor: マージ処理をリファクタ

* Refactor: マージ処理のコードの順序を変更

* Fix: メインエンジン以外のキャラクターが一番上に来たときの挙動を修正

* Add: 同期処理をADD_WORD内でも行うように

* Add: return "の代わりにthrowするように

* Code: ESLintのエラーを修正

* Improve: import_user_dictを必要なときだけ叩くように

* Update src/store/dictionary.ts

Co-authored-by: Hiroshiba <hihokaruta@gmail.com>

Co-authored-by: Hiroshiba <hihokaruta@gmail.com>
  • Loading branch information
sevenc-nanashi and Hiroshiba authored Oct 21, 2022
1 parent 4eafadd commit a01bd2b
Show file tree
Hide file tree
Showing 4 changed files with 165 additions and 40 deletions.
52 changes: 38 additions & 14 deletions src/components/DictionaryManageDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,17 @@
</q-toolbar>
</q-header>
<q-page class="row">
<div v-if="loadingDict" class="loading-dict">
<div v-if="loadingDictState" class="loading-dict">
<div>
<q-spinner color="primary" size="2.5rem" />
<div class="q-mt-xs">読み込み中・・・</div>
<div class="q-mt-xs">
<template v-if="loadingDictState === 'loading'"
>読み込み中・・・</template
>
<template v-if="loadingDictState === 'synchronizing'"
>同期中・・・</template
>
</div>
</div>
</div>
<div class="col-4 word-list-col">
Expand Down Expand Up @@ -264,8 +271,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),
Expand All @@ -274,7 +279,7 @@ export default defineComponent({
const nowGenerating = ref(false);
const nowPlaying = ref(false);
const loadingDict = ref(false);
const loadingDictState = ref<null | "loading" | "synchronizing">("loading");
const userDict = ref<Record<string, UserDictWord>>({});
const createUILockAction = function <T>(action: Promise<T>) {
Expand All @@ -285,16 +290,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({
Expand All @@ -309,7 +311,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) {
Expand Down Expand Up @@ -346,6 +362,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);
Expand Down Expand Up @@ -718,7 +742,7 @@ export default defineComponent({
nowGenerating,
nowPlaying,
userDict,
loadingDict,
loadingDictState,
wordEditing,
surfaceInput,
yomiInput,
Expand Down
144 changes: 118 additions & 26 deletions src/store/dictionary.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UserDictWord } from "@/openapi";
import { UserDictWord, UserDictWordToJSON } from "@/openapi";
import { DictionaryStoreState, DictionaryStoreTypes } from "@/store/type";
import { createPartialStore } from "./vuex";

Expand Down Expand Up @@ -30,12 +30,42 @@ export const dictionaryStore = createPartialStore<DictionaryStoreTypes>({
},
},

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<string, [string, UserDictWord]>();
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", {
Expand All @@ -48,6 +78,8 @@ export const dictionaryStore = createPartialStore<DictionaryStoreTypes>({
priority,
})
);

await dispatch("SYNC_ALL_USER_DICT");
},
},

Expand All @@ -56,35 +88,95 @@ export const dictionaryStore = createPartialStore<DictionaryStoreTypes>({
{ 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,
}
)
)
);
}
);
}
},
},
});
6 changes: 6 additions & 0 deletions src/store/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,9 @@ export type DictionaryStoreTypes = {
engineId: string;
}): Promise<Record<string, UserDictWord>>;
};
LOAD_ALL_USER_DICT: {
action(): Promise<Record<string, UserDictWord>>;
};
ADD_WORD: {
action(payload: {
surface: string;
Expand All @@ -1164,6 +1167,9 @@ export type DictionaryStoreTypes = {
DELETE_WORD: {
action(payload: { wordUuid: string }): Promise<void>;
};
SYNC_ALL_USER_DICT: {
action(): Promise<void>;
};
};

/*
Expand Down
3 changes: 3 additions & 0 deletions src/views/EditorHome.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit a01bd2b

Please sign in to comment.