@midscene/visualizer 0.6.1 → 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.
@@ -96,7 +96,7 @@ const linear = (t) => {
96
96
  const sleep = (ms) => {
97
97
  return new Promise((resolve) => setTimeout(resolve, ms));
98
98
  };
99
- const ERROR_FRAME_CANCEL = "frame cancel";
99
+ const ERROR_FRAME_CANCEL = "frame cancel (this is an error on purpose)";
100
100
  const frameKit = () => {
101
101
  let cancelFlag = false;
102
102
  return {
@@ -123,7 +123,6 @@ const frameKit = () => {
123
123
  }, ms);
124
124
  },
125
125
  cancel: () => {
126
- console.log("set frame cancel");
127
126
  cancelFlag = true;
128
127
  }
129
128
  };
@@ -137,13 +136,16 @@ const Player = () => {
137
136
  var _a;
138
137
  const [titleText, setTitleText] = (0, import_react.useState)("");
139
138
  const [subTitleText, setSubTitleText] = (0, import_react.useState)("");
140
- const scripts = (0, import_store.useExecutionDump)((store) => store.activeExecutionAnimation);
141
- const imageWidth = (0, import_store.useExecutionDump)(
142
- (store) => store.activeExecutionScreenshotWidth
139
+ const taskScripts = (0, import_store.useExecutionDump)(
140
+ (store) => store.activeExecutionAnimation
143
141
  );
144
- const imageHeight = (0, import_store.useExecutionDump)(
145
- (store) => store.activeExecutionScreenshotHeight
142
+ const replayAllMode = (0, import_store.useExecutionDump)((store) => store.replayAllMode);
143
+ const replayAllScripts = (0, import_store.useExecutionDump)(
144
+ (store) => store.allExecutionAnimation
146
145
  );
146
+ const scripts = replayAllMode ? replayAllScripts : taskScripts;
147
+ const imageWidth = (0, import_store.useExecutionDump)((store) => store.insightWidth) || 1920;
148
+ const imageHeight = (0, import_store.useExecutionDump)((store) => store.insightHeight) || 1080;
147
149
  const canvasWidth = imageWidth + canvasPaddingLeft * 2;
148
150
  const canvasHeight = imageHeight + canvasPaddingTop * 2;
149
151
  const currentImg = (0, import_react.useRef)(((_a = scripts == null ? void 0 : scripts[0]) == null ? void 0 : _a.img) || null);
@@ -167,8 +169,8 @@ const Player = () => {
167
169
  top: 0,
168
170
  width: imageWidth,
169
171
  pointer: {
170
- left: imageWidth / 2,
171
- top: imageHeight / 2
172
+ left: Math.round(imageWidth / 2),
173
+ top: Math.round(imageHeight / 2)
172
174
  }
173
175
  };
174
176
  const [animationProgress, setAnimationProgress] = (0, import_react.useState)(-1);
@@ -313,14 +315,18 @@ const Player = () => {
313
315
  const animate = (currentTime) => {
314
316
  const nextState = __spreadValues({}, cameraState.current);
315
317
  const elapsedTime = currentTime - startTime;
316
- if (shouldMovePointer && elapsedTime < pointerMoveDuration) {
317
- const rawMouseProgress = Math.min(
318
- elapsedTime / pointerMoveDuration,
319
- 1
320
- );
321
- const mouseProgress = cubicMouse(rawMouseProgress);
322
- nextState.pointer.left = startPointer.left + (targetState.pointer.left - startPointer.left) * mouseProgress;
323
- nextState.pointer.top = startPointer.top + (targetState.pointer.top - startPointer.top) * mouseProgress;
318
+ if (shouldMovePointer) {
319
+ if (elapsedTime <= pointerMoveDuration) {
320
+ const rawMouseProgress = Math.min(
321
+ elapsedTime / pointerMoveDuration,
322
+ 1
323
+ );
324
+ const mouseProgress = cubicMouse(rawMouseProgress);
325
+ nextState.pointer.left = startPointer.left + (targetState.pointer.left - startPointer.left) * mouseProgress;
326
+ nextState.pointer.top = startPointer.top + (targetState.pointer.top - startPointer.top) * mouseProgress;
327
+ } else {
328
+ nextState.pointer = targetState.pointer;
329
+ }
324
330
  }
325
331
  if (elapsedTime > cameraMoveStart) {
326
332
  const cameraElapsedTime = elapsedTime - cameraMoveStart;
@@ -575,13 +581,13 @@ const Player = () => {
575
581
  if (animationProgress < 1) {
576
582
  statusIconElement = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Spin, { indicator: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.LoadingOutlined, { spin: true }), size: "default" });
577
583
  } else if (mouseOverStatusIcon) {
578
- statusIconElement = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Spin, { indicator: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.PlayCircleOutlined, {}), size: "default" });
584
+ statusIconElement = /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Spin, { indicator: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.CaretRightOutlined, {}), size: "default" });
579
585
  statusStyle.cursor = "pointer";
580
586
  statusStyle.background = "#888";
581
587
  statusOnClick = () => setReplayMark(Date.now());
582
588
  } else {
583
589
  statusIconElement = // <Spin indicator={<CheckCircleOutlined />} size="default" />
584
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Spin, { indicator: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.PlayCircleOutlined, {}), size: "default" });
590
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_antd.Spin, { indicator: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.CaretRightOutlined, {}), size: "default" });
585
591
  }
