@orz/ui-asciinema 0.0.0-stage → 0.1.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/LICENSE +29 -0
- package/README.md +112 -2
- package/dist/asciinema-player.d.ts +24 -0
- package/dist/asciinema-player.d.ts.map +1 -0
- package/dist/asciinema-player.js +277 -0
- package/dist/asciinema-player.js.map +1 -0
- package/dist/cast.d.ts +24 -0
- package/dist/cast.d.ts.map +1 -0
- package/dist/cast.js +156 -0
- package/dist/cast.js.map +1 -0
- package/dist/content.d.ts +13 -0
- package/dist/content.d.ts.map +1 -0
- package/dist/content.js +67 -0
- package/dist/content.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/load-player.d.ts +3 -0
- package/dist/load-player.d.ts.map +1 -0
- package/dist/load-player.js +14 -0
- package/dist/load-player.js.map +1 -0
- package/dist/player-theme.d.ts +10 -0
- package/dist/player-theme.d.ts.map +1 -0
- package/dist/player-theme.js +48 -0
- package/dist/player-theme.js.map +1 -0
- package/package.json +55 -3
- package/styles.css +36 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright 2026 the orzui team
|
|
4
|
+
Copyright 2026 the todou team
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,113 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @orz/ui-asciinema
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[asciinema](https://asciinema.org/) recordings (`.cast` files) played in the page, in the colours
|
|
4
|
+
of `@orz/ui`'s terminal theme slot. The package contributes a renderer to `@orz/ui-content`'s
|
|
5
|
+
registry, so a `.cast` file in a viewer or an attachment plays without importing this package from
|
|
6
|
+
either. The engine is [asciinema-player](https://www.npmjs.com/package/asciinema-player) under a
|
|
7
|
+
`MediaControls`, the control bar from `@orz/ui`.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pnpm add @orz/ui-asciinema @orz/ui-content @orz/ui react lucide-react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
`asciinema-player` is a regular dependency: installing this package is the whole setup.
|
|
16
|
+
`@orz/ui-content` is a peer dependency so that the app and every content package share one registry.
|
|
17
|
+
|
|
18
|
+
## CSS
|
|
19
|
+
|
|
20
|
+
```css
|
|
21
|
+
@import "tailwindcss";
|
|
22
|
+
@import "@orz/ui/styles.css";
|
|
23
|
+
@import "@orz/ui-asciinema/styles.css";
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Import it after `@orz/ui/styles.css`. It brings asciinema-player's own stylesheet, imported into the
|
|
27
|
+
`components` layer, and the overrides that let the frame own the corners, the play overlay and the
|
|
28
|
+
fullscreen layout. Like `@orz/ui`'s stylesheet, it registers `@source "./dist"` on its own.
|
|
29
|
+
|
|
30
|
+
## `asciinemaContent`
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
import { ContentRenderers } from "@orz/ui-content";
|
|
34
|
+
import { asciinemaContent } from "@orz/ui-asciinema";
|
|
35
|
+
|
|
36
|
+
const RENDERERS = [asciinemaContent, markdownContent(), codeContent];
|
|
37
|
+
|
|
38
|
+
<ContentRenderers renderers={RENDERERS}>{app}</ContentRenderers>;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
It accepts files named `*.cast` (any case) or typed `application/x-asciicast`, in either placement,
|
|
42
|
+
and never an inline source: a fence stays code. Register it ahead of any renderer of your own that
|
|
43
|
+
would take a `.cast` as text, since the first renderer that accepts wins.
|
|
44
|
+
|
|
45
|
+
A recording plays under a strip holding its `title` (the file name when it has none) and a
|
|
46
|
+
Download link when the source has a `url`. While it loads, the registry's fallback shows; a load
|
|
47
|
+
that fails shows the message and a Retry button. A cast that does not scan — not JSON, an unknown
|
|
48
|
+
version, a malformed event line — goes on to `Next`, the renderers registered after this one, with
|
|
49
|
+
the reason in a muted line under it, so a broken file still offers its download.
|
|
50
|
+
|
|
51
|
+
`CAST_MAX_BYTES` is 16 MiB. A file whose declared `size` is over it is never loaded, and one that
|
|
52
|
+
turns out over it once loaded is not played: both show the fallback link and "Too large to preview
|
|
53
|
+
— download to view." The player parses a whole recording up front, which at the cap already takes
|
|
54
|
+
about a second.
|
|
55
|
+
|
|
56
|
+
## `AsciinemaPlayer`
|
|
57
|
+
|
|
58
|
+
For a surface that plays a recording outside the registry:
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
import { AsciinemaPlayer } from "@orz/ui-asciinema";
|
|
62
|
+
|
|
63
|
+
<AsciinemaPlayer cast={text} />
|
|
64
|
+
<AsciinemaPlayer cast={text} header={<MyTitleStrip />} speed={2} className="max-w-3xl" />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
| Prop | |
|
|
68
|
+
|---|---|
|
|
69
|
+
| `cast` | The recording's text: asciicast v1, v2 or v3. |
|
|
70
|
+
| `header` | Drawn above the terminal. |
|
|
71
|
+
| `speed` | Initial speed, default 1. The bar offers 0.5×, 1×, 1.5×, 2× and 4×. |
|
|
72
|
+
| `loop` | Restart at the end. Default false; the bar has no toggle for it. |
|
|
73
|
+
| `autoPlay` | Start playing once loaded. Default false. |
|
|
74
|
+
| `useEmbeddedTheme` | Use the palette a cast carries in its header instead of the terminal slot. Default false. |
|
|
75
|
+
| `className` | On the frame. |
|
|
76
|
+
|
|
77
|
+
A cast that does not scan shows the reason in place of the player. Until played, the terminal shows
|
|
78
|
+
the recording's final frame under a play button, and play starts from 0:00. The bar holds play and
|
|
79
|
+
pause, the time, a seek track with a tick per marker (hover for its label, click to jump),
|
|
80
|
+
speed, and fullscreen, which takes the whole frame, bar included. Focused on the terminal, the
|
|
81
|
+
player's own keys work: space, `,` and `.` to step, the arrows to seek, `0`–`9` to jump, `[` and
|
|
82
|
+
`]` between markers; `f` toggles the frame's fullscreen.
|
|
83
|
+
|
|
84
|
+
## Theme
|
|
85
|
+
|
|
86
|
+
The terminal's foreground, background and 16 ANSI colours come from the terminal slot
|
|
87
|
+
(`useTerminalTheme()`), converted to `#rrggbb` first, since the player reads nothing else: an oklch
|
|
88
|
+
custom theme plays in its own colours. The cursor and selection tokens have no counterpart in the
|
|
89
|
+
player. A palette embedded in a recording is ignored unless `useEmbeddedTheme` is set.
|
|
90
|
+
|
|
91
|
+
asciinema-player reads its palette, speed and fit only when it is created. A new terminal theme, a
|
|
92
|
+
new speed and entering or leaving fullscreen therefore build the player again, at the same position
|
|
93
|
+
and still playing if it was; the terminal can flash for a frame while it does.
|
|
94
|
+
|
|
95
|
+
## Loading
|
|
96
|
+
|
|
97
|
+
asciinema-player is only reached through `import("asciinema-player")`, from an effect, so a bundler
|
|
98
|
+
splits it (about 90 KB gzip, its VT engine compiled to WASM) into a chunk of its own, fetched the
|
|
99
|
+
first time a page holds a recording, and server rendering never touches it. Until it arrives, a
|
|
100
|
+
placeholder of the terminal's size holds the space and the bar shows the recording's length. An
|
|
101
|
+
import that fails shows the message and a Retry button that imports again.
|
|
102
|
+
|
|
103
|
+
## Licence
|
|
104
|
+
|
|
105
|
+
This package is BSD-3-Clause. asciinema-player, which it depends on and loads at run time, is
|
|
106
|
+
Apache-2.0.
|
|
107
|
+
|
|
108
|
+
## Entry points
|
|
109
|
+
|
|
110
|
+
| Import | Contents |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `@orz/ui-asciinema` | `asciinemaContent`, `CAST_MAX_BYTES`, `AsciinemaPlayer`, `scanCast`, `playerThemeVars` |
|
|
113
|
+
| `@orz/ui-asciinema/styles.css` | asciinema-player's stylesheet and the frame's overrides |
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
export type AsciinemaPlayerProps = {
|
|
3
|
+
/** The cast's text. */
|
|
4
|
+
cast: string;
|
|
5
|
+
/** Drawn above the terminal; the content renderer puts the filename and Download here. */
|
|
6
|
+
header?: ReactNode;
|
|
7
|
+
/** Initial speed. Default 1. */
|
|
8
|
+
speed?: number;
|
|
9
|
+
loop?: boolean;
|
|
10
|
+
autoPlay?: boolean;
|
|
11
|
+
/** Keep the palette a cast carries in its header instead of the terminal slot. Default false. */
|
|
12
|
+
useEmbeddedTheme?: boolean;
|
|
13
|
+
className?: string;
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* An asciinema recording, played by asciinema-player under a control bar of @orz/ui components,
|
|
17
|
+
* in the colours of the terminal theme slot.
|
|
18
|
+
*
|
|
19
|
+
* The player reads its palette once, and can change neither its speed nor its fit afterwards, so
|
|
20
|
+
* a new terminal theme, speed or fullscreen state builds it again, at the same position and still
|
|
21
|
+
* playing if it was.
|
|
22
|
+
*/
|
|
23
|
+
export declare function AsciinemaPlayer(props: AsciinemaPlayerProps): import("react").JSX.Element;
|
|
24
|
+
//# sourceMappingURL=asciinema-player.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asciinema-player.d.ts","sourceRoot":"","sources":["../src/asciinema-player.tsx"],"names":[],"mappings":"AAMA,OAAO,EAEL,KAAK,SAAS,EAOf,MAAM,OAAO,CAAC;AAKf,MAAM,MAAM,oBAAoB,GAAG;IACjC,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,0FAA0F;IAC1F,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,gCAAgC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iGAAiG;IACjG,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAkBF;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,+BAW1D"}
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@orz/ui/components/button";
|
|
3
|
+
import { MediaControls } from "@orz/ui/components/media-controls";
|
|
4
|
+
import { cn } from "@orz/ui/lib/utils";
|
|
5
|
+
import { useTerminalTheme } from "@orz/ui/theme";
|
|
6
|
+
import { Play } from "lucide-react";
|
|
7
|
+
import { useCallback, useEffect, useId, useMemo, useRef, useState, } from "react";
|
|
8
|
+
import { isCastError, scanCast } from "./cast.js";
|
|
9
|
+
import { loadPlayer } from "./load-player.js";
|
|
10
|
+
import { playerThemeVars } from "./player-theme.js";
|
|
11
|
+
/** Keys the player's own handler acts on; after one, the bar reads the position again. */
|
|
12
|
+
const PLAYER_KEYS = /^(?: |,|\.|\[|\]|[0-9]|ArrowLeft|ArrowRight)$/;
|
|
13
|
+
/** A cell's width and height in em, for a placeholder the size the terminal will be. */
|
|
14
|
+
const CELL_WIDTH = 0.6;
|
|
15
|
+
const LINE_HEIGHT = 4 / 3;
|
|
16
|
+
/**
|
|
17
|
+
* An asciinema recording, played by asciinema-player under a control bar of @orz/ui components,
|
|
18
|
+
* in the colours of the terminal theme slot.
|
|
19
|
+
*
|
|
20
|
+
* The player reads its palette once, and can change neither its speed nor its fit afterwards, so
|
|
21
|
+
* a new terminal theme, speed or fullscreen state builds it again, at the same position and still
|
|
22
|
+
* playing if it was.
|
|
23
|
+
*/
|
|
24
|
+
export function AsciinemaPlayer(props) {
|
|
25
|
+
const scan = useMemo(() => scanCast(props.cast), [props.cast]);
|
|
26
|
+
if (isCastError(scan)) {
|
|
27
|
+
return (_jsx("p", { className: cn("text-destructive text-sm", props.className), children: scan.error }));
|
|
28
|
+
}
|
|
29
|
+
// A different cast starts over: no position, no poster state, to carry.
|
|
30
|
+
return _jsx(CastPlayer, { ...props, info: scan }, props.cast);
|
|
31
|
+
}
|
|
32
|
+
function CastPlayer({ cast, info, header, speed: initialSpeed = 1, loop = false, autoPlay = false, useEmbeddedTheme = false, className, }) {
|
|
33
|
+
const id = useId();
|
|
34
|
+
const frameRef = useRef(null);
|
|
35
|
+
const hostRef = useRef(null);
|
|
36
|
+
const probeRef = useRef(null);
|
|
37
|
+
const playerRef = useRef(null);
|
|
38
|
+
const resumeRef = useRef(null);
|
|
39
|
+
const { tokens } = useTerminalTheme();
|
|
40
|
+
const vars = useMemo(() => playerThemeVars(tokens), [tokens]);
|
|
41
|
+
const paletteKey = Object.entries(vars)
|
|
42
|
+
.map(([name, value]) => `${name}:${value}`)
|
|
43
|
+
.join(";");
|
|
44
|
+
const [speed, setSpeed] = useState(initialSpeed);
|
|
45
|
+
useEffect(() => setSpeed(initialSpeed), [initialSpeed]);
|
|
46
|
+
const [fullscreen, setFullscreen] = useState(false);
|
|
47
|
+
const [fullscreenEnabled, setFullscreenEnabled] = useState(false);
|
|
48
|
+
const [load, setLoad] = useState("loading");
|
|
49
|
+
const [attempt, setAttempt] = useState(0);
|
|
50
|
+
const [playing, setPlaying] = useState(false);
|
|
51
|
+
const playingRef = useRef(false);
|
|
52
|
+
playingRef.current = playing;
|
|
53
|
+
// Until the first play or seek, the terminal shows the final frame while the
|
|
54
|
+
// bar shows 0:00, where play starts from.
|
|
55
|
+
const [started, setStarted] = useState(autoPlay);
|
|
56
|
+
const startedRef = useRef(autoPlay);
|
|
57
|
+
const markStarted = useCallback(() => {
|
|
58
|
+
startedRef.current = true;
|
|
59
|
+
setStarted(true);
|
|
60
|
+
}, []);
|
|
61
|
+
const [time, setTime] = useState(0);
|
|
62
|
+
const [duration, setDuration] = useState(info.duration);
|
|
63
|
+
const sync = useCallback(() => {
|
|
64
|
+
const player = playerRef.current;
|
|
65
|
+
if (player === null)
|
|
66
|
+
return;
|
|
67
|
+
const reported = player.getDuration();
|
|
68
|
+
if (typeof reported === "number")
|
|
69
|
+
setDuration(reported);
|
|
70
|
+
if (startedRef.current)
|
|
71
|
+
setTime(player.getCurrentTime());
|
|
72
|
+
}, []);
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
// Read only as triggers. The colours reach the player through the <style>
|
|
75
|
+
// below, which is already in the DOM when it reads them; a new attempt is
|
|
76
|
+
// Retry after a failed import.
|
|
77
|
+
void [paletteKey, attempt];
|
|
78
|
+
const host = hostRef.current;
|
|
79
|
+
if (host === null)
|
|
80
|
+
return;
|
|
81
|
+
let live = true;
|
|
82
|
+
let player;
|
|
83
|
+
const resume = resumeRef.current;
|
|
84
|
+
resumeRef.current = null;
|
|
85
|
+
loadPlayer().then(({ create }) => {
|
|
86
|
+
if (!live)
|
|
87
|
+
return;
|
|
88
|
+
const fresh = resume === null || !resume.started;
|
|
89
|
+
const probe = probeRef.current;
|
|
90
|
+
// A v1 cast is one JSON object; the player reads it from text only
|
|
91
|
+
// when it is pretty-printed, and from the object either way.
|
|
92
|
+
const data = info.version === 1 ? JSON.parse(cast) : cast;
|
|
93
|
+
const built = create({ data }, host, {
|
|
94
|
+
controls: false,
|
|
95
|
+
preload: true,
|
|
96
|
+
fit: fullscreen ? "both" : "width",
|
|
97
|
+
// No `auto/` prefix: a palette embedded in the cast is ignored.
|
|
98
|
+
theme: useEmbeddedTheme ? "auto/orz" : "orz",
|
|
99
|
+
speed,
|
|
100
|
+
loop,
|
|
101
|
+
autoPlay: fresh && autoPlay,
|
|
102
|
+
// Past the end rather than at it: the player draws a poster from
|
|
103
|
+
// the events strictly before its time, which would leave the last
|
|
104
|
+
// one out. Play still starts from 0.
|
|
105
|
+
poster: fresh && !autoPlay ? `npt:${info.duration + 1}` : undefined,
|
|
106
|
+
terminalFontFamily: probe === null ? undefined : getComputedStyle(probe).fontFamily,
|
|
107
|
+
});
|
|
108
|
+
player = built;
|
|
109
|
+
playerRef.current = built;
|
|
110
|
+
built.addEventListener("play", () => {
|
|
111
|
+
if (live)
|
|
112
|
+
markStarted();
|
|
113
|
+
});
|
|
114
|
+
built.addEventListener("playing", () => {
|
|
115
|
+
if (!live)
|
|
116
|
+
return;
|
|
117
|
+
setPlaying(true);
|
|
118
|
+
sync();
|
|
119
|
+
});
|
|
120
|
+
for (const stopped of ["pause", "ended"]) {
|
|
121
|
+
built.addEventListener(stopped, () => {
|
|
122
|
+
if (!live)
|
|
123
|
+
return;
|
|
124
|
+
setPlaying(false);
|
|
125
|
+
sync();
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
setLoad("ready");
|
|
129
|
+
// Not startAt: the player reads that on the uncapped timeline and
|
|
130
|
+
// shows its frame only once playing, where seek takes the time the
|
|
131
|
+
// player reports and draws it at once.
|
|
132
|
+
if (!fresh) {
|
|
133
|
+
if (resume.time > 0)
|
|
134
|
+
void built.seek(resume.time).then(sync);
|
|
135
|
+
if (resume.playing)
|
|
136
|
+
void built.play();
|
|
137
|
+
}
|
|
138
|
+
if (resume?.focused) {
|
|
139
|
+
built.el
|
|
140
|
+
.querySelector(".ap-term-text")
|
|
141
|
+
?.focus({ preventScroll: true });
|
|
142
|
+
}
|
|
143
|
+
}, (error) => {
|
|
144
|
+
if (!live)
|
|
145
|
+
return;
|
|
146
|
+
setLoad(error instanceof Error ? error : new Error(String(error)));
|
|
147
|
+
});
|
|
148
|
+
return () => {
|
|
149
|
+
live = false;
|
|
150
|
+
if (player === undefined)
|
|
151
|
+
return;
|
|
152
|
+
resumeRef.current = {
|
|
153
|
+
time: player.getCurrentTime(),
|
|
154
|
+
playing: playingRef.current,
|
|
155
|
+
started: startedRef.current,
|
|
156
|
+
focused: player.el.contains(document.activeElement),
|
|
157
|
+
};
|
|
158
|
+
player.dispose();
|
|
159
|
+
if (playerRef.current === player)
|
|
160
|
+
playerRef.current = null;
|
|
161
|
+
};
|
|
162
|
+
}, [
|
|
163
|
+
cast,
|
|
164
|
+
paletteKey,
|
|
165
|
+
speed,
|
|
166
|
+
fullscreen,
|
|
167
|
+
useEmbeddedTheme,
|
|
168
|
+
attempt,
|
|
169
|
+
loop,
|
|
170
|
+
autoPlay,
|
|
171
|
+
info.duration,
|
|
172
|
+
info.version,
|
|
173
|
+
markStarted,
|
|
174
|
+
sync,
|
|
175
|
+
]);
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!playing)
|
|
178
|
+
return;
|
|
179
|
+
// The stock control bar's cadence.
|
|
180
|
+
const interval = setInterval(sync, 100);
|
|
181
|
+
return () => clearInterval(interval);
|
|
182
|
+
}, [playing, sync]);
|
|
183
|
+
useEffect(() => {
|
|
184
|
+
setFullscreenEnabled(document.fullscreenEnabled === true);
|
|
185
|
+
const onChange = () => setFullscreen(document.fullscreenElement !== null &&
|
|
186
|
+
document.fullscreenElement === frameRef.current);
|
|
187
|
+
document.addEventListener("fullscreenchange", onChange);
|
|
188
|
+
return () => document.removeEventListener("fullscreenchange", onChange);
|
|
189
|
+
}, []);
|
|
190
|
+
const toggleFullscreen = useCallback(() => {
|
|
191
|
+
const frame = frameRef.current;
|
|
192
|
+
if (frame === null || !document.fullscreenEnabled)
|
|
193
|
+
return;
|
|
194
|
+
// Refused when the call lacks a user gesture, say; the frame just stays as
|
|
195
|
+
// it is.
|
|
196
|
+
const ignore = () => { };
|
|
197
|
+
if (document.fullscreenElement === frame) {
|
|
198
|
+
document.exitFullscreen().catch(ignore);
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
frame.requestFullscreen().catch(ignore);
|
|
202
|
+
}
|
|
203
|
+
}, []);
|
|
204
|
+
// The poster is drawn past the last event, and the player seeks from there
|
|
205
|
+
// incrementally after clearing it: a seek to the end would leave the screen
|
|
206
|
+
// blank. From 0, where the bar says it is, every seek redraws. The player
|
|
207
|
+
// queues its commands, so a seek a key goes on to make runs after this one.
|
|
208
|
+
const leavePoster = (player) => {
|
|
209
|
+
if (startedRef.current)
|
|
210
|
+
return;
|
|
211
|
+
markStarted();
|
|
212
|
+
void player.seek(0);
|
|
213
|
+
};
|
|
214
|
+
const toggle = () => {
|
|
215
|
+
const player = playerRef.current;
|
|
216
|
+
if (player === null)
|
|
217
|
+
return;
|
|
218
|
+
markStarted();
|
|
219
|
+
if (playing)
|
|
220
|
+
void player.pause();
|
|
221
|
+
else
|
|
222
|
+
void player.play();
|
|
223
|
+
};
|
|
224
|
+
const seek = (to) => {
|
|
225
|
+
const player = playerRef.current;
|
|
226
|
+
if (player === null)
|
|
227
|
+
return;
|
|
228
|
+
leavePoster(player);
|
|
229
|
+
setTime(to);
|
|
230
|
+
// Past the end for the end: the player clamps that to its own duration,
|
|
231
|
+
// where a scanned one a float's breadth short would leave out the last
|
|
232
|
+
// frame.
|
|
233
|
+
void player.seek(to >= duration ? duration + 1 : to).then(sync);
|
|
234
|
+
};
|
|
235
|
+
const onKeyDownCapture = (event) => {
|
|
236
|
+
if (event.altKey || event.metaKey || event.ctrlKey)
|
|
237
|
+
return;
|
|
238
|
+
// Caught on the way down, so the player's own handler never sees them: its
|
|
239
|
+
// `f` would fullscreen the terminal alone, without this bar, and `?`, `k`
|
|
240
|
+
// and `m` open overlays this frame does not use.
|
|
241
|
+
if (event.key === "f") {
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
event.stopPropagation();
|
|
244
|
+
toggleFullscreen();
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
if (event.key === "?" || event.key === "k" || event.key === "m") {
|
|
248
|
+
event.stopPropagation();
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
const player = playerRef.current;
|
|
252
|
+
if (PLAYER_KEYS.test(event.key) &&
|
|
253
|
+
player !== null &&
|
|
254
|
+
event.target instanceof Node &&
|
|
255
|
+
player.el.contains(event.target)) {
|
|
256
|
+
leavePoster(player);
|
|
257
|
+
// Its seek and step resolve after the key; while paused nothing else
|
|
258
|
+
// would read the new position.
|
|
259
|
+
setTimeout(sync, 100);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
const css = `[data-orz-cast="${id}"] .ap-player{${Object.entries(vars)
|
|
263
|
+
.map(([name, value]) => `${name}:${value};`)
|
|
264
|
+
.join("")}}`;
|
|
265
|
+
const ready = load === "ready";
|
|
266
|
+
return (_jsxs("div", { ref: frameRef, "data-orz-cast": id, className: cn("overflow-hidden rounded-lg border bg-terminal-background [&:fullscreen]:rounded-none [&:fullscreen]:border-0", className), onKeyDownCapture: onKeyDownCapture, children: [_jsx("style", { children: css }), _jsx("span", { ref: probeRef, "aria-hidden": true, className: "hidden font-mono" }), header, _jsxs("div", { "data-slot": "cast-screen", className: "relative", children: [!ready && (_jsx("div", { className: "grid w-full place-items-center text-terminal-foreground text-sm", style: {
|
|
267
|
+
aspectRatio: `${info.cols * CELL_WIDTH} / ${info.rows * LINE_HEIGHT}`,
|
|
268
|
+
}, children: load instanceof Error && (_jsxs("div", { className: "grid justify-items-center gap-2 p-4 text-center", children: [_jsxs("span", { children: ["Could not load the player: ", load.message] }), _jsx(Button, { type: "button", size: "xs", variant: "outline", onClick: () => {
|
|
269
|
+
setLoad("loading");
|
|
270
|
+
setAttempt((n) => n + 1);
|
|
271
|
+
}, children: "Retry" })] })) })), _jsx("div", { ref: hostRef, "data-slot": "cast-host" }), ready && !started && (_jsx("button", { type: "button", "aria-label": "Play recording",
|
|
272
|
+
// The bar's play button is the keyboard's way in.
|
|
273
|
+
tabIndex: -1, className: "absolute inset-0 grid cursor-pointer place-items-center bg-terminal-background/40 outline-none", onClick: toggle, children: _jsx("span", { className: "grid size-14 place-items-center rounded-full bg-terminal-foreground/85 text-terminal-background shadow-md", children: _jsx(Play, { className: "size-6 translate-x-0.5 fill-current" }) }) }))] }), _jsx(MediaControls, { playing: playing, time: time, duration: duration, markers: info.markers, speed: speed, fullscreen: fullscreenEnabled
|
|
274
|
+
? { active: fullscreen, onToggle: toggleFullscreen }
|
|
275
|
+
: undefined, disabled: !ready, portal: fullscreen ? frameRef.current : undefined, onToggle: toggle, onSeek: seek, onSpeed: setSpeed })] }));
|
|
276
|
+
}
|
|
277
|
+
//# sourceMappingURL=asciinema-player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asciinema-player.js","sourceRoot":"","sources":["../src/asciinema-player.tsx"],"names":[],"mappings":"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"}
|
package/dist/cast.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** What the control bar and the content renderer need to know about a cast, read without playing it. */
|
|
2
|
+
export type CastInfo = {
|
|
3
|
+
version: 1 | 2 | 3;
|
|
4
|
+
cols: number;
|
|
5
|
+
rows: number;
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Seconds, after the idle-time cap: the timeline the player shows. */
|
|
8
|
+
duration: number;
|
|
9
|
+
markers: {
|
|
10
|
+
time: number;
|
|
11
|
+
label: string;
|
|
12
|
+
}[];
|
|
13
|
+
};
|
|
14
|
+
export type CastError = {
|
|
15
|
+
error: string;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Reads a cast's size, title, duration and markers, without parsing its output. asciinema-player
|
|
19
|
+
* never reports markers and swallows its own load errors, so this is also where a cast that would
|
|
20
|
+
* not play is caught. An error names the line it stopped at.
|
|
21
|
+
*/
|
|
22
|
+
export declare function scanCast(text: string): CastInfo | CastError;
|
|
23
|
+
export declare function isCastError(scan: CastInfo | CastError): scan is CastError;
|
|
24
|
+
//# sourceMappingURL=cast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cast.d.ts","sourceRoot":"","sources":["../src/cast.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,MAAM,MAAM,QAAQ,GAAG;IACrB,OAAO,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uEAAuE;IACvE,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AA6H1C;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CA+B3D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,SAAS,GAAG,IAAI,IAAI,SAAS,CAEzE"}
|
package/dist/cast.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
function isRecord(value) {
|
|
2
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3
|
+
}
|
|
4
|
+
function positiveInt(value) {
|
|
5
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
6
|
+
? value
|
|
7
|
+
: undefined;
|
|
8
|
+
}
|
|
9
|
+
function sizeOf(cols, rows) {
|
|
10
|
+
const c = positiveInt(cols);
|
|
11
|
+
const r = positiveInt(rows);
|
|
12
|
+
return c === undefined || r === undefined ? undefined : { cols: c, rows: r };
|
|
13
|
+
}
|
|
14
|
+
function titleOf(header) {
|
|
15
|
+
return typeof header.title === "string" && header.title !== ""
|
|
16
|
+
? { title: header.title }
|
|
17
|
+
: {};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Applies the idle-time cap the way asciinema-player does: every gap between consecutive events
|
|
21
|
+
* — whatever their code, since the player keeps them all in its timeline — and the gap before
|
|
22
|
+
* the first one, is limited to `limit` seconds.
|
|
23
|
+
*/
|
|
24
|
+
function timeline(limit) {
|
|
25
|
+
let previous = 0;
|
|
26
|
+
let shift = 0;
|
|
27
|
+
return (time) => {
|
|
28
|
+
const gap = time - previous;
|
|
29
|
+
previous = time;
|
|
30
|
+
if (limit !== undefined && gap > limit)
|
|
31
|
+
shift += gap - limit;
|
|
32
|
+
return time - shift;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function idleLimit(header) {
|
|
36
|
+
// Coerced the way the player multiplies it into milliseconds: null caps every
|
|
37
|
+
// gap to 0 and "2" to 2 s there, and anything NaN caps none.
|
|
38
|
+
if (header.idle_time_limit === undefined)
|
|
39
|
+
return undefined;
|
|
40
|
+
const limit = Number(header.idle_time_limit);
|
|
41
|
+
return Number.isNaN(limit) ? undefined : limit;
|
|
42
|
+
}
|
|
43
|
+
function scanV1(header) {
|
|
44
|
+
const size = sizeOf(header.width, header.height);
|
|
45
|
+
if (size === undefined)
|
|
46
|
+
return { error: "Line 1: missing or invalid size" };
|
|
47
|
+
if (!Array.isArray(header.stdout)) {
|
|
48
|
+
return { error: "Line 1: missing stdout" };
|
|
49
|
+
}
|
|
50
|
+
// The player ignores a v1 header's idle_time_limit, so the cap is not applied here either.
|
|
51
|
+
let time = 0;
|
|
52
|
+
for (const [index, frame] of header.stdout.entries()) {
|
|
53
|
+
if (!Array.isArray(frame) || typeof frame[0] !== "number") {
|
|
54
|
+
return {
|
|
55
|
+
error: `Line 1: stdout frame ${index + 1} is not [delay, data]`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
time += frame[0];
|
|
59
|
+
}
|
|
60
|
+
if (header.stdout.length === 0)
|
|
61
|
+
return { error: "No events" };
|
|
62
|
+
return {
|
|
63
|
+
version: 1,
|
|
64
|
+
...size,
|
|
65
|
+
...titleOf(header),
|
|
66
|
+
duration: time,
|
|
67
|
+
markers: [],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
function scanLines(version, header, lines) {
|
|
71
|
+
const term = isRecord(header.term) ? header.term : {};
|
|
72
|
+
const size = version === 2
|
|
73
|
+
? sizeOf(header.width, header.height)
|
|
74
|
+
: sizeOf(term.cols, term.rows);
|
|
75
|
+
if (size === undefined)
|
|
76
|
+
return { error: "Line 1: missing or invalid size" };
|
|
77
|
+
const capped = timeline(idleLimit(header));
|
|
78
|
+
const markers = [];
|
|
79
|
+
let absolute = 0;
|
|
80
|
+
let duration;
|
|
81
|
+
for (let i = 1; i < lines.length; i++) {
|
|
82
|
+
const line = (lines[i] ?? "").trim();
|
|
83
|
+
if (line === "" || (version === 3 && line.startsWith("#")))
|
|
84
|
+
continue;
|
|
85
|
+
let event;
|
|
86
|
+
try {
|
|
87
|
+
event = JSON.parse(line);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
event = undefined;
|
|
91
|
+
}
|
|
92
|
+
if (!Array.isArray(event) ||
|
|
93
|
+
typeof event[0] !== "number" ||
|
|
94
|
+
typeof event[1] !== "string") {
|
|
95
|
+
return { error: `Line ${i + 1}: not an event` };
|
|
96
|
+
}
|
|
97
|
+
// v2 times are absolute, v3 times are the interval since the previous event.
|
|
98
|
+
absolute = version === 2 ? event[0] : absolute + event[0];
|
|
99
|
+
const time = capped(absolute);
|
|
100
|
+
duration = time;
|
|
101
|
+
if (event[1] === "m") {
|
|
102
|
+
markers.push({
|
|
103
|
+
time,
|
|
104
|
+
label: typeof event[2] === "string" ? event[2] : "",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (duration === undefined)
|
|
109
|
+
return { error: "No events" };
|
|
110
|
+
return { version, ...size, ...titleOf(header), duration, markers };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Reads a cast's size, title, duration and markers, without parsing its output. asciinema-player
|
|
114
|
+
* never reports markers and swallows its own load errors, so this is also where a cast that would
|
|
115
|
+
* not play is caught. An error names the line it stopped at.
|
|
116
|
+
*/
|
|
117
|
+
export function scanCast(text) {
|
|
118
|
+
if (text.trim() === "")
|
|
119
|
+
return { error: "Empty recording" };
|
|
120
|
+
const lines = text.split("\n");
|
|
121
|
+
const first = (lines[0] ?? "").trim();
|
|
122
|
+
let header;
|
|
123
|
+
try {
|
|
124
|
+
header = JSON.parse(first);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// v1 is one JSON object, usually pretty-printed over many lines.
|
|
128
|
+
try {
|
|
129
|
+
header = JSON.parse(text);
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return { error: "Line 1: not JSON" };
|
|
133
|
+
}
|
|
134
|
+
if (!isRecord(header) || header.version !== 1) {
|
|
135
|
+
return { error: "Line 1: not an asciicast header" };
|
|
136
|
+
}
|
|
137
|
+
return scanV1(header);
|
|
138
|
+
}
|
|
139
|
+
if (!isRecord(header))
|
|
140
|
+
return { error: "Line 1: not an asciicast header" };
|
|
141
|
+
switch (header.version) {
|
|
142
|
+
case 1:
|
|
143
|
+
return scanV1(header);
|
|
144
|
+
case 2:
|
|
145
|
+
case 3:
|
|
146
|
+
return scanLines(header.version, header, lines);
|
|
147
|
+
default:
|
|
148
|
+
return {
|
|
149
|
+
error: `Line 1: unsupported asciicast version ${JSON.stringify(header.version)}`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
export function isCastError(scan) {
|
|
154
|
+
return "error" in scan;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=cast.js.map
|
package/dist/cast.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cast.js","sourceRoot":"","sources":["../src/cast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type ContentRenderer } from "@orz/ui-content";
|
|
2
|
+
/**
|
|
3
|
+
* Above this a cast is offered as a download instead: the player parses the whole recording up
|
|
4
|
+
* front, which at 16 MiB already takes about a second.
|
|
5
|
+
*/
|
|
6
|
+
export declare const CAST_MAX_BYTES: number;
|
|
7
|
+
/**
|
|
8
|
+
* The registry entry for asciinema recordings: a `.cast` file, or one typed
|
|
9
|
+
* `application/x-asciicast`, in either placement. Register it ahead of any renderer that would
|
|
10
|
+
* take a `.cast` as text.
|
|
11
|
+
*/
|
|
12
|
+
export declare const asciinemaContent: ContentRenderer;
|
|
13
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../src/content.tsx"],"names":[],"mappings":"AACA,OAAO,EAGL,KAAK,eAAe,EAGrB,MAAM,iBAAiB,CAAC;AAMzB;;;GAGG;AACH,eAAO,MAAM,cAAc,QAAmB,CAAC;AAsH/C;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,EAAE,eAK9B,CAAC"}
|
package/dist/content.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button } from "@orz/ui/components/button";
|
|
3
|
+
import { ContentFallback, useSourceText, } from "@orz/ui-content";
|
|
4
|
+
import { Download } from "lucide-react";
|
|
5
|
+
import { useMemo } from "react";
|
|
6
|
+
import { AsciinemaPlayer } from "./asciinema-player.js";
|
|
7
|
+
import { isCastError, scanCast } from "./cast.js";
|
|
8
|
+
/**
|
|
9
|
+
* Above this a cast is offered as a download instead: the player parses the whole recording up
|
|
10
|
+
* front, which at 16 MiB already takes about a second.
|
|
11
|
+
*/
|
|
12
|
+
export const CAST_MAX_BYTES = 16 * 1024 * 1024;
|
|
13
|
+
function isCast(source) {
|
|
14
|
+
if (source.kind === "inline")
|
|
15
|
+
return false;
|
|
16
|
+
return (/\.cast$/i.test(source.name) ||
|
|
17
|
+
source.mime?.toLowerCase() === "application/x-asciicast");
|
|
18
|
+
}
|
|
19
|
+
function TooLargeCast({ source, placement, className }) {
|
|
20
|
+
return (_jsxs("div", { className: className, children: [_jsx(ContentFallback, { source: source, placement: placement }), " ", _jsx("span", { children: "Too large to preview \u2014 download to view." })] }));
|
|
21
|
+
}
|
|
22
|
+
function CastHeader({ source, title }) {
|
|
23
|
+
return (_jsxs("div", { className: "flex items-center gap-2 border-b bg-card py-1 pr-1 pl-3 text-card-foreground text-xs", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", title: source.name, children: title ?? source.name }), source.url !== undefined && (_jsx(Button, { asChild: true, size: "xs", variant: "ghost", children: _jsxs("a", { href: source.url, download: source.name, children: [_jsx(Download, {}), "Download"] }) }))] }));
|
|
24
|
+
}
|
|
25
|
+
function LoadedCast({ source, placement, className, Next }) {
|
|
26
|
+
const text = useSourceText(source);
|
|
27
|
+
const scan = useMemo(() => text.status === "ready" && text.text.length <= CAST_MAX_BYTES
|
|
28
|
+
? scanCast(text.text)
|
|
29
|
+
: undefined, [text.status, text.text]);
|
|
30
|
+
if (text.status === "loading") {
|
|
31
|
+
return (_jsx(ContentFallback, { source: source, placement: placement, className: className }));
|
|
32
|
+
}
|
|
33
|
+
if (text.status === "error") {
|
|
34
|
+
return (_jsxs("div", { className: className, children: [_jsx(ContentFallback, { source: source, placement: placement }), " ", _jsx("span", { children: text.error.message }), " ", _jsx(Button, { size: "xs", variant: "outline", onClick: text.retry, children: "Retry" })] }));
|
|
35
|
+
}
|
|
36
|
+
// UTF-16 units, not bytes; a cast is JSON-escaped and almost all ASCII.
|
|
37
|
+
if (text.text.length > CAST_MAX_BYTES) {
|
|
38
|
+
return (_jsx(TooLargeCast, { source: source, placement: placement, className: className }));
|
|
39
|
+
}
|
|
40
|
+
if (scan === undefined || isCastError(scan)) {
|
|
41
|
+
// A broken recording still offers what the renderers after this one
|
|
42
|
+
// make of the file: a download link, at least.
|
|
43
|
+
return (_jsxs("div", { className: className, children: [_jsx(Next, { source: source, placement: placement }), scan !== undefined && (_jsx("p", { className: "text-muted-foreground text-xs", children: scan.error }))] }));
|
|
44
|
+
}
|
|
45
|
+
return (_jsx(AsciinemaPlayer, { cast: text.text, header: _jsx(CastHeader, { source: source, title: scan.title }), className: className }));
|
|
46
|
+
}
|
|
47
|
+
function Cast(props) {
|
|
48
|
+
const { source, placement, className } = props;
|
|
49
|
+
if (source.kind !== "file")
|
|
50
|
+
return null;
|
|
51
|
+
if (source.size !== undefined && source.size > CAST_MAX_BYTES) {
|
|
52
|
+
return (_jsx(TooLargeCast, { source: source, placement: placement, className: className }));
|
|
53
|
+
}
|
|
54
|
+
return _jsx(LoadedCast, { ...props, source: source });
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* The registry entry for asciinema recordings: a `.cast` file, or one typed
|
|
58
|
+
* `application/x-asciicast`, in either placement. Register it ahead of any renderer that would
|
|
59
|
+
* take a `.cast` as text.
|
|
60
|
+
*/
|
|
61
|
+
export const asciinemaContent = {
|
|
62
|
+
name: "asciinema",
|
|
63
|
+
accepts: isCast,
|
|
64
|
+
display: "block",
|
|
65
|
+
Component: Cast,
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../src/content.tsx"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { AsciinemaPlayer, type AsciinemaPlayerProps, } from "./asciinema-player.tsx";
|
|
2
|
+
export { type CastError, type CastInfo, scanCast } from "./cast.ts";
|
|
3
|
+
export { asciinemaContent, CAST_MAX_BYTES } from "./content.tsx";
|
|
4
|
+
export { playerThemeVars } from "./player-theme.ts";
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,EACf,KAAK,oBAAoB,GAC1B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,KAAK,SAAS,EAAE,KAAK,QAAQ,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,eAAe,GAEhB,MAAM,uBAAwB,CAAC;AAChC,OAAO,EAAiC,QAAQ,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,cAAe,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-player.d.ts","sourceRoot":"","sources":["../src/load-player.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,YAAY,GAAG,cAAc,kBAAkB,CAAC,CAAC;AAO7D,wBAAgB,UAAU,IAAI,OAAO,CAAC,YAAY,CAAC,CAQlD"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// The player needs canvas, requestAnimationFrame and ResizeObserver at import
|
|
2
|
+
// time and weighs ~90 KB gzip; it is only ever reached through this dynamic
|
|
3
|
+
// import, from an effect, so neither SSR nor a page without a cast loads it.
|
|
4
|
+
let loaded;
|
|
5
|
+
export function loadPlayer() {
|
|
6
|
+
loaded ??= import("asciinema-player").catch((error) => {
|
|
7
|
+
// A chunk that failed once (a dropped connection) would stay rejected in
|
|
8
|
+
// the memo, and Retry would get the same rejection back.
|
|
9
|
+
loaded = undefined;
|
|
10
|
+
throw error;
|
|
11
|
+
});
|
|
12
|
+
return loaded;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=load-player.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-player.js","sourceRoot":"","sources":["../src/load-player.ts"],"names":[],"mappings":"AAEA,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,IAAI,MAAyC,CAAC;AAE9C,MAAM,UAAU,UAAU;IACxB,MAAM,KAAK,MAAM,CAAC,kBAAkB,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;QAC7D,yEAAyE;QACzE,yDAAyD;QACzD,MAAM,GAAG,SAAS,CAAC;QACnB,MAAM,KAAK,CAAC;IACd,CAAC,CAAC,CAAC;IACH,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TerminalTokens } from "@orz/ui/theme";
|
|
2
|
+
/** The player's `--term-color-0`…`15`: normal 0–7, then bright 8–15, as `xtermTheme` orders them. */
|
|
3
|
+
export declare const ANSI_ORDER: readonly ["ansi-black", "ansi-red", "ansi-green", "ansi-yellow", "ansi-blue", "ansi-magenta", "ansi-cyan", "ansi-white", "ansi-bright-black", "ansi-bright-red", "ansi-bright-green", "ansi-bright-yellow", "ansi-bright-blue", "ansi-bright-magenta", "ansi-bright-cyan", "ansi-bright-white"];
|
|
4
|
+
/**
|
|
5
|
+
* Terminal tokens as asciinema-player's custom properties, each `#rrggbb`. A token that does not
|
|
6
|
+
* parse is left out, so the player keeps its own default for that colour. The cursor and
|
|
7
|
+
* selection tokens have no counterpart in the player.
|
|
8
|
+
*/
|
|
9
|
+
export declare function playerThemeVars(tokens: TerminalTokens): Record<string, string>;
|
|
10
|
+
//# sourceMappingURL=player-theme.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-theme.d.ts","sourceRoot":"","sources":["../src/player-theme.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,eAAe,CAAC;AAEvE,qGAAqG;AACrG,eAAO,MAAM,UAAU,iSAiB0B,CAAC;AAWlD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,cAAc,GACrB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAYxB"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { formatColor, parseColor } from "@orz/ui/lib/color";
|
|
2
|
+
/** The player's `--term-color-0`…`15`: normal 0–7, then bright 8–15, as `xtermTheme` orders them. */
|
|
3
|
+
export const ANSI_ORDER = [
|
|
4
|
+
"ansi-black",
|
|
5
|
+
"ansi-red",
|
|
6
|
+
"ansi-green",
|
|
7
|
+
"ansi-yellow",
|
|
8
|
+
"ansi-blue",
|
|
9
|
+
"ansi-magenta",
|
|
10
|
+
"ansi-cyan",
|
|
11
|
+
"ansi-white",
|
|
12
|
+
"ansi-bright-black",
|
|
13
|
+
"ansi-bright-red",
|
|
14
|
+
"ansi-bright-green",
|
|
15
|
+
"ansi-bright-yellow",
|
|
16
|
+
"ansi-bright-blue",
|
|
17
|
+
"ansi-bright-magenta",
|
|
18
|
+
"ansi-bright-cyan",
|
|
19
|
+
"ansi-bright-white",
|
|
20
|
+
];
|
|
21
|
+
// The player accepts only #rgb / #rrggbb and silently falls back on anything
|
|
22
|
+
// else, so an oklch custom theme would come out in its default colours.
|
|
23
|
+
function hex(token) {
|
|
24
|
+
const parsed = parseColor(token);
|
|
25
|
+
return parsed === null
|
|
26
|
+
? undefined
|
|
27
|
+
: formatColor(parsed.color, "hex", { alpha: false });
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Terminal tokens as asciinema-player's custom properties, each `#rrggbb`. A token that does not
|
|
31
|
+
* parse is left out, so the player keeps its own default for that colour. The cursor and
|
|
32
|
+
* selection tokens have no counterpart in the player.
|
|
33
|
+
*/
|
|
34
|
+
export function playerThemeVars(tokens) {
|
|
35
|
+
const vars = {};
|
|
36
|
+
const put = (name, token) => {
|
|
37
|
+
const value = hex(token);
|
|
38
|
+
if (value !== undefined)
|
|
39
|
+
vars[name] = value;
|
|
40
|
+
};
|
|
41
|
+
put("--term-color-foreground", tokens["terminal-foreground"]);
|
|
42
|
+
put("--term-color-background", tokens["terminal-background"]);
|
|
43
|
+
ANSI_ORDER.forEach((name, index) => {
|
|
44
|
+
put(`--term-color-${index}`, tokens[name]);
|
|
45
|
+
});
|
|
46
|
+
return vars;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=player-theme.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"player-theme.js","sourceRoot":"","sources":["../src/player-theme.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAG5D,qGAAqG;AACrG,MAAM,CAAC,MAAM,UAAU,GAAG;IACxB,YAAY;IACZ,UAAU;IACV,YAAY;IACZ,aAAa;IACb,WAAW;IACX,cAAc;IACd,WAAW;IACX,YAAY;IACZ,mBAAmB;IACnB,iBAAiB;IACjB,mBAAmB;IACnB,oBAAoB;IACpB,kBAAkB;IAClB,qBAAqB;IACrB,kBAAkB;IAClB,mBAAmB;CAC4B,CAAC;AAElD,6EAA6E;AAC7E,wEAAwE;AACxE,SAAS,GAAG,CAAC,KAAa;IACxB,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC;IACjC,OAAO,MAAM,KAAK,IAAI;QACpB,CAAC,CAAC,SAAS;QACX,CAAC,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;AACzD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAC7B,MAAsB;IAEtB,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,MAAM,GAAG,GAAG,CAAC,IAAY,EAAE,KAAa,EAAE,EAAE;QAC1C,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,KAAK,KAAK,SAAS;YAAE,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IAC9C,CAAC,CAAC;IACF,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC9D,GAAG,CAAC,yBAAyB,EAAE,MAAM,CAAC,qBAAqB,CAAC,CAAC,CAAC;IAC9D,UAAU,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE;QACjC,GAAG,CAAC,gBAAgB,KAAK,EAAE,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACd,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orz/ui-asciinema",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "asciinema recordings played in the active @orz/ui terminal theme, contributed to the @orz/ui-content registry",
|
|
5
|
+
"license": "BSD-3-Clause",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/orzFly/ui.git",
|
|
9
|
+
"directory": "packages/ui-asciinema"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"sideEffects": [
|
|
13
|
+
"**/*.css"
|
|
14
|
+
],
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"@orz/source": "./src/index.ts",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"default": "./dist/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./styles.css": "./styles.css"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist",
|
|
25
|
+
"styles.css"
|
|
26
|
+
],
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"lucide-react": "^1",
|
|
32
|
+
"react": "^19",
|
|
33
|
+
"@orz/ui-content": "^0.1.0",
|
|
34
|
+
"@orz/ui": "^0.1.0"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"asciinema-player": "^3.17.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@testing-library/react": "^16.3.2",
|
|
41
|
+
"@types/react": "^19.2.18",
|
|
42
|
+
"@types/react-dom": "^19.2.4",
|
|
43
|
+
"happy-dom": "^21.0.3",
|
|
44
|
+
"lucide-react": "^1.31.0",
|
|
45
|
+
"radix-ui": "^1.6.7",
|
|
46
|
+
"react": "^19.2.8",
|
|
47
|
+
"react-dom": "^19.2.8",
|
|
48
|
+
"vitest": "^5.0.5",
|
|
49
|
+
"@orz/ui": "0.1.0",
|
|
50
|
+
"@orz/themes": "0.1.0",
|
|
51
|
+
"@orz/ui-content": "0.1.0"
|
|
52
|
+
},
|
|
53
|
+
"scripts": {
|
|
54
|
+
"build": "tsc -p tsconfig.build.json",
|
|
55
|
+
"typecheck": "tsc --noEmit",
|
|
56
|
+
"test": "vitest run"
|
|
57
|
+
}
|
|
6
58
|
}
|
package/styles.css
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* Into the components layer: unlayered, the player's rules would beat every
|
|
2
|
+
override below and every utility, whatever their specificity. */
|
|
3
|
+
@import "asciinema-player/dist/bundle/asciinema-player.css" layer(components);
|
|
4
|
+
|
|
5
|
+
/* Tailwind resolves an imported file's @source against that file, so a
|
|
6
|
+
consumer importing this stylesheet scans the classes in the package's
|
|
7
|
+
shipped JS without writing an @source of its own. */
|
|
8
|
+
@source "./dist";
|
|
9
|
+
|
|
10
|
+
@layer components {
|
|
11
|
+
/* The frame draws the corners and the play overlay. */
|
|
12
|
+
[data-orz-cast] div.ap-wrapper div.ap-player {
|
|
13
|
+
border-radius: 0;
|
|
14
|
+
max-width: none;
|
|
15
|
+
}
|
|
16
|
+
[data-orz-cast] .ap-player .ap-overlay-start {
|
|
17
|
+
display: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* Fullscreen keeps the bar: the terminal takes what is left and the player
|
|
21
|
+
fits it both ways. */
|
|
22
|
+
[data-orz-cast]:fullscreen {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
}
|
|
26
|
+
[data-orz-cast]:fullscreen > [data-slot="cast-screen"] {
|
|
27
|
+
flex: 1;
|
|
28
|
+
min-height: 0;
|
|
29
|
+
}
|
|
30
|
+
[data-orz-cast]:fullscreen [data-slot="cast-host"] {
|
|
31
|
+
height: 100%;
|
|
32
|
+
}
|
|
33
|
+
[data-orz-cast]:fullscreen div.ap-wrapper {
|
|
34
|
+
align-items: center;
|
|
35
|
+
}
|
|
36
|
+
}
|