@@ -3,17 +3,29 @@ import type { Voice } from '@proj-airi/stage-ui/constants'
3
3
4
4
import { voiceList } from ' @proj-airi/stage-ui/constants'
5
5
import { useLLM , useSettings } from ' @proj-airi/stage-ui/stores'
6
+ import { useShortcutsStore } from ' @renderer/stores/shortcuts'
7
+ import { useEventListener } from ' @vueuse/core'
6
8
import { storeToRefs } from ' pinia'
7
- import { onMounted , ref , watch } from ' vue'
9
+ import { computed , onMounted , ref , watch } from ' vue'
8
10
import { useI18n } from ' vue-i18n'
9
11
10
12
const { t, locale } = useI18n ()
11
13
12
14
const settings = useSettings ()
15
+ const { shortcuts } = storeToRefs (useShortcutsStore ())
13
16
const supportedModels = ref <{ id: string , name? : string }[]>([])
14
17
const { models } = useLLM ()
15
18
const { openAiModel, openAiApiBaseURL, openAiApiKey, elevenlabsVoiceEnglish, elevenlabsVoiceJapanese, language } = storeToRefs (settings )
16
19
20
+ const recordingFor = ref <string | null >(null )
21
+ const recordingKeys = ref <{
22
+ modifier: string []
23
+ key: string
24
+ }>({
25
+ modifier: [],
26
+ key: ' ' ,
27
+ })
28
+
17
29
function handleModelChange(event : Event ) {
18
30
const target = event .target as HTMLSelectElement
19
31
const found = supportedModels .value .find (m => m .id === target .value )
@@ -69,6 +81,68 @@ onMounted(async () => {
69
81
function handleQuit() {
70
82
window .electron .ipcRenderer .send (' quit' )
71
83
}
84
+
85
+ // Add function to handle shortcut recording
86
+ function startRecording(shortcut : typeof shortcuts .value [0 ]) {
87
+ recordingFor .value = shortcut .type
88
+ }
89
+
90
+ function isModifierKey(key : string ) {
91
+ return [' Shift' , ' Control' , ' Alt' , ' Meta' ].includes (key )
92
+ }
93
+
94
+ // Handle key combinations
95
+ useEventListener (' keydown' , (e ) => {
96
+ if (! recordingFor .value )
97
+ return
98
+
99
+ e .preventDefault ()
100
+
101
+ if (isModifierKey (e .key )) {
102
+ if (recordingKeys .value .modifier .includes (e .key ))
103
+ return
104
+
105
+ recordingKeys .value .modifier .push (e .key )
106
+
107
+ return
108
+ }
109
+
110
+ if (recordingKeys .value .modifier .length === 0 )
111
+ return
112
+
113
+ recordingKeys .value .key = e .key .toUpperCase ()
114
+
115
+ const shortcut = shortcuts .value .find (s => s .type === recordingFor .value )
116
+ if (shortcut )
117
+ shortcut .shortcut = ` ${recordingKeys .value .modifier .join (' +' )}+${recordingKeys .value .key } `
118
+
119
+ recordingKeys .value = {
120
+ modifier: [],
121
+ key: ' ' ,
122
+ }
123
+ recordingFor .value = null
124
+ }, { passive: false })
125
+
126
+ // Add click outside handler to cancel recording
127
+ useEventListener (' click' , (e ) => {
128
+ if (recordingFor .value ) {
129
+ const target = e .target as HTMLElement
130
+ if (! target .closest (' .shortcut-item' )) {
131
+ recordingFor .value = null
132
+ }
133
+ }
134
+ })
135
+
136
+ const pressKeysMessage = computed (() => {
137
+ if (recordingKeys .value .modifier .length === 0 )
138
+ return t (' settings.press_keys' )
139
+
140
+ return ` ${t (' settings.press_keys' )}: ${recordingKeys .value .modifier .join (' +' )}+${recordingKeys .value .key } `
141
+ })
142
+
143
+ function isConflict(shortcut : typeof shortcuts .value [0 ]) {
144
+ return shortcuts .value .some (s => s .type !== shortcut .type && s .shortcut === shortcut .shortcut )
145
+ }
72
146
</script >
73
147
74
148
<template >
@@ -201,6 +275,36 @@ function handleQuit() {
201
275
</select >
202
276
</div >
203
277
</div >
278
+ <h2 text =" slate-800/80" font-bold >
279
+ {{ t('settings.shortcuts.title') }}
280
+ </h2 >
281
+ <div pb-2 >
282
+ <div
283
+ grid =" ~ cols-[140px_1fr]" my-2 items-center gap-1.5 rounded-lg
284
+ bg =" [#fff6fc]" p-2 text =" pink-400"
285
+ >
286
+ <template v-for =" shortcut in shortcuts " :key =" shortcut .type " >
287
+ <span text =" xs pink-500" >
288
+ {{ t(shortcut.name) }}
289
+ </span >
290
+ <div
291
+ class =" shortcut-item flex items-center justify-end gap-x-2 px-2 py-0.5"
292
+ :class =" { recording: recordingFor === shortcut.type }"
293
+ text =" xs pink-500"
294
+ cursor-pointer
295
+ @click =" startRecording(shortcut)"
296
+ >
297
+ <div v-if =" recordingFor === shortcut.type" class =" pointer-events-none animate-flash animate-count-infinite" >
298
+ {{ pressKeysMessage }}
299
+ </div >
300
+ <div v-else class =" pointer-events-none" >
301
+ {{ shortcut.shortcut }}
302
+ </div >
303
+ <div v-if =" isConflict(shortcut)" text =" xs pink-500" i-solar:danger-square-bold w-4 />
304
+ </div >
305
+ </template >
306
+ </div >
307
+ </div >
204
308
<h2 text =" slate-800/80" font-bold >
205
309
{{ t('settings.other') }}
206
310
</h2 >
0 commit comments