From 9d55fb9f2f314684bc60efc33bc4a1981e17b22c Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sat, 3 Feb 2024 23:07:19 +0900 Subject: [PATCH 01/47] =?UTF-8?q?Add:=20HotkeyManager=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/composables/useHotkeyManager.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/composables/useHotkeyManager.ts diff --git a/src/composables/useHotkeyManager.ts b/src/composables/useHotkeyManager.ts new file mode 100644 index 0000000000..877a95a65d --- /dev/null +++ b/src/composables/useHotkeyManager.ts @@ -0,0 +1,32 @@ +import { HotkeySettingType } from "@/type/preload"; + +let hotkeyManager: HotkeyManager | undefined = undefined; +export const useHotkeyManager = () => { + if (!hotkeyManager) { + hotkeyManager = new HotkeyManager(); + } + return hotkeyManager; +}; + +export class HotkeyManager { + load(data: HotkeySettingType[]): void { + throw new Error("unimplemented"); + } + + refresh(): void { + throw new Error("unimplemented"); + } + + replace(data: HotkeySettingType): void { + throw new Error("unimplemented"); + } + + register(data: { + when: "talk" | "sing"; + enableInTextbox: boolean; + name: string; + action: () => void; + }): void { + throw new Error("unimplemented"); + } +} From 421903b0778ddca2b944808c073465ae01f7ea4a Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sat, 3 Feb 2024 23:47:15 +0900 Subject: [PATCH 02/47] =?UTF-8?q?Add:=20=E3=81=A8=E3=82=8A=E3=81=82?= =?UTF-8?q?=E3=81=88=E3=81=9AUndo/Redo=E3=81=AF=E5=8B=95=E3=81=84=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/HeaderBar.vue | 68 ++++++++++++++++--------- src/components/Talk/EditorHome.vue | 49 +++++++++--------- src/composables/useHotkeyManager.ts | 78 +++++++++++++++++++++++++---- src/store/setting.ts | 3 ++ 4 files changed, 140 insertions(+), 58 deletions(-) diff --git a/src/components/HeaderBar.vue b/src/components/HeaderBar.vue index 04a8fedb14..2a98f6e648 100644 --- a/src/components/HeaderBar.vue +++ b/src/components/HeaderBar.vue @@ -33,6 +33,7 @@ import { ToolbarButtonTagType, } from "@/type/preload"; import { getToolbarButtonName } from "@/store/utility"; +import { useHotkeyManager } from "@/composables/useHotkeyManager"; type ButtonContent = { text: string; @@ -54,29 +55,50 @@ const nowPlayingContinuously = computed( () => store.state.nowPlayingContinuously ); -const undoRedoHotkeyMap = new Map HotkeyReturnType>([ - // undo - [ - "元に戻す", - () => { - if (!uiLocked.value && canUndo.value) { - undo(); - } - return false; - }, - ], - // redo - [ - "やり直す", - () => { - if (!uiLocked.value && canRedo.value) { - redo(); - } - return false; - }, - ], -]); -setHotkeyFunctions(undoRedoHotkeyMap); +const hotkeyManager = useHotkeyManager(); +// // undo +// [ +// "元に戻す", +// () => { +// if (!uiLocked.value && canUndo.value) { +// undo(); +// } +// return false; +// }, +// ], +// // redo +// [ +// "やり直す", +// () => { +// if (!uiLocked.value && canRedo.value) { +// redo(); +// } +// return false; +// }, +// ], + +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "元に戻す", + callback: () => { + if (!uiLocked.value && canUndo.value) { + undo(); + } + return false; + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "やり直す", + callback: () => { + if (!uiLocked.value && canRedo.value) { + redo(); + } + return false; + }, +}); const hotkeyMap = new Map HotkeyReturnType>([ // play/stop continuously diff --git a/src/components/Talk/EditorHome.vue b/src/components/Talk/EditorHome.vue index 54e996276d..2dccf3424b 100644 --- a/src/components/Talk/EditorHome.vue +++ b/src/components/Talk/EditorHome.vue @@ -218,6 +218,7 @@ import { } from "@/type/preload"; import { filterCharacterInfosByStyleType } from "@/store/utility"; import { parseCombo, setHotkeyFunctions } from "@/store/setting"; +import { useHotkeyManager } from "@/composables/useHotkeyManager"; const props = defineProps<{ @@ -234,30 +235,30 @@ const reloadingLocked = computed(() => store.state.reloadingLock); const isMultipleEngine = computed(() => store.state.engineIds.length > 1); // hotkeys handled by Mousetrap -const hotkeyMap = new Map HotkeyReturnType>([ - [ - "テキスト欄にフォーカスを戻す", - () => { - if (activeAudioKey.value != undefined) { - focusCell({ audioKey: activeAudioKey.value, focusTarget: "textField" }); - } - return false; // this is the same with event.preventDefault() - }, - ], - [ - // FIXME: テキスト欄にフォーカスがある状態でも実行できるようにする - // https://github.com/VOICEVOX/voicevox/pull/1096#issuecomment-1378651920 - "テキスト欄を複製", - () => { - if (activeAudioKey.value != undefined) { - duplicateAudioItem(); - } - return false; - }, - ], -]); - -setHotkeyFunctions(hotkeyMap); +const hotkeyManager = useHotkeyManager(); + +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "テキスト欄にフォーカスを戻す", + callback: () => { + if (activeAudioKey.value != undefined) { + focusCell({ audioKey: activeAudioKey.value, focusTarget: "textField" }); + } + return false; + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: true, + action: "テキスト欄を複製", + callback: () => { + if (activeAudioKey.value != undefined) { + duplicateAudioItem(); + } + return false; + }, +}); const removeAudioItem = async () => { if (activeAudioKey.value == undefined) throw new Error(); diff --git a/src/composables/useHotkeyManager.ts b/src/composables/useHotkeyManager.ts index 877a95a65d..64617bcc97 100644 --- a/src/composables/useHotkeyManager.ts +++ b/src/composables/useHotkeyManager.ts @@ -1,4 +1,5 @@ -import { HotkeySettingType } from "@/type/preload"; +import Mousetrap from "mousetrap"; +import { HotkeyActionType, HotkeySettingType } from "@/type/preload"; let hotkeyManager: HotkeyManager | undefined = undefined; export const useHotkeyManager = () => { @@ -8,25 +9,80 @@ export const useHotkeyManager = () => { return hotkeyManager; }; +type HotkeyRegistration = { + editor: "talk" | "song"; + enableInTextbox: boolean; + action: HotkeyActionType; + callback: () => boolean; +}; + export class HotkeyManager { + actions: HotkeyRegistration[] = []; + settings: HotkeySettingType[] = []; + registered: Partial> = {}; + load(data: HotkeySettingType[]): void { - throw new Error("unimplemented"); + const newSettings = this.settings.filter( + (s) => !data.find((d) => d.action === s.action) + ); + this.settings = newSettings.concat(data); + this.refresh(); } - refresh(): void { - throw new Error("unimplemented"); + private refresh(): void { + if (this.settings.length === 0) { + return; + } + const changedActions = this.actions.filter( + (a) => + this.registered[a.action] !== + this.settings.find((s) => s.action === a.action)?.combination + ); + for (const action of changedActions) { + const registered = this.registered[action.action]; + if (registered) { + Mousetrap.unbind(hotkeyToCombo(registered)); + } + const setting = this.settings.find((s) => s.action === action.action); + if (!setting) { + // unreachableのはず + throw new Error("assert: setting == undefined"); + } + + Mousetrap.bind(hotkeyToCombo(setting.combination), (e) => { + if (!action.enableInTextbox) { + const element = e.target as HTMLElement; + if ( + element.tagName === "INPUT" || + element.tagName === "SELECT" || + element.tagName === "TEXTAREA" || + (element instanceof HTMLElement && + element.contentEditable === "true") || + // メニュー項目ではショートカットキーを無効化 + element.classList.contains("q-item") + ) { + return true; + } + } + // TODO: もっと良い感じの取得方法があれば変更する + const path = location.hash.split("/")[1] as "talk" | "song"; + if (path === action.editor) { + return action.callback(); + } + }); + } } replace(data: HotkeySettingType): void { throw new Error("unimplemented"); } - register(data: { - when: "talk" | "sing"; - enableInTextbox: boolean; - name: string; - action: () => void; - }): void { - throw new Error("unimplemented"); + register(data: HotkeyRegistration): void { + this.actions.push(data); + this.refresh(); } } + +const hotkeyToCombo = (hotkeyCombo: string) => { + return hotkeyCombo.toLowerCase().replaceAll(" ", "+"); +}; diff --git a/src/store/setting.ts b/src/store/setting.ts index 0d25d94739..3905f46a9d 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -18,6 +18,7 @@ import { RootMiscSettingType, } from "@/type/preload"; import { IsEqual } from "@/type/utility"; +import { useHotkeyManager } from "@/composables/useHotkeyManager"; const hotkeyFunctionCache: Record HotkeyReturnType> = {}; @@ -81,6 +82,8 @@ export const settingStore = createPartialStore({ data: hotkey, }); }); + const hotkeyManager = useHotkeyManager(); + hotkeyManager.load(hotkeys); }); const theme = await window.electron.theme(); From 22c572c719f93469203ff44a21b883345baae9dd Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 4 Feb 2024 01:01:46 +0900 Subject: [PATCH 03/47] =?UTF-8?q?Change:=20Plugin=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.vue | 25 ++--- src/components/Dialog/HotkeySettingDialog.vue | 6 ++ src/components/HeaderBar.vue | 23 +--- src/components/Talk/EditorHome.vue | 6 +- src/main.ts | 2 + src/plugins/hotkeyPlugin.ts | 101 ++++++++++++++++++ src/store/setting.ts | 23 +--- 7 files changed, 119 insertions(+), 67 deletions(-) create mode 100644 src/plugins/hotkeyPlugin.ts diff --git a/src/App.vue b/src/App.vue index 304ae41451..ac1d4a80ec 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,14 +14,14 @@ diff --git a/src/components/Talk/AudioDetail.vue b/src/components/Talk/AudioDetail.vue index 9ca4539583..d7727a7f8f 100644 --- a/src/components/Talk/AudioDetail.vue +++ b/src/components/Talk/AudioDetail.vue @@ -85,15 +85,10 @@ import { computed, nextTick, ref, watch } from "vue"; import AccentPhrase from "./AccentPhrase.vue"; import ToolTip from "@/components/ToolTip.vue"; import { useStore } from "@/store"; -import { - AudioKey, - HotkeyActionType, - HotkeyReturnType, - isMac, -} from "@/type/preload"; -import { setHotkeyFunctions } from "@/store/setting"; +import { AudioKey, isMac } from "@/type/preload"; import { EngineManifest } from "@/openapi/models"; import { useShiftKey, useAltKey } from "@/composables/useModifierKey"; +import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; const props = defineProps<{ @@ -112,70 +107,80 @@ const supportedFeatures = computed( .supportedFeatures) as EngineManifest["supportedFeatures"] | undefined ); -const hotkeyMap = new Map HotkeyReturnType>([ - [ - "再生/停止", - () => { - if (!nowPlaying.value && !nowGenerating.value && !uiLocked.value) { - play(); - } else { - stop(); - } - }, - ], - [ - "アクセント欄を表示", - () => { - selectedDetail.value = "accent"; - }, - ], - [ - "イントネーション欄を表示", - () => { - if (supportedFeatures.value?.adjustMoraPitch) { - selectedDetail.value = "pitch"; - } - }, - ], - [ - "長さ欄を表示", - () => { - if (supportedFeatures.value?.adjustPhonemeLength) { - selectedDetail.value = "length"; - } - }, - ], - [ - "全体のイントネーションをリセット", - () => { - if (!uiLocked.value && store.getters.ACTIVE_AUDIO_KEY) { - const audioKeys = store.state.experimentalSetting.enableMultiSelect - ? store.getters.SELECTED_AUDIO_KEYS - : [store.getters.ACTIVE_AUDIO_KEY]; - store.dispatch("COMMAND_MULTI_RESET_MORA_PITCH_AND_LENGTH", { - audioKeys, - }); - } - }, - ], - [ - "選択中のアクセント句のイントネーションをリセット", - () => { - if ( - !uiLocked.value && - store.getters.ACTIVE_AUDIO_KEY && - store.getters.AUDIO_PLAY_START_POINT != undefined - ) { - store.dispatch("COMMAND_RESET_SELECTED_MORA_PITCH_AND_LENGTH", { - audioKey: store.getters.ACTIVE_AUDIO_KEY, - accentPhraseIndex: store.getters.AUDIO_PLAY_START_POINT, - }); - } - }, - ], -]); -// このコンポーネントは遅延評価なので手動でバインディングを行う -setHotkeyFunctions(hotkeyMap, true); +const hotkeyManager = useHotkeyManager(); + +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "再生/停止", + callback: () => { + if (!nowPlaying.value && !nowGenerating.value && !uiLocked.value) { + play(); + } else { + stop(); + } + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "アクセント欄を表示", + callback: () => { + selectedDetail.value = "accent"; + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "イントネーション欄を表示", + callback: () => { + if (supportedFeatures.value?.adjustMoraPitch) { + selectedDetail.value = "pitch"; + } + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "長さ欄を表示", + callback: () => { + if (supportedFeatures.value?.adjustPhonemeLength) { + selectedDetail.value = "length"; + } + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "全体のイントネーションをリセット", + callback: () => { + if (!uiLocked.value && store.getters.ACTIVE_AUDIO_KEY) { + const audioKeys = store.state.experimentalSetting.enableMultiSelect + ? store.getters.SELECTED_AUDIO_KEYS + : [store.getters.ACTIVE_AUDIO_KEY]; + store.dispatch("COMMAND_MULTI_RESET_MORA_PITCH_AND_LENGTH", { + audioKeys, + }); + } + }, +}); +hotkeyManager.register({ + editor: "talk", + enableInTextbox: false, + action: "選択中のアクセント句のイントネーションをリセット", + callback: () => { + if ( + !uiLocked.value && + store.getters.ACTIVE_AUDIO_KEY && + store.getters.AUDIO_PLAY_START_POINT != undefined + ) { + store.dispatch("COMMAND_RESET_SELECTED_MORA_PITCH_AND_LENGTH", { + audioKey: store.getters.ACTIVE_AUDIO_KEY, + accentPhraseIndex: store.getters.AUDIO_PLAY_START_POINT, + }); + } + }, +}); // detail selector type DetailTypes = "accent" | "pitch" | "length"; diff --git a/src/components/Talk/EditorHome.vue b/src/components/Talk/EditorHome.vue index e87d938b83..f2851f9ce0 100644 --- a/src/components/Talk/EditorHome.vue +++ b/src/components/Talk/EditorHome.vue @@ -184,7 +184,7 @@ diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index 4b485912c4..5fad41e467 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -15,6 +15,7 @@ type HotkeyRegistration = { editor: "talk" | "song"; enableInTextbox: boolean; action: HotkeyActionType; + keepDefaultBehavior?: boolean; callback: (e: KeyboardEvent) => void; }; @@ -70,6 +71,7 @@ export class HotkeyManager { // TODO: もっと良い感じの取得方法があれば変更する const path = location.hash.split("/")[1] as "talk" | "song"; if (path === action.editor) { + if (!action.keepDefaultBehavior) e.preventDefault(); action.callback(e); } }); @@ -103,3 +105,31 @@ export const hotkeyPlugin: Plugin = { app.provide(hotkeyManagerKey, hotkeyManager); }, }; + +export const parseCombo = (event: KeyboardEvent): string => { + let recordedCombo = ""; + if (event.ctrlKey) { + recordedCombo += "Ctrl "; + } + if (event.altKey) { + recordedCombo += "Alt "; + } + if (event.shiftKey) { + recordedCombo += "Shift "; + } + // event.metaKey は Mac キーボードでは Cmd キー、Windows キーボードでは Windows キーの押下で true になる + if (event.metaKey) { + recordedCombo += "Meta "; + } + if (event.key === " ") { + recordedCombo += "Space"; + } else { + if (["Control", "Shift", "Alt", "Meta"].includes(event.key)) { + recordedCombo = recordedCombo.slice(0, -1); + } else { + recordedCombo += + event.key.length > 1 ? event.key : event.key.toUpperCase(); + } + } + return recordedCombo; +}; diff --git a/src/store/setting.ts b/src/store/setting.ts index 5c1378eb51..4373eb17e1 100644 --- a/src/store/setting.ts +++ b/src/store/setting.ts @@ -2,10 +2,7 @@ import { Dark, setCssVar, colors } from "quasar"; import { SettingStoreState, SettingStoreTypes } from "./type"; import { createUILockAction } from "./ui"; import { createPartialStore } from "./vuex"; -import { useStore } from "@/store"; import { - HotkeyActionType, - HotkeyReturnType, HotkeySettingType, SavingSetting, ExperimentalSettingType, @@ -18,8 +15,6 @@ import { } from "@/type/preload"; import { IsEqual } from "@/type/utility"; -const hotkeyFunctionCache: Record HotkeyReturnType> = {}; - export const settingStoreState: SettingStoreState = { savingSetting: { fileEncoding: "UTF-8", @@ -448,55 +443,3 @@ export const settingStore = createPartialStore({ }, }, }); - -export const setHotkeyFunctions = ( - hotkeyMap: Map HotkeyReturnType>, - reassign?: boolean -): void => { - hotkeyMap.forEach((value, key) => { - hotkeyFunctionCache[key] = value; - }); - if (reassign) { - const store = useStore(); - hotkeyMap.forEach((hotkeyFunction, hotkeyAction) => { - const hotkey = store.state.hotkeySettings.find((value) => { - return value.action == hotkeyAction; - }); - if (hotkey) { - store.dispatch("SET_HOTKEY_SETTINGS", { data: { ...hotkey } }); - } - }); - } -}; - -const hotkey2Combo = (hotkeyCombo: string) => { - return hotkeyCombo.toLowerCase().replaceAll(" ", "+"); -}; - -export const parseCombo = (event: KeyboardEvent): string => { - let recordedCombo = ""; - if (event.ctrlKey) { - recordedCombo += "Ctrl "; - } - if (event.altKey) { - recordedCombo += "Alt "; - } - if (event.shiftKey) { - recordedCombo += "Shift "; - } - // event.metaKey は Mac キーボードでは Cmd キー、Windows キーボードでは Windows キーの押下で true になる - if (event.metaKey) { - recordedCombo += "Meta "; - } - if (event.key === " ") { - recordedCombo += "Space"; - } else { - if (["Control", "Shift", "Alt", "Meta"].includes(event.key)) { - recordedCombo = recordedCombo.slice(0, -1); - } else { - recordedCombo += - event.key.length > 1 ? event.key : event.key.toUpperCase(); - } - } - return recordedCombo; -}; From f5fe09045fa3e85c6f86193248dc29d1105471dc Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 4 Feb 2024 01:52:17 +0900 Subject: [PATCH 06/47] =?UTF-8?q?Change:=20=E3=83=87=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=AB=E3=83=88=E3=83=91=E3=83=A9=E3=83=A1=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/HeaderBar.vue | 3 --- src/components/Talk/AudioDetail.vue | 6 ------ src/components/Talk/EditorHome.vue | 1 - src/components/Talk/MenuBar.vue | 8 -------- src/plugins/hotkeyPlugin.ts | 2 +- 5 files changed, 1 insertion(+), 19 deletions(-) diff --git a/src/components/HeaderBar.vue b/src/components/HeaderBar.vue index 04733822ce..65a2be078d 100644 --- a/src/components/HeaderBar.vue +++ b/src/components/HeaderBar.vue @@ -53,7 +53,6 @@ const nowPlayingContinuously = computed( const hotkeyManager = useHotkeyManager(); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "元に戻す", callback: () => { if (!uiLocked.value && canUndo.value) { @@ -63,7 +62,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "やり直す", callback: () => { if (!uiLocked.value && canRedo.value) { @@ -74,7 +72,6 @@ hotkeyManager.register({ hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "連続再生/停止", callback: () => { if (!uiLocked.value) { diff --git a/src/components/Talk/AudioDetail.vue b/src/components/Talk/AudioDetail.vue index d7727a7f8f..40dc0fc469 100644 --- a/src/components/Talk/AudioDetail.vue +++ b/src/components/Talk/AudioDetail.vue @@ -111,7 +111,6 @@ const hotkeyManager = useHotkeyManager(); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "再生/停止", callback: () => { if (!nowPlaying.value && !nowGenerating.value && !uiLocked.value) { @@ -123,7 +122,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "アクセント欄を表示", callback: () => { selectedDetail.value = "accent"; @@ -131,7 +129,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "イントネーション欄を表示", callback: () => { if (supportedFeatures.value?.adjustMoraPitch) { @@ -141,7 +138,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "長さ欄を表示", callback: () => { if (supportedFeatures.value?.adjustPhonemeLength) { @@ -151,7 +147,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "全体のイントネーションをリセット", callback: () => { if (!uiLocked.value && store.getters.ACTIVE_AUDIO_KEY) { @@ -166,7 +161,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "選択中のアクセント句のイントネーションをリセット", callback: () => { if ( diff --git a/src/components/Talk/EditorHome.vue b/src/components/Talk/EditorHome.vue index f2851f9ce0..76f7b1c67b 100644 --- a/src/components/Talk/EditorHome.vue +++ b/src/components/Talk/EditorHome.vue @@ -236,7 +236,6 @@ const hotkeyManager = useHotkeyManager(); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "テキスト欄にフォーカスを戻す", callback: () => { if (activeAudioKey.value != undefined) { diff --git a/src/components/Talk/MenuBar.vue b/src/components/Talk/MenuBar.vue index ecb7630dec..b24fe9c2c9 100644 --- a/src/components/Talk/MenuBar.vue +++ b/src/components/Talk/MenuBar.vue @@ -228,7 +228,6 @@ const fileSubMenuData = computed(() => [ hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "新規プロジェクト", callback: () => { createNewProject(); @@ -236,7 +235,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "音声書き出し", callback: () => { generateAndSaveAllAudio(); @@ -244,7 +242,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "選択音声を書き出し", callback: () => { generateAndSaveSelectedAudio(); @@ -252,7 +249,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "音声を繋げて書き出し", callback: () => { generateAndConnectAndSaveAllAudio(); @@ -260,7 +256,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "テキスト読み込む", callback: () => { importTextFile(); @@ -268,7 +263,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "プロジェクトを上書き保存", callback: () => { saveProject(); @@ -276,7 +270,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "プロジェクトを名前を付けて保存", callback: () => { saveProjectAs(); @@ -284,7 +277,6 @@ hotkeyManager.register({ }); hotkeyManager.register({ editor: "talk", - enableInTextbox: false, action: "プロジェクト読み込み", callback: () => { importProject(); diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index 5fad41e467..fa25808b4c 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -13,7 +13,7 @@ export const useHotkeyManager = () => { type HotkeyRegistration = { editor: "talk" | "song"; - enableInTextbox: boolean; + enableInTextbox?: boolean; action: HotkeyActionType; keepDefaultBehavior?: boolean; callback: (e: KeyboardEvent) => void; From b93a07c9a9bce75566394eac0a6a69137eff1c46 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 4 Feb 2024 01:57:01 +0900 Subject: [PATCH 07/47] =?UTF-8?q?Code:=20=E3=83=89=E3=82=AD=E3=83=A5?= =?UTF-8?q?=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/hotkeyPlugin.ts | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index fa25808b4c..ccf5c6a2d9 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -11,29 +11,44 @@ export const useHotkeyManager = () => { return hotkeyManager; }; -type HotkeyRegistration = { +/** + * ショートカットキーの情報を格納する型。 + */ +type HotkeyEntry = { + /** どちらのエディタで有効か */ editor: "talk" | "song"; + /** テキストボックス内で有効か。デフォルトはfalse。 */ enableInTextbox?: boolean; + /** 名前。 */ action: HotkeyActionType; + /** ブラウザのデフォルトの挙動をキャンセルするか。デフォルトはfalse。 */ keepDefaultBehavior?: boolean; + /** ショートカットキーが押されたときの処理。 */ callback: (e: KeyboardEvent) => void; }; +/** + * ショートカットキーの管理を行うクラス。 + */ export class HotkeyManager { - actions: HotkeyRegistration[] = []; + actions: HotkeyEntry[] = []; settings: HotkeySettingType[] = []; registered: Partial> = {}; constructor() { + // デフォルトだとテキスト欄でのショートカットキーが効かないので、テキスト欄でも効くようにする hotkeys.filter = () => true; } + /** + * ショートカットキーの設定を読み込む。 + */ load(data: HotkeySettingType[]): void { this.settings = data; - this.refresh(); + this.refreshBinding(); } - private refresh(): void { + private refreshBinding(): void { if (this.settings.length === 0) { return; } @@ -79,18 +94,24 @@ export class HotkeyManager { } } + /** + * ショートカットキーの設定を変更する。 + */ replace(data: HotkeySettingType): void { const index = this.settings.findIndex((s) => s.action === data.action); if (index === -1) { throw new Error("assert: index !== -1"); } this.settings[index] = data; - this.refresh(); + this.refreshBinding(); } - register(data: HotkeyRegistration): void { + /** + * ショートカットキーの登録を行う。 + */ + register(data: HotkeyEntry): void { this.actions.push(data); - this.refresh(); + this.refreshBinding(); } } From 60c7336fbf7745ab51e1a34df6ad55f2ff26ac00 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 4 Feb 2024 09:58:24 +0900 Subject: [PATCH 08/47] =?UTF-8?q?Add:=20sing=E5=81=B4=E3=81=AB=E5=90=8C?= =?UTF-8?q?=E3=81=98=E5=90=8D=E5=89=8D=E3=81=A7=E3=82=B7=E3=83=A7=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=82=AB=E3=83=83=E3=83=88=E3=82=92=E7=99=BB=E9=8C=B2?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ToolBar.vue | 14 +++++++ src/plugins/hotkeyPlugin.ts | 68 ++++++++++++++++++++++++--------- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index 2fdc54081d..28c27afb84 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -118,8 +118,22 @@ import { } from "@/sing/domain"; import CharacterMenuButton from "@/components/Sing/CharacterMenuButton.vue"; import { getStyleDescription } from "@/sing/viewHelper"; +import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; const store = useStore(); +const hotkeyManager = useHotkeyManager(); + +hotkeyManager.register({ + action: "再生/停止", + editor: "song", + callback: () => { + if (nowPlaying.value) { + stop(); + } else { + play(); + } + }, +}); const userOrderedCharacterInfos = computed(() => store.getters.USER_ORDERED_CHARACTER_INFOS("singerLike") diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index ccf5c6a2d9..a44435161c 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -68,28 +68,58 @@ export class HotkeyManager { throw new Error("assert: setting == undefined"); } - hotkeys(hotkeyToCombo(setting.combination), (e) => { - if (!action.enableInTextbox) { - const element = e.target as HTMLElement; - if ( - element.tagName === "INPUT" || - element.tagName === "SELECT" || - element.tagName === "TEXTAREA" || - (element instanceof HTMLElement && - element.contentEditable === "true") || - // メニュー項目ではショートカットキーを無効化 - element.classList.contains("q-item") - ) { - return; - } + const actions = this.actions.filter((a) => a.action === action.action); + if (actions.length > 1) { + const songAction = actions.find((a) => a.editor === "song"); + const talkAction = actions.find((a) => a.editor === "talk"); + if (!songAction || !talkAction) { + throw new Error("assert: songAction && talkAction"); } - // TODO: もっと良い感じの取得方法があれば変更する - const path = location.hash.split("/")[1] as "talk" | "song"; - if (path === action.editor) { + // talk/song両方で有効なショートカットキー + hotkeys(hotkeyToCombo(setting.combination), (e) => { + const path = location.hash.split("/")[1] as "talk" | "song"; + const action = path === "talk" ? talkAction : songAction; + if (!action.enableInTextbox) { + const element = e.target as HTMLElement; + if ( + element.tagName === "INPUT" || + element.tagName === "SELECT" || + element.tagName === "TEXTAREA" || + (element instanceof HTMLElement && + element.contentEditable === "true") || + // メニュー項目ではショートカットキーを無効化 + element.classList.contains("q-item") + ) { + return; + } + } if (!action.keepDefaultBehavior) e.preventDefault(); action.callback(e); - } - }); + }); + } else { + hotkeys(hotkeyToCombo(setting.combination), (e) => { + if (!action.enableInTextbox) { + const element = e.target as HTMLElement; + if ( + element.tagName === "INPUT" || + element.tagName === "SELECT" || + element.tagName === "TEXTAREA" || + (element instanceof HTMLElement && + element.contentEditable === "true") || + // メニュー項目ではショートカットキーを無効化 + element.classList.contains("q-item") + ) { + return; + } + } + // TODO: もっと良い感じの取得方法があれば変更する + const path = location.hash.split("/")[1] as "talk" | "song"; + if (path === action.editor) { + if (!action.keepDefaultBehavior) e.preventDefault(); + action.callback(e); + } + }); + } this.registered[action.action] = setting.combination; } } From 35dce47f257b69b0294bee9989137c8e1e87e3d9 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Sun, 4 Feb 2024 10:09:57 +0900 Subject: [PATCH 09/47] =?UTF-8?q?Revert:=20Space=E3=82=AD=E3=83=BC?= =?UTF-8?q?=E5=86=8D=E7=94=9F=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Sing/ToolBar.vue | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/components/Sing/ToolBar.vue b/src/components/Sing/ToolBar.vue index 28c27afb84..2fdc54081d 100644 --- a/src/components/Sing/ToolBar.vue +++ b/src/components/Sing/ToolBar.vue @@ -118,22 +118,8 @@ import { } from "@/sing/domain"; import CharacterMenuButton from "@/components/Sing/CharacterMenuButton.vue"; import { getStyleDescription } from "@/sing/viewHelper"; -import { useHotkeyManager } from "@/plugins/hotkeyPlugin"; const store = useStore(); -const hotkeyManager = useHotkeyManager(); - -hotkeyManager.register({ - action: "再生/停止", - editor: "song", - callback: () => { - if (nowPlaying.value) { - stop(); - } else { - play(); - } - }, -}); const userOrderedCharacterInfos = computed(() => store.getters.USER_ORDERED_CHARACTER_INFOS("singerLike") From 5e9d70e8c89f9574e5bdc3d647377e536d802a00 Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Tue, 6 Feb 2024 13:04:06 +0900 Subject: [PATCH 10/47] =?UTF-8?q?Improve:=20=E5=8F=AF=E8=AA=AD=E6=80=A7?= =?UTF-8?q?=E3=82=92=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/hotkeyPlugin.ts | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index a44435161c..14c0f3b574 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -1,3 +1,11 @@ +/** + * ショートカットキーを管理するプラグイン。 + * + * HotkeyAction: 何をするか(再生するなど)の名称 + * Combination: ショートカットキーを文字列で表したもの + * HotkeySetting: ユーザーが設定できるもの。ActionとCobinationのペア + * HotkeyEntry: プログラムが管理するもの。関数や発動条件など + */ import { Plugin, inject } from "vue"; import hotkeys from "hotkeys-js"; import { HotkeyActionType, HotkeySettingType } from "@/type/preload"; @@ -31,9 +39,9 @@ type HotkeyEntry = { * ショートカットキーの管理を行うクラス。 */ export class HotkeyManager { - actions: HotkeyEntry[] = []; + entries: HotkeyEntry[] = []; settings: HotkeySettingType[] = []; - registered: Partial> = {}; + registeredCombination: Partial> = {}; constructor() { // デフォルトだとテキスト欄でのショートカットキーが効かないので、テキスト欄でも効くようにする @@ -52,13 +60,13 @@ export class HotkeyManager { if (this.settings.length === 0) { return; } - const changedActions = this.actions.filter( + const changedActions = this.entries.filter( (a) => - this.registered[a.action] !== + this.registeredCombination[a.action] !== this.settings.find((s) => s.action === a.action)?.combination ); for (const action of changedActions) { - const registered = this.registered[action.action]; + const registered = this.registeredCombination[action.action]; if (registered) { hotkeys.unbind(hotkeyToCombo(registered)); } @@ -68,7 +76,7 @@ export class HotkeyManager { throw new Error("assert: setting == undefined"); } - const actions = this.actions.filter((a) => a.action === action.action); + const actions = this.entries.filter((a) => a.action === action.action); if (actions.length > 1) { const songAction = actions.find((a) => a.editor === "song"); const talkAction = actions.find((a) => a.editor === "talk"); @@ -120,7 +128,7 @@ export class HotkeyManager { } }); } - this.registered[action.action] = setting.combination; + this.registeredCombination[action.action] = setting.combination; } } @@ -140,7 +148,7 @@ export class HotkeyManager { * ショートカットキーの登録を行う。 */ register(data: HotkeyEntry): void { - this.actions.push(data); + this.entries.push(data); this.refreshBinding(); } } From 171d8d24df20fc50c36b4a1f7aee178d4def61aa Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Tue, 6 Feb 2024 13:06:56 +0900 Subject: [PATCH 11/47] =?UTF-8?q?Delete:=20keepDefaultBehavior=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/plugins/hotkeyPlugin.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/plugins/hotkeyPlugin.ts b/src/plugins/hotkeyPlugin.ts index 14c0f3b574..958f289786 100644 --- a/src/plugins/hotkeyPlugin.ts +++ b/src/plugins/hotkeyPlugin.ts @@ -29,8 +29,6 @@ type HotkeyEntry = { enableInTextbox?: boolean; /** 名前。 */ action: HotkeyActionType; - /** ブラウザのデフォルトの挙動をキャンセルするか。デフォルトはfalse。 */ - keepDefaultBehavior?: boolean; /** ショートカットキーが押されたときの処理。 */ callback: (e: KeyboardEvent) => void; }; @@ -39,9 +37,9 @@ type HotkeyEntry = { * ショートカットキーの管理を行うクラス。 */ export class HotkeyManager { - entries: HotkeyEntry[] = []; - settings: HotkeySettingType[] = []; - registeredCombination: Partial> = {}; + private entries: HotkeyEntry[] = []; + private settings: HotkeySettingType[] = []; + private registeredCombination: Partial> = {}; constructor() { // デフォルトだとテキスト欄でのショートカットキーが効かないので、テキスト欄でも効くようにする @@ -101,7 +99,7 @@ export class HotkeyManager { return; } } - if (!action.keepDefaultBehavior) e.preventDefault(); + e.preventDefault(); action.callback(e); }); } else { @@ -123,7 +121,7 @@ export class HotkeyManager { // TODO: もっと良い感じの取得方法があれば変更する const path = location.hash.split("/")[1] as "talk" | "song"; if (path === action.editor) { - if (!action.keepDefaultBehavior) e.preventDefault(); + e.preventDefault(); action.callback(e); } }); From cf5c441224f7424a75cf74d5f60202b2970eb8ce Mon Sep 17 00:00:00 2001 From: sevenc-nanashi Date: Tue, 6 Feb 2024 13:15:04 +0900 Subject: [PATCH 12/47] =?UTF-8?q?Change:=20scope=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TitleBarEditorSwitcher.vue | 8 ++- src/plugins/hotkeyPlugin.ts | 68 ++++++----------------- 2 files changed, 24 insertions(+), 52 deletions(-) diff --git a/src/components/TitleBarEditorSwitcher.vue b/src/components/TitleBarEditorSwitcher.vue index 60944c955b..95bea9f9a5 100644 --- a/src/components/TitleBarEditorSwitcher.vue +++ b/src/components/TitleBarEditorSwitcher.vue @@ -20,7 +20,8 @@