feat(preferences): enhance testing framework with real-time UI synchronization and slider controls

This commit updates the dataRefactorTest README to include new features such as real-time UI linkage for theme, language, and zoom preferences, as well as interactive slider controls for various settings. The TestApp component is modified to support dynamic window identification and theme monitoring. Additionally, the PreferenceBasicTests, PreferenceMultipleTests, and PreferenceServiceTests components are enhanced with slider functionality for numerical preferences, improving the testing capabilities for cross-window synchronization and user experience.
This commit is contained in:
fullex 2025-08-12 23:42:10 +08:00
parent 85bdcdc206
commit df876651b9
6 changed files with 431 additions and 96 deletions

View File

@ -8,6 +8,9 @@
- 专用的测试窗口 (DataRefactorTestWindow)
- **双窗口启动**:应用启动时会同时打开主窗口和两个测试窗口
- **跨窗口同步测试**:两个测试窗口可以相互验证偏好设置的实时同步
- **实时UI联动**主题、语言、缩放等偏好设置变化会立即反映在UI上
- **🎛️ Slider联动测试**:多个交互式滑块控制数值类型偏好设置,支持跨窗口实时同步
- **多源窗口编号**支持URL参数、窗口标题、窗口名称等多种方式确定窗口编号
- 完整的测试界面包含4个专业测试组件
- 自动窗口定位,避免重叠
- 窗口编号标识,便于区分
@ -25,12 +28,16 @@
- `app.theme.mode` - 主题模式
- `app.language` - 语言设置
- `app.spell_check.enabled` - 拼写检查
- `app.zoom_factor` - 缩放因子
- `app.zoom_factor` - 缩放因子 (🎛️ 支持Slider)
- `chat.message.font_size` - 消息字体大小 (🎛️ 支持Slider)
- `feature.selection.action_window_opacity` - 选择窗口透明度 (🎛️ 支持Slider)
- 实时值更新和类型转换
- **Slider联动控制**:数值类型偏好设置提供交互式滑块,支持实时拖拽调整
### 3. usePreferences 批量操作测试
- 测试多个偏好设置的批量管理
- 4种预设场景基础设置、UI设置、用户设置、自定义组合
- 5种预设场景基础设置、UI设置、用户设置、🎛数值设置、自定义组合
- **🎛️ 数值设置场景**专门的Slider联动控制区域包含缩放、字体、选择窗口透明度三个滑块
- 批量更新功能支持JSON格式输入
- 快速切换操作
@ -106,13 +113,16 @@ src/renderer/src/windows/dataRefactorTest/
1. **启动应用** - 自动打开2个测试窗口
2. **选择测试** - 在"usePreference Hook 测试"中选择要测试的偏好设置键
3. **跨窗口验证** - 在窗口#1中修改值观察窗口#2是否同步
4. **高级测试** - 使用"Hook 高级功能测试"验证订阅和缓存机制
5. **批量测试** - 使用"usePreferences 批量操作测试"进行多项设置测试
3. **🎛️ Slider联动测试** - 选择数值类型偏好设置拖动Slider观察实时变化
4. **跨窗口验证** - 在窗口#1中修改值观察窗口#2是否同步
5. **批量Slider测试** - 切换到"数值设置场景",同时拖动多个滑块测试批量同步
6. **高级测试** - 使用"Hook 高级功能测试"验证订阅和缓存机制
## 🔧 技术实现
- **窗口管理**: DataRefactorMigrateService 单例管理多个测试窗口
- **数据同步**: 基于真实的 PreferenceService 和 IPC 通信
- **实时主题**: 使用 useSyncExternalStore 实现主题、缩放等设置的实时UI响应
- **跨窗口识别**: 多源窗口编号支持,确保每个窗口都有唯一标识
- **UI框架**: Ant Design + styled-components + React 18
- **类型安全**: 完整的 TypeScript 类型检查和偏好设置键约束

View File

