@midscene/visualizer 1.5.2 → 1.5.3-beta-20260305031559.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 (40) hide show
  1. package/dist/es/component/blackboard/index.css +82 -4
  2. package/dist/es/component/blackboard/index.mjs +73 -301
  3. package/dist/es/component/player/index.css +144 -119
  4. package/dist/es/component/player/index.mjs +468 -830
  5. package/dist/es/component/player/remotion/StepScene.mjs +190 -0
  6. package/dist/es/component/player/remotion/derive-frame-state.mjs +207 -0
  7. package/dist/es/component/player/remotion/export-branded-video.mjs +210 -0
  8. package/dist/es/component/player/remotion/frame-calculator.mjs +149 -0
  9. package/dist/es/component/player/use-frame-player.mjs +88 -0
  10. package/dist/es/component/universal-playground/index.mjs +14 -1
  11. package/dist/es/hooks/usePlaygroundExecution.mjs +11 -7
  12. package/dist/es/store/store.mjs +9 -0
  13. package/dist/es/utils/replay-scripts.mjs +2 -1
  14. package/dist/lib/component/blackboard/index.css +82 -4
  15. package/dist/lib/component/blackboard/index.js +73 -307
  16. package/dist/lib/component/player/index.css +144 -119
  17. package/dist/lib/component/player/index.js +466 -828
  18. package/dist/lib/component/player/remotion/StepScene.js +224 -0
  19. package/dist/lib/component/player/remotion/derive-frame-state.js +241 -0
  20. package/dist/lib/component/player/remotion/export-branded-video.js +244 -0
  21. package/dist/lib/component/player/remotion/frame-calculator.js +186 -0
  22. package/dist/lib/component/player/use-frame-player.js +122 -0
  23. package/dist/lib/component/universal-playground/index.js +14 -1
  24. package/dist/lib/hooks/usePlaygroundExecution.js +11 -7
  25. package/dist/lib/store/store.js +9 -0
  26. package/dist/lib/utils/replay-scripts.js +2 -1
  27. package/dist/types/component/blackboard/index.d.ts +0 -4
  28. package/dist/types/component/player/index.d.ts +0 -1
  29. package/dist/types/component/player/remotion/StepScene.d.ts +9 -0
  30. package/dist/types/component/player/remotion/derive-frame-state.d.ts +38 -0
  31. package/dist/types/component/player/remotion/export-branded-video.d.ts +2 -0
  32. package/dist/types/component/player/remotion/frame-calculator.d.ts +35 -0
  33. package/dist/types/component/player/use-frame-player.d.ts +17 -0
  34. package/dist/types/hooks/usePlaygroundExecution.d.ts +15 -1
  35. package/dist/types/store/store.d.ts +2 -0
  36. package/dist/types/utils/replay-scripts.d.ts +1 -0
  37. package/package.json +5 -8
  38. package/dist/es/utils/pixi-loader.mjs +0 -42
  39. package/dist/lib/utils/pixi-loader.js +0 -82
  40. package/dist/types/utils/pixi-loader.d.ts +0 -5
@@ -21,21 +21,99 @@
21
21
  max-width: 500px;
22
22
  }
23
23
 
