@midscene/visualizer 0.6.2-beta-20241012092515.0 → 0.6.2

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.
@@ -40,10 +40,7 @@ import * as PIXI from "pixi.js";
40
40
  import { useEffect, useMemo, useRef, useState } from "react";
41
41
  import "./player.css";
42
42
  import { mouseLoading, mousePointer } from "../utils";
43
- import {
44
- LoadingOutlined,
45
- PlayCircleOutlined
46
- } from "@ant-design/icons";
43
+ import { CaretRightOutlined, LoadingOutlined } from "@ant-design/icons";
47
44
  import { ConfigProvider, Spin } from "antd";
48
45
  import { rectMarkForItem } from "./blackboard";
49
46
  import { useExecutionDump } from "./store";
@@ -68,7 +65,7 @@ const linear = (t) => {
68
65
  const sleep = (ms) => {
69
66
  return new Promise((resolve) => setTimeout(resolve, ms));
70
67
  };
71
- const ERROR_FRAME_CANCEL = "frame cancel";
68
+ const ERROR_FRAME_CANCEL = "frame cancel (this is an error on purpose)";
72
69
  const frameKit = () => {
73
70
  let cancelFlag = false;
74
71
  return {
@@ -95,7 +92,6 @@ const frameKit = () => {
95
92
  }, ms);
96
93
  },
97
94
  cancel: () => {
98
- console.log("set frame cancel");
99
95
  cancelFlag = true;
100
96
  }
101
97
  };
