@arcblock/terminal 3.4.15 → 3.5.0
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/package.json +7 -4
- package/src/Player.jsx +0 -532
- package/src/Player.stories.jsx +0 -19
- package/src/Terminal.jsx +0 -165
- package/src/index.js +0 -4
- package/src/styles.js +0 -539
- package/src/util.js +0 -170
- package/vite.config.mjs +0 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcblock/terminal",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "A react wrapper for xterm allowing you to easily render a terminal in the browser",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react",
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"bugs": {
|
|
31
31
|
"url": "https://github.com/ArcBlock/ux/issues"
|
|
32
32
|
},
|
|
33
|
+
"files": [
|
|
34
|
+
"lib"
|
|
35
|
+
],
|
|
33
36
|
"publishConfig": {
|
|
34
37
|
"access": "public"
|
|
35
38
|
},
|
|
@@ -40,10 +43,10 @@
|
|
|
40
43
|
"peerDependencies": {
|
|
41
44
|
"react": "^19.0.0"
|
|
42
45
|
},
|
|
43
|
-
"gitHead": "
|
|
46
|
+
"gitHead": "1cfc816004525cf1b352ec2b64d459f4769f0237",
|
|
44
47
|
"dependencies": {
|
|
45
|
-
"@arcblock/react-hooks": "3.
|
|
46
|
-
"@arcblock/ux": "3.
|
|
48
|
+
"@arcblock/react-hooks": "3.5.0",
|
|
49
|
+
"@arcblock/ux": "3.5.0",
|
|
47
50
|
"@emotion/react": "^11.14.0",
|
|
48
51
|
"@emotion/styled": "^11.14.0",
|
|
49
52
|
"@xterm/addon-fit": "^0.10.0",
|
package/src/Player.jsx
DELETED
|
@@ -1,532 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react/no-unused-prop-types, no-console */
|
|
2
|
-
import { useReducer, useRef, useEffect, useCallback } from 'react';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
import isUndefined from 'lodash/isUndefined';
|
|
5
|
-
import noop from 'lodash/noop';
|
|
6
|
-
|
|
7
|
-
import Terminal from './Terminal';
|
|
8
|
-
|
|
9
|
-
import { PlayerRoot } from './styles';
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
formatFrames,
|
|
13
|
-
formatTime,
|
|
14
|
-
findFrameAt,
|
|
15
|
-
getFrameClass,
|
|
16
|
-
getPlayerClass,
|
|
17
|
-
defaultOptions,
|
|
18
|
-
defaultState,
|
|
19
|
-
} from './util';
|
|
20
|
-
|
|
21
|
-
export const PLAYER_FRAME_DELAY = 8;
|
|
22
|
-
|
|
23
|
-
export default function Player({ ...rawProps }) {
|
|
24
|
-
const props = Object.assign({}, rawProps);
|
|
25
|
-
if (isUndefined(props.onComplete)) {
|
|
26
|
-
props.onComplete = noop;
|
|
27
|
-
}
|
|
28
|
-
if (isUndefined(props.onStart)) {
|
|
29
|
-
props.onStart = noop;
|
|
30
|
-
}
|
|
31
|
-
if (isUndefined(props.onStop)) {
|
|
32
|
-
props.onStop = noop;
|
|
33
|
-
}
|
|
34
|
-
if (isUndefined(props.onPause)) {
|
|
35
|
-
props.onPause = noop;
|
|
36
|
-
}
|
|
37
|
-
if (isUndefined(props.onTick)) {
|
|
38
|
-
props.onTick = noop;
|
|
39
|
-
}
|
|
40
|
-
if (isUndefined(props.onJump)) {
|
|
41
|
-
props.onJump = noop;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const options = Object.assign({}, defaultOptions, props.options);
|
|
45
|
-
const { frames, totalDuration } = formatFrames(props.frames, options);
|
|
46
|
-
|
|
47
|
-
const terminalOptions = {
|
|
48
|
-
cols: options.cols,
|
|
49
|
-
rows: options.rows,
|
|
50
|
-
cursorStyle: options.cursorStyle,
|
|
51
|
-
cursorBlink: options.cursorBlink ?? true,
|
|
52
|
-
fontFamily: options.fontFamily,
|
|
53
|
-
fontSize: options.fontSize,
|
|
54
|
-
lineHeight: options.lineHeight,
|
|
55
|
-
letterSpacing: options.letterSpacing,
|
|
56
|
-
allowTransparency: true,
|
|
57
|
-
scrollback: 0,
|
|
58
|
-
theme: options.enableTheme ? options.theme : {},
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
const stateReducer = (state, action) => {
|
|
62
|
-
// console.log(`dispatch.${action.type}`, action.payload);
|
|
63
|
-
switch (action.type) {
|
|
64
|
-
case 'jump':
|
|
65
|
-
return { ...state, ...action.payload };
|
|
66
|
-
case 'start':
|
|
67
|
-
return { ...state, isStarted: true, lastTickTime: Date.now() };
|
|
68
|
-
case 'play':
|
|
69
|
-
return { ...state, isPlaying: true, lastTickTime: Date.now(), ...action.payload };
|
|
70
|
-
case 'pause':
|
|
71
|
-
return { ...state, isPlaying: false, ...action.payload };
|
|
72
|
-
case 'tickStart':
|
|
73
|
-
return { ...state, isRendering: true, lastTickTime: Date.now(), ...action.payload };
|
|
74
|
-
case 'tickEnd':
|
|
75
|
-
return { ...state, isRendering: false, lastTickTime: Date.now(), ...action.payload };
|
|
76
|
-
case 'reset':
|
|
77
|
-
return { ...state, currentFrame: -1, currentTime: 0, ...action.payload };
|
|
78
|
-
default:
|
|
79
|
-
return { ...state, lastTickTime: Date.now(), ...action.payload };
|
|
80
|
-
}
|
|
81
|
-
};
|
|
82
|
-
|
|
83
|
-
const terminal = useRef(null);
|
|
84
|
-
const progress = useRef(null);
|
|
85
|
-
const container = useRef(null);
|
|
86
|
-
const animationRef = useRef(null);
|
|
87
|
-
const startTimeRef = useRef(null);
|
|
88
|
-
const autoPlayRef = useRef(false);
|
|
89
|
-
const [state, dispatch] = useReducer(stateReducer, defaultState);
|
|
90
|
-
|
|
91
|
-
// Render a frame with Promise-based approach
|
|
92
|
-
const renderFrame = useCallback(
|
|
93
|
-
(frameIndex) => {
|
|
94
|
-
return new Promise((resolve) => {
|
|
95
|
-
const frame = frames[frameIndex];
|
|
96
|
-
if (frame && frame.content && terminal.current) {
|
|
97
|
-
if (state.requireReset) {
|
|
98
|
-
terminal.current.reset();
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
terminal.current.write(frame.content, () => {
|
|
102
|
-
resolve();
|
|
103
|
-
});
|
|
104
|
-
} else {
|
|
105
|
-
resolve();
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
},
|
|
109
|
-
[frames, state.requireReset]
|
|
110
|
-
);
|
|
111
|
-
|
|
112
|
-
// Render a frame synchronously for jump operations (no delay)
|
|
113
|
-
const renderFrameSync = useCallback(
|
|
114
|
-
(frameIndex) => {
|
|
115
|
-
const frame = frames[frameIndex];
|
|
116
|
-
if (frame && frame.content && terminal.current) {
|
|
117
|
-
if (state.requireReset) {
|
|
118
|
-
terminal.current.reset();
|
|
119
|
-
}
|
|
120
|
-
// Use synchronous write without callback for instant rendering
|
|
121
|
-
terminal.current.write(frame.content);
|
|
122
|
-
}
|
|
123
|
-
},
|
|
124
|
-
[frames, state.requireReset]
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// Emit a event
|
|
128
|
-
const emitEvent = useCallback(
|
|
129
|
-
(name) => {
|
|
130
|
-
if (typeof props[name] === 'function') {
|
|
131
|
-
props[name]({ state, frames, options });
|
|
132
|
-
}
|
|
133
|
-
},
|
|
134
|
-
[props, state, frames, options]
|
|
135
|
-
);
|
|
136
|
-
|
|
137
|
-
const doJump = useCallback(
|
|
138
|
-
(time) => {
|
|
139
|
-
if (!terminal.current) return;
|
|
140
|
-
|
|
141
|
-
terminal.current.reset();
|
|
142
|
-
const toFrameIndex = findFrameAt(frames, time);
|
|
143
|
-
|
|
144
|
-
if (toFrameIndex >= 0) {
|
|
145
|
-
// Render all frames up to the target frame synchronously for instant jump
|
|
146
|
-
for (let i = 0; i <= toFrameIndex; i++) {
|
|
147
|
-
renderFrameSync(i);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
},
|
|
151
|
-
[frames, renderFrameSync]
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
const onJump = useCallback(
|
|
155
|
-
(e) => {
|
|
156
|
-
if (!progress.current || !terminal.current || !state.isStarted) {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const length = progress.current.getBoundingClientRect().width;
|
|
161
|
-
const position = e.nativeEvent.offsetX;
|
|
162
|
-
|
|
163
|
-
const currentTime = Math.floor((totalDuration * position) / length);
|
|
164
|
-
const targetFrameIndex = findFrameAt(frames, currentTime);
|
|
165
|
-
|
|
166
|
-
// Preserve the current playing state
|
|
167
|
-
const isCurrentlyPlaying = state.isPlaying;
|
|
168
|
-
|
|
169
|
-
// Update state to reflect the jump while preserving play state
|
|
170
|
-
dispatch({
|
|
171
|
-
type: 'jump',
|
|
172
|
-
payload: {
|
|
173
|
-
currentTime,
|
|
174
|
-
currentFrame: targetFrameIndex,
|
|
175
|
-
isPlaying: isCurrentlyPlaying, // Keep the same playing state
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Reset the timing for accurate playback after jump
|
|
180
|
-
startTimeRef.current = null;
|
|
181
|
-
|
|
182
|
-
// Perform the jump
|
|
183
|
-
doJump(currentTime);
|
|
184
|
-
emitEvent('onJump');
|
|
185
|
-
|
|
186
|
-
return false;
|
|
187
|
-
},
|
|
188
|
-
[state.isStarted, state.isPlaying, totalDuration, frames, doJump, emitEvent]
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
const onPlay = useCallback(() => {
|
|
192
|
-
if (state.currentFrame >= frames.length - 1) {
|
|
193
|
-
// 触发一下 resize,保证文字输出正常
|
|
194
|
-
terminal.current.resize();
|
|
195
|
-
// Reset to beginning if at the end
|
|
196
|
-
dispatch({ type: 'reset', payload: { currentFrame: -1, currentTime: 0 } });
|
|
197
|
-
if (terminal.current) {
|
|
198
|
-
terminal.current.reset();
|
|
199
|
-
}
|
|
200
|
-
startTimeRef.current = null;
|
|
201
|
-
// Start from beginning
|
|
202
|
-
dispatch({ type: 'play', payload: { currentFrame: -1, currentTime: 0 } });
|
|
203
|
-
} else {
|
|
204
|
-
// Continue from current position
|
|
205
|
-
startTimeRef.current = null; // Reset timing for accurate playback
|
|
206
|
-
dispatch({ type: 'play' });
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
emitEvent('onPlay');
|
|
210
|
-
return false;
|
|
211
|
-
}, [state.currentFrame, frames.length, emitEvent]);
|
|
212
|
-
|
|
213
|
-
const onStart = useCallback(() => {
|
|
214
|
-
if (state.isStarted === false) {
|
|
215
|
-
// 触发一下 resize,保证文字输出正常
|
|
216
|
-
terminal.current.resize();
|
|
217
|
-
|
|
218
|
-
dispatch({ type: 'start' });
|
|
219
|
-
if (terminal.current) {
|
|
220
|
-
terminal.current.reset();
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Reset start time and frame for accurate timing
|
|
225
|
-
startTimeRef.current = null;
|
|
226
|
-
dispatch({ type: 'play', payload: { currentFrame: -1, currentTime: 0 } });
|
|
227
|
-
emitEvent('onStart');
|
|
228
|
-
|
|
229
|
-
return false;
|
|
230
|
-
}, [state.isStarted, emitEvent]);
|
|
231
|
-
|
|
232
|
-
const onPause = useCallback(() => {
|
|
233
|
-
dispatch({ type: 'pause' });
|
|
234
|
-
emitEvent('onPause');
|
|
235
|
-
return false;
|
|
236
|
-
}, [emitEvent]);
|
|
237
|
-
|
|
238
|
-
// Intersection Observer for viewport detection
|
|
239
|
-
useEffect(() => {
|
|
240
|
-
if (!container.current || !options.autoplay || autoPlayRef.current) {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const observer = new IntersectionObserver(
|
|
245
|
-
(entries) => {
|
|
246
|
-
const [entry] = entries;
|
|
247
|
-
if (entry.isIntersecting && !state.isStarted) {
|
|
248
|
-
// 根据视野范围自动开始播放
|
|
249
|
-
onStart();
|
|
250
|
-
autoPlayRef.current = true;
|
|
251
|
-
// 播放后,断开 observer,避免触发多次
|
|
252
|
-
observer.unobserve(container.current);
|
|
253
|
-
observer.disconnect();
|
|
254
|
-
}
|
|
255
|
-
},
|
|
256
|
-
{
|
|
257
|
-
threshold: options.autoplayThreshold, // 可配置的可见度阈值
|
|
258
|
-
rootMargin: options.autoplayRootMargin, // 可配置的根边距
|
|
259
|
-
}
|
|
260
|
-
);
|
|
261
|
-
|
|
262
|
-
observer.observe(container.current);
|
|
263
|
-
|
|
264
|
-
// eslint-disable-next-line consistent-return
|
|
265
|
-
return () => {
|
|
266
|
-
observer.disconnect();
|
|
267
|
-
};
|
|
268
|
-
}, [options.autoplay, options.autoplayThreshold, options.autoplayRootMargin, state.isStarted, onStart]);
|
|
269
|
-
|
|
270
|
-
// Render thumbnailTime
|
|
271
|
-
useEffect(() => {
|
|
272
|
-
if (!terminal.current) {
|
|
273
|
-
return;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Focus terminal to ensure cursor is visible
|
|
277
|
-
terminal.current.focus();
|
|
278
|
-
|
|
279
|
-
if (!options.autoplay) {
|
|
280
|
-
doJump(Math.min(Math.abs(options.thumbnailTime), totalDuration));
|
|
281
|
-
}
|
|
282
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
283
|
-
}, []);
|
|
284
|
-
|
|
285
|
-
// Use ref to store the latest state and avoid recreating the loop function
|
|
286
|
-
const stateRef = useRef(state);
|
|
287
|
-
stateRef.current = state;
|
|
288
|
-
|
|
289
|
-
// Animation loop - frame-based sequential rendering
|
|
290
|
-
const animationLoop = useCallback(async () => {
|
|
291
|
-
const currentState = stateRef.current;
|
|
292
|
-
|
|
293
|
-
if (!currentState.isPlaying || currentState.isRendering) {
|
|
294
|
-
return;
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
const now = performance.now();
|
|
298
|
-
if (!startTimeRef.current) {
|
|
299
|
-
// Calculate start time based on current frame position
|
|
300
|
-
const currentFrameTime = currentState.currentFrame >= 0 ? frames[currentState.currentFrame]?.startTime || 0 : 0;
|
|
301
|
-
startTimeRef.current = now - currentFrameTime;
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const elapsed = now - startTimeRef.current;
|
|
305
|
-
|
|
306
|
-
// Check if it's time to render the next frame
|
|
307
|
-
const { currentFrame } = currentState;
|
|
308
|
-
const nextFrameIndex = currentFrame + 1;
|
|
309
|
-
|
|
310
|
-
// If we've rendered all frames, we're done
|
|
311
|
-
if (currentFrame >= frames.length - 1) {
|
|
312
|
-
emitEvent('onComplete');
|
|
313
|
-
|
|
314
|
-
if (options.repeat) {
|
|
315
|
-
// Reset for repeat
|
|
316
|
-
startTimeRef.current = now;
|
|
317
|
-
dispatch({ type: 'reset', payload: { currentTime: 0, currentFrame: -1 } });
|
|
318
|
-
return;
|
|
319
|
-
}
|
|
320
|
-
// Stop playing
|
|
321
|
-
const endState = {
|
|
322
|
-
currentTime: totalDuration,
|
|
323
|
-
currentFrame: frames.length - 1,
|
|
324
|
-
requireReset: true,
|
|
325
|
-
isStarted: false,
|
|
326
|
-
};
|
|
327
|
-
dispatch({ type: 'pause', payload: endState });
|
|
328
|
-
startTimeRef.current = null;
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
// Calculate when the next frame should be rendered
|
|
333
|
-
const nextFrame = frames[nextFrameIndex];
|
|
334
|
-
if (!nextFrame) {
|
|
335
|
-
// No more frames, schedule next animation frame
|
|
336
|
-
if (state.isPlaying) {
|
|
337
|
-
animationRef.current = requestAnimationFrame(animationLoop);
|
|
338
|
-
}
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
// Check if enough time has passed for the next frame
|
|
343
|
-
const frameStartTime = nextFrame.startTime || 0;
|
|
344
|
-
if (elapsed >= frameStartTime) {
|
|
345
|
-
// Time to render the next frame
|
|
346
|
-
dispatch({
|
|
347
|
-
type: 'tickStart',
|
|
348
|
-
payload: {
|
|
349
|
-
currentTime: frameStartTime,
|
|
350
|
-
currentFrame: nextFrameIndex,
|
|
351
|
-
},
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
try {
|
|
355
|
-
await renderFrame(nextFrameIndex);
|
|
356
|
-
|
|
357
|
-
const finalState = {
|
|
358
|
-
currentTime: frameStartTime,
|
|
359
|
-
currentFrame: nextFrameIndex,
|
|
360
|
-
};
|
|
361
|
-
if (currentState.requireReset) {
|
|
362
|
-
finalState.requireReset = false;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
dispatch({ type: 'tickEnd', payload: finalState });
|
|
366
|
-
emitEvent('onTick');
|
|
367
|
-
} catch (error) {
|
|
368
|
-
console.error('Frame rendering error:', error);
|
|
369
|
-
}
|
|
370
|
-
} else {
|
|
371
|
-
// Just update time without rendering
|
|
372
|
-
dispatch({ type: 'tick', payload: { currentTime: elapsed } });
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
// Don't schedule here - let useEffect handle it
|
|
376
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
377
|
-
}, [frames, renderFrame, emitEvent, options.repeat, totalDuration]);
|
|
378
|
-
|
|
379
|
-
// Start/stop animation loop based on playing state
|
|
380
|
-
useEffect(() => {
|
|
381
|
-
let isActive = true;
|
|
382
|
-
|
|
383
|
-
const runAnimationLoop = async () => {
|
|
384
|
-
// eslint-disable-next-line no-await-in-loop
|
|
385
|
-
while (isActive && stateRef.current.isPlaying) {
|
|
386
|
-
// eslint-disable-next-line no-await-in-loop
|
|
387
|
-
await new Promise((resolve) => {
|
|
388
|
-
animationRef.current = requestAnimationFrame(resolve);
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
if (isActive && stateRef.current.isPlaying) {
|
|
392
|
-
// eslint-disable-next-line no-await-in-loop
|
|
393
|
-
await animationLoop();
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
if (state.isPlaying) {
|
|
399
|
-
runAnimationLoop();
|
|
400
|
-
} else {
|
|
401
|
-
if (animationRef.current) {
|
|
402
|
-
cancelAnimationFrame(animationRef.current);
|
|
403
|
-
animationRef.current = null;
|
|
404
|
-
}
|
|
405
|
-
startTimeRef.current = null;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
return () => {
|
|
409
|
-
isActive = false;
|
|
410
|
-
if (animationRef.current) {
|
|
411
|
-
cancelAnimationFrame(animationRef.current);
|
|
412
|
-
animationRef.current = null;
|
|
413
|
-
}
|
|
414
|
-
};
|
|
415
|
-
}, [state.isPlaying, animationLoop]);
|
|
416
|
-
|
|
417
|
-
// If controls are enabled, we need to disable frameBox
|
|
418
|
-
if (options.controls) {
|
|
419
|
-
options.frameBox.title = null;
|
|
420
|
-
options.frameBox.type = null;
|
|
421
|
-
options.frameBox.style = {};
|
|
422
|
-
|
|
423
|
-
if (options.theme?.background === 'transparent') {
|
|
424
|
-
options.frameBox.style.background = 'black';
|
|
425
|
-
} else if (options.theme?.background) {
|
|
426
|
-
options.frameBox.style.background = options.theme.background;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
options.frameBox.style.padding = '10px';
|
|
430
|
-
options.frameBox.style.paddingBottom = '40px';
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
return (
|
|
434
|
-
<PlayerRoot className={getPlayerClass(options, state)} ref={container}>
|
|
435
|
-
<div className="cover" onClick={onStart} />
|
|
436
|
-
<div className="start" onClick={onStart}>
|
|
437
|
-
<div className="start-button">
|
|
438
|
-
<svg viewBox="0 0 30 30">
|
|
439
|
-
<polygon points="6.583,3.186 5,4.004 5,15 26,15 26.483,14.128 " />
|
|
440
|
-
<polygon points="6.583,26.814 5,25.996 5,15 26,15 26.483,15.872 " />
|
|
441
|
-
<circle cx="26" cy="15" r="1" />
|
|
442
|
-
<circle cx="6" cy="4" r="1" />
|
|
443
|
-
<circle cx="6" cy="26" r="1" />
|
|
444
|
-
</svg>
|
|
445
|
-
</div>
|
|
446
|
-
</div>
|
|
447
|
-
|
|
448
|
-
{/* Hover overlay for playing state */}
|
|
449
|
-
{state.isPlaying && state.isStarted && (
|
|
450
|
-
<div className="hover-overlay" onClick={onPause}>
|
|
451
|
-
<div className="hover-pause-button">
|
|
452
|
-
<span className="pause-icon" />
|
|
453
|
-
</div>
|
|
454
|
-
</div>
|
|
455
|
-
)}
|
|
456
|
-
|
|
457
|
-
{/* Overlay for paused state */}
|
|
458
|
-
{!state.isPlaying && state.isStarted && (
|
|
459
|
-
<div className="pause-overlay" onClick={onPlay}>
|
|
460
|
-
<div className="overlay-play-button">
|
|
461
|
-
<svg viewBox="0 0 30 30">
|
|
462
|
-
<polygon points="6.583,3.186 5,4.004 5,15 26,15 26.483,14.128 " />
|
|
463
|
-
<polygon points="6.583,26.814 5,25.996 5,15 26,15 26.483,15.872 " />
|
|
464
|
-
<circle cx="26" cy="15" r="1" />
|
|
465
|
-
<circle cx="6" cy="4" r="1" />
|
|
466
|
-
<circle cx="6" cy="26" r="1" />
|
|
467
|
-
</svg>
|
|
468
|
-
</div>
|
|
469
|
-
</div>
|
|
470
|
-
)}
|
|
471
|
-
<div className="terminal">
|
|
472
|
-
<div className={getFrameClass(options)} style={options.frameBox.style || {}}>
|
|
473
|
-
<div className="terminal-titlebar">
|
|
474
|
-
<div className="buttons">
|
|
475
|
-
<div className="close-button" />
|
|
476
|
-
<div className="minimize-button" />
|
|
477
|
-
<div className="maximize-button" />
|
|
478
|
-
</div>
|
|
479
|
-
<div className="title">{options.frameBox.title || ''}</div>
|
|
480
|
-
</div>
|
|
481
|
-
<div className="terminal-body">
|
|
482
|
-
<Terminal ref={terminal} options={terminalOptions} />
|
|
483
|
-
</div>
|
|
484
|
-
</div>
|
|
485
|
-
</div>
|
|
486
|
-
<div className="controller">
|
|
487
|
-
{!state.isPlaying && state.isStarted && (
|
|
488
|
-
<div className="play" onClick={onPlay} title="Play">
|
|
489
|
-
<span className="icon" />
|
|
490
|
-
</div>
|
|
491
|
-
)}
|
|
492
|
-
{state.isPlaying && (
|
|
493
|
-
<div className="pause" onClick={onPause} title="Pause">
|
|
494
|
-
<span className="icon" />
|
|
495
|
-
</div>
|
|
496
|
-
)}
|
|
497
|
-
{!state.isPlaying && !state.isStarted && (
|
|
498
|
-
<div className="play" onClick={onStart} title="Start">
|
|
499
|
-
<span className="icon" />
|
|
500
|
-
</div>
|
|
501
|
-
)}
|
|
502
|
-
<div className="timer">{formatTime(state.currentTime)}</div>
|
|
503
|
-
<div className="progressbar-wrapper">
|
|
504
|
-
<div className="progressbar" ref={progress} onClick={onJump}>
|
|
505
|
-
<div className="progress" style={{ width: `${(state.currentTime / totalDuration) * 100}%` }} />
|
|
506
|
-
</div>
|
|
507
|
-
</div>
|
|
508
|
-
</div>
|
|
509
|
-
</PlayerRoot>
|
|
510
|
-
);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
Player.propTypes = {
|
|
514
|
-
frames: PropTypes.array.isRequired,
|
|
515
|
-
options: PropTypes.shape({
|
|
516
|
-
autoplay: PropTypes.bool,
|
|
517
|
-
repeat: PropTypes.bool,
|
|
518
|
-
controls: PropTypes.bool,
|
|
519
|
-
frameBox: PropTypes.object,
|
|
520
|
-
theme: PropTypes.object,
|
|
521
|
-
cols: PropTypes.number,
|
|
522
|
-
rows: PropTypes.number,
|
|
523
|
-
autoplayThreshold: PropTypes.number,
|
|
524
|
-
autoplayRootMargin: PropTypes.string,
|
|
525
|
-
}).isRequired,
|
|
526
|
-
onComplete: PropTypes.func,
|
|
527
|
-
onStart: PropTypes.func,
|
|
528
|
-
onStop: PropTypes.func,
|
|
529
|
-
onPause: PropTypes.func,
|
|
530
|
-
onTick: PropTypes.func,
|
|
531
|
-
onJump: PropTypes.func,
|
|
532
|
-
};
|
package/src/Player.stories.jsx
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export { default as Player } from './demo/player';
|
|
2
|
-
export { default as AutoPlay } from './demo/autoplay';
|
|
3
|
-
export { default as Loop } from './demo/loop';
|
|
4
|
-
export { default as AutoPlayLoop } from './demo/autoplay-loop';
|
|
5
|
-
export { default as RecordingGuide } from './demo/recording-guide';
|
|
6
|
-
export { default as BlockletLogTerminal } from './demo/blocklet-log-terminal';
|
|
7
|
-
|
|
8
|
-
export default {
|
|
9
|
-
title: 'Data Display/Terminal/Player',
|
|
10
|
-
|
|
11
|
-
parameters: {
|
|
12
|
-
docs: {
|
|
13
|
-
description: {
|
|
14
|
-
component:
|
|
15
|
-
'Terminal Player is a react wrapper for `xterm` allowing you to easily render a terminal in the browser.',
|
|
16
|
-
},
|
|
17
|
-
},
|
|
18
|
-
},
|
|
19
|
-
};
|