24
- .blackboard-filter {
25
- margin: 10px 0;
24
+ .blackboard-main-content {
25
+ position: relative;
26
+ overflow: hidden;
26
27
  }
27
28
 
28
- .blackboard-main-content canvas {
29
+ .blackboard-screenshot {
29
30
  box-sizing: border-box;
30
31
  border: 1px solid #888;
31
32
  width: 100%;
33
+ display: block;
34
+ }
35
+
36
+ .blackboard-overlay {
37
+ pointer-events: none;
38
+ width: 100%;
39
+ position: absolute;
40
+ top: 0;
41
+ left: 0;
42
+ }
43
+
44
+ .blackboard-rect {
45
+ box-sizing: border-box;
46
+ pointer-events: none;
47
+ position: absolute;
48
+ }
49
+
50
+ .blackboard-rect-label {
51
+ white-space: nowrap;
52
+ padding: 1px 4px;
53
+ font-size: 14px;
54
+ font-weight: 600;
55
+ line-height: 1.4;
56
+ position: absolute;
57
+ bottom: 100%;
58
+ left: 0;
59
+ }
60
+
61
+ .blackboard-rect-search {
62
+ background: rgba(2, 131, 145, .4);
63
+ border: 1px solid #028391;
64
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4);
65
+ }
66
+
67
+ .blackboard-rect-search .blackboard-rect-label {
68
+ color: #028391;
69
+ }
70
+
71
+ .blackboard-rect-highlight {
72
+ background: rgba(253, 89, 7, .4);
73
+ border: 1px solid #fd5907;
74
+ animation: 1.2s ease-in-out infinite blackboard-pulse;
75
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4);
76
+ }
77
+
78
+ .blackboard-rect-highlight .blackboard-rect-label {
79
+ color: #000;
80
+ }
81
+
82
+ .blackboard-point {
83
+ pointer-events: none;
84
+ background: rgba(253, 89, 7, .4);
85
+ border: 1px solid #fd5907;
86
+ border-radius: 50%;
87
+ width: 20px;
88
+ height: 20px;
89
+ margin-top: -10px;
90
+ margin-left: -10px;
91
+ animation: 1.2s ease-in-out infinite blackboard-pulse;
92
+ position: absolute;
93
+ box-shadow: 0 0 8px rgba(253, 89, 7, .5);
94
+ }
95
+
96
+ @keyframes blackboard-pulse {
97
+ 0%, 100% {
98
+ opacity: .4;
99
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4);
100
+ }
101
+
102
+ 50% {
103
+ opacity: 1;
104
+ box-shadow: 4px 4px 2px rgba(51, 51, 51, .4), 0 0 16px rgba(253, 89, 7, .5);
105
+ }
32
106
  }
33
107
 
34
108
  [data-theme="dark"] .blackboard .footer, [data-theme="dark"] .blackboard .bottom-tip-item {
35
109
  color: rgba(255, 255, 255, .45);
36
110
  }
37
111
 