@@ -109,13 +105,16 @@ const Player = () => {
109
105
  var _a;
110
106
  const [titleText, setTitleText] = useState("");
111
107
  const [subTitleText, setSubTitleText] = useState("");
112
- const scripts = useExecutionDump((store) => store.activeExecutionAnimation);
113
- const imageWidth = useExecutionDump(
114
- (store) => store.activeExecutionScreenshotWidth
108
+ const taskScripts = useExecutionDump(
109
+ (store) => store.activeExecutionAnimation
115
110
  );
116
- const imageHeight = useExecutionDump(
117
- (store) => store.activeExecutionScreenshotHeight
111
+ const replayAllMode = useExecutionDump((store) => store.replayAllMode);
112
+ const replayAllScripts = useExecutionDump(
113
+ (store) => store.allExecutionAnimation
118
114
  );
115
+ const scripts = replayAllMode ? replayAllScripts : taskScripts;
116
+ const imageWidth = useExecutionDump((store) => store.insightWidth) || 1920;
117
+ const imageHeight = useExecutionDump((store) => store.insightHeight) || 1080;
119
118
  const canvasWidth = imageWidth + canvasPaddingLeft * 2;
120
119
  const canvasHeight = imageHeight + canvasPaddingTop * 2;
121
120
  const currentImg = useRef(((_a = scripts == null ? void 0 : scripts[0]) == null ? void 0 : _a.img) || null);
@@ -139,8 +138,8 @@ const Player = () => {
139
138
  top: 0,
140
139
  width: imageWidth,
141
140
  pointer: {
142
- left: imageWidth / 2,
143
- top: imageHeight / 2
141
+ left: Math.round(imageWidth / 2),
142
+ top: Math.round(imageHeight / 2)
144
143
  }
145
144
  };
146
145
  const [animationProgress, setAnimationProgress] = useState(-1);
@@ -285,14 +284,18 @@ const Player = () => {
285
284
  const animate = (currentTime) => {
286
285
  const nextState = __spreadValues({}, cameraState.current);
287
286
  const elapsedTime = currentTime - startTime;
288
- if (shouldMovePointer && elapsedTime < pointerMoveDuration) {
289
- const rawMouseProgress = Math.min(
290
- elapsedTime / pointerMoveDuration,
291
- 1
292
- );
293
- const mouseProgress = cubicMouse(rawMouseProgress);
294
- nextState.pointer.left = startPointer.left + (targetState.pointer.left - startPointer.left) * mouseProgress;
295
- nextState.pointer.top = startPointer.top + (targetState.pointer.top - startPointer.top) * mouseProgress;
287
+ if (shouldMovePointer) {
288
+ if (elapsedTime <= pointerMoveDuration) {
289
+ const rawMouseProgress = Math.min(
290
+ elapsedTime / pointerMoveDuration,
291
+ 1
292
+ );
293
+ const mouseProgress = cubicMouse(rawMouseProgress);
294
+ nextState.pointer.left = startPointer.left + (targetState.pointer.left - startPointer.left) * mouseProgress;
295
+ nextState.pointer.top = startPointer.top + (targetState.pointer.top - startPointer.top) * mouseProgress;
296
+ } else {
297
+ nextState.pointer = targetState.pointer;
298
+ }
296
299
  }
297
300
  if (elapsedTime > cameraMoveStart) {
298
301
  const cameraElapsedTime = elapsedTime - cameraMoveStart;
@@ -547,13 +550,13 @@ const Player = () => {
547
550
  if (animationProgress < 1) {
548
551
  statusIconElement = /* @__PURE__ */ jsx(Spin, { indicator: /* @__PURE__ */ jsx(LoadingOutlined, { spin: true }), size: "default" });
549
552
  } else if (mouseOverStatusIcon) {
550
- statusIconElement = /* @__PURE__ */ jsx(Spin, { indicator: /* @__PURE__ */ jsx(PlayCircleOutlined, {}), size: "default" });
553
+ statusIconElement = /* @__PURE__ */ jsx(Spin, { indicator: /* @__PURE__ */ jsx(CaretRightOutlined, {}), size: "default" });
551
554
  statusStyle.cursor = "pointer";
552
555
  statusStyle.background = "#888";
553
556
  statusOnClick = () => setReplayMark(Date.now());
554
557
  } else {
555
558
  statusIconElement = // <Spin indicator={<CheckCircleOutlined />} size="default" />
556
- /* @__PURE__ */ jsx(Spin, { indicator: /* @__PURE__ */ jsx(PlayCircleOutlined, {}), size: "default" });
559
+ /* @__PURE__ */ jsx(Spin, { indicator: /* @__PURE__ */ jsx(CaretRightOutlined, {}), size: "default" });
557
560
  }
558
561
  return /* @__PURE__ */ jsxs("div", { className: "player-container", children: [
559
562
  /* @__PURE__ */ jsx("div", { className: "canvas-container", ref: divContainerRef }),
@@ -50,9 +50,9 @@ const cameraStateForRect = (rect, imageWidth, imageHeight) => {
50
50
  );
51
51
  top = Math.max(top, 0);
52
52
  return {
53
- left,
54
- top,
55
- width: cameraWidth
53
+ left: Math.round(left),
54
+ top: Math.round(top),
55
+ width: Math.round(cameraWidth)
56
56
  };
57
57
  };
58
58
  const mergeTwoCameraState = (cameraState1, cameraState2) => {
@@ -208,7 +208,7 @@ const generateAnimationScripts = (execution, imageWidth, imageHeight) => {
208
208
  title: "Done",
209
209
  subTitle: initSubTitle,
210
210
  type: "img",
211
- duration: stillDuration * 0.5,
211
+ duration: stillDuration,
212
212
  camera: fullPageCameraState
213
213
  });
214
214
  return scripts;
@@ -35,11 +35,13 @@ const useExecutionDump = create((set, get) => {
35
35
  const initData = {
36
36
  dump: null,
37
37
  activeTask: null,
38
+ replayAllMode: false,
39
+ allExecutionAnimation: null,
40
+ insightWidth: null,
41
+ insightHeight: null,
38
42
  activeExecution: null,
39
43
  activeExecutionAnimation: null,
40
44
  // TODO: get from dump
41
- activeExecutionScreenshotWidth: 0,
42
- activeExecutionScreenshotHeight: 0,
43
45
  hoverTask: null,
44
46
  hoverTimestamp: null,
45
47
  hoverPreviewConfig: null
@@ -52,22 +54,75 @@ const useExecutionDump = create((set, get) => {
52
54
  const { reset } = useInsightDump.getState();
53
55
  reset();
54
56
  };
57
+ const resetActiveExecution = () => {
58
+ set({
59
+ activeExecution: null,
60
+ activeExecutionAnimation: null,
61
+ _executionDumpLoadId: ++_executionDumpLoadId
62
+ });
63
+ resetInsightDump();
64
+ };
55
65
  return __spreadProps(__spreadValues({}, initData), {
56
66
  _executionDumpLoadId,
67
+ setReplayAllMode: (replayAllMode) => {
68
+ const state = get();
69
+ if (state.allExecutionAnimation) {
70
+ set({ replayAllMode });
71
+ if (replayAllMode) {
72
+ resetActiveExecution();
73
+ }
74
+ } else {
75
+ console.error(
76
+ "allExecutionAnimation not found, failed to set replayAllMode"
77
+ );
78
+ }
79
+ },
57
80
  setGroupedDump: (dump) => {
58
81
  console.log("will set ExecutionDump", dump);
59
82
  set(__spreadProps(__spreadValues({}, initData), {
60
83
  dump
61
84
  }));
62
85
  resetInsightDump();
63
- if (dump && dump.executions.length > 0 && dump.executions[0].tasks.length > 0) {
64
- get().setActiveTask(dump.executions[0].tasks[0]);
86
+ if (dump && dump.executions.length > 0) {
87
+ let width = 0;
88
+ let height = 0;
89
+ dump.executions.forEach((execution) => {
90
+ execution.tasks.forEach((task) => {
91
+ var _a, _b, _c, _d, _e, _f, _g, _h;
92
+ if (task.type === "Insight") {
93
+ const insightTask = task;
94
+ width = ((_d = (_c = (_b = (_a = insightTask.log) == null ? void 0 : _a.dump) == null ? void 0 : _b.context) == null ? void 0 : _c.size) == null ? void 0 : _d.width) || 1920;
95
+ height = ((_h = (_g = (_f = (_e = insightTask.log) == null ? void 0 : _e.dump) == null ? void 0 : _f.context) == null ? void 0 : _g.size) == null ? void 0 : _h.height) || 1080;
96
+ }
97
+ });
98
+ });
99
+ if (!width || !height) {
100
+ console.warn(
101
+ "width or height not found, failed to generate animation"
102
+ );
103
+ return;
104
+ }
105
+ const allScripts = [];
106
+ dump.executions.forEach((execution) => {
107
+ const scripts = generateAnimationScripts(execution, width, height);
108
+ if (scripts) {
109
+ allScripts.push(...scripts);
110
+ }
111
+ });
112
+ set({
113
+ allExecutionAnimation: allScripts,
114
+ _executionDumpLoadId: ++_executionDumpLoadId,
115
+ replayAllMode: true,
116
+ insightWidth: width,
117
+ insightHeight: height
118
+ });
65
119
  }
66
120
  },
67
121
  setActiveTask(task) {
68
122
  var _a;
69
123
  let parentExecution;
70
- const dump = get().dump;
124
+ const state = get();
125
+ const dump = state.dump;
71
126
  if (dump) {
72
127
  parentExecution = dump.executions.find(
73
128
  (execution) => execution.tasks.includes(task)
@@ -76,27 +131,14 @@ const useExecutionDump = create((set, get) => {
76
131
  if (!parentExecution) {
77
132
  throw new Error("parentExecution not found");
78
133
  }
79
- let width = 0;
80
- let height = 0;
81
- parentExecution.tasks.forEach((t) => {
82
- var _a2, _b, _c, _d, _e, _f, _g, _h;
83
- if (t.type === "Insight") {
84
- const insightTask = t;
85
- width = ((_d = (_c = (_b = (_a2 = insightTask.log) == null ? void 0 : _a2.dump) == null ? void 0 : _b.context) == null ? void 0 : _c.size) == null ? void 0 : _d.width) || 1920;
86
- height = ((_h = (_g = (_f = (_e = insightTask.log) == null ? void 0 : _e.dump) == null ? void 0 : _f.context) == null ? void 0 : _g.size) == null ? void 0 : _h.height) || 1080;
87
- }
88
- });
134
+ const width = state.insightWidth;
135
+ const height = state.insightHeight;
89
136
  set({
137
+ replayAllMode: false,
90
138
  activeTask: task,
91
139
  activeExecution: parentExecution,
92
140
  _executionDumpLoadId: ++_executionDumpLoadId,
93
- activeExecutionAnimation: generateAnimationScripts(
94
- parentExecution,
95
- width,
96
- height
97
- ),
98
- activeExecutionScreenshotWidth: width,
99
- activeExecutionScreenshotHeight: height
141
+ activeExecutionAnimation: width && height ? generateAnimationScripts(parentExecution, width, height) : null
100
142
  });
101
143
  console.log("will set task", task);
102
144
  if (task.type === "Insight") {
package/dist/es/index.css CHANGED
@@ -78,6 +78,17 @@ footer.mt-8 {
78
78
  display: flex;
79
79
  flex-direction: row;
80
80
  background: #F8F8F8;
81
+ justify-content: space-between;
82
+ }
83
+ .page-nav .page-nav-left {
84
+ display: flex;
85
+ flex-direction: row;
86
+ }
87
+ .page-nav .page-nav-toolbar {
88
+ margin-left: 20px;
89
+ }
90
+ .page-nav .page-nav-toolbar .ant-btn {
91
+ background: #E9E9E9;
81
92
  }
82
93
  .page-nav .logo img {
83
94
  height: 20px;
@@ -103,6 +114,15 @@ footer.mt-8 {
103
114
  height: 100%;
104
115
  box-sizing: border-box;
105
116
  }
117
+ .main-right .replay-all-mode-wrapper {
118
+ height: 100%;
119
+ display: flex;
120
+ flex-direction: column;
121
+ justify-content: center;
122
+ padding: 22px;
123
+ box-sizing: border-box;
124
+ margin: 0 auto;
125
+ }
106
126
  .main-right .main-content {
107
127
  display: flex;
108
128
  flex-direction: row;
package/dist/es/index.js CHANGED
@@ -22,9 +22,12 @@ import "./index.css";
22
22
  import DetailSide from "./component/detail-side";
23
23
  import Sidebar from "./component/sidebar";
24
24
  import { useExecutionDump } from "./component/store";
25
- import { DownOutlined } from "@ant-design/icons";
25
+ import {
26
+ CaretRightOutlined,
27
+ DownOutlined
28
+ } from "@ant-design/icons";
26
29
  import { Helmet } from "@modern-js/runtime/head";
27
- import { Alert, ConfigProvider, Dropdown, Select, Upload, message } from "antd";
30
+ import { Alert, Button, ConfigProvider, Dropdown, Upload, message } from "antd";
28
31
  import { useEffect, useRef, useState } from "react";
29
32
  import ReactDOM from "react-dom/client";
30
33
  import { Panel, PanelGroup, PanelResizeHandle } from "react-resizable-panels";
@@ -32,12 +35,21 @@ import logo from "./assets/logo-plain.0f78df8a.png";
32
35
  import DetailPanel from "./component/detail-panel";
33
36
  import GlobalHoverPreview from "./component/global-hover-preview";
34
37
  import { iconForStatus, timeCostStrElement } from "./component/misc";
38
+ import Player from "./component/player";
35
39
  import Timeline from "./component/timeline";
36
40
  const { Dragger } = Upload;
37
41
  let globalRenderCount = 1;
38
42
  function Visualizer(props) {
39
43
  const { dumps, hideLogo = false } = props;
40
44
  const executionDump = useExecutionDump((store) => store.dump);
45
+ const executionDumpLoadId = useExecutionDump(
46
+ (store) => store._executionDumpLoadId
47
+ );
48
+ const replayAllMode = useExecutionDump((store) => store.replayAllMode);
49
+ const setReplayAllMode = useExecutionDump((store) => store.setReplayAllMode);
50
+ const replayAllScripts = useExecutionDump(
51
+ (store) => store.allExecutionAnimation
52
+ );
41
53
  const setGroupedDump = useExecutionDump((store) => store.setGroupedDump);
42
54
  const reset = useExecutionDump((store) => store.reset);
43
55
  const [mainLayoutChangeFlag, setMainLayoutChangeFlag] = useState(0);
@@ -117,6 +129,11 @@ function Visualizer(props) {
117
129
  /* @__PURE__ */ jsx("p", { className: "ant-upload-text", children: "All data will be processed locally by the browser. No data will be sent to the server." })
118
130
  ] })) });
119
131
  } else {
132
+ const content = replayAllMode ? /* @__PURE__ */ jsx("div", { className: "replay-all-mode-wrapper", children: /* @__PURE__ */ jsx(Player, {}, executionDumpLoadId) }) : /* @__PURE__ */ jsxs(PanelGroup, { autoSaveId: "page-detail-layout-v2", direction: "horizontal", children: [
133
+ /* @__PURE__ */ jsx(Panel, { defaultSize: 75, maxSize: 95, children: /* @__PURE__ */ jsx("div", { className: "main-content-container", children: /* @__PURE__ */ jsx(DetailPanel, {}) }) }),
134
+ /* @__PURE__ */ jsx(PanelResizeHandle, {}),
135
+ /* @__PURE__ */ jsx(Panel, { maxSize: 95, children: /* @__PURE__ */ jsx("div", { className: "main-side", children: /* @__PURE__ */ jsx(DetailSide, {}) }) })
136
+ ] });
120
137
  mainContent = /* @__PURE__ */ jsxs(
121
138
  PanelGroup,
122
139
  {
@@ -142,18 +159,7 @@ function Visualizer(props) {
142
159
  ),
143
160
  /* @__PURE__ */ jsx(Panel, { defaultSize: 80, maxSize: 95, children: /* @__PURE__ */ jsxs("div", { className: "main-right", children: [
144
161
  /* @__PURE__ */ jsx(Timeline, {}, mainLayoutChangeFlag),
145
- /* @__PURE__ */ jsx("div", { className: "main-content", children: /* @__PURE__ */ jsxs(
146
- PanelGroup,
147
- {
148
- autoSaveId: "page-detail-layout-v2",
149
- direction: "horizontal",
150
- children: [
151
- /* @__PURE__ */ jsx(Panel, { defaultSize: 75, maxSize: 95, children: /* @__PURE__ */ jsx("div", { className: "main-content-container", children: /* @__PURE__ */ jsx(DetailPanel, {}) }) }),
152
- /* @__PURE__ */ jsx(PanelResizeHandle, {}),
153
- /* @__PURE__ */ jsx(Panel, { maxSize: 95, children: /* @__PURE__ */ jsx("div", { className: "main-side", children: /* @__PURE__ */ jsx(DetailSide, {}) }) })
154
- ]
155
- }
156
- ) })
162
+ /* @__PURE__ */ jsx("div", { className: "main-content", children: content })
157
163
  ] }) })
158
164
  ]
159
165
  }
@@ -185,25 +191,6 @@ function Visualizer(props) {
185
191
  globalRenderCount += 1;
186
192
  };
187
193
  }, []);
188
- const selectOptions = dumps == null ? void 0 : dumps.map((dump, index) => ({
189
- value: index,
190
- label: `${dump.groupName} - ${dump.groupDescription}`,
191
- groupName: dump.groupName,
192
- groupDescription: dump.groupDescription
193
- }));
194
- const selectWidget = selectOptions && selectOptions.length > 1 ? /* @__PURE__ */ jsx(
195
- Select,
196
- {
197
- options: selectOptions,
198
- defaultValue: 0,
199
- onChange: (value) => {
200
- const dump = dumps[value];
201
- setGroupedDump(dump);
202
- },
203
- defaultOpen: true,
204
- style: { width: "100%" }
205
- }
206
- ) : null;
207
194
  return /* @__PURE__ */ jsxs(
208
195
  ConfigProvider,
209
196
  {
@@ -226,13 +213,40 @@ function Visualizer(props) {
226
213
  style: { height: containerHeight },
227
214
  children: [
228
215
  /* @__PURE__ */ jsxs("div", { className: "page-nav", children: [
229
- /* @__PURE__ */ jsx("div", { className: "logo", children: /* @__PURE__ */ jsx(
230
- "img",
231
- {
232
- alt: "Midscene_logo",
233
- src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/logo-light-with-text.png"
234
- }
235
- ) }),
216
+ /* @__PURE__ */ jsxs("div", { className: "page-nav-left", children: [
217
+ /* @__PURE__ */ jsx("div", { className: "logo", children: /* @__PURE__ */ jsx(
218
+ "img",
219
+ {
220
+ alt: "Midscene_logo",
221
+ src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/logo-light-with-text.png"
222
+ }
223
+ ) }),
224
+ /* @__PURE__ */ jsx("div", { className: "page-nav-toolbar", children: /* @__PURE__ */ jsx(
225
+ ConfigProvider,
226
+ {
227
+ theme: {
228
+ components: {
229
+ Button: { textHoverBg: "#bfc4da80" }
230
+ }
231
+ },
232
+ children: /* @__PURE__ */ jsx(
233
+ Button,
234
+ {
235
+ type: "text",
236
+ icon: /* @__PURE__ */ jsx(CaretRightOutlined, {}),
237
+ disabled: !replayAllScripts || replayAllScripts.length === 0,
238
+ style: {
239
+ background: replayAllMode ? "#bfc4da80" : void 0
240
+ },
241
+ onClick: () => {
242
+ setReplayAllMode(true);
243
+ },
244
+ children: "Replay All"
245
+ }
246
+ )
247
+ }
248
+ ) })
249
+ ] }),
236
250
  /* @__PURE__ */ jsx(
237
251
  PlaywrightCaseSelector,
238
252
  {
package/dist/index.css CHANGED
@@ -79,6 +79,17 @@ footer.mt-8 {
79
79
  display: flex;
80
80
  flex-direction: row;
81
81
  background: #F8F8F8;
82
+ justify-content: space-between;
83
+ }
84
+ .page-nav .page-nav-left {
85
+ display: flex;
86
+ flex-direction: row;
87
+ }
88
+ .page-nav .page-nav-toolbar {
89
+ margin-left: 20px;
90
+ }
91
+ .page-nav .page-nav-toolbar .ant-btn {
92
+ background: #E9E9E9;
82
93
  }
83
94
  .page-nav .logo img {
84
95
  height: 20px;
@@ -104,6 +115,15 @@ footer.mt-8 {
104
115
  height: 100%;
105
116
  box-sizing: border-box;
106
117
  }
118
+ .main-right .replay-all-mode-wrapper {
119
+ height: 100%;
120
+ display: flex;
121
+ flex-direction: column;
122
+ justify-content: center;
123
+ padding: 22px;
124
+ box-sizing: border-box;
125
+ margin: 0 auto;
126
+ }
107
127
  .main-right .main-content {
108
128
  display: flex;
109
129
  flex-direction: row;