@difizen/libro-core 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/cell/libro-cell-service.d.ts +2 -0
- package/es/cell/libro-cell-service.d.ts.map +1 -1
- package/es/cell/libro-cell-service.js +9 -2
- package/es/cell/libro-cell-view.d.ts +2 -0
- package/es/cell/libro-cell-view.d.ts.map +1 -1
- package/es/cell/libro-cell-view.js +26 -0
- package/es/cell/libro-edit-cell-view.d.ts.map +1 -1
- package/es/cell/libro-edit-cell-view.js +22 -0
- package/es/command/libro-command-contribution.d.ts +4 -0
- package/es/command/libro-command-contribution.d.ts.map +1 -1
- package/es/command/libro-command-contribution.js +40 -26
- package/es/index.less +1 -1
- package/es/libro-protocol.d.ts +5 -2
- package/es/libro-protocol.d.ts.map +1 -1
- package/es/libro-protocol.js +0 -1
- package/es/libro-service.d.ts +2 -0
- package/es/libro-service.d.ts.map +1 -1
- package/es/libro-service.js +37 -21
- package/es/libro-setting.d.ts +1 -0
- package/es/libro-setting.d.ts.map +1 -1
- package/es/libro-setting.js +10 -0
- package/es/libro-view-tracker.d.ts +44 -1
- package/es/libro-view-tracker.d.ts.map +1 -1
- package/es/libro-view-tracker.js +117 -8
- package/es/libro-view.d.ts +3 -0
- package/es/libro-view.d.ts.map +1 -1
- package/es/libro-view.js +80 -23
- package/es/utils/index.d.ts +13 -0
- package/es/utils/index.d.ts.map +1 -1
- package/es/utils/index.js +125 -1
- package/package.json +5 -5
- package/src/cell/libro-cell-service.ts +2 -0
- package/src/cell/libro-cell-view.tsx +36 -0
- package/src/cell/libro-edit-cell-view.tsx +25 -0
- package/src/command/libro-command-contribution.ts +37 -23
- package/src/index.less +1 -1
- package/src/libro-protocol.ts +6 -3
- package/src/libro-service.ts +10 -3
- package/src/libro-setting.ts +11 -0
- package/src/libro-view-tracker.ts +105 -2
- package/src/libro-view.tsx +63 -3
- package/src/utils/index.ts +142 -0
package/src/libro-service.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Disposable } from '@difizen/libro-common/app';
|
|
2
2
|
import { DisposableCollection, Emitter } from '@difizen/libro-common/app';
|
|
3
3
|
import { ThemeService, ViewManager } from '@difizen/libro-common/app';
|
|
4
|
-
import { inject, singleton } from '@difizen/libro-common/app';
|
|
4
|
+
import { inject, singleton, ConfigurationService } from '@difizen/libro-common/app';
|
|
5
5
|
import { prop } from '@difizen/libro-common/app';
|
|
6
6
|
|
|
7
7
|
import type {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
ModelFactory,
|
|
16
16
|
NotebookService,
|
|
17
17
|
} from './libro-protocol.js';
|
|
18
|
+
import { SpmReporter } from './libro-setting.js';
|
|
18
19
|
import { LibroViewTracker } from './libro-view-tracker.js';
|
|
19
20
|
|
|
20
21
|
export interface NotebookViewChange {
|
|
@@ -37,6 +38,7 @@ export class LibroService implements NotebookService, Disposable {
|
|
|
37
38
|
protected toDispose = new DisposableCollection();
|
|
38
39
|
@inject(ModelFactory) protected libroModelFactory: ModelFactory;
|
|
39
40
|
@inject(ViewManager) protected viewManager: ViewManager;
|
|
41
|
+
@inject(ConfigurationService) configurationService: ConfigurationService;
|
|
40
42
|
@inject(LibroViewTracker) protected libroViewTracker: LibroViewTracker;
|
|
41
43
|
protected themeService: ThemeService;
|
|
42
44
|
@prop()
|
|
@@ -142,12 +144,17 @@ export class LibroService implements NotebookService, Disposable {
|
|
|
142
144
|
return model;
|
|
143
145
|
}
|
|
144
146
|
async getOrCreateView(options: NotebookOption): Promise<NotebookView> {
|
|
145
|
-
|
|
147
|
+
if (options.id) {
|
|
148
|
+
const exist = this.libroViewTracker.viewCache.get(options.id);
|
|
149
|
+
if (exist) {
|
|
150
|
+
return exist;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
146
153
|
const notebookViewPromise = this.viewManager.getOrCreateView<NotebookView>(
|
|
147
154
|
notebookViewFactoryId,
|
|
148
155
|
{
|
|
149
156
|
...(options || {}),
|
|
150
|
-
modelId:
|
|
157
|
+
modelId: options.modelId || options.id,
|
|
151
158
|
},
|
|
152
159
|
);
|
|
153
160
|
const notebookView = await notebookViewPromise;
|
package/src/libro-setting.ts
CHANGED
|
@@ -108,3 +108,14 @@ export const SupportCodeFormat: ConfigurationNode<boolean> = {
|
|
|
108
108
|
type: 'boolean',
|
|
109
109
|
},
|
|
110
110
|
};
|
|
111
|
+
|
|
112
|
+
export const SpmReporter: ConfigurationNode<boolean> = {
|
|
113
|
+
id: 'libro.spm.reporter',
|
|
114
|
+
description: l10n.t('是否支持数据日志'),
|
|
115
|
+
title: l10n.t('是否支持数据日志'),
|
|
116
|
+
type: 'checkbox',
|
|
117
|
+
defaultValue: false,
|
|
118
|
+
schema: {
|
|
119
|
+
type: 'boolean',
|
|
120
|
+
},
|
|
121
|
+
};
|
|
@@ -1,9 +1,112 @@
|
|
|
1
|
-
import { singleton } from '@difizen/libro-common/app';
|
|
1
|
+
import { singleton, Emitter } from '@difizen/libro-common/app';
|
|
2
|
+
import { v4 } from 'uuid';
|
|
2
3
|
|
|
3
|
-
import type {
|
|
4
|
+
import type {
|
|
5
|
+
ITracker,
|
|
6
|
+
NotebookModel,
|
|
7
|
+
NotebookView,
|
|
8
|
+
Options,
|
|
9
|
+
} from './libro-protocol.js';
|
|
10
|
+
|
|
11
|
+
export class Tracker implements ITracker {
|
|
12
|
+
[key: string]: any;
|
|
13
|
+
startTime?: number;
|
|
14
|
+
endTime?: number;
|
|
15
|
+
extra?: any;
|
|
16
|
+
id: string;
|
|
17
|
+
|
|
18
|
+
constructor(id?: string, extra?: any) {
|
|
19
|
+
this.id = id || v4();
|
|
20
|
+
this.startTime = Date.now();
|
|
21
|
+
this.extra = extra || {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
clearAll() {
|
|
25
|
+
this.startTime = undefined;
|
|
26
|
+
this.endTime = undefined;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getDuration() {
|
|
30
|
+
if (this.endTime && this.startTime) {
|
|
31
|
+
return this.endTime - this.startTime;
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
log() {
|
|
37
|
+
const result = {
|
|
38
|
+
id: this.id,
|
|
39
|
+
startTime: this.startTime,
|
|
40
|
+
endTime: this.endTime,
|
|
41
|
+
duration: this.getDuration(),
|
|
42
|
+
extra: this.extra,
|
|
43
|
+
};
|
|
44
|
+
this.clearAll();
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export class FpsTracker implements ITracker {
|
|
50
|
+
[key: string]: any;
|
|
51
|
+
extra?: any;
|
|
52
|
+
id: string;
|
|
53
|
+
avgFPS: number;
|
|
54
|
+
maxFrameTime: number;
|
|
55
|
+
totalDropped: number;
|
|
56
|
+
cellsCount: number;
|
|
57
|
+
|
|
58
|
+
constructor(id?: string, extra?: any) {
|
|
59
|
+
this.id = id || v4();
|
|
60
|
+
this.extra = extra || {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
log() {
|
|
64
|
+
const result = {
|
|
65
|
+
id: this.id,
|
|
66
|
+
avgFPS: this.avgFPS,
|
|
67
|
+
maxFrameTime: this.maxFrameTime,
|
|
68
|
+
totalDropped: this.totalDropped,
|
|
69
|
+
extra: this.extra,
|
|
70
|
+
cellsCount: this.cellsCount,
|
|
71
|
+
};
|
|
72
|
+
return result;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
4
75
|
|
|
5
76
|
@singleton()
|
|
6
77
|
export class LibroViewTracker {
|
|
7
78
|
viewCache: Map<string, NotebookView> = new Map();
|
|
8
79
|
modelCache: Map<string, NotebookModel> = new Map();
|
|
80
|
+
trackers: Map<string, ITracker> = new Map();
|
|
81
|
+
isEnabledSpmReporter: boolean;
|
|
82
|
+
|
|
83
|
+
protected onTrackerEmitter: Emitter<Record<string, any>> = new Emitter();
|
|
84
|
+
get onTracker() {
|
|
85
|
+
return this.onTrackerEmitter.event;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getOrCreateTrackers(options: Options) {
|
|
89
|
+
const id = options.id || v4();
|
|
90
|
+
const exist = this.trackers.get(id);
|
|
91
|
+
if (exist) {
|
|
92
|
+
if (options['type'] !== 'fps' && !exist['startTime']) {
|
|
93
|
+
exist['startTime'] = Date.now();
|
|
94
|
+
}
|
|
95
|
+
return exist;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const tracker = options['type'] === 'fps' ? new FpsTracker(id) : new Tracker(id);
|
|
99
|
+
this.trackers.set(id, tracker);
|
|
100
|
+
return tracker;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
tracker(tracker: ITracker) {
|
|
104
|
+
const trackerLog = tracker.log();
|
|
105
|
+
this.trackers.delete(tracker.id);
|
|
106
|
+
this.onTrackerEmitter.fire(trackerLog);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
hasTracker(id: string) {
|
|
110
|
+
return this.trackers.has(id);
|
|
111
|
+
}
|
|
9
112
|
}
|
package/src/libro-view.tsx
CHANGED
|
@@ -26,7 +26,8 @@ import {
|
|
|
26
26
|
ConfigurationService,
|
|
27
27
|
useConfigurationValue,
|
|
28
28
|
} from '@difizen/libro-common/app';
|
|
29
|
-
import {
|
|
29
|
+
import { l10n } from '@difizen/libro-common/l10n';
|
|
30
|
+
import { FloatButton, Button, Spin, message } from 'antd';
|
|
30
31
|
import type { FC, ForwardRefExoticComponent, RefAttributes } from 'react';
|
|
31
32
|
import { forwardRef, memo, useCallback, useEffect, useRef } from 'react';
|
|
32
33
|
import { v4 } from 'uuid';
|
|
@@ -62,8 +63,9 @@ import {
|
|
|
62
63
|
HeaderToolbarVisible,
|
|
63
64
|
RightContentFixed,
|
|
64
65
|
} from './libro-setting.js';
|
|
66
|
+
import { LibroViewTracker } from './libro-view-tracker.js';
|
|
65
67
|
import { LibroSlotManager, LibroSlotView } from './slot/index.js';
|
|
66
|
-
import { useSize } from './utils/index.js';
|
|
68
|
+
import { useFrameMonitor, useSize } from './utils/index.js';
|
|
67
69
|
import { VirtualizedManagerHelper } from './virtualized-manager-helper.js';
|
|
68
70
|
import type { VirtualizedManager } from './virtualized-manager.js';
|
|
69
71
|
import './index.less';
|
|
@@ -83,6 +85,19 @@ export const LibroContentComponent = memo(function LibroContentComponent() {
|
|
|
83
85
|
const HeaderRender = getOrigin(instance.headerRender);
|
|
84
86
|
const [headerVisible] = useConfigurationValue(HeaderToolbarVisible);
|
|
85
87
|
const [rightContentFixed] = useConfigurationValue(RightContentFixed);
|
|
88
|
+
useFrameMonitor(
|
|
89
|
+
libroViewContentRef,
|
|
90
|
+
instance.libroViewTracker.isEnabledSpmReporter,
|
|
91
|
+
(payload) => {
|
|
92
|
+
const fpsTracker = instance.libroViewTracker.getOrCreateTrackers({ type: 'fps' });
|
|
93
|
+
fpsTracker['avgFPS'] = payload.summary.avgFPS;
|
|
94
|
+
fpsTracker['maxFrameTime'] = payload.summary.maxFrameTime;
|
|
95
|
+
fpsTracker['totalDropped'] = payload.summary.totalDropped;
|
|
96
|
+
fpsTracker['extra'] = payload.frames;
|
|
97
|
+
fpsTracker['cells'] = instance.model.cells.length;
|
|
98
|
+
instance.libroViewTracker.tracker(fpsTracker);
|
|
99
|
+
},
|
|
100
|
+
);
|
|
86
101
|
|
|
87
102
|
const handleScroll = useCallback(() => {
|
|
88
103
|
instance.cellScrollEmitter.fire();
|
|
@@ -325,7 +340,7 @@ export class LibroView extends BaseView implements NotebookView {
|
|
|
325
340
|
@inject(LibroService) libroService: LibroService;
|
|
326
341
|
@inject(LibroSlotManager) libroSlotManager: LibroSlotManager;
|
|
327
342
|
@inject(LibroContextKey) contextKey: LibroContextKey;
|
|
328
|
-
|
|
343
|
+
@inject(LibroViewTracker) libroViewTracker: LibroViewTracker;
|
|
329
344
|
@inject(ViewManager) protected viewManager: ViewManager;
|
|
330
345
|
@inject(ConfigurationService) protected configurationService: ConfigurationService;
|
|
331
346
|
|
|
@@ -350,6 +365,8 @@ export class LibroView extends BaseView implements NotebookView {
|
|
|
350
365
|
@prop()
|
|
351
366
|
saving?: boolean;
|
|
352
367
|
|
|
368
|
+
options: NotebookOption;
|
|
369
|
+
|
|
353
370
|
onSaveEmitter: Emitter<boolean> = new Emitter();
|
|
354
371
|
get onSave() {
|
|
355
372
|
return this.onSaveEmitter.event;
|
|
@@ -386,6 +403,7 @@ export class LibroView extends BaseView implements NotebookView {
|
|
|
386
403
|
if (options.id) {
|
|
387
404
|
this.id = options.id;
|
|
388
405
|
}
|
|
406
|
+
this.options = options;
|
|
389
407
|
this.notebookService = notebookService;
|
|
390
408
|
this.model = this.notebookService.getOrCreateModel(options);
|
|
391
409
|
this.collapseService = collapseServiceFactory({ view: this });
|
|
@@ -463,6 +481,24 @@ export class LibroView extends BaseView implements NotebookView {
|
|
|
463
481
|
this.libroService.active = this;
|
|
464
482
|
this.libroSlotManager.setup(this);
|
|
465
483
|
|
|
484
|
+
if (this.libroViewTracker.isEnabledSpmReporter) {
|
|
485
|
+
this.libroViewTracker.getOrCreateTrackers({
|
|
486
|
+
id: this.options.modelId || this.options.id,
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
if (
|
|
490
|
+
this.model.cells.length > 30 ||
|
|
491
|
+
(this.options['fileSize'] || 0) / (1024 * 1024) >= 5
|
|
492
|
+
) {
|
|
493
|
+
message.warning(
|
|
494
|
+
<div>
|
|
495
|
+
{l10n.t(
|
|
496
|
+
'即将打开的文件(内容过大 / cell 过多),可能会导致操作卡顿,请耐心等待~',
|
|
497
|
+
)}
|
|
498
|
+
<Button type="link">{l10n.t('我知道了')}</Button>
|
|
499
|
+
</div>,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
466
502
|
// this.libroService.libroPerformanceStatistics.setRenderEnd(new Date());
|
|
467
503
|
|
|
468
504
|
// console.log(
|
|
@@ -519,11 +555,27 @@ export class LibroView extends BaseView implements NotebookView {
|
|
|
519
555
|
};
|
|
520
556
|
|
|
521
557
|
addCell = async (option: CellOptions, position?: number) => {
|
|
558
|
+
if (this.libroViewTracker.isEnabledSpmReporter && option.id) {
|
|
559
|
+
const id = option.id + this.id;
|
|
560
|
+
const libroTracker = this.libroViewTracker.getOrCreateTrackers({
|
|
561
|
+
id: id + 'add',
|
|
562
|
+
});
|
|
563
|
+
libroTracker['extra'].cellsCount = this.model.cells.length;
|
|
564
|
+
libroTracker['extra'].cellOperation = 'add';
|
|
565
|
+
}
|
|
522
566
|
const cellView = await this.getCellViewByOption(option);
|
|
523
567
|
this.model.addCell(cellView, position);
|
|
524
568
|
};
|
|
525
569
|
|
|
526
570
|
addCellAbove = async (option: CellOptions, position?: number) => {
|
|
571
|
+
if (this.libroViewTracker.isEnabledSpmReporter && option.id) {
|
|
572
|
+
const id = option.id + this.id;
|
|
573
|
+
const libroTracker = this.libroViewTracker.getOrCreateTrackers({
|
|
574
|
+
id: id + 'add',
|
|
575
|
+
});
|
|
576
|
+
libroTracker['extra'].cellsCount = this.model.cells.length;
|
|
577
|
+
libroTracker['extra'].cellOperation = 'add';
|
|
578
|
+
}
|
|
527
579
|
const cellView = await this.getCellViewByOption(option);
|
|
528
580
|
this.model.addCell(cellView, position, 'above');
|
|
529
581
|
};
|
|
@@ -548,6 +600,14 @@ export class LibroView extends BaseView implements NotebookView {
|
|
|
548
600
|
};
|
|
549
601
|
|
|
550
602
|
deleteCell = (cell: CellView) => {
|
|
603
|
+
if (this.libroViewTracker.isEnabledSpmReporter && cell.model.id) {
|
|
604
|
+
const id = cell.model.id + this.id;
|
|
605
|
+
const libroTracker = this.libroViewTracker.getOrCreateTrackers({
|
|
606
|
+
id: id + 'delete',
|
|
607
|
+
});
|
|
608
|
+
libroTracker['extra'].cellsCount = this.model.cells.length;
|
|
609
|
+
libroTracker['extra'].cellOperation = 'delete';
|
|
610
|
+
}
|
|
551
611
|
const deleteIndex = this.model.getCells().findIndex((item) => {
|
|
552
612
|
return equals(item, cell);
|
|
553
613
|
});
|
package/src/utils/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useUnmount } from '@difizen/libro-common/app';
|
|
2
2
|
import type { RefObject } from 'react';
|
|
3
|
+
import { useEffect } from 'react';
|
|
3
4
|
import { useCallback, useLayoutEffect, useRef, useState } from 'react';
|
|
4
5
|
|
|
5
6
|
function useRafState<S>(initialState?: S) {
|
|
@@ -46,3 +47,144 @@ export function useSize(ref: RefObject<HTMLDivElement>): Size | undefined {
|
|
|
46
47
|
}, [ref, setSize]);
|
|
47
48
|
return size;
|
|
48
49
|
}
|
|
50
|
+
|
|
51
|
+
interface FrameMetrics {
|
|
52
|
+
timestamp: number;
|
|
53
|
+
fps: number;
|
|
54
|
+
frameTime: number;
|
|
55
|
+
droppedFrames: number;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export const useFrameMonitor = (
|
|
59
|
+
scrollContainerRef: React.RefObject<HTMLElement>,
|
|
60
|
+
isEnabledSpmReporter: boolean,
|
|
61
|
+
onReport?: (payload: { frames: FrameMetrics[]; summary: any }) => void,
|
|
62
|
+
) => {
|
|
63
|
+
const frameData = useRef<FrameMetrics[]>([]);
|
|
64
|
+
const lastFrameTime = useRef(performance.now());
|
|
65
|
+
const frameCount = useRef(0);
|
|
66
|
+
const lastScrollPos = useRef(0);
|
|
67
|
+
const rafId = useRef<number>();
|
|
68
|
+
const isMonitoring = useRef(false);
|
|
69
|
+
// const reportTimeout = useRef<NodeJS.Timeout>();
|
|
70
|
+
const intervalId = useRef<NodeJS.Timeout>();
|
|
71
|
+
const scrollDebounceTimer = useRef<NodeJS.Timeout>();
|
|
72
|
+
|
|
73
|
+
const stopFrameCapture = useCallback(() => {
|
|
74
|
+
if (rafId.current) {
|
|
75
|
+
cancelAnimationFrame(rafId.current);
|
|
76
|
+
}
|
|
77
|
+
if (intervalId.current) {
|
|
78
|
+
clearInterval(intervalId.current);
|
|
79
|
+
}
|
|
80
|
+
isMonitoring.current = false;
|
|
81
|
+
frameCount.current = 0;
|
|
82
|
+
lastFrameTime.current = performance.now();
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
const startFrameCapture = useCallback(() => {
|
|
86
|
+
if (isMonitoring.current) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const calculateFPS = () => {
|
|
91
|
+
if (!isMonitoring.current) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const now = performance.now();
|
|
95
|
+
const delta = now - lastFrameTime.current;
|
|
96
|
+
frameData.current.push({
|
|
97
|
+
timestamp: now,
|
|
98
|
+
fps: Math.round((frameCount.current * 1000) / delta),
|
|
99
|
+
frameTime: delta / frameCount.current,
|
|
100
|
+
droppedFrames: Math.max(0, Math.floor(delta / 16.67) - frameCount.current),
|
|
101
|
+
});
|
|
102
|
+
lastFrameTime.current = now;
|
|
103
|
+
frameCount.current = 0;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const loop = () => {
|
|
107
|
+
if (isMonitoring.current) {
|
|
108
|
+
frameCount.current++;
|
|
109
|
+
rafId.current = requestAnimationFrame(loop);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
isMonitoring.current = true;
|
|
114
|
+
frameCount.current = 0;
|
|
115
|
+
lastFrameTime.current = performance.now();
|
|
116
|
+
|
|
117
|
+
if (intervalId.current) {
|
|
118
|
+
clearInterval(intervalId.current);
|
|
119
|
+
}
|
|
120
|
+
intervalId.current = setInterval(calculateFPS, 1500); // 每秒生成一个数据点
|
|
121
|
+
rafId.current = requestAnimationFrame(loop);
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
const reportFrames = useCallback(() => {
|
|
125
|
+
if (frameData.current.length === 0) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const send = () => {
|
|
130
|
+
const payload = {
|
|
131
|
+
frames: frameData.current,
|
|
132
|
+
summary: {
|
|
133
|
+
avgFPS:
|
|
134
|
+
frameData.current.reduce((a, b) => a + b.fps, 0) / frameData.current.length,
|
|
135
|
+
maxFrameTime: Math.max(...frameData.current.map((f) => f.frameTime)),
|
|
136
|
+
totalDropped: frameData.current.reduce((a, b) => a + b.droppedFrames, 0),
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
if (onReport) {
|
|
140
|
+
onReport(payload); // 触发外部回调
|
|
141
|
+
}
|
|
142
|
+
frameData.current = [];
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
requestIdleCallback(send, { timeout: 1000 }) || setTimeout(send, 0);
|
|
146
|
+
}, [onReport]); // 添加 onReport 依赖
|
|
147
|
+
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
if (!isEnabledSpmReporter) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const container = scrollContainerRef.current || (window as unknown as HTMLElement);
|
|
153
|
+
|
|
154
|
+
const handleScroll = () => {
|
|
155
|
+
// const currentScroll =
|
|
156
|
+
// (container as Window).scrollY || (container as HTMLElement).scrollTop;
|
|
157
|
+
const currentScroll =
|
|
158
|
+
container instanceof Window ? container.scrollY : container.scrollTop;
|
|
159
|
+
const scrollDelta = Math.abs(currentScroll - lastScrollPos.current);
|
|
160
|
+
|
|
161
|
+
if (scrollDelta > 10) {
|
|
162
|
+
if (!isMonitoring.current) {
|
|
163
|
+
startFrameCapture();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
clearTimeout(scrollDebounceTimer.current);
|
|
167
|
+
scrollDebounceTimer.current = setTimeout(() => {
|
|
168
|
+
stopFrameCapture();
|
|
169
|
+
reportFrames();
|
|
170
|
+
}, 2000);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
lastScrollPos.current = currentScroll;
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
container.addEventListener('scroll', handleScroll, { passive: true });
|
|
177
|
+
return () => {
|
|
178
|
+
container.removeEventListener('scroll', handleScroll);
|
|
179
|
+
stopFrameCapture();
|
|
180
|
+
};
|
|
181
|
+
}, [
|
|
182
|
+
scrollContainerRef,
|
|
183
|
+
startFrameCapture,
|
|
184
|
+
stopFrameCapture,
|
|
185
|
+
reportFrames,
|
|
186
|
+
isEnabledSpmReporter,
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
return { frameData, reportFrames };
|
|
190
|
+
};
|