586
592
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "player-container", children: [
587
593
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "canvas-container", ref: divContainerRef }),
@@ -73,9 +73,9 @@ const cameraStateForRect = (rect, imageWidth, imageHeight) => {
73
73
  );
74
74
  top = Math.max(top, 0);
75
75
  return {
76
- left,
77
- top,
78
- width: cameraWidth
76
+ left: Math.round(left),
77
+ top: Math.round(top),
78
+ width: Math.round(cameraWidth)
79
79
  };
80
80
  };
81
81
  const mergeTwoCameraState = (cameraState1, cameraState2) => {
@@ -231,7 +231,7 @@ const generateAnimationScripts = (execution, imageWidth, imageHeight) => {
231
231
  title: "Done",
232
232
  subTitle: initSubTitle,
233
233
  type: "img",
234
- duration: stillDuration * 0.5,
234
+ duration: stillDuration,
235
235
  camera: fullPageCameraState
236
236
  });
237
237
  return scripts;
@@ -69,11 +69,13 @@ const useExecutionDump = create((set, get) => {
69
69
  const initData = {
70
70
  dump: null,
71
71
  activeTask: null,
72
+ replayAllMode: false,
73
+ allExecutionAnimation: null,
74
+ insightWidth: null,
75
+ insightHeight: null,
72
76
  activeExecution: null,
73
77
  activeExecutionAnimation: null,
74
78
  // TODO: get from dump
75
- activeExecutionScreenshotWidth: 0,
76
- activeExecutionScreenshotHeight: 0,
77
79
  hoverTask: null,
78
80
  hoverTimestamp: null,
79
81
  hoverPreviewConfig: null
@@ -86,22 +88,75 @@ const useExecutionDump = create((set, get) => {
86
88
  const { reset } = useInsightDump.getState();
87
89
  reset();
88
90
  };
91
+ const resetActiveExecution = () => {
92
+ set({
93
+ activeExecution: null,
94
+ activeExecutionAnimation: null,
95
+ _executionDumpLoadId: ++_executionDumpLoadId
96
+ });
97
+ resetInsightDump();
98
+ };
89
99
  return __spreadProps(__spreadValues({}, initData), {
90
100
  _executionDumpLoadId,
101
+ setReplayAllMode: (replayAllMode) => {
102
+ const state = get();
103
+ if (state.allExecutionAnimation) {
104
+ set({ replayAllMode });
105
+ if (replayAllMode) {
106
+ resetActiveExecution();
107
+ }
108
+ } else {
109
+ console.error(
110
+ "allExecutionAnimation not found, failed to set replayAllMode"
111
+ );
112
+ }
113
+ },
91
114
  setGroupedDump: (dump) => {
92
115
  console.log("will set ExecutionDump", dump);
93
116
  set(__spreadProps(__spreadValues({}, initData), {
94
117
  dump
95
118
  }));
96
119
  resetInsightDump();
97
- if (dump && dump.executions.length > 0 && dump.executions[0].tasks.length > 0) {
98
- get().setActiveTask(dump.executions[0].tasks[0]);
120
+ if (dump && dump.executions.length > 0) {
121
+ let width = 0;
122
+ let height = 0;
123
+ dump.executions.forEach((execution) => {
124
+ execution.tasks.forEach((task) => {
125
+ var _a, _b, _c, _d, _e, _f, _g, _h;
126
+ if (task.type === "Insight") {
127
+ const insightTask = task;
128
+ 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;
129
+ 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;
130
+ }
131
+ });
132
+ });
133
+ if (!width || !height) {
134
+ console.warn(
135
+ "width or height not found, failed to generate animation"
136
+ );
137
+ return;
138
+ }
139
+ const allScripts = [];
140
+ dump.executions.forEach((execution) => {
141
+ const scripts = (0, import_replay_scripts.generateAnimationScripts)(execution, width, height);
142
+ if (scripts) {
143
+ allScripts.push(...scripts);
144
+ }
145
+ });
146
+ set({
147
+ allExecutionAnimation: allScripts,
148
+ _executionDumpLoadId: ++_executionDumpLoadId,
149
+ replayAllMode: true,
150
+ insightWidth: width,
151
+ insightHeight: height
152
+ });
99
153
  }
100
154
  },
101
155
  setActiveTask(task) {
102
156
  var _a;
103
157
  let parentExecution;
104
- const dump = get().dump;
158
+ const state = get();
159
+ const dump = state.dump;
105
160
  if (dump) {
106
161
  parentExecution = dump.executions.find(
107
162
  (execution) => execution.tasks.includes(task)
@@ -110,27 +165,14 @@ const useExecutionDump = create((set, get) => {
110
165
  if (!parentExecution) {
111
166
  throw new Error("parentExecution not found");
112
167
  }
113
- let width = 0;
114
- let height = 0;
115
- parentExecution.tasks.forEach((t) => {
116
- var _a2, _b, _c, _d, _e, _f, _g, _h;
117
- if (t.type === "Insight") {
118
- const insightTask = t;
119
- 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;
120
- 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;
121
- }
122
- });
168
+ const width = state.insightWidth;
169
+ const height = state.insightHeight;
123
170
  set({
171
+ replayAllMode: false,
124
172
  activeTask: task,
125
173
  activeExecution: parentExecution,
126
174
  _executionDumpLoadId: ++_executionDumpLoadId,
127
- activeExecutionAnimation: (0, import_replay_scripts.generateAnimationScripts)(
128
- parentExecution,
129
- width,
130
- height
131
- ),
132
- activeExecutionScreenshotWidth: width,
133
- activeExecutionScreenshotHeight: height
175
+ activeExecutionAnimation: width && height ? (0, import_replay_scripts.generateAnimationScripts)(parentExecution, width, height) : null
134
176
  });
135
177
  console.log("will set task", task);
136
178
  if (task.type === "Insight") {
@@ -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/lib/index.js CHANGED
@@ -64,12 +64,21 @@ var import_logo_plain_0f78df8a = __toESM(require("./assets/logo-plain.0f78df8a.p
64
64
  var import_detail_panel = __toESM(require("./component/detail-panel"));
65
65
  var import_global_hover_preview = __toESM(require("./component/global-hover-preview"));
66
66
  var import_misc = require("./component/misc");
67
+ var import_player = __toESM(require("./component/player"));
67
68
  var import_timeline = __toESM(require("./component/timeline"));
68
69
  const { Dragger } = import_antd.Upload;
69
70
  let globalRenderCount = 1;
70
71
  function Visualizer(props) {
71
72
  const { dumps, hideLogo = false } = props;
72
73
  const executionDump = (0, import_store.useExecutionDump)((store) => store.dump);
74
+ const executionDumpLoadId = (0, import_store.useExecutionDump)(
75
+ (store) => store._executionDumpLoadId
76
+ );
77
+ const replayAllMode = (0, import_store.useExecutionDump)((store) => store.replayAllMode);
78
+ const setReplayAllMode = (0, import_store.useExecutionDump)((store) => store.setReplayAllMode);
79
+ const replayAllScripts = (0, import_store.useExecutionDump)(
80
+ (store) => store.allExecutionAnimation
81
+ );
73
82
  const setGroupedDump = (0, import_store.useExecutionDump)((store) => store.setGroupedDump);
74
83
  const reset = (0, import_store.useExecutionDump)((store) => store.reset);
75
84
  const [mainLayoutChangeFlag, setMainLayoutChangeFlag] = (0, import_react.useState)(0);
@@ -149,6 +158,11 @@ function Visualizer(props) {
149
158
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "ant-upload-text", children: "All data will be processed locally by the browser. No data will be sent to the server." })
150
159
  ] })) });
151
160
  } else {
161
+ const content = replayAllMode ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "replay-all-mode-wrapper", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_player.default, {}, executionDumpLoadId) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_react_resizable_panels.PanelGroup, { autoSaveId: "page-detail-layout-v2", direction: "horizontal", children: [
162
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.Panel, { defaultSize: 75, maxSize: 95, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "main-content-container", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_detail_panel.default, {}) }) }),
163
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.PanelResizeHandle, {}),
164
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.Panel, { maxSize: 95, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "main-side", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_detail_side.default, {}) }) })
165
+ ] });
152
166
  mainContent = /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
