Skip to content

Commit

Permalink
feat: improve table performance (#246)
Browse files Browse the repository at this point in the history
* feat: optimize table rendering

* fix: memoize

* fix: more opti

* fix: bug

* fix: render 0

* fix: return type

* Update HTMLRenderer.tsx
  • Loading branch information
kristw authored and zhaoyongjie committed Nov 26, 2021
1 parent 39a8c79 commit b785b20
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import withStyles, { WithStylesProps } from '@airbnb/lunar/lib/composers/withSty
import { Renderers, ParentRow, ColumnMetadata } from '@airbnb/lunar/lib/components/DataTable/types';
import dompurify from 'dompurify';
import { createSelector } from 'reselect';
import { getRenderer, ColumnType, Cell } from './renderer';
import getRenderer, { ColumnType, Cell } from './getRenderer';

type Props = {
data: ParentRow[];
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React, { useMemo } from 'react';
import dompurify from 'dompurify';

const isHTML = RegExp.prototype.test.bind(/(<([^>]+)>)/i);

export default function HTMLRenderer({ value }: { value: string }) {
if (isHTML(value)) {
const html = useMemo(() => ({ __html: dompurify.sanitize(value) }), [value]);

return (
// eslint-disable-next-line react/no-danger
<div dangerouslySetInnerHTML={html} />
);
}

return <>{value}</>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/* eslint-disable complexity */
/* eslint-disable jsx-a11y/no-static-element-interactions */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable no-magic-numbers */
import React, { CSSProperties, useMemo } from 'react';
import { HEIGHT_TO_PX } from '@airbnb/lunar/lib/components/DataTable/constants';
import { RendererProps } from '@airbnb/lunar/lib/components/DataTable/types';
import { NumberFormatter } from '@superset-ui/number-format';
import { TimeFormatter } from '@superset-ui/time-format';
import HTMLRenderer from './components/HTMLRenderer';

const NEGATIVE_COLOR = '#FFA8A8';
const POSITIVE_COLOR = '#ced4da';
const SELECTION_COLOR = '#EBEBEB';

const NOOP = () => {};

const HEIGHT = HEIGHT_TO_PX.micro;

export type ColumnType = {
key: string;
label: string;
format?: NumberFormatter | TimeFormatter | undefined;
type: 'metric' | 'string';
maxValue?: number;
minValue?: number;
};

export type Cell = {
key: string;
value: any;
};

const NUMBER_STYLE: CSSProperties = {
marginLeft: 'auto',
marginRight: '4px',
zIndex: 10,
};

export default function getRenderer({
column,
alignPositiveNegative,
colorPositiveNegative,
enableFilter,
isSelected,
handleCellSelected,
}: {
column: ColumnType;
alignPositiveNegative: boolean;
colorPositiveNegative: boolean;
enableFilter: boolean;
isSelected: (cell: Cell) => boolean;
handleCellSelected: (cell: Cell) => (...args: any[]) => void;
}) {
const { format, type } = column;

const isMetric = type === 'metric';
const cursorStyle = enableFilter && !isMetric ? 'pointer' : 'default';

const boxContainerStyle: CSSProperties = {
alignItems: 'center',
display: 'flex',
margin: '0px 16px',
position: 'relative',
textAlign: isMetric ? 'right' : 'left',
};

const baseBoxStyle: CSSProperties = {
cursor: cursorStyle,
margin: '4px -16px',
};

const selectedBoxStyle: CSSProperties = {
...baseBoxStyle,
backgroundColor: SELECTION_COLOR,
};

const getBoxStyle = enableFilter
? (selected: boolean) => (selected ? selectedBoxStyle : baseBoxStyle)
: () => baseBoxStyle;

const posExtent = Math.abs(Math.max(column.maxValue!, 0));
const negExtent = Math.abs(Math.min(column.minValue!, 0));
const total = posExtent + negExtent;

return ({ keyName, row }: RendererProps) => {
const value = row.rowData.data[keyName];
const cell = { key: keyName as string, value };
const handleClick = isMetric ? NOOP : useMemo(() => handleCellSelected(cell), [keyName, value]);

let Parent;
if (isMetric) {
let left = 0;
let width = 0;
const numericValue = value as number;
if (alignPositiveNegative) {
width = Math.abs(
Math.round((numericValue / Math.max(column.maxValue!, Math.abs(column.minValue!))) * 100),
);
} else {
left = Math.round((Math.min(negExtent + numericValue, negExtent) / total) * 100);
width = Math.round((Math.abs(numericValue) / total) * 100);
}
const color = colorPositiveNegative && numericValue < 0 ? NEGATIVE_COLOR : POSITIVE_COLOR;

Parent = ({ children }: { children: React.ReactNode }) => {
const barStyle: CSSProperties = {
background: color,
borderRadius: 3,
height: HEIGHT / 2 + 4,
left: `${left}%`,
position: 'absolute',
width: `${width}%`,
};

return (
<>
<div style={barStyle} />
<div style={NUMBER_STYLE}>{children}</div>
</>
);
};
} else {
Parent = React.Fragment;
}

return (
<div onClick={handleClick}>
<div style={getBoxStyle(isSelected(cell))}>
<div style={boxContainerStyle}>
<Parent>
{format ? (
format.format(value as number & Date)
) : (
<HTMLRenderer value={value as string} />
)}
</Parent>
</div>
</div>
</div>
);
};
}

This file was deleted.

0 comments on commit b785b20

Please sign in to comment.