@arcblock/terminal 3.1.8 → 3.1.10

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/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright 2018-2022 ArcBlock
1
+ Copyright 2018-2025 ArcBlock
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -1,19 +1,144 @@
1
1
  ![@arcblock/terminal](https://www.arcblock.io/.netlify/functions/badge/?text=terminal)
2
2
 
3
- > Terminal Player is a react wrapper for `xterm` allowing you to easily render a terminal in the browser.
3
+ > Terminal Player is a React component for playing back terminal recordings in the browser, built on top of `xterm.js`. Perfect for creating interactive terminal demos, tutorials, and documentation.
4
4
 
5
- ## Usage
5
+ ## Features
6
+
7
+ - 🎬 **Playback Controls**: Play, pause, seek, and loop terminal recordings
8
+ - 🎨 **Customizable Themes**: Full terminal color theme support
9
+ - 📱 **Responsive Design**: Automatically adapts to container size
10
+ - ⚡ **Performance**: Optimized for smooth playback of long recordings
11
+ - 🔧 **Easy Integration**: Simple React component API
12
+ - 📹 **asciinema Compatible**: Works with standard `.cast` recording format
13
+
14
+ ## Installation
6
15
 
7
16
  ```shell
8
17
  yarn add @arcblock/terminal
9
18
  ```
10
19
 
11
- Then:
20
+ ## Quick Start
21
+
22
+ ### Basic Usage
12
23
 
13
24
  ```javascript
14
- import Terminal from '@arcblock/terminal';
25
+ import { Player } from '@arcblock/terminal';
26
+ import recordingData from './my-recording.json';
15
27
 
16
28
  export default function Demo() {
17
- return <Player frames={records} options={config} />;
29
+ return <Player frames={recordingData.records} options={recordingData.config} autoPlay={true} loop={true} />;
30
+ }
31
+ ```
32
+
33
+ ### Player Props
34
+
35
+ | Prop | Type | Default | Description |
36
+ | ------------ | ---------- | ------------ | -------------------------------- |
37
+ | `frames` | `Array` | **required** | Array of terminal frames to play |
38
+ | `options` | `Object` | **required** | Player configuration and theme |
39
+ | `autoPlay` | `Boolean` | `false` | Auto-start playback when mounted |
40
+ | `loop` | `Boolean` | `false` | Loop playback infinitely |
41
+ | `onStart` | `Function` | - | Callback when playback starts |
42
+ | `onPause` | `Function` | - | Callback when playback pauses |
43
+ | `onComplete` | `Function` | - | Callback when playback completes |
44
+
45
+ ## Recording Terminal Sessions
46
+
47
+ ### 1. Install asciinema
48
+
49
+ ```bash
50
+ # macOS
51
+ brew install asciinema
52
+
53
+ # Ubuntu/Debian
54
+ sudo apt install asciinema
55
+
56
+ # pip
57
+ pipx install asciinema
58
+ ```
59
+
60
+ ### 2. Record Your Session
61
+
62
+ ```bash
63
+ # Start recording
64
+ asciinema rec my-demo.cast
65
+
66
+ # ... perform your terminal commands ...
67
+
68
+ # Stop recording (Ctrl+D or type 'exit')
69
+ ```
70
+
71
+ ### 3. Convert to Player Format
72
+
73
+ Visit our **online converter** to transform your `.cast` file:
74
+
75
+ **👉 [https://arcblock.github.io/ux/?path=/story/data-display-terminal-player--recording-guide](https://arcblock.github.io/ux/?path=/story/data-display-terminal-player--recording-guide)**
76
+
77
+ - Drag & drop your `.cast` file
78
+ - Instant conversion and live preview
79
+ - Download the converted JSON file
80
+ - No command-line tools required!
81
+
82
+ ## Advanced Configuration
83
+
84
+ ### Custom Theme
85
+
86
+ ```javascript
87
+ const customOptions = {
88
+ ...recordingData.config,
89
+ theme: {
90
+ background: '#1e1e1e',
91
+ foreground: '#d4d4d4',
92
+ cursor: '#ffffff',
93
+ // ... more colors
94
+ },
95
+ };
96
+
97
+ <Player frames={frames} options={customOptions} />;
98
+ ```
99
+
100
+ ### Playback Options
101
+
102
+ ```javascript
103
+ const playbackOptions = {
104
+ ...recordingData.config,
105
+ frameDelay: 'auto', // or number in ms
106
+ maxIdleTime: 2000, // max delay between frames
107
+ repeat: true, // loop playback
108
+ autoplay: true, // start automatically
109
+ };
110
+ ```
111
+
112
+ ## Examples
113
+
114
+ Check out our **Storybook demos**:
115
+
116
+ - [Basic Player](https://arcblock.github.io/ux/?path=/story/data-display-terminal-player--player)
117
+ - [Auto Play](https://arcblock.github.io/ux/?path=/story/data-display-terminal-player--auto-play)
118
+ - [Loop Mode](https://arcblock.github.io/ux/?path=/story/data-display-terminal-player--loop)
119
+ - [Recording Guide](https://arcblock.github.io/ux/?path=/story/data-display-terminal-player--recording-guide)
120
+
121
+ ## Data Format
122
+
123
+ The Player expects data in this format:
124
+
125
+ ```json
126
+ {
127
+ "config": {
128
+ "cols": 80,
129
+ "rows": 24,
130
+ "frameDelay": "auto",
131
+ "theme": {
132
+ /* terminal colors */
133
+ }
134
+ },
135
+ "records": [
136
+ { "delay": 100, "content": "Hello World!\n" },
137
+ { "delay": 500, "content": "Next command...\n" }
138
+ ]
18
139
  }
19
140
  ```
141
+
142
+ ## License
143
+
144
+ Apache 2.0
package/lib/Player.js CHANGED
@@ -1,17 +1,17 @@
1
1
  import { jsxs as f, jsx as n } from "react/jsx-runtime";
2
- import { useRef as v, useState as O, useReducer as W, useEffect as x } from "react";
3
- import l from "prop-types";
4
- import H from "@arcblock/react-hooks/lib/useInterval";
5
- import { useSize as I } from "ahooks";
2
+ import { useRef as b, useState as A, useReducer as O, useEffect as x } from "react";
3
+ import o from "prop-types";
4
+ import { useSize as W, useInterval as H } from "ahooks";
6
5
  import y from "lodash/isUndefined";
7
6
  import h from "lodash/noop";
8
- import A from "./Terminal.js";
9
- import "./player.css";
10
- import { defaultOptions as U, formatFrames as L, defaultState as X, isFrameAt as b, findFrameAt as F, getPlayerClass as _, getFrameClass as $, formatTime as G } from "./util.js";
11
- function K({ ...P }) {
12
- const a = Object.assign({}, P);
13
- y(a.onComplete) && (a.onComplete = h), y(a.onStart) && (a.onStart = h), y(a.onStop) && (a.onStop = h), y(a.onPause) && (a.onPause = h), y(a.onTick) && (a.onTick = h), y(a.onJump) && (a.onJump = h);
14
- const t = Object.assign({}, U, a.options), { frames: u, totalDuration: m } = L(a.frames, t), C = {
7
+ import I from "./Terminal.js";
8
+ import { PlayerRoot as L } from "./styles.js";
9
+ import { defaultOptions as _, formatFrames as U, defaultState as Y, isFrameAt as N, findFrameAt as F, getPlayerClass as X, getFrameClass as $, formatTime as G } from "./util.js";
10
+ const K = 8;
11
+ function Q({ ...P }) {
12
+ const i = Object.assign({}, P);
13
+ y(i.onComplete) && (i.onComplete = h), y(i.onStart) && (i.onStart = h), y(i.onStop) && (i.onStop = h), y(i.onPause) && (i.onPause = h), y(i.onTick) && (i.onTick = h), y(i.onJump) && (i.onJump = h);
14
+ const t = Object.assign({}, _, i.options), { frames: u, totalDuration: m } = U(i.frames, t), R = {
15
15
  cols: t.cols,
16
16
  rows: t.rows,
17
17
  cursorStyle: t.cursorStyle,
@@ -20,101 +20,81 @@ function K({ ...P }) {
20
20
  lineHeight: t.lineHeight,
21
21
  letterSpacing: t.letterSpacing,
22
22
  allowTransparency: !0,
23
- scrollback: 0
24
- // theme: options.theme,
25
- }, R = (i, e) => {
23
+ scrollback: 0,
24
+ theme: t.theme || {}
25
+ }, C = (a, e) => {
26
26
  switch (e.type) {
27
27
  case "jump":
28
- return { ...i, isPlaying: !1, ...e.payload };
28
+ return { ...a, isPlaying: !1, ...e.payload };
29
29
  case "start":
30
- return { ...i, isStarted: !0, lastTickTime: Date.now() };
30
+ return { ...a, isStarted: !0, lastTickTime: Date.now() };
31
31
  case "play":
32
- return {
33
- ...i,
34
- isPlaying: !0,
35
- lastTickTime: Date.now(),
36
- ...e.payload
37
- };
32
+ return { ...a, isPlaying: !0, lastTickTime: Date.now(), ...e.payload };
38
33
  case "pause":
39
- return { ...i, isPlaying: !1, ...e.payload };
34
+ return { ...a, isPlaying: !1, ...e.payload };
40
35
  case "tickStart":
41
- return {
42
- ...i,
43
- isRendering: !0,
44
- lastTickTime: Date.now(),
45
- ...e.payload
46
- };
36
+ return { ...a, isRendering: !0, lastTickTime: Date.now(), ...e.payload };
47
37
  case "tickEnd":
48
- return {
49
- ...i,
50
- isRendering: !1,
51
- lastTickTime: Date.now(),
52
- ...e.payload
53
- };
38
+ return { ...a, isRendering: !1, lastTickTime: Date.now(), ...e.payload };
54
39
  case "reset":
55
- return {
56
- ...i,
57
- currentFrame: 0,
58
- currentTime: 0,
59
- ...e.payload
60
- };
40
+ return { ...a, currentFrame: 0, currentTime: 0, ...e.payload };
61
41
  default:
62
- return { ...i, lastTickTime: Date.now(), ...e.payload };
42
+ return { ...a, lastTickTime: Date.now(), ...e.payload };
63
43
  }
64
- }, o = v(null), k = v(null), d = v(null), [B, D] = O(0), M = I(document.body), [r, c] = W(R, X), j = M?.width || 0;
44
+ }, c = b(null), k = b(null), d = b(null), [B, D] = A(0), M = W(document.body), [r, l] = O(C, Y), j = M?.width || 0;
65
45
  x(() => {
66
- if (o.current)
46
+ if (c.current)
67
47
  try {
68
- const i = 8.03305785123967, e = d.current.getBoundingClientRect();
48
+ const a = 8.03305785123967, e = d.current.getBoundingClientRect();
69
49
  let s = e.x < 0 ? e.width + e.x : e.width;
70
50
  if (d.current.parentNode) {
71
- const N = d.current.parentNode.getBoundingClientRect();
72
- e.width > N.width && (s = N.width);
51
+ const v = d.current.parentNode.getBoundingClientRect();
52
+ e.width > v.width && (s = v.width);
73
53
  }
74
54
  t.controls && (s -= 12);
75
- const T = Math.ceil(s / i), E = Math.min(Math.max(T, 40), t.cols);
76
- o.current.resize(E, t.rows);
55
+ const T = Math.ceil(s / a), J = Math.min(Math.max(T, 40), t.cols);
56
+ c.current.resize(J, t.rows);
77
57
  } catch {
78
58
  }
79
59
  }, [j, B]);
80
- const S = (i, e) => {
81
- const s = u[i];
82
- s.content && (r.requireReset && o.current.reset(), o.current.write(s.content, () => {
60
+ const w = (a, e) => {
61
+ const s = u[a];
62
+ s && s.content && (r.requireReset && c.current.reset(), c.current.write(s.content, () => {
83
63
  typeof e == "function" && e();
84
64
  }));
85
- }, p = (i) => {
86
- typeof a[i] == "function" && a[i]({ state: r, frames: u, options: t });
87
- }, w = (i) => {
88
- o.current.reset();
89
- const e = F(u, i);
65
+ }, p = (a) => {
66
+ typeof i[a] == "function" && i[a]({ state: r, frames: u, options: t });
67
+ }, S = (a) => {
68
+ c.current.reset();
69
+ const e = F(u, a);
90
70
  for (let s = 0; s < e; s++)
91
- S(s);
92
- }, z = (i) => {
93
- if (!k.current || !o.current || !r.isStarted)
71
+ w(s);
72
+ }, E = (a) => {
73
+ if (!k.current || !c.current || !r.isStarted)
94
74
  return !1;
95
- const e = k.current.getBoundingClientRect().width, s = i.nativeEvent.offsetX, T = Math.floor(m * s / e);
96
- return c({ type: "jump", payload: { currentTime: T } }), w(T), p("onJump"), !1;
97
- }, g = () => (r.isStarted === !1 && (c({ type: "start" }), o.current.reset()), c({ type: "play" }), p("onStart"), !1), q = () => (c({ type: "pause" }), p("onPause"), !1), J = () => (r.currentFrame === u.length - 1 && r.currentTime === m && (c({ type: "reset" }), o.current.reset()), p("onPlay"), g());
75
+ const e = k.current.getBoundingClientRect().width, s = a.nativeEvent.offsetX, T = Math.floor(m * s / e);
76
+ return l({ type: "jump", payload: { currentTime: T } }), S(T), p("onJump"), !1;
77
+ }, g = () => (r.isStarted === !1 && (l({ type: "start" }), c.current.reset()), l({ type: "play" }), p("onStart"), !1), z = () => (l({ type: "pause" }), p("onPause"), !1), q = () => (r.currentFrame === u.length - 1 && r.currentTime === m && (l({ type: "reset" }), c.current.reset()), p("onPlay"), g());
98
78
  return x(() => {
99
- if (o.current) {
79
+ if (c.current) {
100
80
  if (d.current)
101
81
  try {
102
82
  D(d.current.getBoundingClientRect().width);
103
83
  } catch {
104
84
  }
105
- t.autoplay ? g() : w(Math.min(Math.abs(t.thumbnailTime), m));
85
+ t.autoplay ? g() : S(Math.min(Math.abs(t.thumbnailTime), m));
106
86
  }
107
87
  }, []), H(
108
88
  () => {
109
89
  if (r.isRendering)
110
90
  return !1;
111
- const i = Date.now() - r.lastTickTime, e = {};
112
- r.currentTime < m && (e.currentTime = r.currentTime + i), r.currentTime > m && (e.currentTime = m);
113
- const s = b(u, e.currentTime, r.currentFrame);
114
- return r.currentFrame !== -1 && s ? c({ type: "tick", payload: e }) : r.currentFrame === u.length - 1 ? (p("onComplete"), t.repeat ? c({ type: "reset", payload: e }) : (e.currentTime = 0, e.currentFrame = 0, e.requireReset = !0, e.isStarted = !1, c({ type: "pause", payload: e }))) : (b(e.currentTime, r.currentFrame + 1) ? e.currentFrame = r.currentFrame + 1 : e.currentFrame = F(u, e.currentTime), c({ type: "tickStart", payload: e }), S(e.currentFrame, () => (r.requireReset && (e.requireReset = !1), c({ type: "tickEnd", payload: e }), p("onTick"))));
91
+ const a = Date.now() - r.lastTickTime, e = {};
92
+ r.currentTime < m && (e.currentTime = r.currentTime + a), r.currentTime > m && (e.currentTime = m);
93
+ const s = N(u, e.currentTime, r.currentFrame);
94
+ return r.currentFrame !== -1 && s ? l({ type: "tick", payload: e }) : r.currentFrame === u.length - 1 ? (p("onComplete"), t.repeat ? l({ type: "reset", payload: e }) : (e.currentTime = 0, e.currentFrame = 0, e.requireReset = !0, e.isStarted = !1, l({ type: "pause", payload: e }))) : (e?.currentTime != null && r?.currentFrame != null && N(e.currentTime, r.currentFrame + 1) ? e.currentFrame = r.currentFrame + 1 : e.currentFrame = F(u, e.currentTime), l({ type: "tickStart", payload: e }), w(e.currentFrame, () => (r.requireReset && (e.requireReset = !1), l({ type: "tickEnd", payload: e }), p("onTick"))));
115
95
  },
116
- r.isPlaying ? 8 : null
117
- ), t.controls && (t.frameBox.title = null, t.frameBox.type = null, t.frameBox.style = {}, t.theme.background === "transparent" ? t.frameBox.style.background = "black" : t.frameBox.style.background = t.theme.background, t.frameBox.style.padding = "10px", t.frameBox.style.paddingBottom = "40px"), /* @__PURE__ */ f("div", { className: _(t, r), ref: d, children: [
96
+ r.isPlaying ? K : null
97
+ ), t.controls && (t.frameBox.title = null, t.frameBox.type = null, t.frameBox.style = {}, t.theme?.background === "transparent" ? t.frameBox.style.background = "black" : t.theme?.background && (t.frameBox.style.background = t.theme.background), t.frameBox.style.padding = "10px", t.frameBox.style.paddingBottom = "40px"), /* @__PURE__ */ f(L, { className: X(t, r), ref: d, children: [
118
98
  /* @__PURE__ */ n("div", { className: "cover", onClick: g }),
119
99
  /* @__PURE__ */ n("div", { className: "start", onClick: g, children: /* @__PURE__ */ f("svg", { style: { enableBackground: "new 0 0 30 30" }, viewBox: "0 0 30 30", children: [
120
100
  /* @__PURE__ */ n("polygon", { points: "6.583,3.186 5,4.004 5,15 26,15 26.483,14.128 " }),
@@ -132,27 +112,36 @@ function K({ ...P }) {
132
112
  ] }),
133
113
  /* @__PURE__ */ n("div", { className: "title", children: t.frameBox.title || "" })
134
114
  ] }),
135
- /* @__PURE__ */ n("div", { className: "terminal-body", children: /* @__PURE__ */ n(A, { ref: o, options: C }) })
115
+ /* @__PURE__ */ n("div", { className: "terminal-body", children: /* @__PURE__ */ n(I, { ref: c, options: R }) })
136
116
  ] }) }),
137
117
  /* @__PURE__ */ f("div", { className: "controller", children: [
138
- r.isPlaying && /* @__PURE__ */ n("div", { className: "pause", onClick: q, title: "Pause", children: /* @__PURE__ */ n("span", { className: "icon" }) }),
139
- !r.isPlaying && r.isStarted && /* @__PURE__ */ n("div", { className: "play", onClick: J, title: "Play", children: /* @__PURE__ */ n("span", { className: "icon" }) }),
118
+ r.isPlaying && /* @__PURE__ */ n("div", { className: "pause", onClick: z, title: "Pause", children: /* @__PURE__ */ n("span", { className: "icon" }) }),
119
+ !r.isPlaying && r.isStarted && /* @__PURE__ */ n("div", { className: "play", onClick: q, title: "Play", children: /* @__PURE__ */ n("span", { className: "icon" }) }),
140
120
  !r.isPlaying && !r.isStarted && /* @__PURE__ */ n("div", { className: "play", onClick: g, title: "Start", children: /* @__PURE__ */ n("span", { className: "icon" }) }),
141
121
  /* @__PURE__ */ n("div", { className: "timer", children: G(r.currentTime) }),
142
- /* @__PURE__ */ n("div", { className: "progressbar-wrapper", children: /* @__PURE__ */ n("div", { className: "progressbar", ref: k, onClick: z, children: /* @__PURE__ */ n("div", { className: "progress", style: { width: `${r.currentTime / m * 100}%` } }) }) })
122
+ /* @__PURE__ */ n("div", { className: "progressbar-wrapper", children: /* @__PURE__ */ n("div", { className: "progressbar", ref: k, onClick: E, children: /* @__PURE__ */ n("div", { className: "progress", style: { width: `${r.currentTime / m * 100}%` } }) }) })
143
123
  ] })
144
124
  ] });
145
125
  }
146
- K.propTypes = {
147
- frames: l.array.isRequired,
148
- options: l.object.isRequired,
149
- onComplete: l.func,
150
- onStart: l.func,
151
- onStop: l.func,
152
- onPause: l.func,
153
- onTick: l.func,
154
- onJump: l.func
126
+ Q.propTypes = {
127
+ frames: o.array.isRequired,
128
+ options: o.shape({
129
+ autoplay: o.bool,
130
+ repeat: o.bool,
131
+ controls: o.bool,
132
+ frameBox: o.object,
133
+ theme: o.object,
134
+ cols: o.number,
135
+ rows: o.number
136
+ }).isRequired,
137
+ onComplete: o.func,
138
+ onStart: o.func,
139
+ onStop: o.func,
140
+ onPause: o.func,
141
+ onTick: o.func,
142
+ onJump: o.func
155
143
  };
156
144
  export {
157
- K as default
145
+ K as PLAYER_FRAME_DELAY,
146
+ Q as default
158
147
  };
package/lib/Terminal.js CHANGED
@@ -6,8 +6,8 @@ import { WebLinksAddon as a } from "xterm-addon-web-links";
6
6
  import { FitAddon as d } from "xterm-addon-fit";
7
7
  import p from "lodash/debounce";
8
8
  import s from "lodash/noop";
9
- import "./xterm.css";
10
- class f extends m.Component {
9
+ import { TerminalRoot as f } from "./styles.js";
10
+ class x extends m.Component {
11
11
  xterm = null;
12
12
  container = null;
13
13
  componentDidMount() {
@@ -61,13 +61,19 @@ class f extends m.Component {
61
61
  }
62
62
  render() {
63
63
  const { className: t = "", style: e = {} } = this.props, r = ["react-xterm", t].filter(Boolean).join(" ");
64
- return (
65
- // eslint-disable-next-line no-return-assign
66
- /* @__PURE__ */ n("div", { ref: (o) => this.container = o, className: r, style: e })
64
+ return /* @__PURE__ */ n(
65
+ f,
66
+ {
67
+ ref: (o) => {
68
+ this.container = o;
69
+ },
70
+ className: r,
71
+ style: e
72
+ }
67
73
  );
68
74
  }
69
75
  }
70
- f.propTypes = {
76
+ x.propTypes = {
71
77
  onData: i.func,
72
78
  onRender: i.func,
73
79
  options: i.object,
@@ -76,5 +82,5 @@ f.propTypes = {
76
82
  style: i.object
77
83
  };
78
84
  export {
79
- f as default
85
+ x as default
80
86
  };