@midscene/visualizer 0.0.1 → 0.1.1

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 (65) hide show
  1. package/dist/es/component/assets/logo-plain.js +128 -0
  2. package/dist/es/component/assets/logo-plain2.js +128 -0
  3. package/dist/es/component/blackboard.js +5 -1
  4. package/dist/es/component/detail-panel.js +2 -2
  5. package/dist/es/component/detail-side.css +3 -2
  6. package/dist/es/component/detail-side.js +14 -5
  7. package/dist/es/component/global-hover-preview.css +1 -1
  8. package/dist/es/component/global-hover-preview.js +6 -1
  9. package/dist/es/component/sidebar.css +2 -1
  10. package/dist/es/component/sidebar.js +19 -12
  11. package/dist/es/component/store.js +7 -6
  12. package/dist/es/component/timeline.js +2 -2
  13. package/dist/es/index.css +14 -9
  14. package/dist/es/index.js +33 -7
  15. package/dist/lib/component/assets/logo-plain.js +156 -0
  16. package/dist/lib/component/assets/logo-plain2.js +156 -0
  17. package/dist/lib/component/blackboard.js +5 -1
  18. package/dist/lib/component/detail-panel.js +2 -2
  19. package/dist/lib/component/detail-side.css +3 -2
  20. package/dist/lib/component/detail-side.js +14 -5
  21. package/dist/lib/component/global-hover-preview.css +1 -1
  22. package/dist/lib/component/global-hover-preview.js +6 -1
  23. package/dist/lib/component/sidebar.css +2 -1
  24. package/dist/lib/component/sidebar.js +19 -12
  25. package/dist/lib/component/store.js +7 -6
  26. package/dist/lib/component/timeline.js +2 -2
  27. package/dist/lib/index.css +14 -9
  28. package/dist/lib/index.js +37 -7
  29. package/dist/types/component/sidebar.d.ts +4 -1
  30. package/dist/types/component/store.d.ts +2 -1
  31. package/dist/types/index.d.ts +7 -2
  32. package/package.json +9 -4
  33. package/.eslintrc.js +0 -9
  34. package/dist/es/assets/logo-plain.16842bbc.svg +0 -70
  35. package/dist/es/assets/logo-plain2.16842bbc.svg +0 -70
  36. package/dist/lib/assets/logo-plain.16842bbc.svg +0 -70
  37. package/dist/lib/assets/logo-plain2.16842bbc.svg +0 -70
  38. package/docs/index.tsx +0 -6
  39. package/modern.config.ts +0 -15
  40. package/src/component/assets/logo-plain.svg +0 -70
  41. package/src/component/assets/logo-plain2.svg +0 -70
  42. package/src/component/blackboard.less +0 -37
  43. package/src/component/blackboard.tsx +0 -293
  44. package/src/component/color.tsx +0 -34
  45. package/src/component/common.less +0 -21
  46. package/src/component/detail-panel.less +0 -47
  47. package/src/component/detail-panel.tsx +0 -124
  48. package/src/component/detail-side.less +0 -131
  49. package/src/component/detail-side.tsx +0 -361
  50. package/src/component/global-hover-preview.less +0 -23
  51. package/src/component/global-hover-preview.tsx +0 -50
  52. package/src/component/misc.tsx +0 -20
  53. package/src/component/panel-title.less +0 -11
  54. package/src/component/panel-title.tsx +0 -11
  55. package/src/component/side-item.tsx +0 -0
  56. package/src/component/sidebar.less +0 -122
  57. package/src/component/sidebar.tsx +0 -205
  58. package/src/component/store.tsx +0 -151
  59. package/src/component/timeline.less +0 -25
  60. package/src/component/timeline.tsx +0 -486
  61. package/src/global.d.ts +0 -11
  62. package/src/index.less +0 -113
  63. package/src/index.tsx +0 -210
  64. package/src/utils.ts +0 -58
  65. package/tsconfig.json +0 -24