153
167
  import_react_resizable_panels.PanelGroup,
154
168
  {
@@ -174,18 +188,7 @@ function Visualizer(props) {
174
188
  ),
175
189
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.Panel, { defaultSize: 80, maxSize: 95, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "main-right", children: [
176
190
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_timeline.default, {}, mainLayoutChangeFlag),
177
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "main-content", children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
178
- import_react_resizable_panels.PanelGroup,
179
- {
180
- autoSaveId: "page-detail-layout-v2",
181
- direction: "horizontal",
182
- children: [
183
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.Panel, { defaultSize: 75, maxSize: 95, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "main-content-container", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_detail_panel.default, {}) }) }),
184
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.PanelResizeHandle, {}),
185
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react_resizable_panels.Panel, { maxSize: 95, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "main-side", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_detail_side.default, {}) }) })
186
- ]
187
- }
188
- ) })
191
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "main-content", children: content })
189
192
  ] }) })
190
193
  ]
191
194
  }
@@ -217,25 +220,6 @@ function Visualizer(props) {
217
220
  globalRenderCount += 1;
218
221
  };
219
222
  }, []);
220
- const selectOptions = dumps == null ? void 0 : dumps.map((dump, index) => ({
221
- value: index,
222
- label: `${dump.groupName} - ${dump.groupDescription}`,
223
- groupName: dump.groupName,
224
- groupDescription: dump.groupDescription
225
- }));
226
- const selectWidget = selectOptions && selectOptions.length > 1 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
227
- import_antd.Select,
228
- {
229
- options: selectOptions,
230
- defaultValue: 0,
231
- onChange: (value) => {
232
- const dump = dumps[value];
233
- setGroupedDump(dump);
234
- },
235
- defaultOpen: true,
236
- style: { width: "100%" }
237
- }
238
- ) : null;
239
223
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
240
224
  import_antd.ConfigProvider,
