@brett_lamy/docstream 1.6.1 → 1.7.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.
Files changed (39) hide show
  1. package/README.md +31 -4
  2. package/dist/index.d.ts +2 -2
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +1 -1
  5. package/dist/index.js.map +1 -1
  6. package/dist/replay/ReplayPlayer.d.ts +64 -0
  7. package/dist/replay/ReplayPlayer.d.ts.map +1 -0
  8. package/dist/replay/ReplayPlayer.js +211 -0
  9. package/dist/replay/ReplayPlayer.js.map +1 -0
  10. package/dist/replay/ReplayPreview.d.ts +24 -12
  11. package/dist/replay/ReplayPreview.d.ts.map +1 -1
  12. package/dist/replay/ReplayPreview.js +37 -32
  13. package/dist/replay/ReplayPreview.js.map +1 -1
  14. package/dist/replay/ReplayStageImpl.js +1 -1
  15. package/dist/replay/ReplayStageImpl.js.map +1 -1
  16. package/dist/replay/demo/index.d.ts +3 -0
  17. package/dist/replay/demo/index.d.ts.map +1 -0
  18. package/dist/replay/demo/index.js +66 -0
  19. package/dist/replay/demo/index.js.map +1 -0
  20. package/dist/replay/index.d.ts +2 -2
  21. package/dist/replay/index.d.ts.map +1 -1
  22. package/dist/replay/index.js +1 -1
  23. package/dist/replay/index.js.map +1 -1
  24. package/dist/replay/types.js +1 -1
  25. package/dist/styles.css +565 -75
  26. package/package.json +7 -1
  27. package/src/index.ts +2 -1
  28. package/src/replay/ReplayPlayer.tsx +430 -0
  29. package/src/replay/ReplayPreview.tsx +111 -85
  30. package/src/replay/ReplayStageImpl.tsx +1 -1
  31. package/src/replay/demo/index.ts +67 -0
  32. package/src/replay/index.ts +6 -1
  33. package/src/replay/types.ts +1 -1
  34. package/src/styles.css +565 -75
  35. package/dist/replay/NativeReplay.d.ts +0 -9
  36. package/dist/replay/NativeReplay.d.ts.map +0 -1
  37. package/dist/replay/NativeReplay.js +0 -175
  38. package/dist/replay/NativeReplay.js.map +0 -1
  39. package/src/replay/NativeReplay.tsx +0 -240
