@midscene/visualizer 0.0.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.
- package/.eslintrc.js +9 -0
- package/README.md +24 -0
- package/dist/es/assets/logo-plain.16842bbc.svg +70 -0
- package/dist/es/assets/logo-plain2.16842bbc.svg +70 -0
- package/dist/es/component/blackboard.css +25 -0
- package/dist/es/component/blackboard.js +256 -0
- package/dist/es/component/color.js +34 -0
- package/dist/es/component/common.css +0 -0
- package/dist/es/component/detail-panel.css +34 -0
- package/dist/es/component/detail-panel.js +106 -0
- package/dist/es/component/detail-side.css +99 -0
- package/dist/es/component/detail-side.js +285 -0
- package/dist/es/component/global-hover-preview.css +19 -0
- package/dist/es/component/global-hover-preview.js +44 -0
- package/dist/es/component/misc.js +24 -0
- package/dist/es/component/panel-title.css +8 -0
- package/dist/es/component/panel-title.js +9 -0
- package/dist/es/component/side-item.js +0 -0
- package/dist/es/component/sidebar.css +87 -0
- package/dist/es/component/sidebar.js +175 -0
- package/dist/es/component/store.js +128 -0
- package/dist/es/component/timeline.css +18 -0
- package/dist/es/component/timeline.js +438 -0
- package/dist/es/index.css +89 -0
- package/dist/es/index.js +174 -0
- package/dist/es/utils.js +76 -0
- package/dist/lib/assets/logo-plain.16842bbc.svg +70 -0
- package/dist/lib/assets/logo-plain2.16842bbc.svg +70 -0
- package/dist/lib/component/blackboard.css +25 -0
- package/dist/lib/component/blackboard.js +286 -0
- package/dist/lib/component/color.js +59 -0
- package/dist/lib/component/common.css +0 -0
- package/dist/lib/component/detail-panel.css +34 -0
- package/dist/lib/component/detail-panel.js +136 -0
- package/dist/lib/component/detail-side.css +99 -0
- package/dist/lib/component/detail-side.js +313 -0
- package/dist/lib/component/global-hover-preview.css +19 -0
- package/dist/lib/component/global-hover-preview.js +64 -0
- package/dist/lib/component/misc.js +48 -0
- package/dist/lib/component/panel-title.css +8 -0
- package/dist/lib/component/panel-title.js +29 -0
- package/dist/lib/component/side-item.js +1 -0
- package/dist/lib/component/sidebar.css +87 -0
- package/dist/lib/component/sidebar.js +198 -0
- package/dist/lib/component/store.js +153 -0
- package/dist/lib/component/timeline.css +18 -0
- package/dist/lib/component/timeline.js +466 -0
- package/dist/lib/index.css +89 -0
- package/dist/lib/index.js +202 -0
- package/dist/lib/utils.js +111 -0
- package/dist/types/component/blackboard.d.ts +4 -0
- package/dist/types/component/color.d.ts +2 -0
- package/dist/types/component/detail-panel.d.ts +4 -0
- package/dist/types/component/detail-side.d.ts +4 -0
- package/dist/types/component/global-hover-preview.d.ts +4 -0
- package/dist/types/component/misc.d.ts +2 -0
- package/dist/types/component/panel-title.d.ts +6 -0
- package/dist/types/component/side-item.d.ts +0 -0
- package/dist/types/component/sidebar.d.ts +4 -0
- package/dist/types/component/store.d.ts +35 -0
- package/dist/types/component/timeline.d.ts +4 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/utils.d.ts +5 -0
- package/docs/index.tsx +6 -0
- package/modern.config.ts +15 -0
- package/package.json +46 -0
- package/src/component/assets/logo-plain.svg +70 -0
- package/src/component/assets/logo-plain2.svg +70 -0
- package/src/component/blackboard.less +37 -0
- package/src/component/blackboard.tsx +293 -0
- package/src/component/color.tsx +34 -0
- package/src/component/common.less +21 -0
- package/src/component/detail-panel.less +47 -0
- package/src/component/detail-panel.tsx +124 -0
- package/src/component/detail-side.less +131 -0
- package/src/component/detail-side.tsx +361 -0
- package/src/component/global-hover-preview.less +23 -0
- package/src/component/global-hover-preview.tsx +50 -0
- package/src/component/misc.tsx +20 -0
- package/src/component/panel-title.less +11 -0
- package/src/component/panel-title.tsx +11 -0
- package/src/component/side-item.tsx +0 -0
- package/src/component/sidebar.less +122 -0
- package/src/component/sidebar.tsx +205 -0
- package/src/component/store.tsx +151 -0
- package/src/component/timeline.less +25 -0
- package/src/component/timeline.tsx +486 -0
- package/src/global.d.ts +11 -0
- package/src/index.less +113 -0
- package/src/index.tsx +210 -0
- package/src/utils.ts +58 -0
- package/tsconfig.json +24 -0
|
@@ -0,0 +1,361 @@
|
|
|
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}: {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;
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
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;
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
@import './common.less';
|
|
2
|
+
|
|
3
|
+
.task-list-name {
|
|
4
|
+
padding: 2px @side-horizontal-padding;
|
|
5
|
+
font-weight: bold;
|
|
6
|
+
background: @title-bg;
|
|
7
|
+
border-top: 1px solid @border-color;
|
|
8
|
+
border-bottom: 1px solid @border-color;
|
|
9
|
+
// box-shadow: 1px 1px 5px @border-color;
|
|
10
|
+
margin-top: -1px;
|
|
11
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
@import './common.less';
|
|
2
|
+
|
|
3
|
+
.side-bar {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
justify-content: space-between;
|
|
7
|
+
width: 100%;
|
|
8
|
+
height: 100%;
|
|
9
|
+
border-right: 1px solid @border-color;
|
|
10
|
+
overflow: auto;
|
|
11
|
+
background: @side-bg;
|
|
12
|
+
|
|
13
|
+
.brand {
|
|
14
|
+
padding: @side-horizontal-padding 5px;
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
// margin-bottom: 10px;
|
|
17
|
+
|
|
18
|
+
// .head_name {
|
|
19
|
+
// display: inline-block;
|
|
20
|
+
// margin-left: 12px;
|
|
21
|
+
// margin-right: 30px;
|
|
22
|
+
// cursor: pointer;
|
|
23
|
+
// font-weight: bold;
|
|
24
|
+
// }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.task-meta-section {
|
|
28
|
+
margin-top: 6px;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
.task-meta {
|
|
32
|
+
color: @weak-text;
|
|
33
|
+
font-weight: normal;
|
|
34
|
+
padding-left: @side-horizontal-padding;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// side-seperator side-seperator-line side-seperator-space-up side-seperator-space-down" />
|
|
38
|
+
.side-seperator {
|
|
39
|
+
border-top: 1px solid none;
|
|
40
|
+
&.side-seperator-line {
|
|
41
|
+
border-top: 1px solid @border-color;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
&.side-seperator-space-up {
|
|
45
|
+
margin-top: @side-vertical-spacing;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
&.side-seperator-space-down {
|
|
49
|
+
margin-bottom: @side-vertical-spacing;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
.side-sub-title {
|
|
54
|
+
padding: 0 @side-horizontal-padding;
|
|
55
|
+
font-weight: bold;
|
|
56
|
+
// margin-top: @side-vertical-spacing;
|
|
57
|
+
// color: @strong-text;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
.side-item {
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
transition: .1s;
|
|
63
|
+
// margin-bottom: 9px;
|
|
64
|
+
padding: 2px 0;
|
|
65
|
+
|
|
66
|
+
&:hover {
|
|
67
|
+
background: @hover-bg;
|
|
68
|
+
color: @hover-text;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
&.selected
|
|
72
|
+
{
|
|
73
|
+
background: @selected-bg;
|
|
74
|
+
color: @selected-text;
|
|
75
|
+
|
|
76
|
+
.status-text {
|
|
77
|
+
color: @selected-text;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.side-item-content {
|
|
82
|
+
padding: 0 @side-horizontal-padding 0 calc(@side-horizontal-padding + 16px);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.side-item-name {
|
|
87
|
+
padding: 0 @side-horizontal-padding 0 calc(@side-horizontal-padding + 16px);
|
|
88
|
+
position: relative;
|
|
89
|
+
display: flex;
|
|
90
|
+
justify-content: space-between;
|
|
91
|
+
|
|
92
|
+
.status-icon{
|
|
93
|
+
position: absolute;
|
|
94
|
+
left: @side-horizontal-padding;
|
|
95
|
+
|
|
96
|
+
display: inline-block;
|
|
97
|
+
color: #AAA;
|
|
98
|
+
font-size: 10px;
|
|
99
|
+
line-height: 10px;
|
|
100
|
+
top: 50%;
|
|
101
|
+
margin-top: -5px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.status-icon-success {
|
|
105
|
+
color: rgb(58, 119, 58);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.status-icon-fail {
|
|
109
|
+
color: rgb(255, 10, 10);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.status-text {
|
|
113
|
+
color: @weak-text;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.bottom-controls{
|
|
118
|
+
padding: @side-horizontal-padding 10px;
|
|
119
|
+
text-align: left;
|
|
120
|
+
text-align: center;
|
|
121
|
+
}
|
|
122
|
+
}
|