@arcblock/terminal 3.1.17 → 3.1.18
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/README.md +1 -3
- package/lib/Player.js +134 -152
- package/lib/Terminal.js +24 -19
- package/lib/styles.js +122 -161
- package/package.json +8 -8
- package/src/Player.jsx +30 -73
- package/src/Terminal.jsx +21 -14
- package/src/styles.js +132 -186
package/lib/styles.js
CHANGED
|
@@ -1,134 +1,9 @@
|
|
|
1
1
|
import e from "@emotion/styled";
|
|
2
|
-
const
|
|
2
|
+
const r = e.div({
|
|
3
3
|
// Base styles for .terminal-player
|
|
4
|
-
display: "
|
|
4
|
+
display: "block",
|
|
5
5
|
position: "relative",
|
|
6
|
-
|
|
7
|
-
// Terminal styles
|
|
8
|
-
"& .terminal": { display: "inline-block" },
|
|
9
|
-
"& .terminal .xterm .xterm-viewport": { overflowY: "hidden !important" },
|
|
10
|
-
// Terminal frame
|
|
11
|
-
"& .terminal-frame": { position: "relative" },
|
|
12
|
-
// Reset styles for terminal divs
|
|
13
|
-
"& .terminal div": {
|
|
14
|
-
margin: 0,
|
|
15
|
-
padding: 0,
|
|
16
|
-
border: 0,
|
|
17
|
-
outline: 0,
|
|
18
|
-
fontWeight: "inherit",
|
|
19
|
-
fontStyle: "inherit",
|
|
20
|
-
fontSize: "100%",
|
|
21
|
-
verticalAlign: "baseline"
|
|
22
|
-
},
|
|
23
|
-
// Window theme
|
|
24
|
-
"&.terminal-window .terminal-frame": {
|
|
25
|
-
borderRadius: 6,
|
|
26
|
-
border: "1px solid #b3b3b3",
|
|
27
|
-
boxShadow: "0px 0px 18px #b3b3b3",
|
|
28
|
-
margin: 18,
|
|
29
|
-
overflow: "hidden",
|
|
30
|
-
"& .terminal-titlebar": {
|
|
31
|
-
background: "#e8e8e8",
|
|
32
|
-
borderBottom: "1px solid #b1aeb1",
|
|
33
|
-
borderTop: "1px solid #f3f1f3",
|
|
34
|
-
borderTopLeftRadius: 6,
|
|
35
|
-
borderTopRightRadius: 6,
|
|
36
|
-
color: "#3b4247",
|
|
37
|
-
fontSize: 14,
|
|
38
|
-
height: 22,
|
|
39
|
-
lineHeight: "22px",
|
|
40
|
-
position: "relative",
|
|
41
|
-
textAlign: "center",
|
|
42
|
-
width: "100%",
|
|
43
|
-
"& .buttons": { left: 8, lineHeight: "0px", position: "absolute", top: 3.5 },
|
|
44
|
-
"& .close-button": {
|
|
45
|
-
background: "#ff5c5c",
|
|
46
|
-
borderRadius: "50%",
|
|
47
|
-
border: "1px solid #e33e41",
|
|
48
|
-
display: "inline-block",
|
|
49
|
-
height: 12,
|
|
50
|
-
width: 12
|
|
51
|
-
},
|
|
52
|
-
"& .minimize-button": {
|
|
53
|
-
background: "#ffbd4c",
|
|
54
|
-
borderRadius: "50%",
|
|
55
|
-
border: "1px solid #e09e3e",
|
|
56
|
-
display: "inline-block",
|
|
57
|
-
height: 12,
|
|
58
|
-
marginLeft: 4,
|
|
59
|
-
width: 12
|
|
60
|
-
},
|
|
61
|
-
"& .maximize-button": {
|
|
62
|
-
background: "#00ca56",
|
|
63
|
-
borderRadius: "50%",
|
|
64
|
-
border: "1px solid #14ae46",
|
|
65
|
-
display: "inline-block",
|
|
66
|
-
height: 12,
|
|
67
|
-
marginLeft: 4,
|
|
68
|
-
width: 12
|
|
69
|
-
}
|
|
70
|
-
},
|
|
71
|
-
"& .terminal-body": { backgroundColor: "#1d1d1d", padding: 10 }
|
|
72
|
-
},
|
|
73
|
-
// Floating theme
|
|
74
|
-
"&.terminal-floating .terminal-frame": {
|
|
75
|
-
backgroundColor: "#1d1d1d",
|
|
76
|
-
borderRadius: 6,
|
|
77
|
-
boxShadow: "0px 0px 18px #b3b3b3",
|
|
78
|
-
margin: 18,
|
|
79
|
-
overflow: "hidden",
|
|
80
|
-
"& .terminal-titlebar": {
|
|
81
|
-
color: "white",
|
|
82
|
-
fontSize: 14,
|
|
83
|
-
height: 34,
|
|
84
|
-
lineHeight: "34px",
|
|
85
|
-
position: "relative",
|
|
86
|
-
textAlign: "center",
|
|
87
|
-
width: "100%",
|
|
88
|
-
"& .buttons": { left: 13, lineHeight: "0px", position: "absolute", top: 9 },
|
|
89
|
-
"& .close-button": { background: "#ff5c5c", borderRadius: "50%", display: "inline-block", height: 15, width: 15 },
|
|
90
|
-
"& .minimize-button": {
|
|
91
|
-
background: "#ffbd4c",
|
|
92
|
-
borderRadius: "50%",
|
|
93
|
-
display: "inline-block",
|
|
94
|
-
height: 15,
|
|
95
|
-
lineHeight: "10px",
|
|
96
|
-
marginLeft: 4,
|
|
97
|
-
width: 15
|
|
98
|
-
},
|
|
99
|
-
"& .maximize-button": {
|
|
100
|
-
background: "#00ca56",
|
|
101
|
-
borderRadius: "50%",
|
|
102
|
-
display: "inline-block",
|
|
103
|
-
height: 15,
|
|
104
|
-
lineHeight: "10px",
|
|
105
|
-
marginLeft: 4,
|
|
106
|
-
width: 15
|
|
107
|
-
}
|
|
108
|
-
},
|
|
109
|
-
"& .terminal-body": { padding: 20 }
|
|
110
|
-
},
|
|
111
|
-
// Solid theme
|
|
112
|
-
"&.terminal-solid .terminal-frame": {
|
|
113
|
-
backgroundColor: "#1d1d1d",
|
|
114
|
-
borderRadius: 6,
|
|
115
|
-
boxShadow: "0px 0px 18px #b3b3b3",
|
|
116
|
-
margin: 18,
|
|
117
|
-
overflow: "hidden",
|
|
118
|
-
"& .terminal-titlebar": {
|
|
119
|
-
color: "white",
|
|
120
|
-
fontSize: 14,
|
|
121
|
-
position: "relative",
|
|
122
|
-
textAlign: "center",
|
|
123
|
-
width: "100%",
|
|
124
|
-
"& .title": {
|
|
125
|
-
margin: "15px 15px 15px",
|
|
126
|
-
"&:empty": { display: "none" }
|
|
127
|
-
},
|
|
128
|
-
"& .buttons": { display: "none" }
|
|
129
|
-
},
|
|
130
|
-
"& .terminal-body": { padding: 20 }
|
|
131
|
-
},
|
|
6
|
+
overflow: "hidden",
|
|
132
7
|
// Player controls
|
|
133
8
|
"& .controller": {
|
|
134
9
|
background: "#45484d",
|
|
@@ -194,16 +69,21 @@ const t = e.div({
|
|
|
194
69
|
"& .start svg": {
|
|
195
70
|
cursor: "pointer",
|
|
196
71
|
fill: "#eaeaea",
|
|
197
|
-
|
|
72
|
+
width: 100,
|
|
73
|
+
height: 100,
|
|
198
74
|
left: "50%",
|
|
199
75
|
marginLeft: -65,
|
|
200
76
|
marginTop: -65,
|
|
201
77
|
position: "absolute",
|
|
202
78
|
top: "50%",
|
|
203
|
-
width: 130,
|
|
204
79
|
zIndex: 20,
|
|
205
80
|
filter: "drop-shadow(10px 10px 15px rgba(0, 0, 0, 0.4))",
|
|
206
|
-
WebkitFilter: "drop-shadow(10px 10px 15px rgba(0, 0, 0, 0.4))"
|
|
81
|
+
WebkitFilter: "drop-shadow(10px 10px 15px rgba(0, 0, 0, 0.4))",
|
|
82
|
+
opacity: 0.6,
|
|
83
|
+
"&:hover": {
|
|
84
|
+
opacity: 0.8,
|
|
85
|
+
transition: "all 0.3s ease"
|
|
86
|
+
}
|
|
207
87
|
},
|
|
208
88
|
"&.small .start svg": { height: 60, marginLeft: -30, marginTop: -30, width: 60 },
|
|
209
89
|
"&.framed .start svg": {
|
|
@@ -213,25 +93,31 @@ const t = e.div({
|
|
|
213
93
|
OTransform: "translate(0px, 8px)",
|
|
214
94
|
MsTransform: "translate(0px, 8px)"
|
|
215
95
|
},
|
|
216
|
-
"& .cover:hover + .start svg, & .start:hover svg": { fill: "white" },
|
|
217
96
|
"&.started .cover, &.started .start": { display: "none" },
|
|
218
97
|
"& .terminal-watermark": { zIndex: 99999 }
|
|
219
98
|
}), o = e.div({
|
|
220
|
-
//
|
|
99
|
+
// Base xterm styles
|
|
221
100
|
"& .xterm": {
|
|
222
|
-
|
|
101
|
+
cursor: "text",
|
|
223
102
|
position: "relative",
|
|
224
103
|
userSelect: "none",
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
104
|
+
msUserSelect: "none",
|
|
105
|
+
webkitUserSelect: "none"
|
|
106
|
+
},
|
|
107
|
+
"& .xterm.focus, & .xterm:focus": {
|
|
108
|
+
outline: "none"
|
|
109
|
+
},
|
|
110
|
+
"& .xterm .xterm-helpers": {
|
|
111
|
+
position: "absolute",
|
|
112
|
+
top: 0,
|
|
113
|
+
// The z-index of the helpers must be higher than the canvases in order for IMEs to appear on top
|
|
114
|
+
zIndex: 5
|
|
232
115
|
},
|
|
233
|
-
"& .xterm .xterm-helpers": { position: "absolute", top: 0, zIndex: 5 },
|
|
234
116
|
"& .xterm .xterm-helper-textarea": {
|
|
117
|
+
padding: 0,
|
|
118
|
+
border: 0,
|
|
119
|
+
margin: 0,
|
|
120
|
+
// Move textarea out of the screen to the far left, so that the cursor is not visible
|
|
235
121
|
position: "absolute",
|
|
236
122
|
opacity: 0,
|
|
237
123
|
left: "-9999em",
|
|
@@ -239,34 +125,45 @@ const t = e.div({
|
|
|
239
125
|
width: 0,
|
|
240
126
|
height: 0,
|
|
241
127
|
zIndex: -5,
|
|
128
|
+
// Prevent wrapping so the IME appears against the textarea at the correct position
|
|
242
129
|
whiteSpace: "nowrap",
|
|
243
130
|
overflow: "hidden",
|
|
244
131
|
resize: "none"
|
|
245
132
|
},
|
|
246
133
|
"& .xterm .composition-view": {
|
|
134
|
+
// TODO: Composition position got messed up somewhere
|
|
247
135
|
background: "#000",
|
|
248
|
-
color: "#
|
|
136
|
+
color: "#FFF",
|
|
249
137
|
display: "none",
|
|
250
138
|
position: "absolute",
|
|
251
139
|
whiteSpace: "nowrap",
|
|
252
|
-
zIndex: 1
|
|
253
|
-
|
|
140
|
+
zIndex: 1
|
|
141
|
+
},
|
|
142
|
+
"& .xterm .composition-view.active": {
|
|
143
|
+
display: "block"
|
|
144
|
+
},
|
|
145
|
+
// '& .xterm .xterm-viewport': {
|
|
146
|
+
// // On OS X this is required in order for the scroll bar to appear fully opaque
|
|
147
|
+
// backgroundColor: 'transparent',
|
|
148
|
+
// overflowY: 'scroll',
|
|
149
|
+
// cursor: 'default',
|
|
150
|
+
// position: 'absolute',
|
|
151
|
+
// right: 0,
|
|
152
|
+
// left: 0,
|
|
153
|
+
// top: 0,
|
|
154
|
+
// bottom: 0,
|
|
155
|
+
// },
|
|
156
|
+
"& .xterm .xterm-screen": {
|
|
157
|
+
position: "relative"
|
|
254
158
|
},
|
|
255
|
-
"& .xterm .xterm-
|
|
256
|
-
backgroundColor: "#000",
|
|
257
|
-
overflowY: "scroll",
|
|
258
|
-
cursor: "default",
|
|
159
|
+
"& .xterm .xterm-screen canvas": {
|
|
259
160
|
position: "absolute",
|
|
260
|
-
right: 0,
|
|
261
161
|
left: 0,
|
|
262
|
-
top: 0
|
|
263
|
-
bottom: 0
|
|
162
|
+
top: 0
|
|
264
163
|
},
|
|
265
|
-
"& .xterm .xterm-
|
|
266
|
-
|
|
267
|
-
"& canvas": { position: "absolute", left: 0, top: 0 }
|
|
164
|
+
"& .xterm .xterm-scroll-area": {
|
|
165
|
+
visibility: "hidden"
|
|
268
166
|
},
|
|
269
|
-
"& .xterm .xterm-scroll-area": { visibility: "hidden" },
|
|
270
167
|
"& .xterm-char-measure-element": {
|
|
271
168
|
display: "inline-block",
|
|
272
169
|
visibility: "hidden",
|
|
@@ -275,20 +172,84 @@ const t = e.div({
|
|
|
275
172
|
left: "-9999em",
|
|
276
173
|
lineHeight: "normal"
|
|
277
174
|
},
|
|
278
|
-
"& .xterm
|
|
175
|
+
"& .xterm.enable-mouse-events": {
|
|
176
|
+
// When mouse events are enabled (eg. tmux), revert to the standard pointer cursor
|
|
177
|
+
cursor: "default"
|
|
178
|
+
},
|
|
179
|
+
"& .xterm.xterm-cursor-pointer, & .xterm .xterm-cursor-pointer": {
|
|
180
|
+
cursor: "pointer"
|
|
181
|
+
},
|
|
182
|
+
"& .xterm.column-select.focus": {
|
|
183
|
+
// Column selection mode
|
|
184
|
+
cursor: "crosshair"
|
|
185
|
+
},
|
|
186
|
+
"& .xterm .xterm-accessibility:not(.debug), & .xterm .xterm-message": {
|
|
279
187
|
position: "absolute",
|
|
280
188
|
left: 0,
|
|
281
189
|
top: 0,
|
|
282
190
|
bottom: 0,
|
|
283
191
|
right: 0,
|
|
284
192
|
zIndex: 10,
|
|
193
|
+
color: "transparent",
|
|
194
|
+
pointerEvents: "none"
|
|
195
|
+
},
|
|
196
|
+
"& .xterm .xterm-accessibility-tree:not(.debug) *::selection": {
|
|
285
197
|
color: "transparent"
|
|
286
198
|
},
|
|
287
|
-
"& .xterm .
|
|
288
|
-
|
|
289
|
-
|
|
199
|
+
"& .xterm .xterm-accessibility-tree": {
|
|
200
|
+
userSelect: "text",
|
|
201
|
+
whiteSpace: "pre"
|
|
202
|
+
},
|
|
203
|
+
"& .xterm .live-region": {
|
|
204
|
+
position: "absolute",
|
|
205
|
+
left: "-9999px",
|
|
206
|
+
width: "1px",
|
|
207
|
+
height: "1px",
|
|
208
|
+
overflow: "hidden"
|
|
209
|
+
},
|
|
210
|
+
"& .xterm-dim": {
|
|
211
|
+
// Dim should not apply to background, so the opacity of the foreground color is applied
|
|
212
|
+
// explicitly in the generated class and reset to 1 here
|
|
213
|
+
opacity: "1 !important"
|
|
214
|
+
},
|
|
215
|
+
// Text decoration styles
|
|
216
|
+
"& .xterm-underline-1": { textDecoration: "underline" },
|
|
217
|
+
"& .xterm-underline-2": { textDecoration: "double underline" },
|
|
218
|
+
"& .xterm-underline-3": { textDecoration: "wavy underline" },
|
|
219
|
+
"& .xterm-underline-4": { textDecoration: "dotted underline" },
|
|
220
|
+
"& .xterm-underline-5": { textDecoration: "dashed underline" },
|
|
221
|
+
"& .xterm-overline": {
|
|
222
|
+
textDecoration: "overline"
|
|
223
|
+
},
|
|
224
|
+
"& .xterm-overline.xterm-underline-1": { textDecoration: "overline underline" },
|
|
225
|
+
"& .xterm-overline.xterm-underline-2": { textDecoration: "overline double underline" },
|
|
226
|
+
"& .xterm-overline.xterm-underline-3": { textDecoration: "overline wavy underline" },
|
|
227
|
+
"& .xterm-overline.xterm-underline-4": { textDecoration: "overline dotted underline" },
|
|
228
|
+
"& .xterm-overline.xterm-underline-5": { textDecoration: "overline dashed underline" },
|
|
229
|
+
"& .xterm-strikethrough": {
|
|
230
|
+
textDecoration: "line-through"
|
|
231
|
+
},
|
|
232
|
+
// Decoration styles
|
|
233
|
+
"& .xterm-screen .xterm-decoration-container .xterm-decoration": {
|
|
234
|
+
zIndex: 6,
|
|
235
|
+
position: "absolute"
|
|
236
|
+
},
|
|
237
|
+
"& .xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer": {
|
|
238
|
+
zIndex: 7
|
|
239
|
+
},
|
|
240
|
+
"& .xterm-decoration-overview-ruler": {
|
|
241
|
+
zIndex: 8,
|
|
242
|
+
position: "absolute",
|
|
243
|
+
top: 0,
|
|
244
|
+
right: 0,
|
|
245
|
+
pointerEvents: "none"
|
|
246
|
+
},
|
|
247
|
+
"& .xterm-decoration-top": {
|
|
248
|
+
zIndex: 2,
|
|
249
|
+
position: "relative"
|
|
250
|
+
}
|
|
290
251
|
});
|
|
291
252
|
export {
|
|
292
|
-
|
|
253
|
+
r as PlayerRoot,
|
|
293
254
|
o as TerminalRoot
|
|
294
255
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcblock/terminal",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.18",
|
|
4
4
|
"description": "A react wrapper for xterm allowing you to easily render a terminal in the browser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -40,16 +40,16 @@
|
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"react": "^19.0.0"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "a145d1dd215444e7b65b8fa08cba2d8db513a94a",
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@arcblock/react-hooks": "3.1.
|
|
46
|
-
"@arcblock/ux": "3.1.
|
|
45
|
+
"@arcblock/react-hooks": "3.1.18",
|
|
46
|
+
"@arcblock/ux": "3.1.18",
|
|
47
47
|
"@emotion/react": "^11.14.0",
|
|
48
48
|
"@emotion/styled": "^11.14.0",
|
|
49
|
+
"@xterm/addon-fit": "^0.10.0",
|
|
50
|
+
"@xterm/addon-web-links": "^0.11.0",
|
|
51
|
+
"@xterm/xterm": "^5.5.0",
|
|
49
52
|
"ahooks": "^3.8.5",
|
|
50
|
-
"lodash": "^4.17.21"
|
|
51
|
-
"xterm": "4.19.0",
|
|
52
|
-
"xterm-addon-fit": "^0.2.1",
|
|
53
|
-
"xterm-addon-web-links": "^0.2.1"
|
|
53
|
+
"lodash": "^4.17.21"
|
|
54
54
|
}
|
|
55
55
|
}
|
package/src/Player.jsx
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
/* eslint-disable react/no-unused-prop-types, no-console */
|
|
2
|
-
import { useReducer,
|
|
2
|
+
import { useReducer, useRef, useEffect, useCallback } from 'react';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
|
-
import { useSize } from 'ahooks';
|
|
5
4
|
import isUndefined from 'lodash/isUndefined';
|
|
6
5
|
import noop from 'lodash/noop';
|
|
7
6
|
|
|
@@ -19,7 +18,7 @@ import {
|
|
|
19
18
|
defaultState,
|
|
20
19
|
} from './util';
|
|
21
20
|
|
|
22
|
-
export const PLAYER_FRAME_DELAY =
|
|
21
|
+
export const PLAYER_FRAME_DELAY = 8;
|
|
23
22
|
|
|
24
23
|
export default function Player({ ...rawProps }) {
|
|
25
24
|
const props = Object.assign({}, rawProps);
|
|
@@ -49,13 +48,14 @@ export default function Player({ ...rawProps }) {
|
|
|
49
48
|
cols: options.cols,
|
|
50
49
|
rows: options.rows,
|
|
51
50
|
cursorStyle: options.cursorStyle,
|
|
51
|
+
cursorBlink: options.cursorBlink ?? true,
|
|
52
52
|
fontFamily: options.fontFamily,
|
|
53
53
|
fontSize: options.fontSize,
|
|
54
54
|
lineHeight: options.lineHeight,
|
|
55
55
|
letterSpacing: options.letterSpacing,
|
|
56
56
|
allowTransparency: true,
|
|
57
57
|
scrollback: 0,
|
|
58
|
-
theme: options.theme
|
|
58
|
+
theme: options.enableTheme ? options.theme : {},
|
|
59
59
|
};
|
|
60
60
|
|
|
61
61
|
const stateReducer = (state, action) => {
|
|
@@ -85,39 +85,7 @@ export default function Player({ ...rawProps }) {
|
|
|
85
85
|
const container = useRef(null);
|
|
86
86
|
const animationRef = useRef(null);
|
|
87
87
|
const startTimeRef = useRef(null);
|
|
88
|
-
const [maxWidth, setMaxWidth] = useState(0);
|
|
89
|
-
const size = useSize(document.body);
|
|
90
88
|
const [state, dispatch] = useReducer(stateReducer, defaultState);
|
|
91
|
-
const width = size?.width || 0;
|
|
92
|
-
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
if (!terminal.current) {
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
try {
|
|
99
|
-
const COLUMN_WIDTH = 972 / 121;
|
|
100
|
-
const child = container.current.getBoundingClientRect();
|
|
101
|
-
let containerWidth = child.x < 0 ? child.width + child.x : child.width;
|
|
102
|
-
if (container.current.parentNode) {
|
|
103
|
-
const parent = container.current.parentNode.getBoundingClientRect();
|
|
104
|
-
if (child.width > parent.width) {
|
|
105
|
-
containerWidth = parent.width;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (options.controls) {
|
|
110
|
-
containerWidth -= 12;
|
|
111
|
-
}
|
|
112
|
-
const colContainer = Math.ceil(containerWidth / COLUMN_WIDTH);
|
|
113
|
-
const cols = Math.min(Math.max(colContainer, 40), options.cols);
|
|
114
|
-
|
|
115
|
-
terminal.current.resize(cols, options.rows);
|
|
116
|
-
} catch (err) {
|
|
117
|
-
// Do nothing
|
|
118
|
-
}
|
|
119
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
120
|
-
}, [width, maxWidth]);
|
|
121
89
|
|
|
122
90
|
// Render a frame with Promise-based approach
|
|
123
91
|
const renderFrame = useCallback(
|
|
@@ -140,6 +108,21 @@ export default function Player({ ...rawProps }) {
|
|
|
140
108
|
[frames, state.requireReset]
|
|
141
109
|
);
|
|
142
110
|
|
|
111
|
+
// Render a frame synchronously for jump operations (no delay)
|
|
112
|
+
const renderFrameSync = useCallback(
|
|
113
|
+
(frameIndex) => {
|
|
114
|
+
const frame = frames[frameIndex];
|
|
115
|
+
if (frame && frame.content && terminal.current) {
|
|
116
|
+
if (state.requireReset) {
|
|
117
|
+
terminal.current.reset();
|
|
118
|
+
}
|
|
119
|
+
// Use synchronous write without callback for instant rendering
|
|
120
|
+
terminal.current.write(frame.content);
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
[frames, state.requireReset]
|
|
124
|
+
);
|
|
125
|
+
|
|
143
126
|
// Emit a event
|
|
144
127
|
const emitEvent = useCallback(
|
|
145
128
|
(name) => {
|
|
@@ -151,21 +134,20 @@ export default function Player({ ...rawProps }) {
|
|
|
151
134
|
);
|
|
152
135
|
|
|
153
136
|
const doJump = useCallback(
|
|
154
|
-
|
|
137
|
+
(time) => {
|
|
155
138
|
if (!terminal.current) return;
|
|
156
139
|
|
|
157
140
|
terminal.current.reset();
|
|
158
141
|
const toFrameIndex = findFrameAt(frames, time);
|
|
159
142
|
|
|
160
143
|
if (toFrameIndex >= 0) {
|
|
161
|
-
// Render all frames up to the target frame
|
|
144
|
+
// Render all frames up to the target frame synchronously for instant jump
|
|
162
145
|
for (let i = 0; i <= toFrameIndex; i++) {
|
|
163
|
-
|
|
164
|
-
await renderFrame(i);
|
|
146
|
+
renderFrameSync(i);
|
|
165
147
|
}
|
|
166
148
|
}
|
|
167
149
|
},
|
|
168
|
-
[frames,
|
|
150
|
+
[frames, renderFrameSync]
|
|
169
151
|
);
|
|
170
152
|
|
|
171
153
|
const onJump = useCallback(
|
|
@@ -207,6 +189,9 @@ export default function Player({ ...rawProps }) {
|
|
|
207
189
|
|
|
208
190
|
const onStart = useCallback(() => {
|
|
209
191
|
if (state.isStarted === false) {
|
|
192
|
+
// 触发一下 resize,保证文字输出正常
|
|
193
|
+
terminal.current.resize();
|
|
194
|
+
|
|
210
195
|
dispatch({ type: 'start' });
|
|
211
196
|
if (terminal.current) {
|
|
212
197
|
terminal.current.reset();
|
|
@@ -227,38 +212,15 @@ export default function Player({ ...rawProps }) {
|
|
|
227
212
|
return false;
|
|
228
213
|
}, [emitEvent]);
|
|
229
214
|
|
|
230
|
-
const onPlay = useCallback(() => {
|
|
231
|
-
if (state.currentFrame >= frames.length - 1) {
|
|
232
|
-
// Reset to beginning if at the end
|
|
233
|
-
dispatch({ type: 'reset', payload: { currentFrame: -1, currentTime: 0 } });
|
|
234
|
-
if (terminal.current) {
|
|
235
|
-
terminal.current.reset();
|
|
236
|
-
}
|
|
237
|
-
startTimeRef.current = null;
|
|
238
|
-
// Start from beginning
|
|
239
|
-
dispatch({ type: 'play', payload: { currentFrame: -1, currentTime: 0 } });
|
|
240
|
-
} else {
|
|
241
|
-
// Continue from current position
|
|
242
|
-
startTimeRef.current = null; // Reset timing for accurate playback
|
|
243
|
-
dispatch({ type: 'play' });
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
emitEvent('onPlay');
|
|
247
|
-
return false;
|
|
248
|
-
}, [state.currentFrame, frames.length, emitEvent]);
|
|
249
|
-
|
|
250
215
|
// Render thumbnailTime
|
|
251
216
|
useEffect(() => {
|
|
252
217
|
if (!terminal.current) {
|
|
253
218
|
return;
|
|
254
219
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
// Do nothing
|
|
260
|
-
}
|
|
261
|
-
}
|
|
220
|
+
|
|
221
|
+
// Focus terminal to ensure cursor is visible
|
|
222
|
+
terminal.current.focus();
|
|
223
|
+
|
|
262
224
|
if (options.autoplay) {
|
|
263
225
|
onStart();
|
|
264
226
|
} else {
|
|
@@ -448,11 +410,6 @@ export default function Player({ ...rawProps }) {
|
|
|
448
410
|
<span className="icon" />
|
|
449
411
|
</div>
|
|
450
412
|
)}
|
|
451
|
-
{!state.isPlaying && state.isStarted && (
|
|
452
|
-
<div className="play" onClick={onPlay} title="Play">
|
|
453
|
-
<span className="icon" />
|
|
454
|
-
</div>
|
|
455
|
-
)}
|
|
456
413
|
{!state.isPlaying && !state.isStarted && (
|
|
457
414
|
<div className="play" onClick={onStart} title="Start">
|
|
458
415
|
<span className="icon" />
|
package/src/Terminal.jsx
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/* eslint-disable react/no-unused-class-component-methods */
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
-
import { Terminal as XTerm } from 'xterm';
|
|
6
|
-
import { WebLinksAddon } from 'xterm
|
|
7
|
-
import { FitAddon } from 'xterm
|
|
5
|
+
import { Terminal as XTerm } from '@xterm/xterm';
|
|
6
|
+
import { WebLinksAddon } from '@xterm/addon-web-links';
|
|
7
|
+
import { FitAddon } from '@xterm/addon-fit';
|
|
8
8
|
import debounce from 'lodash/debounce';
|
|
9
9
|
import noop from 'lodash/noop';
|
|
10
10
|
import { TerminalRoot } from './styles';
|
|
@@ -14,6 +14,8 @@ export default class Terminal extends React.Component {
|
|
|
14
14
|
|
|
15
15
|
container = null;
|
|
16
16
|
|
|
17
|
+
shouldTriggerFitWhenWrite = false;
|
|
18
|
+
|
|
17
19
|
componentDidMount() {
|
|
18
20
|
const { value = '', options = {} } = this.props;
|
|
19
21
|
this.fitAddon = new FitAddon();
|
|
@@ -31,15 +33,9 @@ export default class Terminal extends React.Component {
|
|
|
31
33
|
this.xterm.write(value);
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
setTimeout(() => {
|
|
35
|
-
if (this.xterm) {
|
|
36
|
-
this.fitAddon.fit();
|
|
37
|
-
}
|
|
38
|
-
}, 0);
|
|
39
|
-
|
|
40
36
|
this.debounceFit = debounce(() => {
|
|
41
37
|
this.fitAddon.fit();
|
|
42
|
-
},
|
|
38
|
+
}, 500);
|
|
43
39
|
|
|
44
40
|
window.addEventListener('resize', this.debounceFit);
|
|
45
41
|
}
|
|
@@ -75,6 +71,10 @@ export default class Terminal extends React.Component {
|
|
|
75
71
|
write(data, cb) {
|
|
76
72
|
if (this.xterm) {
|
|
77
73
|
this.xterm.write(data, cb);
|
|
74
|
+
if (this.shouldTriggerFitWhenWrite) {
|
|
75
|
+
this.debounceFit();
|
|
76
|
+
this.shouldTriggerFitWhenWrite = false;
|
|
77
|
+
}
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -100,22 +100,29 @@ export default class Terminal extends React.Component {
|
|
|
100
100
|
onRender(data);
|
|
101
101
|
};
|
|
102
102
|
|
|
103
|
-
resize(
|
|
103
|
+
resize() {
|
|
104
104
|
if (this.xterm) {
|
|
105
|
-
|
|
105
|
+
const { cols, rows } = this.props.options;
|
|
106
|
+
this.shouldTriggerFitWhenWrite = true;
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
this.xterm.resize(Math.round(cols), Math.round(rows));
|
|
109
|
+
this.shouldTriggerFitWhenWrite = true;
|
|
110
|
+
}, 250);
|
|
106
111
|
}
|
|
107
112
|
}
|
|
108
113
|
|
|
109
114
|
setOption(key, value) {
|
|
110
115
|
if (this.xterm) {
|
|
111
|
-
|
|
116
|
+
// In xterm.js v5, use options object directly instead of setOption
|
|
117
|
+
this.xterm.options[key] = value;
|
|
112
118
|
}
|
|
113
119
|
}
|
|
114
120
|
|
|
115
121
|
getOption(key) {
|
|
116
122
|
if (this.xterm) {
|
|
117
|
-
this.xterm.
|
|
123
|
+
return this.xterm.options[key];
|
|
118
124
|
}
|
|
125
|
+
return undefined;
|
|
119
126
|
}
|
|
120
127
|
|
|
121
128
|
refresh() {
|