UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

230 lines (225 loc) 7.71 kB
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 }; };