38
- [data-theme="dark"] .blackboard-main-content canvas {
112
+ [data-theme="dark"] .blackboard-screenshot {
39
113
  border-color: rgba(255, 255, 255, .12);
40
114
  }
41
115
 
116
+ [data-theme="dark"] .blackboard-rect-highlight .blackboard-rect-label {
117
+ color: #fff;
118
+ }
119
+
@@ -1,91 +1,10 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import "pixi.js/unsafe-eval";
4
- import { Application, Container, Graphics, Rectangle, Sprite, Text, Texture } from "pixi.js";
5
- import { useEffect, useMemo, useRef, useState } from "react";
6
- import { colorForName, highlightColorForType } from "../../utils/color.mjs";
3
+ import { useMemo, useRef } from "react";
7
4
  import "./index.css";
8
- import { DropShadowFilter, GlowFilter } from "pixi-filters";
9
- function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
10
- try {
11
- var info = gen[key](arg);
12
- var value = info.value;
13
- } catch (error) {
14
- reject(error);
15
- return;
16
- }
17
- if (info.done) resolve(value);
18
- else Promise.resolve(value).then(_next, _throw);
19
- }
20
- function _async_to_generator(fn) {
21
- return function() {
22
- var self = this, args = arguments;
23
- return new Promise(function(resolve, reject) {
24
- var gen = fn.apply(self, args);
25
- function _next(value) {
26
- asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
27
- }
28
- function _throw(err) {
29
- asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
30
- }
31
- _next(void 0);
32
- });
33
- };
34
- }
35
- const itemFillAlpha = 0.4;
36
- const highlightAlpha = 0.4;
37
- const pointRadius = 10;
38
- const pointMarkForItem = (point, type)=>{
39
- const [x, y] = point;
40
- const themeColor = highlightColorForType('element');
41
- const graphics = new Graphics();
42
- graphics.beginFill(themeColor, itemFillAlpha);
43
- graphics.drawCircle(x, y, pointRadius);
44
- graphics.endFill();
45
- return graphics;
46
- };
47
- const rectMarkForItem = (rect, name, type)=>{
48
- const { left, top, width, height } = rect;
49
- let themeColor;
50
- themeColor = 'element' === type ? colorForName(name) : 'searchArea' === type ? highlightColorForType('searchArea') : highlightColorForType('element');
51
- const alpha = 'highlight' === type ? highlightAlpha : itemFillAlpha;
52
- const graphics = new Graphics();
53
- graphics.beginFill(themeColor, alpha);
54
- graphics.lineStyle(1, themeColor, 1);
55
- graphics.drawRect(left, top, width, height);
56
- graphics.endFill();
57
- const dropShadowFilter = new DropShadowFilter({
58
- blur: 2,
59
- quality: 3,
60
- alpha: 0.4,
61
- offset: {
62
- x: 4,
63
- y: 4
64
- },
65
- color: 0x333333
66
- });
67
- graphics.filters = [
68
- dropShadowFilter
69
- ];
70
- const nameFontSize = 18;
71
- if (!name) return [
72
- graphics
73
- ];
74
- const texts = new Text(name, {
75
- fontSize: nameFontSize,
76
- fill: 0x0
77
- });
78
- texts.x = left;
79
- texts.y = Math.max(top - (nameFontSize + 4), 0);
80
- return [
81
- graphics,
82
- texts
83
- ];
84
- };
85
5
  const Blackboard = (props)=>{
86
6
  var _props_uiContext;
87
7
  const highlightElements = props.highlightElements || [];
88
- highlightElements.map((e)=>e.id);
89
8
  const highlightRect = props.highlightRect;
90
9
  const highlightPoints = props.highlightPoints;
91
10
  if (!(null == (_props_uiContext = props.uiContext) ? void 0 : _props_uiContext.shotSize)) return /*#__PURE__*/ jsx("div", {
@@ -100,6 +19,8 @@ const Blackboard = (props)=>{
100
19
  });
101
20
  const context = props.uiContext;
102
21
  const { shotSize, screenshot } = context;
22
+ const screenWidth = shotSize.width;
23
+ const screenHeight = shotSize.height;
103
24
  const screenshotBase64 = useMemo(()=>{
104
25
  if (!screenshot) return '';
105
26
  if ('object' == typeof screenshot && 'base64' in screenshot) return screenshot.base64;
@@ -108,221 +29,20 @@ const Blackboard = (props)=>{
108
29
  }, [
109
30
  screenshot
110
31
  ]);
111
- const screenWidth = shotSize.width;
112
- const screenHeight = shotSize.height;
113
- const domRef = useRef(null);
114
- const app = useMemo(()=>new Application(), []);
115
- const [appInitialed, setAppInitialed] = useState(false);
116
- const highlightContainer = useMemo(()=>new Container(), []);
117
- const elementMarkContainer = useMemo(()=>new Container(), []);
118
- const [hoverElement, setHoverElement] = useState(null);
119
- const pixiBgRef = useRef(void 0);
120
- const animationFrameRef = useRef(null);
121
- const highlightGraphicsRef = useRef([]);
122
- const glowFiltersRef = useRef([]);
123
- const backgroundVisible = true;
124
- const elementsVisible = true;
125
- useEffect(()=>{
126
- Promise.resolve((()=>_async_to_generator(function*() {
127
- if (!domRef.current || !screenWidth) return;
128
- yield app.init({
129
- width: screenWidth,
130
- height: screenHeight,
131
- background: 0xffffff
132
- });
133
- const canvasEl = domRef.current;
134
- domRef.current.appendChild(app.canvas);
135
- const { clientWidth } = domRef.current.parentElement;
136
- const targetHeight = 0.6 * window.innerHeight;
137
- const viewportRatio = clientWidth / targetHeight;
138
- if (screenWidth / screenHeight <= viewportRatio) {
139
- const ratio = targetHeight / screenHeight;
140
- canvasEl.style.width = `${Math.floor(screenWidth * ratio)}px`;
141
- canvasEl.style.height = `${Math.floor(screenHeight * ratio)}px`;
142
- }
143
- app.stage.addChild(highlightContainer);
144
- app.stage.addChild(elementMarkContainer);
145
- setAppInitialed(true);
146
- })())());
147
- return ()=>{
148
- console.log('will destroy');
149
- if (null !== animationFrameRef.current) {
150
- cancelAnimationFrame(animationFrameRef.current);
151
- animationFrameRef.current = null;
152
- }
153
- try {
154
- app.destroy(true, {
155
- children: true,
156
- texture: true
157
- });
158
- } catch (e) {
159
- console.warn('destroy failed', e);
160
- }
161
- };
162
- }, [
163
- app,
164
- screenWidth,
165
- screenHeight
166
- ]);
167
- useEffect(()=>{
168
- if (!appInitialed) return;
169
- app.stage.eventMode = 'static';
170
- app.stage.hitArea = new Rectangle(0, 0, screenWidth, screenHeight);
171
- const clickHandler = (event)=>{
172
- var _props_onCanvasClick;
173
- console.log('pixi click', event);
174
- const { x, y } = event.data.global;
175
- null == (_props_onCanvasClick = props.onCanvasClick) || _props_onCanvasClick.call(props, [
176
- Math.round(x),
177
- Math.round(y)
178
- ]);
179
- };
180
- app.stage.on('click', clickHandler);
181
- return ()=>{
182
- var _app_stage;
183
- null == app || null == (_app_stage = app.stage) || _app_stage.off('click');
184
- };
185
- }, [
186
- appInitialed,
187
- props.onCanvasClick,
188
- screenWidth,
189
- screenHeight
190
- ]);
191
- useEffect(()=>{
192
- if (!appInitialed) return;
193
- const img = new Image();
194
- img.onload = ()=>{
195
- if (!app.stage) return;
196
- const screenshotTexture = Texture.from(img);
197
- const backgroundSprite = new Sprite(screenshotTexture);
198
- backgroundSprite.x = 0;
199
- backgroundSprite.y = 0;
200
- backgroundSprite.width = screenWidth;
201
- backgroundSprite.height = screenHeight;
202
- backgroundSprite.eventMode = 'passive';
203
- app.stage.addChildAt(backgroundSprite, 0);
204
- pixiBgRef.current = backgroundSprite;
205
- backgroundSprite.visible = backgroundVisible;
206
- };
207
- img.onerror = (e)=>{
208
- console.error('load screenshot failed', e);
209
- };
210
- if (screenshotBase64) img.src = screenshotBase64;
211
- else console.error('screenshotBase64 is empty, cannot load image');
212
- }, [
213
- app.stage,
214
- appInitialed,
215
- screenWidth,
216
- screenHeight,
217
- screenshotBase64
218
- ]);
219
- const { highlightElementRects } = useMemo(()=>{
220
- const highlightElementRects = [];
221
- highlightContainer.removeChildren();
222
- elementMarkContainer.removeChildren();
223
- highlightContainer.eventMode = 'passive';
224
- elementMarkContainer.eventMode = 'passive';
225
- highlightGraphicsRef.current = [];
226
- glowFiltersRef.current = [];
227
- if (highlightRect) {
228
- const [graphics] = rectMarkForItem(highlightRect, 'Search Area', 'searchArea');
229
- highlightContainer.addChild(graphics);
230
- }
231
- if (highlightElements.length) highlightElements.forEach((element)=>{
232
- const { rect, content, id } = element;
233
- const items = rectMarkForItem(rect, content, 'highlight');
234
- const graphics = items[0];
235
- const glowFilter = new GlowFilter({
236
- distance: 30,
237
- outerStrength: 3,
238
- innerStrength: 0,
239
- color: 0xfd5907,
240
- quality: 0.5
241
- });
242
- const existingFilters = graphics.filters;
243
- if (Array.isArray(existingFilters)) graphics.filters = [
244
- ...existingFilters,
245
- glowFilter
246
- ];
247
- else if (existingFilters) graphics.filters = [
248
- existingFilters,
249
- glowFilter
250
- ];
251
- else graphics.filters = [
252
- glowFilter
253
- ];
254
- items.forEach((item)=>highlightContainer.addChild(item));
255
- highlightGraphicsRef.current.push(graphics);
256
- glowFiltersRef.current.push(glowFilter);
257
- });
258
- if (null == highlightPoints ? void 0 : highlightPoints.length) highlightPoints.forEach((point)=>{
259
- const graphics = pointMarkForItem(point, 'highlightPoint');
260
- const glowFilter = new GlowFilter({
261
- distance: 25,
262
- outerStrength: 2.5,
263
- innerStrength: 0,
264
- color: 0xfd5907,
265
- quality: 0.5
266
- });
267
- graphics.filters = [
268
- glowFilter
269
- ];
270
- highlightContainer.addChild(graphics);
271
- highlightGraphicsRef.current.push(graphics);
272
- glowFiltersRef.current.push(glowFilter);
273
- });
274
- elementMarkContainer.visible = elementsVisible;
275
- return {
276
- highlightElementRects
277
- };
278
- }, [
279
- app,
280
- appInitialed,
281
- highlightElements,
282
- hoverElement,
283
- highlightRect,
284
- highlightPoints
285
- ]);
286
- useEffect(()=>{
287
- if (!appInitialed || 0 === highlightGraphicsRef.current.length) return void console.log('Animation skipped:', {
288
- appInitialed,
289
- graphicsCount: highlightGraphicsRef.current.length
290
- });
291
- console.log('Starting pulsing animation for', highlightGraphicsRef.current.length, 'graphics');
292
- const graphicsToAnimate = highlightGraphicsRef.current;
293
- const glowFilters = glowFiltersRef.current;
294
- const pulseDuration = 1200;
295
- const minAlpha = 0.4;
296
- const maxAlpha = 1.0;
297
- const minGlowStrength = 2.0;
298
- const maxGlowStrength = 5.0;
299
- const startTime = performance.now();
300
- const animate = ()=>{
301
- const elapsed = performance.now() - startTime;
302
- const progress = elapsed % pulseDuration / pulseDuration;
303
- const sineValue = Math.sin(progress * Math.PI * 2);
304
- const normalizedSine = (sineValue + 1) / 2;
305
- const alpha = minAlpha + normalizedSine * (maxAlpha - minAlpha);
306
- const glowStrength = minGlowStrength + normalizedSine * (maxGlowStrength - minGlowStrength);
307
- graphicsToAnimate.forEach((graphics, index)=>{
308
- graphics.alpha = alpha;
309
- if (glowFilters[index]) glowFilters[index].outerStrength = glowStrength;
310
- });
311
- animationFrameRef.current = requestAnimationFrame(animate);
312
- };
313
- animate();
314
- return ()=>{
315
- console.log('Stopping pulsing animation');
316
- if (null !== animationFrameRef.current) {
317
- cancelAnimationFrame(animationFrameRef.current);
318
- animationFrameRef.current = null;
319
- }
320
- };
321
- }, [
322
- appInitialed,
323
- highlightElements,
324
- highlightPoints
325
- ]);
32
+ const containerRef = useRef(null);
33
+ const handleClick = (e)=>{
34
+ if (!props.onCanvasClick || !containerRef.current) return;
35
+ const rect = containerRef.current.getBoundingClientRect();
36
+ const scaleX = screenWidth / rect.width;
37
+ const scaleY = screenHeight / rect.height;
38
+ const x = Math.round((e.clientX - rect.left) * scaleX);
39
+ const y = Math.round((e.clientY - rect.top) * scaleY);
40
+ props.onCanvasClick([
41
+ x,
42
+ y
43
+ ]);
44
+ };
45
+ const highlightElementRects = highlightElements.map((e)=>e.rect);
326
46
  let bottomTipA = null;
327
47
  if (1 === highlightElementRects.length) bottomTipA = /*#__PURE__*/ jsx("div", {
328
48
  className: "bottom-tip",
@@ -347,12 +67,64 @@ const Blackboard = (props)=>{
347
67
  return /*#__PURE__*/ jsxs("div", {
348
68
  className: "blackboard",
349
69
  children: [
350
- /*#__PURE__*/ jsx("div", {
70
+ /*#__PURE__*/ jsxs("div", {
351
71
  className: "blackboard-main-content",
72
+ ref: containerRef,
73
+ onClick: handleClick,
352
74
  style: {
353
- width: '100%'
75
+ width: '100%',
76
+ position: 'relative',
77
+ cursor: props.onCanvasClick ? 'crosshair' : void 0
354
78
  },
355
- ref: domRef
79
+ children: [
80
+ screenshotBase64 && /*#__PURE__*/ jsx("img", {
81
+ src: screenshotBase64,
82
+ alt: "screenshot",
83
+ className: "blackboard-screenshot",
84
+ draggable: false
85
+ }),
86
+ /*#__PURE__*/ jsxs("div", {
87
+ className: "blackboard-overlay",
88
+ style: {
89
+ aspectRatio: `${screenWidth}/${screenHeight}`
90
+ },
91
+ children: [
92
+ highlightRect && /*#__PURE__*/ jsx("div", {
93
+ className: "blackboard-rect blackboard-rect-search",
94
+ style: {
95
+ left: `${highlightRect.left / screenWidth * 100}%`,
96
+ top: `${highlightRect.top / screenHeight * 100}%`,
97
+ width: `${highlightRect.width / screenWidth * 100}%`,
98
+ height: `${highlightRect.height / screenHeight * 100}%`
99
+ },
100
+ children: /*#__PURE__*/ jsx("span", {
101
+ className: "blackboard-rect-label",
102
+ children: "Search Area"
103
+ })
104
+ }),
105
+ highlightElements.map((el, idx)=>/*#__PURE__*/ jsx("div", {
106
+ className: "blackboard-rect blackboard-rect-highlight",
107
+ style: {
108
+ left: `${el.rect.left / screenWidth * 100}%`,
109
+ top: `${el.rect.top / screenHeight * 100}%`,
110
+ width: `${el.rect.width / screenWidth * 100}%`,
111
+ height: `${el.rect.height / screenHeight * 100}%`
112
+ },
113
+ children: el.content && /*#__PURE__*/ jsx("span", {
114
+ className: "blackboard-rect-label",
115
+ children: el.content
116
+ })
117
+ }, el.id || idx)),
118
+ null == highlightPoints ? void 0 : highlightPoints.map((point, idx)=>/*#__PURE__*/ jsx("div", {
119
+ className: "blackboard-point",
120
+ style: {
121
+ left: `${point[0] / screenWidth * 100}%`,
122
+ top: `${point[1] / screenHeight * 100}%`
123
+ }
124
+ }, idx))
125
+ ]
126
+ })
127
+ ]
356
128
  }),
357
129
  /*#__PURE__*/ jsx("div", {
358
130
  className: "bottom-tip",
@@ -365,4 +137,4 @@ const Blackboard = (props)=>{
365
137
  });
366
138
  };
367
139
  const blackboard = Blackboard;
368
- export { Blackboard, blackboard as default, pointMarkForItem, rectMarkForItem };
140
+ export { Blackboard, blackboard as default };