@midscene/visualizer 0.0.1 → 0.1.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.
- package/dist/es/component/assets/logo-plain.js +128 -0
- package/dist/es/component/assets/logo-plain2.js +128 -0
- package/dist/es/component/detail-panel.js +2 -2
- package/dist/es/component/detail-side.css +1 -1
- package/dist/es/component/detail-side.js +10 -1
- package/dist/es/component/global-hover-preview.css +1 -1
- package/dist/es/component/sidebar.css +2 -1
- package/dist/es/component/sidebar.js +19 -12
- package/dist/es/component/store.js +4 -4
- package/dist/es/component/timeline.js +1 -1
- package/dist/es/index.css +2 -2
- package/dist/es/index.js +11 -6
- package/dist/lib/component/assets/logo-plain.js +156 -0
- package/dist/lib/component/assets/logo-plain2.js +156 -0
- package/dist/lib/component/detail-panel.js +2 -2
- package/dist/lib/component/detail-side.css +1 -1
- package/dist/lib/component/detail-side.js +10 -1
- package/dist/lib/component/global-hover-preview.css +1 -1
- package/dist/lib/component/sidebar.css +2 -1
- package/dist/lib/component/sidebar.js +19 -12
- package/dist/lib/component/store.js +4 -4
- package/dist/lib/component/timeline.js +1 -1
- package/dist/lib/index.css +2 -2
- package/dist/lib/index.js +15 -6
- package/dist/types/component/sidebar.d.ts +4 -1
- package/dist/types/index.d.ts +7 -2
- package/package.json +9 -4
- package/.eslintrc.js +0 -9
- package/dist/es/assets/logo-plain.16842bbc.svg +0 -70
- package/dist/es/assets/logo-plain2.16842bbc.svg +0 -70
- package/dist/lib/assets/logo-plain.16842bbc.svg +0 -70
- package/dist/lib/assets/logo-plain2.16842bbc.svg +0 -70
- package/docs/index.tsx +0 -6
- package/modern.config.ts +0 -15
- package/src/component/assets/logo-plain.svg +0 -70
- package/src/component/assets/logo-plain2.svg +0 -70
- package/src/component/blackboard.less +0 -37
- package/src/component/blackboard.tsx +0 -293
- package/src/component/color.tsx +0 -34
- package/src/component/common.less +0 -21
- package/src/component/detail-panel.less +0 -47
- package/src/component/detail-panel.tsx +0 -124
- package/src/component/detail-side.less +0 -131
- package/src/component/detail-side.tsx +0 -361
- package/src/component/global-hover-preview.less +0 -23
- package/src/component/global-hover-preview.tsx +0 -50
- package/src/component/misc.tsx +0 -20
- package/src/component/panel-title.less +0 -11
- package/src/component/panel-title.tsx +0 -11
- package/src/component/side-item.tsx +0 -0
- package/src/component/sidebar.less +0 -122
- package/src/component/sidebar.tsx +0 -205
- package/src/component/store.tsx +0 -151
- package/src/component/timeline.less +0 -25
- package/src/component/timeline.tsx +0 -486
- package/src/global.d.ts +0 -11
- package/src/index.less +0 -113
- package/src/index.tsx +0 -210
- package/src/utils.ts +0 -58
- package/tsconfig.json +0 -24
|
@@ -1,293 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import * as PIXI from 'pixi.js';
|
|
3
|
-
import { ReactElement, useEffect, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { Checkbox } from 'antd';
|
|
5
|
-
import type { CheckboxProps } from 'antd';
|
|
6
|
-
import { Rect } from '../../../midscene/dist/types';
|
|
7
|
-
import { highlightColorForType, colorForName } from './color';
|
|
8
|
-
import './blackboard.less';
|
|
9
|
-
import { useBlackboardPreference, useInsightDump } from './store';
|
|
10
|
-
|
|
11
|
-
const itemFillAlpha = 0.3;
|
|
12
|
-
const bgOnAlpha = 0.8;
|
|
13
|
-
const bgOffAlpha = 0.3;
|
|
14
|
-
const noop = () => {
|
|
15
|
-
// noop
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const BlackBoard = (): JSX.Element => {
|
|
19
|
-
const dump = useInsightDump((store) => store.data);
|
|
20
|
-
const setHighlightSectionNames = useInsightDump((store) => store.setHighlightSectionNames);
|
|
21
|
-
const setHighlightElements = useInsightDump((store) => store.setHighlightElements);
|
|
22
|
-
const highlightSectionNames = useInsightDump((store) => store.highlightSectionNames);
|
|
23
|
-
const highlightElements = useInsightDump((store) => store.highlightElements);
|
|
24
|
-
|
|
25
|
-
const { context, matchedSection: sections, matchedElement: elements } = dump!;
|
|
26
|
-
const { size, screenshotBase64 } = context;
|
|
27
|
-
|
|
28
|
-
const screenWidth = size.width;
|
|
29
|
-
const screenHeight = size.height;
|
|
30
|
-
|
|
31
|
-
const domRef = useRef<HTMLDivElement>(null); // Should be HTMLDivElement not HTMLInputElement
|
|
32
|
-
const app = useMemo<PIXI.Application>(() => new PIXI.Application(), []);
|
|
33
|
-
const [appInitialed, setAppInitialed] = useState(false);
|
|
34
|
-
|
|
35
|
-
const itemMarkContainer = useMemo(() => new PIXI.Container(), []);
|
|
36
|
-
const textContainer = useMemo(() => new PIXI.Container(), []);
|
|
37
|
-
|
|
38
|
-
// key overlays
|
|
39
|
-
const pixiBgRef = useRef<PIXI.Sprite>();
|
|
40
|
-
const { bgVisible, setBgVisible, textsVisible, setTextsVisible } = useBlackboardPreference();
|
|
41
|
-
|
|
42
|
-
useEffect(() => {
|
|
43
|
-
Promise.resolve(
|
|
44
|
-
(async () => {
|
|
45
|
-
if (!domRef.current || !screenWidth) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
await app.init({ width: screenWidth, height: screenHeight, background: 0xffffff });
|
|
49
|
-
const canvasEl = domRef.current;
|
|
50
|
-
domRef.current.appendChild(app.canvas); // Ensure app.view is appended
|
|
51
|
-
const { clientWidth } = domRef.current.parentElement!;
|
|
52
|
-
const targetHeight = window.innerHeight * 0.7;
|
|
53
|
-
const viewportRatio = clientWidth / targetHeight;
|
|
54
|
-
if (screenWidth / screenHeight <= viewportRatio) {
|
|
55
|
-
const ratio = targetHeight / screenHeight;
|
|
56
|
-
canvasEl.style.width = `${Math.floor(screenWidth * ratio)}px`;
|
|
57
|
-
canvasEl.style.height = `${Math.floor(screenHeight * ratio)}px`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
app.stage.addChild(itemMarkContainer);
|
|
61
|
-
app.stage.addChild(textContainer);
|
|
62
|
-
|
|
63
|
-
setAppInitialed(true);
|
|
64
|
-
})(),
|
|
65
|
-
);
|
|
66
|
-
|
|
67
|
-
// Clean up the PIXI application when the component unmounts
|
|
68
|
-
return () => {
|
|
69
|
-
console.log('will destroy');
|
|
70
|
-
app?.destroy(true, { children: true, texture: true });
|
|
71
|
-
};
|
|
72
|
-
}, [app, screenWidth, screenHeight]);
|
|
73
|
-
|
|
74
|
-
// draw all texts on PIXI app
|
|
75
|
-
useEffect(() => {
|
|
76
|
-
if (!appInitialed) {
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// draw the screenshot base64
|
|
81
|
-
const img = new Image();
|
|
82
|
-
img.src = screenshotBase64;
|
|
83
|
-
img.onload = () => {
|
|
84
|
-
const screenshotTexture = PIXI.Texture.from(img);
|
|
85
|
-
const screenshotSprite = new PIXI.Sprite(screenshotTexture);
|
|
86
|
-
screenshotSprite.x = 0;
|
|
87
|
-
screenshotSprite.y = 0;
|
|
88
|
-
screenshotSprite.width = screenWidth;
|
|
89
|
-
screenshotSprite.height = screenHeight;
|
|
90
|
-
app.stage.addChildAt(screenshotSprite, 0);
|
|
91
|
-
pixiBgRef.current = screenshotSprite;
|
|
92
|
-
screenshotSprite.alpha = bgVisible ? bgOnAlpha : bgOffAlpha;
|
|
93
|
-
};
|
|
94
|
-
}, [app.stage, appInitialed]);
|
|
95
|
-
|
|
96
|
-
const rectMarkForItem = (
|
|
97
|
-
rect: Rect,
|
|
98
|
-
name: string,
|
|
99
|
-
themeColor: string,
|
|
100
|
-
alpha: number,
|
|
101
|
-
onPointOver: () => void,
|
|
102
|
-
onPointerOut: () => void,
|
|
103
|
-
) => {
|
|
104
|
-
const { left, top, width, height } = rect;
|
|
105
|
-
const graphics = new PIXI.Graphics();
|
|
106
|
-
graphics.beginFill(themeColor, alpha);
|
|
107
|
-
graphics.lineStyle(1, themeColor, 1);
|
|
108
|
-
graphics.drawRect(left, top, width, height);
|
|
109
|
-
graphics.endFill();
|
|
110
|
-
graphics.interactive = true;
|
|
111
|
-
graphics.on('pointerover', onPointOver);
|
|
112
|
-
graphics.on('pointerout', onPointerOut);
|
|
113
|
-
|
|
114
|
-
const nameFontSize = 18;
|
|
115
|
-
const texts = new PIXI.Text(name, {
|
|
116
|
-
fontSize: nameFontSize,
|
|
117
|
-
fill: 0x0,
|
|
118
|
-
});
|
|
119
|
-
texts.x = left;
|
|
120
|
-
texts.y = Math.max(top - (nameFontSize + 4), 0);
|
|
121
|
-
return [graphics, texts];
|
|
122
|
-
};
|
|
123
|
-
|
|
124
|
-
const { highlightSectionRects, highlightElementRects } = useMemo(() => {
|
|
125
|
-
const highlightSectionRects: Rect[] = [];
|
|
126
|
-
const highlightElementRects: Rect[] = [];
|
|
127
|
-
|
|
128
|
-
itemMarkContainer.removeChildren();
|
|
129
|
-
textContainer.removeChildren();
|
|
130
|
-
|
|
131
|
-
sections.forEach((section) => {
|
|
132
|
-
// draw a section overlay
|
|
133
|
-
const ifHighlight = highlightSectionNames.includes(section.name);
|
|
134
|
-
if (ifHighlight) {
|
|
135
|
-
highlightSectionRects.push(section.rect);
|
|
136
|
-
}
|
|
137
|
-
const [graphics, texts] = rectMarkForItem(
|
|
138
|
-
section.rect,
|
|
139
|
-
section.name,
|
|
140
|
-
ifHighlight ? highlightColorForType('section') : colorForName('section', section.name),
|
|
141
|
-
ifHighlight ? 1 : itemFillAlpha,
|
|
142
|
-
() => {
|
|
143
|
-
setHighlightSectionNames([section.name]);
|
|
144
|
-
},
|
|
145
|
-
() => {
|
|
146
|
-
setHighlightSectionNames([]);
|
|
147
|
-
},
|
|
148
|
-
);
|
|
149
|
-
itemMarkContainer.addChild(graphics);
|
|
150
|
-
textContainer.addChild(texts);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
// some are tmp highlights, draw them separately
|
|
154
|
-
highlightElements.forEach((element) => {
|
|
155
|
-
const { rect } = element;
|
|
156
|
-
highlightElementRects.push(rect);
|
|
157
|
-
if (elements.includes(element)) {
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
const [graphics, texts] = rectMarkForItem(rect, '', highlightColorForType('element'), 1, noop, noop);
|
|
161
|
-
itemMarkContainer.addChild(graphics);
|
|
162
|
-
textContainer.addChild(texts);
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// element mark
|
|
166
|
-
elements.forEach((element) => {
|
|
167
|
-
const { rect, content } = element;
|
|
168
|
-
const ifHighlight = highlightElements.includes(element);
|
|
169
|
-
const [graphics, texts] = rectMarkForItem(
|
|
170
|
-
rect,
|
|
171
|
-
content,
|
|
172
|
-
ifHighlight ? highlightColorForType('element') : colorForName('element', content),
|
|
173
|
-
ifHighlight ? 1 : itemFillAlpha,
|
|
174
|
-
() => {
|
|
175
|
-
setHighlightElements([element]);
|
|
176
|
-
},
|
|
177
|
-
() => {
|
|
178
|
-
setHighlightElements([]);
|
|
179
|
-
},
|
|
180
|
-
);
|
|
181
|
-
itemMarkContainer.addChild(graphics);
|
|
182
|
-
textContainer.addChild(texts);
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
sections.forEach((section) => {
|
|
186
|
-
const { content } = section;
|
|
187
|
-
const ifHighlight = highlightSectionNames.includes(section.name);
|
|
188
|
-
|
|
189
|
-
const sectionTheme = ifHighlight ? '#FFFFFF' : colorForName('section', section.name);
|
|
190
|
-
|
|
191
|
-
content.forEach((text) => {
|
|
192
|
-
const { content, rect } = text;
|
|
193
|
-
const { left, top } = rect;
|
|
194
|
-
const style = new PIXI.TextStyle({
|
|
195
|
-
wordWrap: true,
|
|
196
|
-
wordWrapWidth: rect.width,
|
|
197
|
-
fontSize: 18,
|
|
198
|
-
fill: sectionTheme,
|
|
199
|
-
});
|
|
200
|
-
const textElement = new PIXI.Text(content, style);
|
|
201
|
-
textElement.x = left;
|
|
202
|
-
textElement.y = top;
|
|
203
|
-
textContainer.addChild(textElement);
|
|
204
|
-
|
|
205
|
-
const textBorder = new PIXI.Graphics();
|
|
206
|
-
textBorder.beginFill(0xaaaaaa, 0.2);
|
|
207
|
-
textBorder.lineStyle(1, 0x0, 1);
|
|
208
|
-
textBorder.drawRect(left, top, rect.width, rect.height);
|
|
209
|
-
textBorder.endFill();
|
|
210
|
-
textContainer.addChild(textBorder);
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
textContainer.visible = textsVisible;
|
|
214
|
-
return {
|
|
215
|
-
highlightSectionRects,
|
|
216
|
-
highlightElementRects,
|
|
217
|
-
};
|
|
218
|
-
}, [
|
|
219
|
-
app,
|
|
220
|
-
appInitialed,
|
|
221
|
-
sections,
|
|
222
|
-
highlightSectionNames,
|
|
223
|
-
highlightElements,
|
|
224
|
-
// bgVisible,
|
|
225
|
-
// textsVisible,
|
|
226
|
-
]);
|
|
227
|
-
|
|
228
|
-
const onSetBg: CheckboxProps['onChange'] = (e) => {
|
|
229
|
-
setBgVisible(e.target.checked);
|
|
230
|
-
if (pixiBgRef.current) {
|
|
231
|
-
pixiBgRef.current.alpha = e.target.checked ? bgOnAlpha : bgOffAlpha;
|
|
232
|
-
}
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const onSetTextsVisible: CheckboxProps['onChange'] = (e) => {
|
|
236
|
-
setTextsVisible(e.target.checked);
|
|
237
|
-
textContainer.visible = e.target.checked;
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
let bottomTipA: ReactElement | null = null;
|
|
241
|
-
if (highlightElementRects.length === 1) {
|
|
242
|
-
bottomTipA = (
|
|
243
|
-
<div className="bottom-tip">
|
|
244
|
-
<div className="bottom-tip-item">Element: {JSON.stringify(highlightElementRects[0])}</div>
|
|
245
|
-
</div>
|
|
246
|
-
);
|
|
247
|
-
} else if (highlightElementRects.length > 1) {
|
|
248
|
-
bottomTipA = (
|
|
249
|
-
<div className="bottom-tip">
|
|
250
|
-
<div className="bottom-tip-item">Element: {JSON.stringify(highlightElementRects)}</div>
|
|
251
|
-
</div>
|
|
252
|
-
);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
let bottomTipB: ReactElement | null = null;
|
|
256
|
-
if (highlightSectionRects.length === 1) {
|
|
257
|
-
bottomTipB = (
|
|
258
|
-
<div className="bottom-tip">
|
|
259
|
-
<div className="bottom-tip-item">Section: {JSON.stringify(highlightSectionRects[0])}</div>
|
|
260
|
-
</div>
|
|
261
|
-
);
|
|
262
|
-
} else if (highlightSectionRects.length > 1) {
|
|
263
|
-
bottomTipB = (
|
|
264
|
-
<div className="bottom-tip">
|
|
265
|
-
<div className="bottom-tip-item">Sections: {JSON.stringify(highlightSectionRects)}</div>
|
|
266
|
-
</div>
|
|
267
|
-
);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
<div className="blackboard">
|
|
272
|
-
<div className="blackboard-main-content" style={{ width: '100%' }} ref={domRef} />
|
|
273
|
-
<div className="blackboard-filter">
|
|
274
|
-
<div className="overlay-control">
|
|
275
|
-
<Checkbox checked={bgVisible} onChange={onSetBg}>
|
|
276
|
-
Screenshot
|
|
277
|
-
</Checkbox>
|
|
278
|
-
<Checkbox checked={textsVisible} onChange={onSetTextsVisible}>
|
|
279
|
-
Text Mark
|
|
280
|
-
</Checkbox>
|
|
281
|
-
</div>
|
|
282
|
-
</div>
|
|
283
|
-
<div className="bottom-tip">
|
|
284
|
-
{bottomTipA}
|
|
285
|
-
{bottomTipB}
|
|
286
|
-
</div>
|
|
287
|
-
|
|
288
|
-
{/* {footer} */}
|
|
289
|
-
</div>
|
|
290
|
-
);
|
|
291
|
-
};
|
|
292
|
-
|
|
293
|
-
export default BlackBoard;
|
package/src/component/color.tsx
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
// https://coolors.co/palettes/popular/#01204e
|
|
2
|
-
const sectionColor = ['#028391'];
|
|
3
|
-
const elementColor = ['#fb6107'];
|
|
4
|
-
const highlightColorForSection = '#01204E';
|
|
5
|
-
const highlightColorForElement = '#F56824'; // @main-orange
|
|
6
|
-
|
|
7
|
-
function djb2Hash(str?: string): number {
|
|
8
|
-
if (!str) {
|
|
9
|
-
console.warn('djb2Hash: empty string');
|
|
10
|
-
str = 'unnamed';
|
|
11
|
-
}
|
|
12
|
-
let hash = 5381;
|
|
13
|
-
for (let i = 0; i < str.length; i++) {
|
|
14
|
-
hash = (hash << 5) + hash + str.charCodeAt(i); // hash * 33 + c
|
|
15
|
-
}
|
|
16
|
-
return hash >>> 0; // Convert to unsigned 32
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function colorForName(type: 'section' | 'element', name: string): string {
|
|
20
|
-
const hashNumber = djb2Hash(name);
|
|
21
|
-
if (type === 'section') {
|
|
22
|
-
return sectionColor[hashNumber % sectionColor.length];
|
|
23
|
-
} else {
|
|
24
|
-
return elementColor[hashNumber % elementColor.length];
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function highlightColorForType(type: 'section' | 'element'): string {
|
|
29
|
-
if (type === 'section') {
|
|
30
|
-
return highlightColorForSection;
|
|
31
|
-
} else {
|
|
32
|
-
return highlightColorForElement;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
@main-blue: #06B1AB;
|
|
2
|
-
@sub-blue: #A3D6D2;
|
|
3
|
-
|
|
4
|
-
@main-orange: #F9483E;
|
|
5
|
-
|
|
6
|
-
@side-bg: #ECECEC;
|
|
7
|
-
@title-bg: #DDDDDD;
|
|
8
|
-
@border-color: #CCCCCC;
|
|
9
|
-
@heavy-border-color: #888;
|
|
10
|
-
|
|
11
|
-
@selected-bg: @main-blue;
|
|
12
|
-
@selected-text: #ffffff;
|
|
13
|
-
@hover-bg: @sub-blue;
|
|
14
|
-
@hover-text: #333;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
@weak-text: #777;
|
|
18
|
-
@weak-bg: #F3F3F3;
|
|
19
|
-
|
|
20
|
-
@side-horizontal-padding: 10px;
|
|
21
|
-
@side-vertical-spacing: 10px;
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
@import './common.less';
|
|
2
|
-
|
|
3
|
-
@layout-space: 10px;
|
|
4
|
-
|
|
5
|
-
.detail-panel {
|
|
6
|
-
display: flex;
|
|
7
|
-
flex-direction: column;
|
|
8
|
-
height: 100%;
|
|
9
|
-
padding: @layout-space;
|
|
10
|
-
background: #FFF;
|
|
11
|
-
|
|
12
|
-
.ant-segmented{
|
|
13
|
-
padding: 0;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
.view-switcher{
|
|
17
|
-
margin-bottom: @layout-space;
|
|
18
|
-
flex-shrink: 0;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
.detail-content{
|
|
22
|
-
box-sizing: border-box;
|
|
23
|
-
justify-content: center;
|
|
24
|
-
flex-direction: column;
|
|
25
|
-
display: flex;
|
|
26
|
-
flex-grow: 1;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.blackboard {
|
|
30
|
-
margin: 0 auto;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
.screenshot-item {
|
|
34
|
-
margin-bottom: @layout-space;
|
|
35
|
-
|
|
36
|
-
.screenshot-item-title {
|
|
37
|
-
// font-weight: bold;
|
|
38
|
-
margin-bottom: 5px;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
img {
|
|
42
|
-
border: 1px solid @heavy-border-color;
|
|
43
|
-
max-width: 100%;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
}
|
|
@@ -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
|
-
}
|