codex-lens 0.1.12 → 0.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build.js +6 -2
- package/dist/aggregator.js +0 -100
- package/dist/public/assets/{main-DA0U-HuE.css → main-0MulWSMb.css} +1 -1
- package/dist/public/assets/{main-4W6-SFMa.js → main-gWFaVAuJ.js} +15 -15
- package/dist/public/index.html +2 -2
- package/package.json +1 -1
- package/src/aggregator.js +0 -116
- package/src/components/App.jsx +79 -193
- package/src/global.css +5 -37
- package/dist/snapshot-manager.js +0 -208
- package/src/snapshot-manager.js +0 -230
package/dist/public/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Viewer</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/main-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="./assets/main-
|
|
7
|
+
<script type="module" crossorigin src="./assets/main-gWFaVAuJ.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="./assets/main-0MulWSMb.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/package.json
CHANGED
package/src/aggregator.js
CHANGED
|
@@ -7,7 +7,6 @@ import { createProxyServer } from './proxy.js';
|
|
|
7
7
|
import { FileWatcher, scanDirectory } from './watcher.js';
|
|
8
8
|
import { createLogger } from './lib/logger.js';
|
|
9
9
|
import { spawnCodex, writeToPty, resizePty, killPty, onPtyData, onPtyExit, getPtyState, getOutputBuffer } from './pty-manager.js';
|
|
10
|
-
import { SnapshotManager } from './snapshot-manager.js';
|
|
11
10
|
import path from 'path';
|
|
12
11
|
import { fileURLToPath } from 'url';
|
|
13
12
|
import { readFileSync, existsSync } from 'fs';
|
|
@@ -57,9 +56,6 @@ class Aggregator {
|
|
|
57
56
|
this.proxyServer = null;
|
|
58
57
|
this.fileWatcher = null;
|
|
59
58
|
this.ptyProcess = null;
|
|
60
|
-
this.snapshotManager = new SnapshotManager();
|
|
61
|
-
this.currentTaskId = null;
|
|
62
|
-
this.taskStatus = 'idle';
|
|
63
59
|
}
|
|
64
60
|
|
|
65
61
|
async start(proxyPort) {
|
|
@@ -251,121 +247,9 @@ class Aggregator {
|
|
|
251
247
|
}
|
|
252
248
|
}));
|
|
253
249
|
}
|
|
254
|
-
} else if (data.type === 'start_task') {
|
|
255
|
-
this.handleStartTask(ws);
|
|
256
|
-
} else if (data.type === 'rollback_task') {
|
|
257
|
-
this.handleRollbackTask(ws);
|
|
258
|
-
} else if (data.type === 'complete_task') {
|
|
259
|
-
this.handleCompleteTask(ws);
|
|
260
|
-
} else if (data.type === 'get_task_status') {
|
|
261
|
-
ws.send(JSON.stringify({
|
|
262
|
-
type: 'task_status',
|
|
263
|
-
data: {
|
|
264
|
-
status: this.taskStatus,
|
|
265
|
-
taskId: this.currentTaskId
|
|
266
|
-
}
|
|
267
|
-
}));
|
|
268
250
|
}
|
|
269
251
|
}
|
|
270
252
|
|
|
271
|
-
async handleStartTask(ws) {
|
|
272
|
-
if (this.taskStatus === 'running') {
|
|
273
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Task already running' }));
|
|
274
|
-
return;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
this.currentTaskId = Date.now().toString();
|
|
278
|
-
this.taskStatus = 'running';
|
|
279
|
-
|
|
280
|
-
const result = await this.snapshotManager.createSnapshot(this.projectRoot, this.currentTaskId);
|
|
281
|
-
|
|
282
|
-
if (result.success) {
|
|
283
|
-
logger.info(`Task started: ${this.currentTaskId}, ${result.filesCount} files snapshotted`);
|
|
284
|
-
this.broadcast({
|
|
285
|
-
type: 'task_status',
|
|
286
|
-
data: {
|
|
287
|
-
status: this.taskStatus,
|
|
288
|
-
taskId: this.currentTaskId,
|
|
289
|
-
filesCount: result.filesCount
|
|
290
|
-
}
|
|
291
|
-
});
|
|
292
|
-
ws.send(JSON.stringify({
|
|
293
|
-
type: 'task_started',
|
|
294
|
-
data: {
|
|
295
|
-
taskId: this.currentTaskId,
|
|
296
|
-
filesCount: result.filesCount
|
|
297
|
-
}
|
|
298
|
-
}));
|
|
299
|
-
} else {
|
|
300
|
-
this.taskStatus = 'idle';
|
|
301
|
-
this.currentTaskId = null;
|
|
302
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Failed to create snapshot: ' + result.error }));
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
async handleRollbackTask(ws) {
|
|
307
|
-
if (!this.currentTaskId || this.taskStatus !== 'running') {
|
|
308
|
-
ws.send(JSON.stringify({ type: 'error', message: 'No active task to rollback' }));
|
|
309
|
-
return;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
const taskId = this.currentTaskId;
|
|
313
|
-
const result = await this.snapshotManager.restoreSnapshot(taskId);
|
|
314
|
-
|
|
315
|
-
if (result.success) {
|
|
316
|
-
await this.snapshotManager.deleteSnapshot(taskId);
|
|
317
|
-
logger.info(`Task rolled back: ${taskId}, ${result.restoredCount} files restored`);
|
|
318
|
-
|
|
319
|
-
this.taskStatus = 'idle';
|
|
320
|
-
this.currentTaskId = null;
|
|
321
|
-
|
|
322
|
-
this.broadcast({
|
|
323
|
-
type: 'task_status',
|
|
324
|
-
data: {
|
|
325
|
-
status: this.taskStatus,
|
|
326
|
-
taskId: null
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
ws.send(JSON.stringify({
|
|
331
|
-
type: 'task_rolled_back',
|
|
332
|
-
data: {
|
|
333
|
-
restoredCount: result.restoredCount
|
|
334
|
-
}
|
|
335
|
-
}));
|
|
336
|
-
} else {
|
|
337
|
-
ws.send(JSON.stringify({ type: 'error', message: 'Failed to rollback: ' + result.error }));
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
async handleCompleteTask(ws) {
|
|
342
|
-
if (!this.currentTaskId || this.taskStatus !== 'running') {
|
|
343
|
-
ws.send(JSON.stringify({ type: 'error', message: 'No active task to complete' }));
|
|
344
|
-
return;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const taskId = this.currentTaskId;
|
|
348
|
-
await this.snapshotManager.deleteSnapshot(taskId);
|
|
349
|
-
|
|
350
|
-
logger.info(`Task completed: ${taskId}`);
|
|
351
|
-
|
|
352
|
-
this.taskStatus = 'idle';
|
|
353
|
-
this.currentTaskId = null;
|
|
354
|
-
|
|
355
|
-
this.broadcast({
|
|
356
|
-
type: 'task_status',
|
|
357
|
-
data: {
|
|
358
|
-
status: this.taskStatus,
|
|
359
|
-
taskId: null
|
|
360
|
-
}
|
|
361
|
-
});
|
|
362
|
-
|
|
363
|
-
ws.send(JSON.stringify({
|
|
364
|
-
type: 'task_completed',
|
|
365
|
-
data: {}
|
|
366
|
-
}));
|
|
367
|
-
}
|
|
368
|
-
|
|
369
253
|
broadcast(event) {
|
|
370
254
|
const message = JSON.stringify(event);
|
|
371
255
|
for (const client of this.clients) {
|
package/src/components/App.jsx
CHANGED
|
@@ -5,12 +5,8 @@ export function App() {
|
|
|
5
5
|
const [files, setFiles] = useState([]);
|
|
6
6
|
const [tabs, setTabs] = useState([]);
|
|
7
7
|
const [activeTabId, setActiveTabId] = useState(null);
|
|
8
|
-
const [recentChanges, setRecentChanges] = useState([]);
|
|
9
8
|
const [wsStatus, setWsStatus] = useState('disconnected');
|
|
10
9
|
const [contextMenu, setContextMenu] = useState(null);
|
|
11
|
-
const [taskStatus, setTaskStatus] = useState('idle');
|
|
12
|
-
const [taskId, setTaskId] = useState(null);
|
|
13
|
-
const [showRollbackConfirm, setShowRollbackConfirm] = useState(false);
|
|
14
10
|
const [version, setVersion] = useState(null);
|
|
15
11
|
const [latestVersion, setLatestVersion] = useState(null);
|
|
16
12
|
const [hasUpdate, setHasUpdate] = useState(false);
|
|
@@ -52,24 +48,6 @@ export function App() {
|
|
|
52
48
|
}
|
|
53
49
|
|
|
54
50
|
function handleKeyDown(e) {
|
|
55
|
-
if (e.ctrlKey && e.shiftKey && e.key === 'Enter') {
|
|
56
|
-
e.preventDefault();
|
|
57
|
-
handleStartTask();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
if (taskStatus === 'running' && e.ctrlKey && e.key === 'Enter') {
|
|
62
|
-
e.preventDefault();
|
|
63
|
-
handleCompleteTask();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (taskStatus === 'running' && e.ctrlKey && e.key === 'z') {
|
|
68
|
-
e.preventDefault();
|
|
69
|
-
setShowRollbackConfirm(true);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
51
|
if (!activeTabId) return;
|
|
74
52
|
|
|
75
53
|
if (e.ctrlKey && e.key === 'w') {
|
|
@@ -94,13 +72,12 @@ export function App() {
|
|
|
94
72
|
const ws = new WebSocket(wsUrl);
|
|
95
73
|
|
|
96
74
|
ws.onopen = () => {
|
|
97
|
-
console.log('Connected to Codex
|
|
75
|
+
console.log('Connected to Codex Lens');
|
|
98
76
|
setWsStatus('connected');
|
|
99
|
-
ws.send(JSON.stringify({ type: 'get_task_status' }));
|
|
100
77
|
};
|
|
101
78
|
|
|
102
79
|
ws.onclose = () => {
|
|
103
|
-
console.log('Disconnected from Codex
|
|
80
|
+
console.log('Disconnected from Codex Lens');
|
|
104
81
|
setWsStatus('disconnected');
|
|
105
82
|
setTimeout(connectWebSocket, 3000);
|
|
106
83
|
};
|
|
@@ -132,28 +109,6 @@ export function App() {
|
|
|
132
109
|
case 'file_content':
|
|
133
110
|
openFileInTab(msg.data);
|
|
134
111
|
break;
|
|
135
|
-
case 'task_status':
|
|
136
|
-
setTaskStatus(msg.data.status);
|
|
137
|
-
setTaskId(msg.data.taskId);
|
|
138
|
-
break;
|
|
139
|
-
case 'task_started':
|
|
140
|
-
setTaskStatus('running');
|
|
141
|
-
setTaskId(msg.data.taskId);
|
|
142
|
-
alert(`任务已创建,快照包含 ${msg.data.filesCount} 个文件`);
|
|
143
|
-
break;
|
|
144
|
-
case 'task_rolled_back':
|
|
145
|
-
setTaskStatus('idle');
|
|
146
|
-
setTaskId(null);
|
|
147
|
-
setTabs([]);
|
|
148
|
-
setActiveTabId(null);
|
|
149
|
-
alert(`已撤回 ${msg.data.restoredCount} 个文件`);
|
|
150
|
-
setShowRollbackConfirm(false);
|
|
151
|
-
break;
|
|
152
|
-
case 'task_completed':
|
|
153
|
-
setTaskStatus('idle');
|
|
154
|
-
setTaskId(null);
|
|
155
|
-
alert('任务已完成,修改已保留');
|
|
156
|
-
break;
|
|
157
112
|
case 'connected':
|
|
158
113
|
console.log('Server confirmed connection');
|
|
159
114
|
break;
|
|
@@ -162,27 +117,6 @@ export function App() {
|
|
|
162
117
|
}
|
|
163
118
|
}
|
|
164
119
|
|
|
165
|
-
function handleStartTask() {
|
|
166
|
-
if (taskStatus === 'running') return;
|
|
167
|
-
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
168
|
-
wsRef.current.send(JSON.stringify({ type: 'start_task' }));
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function handleRollbackTask() {
|
|
173
|
-
if (taskStatus !== 'running') return;
|
|
174
|
-
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
175
|
-
wsRef.current.send(JSON.stringify({ type: 'rollback_task' }));
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function handleCompleteTask() {
|
|
180
|
-
if (taskStatus !== 'running') return;
|
|
181
|
-
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
182
|
-
wsRef.current.send(JSON.stringify({ type: 'complete_task' }));
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
120
|
function openFileInTab(data) {
|
|
187
121
|
const fileName = data.path.split(/[/\\]/).pop();
|
|
188
122
|
|
|
@@ -209,22 +143,28 @@ export function App() {
|
|
|
209
143
|
|
|
210
144
|
function handleFileChange(data) {
|
|
211
145
|
const fileName = data.path.split(/[/\\]/).pop();
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
: t
|
|
219
|
-
));
|
|
220
|
-
if (activeTabId === existingTab.id) {
|
|
146
|
+
|
|
147
|
+
setTabs(prevTabs => {
|
|
148
|
+
const existingTab = prevTabs.find(t => t.path === data.path);
|
|
149
|
+
|
|
150
|
+
if (existingTab) {
|
|
151
|
+
const updatedTab = { ...existingTab, content: data.newContent, diff: data.diff, isDiff: true };
|
|
221
152
|
setActiveTabId(existingTab.id);
|
|
153
|
+
return prevTabs.map(t =>
|
|
154
|
+
t.path === data.path ? updatedTab : t
|
|
155
|
+
);
|
|
156
|
+
} else {
|
|
157
|
+
const newTab = {
|
|
158
|
+
id: Date.now().toString(),
|
|
159
|
+
path: data.path,
|
|
160
|
+
name: fileName,
|
|
161
|
+
content: data.newContent,
|
|
162
|
+
diff: data.diff,
|
|
163
|
+
isDiff: true
|
|
164
|
+
};
|
|
165
|
+
setActiveTabId(newTab.id);
|
|
166
|
+
return [...prevTabs, newTab];
|
|
222
167
|
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
setRecentChanges(prev => {
|
|
226
|
-
const filtered = prev.filter(c => c.path !== data.path);
|
|
227
|
-
return [{ path: data.path, time: Date.now(), diff: data.diff }, ...filtered].slice(0, 10);
|
|
228
168
|
});
|
|
229
169
|
}
|
|
230
170
|
|
|
@@ -240,28 +180,19 @@ export function App() {
|
|
|
240
180
|
}
|
|
241
181
|
|
|
242
182
|
function closeTab(tabId) {
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
setActiveTabId(null);
|
|
249
|
-
} else if (activeTabId === tabId) {
|
|
250
|
-
if (tabIndex >= newTabs.length) {
|
|
251
|
-
setActiveTabId(newTabs[newTabs.length - 1].id);
|
|
252
|
-
} else {
|
|
253
|
-
setActiveTabId(newTabs[tabIndex].id);
|
|
183
|
+
setTabs(prev => {
|
|
184
|
+
const newTabs = prev.filter(t => t.id !== tabId);
|
|
185
|
+
if (activeTabId === tabId) {
|
|
186
|
+
const newActiveId = newTabs.length > 0 ? newTabs[newTabs.length - 1].id : null;
|
|
187
|
+
setActiveTabId(newActiveId);
|
|
254
188
|
}
|
|
255
|
-
|
|
256
|
-
}
|
|
257
|
-
setTabs(newTabs);
|
|
258
|
-
}
|
|
189
|
+
return newTabs;
|
|
190
|
+
});
|
|
259
191
|
}
|
|
260
192
|
|
|
261
|
-
function closeOtherTabs(
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
setActiveTabId(keepTabId);
|
|
193
|
+
function closeOtherTabs(tabId) {
|
|
194
|
+
setTabs(prev => prev.filter(t => t.id === tabId));
|
|
195
|
+
setActiveTabId(tabId);
|
|
265
196
|
}
|
|
266
197
|
|
|
267
198
|
function closeAllTabs() {
|
|
@@ -269,27 +200,31 @@ export function App() {
|
|
|
269
200
|
setActiveTabId(null);
|
|
270
201
|
}
|
|
271
202
|
|
|
203
|
+
function clearAllDiff() {
|
|
204
|
+
setTabs(prevTabs => prevTabs.map(tab => ({ ...tab, diff: null, isDiff: false })));
|
|
205
|
+
}
|
|
206
|
+
|
|
272
207
|
function switchToNextTab() {
|
|
273
|
-
if (tabs.length <= 1) return;
|
|
274
208
|
const currentIndex = tabs.findIndex(t => t.id === activeTabId);
|
|
275
|
-
|
|
276
|
-
|
|
209
|
+
if (currentIndex < tabs.length - 1) {
|
|
210
|
+
setActiveTabId(tabs[currentIndex + 1].id);
|
|
211
|
+
} else if (tabs.length > 0) {
|
|
212
|
+
setActiveTabId(tabs[0].id);
|
|
213
|
+
}
|
|
277
214
|
}
|
|
278
215
|
|
|
279
216
|
function switchToPrevTab() {
|
|
280
|
-
if (tabs.length <= 1) return;
|
|
281
217
|
const currentIndex = tabs.findIndex(t => t.id === activeTabId);
|
|
282
|
-
|
|
283
|
-
|
|
218
|
+
if (currentIndex > 0) {
|
|
219
|
+
setActiveTabId(tabs[currentIndex - 1].id);
|
|
220
|
+
} else if (tabs.length > 0) {
|
|
221
|
+
setActiveTabId(tabs[tabs.length - 1].id);
|
|
222
|
+
}
|
|
284
223
|
}
|
|
285
224
|
|
|
286
225
|
function handleContextMenu(e, tabId) {
|
|
287
226
|
e.preventDefault();
|
|
288
|
-
setContextMenu({
|
|
289
|
-
x: e.clientX,
|
|
290
|
-
y: e.clientY,
|
|
291
|
-
tabId
|
|
292
|
-
});
|
|
227
|
+
setContextMenu({ x: e.clientX, y: e.clientY, tabId });
|
|
293
228
|
}
|
|
294
229
|
|
|
295
230
|
const activeTab = tabs.find(t => t.id === activeTabId);
|
|
@@ -298,17 +233,15 @@ export function App() {
|
|
|
298
233
|
<div className="app-container">
|
|
299
234
|
<LeftPanel
|
|
300
235
|
files={files}
|
|
301
|
-
recentChanges={recentChanges}
|
|
302
236
|
activeFile={activeTab?.path || null}
|
|
303
237
|
onFileClick={handleFileClick}
|
|
304
238
|
/>
|
|
305
239
|
<div className="panel middle-panel">
|
|
306
|
-
<
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
/>
|
|
240
|
+
<div className="task-bar">
|
|
241
|
+
<button className="task-btn task-btn-clear" onClick={clearAllDiff} title="清空所有 diff 显示">
|
|
242
|
+
清空 diff
|
|
243
|
+
</button>
|
|
244
|
+
</div>
|
|
312
245
|
<TabBar
|
|
313
246
|
tabs={tabs}
|
|
314
247
|
activeTabId={activeTabId}
|
|
@@ -334,24 +267,6 @@ export function App() {
|
|
|
334
267
|
<pre className="code-content">{activeTab.content}</pre>
|
|
335
268
|
)}
|
|
336
269
|
</div>
|
|
337
|
-
{showRollbackConfirm && (
|
|
338
|
-
<div className="modal-overlay" onClick={() => setShowRollbackConfirm(false)}>
|
|
339
|
-
<div className="modal-content" onClick={e => e.stopPropagation()}>
|
|
340
|
-
<div className="modal-title">确认撤回</div>
|
|
341
|
-
<div className="modal-body">
|
|
342
|
-
确定要撤回所有修改吗?此操作将把项目恢复到任务开始前的状态,且无法撤销。
|
|
343
|
-
</div>
|
|
344
|
-
<div className="modal-buttons">
|
|
345
|
-
<button className="modal-btn modal-btn-cancel" onClick={() => setShowRollbackConfirm(false)}>
|
|
346
|
-
取消
|
|
347
|
-
</button>
|
|
348
|
-
<button className="modal-btn modal-btn-danger" onClick={handleRollbackTask}>
|
|
349
|
-
确认撤回
|
|
350
|
-
</button>
|
|
351
|
-
</div>
|
|
352
|
-
</div>
|
|
353
|
-
</div>
|
|
354
|
-
)}
|
|
355
270
|
</div>
|
|
356
271
|
{contextMenu && (
|
|
357
272
|
<ContextMenu
|
|
@@ -393,32 +308,6 @@ export function App() {
|
|
|
393
308
|
);
|
|
394
309
|
}
|
|
395
310
|
|
|
396
|
-
function TaskBar({ taskStatus, onStartTask, onRollback, onComplete }) {
|
|
397
|
-
return (
|
|
398
|
-
<div className="task-bar">
|
|
399
|
-
<div className={`task-status ${taskStatus}`}>
|
|
400
|
-
{taskStatus === 'idle' ? '空闲' : '任务进行中'}
|
|
401
|
-
</div>
|
|
402
|
-
<div className="task-buttons">
|
|
403
|
-
{taskStatus === 'idle' ? (
|
|
404
|
-
<button className="task-btn task-btn-start" disabled title="快照功能暂时禁用">
|
|
405
|
-
开始任务
|
|
406
|
-
</button>
|
|
407
|
-
) : (
|
|
408
|
-
<>
|
|
409
|
-
<button className="task-btn task-btn-rollback" onClick={onRollback} title="Ctrl+Z">
|
|
410
|
-
撤回
|
|
411
|
-
</button>
|
|
412
|
-
<button className="task-btn task-btn-complete" onClick={onComplete} title="Ctrl+Enter">
|
|
413
|
-
完成
|
|
414
|
-
</button>
|
|
415
|
-
</>
|
|
416
|
-
)}
|
|
417
|
-
</div>
|
|
418
|
-
</div>
|
|
419
|
-
);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
311
|
function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
423
312
|
return (
|
|
424
313
|
<div className="tab-bar">
|
|
@@ -447,15 +336,15 @@ function TabBar({ tabs, activeTabId, onTabClick, onTabClose, onContextMenu }) {
|
|
|
447
336
|
|
|
448
337
|
function ContextMenu({ x, y, onClose, onCloseTab, onCloseOtherTabs, onCloseAllTabs }) {
|
|
449
338
|
return (
|
|
450
|
-
<div className="context-menu" style={{ left: x, top: y }} onClick={e => e.stopPropagation()}>
|
|
451
|
-
<div className="context-menu-item" onClick={onCloseTab}
|
|
452
|
-
<div className="context-menu-item" onClick={onCloseOtherTabs}
|
|
453
|
-
<div className="context-menu-item" onClick={onCloseAllTabs}
|
|
339
|
+
<div className="context-menu" style={{ left: x, top: y }} onClick={(e) => e.stopPropagation()}>
|
|
340
|
+
<div className="context-menu-item" onClick={onCloseTab}>关闭</div>
|
|
341
|
+
<div className="context-menu-item" onClick={onCloseOtherTabs}>关闭其他</div>
|
|
342
|
+
<div className="context-menu-item" onClick={onCloseAllTabs}>关闭所有</div>
|
|
454
343
|
</div>
|
|
455
344
|
);
|
|
456
345
|
}
|
|
457
346
|
|
|
458
|
-
function LeftPanel({ files,
|
|
347
|
+
function LeftPanel({ files, activeFile, onFileClick }) {
|
|
459
348
|
const [expandedDirs, setExpandedDirs] = useState({});
|
|
460
349
|
const [contextMenu, setContextMenu] = useState(null);
|
|
461
350
|
|
|
@@ -532,27 +421,10 @@ function LeftPanel({ files, recentChanges, activeFile, onFileClick }) {
|
|
|
532
421
|
文件浏览器
|
|
533
422
|
</div>
|
|
534
423
|
<div className="panel-content">
|
|
535
|
-
{recentChanges.length > 0 && (
|
|
536
|
-
<div className="section">
|
|
537
|
-
<div className="section-title">最近修改</div>
|
|
538
|
-
{recentChanges.map((change, i) => (
|
|
539
|
-
<div
|
|
540
|
-
key={i}
|
|
541
|
-
className={`file-item ${activeFile === change.path ? 'active' : ''}`}
|
|
542
|
-
onDoubleClick={() => onFileClick(change.path)}
|
|
543
|
-
>
|
|
544
|
-
<span className="file-icon">📝</span>
|
|
545
|
-
<span className="file-name">{change.path.split(/[/\\]/).pop()}</span>
|
|
546
|
-
</div>
|
|
547
|
-
))}
|
|
548
|
-
</div>
|
|
549
|
-
)}
|
|
550
424
|
<div className="section">
|
|
551
425
|
<div className="section-title">项目文件</div>
|
|
552
426
|
{files.length === 0 ? (
|
|
553
|
-
<div className="empty-state">
|
|
554
|
-
{recentChanges.length === 0 ? '等待文件变化...' : '暂无其他文件'}
|
|
555
|
-
</div>
|
|
427
|
+
<div className="empty-state">等待文件变化...</div>
|
|
556
428
|
) : (
|
|
557
429
|
renderFileTree(files)
|
|
558
430
|
)}
|
|
@@ -577,11 +449,25 @@ function LeftPanel({ files, recentChanges, activeFile, onFileClick }) {
|
|
|
577
449
|
}
|
|
578
450
|
|
|
579
451
|
function getFileIcon(type) {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
452
|
+
const icons = {
|
|
453
|
+
'.js': '📜',
|
|
454
|
+
'.jsx': '⚛️',
|
|
455
|
+
'.ts': '📘',
|
|
456
|
+
'.tsx': '⚛️',
|
|
457
|
+
'.py': '🐍',
|
|
458
|
+
'.json': '📋',
|
|
459
|
+
'.css': '🎨',
|
|
460
|
+
'.html': '🌐',
|
|
461
|
+
'.md': '📝',
|
|
462
|
+
'.txt': '📄',
|
|
463
|
+
'.yml': '⚙️',
|
|
464
|
+
'.yaml': '⚙️',
|
|
465
|
+
'.toml': '⚙️',
|
|
466
|
+
'.ini': '⚙️',
|
|
467
|
+
'.env': '🔐',
|
|
468
|
+
'.gitignore': '🙈',
|
|
469
|
+
'.dockerignore': '🐳',
|
|
470
|
+
'default': '📄'
|
|
471
|
+
};
|
|
472
|
+
return icons[type] || icons['default'];
|
|
585
473
|
}
|
|
586
|
-
|
|
587
|
-
export default App;
|
package/src/global.css
CHANGED
|
@@ -553,46 +553,14 @@ body {
|
|
|
553
553
|
transform: translateY(0);
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
-
.task-btn-
|
|
557
|
-
background: var(--accent-color);
|
|
558
|
-
color: white;
|
|
559
|
-
box-shadow: var(--shadow-sm);
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
.task-btn-start:hover {
|
|
563
|
-
background: var(--accent-hover);
|
|
564
|
-
box-shadow: var(--shadow-glow);
|
|
565
|
-
}
|
|
566
|
-
|
|
567
|
-
.task-btn-start:disabled {
|
|
556
|
+
.task-btn-clear {
|
|
568
557
|
background: var(--bg-tertiary);
|
|
569
|
-
color: var(--text-
|
|
570
|
-
|
|
571
|
-
box-shadow: none;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
.task-btn-start:disabled:hover {
|
|
575
|
-
transform: none;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
.task-btn-rollback {
|
|
579
|
-
background: var(--danger-color);
|
|
580
|
-
color: white;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
.task-btn-rollback:hover {
|
|
584
|
-
background: var(--danger-hover);
|
|
585
|
-
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.3);
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
.task-btn-complete {
|
|
589
|
-
background: var(--success-color);
|
|
590
|
-
color: white;
|
|
558
|
+
color: var(--text-primary);
|
|
559
|
+
border: 1px solid var(--border-color);
|
|
591
560
|
}
|
|
592
561
|
|
|
593
|
-
.task-btn-
|
|
594
|
-
background: var(--
|
|
595
|
-
box-shadow: 0 4px 12px rgba(34, 197, 94, 0.3);
|
|
562
|
+
.task-btn-clear:hover {
|
|
563
|
+
background: var(--border-color);
|
|
596
564
|
}
|
|
597
565
|
|
|
598
566
|
.modal-overlay {
|