Skip to content

Commit b316f6c

Browse files
committed
feat: 实现 ControlPanel 播放控制面板组件
- 添加上一步/下一步按钮(带快捷键标注 ←/→) - 添加播放/暂停按钮(带快捷键标注 空格) - 添加重置按钮(带快捷键标注 R) - 实现自定义播放速度控制器(0.5x-2x) - 实现可拖动进度条(绿色已播放/灰色未播放) - 添加步骤计数器显示 - 播放速度持久化到 IndexedDB - 添加播放控制状态一致性属性测试
1 parent 77f6dd9 commit b316f6c

File tree

7 files changed

+708
-56
lines changed

7 files changed

+708
-56
lines changed

.kiro/specs/min-stack-visualizer/tasks.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
- 数据验证和错误提示
105105
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5, 4.8, 4.9_
106106

107-
- [-] 10. UI 组件开发 - CodePanel
107+
- [x] 10. UI 组件开发 - CodePanel
108108
- [x] 10.1 实现 CodePanel 组件
109109
- 创建 components/CodePanel/CodePanel.tsx 和样式文件
110110
- 四种语言代码展示
@@ -139,23 +139,23 @@
139139
- 状态变更说明
140140
- _Requirements: 6.10, 6.11, 6.12, 6.13_
141141

142-
- [ ] 12. UI 组件开发 - ControlPanel
143-
- [ ] 12.1 实现 ControlPanel 组件
142+
- [x] 12. UI 组件开发 - ControlPanel
143+
- [x] 12.1 实现 ControlPanel 组件
144144
- 创建 components/ControlPanel/ControlPanel.tsx 和样式文件
145145
- 上一步按钮(← 快捷键)
146146
- 下一步按钮(→ 快捷键)
147147
- 播放/暂停按钮(空格快捷键)
148148
- 重置按钮(R 快捷键)
149149
- _Requirements: 7.1, 7.2, 7.3, 7.4, 7.5, 7.6, 7.7, 7.8, 7.9_
150-
- [ ] 12.2 实现播放速度控制
150+
- [x] 12.2 实现播放速度控制
151151
- 自定义速度控制器(非原生组件)
152152
- 默认 1x 速度
153153
- _Requirements: 7.10_
154-
- [ ] 12.3 实现进度条
154+
- [x] 12.3 实现进度条
155155
- 进度条显示(绿色已播放,灰色未播放)
156156
- 支持拖动跳转
157157
- _Requirements: 7.13, 7.14, 7.15_
158-
- [ ] 12.4 编写属性测试:播放控制状态一致性
158+
- [x] 12.4 编写属性测试:播放控制状态一致性
159159
- **Property 7: 播放控制状态一致性**
160160
- **Validates: Requirements 7.3, 7.5, 7.9, 7.15**
161161

src/App.css

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -100,34 +100,3 @@ html, body, #root {
100100
display: flex;
101101
flex-direction: column;
102102
}
103-
104-
.control-panel {
105-
display: flex;
106-
justify-content: center;
107-
gap: 12px;
108-
padding: 12px;
109-
background: #161b22;
110-
border-radius: 8px;
111-
border: 1px solid #30363d;
112-
}
113-
114-
.control-panel button {
115-
padding: 8px 16px;
116-
background: #21262d;
117-
border: 1px solid #30363d;
118-
border-radius: 6px;
119-
color: #c9d1d9;
120-
cursor: pointer;
121-
font-size: 14px;
122-
transition: all 0.2s ease;
123-
}
124-
125-
.control-panel button:hover:not(:disabled) {
126-
background: #30363d;
127-
border-color: #8b949e;
128-
}
129-
130-
.control-panel button:disabled {
131-
opacity: 0.5;
132-
cursor: not-allowed;
133-
}