@@ -1,175 +0,0 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- /* The rrweb player. Loaded on demand by ReplayPreview, so pages that only embed Loop QA
3
- iframes (the markdown `{% embed %}` case) never download rrweb or its stylesheet. */
4
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
5
- import { EventType, Replayer } from 'rrweb';
6
- import { sanitizeReplayEvent } from './sanitize.js';
7
- import 'rrweb/dist/style.css';
8
- const DEFAULT_SPEEDS = [0.5, 1, 2, 4, 8];
9
- const SKIP_IDLE_THRESHOLD_MS = 5_000;
10
- export default function NativeReplay({ events, autoplay = false, loop = false, speeds = DEFAULT_SPEEDS, }) {
11
- const stageRef = useRef(null);
12
- const playerRef = useRef(null);
13
- const replayerRef = useRef(null);
14
- const fedCountRef = useRef(0);
15
- const [playing, setPlaying] = useState(false);
16
- const [speed, setSpeed] = useState(1);
17
- const [dims, setDims] = useState(null);
18
- const [scale, setScale] = useState(1);
19
- const [currentTime, setCurrentTime] = useState(0);
20
- const [totalTime, setTotalTime] = useState(0);
21
- const [scrubbing, setScrubbing] = useState(null);
22
- const speedRef = useRef(speed);
23
- const loopRef = useRef(loop);
24
- const autoplayRef = useRef(autoplay);
25
- const prepared = useMemo(() => events
26
- .map((event) => sanitizeReplayEvent(event))
27
- .sort((a, b) => a.timestamp - b.timestamp), [events]);
28
- const hasSnapshot = prepared.some((event) => event.type === EventType.FullSnapshot);
29
- useEffect(() => {
30
- if (replayerRef.current && prepared.length < fedCountRef.current) {
31
- try {
32
- replayerRef.current.destroy();
33
- }
34
- catch {
35
- // Ignore stale player teardown.
36
- }
37
- replayerRef.current = null;
38
- fedCountRef.current = 0;
39
- }
40
- if (!hasSnapshot || !playerRef.current)
41
- return;
42
- if (!replayerRef.current) {
43
- const replayer = new Replayer(prepared, {
44
- root: playerRef.current,
45
- liveMode: false,
46
- speed: speedRef.current,
47
- skipInactive: true,
48
- inactivePeriodThreshold: SKIP_IDLE_THRESHOLD_MS,
49
- mouseTail: false,
50
- showWarning: false,
51
- });
52
- replayer.on('resize', (payload) => {
53
- const size = payload;
54
- if (size.width && size.height)
55
- setDims({ w: size.width, h: size.height });
56
- });
57
- replayer.on('finish', () => {
58
- if (loopRef.current) {
59
- replayer.play(0);
60
- setPlaying(true);
61
- }
62
- else {
63
- setPlaying(false);
64
- }
65
- });
66
- replayer.pause(0);
67
- replayerRef.current = replayer;
68
- fedCountRef.current = prepared.length;
69
- if (autoplayRef.current) {
70
- replayer.play(0);
71
- setPlaying(true);
72
- }
73
- }
74
- else if (prepared.length > fedCountRef.current) {
75
- for (let i = fedCountRef.current; i < prepared.length; i++) {
76
- replayerRef.current.addEvent(prepared[i]);
77
- }
78
- fedCountRef.current = prepared.length;
79
- }
80
- }, [hasSnapshot, prepared]);
81
- useEffect(() => () => {
82
- try {
83
- replayerRef.current?.destroy();
84
- }
85
- catch {
86
- // Ignore unmount teardown errors from rrweb.
87
- }
88
- replayerRef.current = null;
89
- fedCountRef.current = 0;
90
- }, []);
91
- useEffect(() => {
92
- speedRef.current = speed;
93
- replayerRef.current?.setConfig({ speed });
94
- }, [speed]);
95
- useEffect(() => {
96
- loopRef.current = loop;
97
- }, [loop]);
98
- useEffect(() => {
99
- autoplayRef.current = autoplay;
100
- }, [autoplay]);
101
- const recalcScale = useCallback(() => {
102
- if (!dims || !stageRef.current || dims.w <= 0)
103
- return;
104
- setScale(Math.min(1, stageRef.current.clientWidth / dims.w));
105
- }, [dims]);
106
- useEffect(() => {
107
- recalcScale();
108
- window.addEventListener('resize', recalcScale);
109
- return () => window.removeEventListener('resize', recalcScale);
110
- }, [recalcScale]);
111
- useEffect(() => {
112
- if (!hasSnapshot)
113
- return;
114
- const interval = window.setInterval(() => {
115
- const replayer = replayerRef.current;
116
- if (!replayer)
117
- return;
118
- try {
119
- const total = replayer.getMetaData().totalTime;
120
- const current = Math.max(0, Math.min(replayer.getCurrentTime(), total));
121
- setTotalTime(total);
122
- if (scrubbing === null)
123
- setCurrentTime(current);
124
- }
125
- catch {
126
- // rrweb can briefly be unready while a full snapshot is applied.
127
- }
128
- }, 200);
129
- return () => window.clearInterval(interval);
130
- }, [hasSnapshot, scrubbing]);
131
- const seek = useCallback((value) => {
132
- const replayer = replayerRef.current;
133
- if (!replayer)
134
- return;
135
- const next = Math.max(0, Math.min(value, totalTime));
136
- if (playing)
137
- replayer.play(next);
138
- else
139
- replayer.pause(next);
140
- setCurrentTime(next);
141
- }, [playing, totalTime]);
142
- const toggle = () => {
143
- const replayer = replayerRef.current;
144
- if (!replayer)
145
- return;
146
- if (playing) {
147
- replayer.pause();
148
- setPlaying(false);
149
- return;
150
- }
151
- let offset = currentTime;
152
- try {
153
- if (offset >= replayer.getMetaData().totalTime - 1)
154
- offset = 0;
155
- }
156
- catch {
157
- offset = 0;
158
- }
159
- replayer.play(offset);
160
- setPlaying(true);
161
- };
162
- const restart = () => {
163
- replayerRef.current?.play(0);
164
- setCurrentTime(0);
165
- setPlaying(true);
166
- };
167
- if (!hasSnapshot) {
168
- return (_jsx("div", { className: "replay-preview__empty", children: events.length ? `Captured ${events.length} events, but no full DOM snapshot is available.` : 'Replay data is not available yet.' }));
169
- }
170
- return (_jsxs("div", { className: "replay-preview__native", children: [_jsx("div", { ref: stageRef, className: "replay-preview__stage", style: dims ? { height: Math.max(1, Math.round(dims.h * scale)) } : undefined, children: _jsx("div", { className: "replay-preview__scaler", style: { transform: `scale(${scale})`, transformOrigin: 'top left' }, children: _jsx("div", { ref: playerRef, className: "replay-preview__player", "aria-hidden": "true" }) }) }), _jsxs("div", { className: "replay-preview__controls", children: [_jsx("button", { type: "button", onClick: toggle, "aria-label": playing ? 'Pause replay' : 'Play replay', children: playing ? 'Pause' : 'Play' }), _jsx("button", { type: "button", onClick: restart, "aria-label": "Restart replay", children: "Restart" }), _jsx("input", { type: "range", min: 0, max: Math.max(totalTime, 1), step: 1, value: scrubbing ?? currentTime, onChange: (event) => setScrubbing(Number(event.target.value)), onPointerUp: (event) => {
171
- seek(Number(event.currentTarget.value));
172
- setScrubbing(null);
173
- }, "aria-label": "Replay position" }), _jsx("select", { value: speed, onChange: (event) => setSpeed(Number(event.target.value)), "aria-label": "Replay speed", children: speeds.map((option) => (_jsxs("option", { value: option, children: [option, "\u00D7"] }, option))) })] })] }));
174
- }
175
- //# sourceMappingURL=NativeReplay.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"NativeReplay.js","sourceRoot":"","sources":["../../src/replay/NativeReplay.tsx"],"names":[],"mappings":"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"}
@@ -1,240 +0,0 @@
1
- /* The rrweb player. Loaded on demand by ReplayPreview, so pages that only embed Loop QA
2
- iframes (the markdown `{% embed %}` case) never download rrweb or its stylesheet. */
3
- import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
- import { EventType, Replayer } from 'rrweb';
5
- import type { eventWithTime } from 'rrweb';
6
- import { sanitizeReplayEvent } from './sanitize';
7
- import type { ReplayEvent } from './types';
8
- import 'rrweb/dist/style.css';
9
-
10
- const DEFAULT_SPEEDS = [0.5, 1, 2, 4, 8];
11
- const SKIP_IDLE_THRESHOLD_MS = 5_000;
12
-
13
- export default function NativeReplay({
14
- events,
15
- autoplay = false,
16
- loop = false,
17
- speeds = DEFAULT_SPEEDS,
18
- }: {
19
- events: ReplayEvent[];
20
- autoplay?: boolean;
21
- loop?: boolean;
22
- speeds?: number[];
23
- }) {
24
- const stageRef = useRef<HTMLDivElement>(null);
25
- const playerRef = useRef<HTMLDivElement>(null);
26
- const replayerRef = useRef<Replayer | null>(null);
27
- const fedCountRef = useRef(0);
28
- const [playing, setPlaying] = useState(false);
29
- const [speed, setSpeed] = useState(1);
30
- const [dims, setDims] = useState<{ w: number; h: number } | null>(null);
31
- const [scale, setScale] = useState(1);
32
- const [currentTime, setCurrentTime] = useState(0);
33
- const [totalTime, setTotalTime] = useState(0);
34
- const [scrubbing, setScrubbing] = useState<number | null>(null);
35
- const speedRef = useRef(speed);
36
- const loopRef = useRef(loop);
37
- const autoplayRef = useRef(autoplay);
38
-
39
- const prepared = useMemo(
40
- () =>
41
- events
42
- .map((event) => sanitizeReplayEvent(event))
43
- .sort((a, b) => a.timestamp - b.timestamp) as unknown as eventWithTime[],
44
- [events],
45
- );
46
- const hasSnapshot = prepared.some((event) => event.type === EventType.FullSnapshot);
47
-
48
- useEffect(() => {
49
- if (replayerRef.current && prepared.length < fedCountRef.current) {
50
- try {
51
- replayerRef.current.destroy();
52
- } catch {
53
- // Ignore stale player teardown.
54
- }
55
- replayerRef.current = null;
56
- fedCountRef.current = 0;
57
- }
58
- if (!hasSnapshot || !playerRef.current) return;
59
-
60
- if (!replayerRef.current) {
61
- const replayer = new Replayer(prepared, {
62
- root: playerRef.current,
63
- liveMode: false,
64
- speed: speedRef.current,
65
- skipInactive: true,
66
- inactivePeriodThreshold: SKIP_IDLE_THRESHOLD_MS,
67
- mouseTail: false,
68
- showWarning: false,
69
- });
70
- replayer.on('resize', (payload) => {
71
- const size = payload as { width?: number; height?: number };
72
- if (size.width && size.height) setDims({ w: size.width, h: size.height });
73
- });
74
- replayer.on('finish', () => {
75
- if (loopRef.current) {
76
- replayer.play(0);
77
- setPlaying(true);
78
- } else {
79
- setPlaying(false);
80
- }
81
- });
82
- replayer.pause(0);
83
- replayerRef.current = replayer;
84
- fedCountRef.current = prepared.length;
85
- if (autoplayRef.current) {
86
- replayer.play(0);
87
- setPlaying(true);
88
- }
89
- } else if (prepared.length > fedCountRef.current) {
90
- for (let i = fedCountRef.current; i < prepared.length; i++) {
91
- replayerRef.current.addEvent(prepared[i]!);
92
- }
93
- fedCountRef.current = prepared.length;
94
- }
95
- }, [hasSnapshot, prepared]);
96
-
97
- useEffect(
98
- () => () => {
99
- try {
100
- replayerRef.current?.destroy();
101
- } catch {
102
- // Ignore unmount teardown errors from rrweb.
103
- }
104
- replayerRef.current = null;
105
- fedCountRef.current = 0;
106
- },
107
- [],
108
- );
109
-
110
- useEffect(() => {
111
- speedRef.current = speed;
112
- replayerRef.current?.setConfig({ speed });
113
- }, [speed]);
114
-
115
- useEffect(() => {
116
- loopRef.current = loop;
117
- }, [loop]);
118
-
119
- useEffect(() => {
120
- autoplayRef.current = autoplay;
121
- }, [autoplay]);
122
-
123
- const recalcScale = useCallback(() => {
124
- if (!dims || !stageRef.current || dims.w <= 0) return;
125
- setScale(Math.min(1, stageRef.current.clientWidth / dims.w));
126
- }, [dims]);
127
-
128
- useEffect(() => {
129
- recalcScale();
130
- window.addEventListener('resize', recalcScale);
131
- return () => window.removeEventListener('resize', recalcScale);
132
- }, [recalcScale]);
133
-
134
- useEffect(() => {
135
- if (!hasSnapshot) return;
136
- const interval = window.setInterval(() => {
137
- const replayer = replayerRef.current;
138
- if (!replayer) return;
139
- try {
140
- const total = replayer.getMetaData().totalTime;
141
- const current = Math.max(0, Math.min(replayer.getCurrentTime(), total));
142
- setTotalTime(total);
143
- if (scrubbing === null) setCurrentTime(current);
144
- } catch {
145
- // rrweb can briefly be unready while a full snapshot is applied.
146
- }
147
- }, 200);
148
- return () => window.clearInterval(interval);
149
- }, [hasSnapshot, scrubbing]);
150
-
151
- const seek = useCallback(
152
- (value: number) => {
153
- const replayer = replayerRef.current;
154
- if (!replayer) return;
155
- const next = Math.max(0, Math.min(value, totalTime));
156
- if (playing) replayer.play(next);
157
- else replayer.pause(next);
158
- setCurrentTime(next);
159
- },
160
- [playing, totalTime],
161
- );
162
-
163
- const toggle = () => {
164
- const replayer = replayerRef.current;
165
- if (!replayer) return;
166
- if (playing) {
167
- replayer.pause();
168
- setPlaying(false);
169
- return;
170
- }
171
- let offset = currentTime;
172
- try {
173
- if (offset >= replayer.getMetaData().totalTime - 1) offset = 0;
174
- } catch {
175
- offset = 0;
176
- }
177
- replayer.play(offset);
178
- setPlaying(true);
179
- };
180
-
181
- const restart = () => {
182
- replayerRef.current?.play(0);
183
- setCurrentTime(0);
184
- setPlaying(true);
185
- };
186
-
187
- if (!hasSnapshot) {
188
- return (
189
- <div className="replay-preview__empty">
190
- {events.length ? `Captured ${events.length} events, but no full DOM snapshot is available.` : 'Replay data is not available yet.'}
191
- </div>
192
- );
193
- }
194
-
195
- return (
196
- <div className="replay-preview__native">
197
- <div
198
- ref={stageRef}
199
- className="replay-preview__stage"
200
- style={dims ? { height: Math.max(1, Math.round(dims.h * scale)) } : undefined}
201
- >
202
- <div
203
- className="replay-preview__scaler"
204
- style={{ transform: `scale(${scale})`, transformOrigin: 'top left' }}
205
- >
206
- <div ref={playerRef} className="replay-preview__player" aria-hidden="true" />
207
- </div>
208
- </div>
209
- <div className="replay-preview__controls">
210
- <button type="button" onClick={toggle} aria-label={playing ? 'Pause replay' : 'Play replay'}>
211
- {playing ? 'Pause' : 'Play'}
212
- </button>
213
- <button type="button" onClick={restart} aria-label="Restart replay">
214
- Restart
215
- </button>
216
- <input
217
- type="range"
218
- min={0}
219
- max={Math.max(totalTime, 1)}
220
- step={1}
221
- value={scrubbing ?? currentTime}
222
- onChange={(event) => setScrubbing(Number(event.target.value))}
223
- onPointerUp={(event) => {
224
- seek(Number((event.currentTarget as HTMLInputElement).value));
225
- setScrubbing(null);
226
- }}
227
- aria-label="Replay position"
228
- />
229
- <select value={speed} onChange={(event) => setSpeed(Number(event.target.value))} aria-label="Replay speed">
230
- {speeds.map((option) => (
231
- <option key={option} value={option}>
232
- {option}×
233
- </option>
234
- ))}
235
- </select>
236
- </div>
237
- </div>
238
- );
239
- }
240
-