@atlaskit/editor-plugin-table 2.7.2 → 2.8.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/CHANGELOG.md +16 -0
- package/dist/cjs/plugins/table/nodeviews/TableResizer.js +25 -26
- package/dist/cjs/plugins/table/nodeviews/table.js +31 -18
- 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/nodeviews/table.js +30 -16
- 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/nodeviews/table.js +31 -18
- 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/nodeviews/table.tsx +48 -27
- package/src/plugins/table/utils/analytics.ts +168 -0
|
@@ -29,6 +29,23 @@ import {
|
|
|
29
29
|
import { pluginKey } from '../../../plugins/table/pm-plugins/plugin-key';
|
|
30
30
|
import { TablePluginState } from '../../../plugins/table/types';
|
|
31
31
|
|
|
32
|
+
const mockStartMeasure = jest.fn();
|
|
33
|
+
const mockEndMeasure = jest.fn(() => {
|
|
34
|
+
return [51, 52, 53, 54];
|
|
35
|
+
});
|
|
36
|
+
const mockCountFrames = jest.fn();
|
|
37
|
+
|
|
38
|
+
jest.mock('../../../plugins/table/utils/analytics', () => ({
|
|
39
|
+
...jest.requireActual('../../../plugins/table/utils/analytics'),
|
|
40
|
+
useMeasureFramerate: () => {
|
|
41
|
+
return {
|
|
42
|
+
startMeasure: mockStartMeasure,
|
|
43
|
+
endMeasure: mockEndMeasure,
|
|
44
|
+
countFrames: mockCountFrames,
|
|
45
|
+
};
|
|
46
|
+
},
|
|
47
|
+
}));
|
|
48
|
+
|
|
32
49
|
describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
33
50
|
const createEditor = createEditorFactory<TablePluginState>();
|
|
34
51
|
const editor = (
|
|
@@ -202,7 +219,7 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
202
219
|
fireEvent.mouseMove(container.querySelector('.resizer-handle-right')!);
|
|
203
220
|
fireEvent.mouseUp(container.querySelector('.resizer-handle-right')!);
|
|
204
221
|
|
|
205
|
-
expect(analyticsMock).
|
|
222
|
+
expect(analyticsMock).toHaveBeenCalledWith({
|
|
206
223
|
action: TABLE_ACTION.RESIZED,
|
|
207
224
|
actionSubject: ACTION_SUBJECT.TABLE,
|
|
208
225
|
eventType: EVENT_TYPE.TRACK,
|
|
@@ -215,6 +232,45 @@ describe('table -> nodeviews -> TableContainer.tsx', () => {
|
|
|
215
232
|
totalColumnCount: 3,
|
|
216
233
|
},
|
|
217
234
|
});
|
|
235
|
+
|
|
236
|
+
expect(analyticsMock).toHaveBeenCalledWith({
|
|
237
|
+
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
238
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
239
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
240
|
+
attributes: {
|
|
241
|
+
docSize: 22,
|
|
242
|
+
frameRate: 51,
|
|
243
|
+
isInitialSample: true,
|
|
244
|
+
nodeSize: 20,
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
expect(analyticsMock).toHaveBeenCalledWith({
|
|
249
|
+
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
250
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
251
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
252
|
+
attributes: {
|
|
253
|
+
docSize: 22,
|
|
254
|
+
frameRate: 53,
|
|
255
|
+
isInitialSample: false,
|
|
256
|
+
nodeSize: 20,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
analyticsMock.mockReset();
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
test('calls useMeasureFramerate handlers', async () => {
|
|
264
|
+
const { container } = buildContainer({ layout: 'wide' });
|
|
265
|
+
|
|
266
|
+
fireEvent.mouseDown(container.querySelector('.resizer-handle-right')!);
|
|
267
|
+
fireEvent.mouseMove(container.querySelector('.resizer-handle-right')!, {
|
|
268
|
+
clientX: 100,
|
|
269
|
+
});
|
|
270
|
+
fireEvent.mouseUp(container.querySelector('.resizer-handle-right')!);
|
|
271
|
+
|
|
272
|
+
expect(mockStartMeasure).toHaveBeenCalled();
|
|
273
|
+
expect(mockEndMeasure).toHaveBeenCalled();
|
|
218
274
|
});
|
|
219
275
|
});
|
|
220
276
|
});
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { renderHook } from '@testing-library/react-hooks';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
ACTION_SUBJECT,
|
|
5
|
+
EVENT_TYPE,
|
|
6
|
+
TABLE_ACTION,
|
|
7
|
+
} from '@atlaskit/editor-common/analytics';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
generateResizeFrameRatePayloads,
|
|
11
|
+
reduceResizeFrameRateSamples,
|
|
12
|
+
useMeasureFramerate,
|
|
13
|
+
} from '../../../plugins/table/utils/analytics';
|
|
14
|
+
|
|
15
|
+
describe('reduceResizeFrameRateSamples()', () => {
|
|
16
|
+
it('should return the same array if it has only one element', () => {
|
|
17
|
+
expect(reduceResizeFrameRateSamples([1])).toEqual([1]);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should return the first element and the average of the array if length > 1', () => {
|
|
21
|
+
expect(reduceResizeFrameRateSamples([3, 2, 4, 6])).toEqual([3, 4]);
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('generateResizeFrameRatePayloads()', () => {
|
|
26
|
+
it('should return an empty array if the array is empty', () => {
|
|
27
|
+
expect(
|
|
28
|
+
generateResizeFrameRatePayloads({
|
|
29
|
+
docSize: 10,
|
|
30
|
+
frameRateSamples: [],
|
|
31
|
+
originalNode: { nodeSize: 5 } as any,
|
|
32
|
+
}),
|
|
33
|
+
).toEqual([]);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should return an array of payloads with the correct attributes', () => {
|
|
37
|
+
expect(
|
|
38
|
+
generateResizeFrameRatePayloads({
|
|
39
|
+
docSize: 10,
|
|
40
|
+
frameRateSamples: [3, 2, 4, 6],
|
|
41
|
+
originalNode: { nodeSize: 5 } as any,
|
|
42
|
+
}),
|
|
43
|
+
).toEqual([
|
|
44
|
+
{
|
|
45
|
+
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
46
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
47
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
48
|
+
attributes: {
|
|
49
|
+
frameRate: 3,
|
|
50
|
+
nodeSize: 5,
|
|
51
|
+
docSize: 10,
|
|
52
|
+
isInitialSample: true,
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
|
|
57
|
+
actionSubject: ACTION_SUBJECT.TABLE,
|
|
58
|
+
eventType: EVENT_TYPE.OPERATIONAL,
|
|
59
|
+
attributes: {
|
|
60
|
+
frameRate: 4,
|
|
61
|
+
nodeSize: 5,
|
|
62
|
+
docSize: 10,
|
|
63
|
+
isInitialSample: false,
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('useMeasureFramerate()', () => {
|
|
71
|
+
jest.useFakeTimers('modern');
|
|
72
|
+
|
|
73
|
+
it('should return the correct handlers', () => {
|
|
74
|
+
const { result } = renderHook(() => useMeasureFramerate());
|
|
75
|
+
const { startMeasure, endMeasure, countFrames } = result.current;
|
|
76
|
+
|
|
77
|
+
expect(startMeasure).toBeInstanceOf(Function);
|
|
78
|
+
expect(endMeasure).toBeInstanceOf(Function);
|
|
79
|
+
expect(countFrames).toBeInstanceOf(Function);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('should return the correct frame rate sample', async () => {
|
|
83
|
+
const { result } = renderHook(() =>
|
|
84
|
+
useMeasureFramerate({ minTimeMs: 0, minFrames: 0, sampleRateMs: 0 }),
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
const { startMeasure, endMeasure, countFrames } = result.current;
|
|
88
|
+
jest.advanceTimersByTime(100);
|
|
89
|
+
startMeasure();
|
|
90
|
+
jest.advanceTimersByTime(100);
|
|
91
|
+
countFrames();
|
|
92
|
+
const samples = endMeasure();
|
|
93
|
+
|
|
94
|
+
expect(samples).toEqual([10]);
|
|
95
|
+
|
|
96
|
+
jest.useRealTimers();
|
|
97
|
+
});
|
|
98
|
+
});
|
|
@@ -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]);
|
|
@@ -36,41 +36,45 @@ import { Props, TableOptions } from './types';
|
|
|
36
36
|
|
|
37
37
|
type ForwardRef = (node: HTMLElement | null) => void;
|
|
38
38
|
|
|
39
|
-
const tableAttributes = (
|
|
39
|
+
const tableAttributes = (node: PmNode) => {
|
|
40
|
+
return {
|
|
41
|
+
'data-number-column': node.attrs.isNumberColumnEnabled,
|
|
42
|
+
'data-layout': node.attrs.layout,
|
|
43
|
+
'data-autosize': node.attrs.__autoSize,
|
|
44
|
+
'data-table-local-id': node.attrs.localId || '',
|
|
45
|
+
'data-table-width': node.attrs.width,
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const getInlineWidth = (
|
|
40
50
|
node: PmNode,
|
|
41
51
|
options: Props['options'],
|
|
42
52
|
state: EditorState,
|
|
43
53
|
pos: number | undefined,
|
|
44
|
-
) => {
|
|
54
|
+
): number | undefined => {
|
|
45
55
|
// provide a width for tables when custom table width is supported
|
|
46
56
|
// this is to ensure 'responsive' tables (colgroup widths are undefined) become fixed to
|
|
47
57
|
// support screen size adjustments
|
|
48
58
|
const shouldHaveInlineWidth =
|
|
49
59
|
options?.isTableResizingEnabled && !isTableNested(state, pos);
|
|
50
60
|
|
|
51
|
-
let
|
|
52
|
-
node.attrs.isNumberColumnEnabled
|
|
53
|
-
? getTableContainerWidth(node) - akEditorTableNumberColumnWidth
|
|
54
|
-
: getTableContainerWidth(node)
|
|
55
|
-
}px`;
|
|
61
|
+
let widthValue = getTableContainerWidth(node);
|
|
56
62
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
'data-layout': node.attrs.layout,
|
|
60
|
-
'data-autosize': node.attrs.__autoSize,
|
|
61
|
-
'data-table-local-id': node.attrs.localId || '',
|
|
62
|
-
'data-table-width': node.attrs.width,
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
if (shouldHaveInlineWidth) {
|
|
66
|
-
// this should be fixed because style will overwrite any existing styles, current found conflict with sticky headers
|
|
67
|
-
return {
|
|
68
|
-
...dataAttrsInTable,
|
|
69
|
-
style,
|
|
70
|
-
};
|
|
63
|
+
if (node.attrs.isNumberColumnEnabled) {
|
|
64
|
+
widthValue -= akEditorTableNumberColumnWidth;
|
|
71
65
|
}
|
|
72
66
|
|
|
73
|
-
return
|
|
67
|
+
return shouldHaveInlineWidth ? widthValue : undefined;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const handleInlineTableWidth = (
|
|
71
|
+
table: HTMLElement,
|
|
72
|
+
width: number | undefined,
|
|
73
|
+
) => {
|
|
74
|
+
if (!table || !width) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
table.style.setProperty('width', `${width}px`);
|
|
74
78
|
};
|
|
75
79
|
|
|
76
80
|
const toDOM = (node: PmNode, props: Props) => {
|
|
@@ -82,7 +86,7 @@ const toDOM = (node: PmNode, props: Props) => {
|
|
|
82
86
|
|
|
83
87
|
return [
|
|
84
88
|
'table',
|
|
85
|
-
tableAttributes(node
|
|
89
|
+
tableAttributes(node),
|
|
86
90
|
colgroup,
|
|
87
91
|
['tbody', 0],
|
|
88
92
|
] as DOMOutputSpec;
|
|
@@ -123,8 +127,18 @@ export default class TableView extends ReactNodeView<Props> {
|
|
|
123
127
|
contentDOM?: HTMLElement;
|
|
124
128
|
};
|
|
125
129
|
|
|
130
|
+
const tableInlineWidth = getInlineWidth(
|
|
131
|
+
this.node,
|
|
132
|
+
this.reactComponentProps.options,
|
|
133
|
+
this.reactComponentProps.view.state,
|
|
134
|
+
this.reactComponentProps.getPos(),
|
|
135
|
+
);
|
|
136
|
+
|
|
126
137
|
if (rendered.dom) {
|
|
127
138
|
this.table = rendered.dom;
|
|
139
|
+
if (tableInlineWidth) {
|
|
140
|
+
handleInlineTableWidth(this.table, tableInlineWidth);
|
|
141
|
+
}
|
|
128
142
|
}
|
|
129
143
|
|
|
130
144
|
return rendered;
|
|
@@ -135,15 +149,22 @@ export default class TableView extends ReactNodeView<Props> {
|
|
|
135
149
|
return;
|
|
136
150
|
}
|
|
137
151
|
|
|
138
|
-
const attrs = tableAttributes(
|
|
152
|
+
const attrs = tableAttributes(node);
|
|
153
|
+
(Object.keys(attrs) as Array<keyof typeof attrs>).forEach((attr) => {
|
|
154
|
+
this.table!.setAttribute(attr, attrs[attr]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// handle inline style when table been resized
|
|
158
|
+
const tableInlineWidth = getInlineWidth(
|
|
139
159
|
node,
|
|
140
160
|
(this.reactComponentProps as Props).options,
|
|
141
161
|
this.view.state,
|
|
142
162
|
this.getPos(),
|
|
143
163
|
);
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
164
|
+
|
|
165
|
+
if (tableInlineWidth) {
|
|
166
|
+
handleInlineTableWidth(this.table, tableInlineWidth);
|
|
167
|
+
}
|
|
147
168
|
}
|
|
148
169
|
|
|
149
170
|
getNode = () => {
|
|
@@ -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
|
+
};
|