diff --git a/src/components/MenuBar.vue b/src/components/MenuBar.vue index 76f13c4b84..f556269744 100644 --- a/src/components/MenuBar.vue +++ b/src/components/MenuBar.vue @@ -49,21 +49,24 @@ export type MenuItemBase = { export type MenuItemSeparator = MenuItemBase<"separator">; export type MenuItemRoot = MenuItemBase<"root"> & { - onClick: () => void; + onClick?: () => void; subMenu: MenuItemData[]; icon?: string; + disabled?: boolean; disableWhenUiLocked: boolean; }; export type MenuItemButton = MenuItemBase<"button"> & { onClick: () => void; icon?: string; + disabled?: boolean; disableWhenUiLocked: boolean; }; export type MenuItemCheckbox = MenuItemBase<"checkbox"> & { checked: ComputedRef; onClick: () => void; + disabled?: boolean; disableWhenUiLocked: boolean; }; @@ -300,6 +303,12 @@ const menudata = ref([ }, disableWhenUiLocked: true, }, + { + type: "root", + label: "最近使ったプロジェクト", + disableWhenUiLocked: true, + subMenu: [], + }, ], }, { @@ -523,6 +532,52 @@ if (store.state.isMultiEngineOffMode) { }); } +// 「最近開いたプロジェクト」の更新 +async function updateRecentProjects() { + const projectsMenu = menudata.value.find( + (x) => x.type === "root" && x.label === "ファイル" + ) as MenuItemRoot; + const recentProjectsMenu = projectsMenu.subMenu.find( + (x) => x.type === "root" && x.label === "最近使ったプロジェクト" + ) as MenuItemRoot; + + const recentlyUsedProjects = await store.dispatch( + "GET_RECENTLY_USED_PROJECTS" + ); + recentProjectsMenu.subMenu = + recentlyUsedProjects.length === 0 + ? [ + { + type: "button", + label: "最近使ったプロジェクトはありません", + onClick: () => { + // 何もしない + }, + disabled: true, + disableWhenUiLocked: false, + }, + ] + : recentlyUsedProjects.map( + (projectFilePath) => + ({ + type: "button", + label: projectFilePath, + onClick: () => { + store.dispatch("LOAD_PROJECT_FILE", { + filePath: projectFilePath, + }); + }, + disableWhenUiLocked: false, + } as MenuItemData) + ); +} + +const projectFilePath = computed(() => store.state.projectFilePath); + +watch(projectFilePath, updateRecentProjects, { + immediate: true, +}); + watch(uiLocked, () => { // UIのロックが解除された時に再びメニューが開かれてしまうのを防ぐ if (uiLocked.value) { diff --git a/src/components/MenuButton.vue b/src/components/MenuButton.vue index 983673d3ac..2e5e75362e 100644 --- a/src/components/MenuButton.vue +++ b/src/components/MenuButton.vue @@ -13,12 +13,12 @@ :disable="disable" @click=" (menudata.type === 'button' || menudata.type === 'root') && - menudata.onClick() + menudata.onClick?.() " > {{ menudata.label }} {{ menudata.label }} + + {{ getMenuBarHotkey(menudata.label) }} + @@ -40,6 +45,7 @@ v-ripple v-close-popup class="bg-background" + :disable="menudata.disabled" @click="menudata.onClick" > diff --git a/src/store/project.ts b/src/store/project.ts index 755cafa1c0..04011af826 100755 --- a/src/store/project.ts +++ b/src/store/project.ts @@ -89,6 +89,9 @@ export const projectStore = createPartialStore({ const projectFileErrorMsg = `VOICEVOX Project file "${filePath}" is a invalid file.`; try { + await context.dispatch("APPEND_RECENTLY_USED_PROJECT", { + filePath, + }); const buf = await window.electron.readFile({ filePath }); const text = new TextDecoder("utf-8").decode(buf).trim(); const projectData = JSON.parse(text); @@ -407,6 +410,9 @@ export const projectStore = createPartialStore({ }); } + await context.dispatch("APPEND_RECENTLY_USED_PROJECT", { + filePath, + }); const appInfos = await window.electron.getAppInfos(); const { audioItems, audioKeys } = context.state; const projectData: ProjectType = { diff --git a/src/store/setting.ts b/src/store/setting.ts index e9e0df87ce..bb31b67de8 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -384,6 +384,26 @@ export const settingStore = createPartialStore({ } ), }, + + GET_RECENTLY_USED_PROJECTS: { + async action() { + return await window.electron.getSetting("recentlyUsedProjects"); + }, + }, + + APPEND_RECENTLY_USED_PROJECT: { + async action({ dispatch }, { filePath }) { + const recentlyUsedProjects = await dispatch("GET_RECENTLY_USED_PROJECTS"); + const newRecentlyUsedProjects = [ + filePath, + ...recentlyUsedProjects.filter((value) => value != filePath), + ].slice(0, 10); + await window.electron.setSetting( + "recentlyUsedProjects", + newRecentlyUsedProjects + ); + }, + }, }); export const setHotkeyFunctions = ( diff --git a/src/store/type.ts b/src/store/type.ts index e3252d8090..58198970ac 100644 --- a/src/store/type.ts +++ b/src/store/type.ts @@ -1074,6 +1074,14 @@ export type SettingStoreTypes = { CHANGE_USE_GPU: { action(payload: { useGpu: boolean; engineId: EngineId }): Promise; }; + + GET_RECENTLY_USED_PROJECTS: { + action(): Promise; + }; + + APPEND_RECENTLY_USED_PROJECT: { + action(payload: { filePath: string }): Promise; + }; }; /* diff --git a/src/type/preload.ts b/src/type/preload.ts index f717464a3d..41be660a75 100644 --- a/src/type/preload.ts +++ b/src/type/preload.ts @@ -586,6 +586,7 @@ export const electronStoreSchema = z .passthrough() .default({}), registeredEngineDirs: z.string().array().default([]), + recentlyUsedProjects: z.string().array().default([]), }) .passthrough(); export type ElectronStoreType = z.infer;