241
225
  {
@@ -258,13 +242,40 @@ function Visualizer(props) {
258
242
  style: { height: containerHeight },
259
243
  children: [
260
244
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "page-nav", children: [
261
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "logo", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
262
- "img",
263
- {
264
- alt: "Midscene_logo",
265
- src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/logo-light-with-text.png"
266
- }
267
- ) }),
245
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "page-nav-left", children: [
246
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "logo", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
247
+ "img",
248
+ {
249
+ alt: "Midscene_logo",
250
+ src: "https://lf3-static.bytednsdoc.com/obj/eden-cn/vhaeh7vhabf/logo-light-with-text.png"
251
+ }
252
+ ) }),
253
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "page-nav-toolbar", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
254
+ import_antd.ConfigProvider,
255
+ {
256
+ theme: {
257
+ components: {
258
+ Button: { textHoverBg: "#bfc4da80" }
259
+ }
260
+ },
261
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
262
+ import_antd.Button,
263
+ {
264
+ type: "text",
265
+ icon: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_icons.CaretRightOutlined, {}),
266
+ disabled: !replayAllScripts || replayAllScripts.length === 0,
267
+ style: {
268
+ background: replayAllMode ? "#bfc4da80" : void 0
269
+ },
270
+ onClick: () => {
271
+ setReplayAllMode(true);
272
+ },
273
+ children: "Replay All"
274
+ }
275
+ )
276
+ }
277
+ ) })
278
+ ] }),
268
279
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
269
280
  PlaywrightCaseSelector,
270
281
  {