Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

複数エンジン対応:複数の辞書に対する操作を追加 #980

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -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),
Expand All @@ -273,7 +278,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 @@ -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({
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -710,7 +734,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: {
sevenc-nanashi marked this conversation as resolved.
Show resolved Hide resolved
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