@atlaskit/editor-plugin-table 2.7.2 → 2.8.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/CHANGELOG.md +10 -0
- package/dist/cjs/plugins/table/nodeviews/TableResizer.js +25 -26
- package/dist/cjs/plugins/table/utils/analytics.js +146 -2
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/plugins/table/nodeviews/TableResizer.js +27 -27
- package/dist/es2019/plugins/table/utils/analytics.js +130 -0
- package/dist/es2019/version.json +1 -1
- package/dist/esm/plugins/table/nodeviews/TableResizer.js +26 -27
- package/dist/esm/plugins/table/utils/analytics.js +139 -0
- package/dist/esm/version.json +1 -1
- package/dist/types/plugins/table/utils/analytics.d.ts +28 -1
- package/dist/types-ts4.5/plugins/table/utils/analytics.d.ts +28 -1
- package/package.json +3 -2
- package/src/__tests__/unit/nodeviews/TableContainer.tsx +57 -1
- package/src/__tests__/unit/utils/analytics.ts +98 -0
- package/src/plugins/table/nodeviews/TableResizer.tsx +35 -34
- package/src/plugins/table/utils/analytics.ts +168 -0
|
@@ -8,12 +8,7 @@ import React, {
|
|
|
8
8
|
|
|
9
9
|
import rafSchd from 'raf-schd';
|
|
10
10
|
|
|
11
|
-
import {
|
|
12
|
-
ACTION_SUBJECT,
|
|
13
|
-
EVENT_TYPE,
|
|
14
|
-
TABLE_ACTION,
|
|
15
|
-
TableEventPayload,
|
|
16
|
-
} from '@atlaskit/editor-common/analytics';
|
|
11
|
+
import { TableEventPayload } from '@atlaskit/editor-common/analytics';
|
|
17
12
|
import { getGuidelinesWithHighlights } from '@atlaskit/editor-common/guideline';
|
|
18
13
|
import type { GuidelineConfig } from '@atlaskit/editor-common/guideline';
|
|
19
14
|
import {
|
|
@@ -25,21 +20,24 @@ import { resizerHandleShadowClassName } from '@atlaskit/editor-common/styles';
|
|
|
25
20
|
import { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
26
21
|
import { Transaction } from '@atlaskit/editor-prosemirror/state';
|
|
27
22
|
import { EditorView } from '@atlaskit/editor-prosemirror/view';
|
|
28
|
-
import { TableMap } from '@atlaskit/editor-tables';
|
|
29
23
|
import { findTable } from '@atlaskit/editor-tables/utils';
|
|
30
24
|
|
|
31
25
|
import {
|
|
32
26
|
COLUMN_MIN_WIDTH,
|
|
33
27
|
getColgroupChildrenLength,
|
|
34
|
-
hasTableBeenResized,
|
|
35
28
|
previewScaleTable,
|
|
36
29
|
scaleTable,
|
|
37
30
|
} from '../pm-plugins/table-resizing/utils';
|
|
38
31
|
import { pluginKey as tableWidthPluginKey } from '../pm-plugins/table-width';
|
|
39
32
|
import { TABLE_HIGHLIGHT_GAP, TABLE_SNAP_GAP } from '../ui/consts';
|
|
40
|
-
import {
|
|
33
|
+
import {
|
|
34
|
+
generateResizedPayload,
|
|
35
|
+
generateResizeFrameRatePayloads,
|
|
36
|
+
useMeasureFramerate,
|
|
37
|
+
} from '../utils/analytics';
|
|
41
38
|
import { defaultGuidelines, defaultGuidelineWidths } from '../utils/guidelines';
|
|
42
39
|
import { findClosestSnap } from '../utils/snapping';
|
|
40
|
+
|
|
43
41
|
interface TableResizerProps {
|
|
44
42
|
width: number;
|
|
45
43
|
maxWidth: number;
|
|
@@ -57,29 +55,6 @@ interface TableResizerProps {
|
|
|
57
55
|
const handles = { right: true };
|
|
58
56
|
const tableHandleMarginTop = 12;
|
|
59
57
|
|
|
60
|
-
const generateResizedPayload = (props: {
|
|
61
|
-
originalNode: PMNode;
|
|
62
|
-
resizedNode: PMNode;
|
|
63
|
-
}): TableEventPayload => {
|
|
64
|
-
const tableMap = TableMap.get(props.resizedNode);
|
|
65
|
-
|
|
66
|
-
return {
|
|
67
|
-
action: TABLE_ACTION.RESIZED,
|
|
68
|
-
actionSubject: ACTION_SUBJECT.TABLE,
|
|
69
|
-
eventType: EVENT_TYPE.TRACK,
|
|
70
|
-
attributes: {
|
|
71
|
-
newWidth: props.resizedNode.attrs.width,
|
|
72
|
-
prevWidth: props.originalNode.attrs.width ?? null,
|
|
73
|
-
nodeSize: props.resizedNode.nodeSize,
|
|
74
|
-
totalTableWidth: hasTableBeenResized(props.resizedNode)
|
|
75
|
-
? getTableWidth(props.resizedNode)
|
|
76
|
-
: null,
|
|
77
|
-
totalRowCount: tableMap.height,
|
|
78
|
-
totalColumnCount: tableMap.width,
|
|
79
|
-
},
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
|
|
83
58
|
const getResizerHandleHeight = (tableRef: HTMLTableElement) => {
|
|
84
59
|
const tableHeight = tableRef?.clientHeight;
|
|
85
60
|
let handleHeightSize: HandleHeightSizeType | undefined = 'small';
|
|
@@ -129,6 +104,8 @@ export const TableResizer = ({
|
|
|
129
104
|
const resizerMinWidth = getResizerMinWidth(node);
|
|
130
105
|
const handleHeightSize = getResizerHandleHeight(tableRef);
|
|
131
106
|
|
|
107
|
+
const { startMeasure, endMeasure, countFrames } = useMeasureFramerate();
|
|
108
|
+
|
|
132
109
|
const updateActiveGuidelines = useCallback(
|
|
133
110
|
({ gap, keys }: { gap: number; keys: string[] }) => {
|
|
134
111
|
if (gap !== currentGap.current) {
|
|
@@ -157,6 +134,8 @@ export const TableResizer = ({
|
|
|
157
134
|
);
|
|
158
135
|
|
|
159
136
|
const handleResizeStart = useCallback(() => {
|
|
137
|
+
startMeasure();
|
|
138
|
+
|
|
160
139
|
const {
|
|
161
140
|
dispatch,
|
|
162
141
|
state: { tr },
|
|
@@ -165,7 +144,7 @@ export const TableResizer = ({
|
|
|
165
144
|
dispatch(tr.setMeta(tableWidthPluginKey, { resizing: true }));
|
|
166
145
|
|
|
167
146
|
setSnappingEnabled(displayGuideline(defaultGuidelines));
|
|
168
|
-
}, [displayGuideline, editorView]);
|
|
147
|
+
}, [displayGuideline, editorView, startMeasure]);
|
|
169
148
|
|
|
170
149
|
const handleResizeStop = useCallback<HandleResize>(
|
|
171
150
|
(originalState, delta) => {
|
|
@@ -174,6 +153,18 @@ export const TableResizer = ({
|
|
|
174
153
|
const pos = getPos();
|
|
175
154
|
|
|
176
155
|
let tr = state.tr.setMeta(tableWidthPluginKey, { resizing: false });
|
|
156
|
+
const frameRateSamples = endMeasure();
|
|
157
|
+
|
|
158
|
+
if (frameRateSamples.length > 0) {
|
|
159
|
+
const resizeFrameRatePayloads = generateResizeFrameRatePayloads({
|
|
160
|
+
docSize: state.doc.nodeSize,
|
|
161
|
+
frameRateSamples,
|
|
162
|
+
originalNode: node,
|
|
163
|
+
});
|
|
164
|
+
resizeFrameRatePayloads.forEach((payload) => {
|
|
165
|
+
attachAnalyticsEvent(payload)?.(tr);
|
|
166
|
+
});
|
|
167
|
+
}
|
|
177
168
|
|
|
178
169
|
if (typeof pos === 'number') {
|
|
179
170
|
tr = tr.setNodeMarkup(pos, undefined, {
|
|
@@ -219,11 +210,13 @@ export const TableResizer = ({
|
|
|
219
210
|
tableRef,
|
|
220
211
|
displayGuideline,
|
|
221
212
|
attachAnalyticsEvent,
|
|
213
|
+
endMeasure,
|
|
222
214
|
],
|
|
223
215
|
);
|
|
224
216
|
|
|
225
217
|
const handleResize = useCallback(
|
|
226
218
|
(originalState, delta) => {
|
|
219
|
+
countFrames();
|
|
227
220
|
const newWidth = originalState.width + delta.width;
|
|
228
221
|
const pos = getPos();
|
|
229
222
|
if (typeof pos !== 'number') {
|
|
@@ -254,7 +247,15 @@ export const TableResizer = ({
|
|
|
254
247
|
|
|
255
248
|
return newWidth;
|
|
256
249
|
},
|
|
257
|
-
[
|
|
250
|
+
[
|
|
251
|
+
editorView,
|
|
252
|
+
getPos,
|
|
253
|
+
node,
|
|
254
|
+
tableRef,
|
|
255
|
+
updateWidth,
|
|
256
|
+
updateActiveGuidelines,
|
|
257
|
+
countFrames,
|
|
258
|
+
],
|
|
258
259
|
);
|
|
259
260
|
|
|
260
261
|
const scheduleResize = useMemo(() => rafSchd(handleResize), [handleResize]);
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
import { useEffect, useRef } from 'react';
|
|
2
|
+
|
|
1
3
|
import type {
|
|
2
4
|
AnalyticsEventPayload,
|
|
3
5
|
AnalyticsEventPayloadCallback,
|
|
4
6
|
EditorAnalyticsAPI,
|
|
7
|
+
TableEventPayload,
|
|
8
|
+
} from '@atlaskit/editor-common/analytics';
|
|
9
|
+
import {
|
|
10
|
+
ACTION_SUBJECT,
|
|
11
|
+
EVENT_TYPE,
|
|
12
|
+
TABLE_ACTION,
|
|
5
13
|
} from '@atlaskit/editor-common/analytics';
|
|
6
14
|
import { HigherOrderCommand } from '@atlaskit/editor-common/types';
|
|
15
|
+
import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
|
|
7
16
|
import { Selection } from '@atlaskit/editor-prosemirror/state';
|
|
8
17
|
import { TableMap } from '@atlaskit/editor-tables/table-map';
|
|
9
18
|
import { findTable, getSelectionRect } from '@atlaskit/editor-tables/utils';
|
|
10
19
|
|
|
20
|
+
import { hasTableBeenResized } from '../pm-plugins/table-resizing/utils';
|
|
21
|
+
import { getTableWidth } from '../utils';
|
|
22
|
+
|
|
11
23
|
export function getSelectedTableInfo(selection: Selection) {
|
|
12
24
|
let map;
|
|
13
25
|
let totalRowCount = 0;
|
|
@@ -78,3 +90,159 @@ export const withEditorAnalyticsAPI =
|
|
|
78
90
|
view,
|
|
79
91
|
);
|
|
80
92
|
};
|
|
93
|
+
|
|
94
|
+
interface UseMeasureFramerateConfig {
|
|
95
|
+
maxSamples?: number;
|
|
96
|
+
minFrames?: number;
|
|
97
|
+
minTimeMs?: number;
|
|
98
|
+
sampleRateMs?: number;
|
|
99
|
+
timeoutMs?: number;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export const generateResizedPayload = (props: {
|
|
103
|
+
originalNode: PMNode;
|
|
104
|
+
resizedNode: PMNode;
|
|
105
|
+
}): TableEventPayload => {
|
|
106
|
+
const tableMap = TableMap.get(props.resizedNode);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
action: TABLE_ACTION.RESIZED,
|
|
110
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
111
|
+
eventType: EVENT_TYPE.TRACK,
|
|
112
|
+
attributes: {
|
|
113
|
+
newWidth: props.resizedNode.attrs.width,
|
|
114
|
+
prevWidth: props.originalNode.attrs.width ?? null,
|
|
115
|
+
nodeSize: props.resizedNode.nodeSize,
|
|
116
|
+
totalTableWidth: hasTableBeenResized(props.resizedNode)
|
|
117
|
+
? getTableWidth(props.resizedNode)
|
|
118
|
+
: null,
|
|
119
|
+
totalRowCount: tableMap.height,
|
|
120
|
+
totalColumnCount: tableMap.width,
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
export const reduceResizeFrameRateSamples = (frameRateSamples: number[]) => {
|
|
126
|
+
if (frameRateSamples.length > 1) {
|
|
127
|
+
const frameRateSum = frameRateSamples.reduce((sum, frameRate, index) => {
|
|
128
|
+
if (index === 0) {
|
|
129
|
+
return sum;
|
|
130
|
+
} else {
|
|
131
|
+
return sum + frameRate;
|
|
132
|
+
}
|
|
133
|
+
}, 0);
|
|
134
|
+
const averageFrameRate = Math.round(
|
|
135
|
+
frameRateSum / (frameRateSamples.length - 1),
|
|
136
|
+
);
|
|
137
|
+
return [frameRateSamples[0], averageFrameRate];
|
|
138
|
+
} else {
|
|
139
|
+
return frameRateSamples;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export const generateResizeFrameRatePayloads = (props: {
|
|
144
|
+
docSize: number;
|
|
145
|
+
frameRateSamples: number[];
|
|
146
|
+
originalNode: PMNode;
|
|
147
|
+
}): TableEventPayload[] => {
|
|
148
|
+
const reducedResizeFrameRateSamples = reduceResizeFrameRateSamples(
|
|
149
|
+
props.frameRateSamples,
|
|
150
|
+
);
|
|
151
|
+
return reducedResizeFrameRateSamples.map((frameRateSample, index) => ({
|
|
152
|
+
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
153
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
154
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
155
|
+
attributes: {
|
|
156
|
+
frameRate: frameRateSample,
|
|
157
|
+
nodeSize: props.originalNode.nodeSize,
|
|
158
|
+
docSize: props.docSize,
|
|
159
|
+
isInitialSample: index === 0,
|
|
160
|
+
},
|
|
161
|
+
}));
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Measures the framerate of a component over a given time period.
|
|
166
|
+
*/
|
|
167
|
+
export const useMeasureFramerate = (config: UseMeasureFramerateConfig = {}) => {
|
|
168
|
+
const {
|
|
169
|
+
maxSamples = 10,
|
|
170
|
+
minFrames = 5,
|
|
171
|
+
minTimeMs = 500,
|
|
172
|
+
sampleRateMs = 1000,
|
|
173
|
+
timeoutMs = 200,
|
|
174
|
+
} = config;
|
|
175
|
+
|
|
176
|
+
let frameCount = useRef(0);
|
|
177
|
+
let lastTime = useRef(0);
|
|
178
|
+
let timeoutId = useRef<NodeJS.Timeout | undefined>();
|
|
179
|
+
let frameRateSamples = useRef<number[]>([]);
|
|
180
|
+
|
|
181
|
+
useEffect(() => {
|
|
182
|
+
return () => {
|
|
183
|
+
if (timeoutId.current) {
|
|
184
|
+
clearTimeout(timeoutId.current);
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}, []);
|
|
188
|
+
|
|
189
|
+
const startMeasure = () => {
|
|
190
|
+
frameCount.current = 0;
|
|
191
|
+
lastTime.current = performance.now();
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Returns an array of frame rate samples as integers.
|
|
196
|
+
*/
|
|
197
|
+
const endMeasure = () => {
|
|
198
|
+
const samples = frameRateSamples.current;
|
|
199
|
+
frameRateSamples.current = [];
|
|
200
|
+
return samples;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const sampleFrameRate = (delay = 0) => {
|
|
204
|
+
const currentTime = performance.now();
|
|
205
|
+
const deltaTime = currentTime - lastTime.current - delay;
|
|
206
|
+
const isValidSample =
|
|
207
|
+
deltaTime > minTimeMs && frameCount.current >= minFrames;
|
|
208
|
+
if (isValidSample) {
|
|
209
|
+
const frameRate = Math.round(frameCount.current / (deltaTime / 1000));
|
|
210
|
+
frameRateSamples.current.push(frameRate);
|
|
211
|
+
}
|
|
212
|
+
frameCount.current = 0;
|
|
213
|
+
lastTime.current = 0;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Counts the number of frames that occur within a given time period. Intended to be called
|
|
218
|
+
* inside a `requestAnimationFrame` callback.
|
|
219
|
+
*/
|
|
220
|
+
const countFrames = () => {
|
|
221
|
+
if (frameRateSamples.current.length >= maxSamples && timeoutId.current) {
|
|
222
|
+
clearTimeout(timeoutId.current);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Allows us to keep counting frames even if `startMeasure` is not called
|
|
228
|
+
*/
|
|
229
|
+
if (lastTime.current === 0) {
|
|
230
|
+
lastTime.current = performance.now();
|
|
231
|
+
}
|
|
232
|
+
frameCount.current++;
|
|
233
|
+
|
|
234
|
+
if (timeoutId.current) {
|
|
235
|
+
clearTimeout(timeoutId.current);
|
|
236
|
+
}
|
|
237
|
+
if (performance.now() - lastTime.current > sampleRateMs) {
|
|
238
|
+
sampleFrameRate();
|
|
239
|
+
} else {
|
|
240
|
+
timeoutId.current = setTimeout(
|
|
241
|
+
() => sampleFrameRate(timeoutMs),
|
|
242
|
+
timeoutMs,
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
return { startMeasure, endMeasure, countFrames };
|
|
248
|
+
};
|