@kylincloud/flamegraph 0.35.22 → 0.35.23
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/CHANGELOG.md +9 -0
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +6 -1
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +7 -0
- package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
- package/dist/FlameGraph/FlameGraphRenderer.d.ts +4 -0
- package/dist/FlameGraph/FlameGraphRenderer.d.ts.map +1 -1
- package/dist/ProfilerTable.d.ts.map +1 -1
- package/dist/i18n.d.ts +2 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/index.cjs.js +2 -2
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.esm.js +3 -3
- package/dist/index.esm.js.map +1 -1
- package/dist/index.node.cjs.js +4 -4
- package/dist/index.node.cjs.js.map +1 -1
- package/dist/index.node.esm.js +3 -3
- package/dist/index.node.esm.js.map +1 -1
- package/dist/shims/Table.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +7 -2
- package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +31 -2
- package/src/FlameGraph/FlameGraphComponent/index.tsx +14 -3
- package/src/FlameGraph/FlameGraphRenderer.tsx +69 -5
- package/src/ProfilerTable.module.scss +1 -2
- package/src/ProfilerTable.tsx +52 -46
- package/src/i18n.tsx +12 -0
- package/src/shims/Table.module.scss +9 -2
- package/src/shims/Table.tsx +44 -40
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Table.d.ts","sourceRoot":"","sources":["../../src/shims/Table.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,
|
|
1
|
+
{"version":3,"file":"Table.d.ts","sourceRoot":"","sources":["../../src/shims/Table.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAoB,SAAS,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAUrF,UAAU,UAAU;IAClB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,aAAa,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,CAAC;CACvH;AAED,MAAM,WAAW,IAAK,SAAQ,UAAU;IACtC,KAAK,EAAE,SAAS,GAAG,MAAM,CAAC;IAC1B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,UAAU,QAAS,SAAQ,UAAU;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,OAAO;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,IAAI,EAAE,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,aAAa,GACrB;IACA,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,GACC;IACA,IAAI,EAAE,QAAQ,CAAC;IACf,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB,CAAC;AAEJ,KAAK,KAAK,GAAG,aAAa,GAAG;IAC3B,OAAO,EAAE,QAAQ,EAAE,CAAC;CACrB,CAAC;AAEF,UAAU,cAAc;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACtC,eAAe,EAAE,MAAM,GAAG,KAAK,CAAC;CACjC;AAED,eAAO,MAAM,YAAY,YAAa,QAAQ,EAAE,KAAG,cAsBlD,CAAC;AAEF,UAAU,UAAU;IAClB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/C,KAAK,EAAE,KAAK,CAAC;IACb,YAAY,CAAC,EAAE,SAAS,CAAC,uBAAuB,CAAC,CAAC;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IAEpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA0CD,iBAAS,KAAK,CAAC,EACb,eAAe,EACf,MAAM,EACN,gBAAgB,EAChB,KAAK,EACL,YAAY,EACZ,SAAS,EACT,SAAS,EACT,YAAY,GACb,EAAE,UAAU,2CAkEZ;AA2DD,eAAe,KAAK,CAAC"}
|
package/package.json
CHANGED
|
@@ -12,7 +12,7 @@ import { PX_PER_LEVEL, BAR_HEIGHT, COLLAPSE_THRESHOLD } from './constants';
|
|
|
12
12
|
import type { FlamegraphPalette } from './colorPalette';
|
|
13
13
|
// there's a dependency cycle here but it should be fine
|
|
14
14
|
/* eslint-disable-next-line import/no-cycle */
|
|
15
|
-
import RenderCanvas from './Flamegraph_render';
|
|
15
|
+
import RenderCanvas, { CanvasI18nMessages } from './Flamegraph_render';
|
|
16
16
|
|
|
17
17
|
/* eslint-disable no-useless-constructor */
|
|
18
18
|
|
|
@@ -47,7 +47,10 @@ export default class Flamegraph {
|
|
|
47
47
|
private readonly highlightQuery: string,
|
|
48
48
|
private zoom: Maybe<DeepReadonly<{ i: number; j: number }>>,
|
|
49
49
|
|
|
50
|
-
private palette: FlamegraphPalette
|
|
50
|
+
private palette: FlamegraphPalette,
|
|
51
|
+
|
|
52
|
+
/** i18n messages for canvas rendering */
|
|
53
|
+
private messages?: CanvasI18nMessages
|
|
51
54
|
) {
|
|
52
55
|
// TODO
|
|
53
56
|
// these were only added because storybook is not setting
|
|
@@ -59,6 +62,7 @@ export default class Flamegraph {
|
|
|
59
62
|
this.highlightQuery = highlightQuery;
|
|
60
63
|
this.ff = createFF(flamebearer.format);
|
|
61
64
|
this.palette = palette;
|
|
65
|
+
this.messages = messages;
|
|
62
66
|
|
|
63
67
|
// don't allow to have a zoom smaller than the focus
|
|
64
68
|
// since it does not make sense
|
|
@@ -96,6 +100,7 @@ export default class Flamegraph {
|
|
|
96
100
|
pxPerTick: this.pxPerTick(),
|
|
97
101
|
tickToX: this.tickToX,
|
|
98
102
|
palette: this.palette,
|
|
103
|
+
messages: this.messages,
|
|
99
104
|
};
|
|
100
105
|
|
|
101
106
|
const { format: viewType } = this.flamebearer;
|
|
@@ -49,6 +49,18 @@ import { isMatch } from '../../search';
|
|
|
49
49
|
/* eslint-disable-next-line import/no-cycle */
|
|
50
50
|
import Flamegraph from './Flamegraph';
|
|
51
51
|
|
|
52
|
+
/** i18n messages needed for canvas rendering */
|
|
53
|
+
export interface CanvasI18nMessages {
|
|
54
|
+
collapsedLevelsSingular: string;
|
|
55
|
+
collapsedLevelsPlural: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Default English messages (fallback) */
|
|
59
|
+
const defaultCanvasMessages: CanvasI18nMessages = {
|
|
60
|
+
collapsedLevelsSingular: 'total (1 level collapsed)',
|
|
61
|
+
collapsedLevelsPlural: 'total ({n} levels collapsed)',
|
|
62
|
+
};
|
|
63
|
+
|
|
52
64
|
type CanvasRendererConfig = Flamebearer & {
|
|
53
65
|
canvas: HTMLCanvasElement;
|
|
54
66
|
focusedNode: ConstructorParameters<typeof Flamegraph>[2];
|
|
@@ -73,13 +85,30 @@ type CanvasRendererConfig = Flamebearer & {
|
|
|
73
85
|
|
|
74
86
|
palette: FlamegraphPalette;
|
|
75
87
|
maxSelf?: number;
|
|
88
|
+
|
|
89
|
+
/** i18n messages for collapsed text */
|
|
90
|
+
messages?: CanvasI18nMessages;
|
|
76
91
|
};
|
|
77
92
|
|
|
93
|
+
/**
|
|
94
|
+
* 生成 collapsed 层级的显示文本
|
|
95
|
+
*/
|
|
96
|
+
function getCollapsedText(
|
|
97
|
+
levelCount: number,
|
|
98
|
+
messages: CanvasI18nMessages
|
|
99
|
+
): string {
|
|
100
|
+
if (levelCount === 1) {
|
|
101
|
+
return messages.collapsedLevelsSingular;
|
|
102
|
+
}
|
|
103
|
+
return messages.collapsedLevelsPlural.replace('{n}', String(levelCount));
|
|
104
|
+
}
|
|
105
|
+
|
|
78
106
|
export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
79
107
|
const { canvas, fitMode, units, tickToX, levels, palette } = props;
|
|
80
108
|
const { numTicks, sampleRate, pxPerTick } = props;
|
|
81
109
|
const { rangeMin, rangeMax } = props;
|
|
82
110
|
const { focusedNode, zoom } = props;
|
|
111
|
+
const messages = props.messages || defaultCanvasMessages;
|
|
83
112
|
|
|
84
113
|
const graphWidth = getCanvasWidth(canvas);
|
|
85
114
|
// TODO: why is this needed? otherwise height is all messed up
|
|
@@ -134,10 +163,10 @@ export default function RenderCanvas(props: CanvasRendererConfig) {
|
|
|
134
163
|
ctx.fillStyle = colorGreyscale(200, 1).rgb().string();
|
|
135
164
|
ctx.fill();
|
|
136
165
|
|
|
137
|
-
//
|
|
166
|
+
// 使用 i18n 翻译的 collapsed 文本
|
|
138
167
|
const shortName = focusedNode.mapOrElse(
|
|
139
168
|
() => 'total',
|
|
140
|
-
(f) =>
|
|
169
|
+
(f) => getCollapsedText(f.i - 1, messages)
|
|
141
170
|
);
|
|
142
171
|
|
|
143
172
|
// Set the font syle
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/FlameGraph/FlameGraphComponent/index.tsx
|
|
2
2
|
/* eslint-disable no-unused-expressions, import/no-extraneous-dependencies */
|
|
3
|
-
import React, { useCallback, useRef } from 'react';
|
|
3
|
+
import React, { useCallback, useRef, useMemo } from 'react';
|
|
4
4
|
import clsx from 'clsx';
|
|
5
5
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
|
6
6
|
import { faRedo } from '@fortawesome/free-solid-svg-icons/faRedo';
|
|
@@ -63,6 +63,15 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
|
|
|
63
63
|
const flamegraph = useRef<Flamegraph>();
|
|
64
64
|
const i18n = useFlamegraphI18n();
|
|
65
65
|
|
|
66
|
+
// ====== 新增:提取 canvas 渲染需要的 i18n messages ======
|
|
67
|
+
const canvasMessages = useMemo(
|
|
68
|
+
() => ({
|
|
69
|
+
collapsedLevelsSingular: i18n.collapsedLevelsSingular,
|
|
70
|
+
collapsedLevelsPlural: i18n.collapsedLevelsPlural,
|
|
71
|
+
}),
|
|
72
|
+
[i18n.collapsedLevelsSingular, i18n.collapsedLevelsPlural]
|
|
73
|
+
);
|
|
74
|
+
|
|
66
75
|
const [rightClickedNode, setRightClickedNode] = React.useState<
|
|
67
76
|
Maybe<{ top: number; left: number; width: number }>
|
|
68
77
|
>(Maybe.nothing());
|
|
@@ -291,17 +300,19 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
|
|
|
291
300
|
fitMode,
|
|
292
301
|
highlightQuery,
|
|
293
302
|
zoom,
|
|
294
|
-
palette
|
|
303
|
+
palette,
|
|
304
|
+
canvasMessages // ====== 新增:传递 i18n messages ======
|
|
295
305
|
);
|
|
296
306
|
|
|
297
307
|
flamegraph.current = f;
|
|
298
308
|
}
|
|
299
309
|
};
|
|
300
310
|
|
|
311
|
+
// ====== 修改:添加 canvasMessages 依赖 ======
|
|
301
312
|
React.useEffect(() => {
|
|
302
313
|
constructCanvas();
|
|
303
314
|
renderCanvas();
|
|
304
|
-
}, [palette]);
|
|
315
|
+
}, [palette, canvasMessages]);
|
|
305
316
|
|
|
306
317
|
React.useEffect(() => {
|
|
307
318
|
constructCanvas();
|
|
@@ -275,6 +275,55 @@ class FlameGraphRenderer extends Component<
|
|
|
275
275
|
});
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* 检查指定名称的函数是否在当前聚焦节点的子树中
|
|
280
|
+
*/
|
|
281
|
+
isNameInFocusedSubtree = (name: string): boolean => {
|
|
282
|
+
const { focusedNode } = this.state.flamegraphConfigs;
|
|
283
|
+
|
|
284
|
+
// 如果没有聚焦节点,任何 name 都算"在子树中"
|
|
285
|
+
if (focusedNode.isNothing) {
|
|
286
|
+
return true;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const { flamebearer } = this.state;
|
|
290
|
+
if (!flamebearer || !flamebearer.levels) {
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const { names, levels, format } = flamebearer;
|
|
295
|
+
const ff = createFF(format);
|
|
296
|
+
const focusedI = (focusedNode as any).value.i;
|
|
297
|
+
const focusedJ = (focusedNode as any).value.j;
|
|
298
|
+
|
|
299
|
+
// 获取聚焦节点的范围
|
|
300
|
+
const focusedLevel = levels[focusedI];
|
|
301
|
+
if (!focusedLevel) return true;
|
|
302
|
+
|
|
303
|
+
const focusedOffset = ff.getBarOffset(focusedLevel, focusedJ);
|
|
304
|
+
const focusedTotal = ff.getBarTotal(focusedLevel, focusedJ);
|
|
305
|
+
const focusedEnd = focusedOffset + focusedTotal;
|
|
306
|
+
|
|
307
|
+
// 遍历聚焦节点及其下面的所有层级,检查是否有匹配的函数名
|
|
308
|
+
for (let i = focusedI; i < levels.length; i++) {
|
|
309
|
+
const level = levels[i];
|
|
310
|
+
for (let j = 0; j < level.length; j += ff.jStep) {
|
|
311
|
+
const offset = ff.getBarOffset(level, j);
|
|
312
|
+
const total = ff.getBarTotal(level, j);
|
|
313
|
+
|
|
314
|
+
// 检查这个节点是否在聚焦范围内
|
|
315
|
+
if (offset >= focusedOffset && offset + total <= focusedEnd) {
|
|
316
|
+
const nameIndex = ff.getBarName(level, j);
|
|
317
|
+
if (names[nameIndex] === name) {
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return false;
|
|
325
|
+
};
|
|
326
|
+
|
|
278
327
|
setActiveItem = (item: { name: string }) => {
|
|
279
328
|
const { name } = item;
|
|
280
329
|
|
|
@@ -288,10 +337,25 @@ class FlameGraphRenderer extends Component<
|
|
|
288
337
|
}
|
|
289
338
|
}
|
|
290
339
|
|
|
291
|
-
//
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
340
|
+
// 检查选中的 item 是否在当前聚焦的子树中
|
|
341
|
+
// 如果不在,需要重置聚焦状态,以便在火焰图中正确显示高亮
|
|
342
|
+
const isInSubtree = this.isNameInFocusedSubtree(name);
|
|
343
|
+
|
|
344
|
+
if (isInSubtree) {
|
|
345
|
+
// 在子树中,只更新选中项
|
|
346
|
+
this.setState({
|
|
347
|
+
selectedItem: Maybe.just(name),
|
|
348
|
+
});
|
|
349
|
+
} else {
|
|
350
|
+
// 不在子树中,重置聚焦并更新选中项
|
|
351
|
+
this.setState({
|
|
352
|
+
selectedItem: Maybe.just(name),
|
|
353
|
+
flamegraphConfigs: {
|
|
354
|
+
...this.state.flamegraphConfigs,
|
|
355
|
+
focusedNode: this.resetFlamegraphState.focusedNode,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
}
|
|
295
359
|
};
|
|
296
360
|
|
|
297
361
|
getHighlightQuery = () => {
|
|
@@ -607,4 +671,4 @@ function decidePanesOrder(
|
|
|
607
671
|
}
|
|
608
672
|
}
|
|
609
673
|
|
|
610
|
-
export default FlameGraphRenderer;
|
|
674
|
+
export default FlameGraphRenderer;
|
package/src/ProfilerTable.tsx
CHANGED
|
@@ -426,11 +426,11 @@ const getTableBody = ({
|
|
|
426
426
|
return rows.length > 0
|
|
427
427
|
? { bodyRows: rows, type: 'filled' as const }
|
|
428
428
|
: {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
429
|
+
value: (
|
|
430
|
+
<div className="unsupported-format">{messages.noItemsFound}</div>
|
|
431
|
+
),
|
|
432
|
+
type: 'not-filled' as const,
|
|
433
|
+
};
|
|
434
434
|
};
|
|
435
435
|
|
|
436
436
|
export interface ProfilerTableProps {
|
|
@@ -469,9 +469,15 @@ function Table({
|
|
|
469
469
|
const [anchorPoint, setAnchorPoint] = useState({ x: 0, y: 0 });
|
|
470
470
|
const [contextMenuTarget, setContextMenuTarget] = useState<string | null>(null);
|
|
471
471
|
|
|
472
|
-
//
|
|
472
|
+
// 双击处理:高亮 + 聚焦到火焰图
|
|
473
473
|
const handleDoubleClick = useCallback(
|
|
474
474
|
(name: string) => {
|
|
475
|
+
// 先确保该项被高亮(如果未高亮则高亮,如果已高亮则保持)
|
|
476
|
+
if (!selectedItem.isJust || selectedItem.value !== name) {
|
|
477
|
+
handleTableItemClick({ name });
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// 然后聚焦到火焰图
|
|
475
481
|
if (!onFocusOnNode) return;
|
|
476
482
|
|
|
477
483
|
const node = findNodeByName(flamebearer, name);
|
|
@@ -479,7 +485,7 @@ function Table({
|
|
|
479
485
|
onFocusOnNode(node.i, node.j);
|
|
480
486
|
}
|
|
481
487
|
},
|
|
482
|
-
[flamebearer, onFocusOnNode]
|
|
488
|
+
[flamebearer, onFocusOnNode, selectedItem, handleTableItemClick]
|
|
483
489
|
);
|
|
484
490
|
|
|
485
491
|
// 右键菜单处理
|
|
@@ -535,46 +541,46 @@ function Table({
|
|
|
535
541
|
() =>
|
|
536
542
|
isDoubles
|
|
537
543
|
? [
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
544
|
+
{
|
|
545
|
+
sortable: 1,
|
|
546
|
+
name: 'name' as const,
|
|
547
|
+
label: i18n.location,
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
sortable: 1,
|
|
551
|
+
name: 'baseline' as const,
|
|
552
|
+
label: i18n.baseline,
|
|
553
|
+
default: true,
|
|
554
|
+
},
|
|
555
|
+
{
|
|
556
|
+
sortable: 1,
|
|
557
|
+
name: 'comparison' as const,
|
|
558
|
+
label: i18n.comparison,
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
sortable: 1,
|
|
562
|
+
name: 'diff' as const,
|
|
563
|
+
label: i18n.diff,
|
|
564
|
+
},
|
|
565
|
+
]
|
|
560
566
|
: [
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
567
|
+
{
|
|
568
|
+
sortable: 1,
|
|
569
|
+
name: 'name' as const,
|
|
570
|
+
label: i18n.location,
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
sortable: 1,
|
|
574
|
+
name: 'self' as const,
|
|
575
|
+
label: i18n.self,
|
|
576
|
+
default: true,
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
sortable: 1,
|
|
580
|
+
name: 'total' as const,
|
|
581
|
+
label: i18n.total,
|
|
582
|
+
},
|
|
583
|
+
],
|
|
578
584
|
[i18n, isDoubles]
|
|
579
585
|
);
|
|
580
586
|
|
package/src/i18n.tsx
CHANGED
|
@@ -73,6 +73,10 @@ export type FlamegraphMessages = {
|
|
|
73
73
|
focusOnThisFunction: string;
|
|
74
74
|
tableDoubleClickToFocus: string;
|
|
75
75
|
tableRightClickForOptions: string;
|
|
76
|
+
|
|
77
|
+
// 火焰图聚焦时的 collapsed 提示
|
|
78
|
+
collapsedLevelsSingular: string; // "total (1 level collapsed)"
|
|
79
|
+
collapsedLevelsPlural: string; // "total (n levels collapsed)"
|
|
76
80
|
};
|
|
77
81
|
|
|
78
82
|
const defaultTooltipUnitTitles: Record<Units, TooltipUnitMessages> = {
|
|
@@ -227,6 +231,10 @@ export const defaultMessages: FlamegraphMessages = {
|
|
|
227
231
|
focusOnThisFunction: 'Focus on this function',
|
|
228
232
|
tableDoubleClickToFocus: 'Double-click to focus in flamegraph',
|
|
229
233
|
tableRightClickForOptions: 'Right-click for more options',
|
|
234
|
+
|
|
235
|
+
// 火焰图聚焦时的 collapsed 提示
|
|
236
|
+
collapsedLevelsSingular: 'total (1 level collapsed)',
|
|
237
|
+
collapsedLevelsPlural: 'total ({n} levels collapsed)',
|
|
230
238
|
};
|
|
231
239
|
|
|
232
240
|
export const zhCNMessages: FlamegraphMessages = {
|
|
@@ -286,6 +294,10 @@ export const zhCNMessages: FlamegraphMessages = {
|
|
|
286
294
|
focusOnThisFunction: '聚焦到此函数',
|
|
287
295
|
tableDoubleClickToFocus: '双击可聚焦到火焰图',
|
|
288
296
|
tableRightClickForOptions: '右键查看更多选项',
|
|
297
|
+
|
|
298
|
+
// 火焰图聚焦时的 collapsed 提示
|
|
299
|
+
collapsedLevelsSingular: '总计(已折叠 1 层)',
|
|
300
|
+
collapsedLevelsPlural: '总计(已折叠 {n} 层)',
|
|
289
301
|
};
|
|
290
302
|
|
|
291
303
|
const I18nContext = createContext<FlamegraphMessages>(defaultMessages);
|
|
@@ -49,10 +49,17 @@
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
tr {
|
|
52
|
+
/* 选中行样式 - 使用更明显的 antd 风格蓝色 */
|
|
52
53
|
&.isRowSelected {
|
|
53
54
|
cursor: pointer;
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
/* antd 选中蓝色,更明显但不会与红绿/红蓝火焰图冲突 */
|
|
56
|
+
background: var(--ps-table-highlight-row-bg, #bae0ff) !important;
|
|
57
|
+
color: var(--ps-table-highlight-row-text, #0958d9);
|
|
58
|
+
|
|
59
|
+
/* 左侧添加强调边框 */
|
|
60
|
+
td:first-child {
|
|
61
|
+
box-shadow: inset 3px 0 0 0 var(--ps-table-highlight-border, #1677ff);
|
|
62
|
+
}
|
|
56
63
|
}
|
|
57
64
|
|
|
58
65
|
&.isRowDisabled td {
|
package/src/shims/Table.tsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/shims/Table.tsx
|
|
2
2
|
/* eslint-disable react/jsx-props-no-spreading */
|
|
3
|
-
import React, { useState, ReactNode, CSSProperties, RefObject } from 'react';
|
|
3
|
+
import React, { useState, useRef, ReactNode, CSSProperties, RefObject } from 'react';
|
|
4
4
|
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons/faChevronLeft';
|
|
5
5
|
import { faChevronRight } from '@fortawesome/free-solid-svg-icons/faChevronRight';
|
|
6
6
|
import clsx from 'clsx';
|
|
@@ -94,6 +94,46 @@ interface TableProps {
|
|
|
94
94
|
itemsPerPage?: number;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* 表格行组件
|
|
99
|
+
* 单击立即高亮,双击同时触发高亮和聚焦
|
|
100
|
+
*/
|
|
101
|
+
function TableRow({ row }: { row: BodyRow }) {
|
|
102
|
+
const {
|
|
103
|
+
cells,
|
|
104
|
+
isRowSelected,
|
|
105
|
+
isRowDisabled,
|
|
106
|
+
className,
|
|
107
|
+
onClick,
|
|
108
|
+
onDoubleClick,
|
|
109
|
+
onContextMenu,
|
|
110
|
+
...rest
|
|
111
|
+
} = row;
|
|
112
|
+
|
|
113
|
+
const renderID = useRef(Math.random()).current;
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<tr
|
|
117
|
+
{...rest}
|
|
118
|
+
onClick={onClick}
|
|
119
|
+
onDoubleClick={onDoubleClick}
|
|
120
|
+
onContextMenu={onContextMenu}
|
|
121
|
+
className={clsx(className, {
|
|
122
|
+
[styles.isRowSelected]: isRowSelected,
|
|
123
|
+
[styles.isRowDisabled]: isRowDisabled,
|
|
124
|
+
})}
|
|
125
|
+
>
|
|
126
|
+
{cells &&
|
|
127
|
+
cells.map(({ style, value, ...cellRest }: Cell, index: number) => (
|
|
128
|
+
// eslint-disable-next-line react/no-array-index-key
|
|
129
|
+
<td key={renderID + index} style={style} {...cellRest}>
|
|
130
|
+
{value}
|
|
131
|
+
</td>
|
|
132
|
+
))}
|
|
133
|
+
</tr>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
|
|
97
137
|
function Table({
|
|
98
138
|
sortByDirection,
|
|
99
139
|
sortBy,
|
|
@@ -154,45 +194,9 @@ function Table({
|
|
|
154
194
|
</tr>
|
|
155
195
|
) : (
|
|
156
196
|
paginate(table.bodyRows, currPage, itemsPerPage).map(
|
|
157
|
-
(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
isRowDisabled,
|
|
161
|
-
className,
|
|
162
|
-
onClick,
|
|
163
|
-
onDoubleClick,
|
|
164
|
-
onContextMenu,
|
|
165
|
-
...rest
|
|
166
|
-
}) => {
|
|
167
|
-
// The problem is that when you switch apps or time-range and the function
|
|
168
|
-
// names stay the same it leads to an issue where rows don't get re-rendered
|
|
169
|
-
// So we force a rerender each time.
|
|
170
|
-
const renderID = Math.random();
|
|
171
|
-
|
|
172
|
-
return (
|
|
173
|
-
<tr
|
|
174
|
-
key={renderID}
|
|
175
|
-
{...rest}
|
|
176
|
-
onClick={onClick}
|
|
177
|
-
onDoubleClick={onDoubleClick}
|
|
178
|
-
onContextMenu={onContextMenu}
|
|
179
|
-
className={clsx(className, {
|
|
180
|
-
[styles.isRowSelected]: isRowSelected,
|
|
181
|
-
[styles.isRowDisabled]: isRowDisabled,
|
|
182
|
-
})}
|
|
183
|
-
>
|
|
184
|
-
{cells &&
|
|
185
|
-
cells.map(
|
|
186
|
-
({ style, value, ...rest }: Cell, index: number) => (
|
|
187
|
-
// eslint-disable-next-line react/no-array-index-key
|
|
188
|
-
<td key={renderID + index} style={style} {...rest}>
|
|
189
|
-
{value}
|
|
190
|
-
</td>
|
|
191
|
-
)
|
|
192
|
-
)}
|
|
193
|
-
</tr>
|
|
194
|
-
);
|
|
195
|
-
}
|
|
197
|
+
(row, idx) => (
|
|
198
|
+
<TableRow key={row['data-row'] ?? `row-${idx}`} row={row} />
|
|
199
|
+
)
|
|
196
200
|
)
|
|
197
201
|
)}
|
|
198
202
|
</tbody>
|