121 lines
3.4 KiB
TypeScript
121 lines
3.4 KiB
TypeScript
import { create } from 'zustand'
|
|
import { createLogger } from '../utils/logger'
|
|
import {
|
|
DEFAULT_KEYWORD_HIGHLIGHT_RULES,
|
|
type KeywordHighlightRule,
|
|
} from '../utils/keywordHighlight'
|
|
|
|
const logger = createLogger('SettingsStore')
|
|
|
|
export interface AppSettings {
|
|
// 外观
|
|
theme: 'dark' | 'light' | 'system'
|
|
sidebarWidth: number
|
|
|
|
// 终端
|
|
fontFamily: string
|
|
fontSize: number
|
|
terminalTheme: 'dracula' | 'monokai' | 'solarized' | 'one-dark'
|
|
|
|
// SSH
|
|
keepAlive: boolean
|
|
timeout: number
|
|
|
|
// 专业连接管理
|
|
maxConnections: number
|
|
maxBackgroundConnections: number
|
|
idleTimeoutMs: number
|
|
suspendTimeoutMs: number
|
|
autoReconnect: boolean
|
|
maxReconnectAttempts: number
|
|
reconnectBaseDelayMs: number
|
|
|
|
// 终端增强
|
|
scrollback: number
|
|
keywordHighlightEnabled: boolean
|
|
keywordHighlightRules: KeywordHighlightRule[]
|
|
}
|
|
|
|
const defaultSettings: AppSettings = {
|
|
theme: 'dark',
|
|
sidebarWidth: 256,
|
|
// Prefer Nerd Font so local zsh powerline prompts render correctly
|
|
fontFamily: 'MesloLGS NF',
|
|
fontSize: 14,
|
|
terminalTheme: 'dracula',
|
|
keepAlive: true,
|
|
timeout: 30,
|
|
// 专业连接管理默认值
|
|
maxConnections: 10,
|
|
maxBackgroundConnections: 5,
|
|
idleTimeoutMs: 5 * 60 * 1000, // 5 分钟
|
|
suspendTimeoutMs: 30 * 60 * 1000, // 30 分钟
|
|
autoReconnect: true,
|
|
maxReconnectAttempts: 5,
|
|
reconnectBaseDelayMs: 1500,
|
|
scrollback: 10000,
|
|
keywordHighlightEnabled: true,
|
|
keywordHighlightRules: DEFAULT_KEYWORD_HIGHLIGHT_RULES,
|
|
}
|
|
|
|
export function normalizeAppSettings(value: unknown): AppSettings {
|
|
const source = value && typeof value === 'object'
|
|
? value as Record<string, unknown>
|
|
: {}
|
|
const normalized: Record<string, unknown> = {}
|
|
|
|
for (const key of Object.keys(defaultSettings) as Array<keyof AppSettings>) {
|
|
const candidate = source[key]
|
|
const fallback = defaultSettings[key]
|
|
normalized[key] = typeof candidate === typeof fallback ? candidate : fallback
|
|
}
|
|
|
|
return normalized as unknown as AppSettings
|
|
}
|
|
|
|
interface SettingsState {
|
|
settings: AppSettings
|
|
isLoaded: boolean
|
|
setSetting: <K extends keyof AppSettings>(key: K, value: AppSettings[K]) => Promise<void>
|
|
loadSettings: () => Promise<void>
|
|
saveSettings: (settings: Partial<AppSettings>) => Promise<void>
|
|
}
|
|
|
|
export const useSettingsStore = create<SettingsState>((set, get) => ({
|
|
settings: defaultSettings,
|
|
isLoaded: false,
|
|
|
|
loadSettings: async () => {
|
|
try {
|
|
const result = await window.opsxApi?.settingsGet('app-settings')
|
|
if (result?.success && result.value) {
|
|
const settings = normalizeAppSettings(result.value)
|
|
set({ settings, isLoaded: true })
|
|
|
|
const storedKeys = Object.keys(result.value as Record<string, unknown>)
|
|
const currentKeys = new Set(Object.keys(defaultSettings))
|
|
if (storedKeys.some((key) => !currentKeys.has(key))) {
|
|
await window.opsxApi?.settingsSet('app-settings', settings)
|
|
}
|
|
} else {
|
|
set({ isLoaded: true })
|
|
}
|
|
} catch (error) {
|
|
logger.error('Failed to load settings:', error)
|
|
set({ isLoaded: true })
|
|
}
|
|
},
|
|
|
|
setSetting: async (key, value) => {
|
|
const newSettings = { ...get().settings, [key]: value }
|
|
set({ settings: newSettings })
|
|
await window.opsxApi?.settingsSet('app-settings', newSettings)
|
|
},
|
|
|
|
saveSettings: async (newSettings) => {
|
|
const merged = { ...get().settings, ...newSettings }
|
|
set({ settings: merged })
|
|
await window.opsxApi?.settingsSet('app-settings', merged)
|
|
},
|
|
}))
|