1+ import React , { useState , useEffect } from 'react' ;
2+ import { browser } from '#imports' ;
3+ import { getGlobalSettings , updateGlobalSettings , type GlobalSettings } from '@/utils/globalSettings' ;
4+ import { t } from '@/utils/i18n' ;
5+
6+ const GlobalSettingsPage : React . FC = ( ) => {
7+ const [ settings , setSettings ] = useState < GlobalSettings > ( {
8+ closeModalOnOutsideClick : true ,
9+ } ) ;
10+ const [ isLoading , setIsLoading ] = useState ( true ) ;
11+ const [ isSaving , setIsSaving ] = useState ( false ) ;
12+ const [ shortcuts , setShortcuts ] = useState < { [ key : string ] : string } > ( { } ) ;
13+
14+ // 加载设置
15+ useEffect ( ( ) => {
16+ const loadSettings = async ( ) => {
17+ try {
18+ setIsLoading ( true ) ;
19+ const globalSettings = await getGlobalSettings ( ) ;
20+ setSettings ( globalSettings ) ;
21+
22+ // 获取快捷键设置
23+ try {
24+ const commands = await browser . commands . getAll ( ) ;
25+ const shortcutMap : { [ key : string ] : string } = { } ;
26+ commands . forEach ( command => {
27+ if ( command . name && command . shortcut ) {
28+ shortcutMap [ command . name ] = command . shortcut ;
29+ }
30+ } ) ;
31+ setShortcuts ( shortcutMap ) ;
32+ } catch ( error ) {
33+ console . warn ( 'Unable to get shortcuts:' , error ) ;
34+ }
35+ } catch ( error ) {
36+ console . error ( 'Failed to load settings:' , error ) ;
37+ } finally {
38+ setIsLoading ( false ) ;
39+ }
40+ } ;
41+
42+ loadSettings ( ) ;
43+ } , [ ] ) ;
44+
45+ // 更新设置
46+ const handleSettingChange = async ( key : keyof GlobalSettings , value : any ) => {
47+ try {
48+ setIsSaving ( true ) ;
49+ const newSettings = { ...settings , [ key ] : value } ;
50+ setSettings ( newSettings ) ;
51+ await updateGlobalSettings ( { [ key ] : value } ) ;
52+ } catch ( error ) {
53+ console . error ( 'Failed to update setting:' , error ) ;
54+ // 恢复原设置
55+ setSettings ( prev => ( { ...prev , [ key ] : settings [ key ] } ) ) ;
56+ } finally {
57+ setIsSaving ( false ) ;
58+ }
59+ } ;
60+
61+ // 打开浏览器快捷键设置页
62+ const openShortcutSettings = ( ) => {
63+ const isChrome = navigator . userAgent . includes ( 'Chrome' ) ;
64+ const isFirefox = navigator . userAgent . includes ( 'Firefox' ) ;
65+
66+ if ( isChrome ) {
67+ browser . tabs . create ( { url : 'chrome://extensions/shortcuts' } ) ;
68+ } else if ( isFirefox ) {
69+ browser . tabs . create ( { url : 'about:addons' } ) ;
70+ } else {
71+ // 其他浏览器的通用方法
72+ alert ( t ( 'pleaseManuallyNavigateToShortcuts' ) ) ;
73+ }
74+ } ;
75+
76+ if ( isLoading ) {
77+ return (
78+ < div className = "min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900" >
79+ < div className = "flex justify-center items-center min-h-screen" >
80+ < div className = "text-center space-y-4" >
81+ < div className = "relative" >
82+ < div className = "w-16 h-16 mx-auto" >
83+ < div className = "absolute inset-0 border-4 border-blue-200 dark:border-blue-800 rounded-full" > </ div >
84+ < div className = "absolute inset-0 border-4 border-blue-600 dark:border-blue-400 rounded-full border-t-transparent animate-spin" > </ div >
85+ </ div >
86+ </ div >
87+ < div className = "space-y-2" >
88+ < h3 className = "text-lg font-semibold text-gray-700 dark:text-gray-300" > { t ( 'loading' ) } </ h3 >
89+ < p className = "text-sm text-gray-500 dark:text-gray-400" > { t ( 'loadingGlobalSettings' ) } </ p >
90+ </ div >
91+ </ div >
92+ </ div >
93+ </ div >
94+ ) ;
95+ }
96+
97+ return (
98+ < div className = "min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900" >
99+ < div className = "max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 py-8" >
100+ { /* 页面头部 */ }
101+ < div className = "mb-10" >
102+ < div className = "flex items-center space-x-3 mb-4" >
103+ < div className = "w-10 h-10 bg-gradient-to-br from-blue-500 to-indigo-600 rounded-xl flex items-center justify-center shadow-lg" >
104+ < svg className = "w-6 h-6 text-white" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
105+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
106+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
107+ </ svg >
108+ </ div >
109+ < h1 className = "text-4xl font-bold bg-gradient-to-r from-gray-900 via-blue-900 to-indigo-900 dark:from-gray-100 dark:via-blue-100 dark:to-indigo-100 bg-clip-text text-transparent" >
110+ { t ( 'globalSettings' ) }
111+ </ h1 >
112+ </ div >
113+ < p className = "text-lg text-gray-600 dark:text-gray-300 max-w-3xl" >
114+ { t ( 'globalSettingsDescription' ) }
115+ </ p >
116+ </ div >
117+
118+ < div className = "space-y-6" >
119+ { /* 弹窗行为设置 */ }
120+ < div className = "bg-white/90 dark:bg-gray-800/90 backdrop-blur-sm border border-white/20 dark:border-gray-700/50 shadow-xl rounded-2xl p-8" >
121+ < div className = "flex items-center space-x-3 mb-6" >
122+ < div className = "w-8 h-8 bg-purple-100 dark:bg-purple-900/30 rounded-lg flex items-center justify-center" >
123+ < svg className = "w-5 h-5 text-purple-600 dark:text-purple-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
124+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M7 8h10M7 12h4m1 8l-4-4H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-3l-4 4z" />
125+ </ svg >
126+ </ div >
127+ < h2 className = "text-xl font-semibold text-gray-800 dark:text-gray-200" > { t ( 'modalBehavior' ) } </ h2 >
128+ </ div >
129+
130+ < div className = "space-y-4" >
131+ < div className = "flex items-center justify-between" >
132+ < div className = "flex-1" >
133+ < h3 className = "text-sm font-medium text-gray-900 dark:text-gray-100" >
134+ { t ( 'closeModalOnOutsideClick' ) }
135+ </ h3 >
136+ < p className = "text-sm text-gray-500 dark:text-gray-400 mt-1" >
137+ { t ( 'closeModalOnOutsideClickDescription' ) }
138+ </ p >
139+ </ div >
140+ < label className = "relative inline-flex items-center cursor-pointer ml-4" >
141+ < input
142+ type = "checkbox"
143+ checked = { settings . closeModalOnOutsideClick }
144+ onChange = { ( e ) => handleSettingChange ( 'closeModalOnOutsideClick' , e . target . checked ) }
145+ disabled = { isSaving }
146+ className = "sr-only peer"
147+ />
148+ < div className = "relative w-11 h-6 bg-gray-200 dark:bg-gray-700 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-purple-300 dark:peer-focus:ring-purple-800 rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-1/2 after:right-1/2 after:-translate-y-1/2 after:bg-white after:border-gray-300 dark:after:border-gray-600 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-purple-600 disabled:opacity-50" > </ div >
149+ </ label >
150+ </ div >
151+ </ div >
152+ </ div >
153+
154+ { /* 快捷键设置 */ }
155+ < div className = "bg-white/90 dark:bg-gray-800/90 backdrop-blur-sm border border-white/20 dark:border-gray-700/50 shadow-xl rounded-2xl p-8" >
156+ < div className = "flex items-center space-x-3 mb-6" >
157+ < div className = "w-8 h-8 bg-green-100 dark:bg-green-900/30 rounded-lg flex items-center justify-center" >
158+ < svg className = "w-5 h-5 text-green-600 dark:text-green-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
159+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M12 18h.01M8 21h8a2 2 0 002-2V5a2 2 0 00-2-2H8a2 2 0 00-2 2v14a2 2 0 002 2z" />
160+ </ svg >
161+ </ div >
162+ < h2 className = "text-xl font-semibold text-gray-800 dark:text-gray-200" > { t ( 'keyboardShortcuts' ) } </ h2 >
163+ </ div >
164+
165+ < div className = "space-y-4" >
166+ < p className = "text-sm text-gray-600 dark:text-gray-400" >
167+ { t ( 'shortcutsDescription' ) }
168+ </ p >
169+
170+ { /* 显示当前快捷键 */ }
171+ < div className = "space-y-3" >
172+ < div className = "flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg" >
173+ < div >
174+ < h4 className = "text-sm font-medium text-gray-900 dark:text-gray-100" >
175+ { t ( 'openPromptSelector' ) }
176+ </ h4 >
177+ < p className = "text-xs text-gray-500 dark:text-gray-400" >
178+ { t ( 'openPromptSelectorDescription' ) }
179+ </ p >
180+ </ div >
181+ < div className = "flex items-center space-x-2" >
182+ { shortcuts [ 'open-prompt-selector' ] ? (
183+ < kbd className = "px-2 py-1 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600" >
184+ { shortcuts [ 'open-prompt-selector' ] }
185+ </ kbd >
186+ ) : (
187+ < span className = "text-xs text-gray-400 dark:text-gray-500" >
188+ { t ( 'notSet' ) }
189+ </ span >
190+ ) }
191+ </ div >
192+ </ div >
193+
194+ < div className = "flex items-center justify-between p-4 bg-gray-50 dark:bg-gray-700/50 rounded-lg" >
195+ < div >
196+ < h4 className = "text-sm font-medium text-gray-900 dark:text-gray-100" >
197+ { t ( 'openOptionsPage' ) }
198+ </ h4 >
199+ < p className = "text-xs text-gray-500 dark:text-gray-400" >
200+ { t ( 'openOptionsPageDescription' ) }
201+ </ p >
202+ </ div >
203+ < div className = "flex items-center space-x-2" >
204+ { shortcuts [ 'open-options' ] ? (
205+ < kbd className = "px-2 py-1 text-xs font-semibold text-gray-800 bg-gray-100 border border-gray-200 rounded-lg dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600" >
206+ { shortcuts [ 'open-options' ] }
207+ </ kbd >
208+ ) : (
209+ < span className = "text-xs text-gray-400 dark:text-gray-500" >
210+ { t ( 'notSet' ) }
211+ </ span >
212+ ) }
213+ </ div >
214+ </ div >
215+ </ div >
216+
217+ { /* 设置快捷键按钮 */ }
218+ < div className = "flex justify-center pt-4" >
219+ < button
220+ onClick = { openShortcutSettings }
221+ className = "inline-flex items-center px-6 py-3 bg-gradient-to-r from-green-600 to-emerald-600 hover:from-green-700 hover:to-emerald-700 text-white text-sm font-medium rounded-xl transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5 shadow-green-500/25"
222+ >
223+ < svg className = "w-5 h-5 mr-2" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
224+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" />
225+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M15 12a3 3 0 11-6 0 3 3 0 016 0z" />
226+ </ svg >
227+ { t ( 'manageShortcuts' ) }
228+ </ button >
229+ </ div >
230+ </ div >
231+ </ div >
232+
233+ { /* 帮助信息 */ }
234+ < div className = "bg-blue-50/80 dark:bg-blue-900/30 backdrop-blur-sm border border-blue-200/50 dark:border-blue-800/50 rounded-2xl p-6" >
235+ < div className = "flex items-start space-x-3" >
236+ < div className = "flex-shrink-0" >
237+ < svg className = "w-6 h-6 text-blue-600 dark:text-blue-400" fill = "none" stroke = "currentColor" viewBox = "0 0 24 24" >
238+ < path strokeLinecap = "round" strokeLinejoin = "round" strokeWidth = "2" d = "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
239+ </ svg >
240+ </ div >
241+ < div >
242+ < h3 className = "text-sm font-medium text-blue-900 dark:text-blue-200 mb-1" >
243+ { t ( 'helpAndTips' ) }
244+ </ h3 >
245+ < div className = "text-sm text-blue-800 dark:text-blue-300 space-y-2" >
246+ < p > { t ( 'globalSettingsHelpText1' ) } </ p >
247+ < p > { t ( 'globalSettingsHelpText2' ) } </ p >
248+ </ div >
249+ </ div >
250+ </ div >
251+ </ div >
252+ </ div >
253+ </ div >
254+ </ div >
255+ ) ;
256+ } ;
257+
258+ export default GlobalSettingsPage ;
0 commit comments