@@ -1,124 +0,0 @@
1
- 'use client';
2
- import './detail-panel.less';
3
- import { Segmented, ConfigProvider } from 'antd';
4
- import { CameraOutlined, FileTextOutlined, ScheduleOutlined } from '@ant-design/icons';
5
- import { useEffect, useState } from 'react';
6
- import BlackBoard from './blackboard';
7
- import { useExecutionDump, useInsightDump } from '@/component/store';
8
- import { timeStr, filterBase64Value } from '@/utils';
9
-
10
- const ScreenshotItem = (props: { time: string; img: string }) => {
11
- return (
12
- <div className="screenshot-item">
13
- <div className="screenshot-item-title">{props.time}</div>
14
- <div>
15
- <img src={props.img} />
16
- </div>
17
- </div>
18
- );
19
- };
20
-
21
- const VIEW_TYPE_SCREENSHOT = 'screenshot';
22
- const VIEW_TYPE_JSON = 'json';
23
- const VIEW_TYPE_BLACKBOARD = 'blackboard';
24
-
25
- const DetailPanel = (): JSX.Element => {
26
- const dumpId = useInsightDump((store) => store._loadId);
27
- const blackboardViewAvailable = Boolean(dumpId);
28
- const activeTask = useExecutionDump((store) => store.activeTask);
29
- const [preferredViewType, setViewType] = useState(dumpId ? VIEW_TYPE_BLACKBOARD : VIEW_TYPE_SCREENSHOT);
30
-
31
- const viewType =
32
- preferredViewType === VIEW_TYPE_BLACKBOARD && !dumpId ? VIEW_TYPE_SCREENSHOT : preferredViewType;
33
-
34
- let content;
35
- if (!activeTask) {
36
- content = <div>please select a task</div>;
37
- } else if (viewType === VIEW_TYPE_JSON) {
38
- content = (
39
- <div className="json-content">{filterBase64Value(JSON.stringify(activeTask, undefined, 2))}</div>
40
- );
41
- } else if (viewType === VIEW_TYPE_BLACKBOARD) {
42
- if (dumpId) {
43
- content = <BlackBoard key={`${dumpId}`} />;
44
- } else {
45
- content = <div>invalid view</div>;
46
- }
47
- } else if (viewType === VIEW_TYPE_SCREENSHOT) {
48
- if (activeTask.recorder?.length) {
49
- content = (
50
- <div>
51
- {activeTask.recorder
52
- .filter((item) => item.screenshot)
53
- .map((item, index) => {
54
- const fullTime = timeStr(item.ts);
55
- const str = item.timing ? `${fullTime} / ${item.timing}` : fullTime;
56
- return <ScreenshotItem key={index} time={str} img={item.screenshot!} />;
57
- })}
58
- </div>
59
- );
60
- } else {
61
- content = <div>no screenshot</div>; // TODO: pretty error message
62
- }
63
- }
64
-
65
- useEffect(() => {
66
- // hit `Tab` to toggle viewType
67
- const handleKeyDown = (e: KeyboardEvent) => {
68
- if (e.key === 'Tab') {
69
- if (viewType === VIEW_TYPE_BLACKBOARD) {
70
- setViewType(VIEW_TYPE_SCREENSHOT);
71
- } else if (viewType === VIEW_TYPE_SCREENSHOT) {
72
- setViewType(VIEW_TYPE_JSON);
73
- } else {
74
- setViewType(blackboardViewAvailable ? VIEW_TYPE_BLACKBOARD : VIEW_TYPE_SCREENSHOT);
75
- }
76
- e.preventDefault();
77
- }
78
- };
79
-
80
- document.addEventListener('keydown', handleKeyDown);
81
-
82
- return () => {
83
- document.removeEventListener('keydown', handleKeyDown);
84
- };
85
- });
86
-
87
- const options = [
88
- { label: 'Screenshots', value: VIEW_TYPE_SCREENSHOT, icon: <CameraOutlined /> },
89
- { label: 'JSON View', value: VIEW_TYPE_JSON, icon: <FileTextOutlined /> },
90
- ];
91
- if (blackboardViewAvailable) {
92
- options.unshift({ label: 'Visualization', value: VIEW_TYPE_BLACKBOARD, icon: <ScheduleOutlined /> });
93
- }
94
- return (
95
- <div className="detail-panel">
96
- <div className="view-switcher">
97
- <ConfigProvider
98
- theme={{
99
- components: {
100
- Segmented: {
101
- itemSelectedBg: '#D7D7D7',
102
- itemSelectedColor: '#000000',
103
- // itemHoverColor: '#ffffff',
104
- // itemHoverBg: '#A3D6D2', // @sub-blue
105
- /* 这里是你的组件 token */
106
- },
107
- },
108
- }}
109
- >
110
- <Segmented
111
- options={options}
112
- value={viewType}
113
- onChange={(value: any) => {
114
- setViewType(value);
115
- }}
116
- />
117
- </ConfigProvider>
118
- </div>
119
- <div className="detail-content">{content}</div>
120
- </div>
121
- );
122
- };
123
-
124
- export default DetailPanel;
@@ -1,131 +0,0 @@
1
- @import './common.less';
2
-
3
- @subSpacing: 5px;
4
- @lightTextColor: #777;
5
-
6
- .detail-side {
7
- h2 {
8
- padding-top: 0;
9
- margin-top: 0;
10
- margin-bottom: 4px;
11
- }
12
-
13
- .ant-tag{
14
- margin-top: 2px;
15
- }
16
-
17
- .meta-kv {
18
- padding: @side-vertical-spacing @side-horizontal-padding calc(@side-vertical-spacing + 4px);
19
- .meta {
20
- box-sizing: border-box;
21
- padding: 2px 0;
22
- width: 100%;
23
- display: flex;
24
- flex-direction: row;
25
- line-height: 1.5;
26
-
27
- .meta-key {
28
- // font-weight: bold;
29
- width:100px;
30
- text-align: right;
31
- padding-right: 10px;
32
- // color: #999;
33
- }
34
-
35
- .meta-value {
36
- flex: 1;
37
- }
38
- }
39
- }
40
-
41
- .item-list {
42
- padding: @side-vertical-spacing @side-horizontal-padding;
43
-
44
- cursor:default;
45
- margin-bottom: 10px;
46
-
47
- .item {
48
- padding: 16px @side-horizontal-padding @side-horizontal-padding;
49
- transition: .1s;
50
- border: 1px solid #DDD;
51
- border-radius: @subSpacing;
52
- margin-bottom: @side-vertical-spacing;
53
- position: relative;
54
-
55
- &.item-lite {
56
- border: none;
57
- padding: 0;
58
- }
59
- }
60
-
61
- .item-highlight {
62
- color: #FFF;
63
- .subtitle {
64
- color: #CCC;
65
- }
66
- }
67
-
68
- .item-extra {
69
- position: absolute;
70
- right: @side-horizontal-padding;
71
- top: @side-horizontal-padding;
72
- color: @lightTextColor;
73
- }
74
-
75
- .title-right-padding {
76
- padding-right: 15px;
77
- }
78
-
79
- .title {
80
- font-size: 18px;
81
- font-weight: bold;
82
- margin-bottom: @subSpacing;
83
-
84
- .title-tag {
85
- display: inline-block;
86
- margin-left: 6px;
87
- color: @lightTextColor;
88
- font-size: 14px;
89
- line-height: 18px;
90
- }
91
- }
92
-
93
- .subtitle {
94
- font-weight: normal;
95
- font-size: 14px;
96
- color: #777;
97
- }
98
-
99
- .description {
100
- margin-top: @subSpacing;
101
- }
102
-
103
- .description-content {
104
- font-size: 14px;
105
- margin-top: @side-horizontal-padding;
106
- white-space: break-spaces;
107
- margin: 0;
108
- }
109
-
110
- .element-button:hover {
111
- // font-weight: bold;
112
- color: #fff;
113
- background: @main-orange;
114
- }
115
-
116
- .section-button:hover {
117
- color: #fff;
118
- background: #01204E;
119
- }
120
- }
121
-
122
- .context {
123
- pre {
124
- text-wrap: balance;
125
- }
126
- }
127
-
128
- .item-list-space-up {
129
- margin-top: @side-vertical-spacing;
130
- }
131
- }
@@ -1,361 +0,0 @@
1
- /* eslint-disable max-lines */
2
- 'use client';
3
- import './detail-side.less';
4
- import { RadiusSettingOutlined } from '@ant-design/icons';
5
- import { Tooltip, Tag, Timeline, TimelineItemProps } from 'antd';
6
- import {
7
- BaseElement,
8
- ExecutionTaskAction,
9
- ExecutionTaskInsightLocate,
10
- ExecutionTaskInsightQuery,
11
- ExecutionTaskPlanning,
12
- UISection,
13
- } from '@midscene/core';
14
- import { highlightColorForType } from './color';
15
- import { useExecutionDump, useInsightDump } from './store';
16
- import PanelTitle from './panel-title';
17
- import { timeCostStrElement } from './misc';
18
- import { timeStr, typeStr } from '@/utils';
19
-
20
- const noop = () => {};
21
- const Card = (props: {
22
- liteMode?: boolean;
23
- highlightWithColor?: string;
24
- title?: string;
25
- subtitle?: string;
26
- characteristic?: string;
27
- onMouseEnter?: () => void;
28
- onMouseLeave?: () => void;
29
- content: any;
30
- }) => {
31
- const { highlightWithColor, title, subtitle, onMouseEnter, onMouseLeave, content, characteristic } = props;
32
- const titleTag = props.characteristic ? (
33
- <div className="item-extra">
34
- <div className="title-tag">
35
- <Tooltip placement="bottomRight" title={characteristic} mouseEnterDelay={0}>
36
- <span>
37
- <RadiusSettingOutlined />
38
- </span>
39
- </Tooltip>
40
- </div>
41
- </div>
42
- ) : null;
43
-
44
- const titleRightPaddingClass = props.characteristic ? 'title-right-padding' : '';
45
- const modeClass = props.liteMode ? 'item-lite' : '';
46
- const highlightStyle = highlightWithColor ? { backgroundColor: highlightWithColor } : {};
47
- return (
48
- <div
49
- className={`item ${modeClass} ${highlightWithColor ? 'item-highlight' : ''}`}
50
- style={{ ...highlightStyle }}
51
- onMouseEnter={onMouseEnter}
52
- onMouseLeave={onMouseLeave}
53
- >
54
- {/* {extraSection} */}
55
-
56
- <div className={`title ${titleRightPaddingClass}`} style={{ display: title ? 'block' : 'none' }}>
57
- {title}
58
- {titleTag}
59
- </div>
60
- <div className={`subtitle ${titleRightPaddingClass}`} style={{ display: subtitle ? 'block' : 'none' }}>
61
- {subtitle}
62
- </div>
63
- <div className="description" style={{ display: content ? 'block' : 'none' }}>
64
- {content}
65
- </div>
66
- </div>
67
- );
68
- };
69
-
70
- const MetaKV = (props: { data: { key: string; content: string | JSX.Element }[] }) => {
71
- return (
72
- <div className="meta-kv">
73
- {props.data.map((item, index) => {
74
- return (
75
- <div className="meta" key={index}>
76
- <div className="meta-key">{item.key}</div>
77
- <div className="meta-value">{item.content}</div>
78
- </div>
79
- );
80
- })}
81
- </div>
82
- );
83
- };
84
-
85
- const objectWithoutKeys = (obj: Record<string, unknown>, keys: string[]) =>
86
- Object.keys(obj).reduce((acc, key) => {
87
- if (!keys.includes(key)) {
88
- (acc as any)[key] = obj[key];
89
- }
90
- return acc;
91
- }, {});
92
-
93
- const DetailSide = (): JSX.Element => {
94
- const task = useExecutionDump((store) => store.activeTask);
95
- const dump = useInsightDump((store) => store.data);
96
- const { matchedSection: sections, matchedElement: elements } = dump || {};
97
- const highlightSectionNames = useInsightDump((store) => store.highlightSectionNames);
98
- const highlightElements = useInsightDump((store) => store.highlightElements);
99
- const setHighlightSectionNames = useInsightDump((store) => store.setHighlightSectionNames);
100
- const setHighlightElements = useInsightDump((store) => store.setHighlightElements);
101
-
102
- const setHighlightSectionName = function (name: string) {
103
- setHighlightSectionNames([name]);
104
- };
105
- const setHighlightElement = function (element: BaseElement) {
106
- setHighlightElements([element]);
107
- };
108
-
109
- const unhighlightSection = function () {
110
- setHighlightSectionNames([]);
111
- };
112
-
113
- const unhighlightElement = function () {
114
- setHighlightElements([]);
115
- };
116
-
117
- const kv = function (data: Record<string, unknown>) {
118
- const isElementItem = (value: unknown): value is BaseElement =>
119
- Boolean(value) &&
120
- typeof value === 'object' &&
121
- typeof (value as any).content !== 'undefined' &&
122
- Boolean((value as any).center) &&
123
- Boolean((value as any).rect);
124
-
125
- const isSectionItem = (value: unknown): value is UISection =>
126
- Boolean(value) &&
127
- typeof (value as any)?.sectionCharacteristics !== 'undefined' &&
128
- typeof (value as any)?.rect !== 'undefined';
129
-
130
- const elementEl = (value: BaseElement) => (
131
- <span
132
- onMouseEnter={() => {
133
- setHighlightElement(value);
134
- }}
135
- onMouseLeave={unhighlightElement}
136
- >
137
- <Tag bordered={false} color="orange" className="element-button">
138
- Element
139
- </Tag>
140
- </span>
141
- );
142
-
143
- const sectionEl = (value: UISection) => (
144
- <span
145
- onMouseEnter={() => {
146
- setHighlightSectionName(value.name);
147
- }}
148
- onMouseLeave={unhighlightSection}
149
- >
150
- <Tag bordered={false} color="blue" className="section-button">
151
- Section
152
- </Tag>
153
- </span>
154
- );
155
-
156
- if (Array.isArray(data) || typeof data !== 'object') {
157
- return <pre className="description-content">{JSON.stringify(data, undefined, 2)}</pre>;
158
- }
159
-
160
- return Object.keys(data).map((key) => {
161
- const value = data[key];
162
- let content;
163
- if (typeof value === 'object' && isElementItem(value)) {
164
- content = elementEl(value);
165
- } else if (Array.isArray(value) && value.some((item) => isElementItem(item))) {
166
- content = value.map((item, index) => <span key={index}>{elementEl(item)}</span>);
167
- } else if (typeof value === 'object' && isSectionItem(value)) {
168
- content = sectionEl(value);
169
- } else if (Array.isArray(value) && value.some((item) => isSectionItem(item))) {
170
- content = value.map((item, index) => <span key={index}>{sectionEl(item)}</span>);
171
- } else {
172
- content = typeof value === 'string' ? value : JSON.stringify(value, undefined, 2);
173
- }
174
-
175
- return (
176
- <pre className="description-content" key={key}>
177
- {key}:&nbsp;{content}
178
- </pre>
179
- );
180
- });
181
- };
182
-
183
- const metaKVElement = MetaKV({
184
- data: [
185
- {
186
- key: 'Status',
187
- content: task?.status || '',
188
- },
189
- {
190
- key: 'Start',
191
- content: timeStr(task?.timing?.start),
192
- },
193
- {
194
- key: 'End',
195
- content: timeStr(task?.timing?.end),
196
- },
197
- {
198
- key: 'Time Cost',
199
- content: timeCostStrElement(task?.timing?.cost),
200
- },
201
- ],
202
- });
203
-
204
- let taskParam: JSX.Element | null = null;
205
- if (task?.type === 'Planning') {
206
- taskParam = MetaKV({
207
- data: [
208
- { key: 'type', content: (task && typeStr(task)) || '' },
209
- { key: 'param', content: (task as ExecutionTaskPlanning)?.param?.userPrompt },
210
- ],
211
- });
212
- } else if (task?.type === 'Insight') {
213
- taskParam = MetaKV({
214
- data: [
215
- { key: 'type', content: (task && typeStr(task)) || '' },
216
- {
217
- key: 'param',
218
- content: JSON.stringify(
219
- (task as ExecutionTaskInsightLocate)?.param?.prompt ||
220
- (task as ExecutionTaskInsightQuery)?.param?.dataDemand,
221
- ),
222
- },
223
- ],
224
- });
225
- } else if (task?.type === 'Action') {
226
- taskParam = MetaKV({
227
- data: [
228
- { key: 'type', content: (task && typeStr(task)) || '' },
229
- {
230
- key: 'value',
231
- content: JSON.stringify((task as ExecutionTaskAction)?.param?.value, undefined, 2),
232
- },
233
- ],
234
- });
235
- }
236
-
237
- const matchedSectionsEl = sections?.length
238
- ? sections.map((section) => {
239
- const { name } = section;
240
- const ifHighlight = highlightSectionNames.includes(name);
241
-
242
- const kvToShow = objectWithoutKeys(section as Record<string, any>, [
243
- 'name',
244
- 'description',
245
- 'texts',
246
- 'rect',
247
- 'sectionCharacteristics',
248
- ]);
249
- const sectionKV = Object.keys(kvToShow).length ? kv(kvToShow) : null;
250
- const highlightColor = ifHighlight ? highlightColorForType('section') : undefined;
251
-
252
- return (
253
- <Card
254
- title={section.name}
255
- highlightWithColor={highlightColor}
256
- subtitle={section.description}
257
- characteristic={section.sectionCharacteristics}
258
- onMouseEnter={setHighlightSectionName.bind(this, name)}
259
- onMouseLeave={unhighlightSection}
260
- content={sectionKV}
261
- key={name}
262
- />
263
- );
264
- })
265
- : null;
266
-
267
- const matchedElementsEl = elements?.length
268
- ? elements.map((element, idx) => {
269
- const ifHighlight = highlightElements.includes(element);
270
- const highlightColor = ifHighlight ? highlightColorForType('element') : undefined;
271
-
272
- const elementKV = kv(
273
- objectWithoutKeys(element as any, [
274
- 'content',
275
- 'rect',
276
- 'center',
277
- 'left',
278
- 'top',
279
- 'right',
280
- 'bottom',
281
- 'locator',
282
- ]),
283
- );
284
-
285
- return (
286
- <Card
287
- title={element.content}
288
- highlightWithColor={highlightColor}
289
- subtitle=""
290
- onMouseEnter={setHighlightElement.bind(this, element)}
291
- onMouseLeave={unhighlightElement}
292
- content={elementKV}
293
- key={idx}
294
- />
295
- );
296
- })
297
- : null;
298
-
299
- // const [showQuery, setShowQuery] = useState(false);
300
-
301
- const errorSection = dump?.error ? (
302
- <Card
303
- liteMode={true}
304
- title="Error"
305
- onMouseEnter={noop}
306
- onMouseLeave={noop}
307
- content={
308
- <pre className="description-content" style={{ color: '#F00' }}>
309
- {dump.error}
310
- </pre>
311
- }
312
- />
313
- ) : null;
314
-
315
- const dataCard = dump?.data ? (
316
- <Card liteMode={true} onMouseEnter={noop} onMouseLeave={noop} content={<pre>{kv(dump.data)}</pre>}></Card>
317
- ) : null;
318
-
319
- const plans = (task as ExecutionTaskPlanning)?.output?.plans;
320
- let timelineData: TimelineItemProps[] = [];
321
- if (plans) {
322
- timelineData = timelineData.concat(
323
- plans.map((item) => {
324
- return {
325
- color: '#06B1AB',
326
- children: (
327
- <>
328
- <p>
329
- <b>{typeStr(item as any)}</b>
330
- </p>
331
- <p>{item.thought}</p>
332
- <p>{item.param ? JSON.stringify(item.param || {}, undefined, 2) : null}</p>
333
- </>
334
- ),
335
- };
336
- }),
337
- );
338
- }
339
-
340
- return (
341
- <div className="detail-side">
342
- {/* Meta */}
343
- <PanelTitle title="Task Meta" />
344
- {metaKVElement}
345
- {/* Param */}
346
- <PanelTitle title="Param" />
347
- {taskParam}
348
- {/* Response */}
349
- <PanelTitle title="Output" />
350
- <div className="item-list item-list-space-up">
351
- {errorSection}
352
- {dataCard}
353
- {matchedSectionsEl}
354
- {matchedElementsEl}
355
- <Timeline items={timelineData} />
356
- </div>
357
- </div>
358
- );
359
- };
360
-
361
- export default DetailSide;
@@ -1,23 +0,0 @@
1
- @import './common.less';
2
-
3
- @max-size: 400px;
4
- .global-hover-preview {
5
- position: fixed;
6
- display: block;
7
- max-width: @max-size;
8
- max-height: @max-size;
9
- overflow: hidden;
10
- z-index: 10;
11
- text-align: center;
12
- border: 1px solid @border-color;
13
- box-sizing: border-box;
14
- background: @side-bg;
15
- box-shadow: 1px 1px 5px 0 rgba(0, 0, 0, 0.2);
16
-
17
- img {
18
- max-width: @max-size;
19
- max-height: @max-size;
20
- width: auto;
21
- height: auto;
22
- }
23
- }
@@ -1,50 +0,0 @@
1
- import { useRef, useState } from 'react';
2
- import { useExecutionDump } from './store';
3
- import './global-hover-preview.less';
4
-
5
- const size = 400; // @max-size
6
- const GlobalHoverPreview = () => {
7
- const wrapperRef = useRef<HTMLDivElement>(null);
8
- const hoverTask = useExecutionDump((store) => store.hoverTask);
9
- const hoverPreviewConfig = useExecutionDump((store) => store.hoverPreviewConfig);
10
- const [imageW, setImageW] = useState(size);
11
- const [imageH, setImageH] = useState(size);
12
-
13
- const images = hoverTask?.recorder
14
- ?.filter((item) => {
15
- return item.screenshot;
16
- })
17
- .map((item) => item.screenshot);
18
-
19
- const { x, y } = hoverPreviewConfig || {};
20
- let left = 0;
21
- let top = 0;
22
-
23
- const shouldShow = images?.length && typeof x !== 'undefined' && typeof y !== 'undefined';
24
- if (shouldShow) {
25
- const { clientWidth, clientHeight } = document.body;
26
- const widthInPractice = imageW >= imageH ? size : size * (imageW / imageH);
27
- const heightInPractice = imageW >= imageH ? size * (imageH / imageW) : size;
28
- left = x + widthInPractice > clientWidth ? clientWidth - widthInPractice : x;
29
- top = y + heightInPractice > clientHeight ? clientHeight - heightInPractice : y;
30
- }
31
- // if x + size exceed the screen width, use (screenWidth - size) instead
32
-
33
- return shouldShow ? (
34
- <div className="global-hover-preview" style={{ left, top }} ref={wrapperRef}>
35
- {images?.length ? (
36
- <img
37
- src={images[0]}
38
- onLoad={(img) => {
39
- const imgElement = img.target as HTMLImageElement;
40
- const width = imgElement.naturalWidth;
41
- const height = imgElement.naturalHeight;
42
- setImageW(width);
43
- setImageH(height);
44
- }}
45
- />
46
- ) : null}
47
- </div>
48
- ) : null;
49
- };
50
- export default GlobalHoverPreview;
@@ -1,20 +0,0 @@
1
- export function timeCostStrElement(timeCost?: number) {
2
- let str: string;
3
- if (typeof timeCost !== 'number') {
4
- str = '- ms';
5
- } else if (timeCost > 1000) {
6
- str = `${(timeCost / 1000).toFixed(2)}s`;
7
- } else {
8
- str = `${timeCost}ms`;
9
- }
10
- return (
11
- <span
12
- style={{
13
- fontVariantNumeric: 'tabular-nums',
14
- fontFeatureSettings: 'tnum',
15
- }}
16
- >
17
- {str}
18
- </span>
19
- );
20
- }