src/App.tsx

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,15 @@ import { AlgorithmModal } from './components/AlgorithmModal';
44
import { InputPanel } from './components/InputPanel';
55
import { CodePanel } from './components/CodePanel/CodePanel';
66
import { Canvas } from './components/Canvas';
7+
import { ControlPanel } from './components/ControlPanel';
78
import type { Operation, Language, AlgorithmStep } from './types';
89
import { generateSteps } from './services/stepGenerator';
9-
import { getLanguagePreference, saveLanguagePreference } from './services/storageService';
10+
import {
11+
getLanguagePreference,
12+
saveLanguagePreference,
13+
getPlaybackSpeed,
14+
savePlaybackSpeed
15+
} from './services/storageService';
1016
import './App.css';
1117

1218
function App() {
@@ -17,12 +23,16 @@ function App() {
1723
const [language, setLanguage] = useState<Language>('python');
1824
const [isPlaying, setIsPlaying] = useState(false);
1925
const [isAnimating, setIsAnimating] = useState(false);
26+
const [playbackSpeed, setPlaybackSpeed] = useState(1);
2027

21-
// 加载用户语言偏好
28+
// 加载用户偏好
2229
useEffect(() => {
2330
getLanguagePreference().then((lang) => {
2431
if (lang) setLanguage(lang);
2532
});
33+
getPlaybackSpeed().then((speed) => {
34+
if (speed) setPlaybackSpeed(speed);
35+
});
2636
}, []);
2737

2838
// 处理语言切换
@@ -79,10 +89,22 @@ function App() {
7989
setIsPlaying(false);
8090
}, []);
8191

92+
// 处理播放速度变化
93+
const handleSpeedChange = useCallback((speed: number) => {
94+
setPlaybackSpeed(speed);
95+
savePlaybackSpeed(speed);
96+
}, []);
97+
98+
// 处理进度条跳转
99+
const handleSeek = useCallback((step: number) => {
100+
setCurrentStep(Math.max(0, Math.min(steps.length - 1, step)));
101+
}, [steps.length]);
102+
82103
// 自动播放
83104
useEffect(() => {
84105
if (!isPlaying || steps.length === 0) return;
85106

107+
const interval = 1000 / playbackSpeed;
86108
const timer = setInterval(() => {
87109
setCurrentStep((prev) => {
88110
if (prev >= steps.length - 1) {
@@ -91,10 +113,10 @@ function App() {
91113
}
92114
return prev + 1;
93115
});
94-
}, 1000);
116+
}, interval);
95117

96118
return () => clearInterval(timer);
97-
}, [isPlaying, steps.length]);
119+
}, [isPlaying, steps.length, playbackSpeed]);
98120

99121
// 键盘快捷键
100122
useEffect(() => {
@@ -151,22 +173,20 @@ function App() {
151173
</div>
152174
</div>
153175

154-
{/* 简单的播放控制 */}
176+
{/* 播放控制面板 */}
155177
{steps.length > 0 && (
156-
<div className="control-panel">
157-
<button onClick={handlePrevStep} disabled={currentStep === 0}>
158-
← 上一步
159-
</button>
160-
<button onClick={handlePlayPause}>
161-
{isPlaying ? '暂停' : '播放'} (空格)
162-
</button>
163-
<button onClick={handleNextStep} disabled={currentStep >= steps.length - 1}>
164-
下一步 →
165-
</button>
166-
<button onClick={handleReset}>
167-
重置 (R)
168-
</button>
169-
</div>
178+
<ControlPanel
179+
currentStep={currentStep}
180+
totalSteps={steps.length}
181+
isPlaying={isPlaying}
182+
playbackSpeed={playbackSpeed}
183+
onPrevStep={handlePrevStep}
184+
onNextStep={handleNextStep}
185+
onPlayPause={handlePlayPause}
186+
onReset={handleReset}
187+
onSpeedChange={handleSpeedChange}
188+
onSeek={handleSeek}
189+
/>
170190
)}
171191
</main>
172192
</div>

0 commit comments

Comments
 (0)