codex-lens 0.1.27 → 0.1.28
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/dist/aggregator.js +0 -1
- package/dist/public/assets/{main-d-2BqwRB.js → main-BRIK-RhC.js} +38 -36
- package/dist/public/index.html +1 -1
- package/package.json +1 -1
- package/src/aggregator.js +0 -1
- package/src/components/CodeViewer.jsx +2 -2
- package/src/components/ErrorBoundary.jsx +172 -0
- package/src/components/TerminalPanel.jsx +154 -163
- package/src/main.jsx +10 -2
- package/tsconfig.json +24 -0
package/dist/public/index.html
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Codex Lens</title>
|
|
7
|
-
<script type="module" crossorigin src="./assets/main-
|
|
7
|
+
<script type="module" crossorigin src="./assets/main-BRIK-RhC.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="./assets/main-DNXrKVO-.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
package/package.json
CHANGED
package/src/aggregator.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useMemo, useRef } from 'react';
|
|
2
2
|
import CodeMirror from '@uiw/react-codemirror';
|
|
3
|
-
import { EditorView, Decoration, ViewPlugin
|
|
3
|
+
import { EditorView, Decoration, ViewPlugin } from '@codemirror/view';
|
|
4
4
|
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
|
5
5
|
import { tags as t } from '@lezer/highlight';
|
|
6
6
|
import { RangeSetBuilder } from '@codemirror/state';
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { Component } from 'react';
|
|
2
|
+
|
|
3
|
+
class DefaultErrorFallback extends Component {
|
|
4
|
+
render() {
|
|
5
|
+
const { error, onReload, onReset } = this.props;
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div style={{
|
|
9
|
+
height: '100vh',
|
|
10
|
+
display: 'flex',
|
|
11
|
+
flexDirection: 'column',
|
|
12
|
+
alignItems: 'center',
|
|
13
|
+
justifyContent: 'center',
|
|
14
|
+
background: '#1e1e1e',
|
|
15
|
+
color: '#e0e0e0',
|
|
16
|
+
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
|
|
17
|
+
padding: '20px',
|
|
18
|
+
}}>
|
|
19
|
+
<div style={{
|
|
20
|
+
maxWidth: '600px',
|
|
21
|
+
padding: '32px',
|
|
22
|
+
background: '#252526',
|
|
23
|
+
borderRadius: '8px',
|
|
24
|
+
border: '1px solid #3c3c3c',
|
|
25
|
+
boxShadow: '0 4px 20px rgba(0, 0, 0, 0.4)',
|
|
26
|
+
textAlign: 'center',
|
|
27
|
+
}}>
|
|
28
|
+
<div style={{
|
|
29
|
+
fontSize: '48px',
|
|
30
|
+
marginBottom: '16px',
|
|
31
|
+
}}>
|
|
32
|
+
<span style={{ color: '#f85149' }}>!</span>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<h1 style={{
|
|
36
|
+
fontSize: '24px',
|
|
37
|
+
fontWeight: 600,
|
|
38
|
+
marginBottom: '8px',
|
|
39
|
+
color: '#ffffff',
|
|
40
|
+
}}>
|
|
41
|
+
应用程序出错
|
|
42
|
+
</h1>
|
|
43
|
+
|
|
44
|
+
<p style={{
|
|
45
|
+
fontSize: '14px',
|
|
46
|
+
color: '#858585',
|
|
47
|
+
marginBottom: '24px',
|
|
48
|
+
}}>
|
|
49
|
+
抱歉,应用程序遇到了一个错误。请尝试刷新页面或重置应用状态。
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
{error && (
|
|
53
|
+
<details style={{
|
|
54
|
+
marginBottom: '24px',
|
|
55
|
+
padding: '12px',
|
|
56
|
+
background: '#1e1e1e',
|
|
57
|
+
borderRadius: '4px',
|
|
58
|
+
textAlign: 'left',
|
|
59
|
+
}}>
|
|
60
|
+
<summary style={{
|
|
61
|
+
cursor: 'pointer',
|
|
62
|
+
fontSize: '13px',
|
|
63
|
+
color: '#e0e0e0',
|
|
64
|
+
fontWeight: 500,
|
|
65
|
+
}}>
|
|
66
|
+
错误详情
|
|
67
|
+
</summary>
|
|
68
|
+
<pre style={{
|
|
69
|
+
marginTop: '12px',
|
|
70
|
+
padding: '12px',
|
|
71
|
+
background: '#0a0a0a',
|
|
72
|
+
borderRadius: '4px',
|
|
73
|
+
fontSize: '12px',
|
|
74
|
+
color: '#f48771',
|
|
75
|
+
overflow: 'auto',
|
|
76
|
+
maxHeight: '200px',
|
|
77
|
+
fontFamily: '"SF Mono", Consolas, monospace',
|
|
78
|
+
}}>
|
|
79
|
+
{error.toString()}
|
|
80
|
+
{error.stack && `\n\n${error.stack}`}
|
|
81
|
+
</pre>
|
|
82
|
+
</details>
|
|
83
|
+
)}
|
|
84
|
+
|
|
85
|
+
<div style={{
|
|
86
|
+
display: 'flex',
|
|
87
|
+
gap: '12px',
|
|
88
|
+
justifyContent: 'center',
|
|
89
|
+
}}>
|
|
90
|
+
<button
|
|
91
|
+
onClick={onReset}
|
|
92
|
+
style={{
|
|
93
|
+
padding: '10px 20px',
|
|
94
|
+
background: '#3c3c3c',
|
|
95
|
+
border: '1px solid #4c4c4c',
|
|
96
|
+
borderRadius: '4px',
|
|
97
|
+
color: '#cccccc',
|
|
98
|
+
fontSize: '14px',
|
|
99
|
+
cursor: 'pointer',
|
|
100
|
+
}}
|
|
101
|
+
>
|
|
102
|
+
重置应用
|
|
103
|
+
</button>
|
|
104
|
+
<button
|
|
105
|
+
onClick={onReload}
|
|
106
|
+
style={{
|
|
107
|
+
padding: '10px 20px',
|
|
108
|
+
background: '#0078d4',
|
|
109
|
+
border: 'none',
|
|
110
|
+
borderRadius: '4px',
|
|
111
|
+
color: '#ffffff',
|
|
112
|
+
fontSize: '14px',
|
|
113
|
+
cursor: 'pointer',
|
|
114
|
+
fontWeight: 500,
|
|
115
|
+
}}
|
|
116
|
+
>
|
|
117
|
+
刷新页面
|
|
118
|
+
</button>
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export class ErrorBoundary extends Component {
|
|
127
|
+
constructor(props) {
|
|
128
|
+
super(props);
|
|
129
|
+
this.state = {
|
|
130
|
+
hasError: false,
|
|
131
|
+
error: null,
|
|
132
|
+
errorInfo: null,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static getDerivedStateFromError(error) {
|
|
137
|
+
return { hasError: true, error };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
componentDidCatch(error, errorInfo) {
|
|
141
|
+
this.setState({ errorInfo });
|
|
142
|
+
this.props.onError?.(error, errorInfo);
|
|
143
|
+
console.error('[ErrorBoundary]', error, errorInfo);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
handleReload = () => {
|
|
147
|
+
this.setState({ hasError: false, error: null, errorInfo: null });
|
|
148
|
+
window.location.reload();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
handleReset = () => {
|
|
152
|
+
this.setState({ hasError: false, error: null, errorInfo: null });
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
render() {
|
|
156
|
+
if (this.state.hasError) {
|
|
157
|
+
if (this.props.fallback) {
|
|
158
|
+
return this.props.fallback;
|
|
159
|
+
}
|
|
160
|
+
return (
|
|
161
|
+
<DefaultErrorFallback
|
|
162
|
+
error={this.state.error}
|
|
163
|
+
errorInfo={this.state.errorInfo}
|
|
164
|
+
onReload={this.handleReload}
|
|
165
|
+
onReset={this.handleReset}
|
|
166
|
+
/>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return this.props.children;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useEffect, useRef, useCallback } from 'react';
|
|
2
2
|
import { Terminal } from '@xterm/xterm';
|
|
3
3
|
import { FitAddon } from '@xterm/addon-fit';
|
|
4
4
|
import { WebLinksAddon } from '@xterm/addon-web-links';
|
|
@@ -15,42 +15,66 @@ const VIRTUAL_KEYS = [
|
|
|
15
15
|
{ label: 'Ctrl+C', seq: '\x03' },
|
|
16
16
|
];
|
|
17
17
|
|
|
18
|
-
export
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
componentWillUnmount() {
|
|
37
|
-
if (this._writeTimer) {
|
|
38
|
-
cancelAnimationFrame(this._writeTimer);
|
|
18
|
+
export function TerminalPanel() {
|
|
19
|
+
const containerRef = useRef(null);
|
|
20
|
+
const terminalRef = useRef(null);
|
|
21
|
+
const fitAddonRef = useRef(null);
|
|
22
|
+
const wsRef = useRef(null);
|
|
23
|
+
const resizeObserverRef = useRef(null);
|
|
24
|
+
const writeBufferRef = useRef('');
|
|
25
|
+
const writeTimerRef = useRef(null);
|
|
26
|
+
const resizeTimerRef = useRef(null);
|
|
27
|
+
|
|
28
|
+
const sendResize = useCallback(() => {
|
|
29
|
+
if (wsRef.current?.readyState === WebSocket.OPEN && terminalRef.current) {
|
|
30
|
+
wsRef.current.send(JSON.stringify({
|
|
31
|
+
type: 'resize',
|
|
32
|
+
cols: terminalRef.current.cols,
|
|
33
|
+
rows: terminalRef.current.rows,
|
|
34
|
+
}));
|
|
39
35
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
const flushWrite = useCallback(() => {
|
|
39
|
+
if (writeTimerRef.current) {
|
|
40
|
+
cancelAnimationFrame(writeTimerRef.current);
|
|
41
|
+
writeTimerRef.current = null;
|
|
43
42
|
}
|
|
44
|
-
if (
|
|
45
|
-
|
|
43
|
+
if (!writeBufferRef.current || !terminalRef.current) return;
|
|
44
|
+
|
|
45
|
+
const CHUNK_SIZE = 32768;
|
|
46
|
+
if (writeBufferRef.current.length <= CHUNK_SIZE) {
|
|
47
|
+
const buf = writeBufferRef.current;
|
|
48
|
+
writeBufferRef.current = '';
|
|
49
|
+
terminalRef.current.write(buf);
|
|
50
|
+
} else {
|
|
51
|
+
const chunk = writeBufferRef.current.slice(0, CHUNK_SIZE);
|
|
52
|
+
writeBufferRef.current = writeBufferRef.current.slice(CHUNK_SIZE);
|
|
53
|
+
terminalRef.current.write(chunk);
|
|
54
|
+
writeTimerRef.current = requestAnimationFrame(() => {
|
|
55
|
+
flushWrite();
|
|
56
|
+
});
|
|
46
57
|
}
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
}, []);
|
|
59
|
+
|
|
60
|
+
const throttledWrite = useCallback((data) => {
|
|
61
|
+
writeBufferRef.current += data;
|
|
62
|
+
if (!writeTimerRef.current) {
|
|
63
|
+
writeTimerRef.current = requestAnimationFrame(() => {
|
|
64
|
+
flushWrite();
|
|
65
|
+
});
|
|
49
66
|
}
|
|
50
|
-
}
|
|
67
|
+
}, [flushWrite]);
|
|
51
68
|
|
|
52
|
-
|
|
53
|
-
|
|
69
|
+
const handleVirtualKey = useCallback((seq) => {
|
|
70
|
+
if (wsRef.current?.readyState === WebSocket.OPEN) {
|
|
71
|
+
wsRef.current.send(JSON.stringify({ type: 'input', data: seq }));
|
|
72
|
+
}
|
|
73
|
+
terminalRef.current?.focus();
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
useEffect(() => {
|
|
77
|
+
const terminal = new Terminal({
|
|
54
78
|
cursorBlink: true,
|
|
55
79
|
cursorStyle: 'bar',
|
|
56
80
|
fontSize: 13,
|
|
@@ -65,52 +89,54 @@ export class TerminalPanel extends React.Component {
|
|
|
65
89
|
allowProposedApi: true,
|
|
66
90
|
});
|
|
67
91
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
92
|
+
const fitAddon = new FitAddon();
|
|
93
|
+
terminal.loadAddon(fitAddon);
|
|
94
|
+
terminal.loadAddon(new WebLinksAddon());
|
|
71
95
|
|
|
72
|
-
|
|
96
|
+
terminal.open(containerRef.current);
|
|
97
|
+
terminalRef.current = terminal;
|
|
98
|
+
fitAddonRef.current = fitAddon;
|
|
73
99
|
|
|
74
100
|
requestAnimationFrame(() => {
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
|
|
101
|
+
if (fitAddon) {
|
|
102
|
+
fitAddon.fit();
|
|
103
|
+
terminal.focus();
|
|
78
104
|
}
|
|
79
105
|
});
|
|
80
106
|
|
|
81
|
-
|
|
82
|
-
if (
|
|
83
|
-
|
|
107
|
+
terminal.onData((data) => {
|
|
108
|
+
if (wsRef.current && wsRef.current.readyState === WebSocket.OPEN) {
|
|
109
|
+
wsRef.current.send(JSON.stringify({ type: 'input', data }));
|
|
84
110
|
}
|
|
85
111
|
});
|
|
86
|
-
}
|
|
87
112
|
|
|
88
|
-
|
|
113
|
+
// WebSocket connection
|
|
89
114
|
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
90
115
|
const host = window.location.hostname;
|
|
91
116
|
const port = window.location.port === '5173' ? '5174' : window.location.port;
|
|
92
117
|
const wsUrl = `${protocol}//${host}:${port}/ws/terminal`;
|
|
93
118
|
|
|
94
|
-
|
|
119
|
+
const ws = new WebSocket(wsUrl);
|
|
120
|
+
wsRef.current = ws;
|
|
95
121
|
|
|
96
|
-
|
|
122
|
+
ws.onopen = () => {
|
|
97
123
|
console.log('[Terminal] Connected to PTY service');
|
|
98
|
-
|
|
124
|
+
sendResize();
|
|
99
125
|
};
|
|
100
126
|
|
|
101
|
-
|
|
127
|
+
ws.onmessage = (event) => {
|
|
102
128
|
try {
|
|
103
129
|
const msg = JSON.parse(event.data);
|
|
104
130
|
if (msg.type === 'data') {
|
|
105
|
-
|
|
131
|
+
throttledWrite(msg.data);
|
|
106
132
|
} else if (msg.type === 'exit') {
|
|
107
|
-
|
|
108
|
-
if (
|
|
109
|
-
|
|
133
|
+
flushWrite();
|
|
134
|
+
if (terminal) {
|
|
135
|
+
terminal.write(`\r\n[Process exited with code ${msg.exitCode ?? '?'}]\r\n`);
|
|
110
136
|
}
|
|
111
137
|
} else if (msg.type === 'state') {
|
|
112
|
-
if (!msg.running &&
|
|
113
|
-
|
|
138
|
+
if (!msg.running && terminal) {
|
|
139
|
+
flushWrite();
|
|
114
140
|
}
|
|
115
141
|
}
|
|
116
142
|
} catch (e) {
|
|
@@ -118,132 +144,97 @@ export class TerminalPanel extends React.Component {
|
|
|
118
144
|
}
|
|
119
145
|
};
|
|
120
146
|
|
|
121
|
-
|
|
147
|
+
ws.onclose = () => {
|
|
122
148
|
console.log('[Terminal] Disconnected, reconnecting in 3s...');
|
|
123
149
|
setTimeout(() => {
|
|
124
|
-
if (
|
|
125
|
-
|
|
150
|
+
if (containerRef.current) {
|
|
151
|
+
// Reconnect handled by re-mounting effect
|
|
126
152
|
}
|
|
127
153
|
}, 3000);
|
|
128
154
|
};
|
|
129
155
|
|
|
130
|
-
|
|
156
|
+
ws.onerror = (error) => {
|
|
131
157
|
console.error('[Terminal] WebSocket error:', error);
|
|
132
158
|
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
sendResize() {
|
|
136
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.terminal) {
|
|
137
|
-
this.ws.send(JSON.stringify({
|
|
138
|
-
type: 'resize',
|
|
139
|
-
cols: this.terminal.cols,
|
|
140
|
-
rows: this.terminal.rows,
|
|
141
|
-
}));
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
159
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
this._resizeTimer = setTimeout(() => {
|
|
151
|
-
if (this.fitAddon && this.containerRef.current) {
|
|
160
|
+
// ResizeObserver setup
|
|
161
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
162
|
+
if (resizeTimerRef.current) clearTimeout(resizeTimerRef.current);
|
|
163
|
+
resizeTimerRef.current = setTimeout(() => {
|
|
164
|
+
if (fitAddonRef.current && containerRef.current) {
|
|
152
165
|
try {
|
|
153
|
-
|
|
154
|
-
|
|
166
|
+
fitAddonRef.current.fit();
|
|
167
|
+
sendResize();
|
|
155
168
|
} catch {}
|
|
156
169
|
}
|
|
157
170
|
}, 150);
|
|
158
171
|
});
|
|
159
172
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
_throttledWrite(data) {
|
|
164
|
-
this._writeBuffer += data;
|
|
165
|
-
if (!this._writeTimer) {
|
|
166
|
-
this._writeTimer = requestAnimationFrame(() => {
|
|
167
|
-
this._flushWrite();
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
_flushWrite() {
|
|
173
|
-
if (this._writeTimer) {
|
|
174
|
-
cancelAnimationFrame(this._writeTimer);
|
|
175
|
-
this._writeTimer = null;
|
|
176
|
-
}
|
|
177
|
-
if (!this._writeBuffer || !this.terminal) return;
|
|
178
|
-
|
|
179
|
-
const CHUNK_SIZE = 32768;
|
|
180
|
-
if (this._writeBuffer.length <= CHUNK_SIZE) {
|
|
181
|
-
const buf = this._writeBuffer;
|
|
182
|
-
this._writeBuffer = '';
|
|
183
|
-
this.terminal.write(buf);
|
|
184
|
-
} else {
|
|
185
|
-
const chunk = this._writeBuffer.slice(0, CHUNK_SIZE);
|
|
186
|
-
this._writeBuffer = this._writeBuffer.slice(CHUNK_SIZE);
|
|
187
|
-
this.terminal.write(chunk);
|
|
188
|
-
this._writeTimer = requestAnimationFrame(() => {
|
|
189
|
-
this._flushWrite();
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
}
|
|
173
|
+
resizeObserver.observe(containerRef.current);
|
|
174
|
+
resizeObserverRef.current = resizeObserver;
|
|
193
175
|
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
176
|
+
// Cleanup on unmount
|
|
177
|
+
return () => {
|
|
178
|
+
if (writeTimerRef.current) {
|
|
179
|
+
cancelAnimationFrame(writeTimerRef.current);
|
|
180
|
+
}
|
|
181
|
+
if (ws) {
|
|
182
|
+
ws.close();
|
|
183
|
+
wsRef.current = null;
|
|
184
|
+
}
|
|
185
|
+
if (resizeObserver) {
|
|
186
|
+
resizeObserver.disconnect();
|
|
187
|
+
}
|
|
188
|
+
if (terminal) {
|
|
189
|
+
terminal.dispose();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
}, [sendResize, throttledWrite, flushWrite]);
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<div style={{
|
|
196
|
+
height: '100%',
|
|
197
|
+
display: 'flex',
|
|
198
|
+
flexDirection: 'column',
|
|
199
|
+
background: '#0a0a0a',
|
|
200
|
+
}}>
|
|
201
|
+
<div
|
|
202
|
+
ref={containerRef}
|
|
203
|
+
style={{
|
|
204
|
+
flex: 1,
|
|
205
|
+
overflow: 'hidden',
|
|
206
|
+
padding: '4px 8px',
|
|
207
|
+
}}
|
|
208
|
+
/>
|
|
203
209
|
<div style={{
|
|
204
|
-
height: '100%',
|
|
205
210
|
display: 'flex',
|
|
206
|
-
|
|
207
|
-
|
|
211
|
+
gap: '4px',
|
|
212
|
+
padding: '8px',
|
|
213
|
+
background: '#111',
|
|
214
|
+
borderTop: '1px solid #222',
|
|
215
|
+
flexWrap: 'wrap',
|
|
208
216
|
}}>
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
style={{
|
|
230
|
-
padding: '8px 12px',
|
|
231
|
-
border: '1px solid #333',
|
|
232
|
-
borderRadius: '4px',
|
|
233
|
-
background: '#1a1a1a',
|
|
234
|
-
color: '#ccc',
|
|
235
|
-
fontSize: '13px',
|
|
236
|
-
fontFamily: 'Menlo, Monaco, monospace',
|
|
237
|
-
cursor: 'pointer',
|
|
238
|
-
minWidth: '44px',
|
|
239
|
-
minHeight: '44px',
|
|
240
|
-
}}
|
|
241
|
-
>
|
|
242
|
-
{key.label}
|
|
243
|
-
</button>
|
|
244
|
-
))}
|
|
245
|
-
</div>
|
|
217
|
+
{VIRTUAL_KEYS.map((key) => (
|
|
218
|
+
<button
|
|
219
|
+
key={key.label}
|
|
220
|
+
onClick={() => handleVirtualKey(key.seq)}
|
|
221
|
+
style={{
|
|
222
|
+
padding: '8px 12px',
|
|
223
|
+
border: '1px solid #333',
|
|
224
|
+
borderRadius: '4px',
|
|
225
|
+
background: '#1a1a1a',
|
|
226
|
+
color: '#ccc',
|
|
227
|
+
fontSize: '13px',
|
|
228
|
+
fontFamily: 'Menlo, Monaco, monospace',
|
|
229
|
+
cursor: 'pointer',
|
|
230
|
+
minWidth: '44px',
|
|
231
|
+
minHeight: '44px',
|
|
232
|
+
}}
|
|
233
|
+
>
|
|
234
|
+
{key.label}
|
|
235
|
+
</button>
|
|
236
|
+
))}
|
|
246
237
|
</div>
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
238
|
+
</div>
|
|
239
|
+
);
|
|
240
|
+
}
|
package/src/main.jsx
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
1
|
import { createRoot } from 'react-dom/client';
|
|
3
2
|
import { App } from './components/App.jsx';
|
|
3
|
+
import { ErrorBoundary } from './components/ErrorBoundary.jsx';
|
|
4
4
|
import './global.css';
|
|
5
5
|
|
|
6
6
|
const container = document.getElementById('root');
|
|
7
7
|
const root = createRoot(container);
|
|
8
8
|
|
|
9
|
-
root.render(
|
|
9
|
+
root.render(
|
|
10
|
+
<ErrorBoundary
|
|
11
|
+
onError={(error, errorInfo) => {
|
|
12
|
+
console.error('[Global Error]', error, errorInfo);
|
|
13
|
+
}}
|
|
14
|
+
>
|
|
15
|
+
<App />
|
|
16
|
+
</ErrorBoundary>
|
|
17
|
+
);
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"jsx": "react-jsx",
|
|
14
|
+
"strict": true,
|
|
15
|
+
"noUnusedLocals": true,
|
|
16
|
+
"noUnusedParameters": true,
|
|
17
|
+
"noFallthroughCasesInSwitch": true,
|
|
18
|
+
"allowSyntheticDefaultImports": true,
|
|
19
|
+
"esModuleInterop": true,
|
|
20
|
+
"forceConsistentCasingInFileNames": true
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"],
|
|
23
|
+
"exclude": ["node_modules", "dist"]
|
|
24
|
+
}
|