@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
230 lines (225 loc) • 7.71 kB
JavaScript
import { useEffect, useRef } from 'react';
import { ACTION_SUBJECT, EVENT_TYPE, TABLE_ACTION } from '@atlaskit/editor-common/analytics';
import { getBreakpointKey } from '@atlaskit/editor-common/utils/analytics';
import { TableMap } from '@atlaskit/editor-tables/table-map';
import { findTable, getSelectionRect } from '@atlaskit/editor-tables/utils';
import { hasTableBeenResized } from '../table-resizing/utils/colgroup';
import { getTableWidth } from './nodes';
export function getSelectedTableInfo(selection) {
let map;
let totalRowCount = 0;
let totalColumnCount = 0;
const table = findTable(selection);
if (table) {
map = TableMap.get(table.node);
totalRowCount = map.height;
totalColumnCount = map.width;
}
return {
table,
map,
totalRowCount,
totalColumnCount
};
}
export function getSelectedCellInfo(selection) {
let horizontalCells = 1;
let verticalCells = 1;
let totalCells = 1;
const {
table,
map,
totalRowCount,
totalColumnCount
} = getSelectedTableInfo(selection);
if (table && map) {
const rect = getSelectionRect(selection);
if (rect) {
totalCells = map.cellsInRect(rect).length;
horizontalCells = rect.right - rect.left;
verticalCells = rect.bottom - rect.top;
}
}
return {
totalRowCount,
totalColumnCount,
horizontalCells,
verticalCells,
totalCells
};
}
export const withEditorAnalyticsAPI = payload => editorAnalyticsAPI => {
return command => (state, dispatch, view) => command(state, tr => {
const dynamicPayload = payload instanceof Function ? payload(state) : payload;
if (dynamicPayload) {
editorAnalyticsAPI === null || editorAnalyticsAPI === void 0 ? void 0 : editorAnalyticsAPI.attachAnalyticsEvent(dynamicPayload)(tr);
}
if (dispatch) {
dispatch(tr);
}
return true;
}, view);
};
export const generateResizedPayload = props => {
var _props$originalNode$a;
const tableMap = TableMap.get(props.resizedNode);
return {
action: TABLE_ACTION.RESIZED,
actionSubject: ACTION_SUBJECT.TABLE,
eventType: EVENT_TYPE.TRACK,
attributes: {
newWidth: props.resizedNode.attrs.width,
prevWidth: (_props$originalNode$a = props.originalNode.attrs.width) !== null && _props$originalNode$a !== void 0 ? _props$originalNode$a : null,
nodeSize: props.resizedNode.nodeSize,
totalTableWidth: hasTableBeenResized(props.resizedNode) ? getTableWidth(props.resizedNode) : null,
totalRowCount: tableMap.height,
totalColumnCount: tableMap.width
}
};
};
export const reduceResizeFrameRateSamples = frameRateSamples => {
if (frameRateSamples.length > 1) {
const frameRateSum = frameRateSamples.reduce((sum, frameRate, index) => {
if (index === 0) {
return sum;
} else {
return sum + frameRate;
}
}, 0);
const averageFrameRate = Math.round(frameRateSum / (frameRateSamples.length - 1));
return [frameRateSamples[0], averageFrameRate];
} else {
return frameRateSamples;
}
};
export const generateResizeFrameRatePayloads = props => {
const reducedResizeFrameRateSamples = reduceResizeFrameRateSamples(props.frameRateSamples);
return reducedResizeFrameRateSamples.map((frameRateSample, index) => ({
action: TABLE_ACTION.RESIZE_PERF_SAMPLING,
actionSubject: ACTION_SUBJECT.TABLE,
eventType: EVENT_TYPE.OPERATIONAL,
attributes: {
frameRate: frameRateSample,
nodeSize: props.originalNode.nodeSize,
docSize: props.docSize,
isInitialSample: index === 0
}
}));
};
/**
* Measures the framerate of a component over a given time period.
*/
export const useMeasureFramerate = (config = {}) => {
const {
maxSamples = 10,
minFrames = 5,
minTimeMs = 500,
sampleRateMs = 1000,
timeoutMs = 200
} = config;
const frameCount = useRef(0);
const lastTime = useRef(0);
const timeoutId = useRef();
const frameRateSamples = useRef([]);
useEffect(() => {
return () => {
if (timeoutId.current) {
clearTimeout(timeoutId.current);
}
};
}, []);
const startMeasure = () => {
frameCount.current = 0;
lastTime.current = performance.now();
};
/**
* Returns an array of frame rate samples as integers.
*/
const endMeasure = () => {
const samples = frameRateSamples.current;
frameRateSamples.current = [];
return samples;
};
const sampleFrameRate = (delay = 0) => {
const currentTime = performance.now();
const deltaTime = currentTime - lastTime.current - delay;
const isValidSample = deltaTime > minTimeMs && frameCount.current >= minFrames;
if (isValidSample) {
const frameRate = Math.round(frameCount.current / (deltaTime / 1000));
frameRateSamples.current.push(frameRate);
}
frameCount.current = 0;
lastTime.current = 0;
};
/**
* Counts the number of frames that occur within a given time period. Intended to be called
* inside a `requestAnimationFrame` callback.
*/
const countFrames = () => {
if (frameRateSamples.current.length >= maxSamples && timeoutId.current) {
clearTimeout(timeoutId.current);
return;
}
/**
* Allows us to keep counting frames even if `startMeasure` is not called
*/
if (lastTime.current === 0) {
lastTime.current = performance.now();
}
frameCount.current++;
if (timeoutId.current) {
clearTimeout(timeoutId.current);
}
if (performance.now() - lastTime.current > sampleRateMs) {
sampleFrameRate();
} else {
timeoutId.current = setTimeout(() => sampleFrameRate(timeoutMs), timeoutMs);
}
};
return {
startMeasure,
endMeasure,
countFrames
};
};
const tableContainerNodes = new Set(['layoutSection', 'layoutColumn', 'expand', 'nestedExpand', 'extension', 'bodiedExtension', 'multiBodiedExtension', 'extensionFrame', 'table', 'tableCell', 'tableHeader', 'tableRow']);
export const getWidthInfoPayload = (editorView, editorWidth) => {
const tablesInfo = [];
editorView.state.doc.nodesBetween(0, editorView.state.doc.content.size, (node, pos, parent) => {
if (!tableContainerNodes.has(node.type.name)) {
return false;
}
if (node.type.name === 'table') {
var _domAtPos$node;
const domAtPos = editorView.domAtPos(pos + 1);
const table = (_domAtPos$node = domAtPos.node) === null || _domAtPos$node === void 0 ? void 0 : _domAtPos$node.parentElement;
const isNestedTable = (parent === null || parent === void 0 ? void 0 : parent.type.name) === 'tableCell' || (parent === null || parent === void 0 ? void 0 : parent.type.name) === 'tableHeader';
if (table instanceof HTMLTableElement) {
tablesInfo.push({
tableWidth: table.scrollWidth,
isNestedTable: isNestedTable,
hasScrollbar: table.parentElement ? (table === null || table === void 0 ? void 0 : table.parentElement.clientWidth) < table.scrollWidth : false
});
}
}
});
// only send the event if there are tables on the page
if (tablesInfo.length === 0) {
return undefined;
}
const maxTableWidth = Math.max(...tablesInfo.map(table => table.tableWidth));
return {
action: TABLE_ACTION.TABLE_WIDTH_INFO,
actionSubject: ACTION_SUBJECT.TABLE,
attributes: {
editorWidth,
editorWidthBreakpoint: getBreakpointKey(editorWidth),
hasTableWithScrollbar: tablesInfo.some(table => table.hasScrollbar),
hasTableWiderThanEditor: maxTableWidth > editorWidth,
maxTableWidthBreakpoint: getBreakpointKey(maxTableWidth),
tableWidthInfo: tablesInfo,
mode: 'editor'
},
eventType: EVENT_TYPE.OPERATIONAL
};
};