@ -1,4 +1,5 @@
import { AppLogo } from '@renderer/config/env'
import { usePreference } from '@renderer/data/hooks/usePreference'
import { loggerService } from '@renderer/services/LoggerService'
import { Button, Card, Col, Divider, Layout, Row, Space, Typography } from 'antd'
import { Database, FlaskConical, Settings, TestTube } from 'lucide-react'
@ -16,47 +17,91 @@ const { Title, Text } = Typography
const logger = loggerService.withContext('TestApp')
const TestApp: React.FC = () => {
// Get window title to determine window number
const windowTitle = document.title
const windowMatch = windowTitle.match(/#(\d+)/)
const windowNumber = windowMatch ? windowMatch[1] : '1'
// Get window number from multiple sources
const getWindowNumber = () => {
// Try URL search params first
const urlParams = new URLSearchParams(window.location.search)
const windowParam = urlParams.get('window')
if (windowParam) {
return windowParam
}
// Try document title
const windowTitle = document.title
const windowMatch = windowTitle.match(/#(\d+)/)
if (windowMatch) {
return windowMatch[1]
}
// Try window name
if (window.name && window.name.includes('#')) {
const nameMatch = window.name.match(/#(\d+)/)
if (nameMatch) {
return nameMatch[1]
}
}
// Fallback: generate based on window creation time
return Math.floor(Date.now() / 1000) % 100
}
const windowNumber = getWindowNumber()
// Add theme preference monitoring for UI changes
const [theme, setTheme] = usePreference('app.theme.mode')
const [language] = usePreference('app.language')
const [zoomFactor] = usePreference('app.zoom_factor')
// Apply theme-based styling
const isDarkTheme = theme === 'ThemeMode.dark'
const headerBg = isDarkTheme ? '#141414' : '#fff'
const borderColor = isDarkTheme ? '#303030' : '#f0f0f0'
const textColor = isDarkTheme ? '#fff' : '#000'
// Apply zoom factor
const zoomValue = typeof zoomFactor === 'number' ? zoomFactor : 1.0
return (
<Layout style={{ height: '100vh' }}>
<Header style={{ background: '#fff', borderBottom: '1px solid #f0f0f0', padding: '0 24px' }}>
<Layout style={{ height: '100vh', transform: `scale(${zoomValue})`, transformOrigin: 'top left' }}>
<Header
style={{ background: headerBg, borderBottom: `1px solid ${borderColor}`, padding: '0 24px', color: textColor }}>
<HeaderContent>
<Space align="center">
<img src={AppLogo} alt="Logo" style={{ width: 28, height: 28, borderRadius: 6 }} />
<Title level={4} style={{ margin: 0, color: 'var(--color-primary)' }}>
Test Window #{windowNumber}
<Title level={4} style={{ margin: 0, color: textColor }}>
Test Window #{windowNumber} {isDarkTheme ? '🌙' : '☀️'}
</Title>
</Space>
<Space>
<FlaskConical size={20} color="var(--color-primary)" />
<Text type="secondary">Cross-Window Sync Testing</Text>
<FlaskConical size={20} color={isDarkTheme ? '#fff' : 'var(--color-primary)'} />
<Text style={{ color: textColor }}>
Cross-Window Sync Testing | {language || 'en-US'} | Zoom: {Math.round(zoomValue * 100)}%
</Text>
</Space>
</HeaderContent>
</Header>
<Content style={{ padding: '24px', overflow: 'auto' }}>
<Content style={{ padding: '24px', overflow: 'auto', backgroundColor: isDarkTheme ? '#000' : '#f5f5f5' }}>
<Container>
<Row gutter={[24, 24]}>
{/* Introduction Card */}
<Col span={24}>
<Card>
<Card style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Space align="center">
<TestTube size={24} color="var(--color-primary)" />
<Title level={3} style={{ margin: 0 }}>
<Title level={3} style={{ margin: 0, color: textColor }}>
PreferenceService ( #{windowNumber})
</Title>
</Space>
<Text type="secondary">
<Text style={{ color: isDarkTheme ? '#d9d9d9' : 'rgba(0, 0, 0, 0.45)' }}>
PreferenceService usePreference hooks
</Text>
<Text type="secondary">使</Text>
<Text type="secondary" style={{ color: 'var(--color-primary)', fontWeight: 'bold' }}>
<Text style={{ color: isDarkTheme ? '#d9d9d9' : 'rgba(0, 0, 0, 0.45)' }}>
使
</Text>
<Text style={{ color: 'var(--color-primary)', fontWeight: 'bold' }}>
📋
</Text>
</Space>
@ -68,11 +113,12 @@ const TestApp: React.FC = () => {
<Card
title={
<Space>
<Database size={18} />
<span>PreferenceService </span>
<Database size={18} color={isDarkTheme ? '#fff' : '#000'} />
<span style={{ color: textColor }}>PreferenceService </span>
</Space>
}
size="small">
size="small"
style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
<PreferenceServiceTests />
</Card>
</Col>
@ -82,11 +128,12 @@ const TestApp: React.FC = () => {
<Card
title={
<Space>
<Settings size={18} />
<span>usePreference Hook </span>
<Settings size={18} color={isDarkTheme ? '#fff' : '#000'} />
<span style={{ color: textColor }}>usePreference Hook </span>
</Space>
}
size="small">
size="small"
style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
<PreferenceBasicTests />
</Card>
</Col>
@ -96,11 +143,12 @@ const TestApp: React.FC = () => {
<Card
title={
<Space>
<Settings size={18} />
<span>Hook </span>
<Settings size={18} color={isDarkTheme ? '#fff' : '#000'} />
<span style={{ color: textColor }}>Hook </span>
</Space>
}
size="small">
size="small"
style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
<PreferenceHookTests />
</Card>
</Col>
@ -110,11 +158,12 @@ const TestApp: React.FC = () => {
<Card
title={
<Space>
<Database size={18} />
<span>usePreferences </span>
<Database size={18} color={isDarkTheme ? '#fff' : '#000'} />
<span style={{ color: textColor }}>usePreferences </span>
</Space>
}
size="small">
size="small"
style={{ backgroundColor: isDarkTheme ? '#1f1f1f' : '#fff', borderColor: borderColor }}>
<PreferenceMultipleTests />
</Card>
</Col>
@ -123,14 +172,28 @@ const TestApp: React.FC = () => {
<Divider />
<Row justify="center">
<Button
type="primary"
onClick={() => {
logger.info('Closing test window')
window.close()
}}>
</Button>
<Space>
<Button
icon={isDarkTheme ? '☀️' : '🌙'}
onClick={async () => {
await setTheme(isDarkTheme ? 'ThemeMode.light' : 'ThemeMode.dark')
}}
style={{
backgroundColor: isDarkTheme ? '#434343' : '#f0f0f0',
borderColor: borderColor,
color: textColor
}}>
{isDarkTheme ? '切换到亮色主题' : '切换到暗色主题'}
</Button>
<Button
type="primary"
onClick={() => {
logger.info('Closing test window')
window.close()
}}>
</Button>
</Space>
</Row>
</Container>
</Content>

View File

@ -1,6 +1,6 @@
import { usePreference } from '@renderer/data/hooks/usePreference'
import type { PreferenceKeyType } from '@shared/data/types'
import { Button, Input, message, Select, Space, Switch, Typography } from 'antd'
import { Button, Input, message, Select, Slider, Space, Switch, Typography } from 'antd'
import React, { useState } from 'react'
import styled from 'styled-components'
@ -19,6 +19,10 @@ const PreferenceBasicTests: React.FC = () => {
const [inputValue, setInputValue] = useState<string>('')
// Add theme monitoring for visual changes
const [currentTheme] = usePreference('app.theme.mode')
const isDarkTheme = currentTheme === 'ThemeMode.dark'
const handleSetValue = async () => {
try {
let parsedValue: any = inputValue
@ -47,15 +51,33 @@ const PreferenceBasicTests: React.FC = () => {
}
const testCases = [
{ key: 'app.theme.mode', label: 'App Theme Mode', sampleValue: 'ThemeMode.dark' },
{ key: 'app.language', label: 'App Language', sampleValue: 'zh-CN' },
{ key: 'app.spell_check.enabled', label: 'Spell Check', sampleValue: 'true' },
{ key: 'app.zoom_factor', label: 'Zoom Factor', sampleValue: '1.2' },
{ key: 'app.tray.enabled', label: 'Tray Enabled', sampleValue: 'true' }
{ key: 'app.theme.mode', label: 'App Theme Mode', sampleValue: 'ThemeMode.dark', type: 'enum' },
{ key: 'app.language', label: 'App Language', sampleValue: 'zh-CN', type: 'enum' },
{ key: 'app.spell_check.enabled', label: 'Spell Check', sampleValue: 'true', type: 'boolean' },
{ key: 'app.zoom_factor', label: 'Zoom Factor', sampleValue: '1.2', type: 'number', min: 0.5, max: 2.0, step: 0.1 },
{ key: 'app.tray.enabled', label: 'Tray Enabled', sampleValue: 'true', type: 'boolean' },
{
key: 'chat.message.font_size',
label: 'Message Font Size',
sampleValue: '14',
type: 'number',
min: 8,
max: 72,
step: 1
},
{
key: 'feature.selection.action_window_opacity',
label: 'Selection Window Opacity',
sampleValue: '95',
type: 'number',
min: 10,
max: 100,
step: 5
}
]
return (
<TestContainer>
<TestContainer isDark={isDarkTheme}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
{/* Key Selection */}
<div>
@ -74,7 +96,7 @@ const PreferenceBasicTests: React.FC = () => {
</div>
{/* Current Value Display */}
<CurrentValueContainer>
<CurrentValueContainer isDark={isDarkTheme}>
<Text strong>:</Text>
<ValueDisplay>
{value !== undefined ? (
@ -109,12 +131,14 @@ const PreferenceBasicTests: React.FC = () => {
<div>
<Text strong>:</Text>
<Space wrap style={{ marginTop: 8 }}>
{/* Theme Toggle */}
{/* Theme Toggle with Visual Feedback */}
{selectedKey === 'app.theme.mode' && (
<Button
size="small"
type={isDarkTheme ? 'default' : 'primary'}
icon={isDarkTheme ? '🌙' : '☀️'}
onClick={() => setValue(value === 'ThemeMode.dark' ? 'ThemeMode.light' : 'ThemeMode.dark')}>
({value === 'ThemeMode.dark' ? 'light' : 'dark'})
({value === 'ThemeMode.dark' ? '→ Light' : '→ Dark'})
</Button>
)}
@ -140,20 +164,134 @@ const PreferenceBasicTests: React.FC = () => {
</>
)}
{/* Zoom Factor */}
{selectedKey === 'app.zoom_factor' && (
<>
<Button size="small" onClick={() => setValue(0.8)}>
80%
</Button>
<Button size="small" onClick={() => setValue(1.0)}>
100%
</Button>
<Button size="small" onClick={() => setValue(1.2)}>
120%
</Button>
</>
)}
{/* Number Type Sliders */}
{(() => {
const currentTestCase = testCases.find((tc) => tc.key === selectedKey)
if (currentTestCase?.type === 'number') {
const numValue =
typeof value === 'number'
? value
: typeof value === 'string'
? parseFloat(value)
: currentTestCase.min || 0
const min = currentTestCase.min || 0
const max = currentTestCase.max || 100
const step = currentTestCase.step || 1
const getDisplayValue = () => {
if (selectedKey === 'app.zoom_factor') {
return `${Math.round(numValue * 100)}%`
} else if (selectedKey === 'feature.selection.action_window_opacity') {
return `${Math.round(numValue)}%`
} else {
return numValue.toString()
}
}
const getMarks = () => {
if (selectedKey === 'app.zoom_factor') {
return {
0.5: '50%',
0.8: '80%',
1.0: '100%',
1.2: '120%',
1.5: '150%',
2.0: '200%'
}
} else if (selectedKey === 'chat.message.font_size') {
return {
8: '8px',
12: '12px',
14: '14px',
16: '16px',
18: '18px',
24: '24px',
36: '36px',
72: '72px'
}
} else if (selectedKey === 'feature.selection.action_window_opacity') {
return {
10: '10%',
30: '30%',
50: '50%',
70: '70%',
90: '90%',
100: '100%'
}
}
return {}
}
return (
<div style={{ width: '100%', marginTop: 8 }}>
<Space direction="vertical" style={{ width: '100%' }}>
<Text>
{currentTestCase.label}: <strong>{getDisplayValue()}</strong>
</Text>
<Slider
min={min}
max={max}
step={step}
value={numValue}
onChange={(val) => setValue(val)}
marks={getMarks()}
tooltip={{
formatter: (val) => {
if (selectedKey === 'app.zoom_factor') {
return `${Math.round((val || 0) * 100)}%`
} else if (selectedKey === 'feature.selection.action_window_opacity') {
return `${Math.round(val || 0)}%`
}
return val?.toString() || '0'
}
}}
style={{ width: '100%', marginBottom: 8 }}
/>
{selectedKey === 'app.zoom_factor' && (
<Space>
<Button size="small" onClick={() => setValue(0.8)}>
80%
</Button>
<Button size="small" onClick={() => setValue(1.0)}>
100%
</Button>
<Button size="small" onClick={() => setValue(1.2)}>
120%
</Button>
</Space>
)}
{selectedKey === 'chat.message.font_size' && (
<Space>
<Button size="small" onClick={() => setValue(12)}>
Small
</Button>
<Button size="small" onClick={() => setValue(14)}>
Normal
</Button>
<Button size="small" onClick={() => setValue(16)}>
Large
</Button>
</Space>
)}
{selectedKey === 'feature.selection.action_window_opacity' && (
<Space>
<Button size="small" onClick={() => setValue(50)}>
50%
</Button>
<Button size="small" onClick={() => setValue(80)}>
80%
</Button>
<Button size="small" onClick={() => setValue(100)}>
100%
</Button>
</Space>
)}
</Space>
</div>
)
}
return null
})()}
{/* Sample Values */}
<Button
@ -181,15 +319,31 @@ const PreferenceBasicTests: React.FC = () => {
)
}
const TestContainer = styled.div`
const TestContainer = styled.div<{ isDark: boolean }>`
padding: 16px;
background: #fafafa;
background: ${(props) => (props.isDark ? '#262626' : '#fafafa')};
border-radius: 8px;
.ant-typography {
color: ${(props) => (props.isDark ? '#fff' : 'inherit')} !important;
}
.ant-select-selector {
background-color: ${(props) => (props.isDark ? '#1f1f1f' : '#fff')} !important;
border-color: ${(props) => (props.isDark ? '#434343' : '#d9d9d9')} !important;
color: ${(props) => (props.isDark ? '#fff' : '#000')} !important;
}
.ant-input {
background-color: ${(props) => (props.isDark ? '#1f1f1f' : '#fff')} !important;
border-color: ${(props) => (props.isDark ? '#434343' : '#d9d9d9')} !important;
color: ${(props) => (props.isDark ? '#fff' : '#000')} !important;
}
`
const CurrentValueContainer = styled.div`
const CurrentValueContainer = styled.div<{ isDark?: boolean }>`
padding: 12px;
background: #f0f0f0;
background: ${(props) => (props.isDark ? '#1f1f1f' : '#f0f0f0')};
border-radius: 6px;
border-left: 4px solid var(--color-primary);
`

View File

@ -1,4 +1,6 @@
import { usePreference, usePreferencePreload, usePreferenceService } from '@renderer/data/hooks/usePreference'
import { usePreference } from '@renderer/data/hooks/usePreference'
import { preferenceService } from '@renderer/data/PreferenceService'
import { loggerService } from '@renderer/services/LoggerService'
import type { PreferenceKeyType } from '@shared/data/types'
import { Button, Card, message, Space, Typography } from 'antd'
import React, { useState } from 'react'
@ -6,12 +8,13 @@ import styled from 'styled-components'
const { Text } = Typography
const logger = loggerService.withContext('PreferenceHookTests')
/**
* Advanced usePreference hook testing component
* Tests preloading, service access, and hook behavior
*/
const PreferenceHookTests: React.FC = () => {
const preferenceService = usePreferenceService()
const [subscriptionCount, setSubscriptionCount] = useState(0)
// Test multiple hooks with same key
@ -19,8 +22,13 @@ const PreferenceHookTests: React.FC = () => {
const [theme2] = usePreference('app.theme.mode')
const [language] = usePreference('app.language')
// Preload test
usePreferencePreload(['app.theme.mode', 'app.language', 'app.zoom_factor'])
// Manual preload implementation using useEffect
React.useEffect(() => {
const preloadKeys: PreferenceKeyType[] = ['app.theme.mode', 'app.language', 'app.zoom_factor']
preferenceService.preload(preloadKeys).catch((error) => {
logger.error('Failed to preload preferences:', error as Error)
})
}, [])
// Use useRef to track render count without causing re-renders
const renderCountRef = React.useRef(0)
@ -28,7 +36,7 @@ const PreferenceHookTests: React.FC = () => {
const testSubscriptions = () => {
// Test subscription behavior
const unsubscribe = preferenceService.subscribeToKey('app.theme.mode')(() => {
const unsubscribe = preferenceService.subscribeKeyChange('app.theme.mode')(() => {
setSubscriptionCount((prev) => prev + 1)
})
@ -53,7 +61,7 @@ const PreferenceHookTests: React.FC = () => {
}))
message.success(`预加载完成。缓存状态: ${cachedStates.filter((s) => s.isCached).length}/${keys.length}`)
console.log('Cache states:', cachedStates)
logger.debug('Cache states:', { cachedStates })
} catch (error) {
message.error(`预加载失败: ${(error as Error).message}`)
}
@ -65,7 +73,7 @@ const PreferenceHookTests: React.FC = () => {
const result = await preferenceService.getMultiple(keys)
message.success(`批量获取成功: ${Object.keys(result).length}`)
console.log('Batch get result:', result)
logger.debug('Batch get result:', { result })
// Test batch set
await preferenceService.setMultiple({

View File

@ -1,5 +1,5 @@
import { useMultiplePreferences } from '@renderer/data/hooks/usePreference'
import { Button, Card, Input, message, Select, Space, Table, Typography } from 'antd'
import { Button, Card, Input, message, Select, Slider, Space, Table, Typography } from 'antd'
import { ColumnType } from 'antd/es/table'
import React, { useState } from 'react'
import styled from 'styled-components'
@ -31,6 +31,11 @@ const PreferenceMultipleTests: React.FC = () => {
userName: 'app.user.name',
devMode: 'app.developer_mode.enabled'
},
numbers: {
zoomFactor: 'app.zoom_factor',
fontSize: 'chat.message.font_size',
opacity: 'feature.selection.action_window_opacity'
},
custom: {
key1: 'app.theme.mode',
key2: 'app.language',
@ -86,6 +91,12 @@ const PreferenceMultipleTests: React.FC = () => {
case 'app.spell_check.enabled':
sampleUpdates[localKey] = !values[localKey]
break
case 'chat.message.font_size':
sampleUpdates[localKey] = 14 + index * 2
break
case 'feature.selection.action_window_opacity':
sampleUpdates[localKey] = 80 + index * 10
break
default:
sampleUpdates[localKey] = `sample_value_${index}`
}
@ -184,10 +195,11 @@ const PreferenceMultipleTests: React.FC = () => {
<Card size="small" title="测试场景选择">
<Space align="center" wrap>
<Text>:</Text>
<Select value={scenario} onChange={setScenario} style={{ width: 200 }}>
<Select value={scenario} onChange={setScenario} style={{ width: 250 }}>
<Option value="basic"> (theme, language, zoom)</Option>
<Option value="ui">UI设置 (theme, zoom, spell)</Option>
<Option value="user"> (tray, userName, devMode)</Option>
<Option value="numbers">🎛 (zoom, fontSize, selection opacity)</Option>
<Option value="custom"> (4)</Option>
</Select>
<Text type="secondary">: {Object.keys(currentKeys).length} </Text>
@ -199,6 +211,86 @@ const PreferenceMultipleTests: React.FC = () => {
<Table columns={columns} dataSource={tableData} pagination={false} size="small" bordered />
</Card>
{/* Interactive Slider Controls for Numbers Scenario */}
{scenario === 'numbers' && (
<Card size="small" title="🎛️ 实时Slider联动控制" style={{ backgroundColor: '#f0f8ff' }}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
<Text type="secondary"></Text>
{/* Zoom Factor Slider */}
<div>
<Text strong>
: {Math.round((typeof (values as any).zoomFactor === 'number' ? (values as any).zoomFactor : 1.0) * 100)}%
</Text>
<Slider
min={0.5}
max={2.0}
step={0.1}
value={typeof (values as any).zoomFactor === 'number' ? (values as any).zoomFactor : 1.0}
onChange={(val) => updateValues({ zoomFactor: val } as any)}
marks={{
0.5: '50%',
1.0: '100%',
1.5: '150%',
2.0: '200%'
}}
tooltip={{ formatter: (val) => `${Math.round((val || 1) * 100)}%` }}
/>
</div>
{/* Font Size Slider */}
<div>
<Text strong>: {typeof (values as any).fontSize === 'number' ? (values as any).fontSize : 14}px</Text>
<Slider
min={8}
max={72}
step={1}
value={typeof (values as any).fontSize === 'number' ? (values as any).fontSize : 14}
onChange={(val) => updateValues({ fontSize: val } as any)}
marks={{
8: '8px',
12: '12px',
16: '16px',
24: '24px',
36: '36px',
72: '72px'
}}
tooltip={{ formatter: (val) => `${val}px` }}
/>
</div>
{/* Selection Window Opacity Slider */}
<div>
<Text strong>
: {Math.round(typeof (values as any).opacity === 'number' ? (values as any).opacity : 100)}%
</Text>
<Slider
min={10}
max={100}
step={5}
value={typeof (values as any).opacity === 'number' ? (values as any).opacity : 100}
onChange={(val) => updateValues({ opacity: val } as any)}
marks={{
10: '10%',
30: '30%',
50: '50%',
70: '70%',
90: '90%',
100: '100%'
}}
tooltip={{ formatter: (val) => `${Math.round(val || 100)}%` }}
/>
</div>
<div style={{ padding: '8px', backgroundColor: '#e6f7ff', borderRadius: '4px' }}>
<Text type="secondary" style={{ fontSize: '12px' }}>
💡
</Text>
</div>
</Space>
</Card>
)}
{/* Batch Update */}
<Card size="small" title="批量更新">
<Space direction="vertical" size="middle" style={{ width: '100%' }}>

View File

@ -1,3 +1,4 @@
import { usePreference } from '@renderer/data/hooks/usePreference'
import { preferenceService } from '@renderer/data/PreferenceService'
import type { PreferenceKeyType } from '@shared/data/types'
import { Button, Input, message, Space, Typography } from 'antd'
@ -16,6 +17,10 @@ const PreferenceServiceTests: React.FC = () => {
const [getResult, setGetResult] = useState<any>(null)
const [loading, setLoading] = useState(false)
// Theme monitoring for visual changes
const [currentTheme] = usePreference('app.theme.mode')
const isDarkTheme = currentTheme === 'ThemeMode.dark'
const handleGet = async () => {
try {
setLoading(true)
@ -98,17 +103,10 @@ const PreferenceServiceTests: React.FC = () => {
const handleGetAll = async () => {
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 result = await preferenceService.getMultiple(sampleKeys)
setGetResult(`Sample preferences (${Object.keys(result).length} keys):\n${JSON.stringify(result, null, 2)}`)
message.success('获取示例偏好设置成功')
// Use loadAll to get all preferences at once
const result = await preferenceService.loadAll()
setGetResult(`All preferences (${Object.keys(result).length} keys):\n${JSON.stringify(result, null, 2)}`)
message.success('获取所有偏好设置成功')
} catch (error) {
message.error(`获取偏好设置失败: ${(error as Error).message}`)
setGetResult(`Error: ${(error as Error).message}`)
@ -118,7 +116,7 @@ const PreferenceServiceTests: React.FC = () => {
}
return (
<TestContainer>
<TestContainer isDark={isDarkTheme}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
{/* Input Controls */}
<Space direction="vertical" size="small" style={{ width: '100%' }}>
@ -156,13 +154,13 @@ const PreferenceServiceTests: React.FC = () => {
Preload
</Button>
<Button onClick={handleGetAll} loading={loading}>
Get All
Load All
</Button>
</Space>
{/* Result Display */}
{getResult !== null && (
<ResultContainer>
<ResultContainer isDark={isDarkTheme}>
<Text strong>Result:</Text>
<ResultText>
{typeof getResult === 'object' ? JSON.stringify(getResult, null, 2) : String(getResult)}
@ -202,16 +200,26 @@ const PreferenceServiceTests: React.FC = () => {
)
}
const TestContainer = styled.div`
const TestContainer = styled.div<{ isDark: boolean }>`
padding: 16px;
background: #fafafa;
background: ${(props) => (props.isDark ? '#262626' : '#fafafa')};
border-radius: 8px;
.ant-typography {
color: ${(props) => (props.isDark ? '#fff' : 'inherit')} !important;
}
.ant-input {
background-color: ${(props) => (props.isDark ? '#1f1f1f' : '#fff')} !important;
border-color: ${(props) => (props.isDark ? '#434343' : '#d9d9d9')} !important;
color: ${(props) => (props.isDark ? '#fff' : '#000')} !important;
}
`
const ResultContainer = styled.div`
const ResultContainer = styled.div<{ isDark?: boolean }>`
margin-top: 16px;
padding: 12px;
background: #f0f0f0;
background: ${(props) => (props.isDark ? '#1f1f1f' : '#f0f0f0')};
border-radius: 6px;
border-left: 4px solid var(--color-primary);
`