refactor(preferences): enhance PreferenceService with public access modifiers and improve caching logic

This commit updates the PreferenceService by adding public access modifiers to several methods, improving code clarity and consistency. It also refines the caching logic to eliminate unnecessary type assertions and streamline the handling of preference values. Additionally, minor formatting adjustments are made for better readability across the service and related hooks.
This commit is contained in:
fullex 2025-08-12 18:14:39 +08:00
parent b219e96544
commit 2860935e5b
7 changed files with 302 additions and 145 deletions

View File

@ -97,9 +97,8 @@ export class PreferenceService {
throw new Error(`Preference ${key} not found in cache`)
}
const db = dbService.getDb()
await db
await dbService
.getDb()
.update(preferenceTable)
.set({
value: value as any

View File

@ -10,7 +10,7 @@ const logger = loggerService.withContext('PreferenceService')
*/
export class PreferenceService {
private static instance: PreferenceService
private cache: Partial<PreferenceDefaultScopeType> = {}
private cache: Record<string, any> = {}
private listeners = new Set<() => void>()
private keyListeners = new Map<string, Set<() => void>>()
private changeListenerCleanup: (() => void) | null = null
@ -24,7 +24,7 @@ export class PreferenceService {
/**
* Get the singleton instance of PreferenceService
*/
static getInstance(): PreferenceService {
public static getInstance(): PreferenceService {
if (!PreferenceService.instance) {
PreferenceService.instance = new PreferenceService()
}
@ -68,7 +68,7 @@ export class PreferenceService {
/**
* Get a single preference value with caching
*/
async get<K extends PreferenceKeyType>(key: K): Promise<PreferenceDefaultScopeType[K]> {
public async get<K extends PreferenceKeyType>(key: K): Promise<PreferenceDefaultScopeType[K]> {
// Check cache first
if (key in this.cache && this.cache[key] !== undefined) {
return this.cache[key] as PreferenceDefaultScopeType[K]
@ -95,7 +95,7 @@ export class PreferenceService {
/**
* Set a single preference value
*/
async set<K extends PreferenceKeyType>(key: K, value: PreferenceDefaultScopeType[K]): Promise<void> {
public async set<K extends PreferenceKeyType>(key: K, value: PreferenceDefaultScopeType[K]): Promise<void> {
try {
await window.api.preference.set(key, value)
@ -113,14 +113,14 @@ export class PreferenceService {
/**
* Get multiple preferences at once
*/
async getMultiple(keys: PreferenceKeyType[]): Promise<Record<string, any>> {
public async getMultiple(keys: PreferenceKeyType[]): Promise<Record<string, any>> {
// Check which keys are already cached
const cachedResults: Partial<PreferenceDefaultScopeType> = {}
const uncachedKeys: PreferenceKeyType[] = []
for (const key of keys) {
if (key in this.cache && this.cache[key] !== undefined) {
;(cachedResults as any)[key] = this.cache[key]
cachedResults[key] = this.cache[key]
} else {
uncachedKeys.push(key)
}
@ -133,7 +133,7 @@ export class PreferenceService {
// Update cache with new results
for (const [key, value] of Object.entries(uncachedResults)) {
;(this.cache as any)[key] = value
this.cache[key as PreferenceKeyType] = value
}
// Auto-subscribe to new keys
@ -165,13 +165,13 @@ export class PreferenceService {
/**
* Set multiple preferences at once
*/
async setMultiple(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
public async setMultiple(updates: Partial<PreferenceDefaultScopeType>): Promise<void> {
try {
await window.api.preference.setMultiple(updates)
// Update local cache for all updated values
for (const [key, value] of Object.entries(updates)) {
;(this.cache as any)[key] = value
this.cache[key as PreferenceKeyType] = value
this.notifyListeners(key)
}
@ -200,7 +200,7 @@ export class PreferenceService {
/**
* Subscribe to global preference changes (for useSyncExternalStore)
*/
subscribe = (callback: () => void): (() => void) => {
public subscribe = (callback: () => void): (() => void) => {
this.listeners.add(callback)
return () => {
this.listeners.delete(callback)
@ -210,7 +210,7 @@ export class PreferenceService {
/**
* Subscribe to specific key changes (for useSyncExternalStore)
*/
subscribeToKey =
public subscribeToKey =
(key: PreferenceKeyType) =>
(callback: () => void): (() => void) => {
if (!this.keyListeners.has(key)) {
@ -234,7 +234,7 @@ export class PreferenceService {
/**
* Get snapshot for useSyncExternalStore
*/
getSnapshot =
public getSnapshot =
<K extends PreferenceKeyType>(key: K) =>
(): PreferenceDefaultScopeType[K] | undefined => {
return this.cache[key]
@ -243,14 +243,14 @@ export class PreferenceService {
/**
* Get cached value without async fetch
*/
getCachedValue<K extends PreferenceKeyType>(key: K): PreferenceDefaultScopeType[K] | undefined {
public getCachedValue<K extends PreferenceKeyType>(key: K): PreferenceDefaultScopeType[K] | undefined {
return this.cache[key]
}
/**
* Check if a preference is cached
*/
isCached(key: PreferenceKeyType): boolean {
public isCached(key: PreferenceKeyType): boolean {
return key in this.cache && this.cache[key] !== undefined
}
@ -258,13 +258,13 @@ export class PreferenceService {
* Load all preferences from main process at once
* Provides optimal performance by loading complete preference set into memory
*/
async loadAll(): Promise<PreferenceDefaultScopeType> {
public async loadAll(): Promise<PreferenceDefaultScopeType> {
try {
const allPreferences = await window.api.preference.getAll()
// Update local cache with all preferences
for (const [key, value] of Object.entries(allPreferences)) {
;(this.cache as any)[key] = value
this.cache[key as PreferenceKeyType] = value
// Auto-subscribe to this key if not already subscribed
if (!this.subscribedKeys.has(key)) {
@ -285,7 +285,7 @@ export class PreferenceService {
/**
* Check if all preferences are loaded in cache
*/
isFullyCached(): boolean {
public isFullyCached(): boolean {
return this.fullCacheLoaded
}
@ -308,7 +308,7 @@ export class PreferenceService {
/**
* Clear all cached preferences (for testing/debugging)
*/
clearCache(): void {
public clearCache(): void {
this.cache = {}
this.fullCacheLoaded = false
logger.debug('Preference cache cleared')
@ -317,7 +317,7 @@ export class PreferenceService {
/**
* Cleanup service (call when shutting down)
*/
cleanup(): void {
public cleanup(): void {
if (this.changeListenerCleanup) {
this.changeListenerCleanup()
this.changeListenerCleanup = null

View File

@ -1,17 +1,69 @@
import { preferenceService } from '@data/PreferenceService'
import { loggerService } from '@logger'
import type { PreferenceDefaultScopeType, PreferenceKeyType } from '@shared/data/types'
import { useCallback, useEffect, useMemo, useRef, useSyncExternalStore } from 'react'
import { preferenceService } from '../PreferenceService'
const logger = loggerService.withContext('usePreference')
/**
* React hook for managing a single preference value
* Uses useSyncExternalStore for optimal React 18 integration
* React hook for managing a single preference value with automatic synchronization
* Uses useSyncExternalStore for optimal React 18 integration and real-time updates
*
* @param key - The preference key to manage
* @returns [value, setValue] - Current value and setter function
* @param key - The preference key to manage (must be a valid PreferenceKeyType)
* @returns A tuple [value, setValue] where:
* - value: Current preference value or undefined if not loaded/cached
* - setValue: Async function to update the preference value
*
* @example
* ```typescript
* // Basic usage - managing theme preference
* const [theme, setTheme] = usePreference('app.theme.mode')
*
* // Conditional rendering based on preference value
* if (theme === undefined) {
* return <LoadingSpinner />
* }
*
* // Updating preference value
* const handleThemeChange = async (newTheme: string) => {
* try {
* await setTheme(newTheme)
* } catch (error) {
* console.error('Failed to update theme:', error)
* }
* }
*
* return (
* <select value={theme} onChange={(e) => handleThemeChange(e.target.value)}>
* <option value="ThemeMode.light">Light</option>
* <option value="ThemeMode.dark">Dark</option>
* <option value="ThemeMode.system">System</option>
* </select>
* )
* ```
*
* @example
* ```typescript
* // Advanced usage with form handling for message font size
* const [fontSize, setFontSize] = usePreference('chat.message.font_size')
*
* const handleFontSizeChange = useCallback(async (size: number) => {
* if (size < 8 || size > 72) {
* throw new Error('Font size must be between 8 and 72')
* }
* await setFontSize(size)
* }, [setFontSize])
*
* return (
* <input
* type="number"
* value={fontSize ?? 14}
* onChange={(e) => handleFontSizeChange(Number(e.target.value))}
* min={8}
* max={72}
* />
* )
* ```
*/
export function usePreference<K extends PreferenceKeyType>(
key: K
@ -49,13 +101,126 @@ export function usePreference<K extends PreferenceKeyType>(
}
/**
* React hook for managing multiple preference values
* Efficiently batches operations and provides type-safe interface
* React hook for managing multiple preference values with efficient batch operations
* Automatically synchronizes all specified preferences and provides type-safe access
*
* @param keys - Object mapping local names to preference keys
* @returns [values, updateValues] - Current values and batch update function
* @param keys - Object mapping local names to preference keys. Keys are your custom names,
* values must be valid PreferenceKeyType identifiers
* @returns A tuple [values, updateValues] where:
* - values: Object with your local keys mapped to current preference values (undefined if not loaded)
* - updateValues: Async function to batch update multiple preferences at once
*
* @example
* ```typescript
* // Basic usage - managing related UI preferences
* const [uiSettings, setUISettings] = useMultiplePreferences({
* theme: 'app.theme.mode',
* fontSize: 'chat.message.font_size',
* showLineNumbers: 'chat.code.show_line_numbers'
* })
*
* // Accessing individual values with type safety
* const currentTheme = uiSettings.theme // string | undefined
* const currentFontSize = uiSettings.fontSize // number | undefined
* const showLines = uiSettings.showLineNumbers // boolean | undefined
*
* // Batch updating multiple preferences
* const resetToDefaults = async () => {
* await setUISettings({
* theme: 'ThemeMode.light',
* fontSize: 14,
* showLineNumbers: true
* })
* }
*
* // Partial updates (only specified keys will be updated)
* const toggleTheme = async () => {
* await setUISettings({
* theme: currentTheme === 'ThemeMode.light' ? 'ThemeMode.dark' : 'ThemeMode.light'
* })
* }
* ```
*
* @example
* ```typescript
* // Advanced usage - backup settings form with validation
* const [settings, updateSettings] = useMultiplePreferences({
* autoSync: 'data.backup.local.auto_sync',
* backupDir: 'data.backup.local.dir',
* maxBackups: 'data.backup.local.max_backups',
* syncInterval: 'data.backup.local.sync_interval'
* })
*
* // Form submission with error handling
* const handleSubmit = async (formData: Partial<typeof settings>) => {
* try {
* // Validate before saving
* if (formData.maxBackups && formData.maxBackups < 0) {
* throw new Error('Max backups must be non-negative')
* }
*
* await updateSettings(formData)
* showSuccessMessage('Backup settings saved successfully')
* } catch (error) {
* showErrorMessage(`Failed to save settings: ${error.message}`)
* }
* }
*
* // Conditional rendering based on loading state
* if (Object.values(settings).every(val => val === undefined)) {
* return <SettingsSkeletonLoader />
* }
*
* return (
* <form onSubmit={(e) => {
* e.preventDefault()
* handleSubmit({
* maxBackups: parseInt(e.target.maxBackups.value),
* syncInterval: parseInt(e.target.syncInterval.value)
* })
* }}>
* <input
* name="maxBackups"
* type="number"
* defaultValue={settings.maxBackups ?? 10}
* min="0"
* />
* <input
* name="syncInterval"
* type="number"
* defaultValue={settings.syncInterval ?? 3600}
* min="60"
* />
* <button type="submit">Save Backup Settings</button>
* </form>
* )
* ```
*
* @example
* ```typescript
* // Performance optimization - grouping related chat code preferences
* const [codePrefs] = useMultiplePreferences({
* showLineNumbers: 'chat.code.show_line_numbers',
* wrappable: 'chat.code.wrappable',
* collapsible: 'chat.code.collapsible',
* autocompletion: 'chat.code.editor.autocompletion',
* foldGutter: 'chat.code.editor.fold_gutter'
* })
*
* // Single subscription handles all code preferences
* // More efficient than 5 separate usePreference calls
* const codeConfig = useMemo(() => ({
* showLineNumbers: codePrefs.showLineNumbers ?? false,
* wrappable: codePrefs.wrappable ?? false,
* collapsible: codePrefs.collapsible ?? false,
* autocompletion: codePrefs.autocompletion ?? true,
* foldGutter: codePrefs.foldGutter ?? false
* }), [codePrefs])
*
* return <CodeBlock config={codeConfig} />
* ```
*/
export function usePreferences<T extends Record<string, PreferenceKeyType>>(
export function useMultiplePreferences<T extends Record<string, PreferenceKeyType>>(
keys: T
): [
{ [P in keyof T]: PreferenceDefaultScopeType[T[P]] | undefined },
@ -63,7 +228,6 @@ export function usePreferences<T extends Record<string, PreferenceKeyType>>(
] {
// Create stable key dependencies
const keyList = useMemo(() => Object.values(keys), [keys])
const keysStringified = useMemo(() => JSON.stringify(keys), [keys])
// Cache the last snapshot to avoid infinite loops
const lastSnapshotRef = useRef<Record<string, any>>({})
@ -78,7 +242,7 @@ export function usePreferences<T extends Record<string, PreferenceKeyType>>(
unsubscribeFunctions.forEach((unsubscribe) => unsubscribe())
}
},
[keysStringified]
[keyList]
),
useCallback(() => {
@ -101,9 +265,9 @@ export function usePreferences<T extends Record<string, PreferenceKeyType>>(
}
return lastSnapshotRef.current
}, [keysStringified]),
}, [keys]),
() => ({}) // SSR snapshot
() => ({}) // No SSR snapshot
)
// Load initial values asynchronously if not cached
@ -115,7 +279,7 @@ export function usePreferences<T extends Record<string, PreferenceKeyType>>(
logger.error('Failed to load initial preferences:', error as Error)
})
}
}, [keysStringified])
}, [keyList])
// Memoized batch update function
const updateValues = useCallback(
@ -136,7 +300,7 @@ export function usePreferences<T extends Record<string, PreferenceKeyType>>(
throw error
}
},
[keysStringified]
[keys]
)
// Type-cast the values to the expected shape
@ -144,26 +308,3 @@ export function usePreferences<T extends Record<string, PreferenceKeyType>>(
return [typedValues, updateValues]
}
/**
* Hook for preloading preferences to improve performance
* Useful for components that will use many preferences
*
* @param keys - Array of preference keys to preload
*/
export function usePreferencePreload(keys: PreferenceKeyType[]): void {
const keysString = useMemo(() => keys.join(','), [keys])
useEffect(() => {
preferenceService.preload(keys).catch((error) => {
logger.error('Failed to preload preferences:', error as Error)
})
}, [keysString])
}
/**
* Hook for getting the preference service instance
* Useful for non-reactive operations or advanced usage
*/
export function usePreferenceService() {
return preferenceService
}

View File

@ -29,11 +29,11 @@ const PreferenceHookTests: React.FC = () => {
const testSubscriptions = () => {
// Test subscription behavior
const unsubscribe = preferenceService.subscribeToKey('app.theme.mode')(() => {
setSubscriptionCount(prev => prev + 1)
setSubscriptionCount((prev) => prev + 1)
})
message.info('已添加订阅修改app.theme.mode将触发计数')
// Clean up after 10 seconds
setTimeout(() => {
unsubscribe()
@ -45,14 +45,14 @@ const PreferenceHookTests: React.FC = () => {
try {
const keys: PreferenceKeyType[] = ['app.theme.mode', 'app.language', 'app.zoom_factor', 'app.spell_check.enabled']
await preferenceService.preload(keys)
const cachedStates = keys.map(key => ({
const cachedStates = keys.map((key) => ({
key,
isCached: preferenceService.isCached(key),
value: preferenceService.getCachedValue(key)
}))
message.success(`预加载完成。缓存状态: ${cachedStates.filter(s => s.isCached).length}/${keys.length}`)
message.success(`预加载完成。缓存状态: ${cachedStates.filter((s) => s.isCached).length}/${keys.length}`)
console.log('Cache states:', cachedStates)
} catch (error) {
message.error(`预加载失败: ${(error as Error).message}`)
@ -63,16 +63,16 @@ const PreferenceHookTests: React.FC = () => {
try {
const keys: PreferenceKeyType[] = ['app.theme.mode', 'app.language']
const result = await preferenceService.getMultiple(keys)
message.success(`批量获取成功: ${Object.keys(result).length}`)
console.log('Batch get result:', result)
// Test batch set
await preferenceService.setMultiple({
'app.theme.mode': theme1 === 'ThemeMode.dark' ? 'ThemeMode.light' : 'ThemeMode.dark',
'app.language': language === 'zh-CN' ? 'en-US' : 'zh-CN'
})
message.success('批量设置成功')
} catch (error) {
message.error(`批量操作失败: ${(error as Error).message}`)
@ -82,23 +82,25 @@ const PreferenceHookTests: React.FC = () => {
const performanceTest = async () => {
const start = performance.now()
const iterations = 100
try {
// Test rapid reads
for (let i = 0; i < iterations; i++) {
preferenceService.getCachedValue('app.theme.mode')
}
const readTime = performance.now() - start
// Test rapid writes
const writeStart = performance.now()
for (let i = 0; i < 10; i++) {
await preferenceService.set('app.theme.mode', `ThemeMode.test_${i}`)
}
const writeTime = performance.now() - writeStart
message.success(`性能测试完成: 读取${iterations}次耗时${readTime.toFixed(2)}ms, 写入10次耗时${writeTime.toFixed(2)}ms`)
message.success(
`性能测试完成: 读取${iterations}次耗时${readTime.toFixed(2)}ms, 写入10次耗时${writeTime.toFixed(2)}ms`
)
} catch (error) {
message.error(`性能测试失败: ${(error as Error).message}`)
}
@ -110,11 +112,21 @@ const PreferenceHookTests: React.FC = () => {
{/* Hook State Display */}
<Card size="small" title="Hook 状态监控">
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Text>: <Text strong>{renderCountRef.current}</Text></Text>
<Text>: <Text strong>{subscriptionCount}</Text></Text>
<Text>Theme Hook 1: <Text code>{String(theme1)}</Text></Text>
<Text>Theme Hook 2: <Text code>{String(theme2)}</Text></Text>
<Text>Language Hook: <Text code>{String(language)}</Text></Text>
<Text>
: <Text strong>{renderCountRef.current}</Text>
</Text>
<Text>
: <Text strong>{subscriptionCount}</Text>
</Text>
<Text>
Theme Hook 1: <Text code>{String(theme1)}</Text>
</Text>
<Text>
Theme Hook 2: <Text code>{String(theme2)}</Text>
</Text>
<Text>
Language Hook: <Text code>{String(language)}</Text>
</Text>
<Text type="secondary" style={{ fontSize: '12px' }}>
注意: 相同key的多个hook应该返回相同值
</Text>
@ -123,24 +135,18 @@ const PreferenceHookTests: React.FC = () => {
{/* Test Actions */}
<Space wrap>
<Button onClick={testSubscriptions}>
</Button>
<Button onClick={testCacheWarming}>
</Button>
<Button onClick={testBatchOperations}>
</Button>
<Button onClick={performanceTest}>
</Button>
<Button onClick={testSubscriptions}></Button>
<Button onClick={testCacheWarming}></Button>
<Button onClick={testBatchOperations}></Button>
<Button onClick={performanceTest}></Button>
</Space>
{/* Service Information */}
<Card size="small" title="Service 信息">
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Text>Service实例: <Text code>{preferenceService ? '已连接' : '未连接'}</Text></Text>
<Text>
Service实例: <Text code>{preferenceService ? '已连接' : '未连接'}</Text>
</Text>
<Text>预加载Keys: app.theme.mode, app.language, app.zoom_factor</Text>
<Text type="secondary" style={{ fontSize: '12px' }}>
usePreferenceService()
@ -152,15 +158,9 @@ const PreferenceHookTests: React.FC = () => {
<Card size="small" title="Hook 行为测试">
<Space direction="vertical" size="small" style={{ width: '100%' }}>
<Text strong>:</Text>
<Text type="secondary">
1. app.theme.mode app.language
</Text>
<Text type="secondary">
2.
</Text>
<Text type="secondary">
3.
</Text>
<Text type="secondary">1. app.theme.mode app.language</Text>
<Text type="secondary">2. </Text>
<Text type="secondary">3. </Text>
</Space>
</Card>
</Space>
@ -174,4 +174,4 @@ const TestContainer = styled.div`
border-radius: 8px;
`
export default PreferenceHookTests
export default PreferenceHookTests

View File

@ -1,4 +1,4 @@
import { usePreferences } from '@renderer/data/hooks/usePreference'
import { useMultiplePreferences } from '@renderer/data/hooks/usePreference'
import { Button, Card, Input, message, Select, Space, Table, Typography } from 'antd'
import { ColumnType } from 'antd/es/table'
import React, { useState } from 'react'
@ -40,7 +40,7 @@ const PreferenceMultipleTests: React.FC = () => {
} as const
const currentKeys = scenarios[scenario as keyof typeof scenarios]
const [values, updateValues] = usePreferences(currentKeys)
const [values, updateValues] = useMultiplePreferences(currentKeys)
const [batchInput, setBatchInput] = useState<string>('')
@ -72,7 +72,7 @@ const PreferenceMultipleTests: React.FC = () => {
const sampleUpdates: Record<string, any> = {}
Object.keys(currentKeys).forEach((localKey, index) => {
const prefKey = currentKeys[localKey as keyof typeof currentKeys]
switch (prefKey) {
case 'app.theme.mode':
sampleUpdates[localKey] = values[localKey] === 'ThemeMode.dark' ? 'ThemeMode.light' : 'ThemeMode.dark'
@ -81,7 +81,7 @@ const PreferenceMultipleTests: React.FC = () => {
sampleUpdates[localKey] = values[localKey] === 'zh-CN' ? 'en-US' : 'zh-CN'
break
case 'app.zoom_factor':
sampleUpdates[localKey] = 1.0 + (index * 0.1)
sampleUpdates[localKey] = 1.0 + index * 0.1
break
case 'app.spell_check.enabled':
sampleUpdates[localKey] = !values[localKey]
@ -90,7 +90,7 @@ const PreferenceMultipleTests: React.FC = () => {
sampleUpdates[localKey] = `sample_value_${index}`
}
})
setBatchInput(JSON.stringify(sampleUpdates, null, 2))
}
@ -115,7 +115,11 @@ const PreferenceMultipleTests: React.FC = () => {
render: (value: any) => (
<ValueDisplay>
{value !== undefined ? (
typeof value === 'object' ? JSON.stringify(value) : String(value)
typeof value === 'object' ? (
JSON.stringify(value)
) : (
String(value)
)
) : (
<Text type="secondary">undefined</Text>
)}
@ -136,7 +140,14 @@ const PreferenceMultipleTests: React.FC = () => {
render: (_, record) => (
<Space size="small">
{record.prefKey === 'app.theme.mode' && (
<Button size="small" onClick={() => handleQuickUpdate(record.localKey, record.value === 'ThemeMode.dark' ? 'ThemeMode.light' : 'ThemeMode.dark')}>
<Button
size="small"
onClick={() =>
handleQuickUpdate(
record.localKey,
record.value === 'ThemeMode.dark' ? 'ThemeMode.light' : 'ThemeMode.dark'
)
}>
</Button>
)}
@ -146,7 +157,9 @@ const PreferenceMultipleTests: React.FC = () => {
</Button>
)}
{record.prefKey === 'app.language' && (
<Button size="small" onClick={() => handleQuickUpdate(record.localKey, record.value === 'zh-CN' ? 'en-US' : 'zh-CN')}>
<Button
size="small"
onClick={() => handleQuickUpdate(record.localKey, record.value === 'zh-CN' ? 'en-US' : 'zh-CN')}>
</Button>
)}
@ -183,35 +196,27 @@ const PreferenceMultipleTests: React.FC = () => {
{/* Current Values Table */}
<Card size="small" title="当前值状态">
<Table
columns={columns}
dataSource={tableData}
pagination={false}
size="small"
bordered
/>
<Table columns={columns} dataSource={tableData} pagination={false} size="small" bordered />
</Card>
{/* Batch Update */}
<Card size="small" title="批量更新">
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Space>
<Button onClick={generateSampleBatch}>
</Button>
<Button onClick={generateSampleBatch}></Button>
<Button type="primary" onClick={handleBatchUpdate} disabled={!batchInput.trim()}>
</Button>
</Space>
<Input.TextArea
value={batchInput}
onChange={(e) => setBatchInput(e.target.value)}
placeholder="输入JSON格式的批量更新数据例如: {&quot;theme&quot;: &quot;dark&quot;, &quot;language&quot;: &quot;en-US&quot;}"
placeholder='输入JSON格式的批量更新数据例如: {"theme": "dark", "language": "en-US"}'
rows={6}
style={{ fontFamily: 'monospace' }}
/>
<Text type="secondary" style={{ fontSize: '12px' }}>
: &#123;"localKey": "newValue", ...&#125; - 使
</Text>
@ -221,15 +226,17 @@ const PreferenceMultipleTests: React.FC = () => {
{/* Quick Actions */}
<Card size="small" title="快速操作">
<Space wrap>
<Button
onClick={() => updateValues(Object.fromEntries(Object.keys(currentKeys).map(key => [key, 'test_value'])))}>
<Button
onClick={() =>
updateValues(Object.fromEntries(Object.keys(currentKeys).map((key) => [key, 'test_value'])))
}>
</Button>
<Button
onClick={() => updateValues(Object.fromEntries(Object.keys(currentKeys).map(key => [key, undefined])))}>
<Button
onClick={() => updateValues(Object.fromEntries(Object.keys(currentKeys).map((key) => [key, undefined])))}>
</Button>
<Button
<Button
onClick={async () => {
const toggles: Record<string, any> = {}
Object.entries(currentKeys).forEach(([localKey, prefKey]) => {
@ -259,7 +266,7 @@ const PreferenceMultipleTests: React.FC = () => {
: <Text strong>{Object.keys(values).length}</Text>
</Text>
<Text>
: <Text strong>{Object.values(values).filter(v => v !== undefined).length}</Text>
: <Text strong>{Object.values(values).filter((v) => v !== undefined).length}</Text>
</Text>
<Text type="secondary" style={{ fontSize: '12px' }}>
usePreferences 使
@ -283,4 +290,4 @@ const ValueDisplay = styled.span`
word-break: break-all;
`
export default PreferenceMultipleTests
export default PreferenceMultipleTests

View File

@ -34,16 +34,22 @@ const PreferenceServiceTests: React.FC = () => {
try {
setLoading(true)
let parsedValue: any = testValue
// Try to parse as JSON if it looks like an object/array/boolean/number
if (testValue.startsWith('{') || testValue.startsWith('[') || testValue === 'true' || testValue === 'false' || !isNaN(Number(testValue))) {
if (
testValue.startsWith('{') ||
testValue.startsWith('[') ||
testValue === 'true' ||
testValue === 'false' ||
!isNaN(Number(testValue))
) {
try {
parsedValue = JSON.parse(testValue)
} catch {
// Keep as string if JSON parsing fails
}
}
await preferenceService.set(testKey as PreferenceKeyType, parsedValue)
message.success('设置成功')
// Automatically get the updated value
@ -93,7 +99,13 @@ const PreferenceServiceTests: React.FC = () => {
try {
setLoading(true)
// Get multiple keys to simulate getAll functionality
const sampleKeys = ['app.theme.mode', 'app.language', 'app.zoom_factor', 'app.spell_check.enabled', 'app.user.name'] as PreferenceKeyType[]
const sampleKeys = [
'app.theme.mode',
'app.language',
'app.zoom_factor',
'app.spell_check.enabled',
'app.user.name'
] as PreferenceKeyType[]
const result = await preferenceService.getMultiple(sampleKeys)
setGetResult(`Sample preferences (${Object.keys(result).length} keys):\n${JSON.stringify(result, null, 2)}`)
message.success('获取示例偏好设置成功')
@ -138,12 +150,8 @@ const PreferenceServiceTests: React.FC = () => {
<Button onClick={handleSet} loading={loading}>
Set
</Button>
<Button onClick={handleGetCached}>
Get Cached
</Button>
<Button onClick={handleIsCached}>
Is Cached
</Button>
<Button onClick={handleGetCached}>Get Cached</Button>
<Button onClick={handleIsCached}>Is Cached</Button>
<Button onClick={handlePreload} loading={loading}>
Preload
</Button>
@ -156,7 +164,9 @@ const PreferenceServiceTests: React.FC = () => {
{getResult !== null && (
<ResultContainer>
<Text strong>Result:</Text>
<ResultText>{typeof getResult === 'object' ? JSON.stringify(getResult, null, 2) : String(getResult)}</ResultText>
<ResultText>
{typeof getResult === 'object' ? JSON.stringify(getResult, null, 2) : String(getResult)}
</ResultText>
</ResultContainer>
)}
@ -215,4 +225,4 @@ const ResultText = styled.pre`
word-break: break-all;
`
export default PreferenceServiceTests
export default PreferenceServiceTests

View File

@ -10,4 +10,4 @@ loggerService.initWindowSource('DataRefactorTestWindow')
const root = createRoot(document.getElementById('root') as HTMLElement)
root.render(<TestApp />)
root.render(<TestApp />)