table using MultiGrid

This commit is contained in:
ryan
2019-03-09 00:27:33 -08:00
parent 343e49decb
commit 078d8f1210
6 changed files with 646 additions and 402 deletions
@@ -1,40 +1,10 @@
import React, { FunctionComponent } from 'react';
// import React from 'react';
import { storiesOf } from '@storybook/react';
import { Table } from './Table';
import { migratedTestTable, migratedTestStyles, simpleTable } from './examples';
import { ScopedVars } from '../../types/index';
import { renderComponentWithTheme } from '../../utils/storybook/withTheme';
import { AutoSizer } from 'react-virtualized';
const CenteredStory: FunctionComponent<{}> = ({ children }) => {
return (
<div
style={{
width: '100%',
height: '100vh',
}}
>
<AutoSizer>
{({ width, height }) => (
<div
style={{
width: `${width}px`,
height: `${height}px`,
border: '1px solid red',
}}
>
<div>
Need to pass {width}/{height} to the table?
</div>
{children}
</div>
)}
</AutoSizer>
</div>
);
};
import { ScopedVars, TableData } from '../../types/index';
import { withFullSizeStory } from '../../utils/storybook/withFullSizeStory';
const replaceVariables = (value: any, scopedVars: ScopedVars | undefined) => {
// if (scopedVars) {
@@ -47,24 +17,49 @@ const replaceVariables = (value: any, scopedVars: ScopedVars | undefined) => {
};
storiesOf('UI - Alpha/Table', module)
.addDecorator(story => <CenteredStory>{story()}</CenteredStory>)
.add('basic', () => {
return renderComponentWithTheme(Table, {
return withFullSizeStory(Table, {
styles: [],
data: simpleTable,
replaceVariables,
showHeader: true,
width: 500,
height: 300,
});
})
.add('Test Configuration', () => {
return renderComponentWithTheme(Table, {
return withFullSizeStory(Table, {
styles: migratedTestStyles,
data: migratedTestTable,
replaceVariables,
showHeader: true,
width: 500,
height: 300,
});
})
.add('Lots of cells', () => {
const data = {
columns: [],
rows: [],
type: 'table',
columnMap: {},
} as TableData;
for (let i = 0; i < 20; i++) {
data.columns.push({
text: 'Column ' + i,
});
}
for (let r = 0; r < 500; r++) {
const row = [];
for (let i = 0; i < 20; i++) {
row.push(r + i);
}
data.rows.push(row);
}
console.log('DATA:', data);
return withFullSizeStory(Table, {
styles: simpleTable,
data,
replaceVariables,
showHeader: true,
fixedColumnCount: 1,
fixedRowCount: 1,
});
});
@@ -3,9 +3,9 @@ import _ from 'lodash';
import { getColorDefinitionByName } from '@grafana/ui';
import { ScopedVars } from '@grafana/ui/src/types';
import { getTheme } from '../../themes';
import Table from './Table';
import { migratedTestTable, migratedTestStyles } from './examples';
import TableXXXX from './TableXXXX';
// TODO: this is commented out with *x* describe!
// Essentially all the elements need to replace the <td> with <div>
@@ -28,7 +28,7 @@ xdescribe('when rendering table', () => {
};
const table = migratedTestTable;
const renderer = new Table({
const renderer = new TableXXXX({
styles: migratedTestStyles,
data: migratedTestTable,
replaceVariables,
+108 -348
View File
@@ -1,22 +1,19 @@
// Libraries
import _ from 'lodash';
import React, { Component, CSSProperties, ReactNode } from 'react';
import React, { Component, ReactNode } from 'react';
import {
Table as RVTable,
SortDirectionType,
SortIndicator,
Column as RVColumn,
TableHeaderProps,
TableCellProps,
MultiGrid,
CellMeasurerCache,
CellMeasurer,
GridCellProps,
} from 'react-virtualized';
import { Themeable } from '../../types/theme';
import { sortTableData } from '../../utils/processTimeSeries';
import moment from 'moment';
import { getValueFormat, TableData, getColorFromHexRgbOrName, InterpolateFunction, Column } from '@grafana/ui';
import { Index } from 'react-virtualized';
import { TableData, InterpolateFunction } from '@grafana/ui';
import { ColumnStyle } from './Table';
// APP Imports!!!
@@ -27,7 +24,7 @@ export interface ColumnStyle {
pattern?: string;
alias?: string;
colorMode?: string;
colorMode?: 'cell' | 'value';
colors?: any[];
decimals?: number;
thresholds?: any[];
@@ -61,6 +58,8 @@ interface ColumnInfo {
interface Props extends Themeable {
data?: TableData;
showHeader: boolean;
fixedColumnCount: number;
fixedRowCount: number;
styles: ColumnStyle[];
replaceVariables: InterpolateFunction;
width: number;
@@ -78,8 +77,12 @@ export class Table extends Component<Props, State> {
columns: ColumnInfo[] = [];
colorState: any;
_cache: CellMeasurerCache;
static defaultProps = {
showHeader: true,
fixedRowCount: 1,
fixedColumnCount: 0,
};
constructor(props: Props) {
@@ -89,6 +92,11 @@ export class Table extends Component<Props, State> {
data: props.data,
};
this._cache = new CellMeasurerCache({
defaultHeight: 30,
defaultWidth: 150,
});
this.initRenderer();
}
@@ -109,314 +117,17 @@ export class Table extends Component<Props, State> {
}
}
initRenderer() {
const { styles } = this.props;
const { data } = this.state;
this.colorState = {};
if (!data || !data.columns) {
this.columns = [];
return;
}
this.columns = data.columns.map((col, index) => {
let title = col.text;
let style; // ColumnStyle
// Find the style based on the text
for (let i = 0; i < styles.length; i++) {
const s = styles[i];
const regex = 'XXX'; //kbn.stringToJsRegex(s.pattern);
if (title.match(regex)) {
style = s;
if (s.alias) {
title = title.replace(regex, s.alias);
}
break;
}
}
return {
header: title,
accessor: col.text, // unique?
style: style,
formatter: this.createColumnFormatter(col, style),
};
});
}
//----------------------------------------------------------------------
// renderer.ts copy (taken from angular version!!!)
//----------------------------------------------------------------------
getColorForValue(value: any, style: ColumnStyle) {
if (!style.thresholds || !style.colors) {
return null;
}
const { theme } = this.props;
for (let i = style.thresholds.length; i > 0; i--) {
if (value >= style.thresholds[i - 1]) {
return getColorFromHexRgbOrName(style.colors[i], theme.type);
}
}
return getColorFromHexRgbOrName(_.first(style.colors), theme.type);
}
defaultCellFormatter(v: any, style?: ColumnStyle): string {
if (v === null || v === void 0 || v === undefined) {
return '';
}
if (_.isArray(v)) {
v = v.join(', ');
}
return v; // react will sanitize
}
createColumnFormatter(schema: Column, style?: ColumnStyle): CellFormatter {
if (!style) {
return this.defaultCellFormatter;
}
if (style.type === 'hidden') {
return v => {
return undefined;
};
}
if (style.type === 'date') {
return v => {
if (v === undefined || v === null) {
return '-';
}
if (_.isArray(v)) {
v = v[0];
}
let date = moment(v);
if (this.props.isUTC) {
date = date.utc();
}
return date.format(style.dateFormat);
};
}
if (style.type === 'string') {
return v => {
if (_.isArray(v)) {
v = v.join(', ');
}
const mappingType = style.mappingType || 0;
if (mappingType === 1 && style.valueMaps) {
for (let i = 0; i < style.valueMaps.length; i++) {
const map = style.valueMaps[i];
if (v === null) {
if (map.value === 'null') {
return map.text;
}
continue;
}
// Allow both numeric and string values to be mapped
if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) {
this.setColorState(v, style);
return this.defaultCellFormatter(map.text, style);
}
}
}
if (mappingType === 2 && style.rangeMaps) {
for (let i = 0; i < style.rangeMaps.length; i++) {
const map = style.rangeMaps[i];
if (v === null) {
if (map.from === 'null' && map.to === 'null') {
return map.text;
}
continue;
}
if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) {
this.setColorState(v, style);
return this.defaultCellFormatter(map.text, style);
}
}
}
if (v === null || v === void 0) {
return '-';
}
this.setColorState(v, style);
return this.defaultCellFormatter(v, style);
};
}
if (style.type === 'number') {
const valueFormatter = getValueFormat(style.unit || schema.unit || 'none');
return v => {
if (v === null || v === void 0) {
return '-';
}
if (_.isString(v) || _.isArray(v)) {
return this.defaultCellFormatter(v, style);
}
this.setColorState(v, style);
return valueFormatter(v, style.decimals, null);
};
}
return value => {
return this.defaultCellFormatter(value, style);
};
}
setColorState(value: any, style: ColumnStyle) {
if (!style.colorMode) {
return;
}
if (value === null || value === void 0 || _.isArray(value)) {
return;
}
if (_.isNaN(value)) {
return;
}
const numericValue = Number(value);
this.colorState[style.colorMode] = this.getColorForValue(numericValue, style);
}
renderRowVariables(rowIndex: number) {
const scopedVars: any = {};
const row = this.rowGetter({ index: rowIndex });
for (let i = 0; i < row.length; i++) {
scopedVars[`__cell_${i}`] = { value: row[i] };
}
return scopedVars;
}
renderCell(columnIndex: number, rowIndex: number, value: any): ReactNode {
const column = this.columns[columnIndex];
if (column.formatter) {
value = column.formatter(value, column.style);
}
const style: CSSProperties = {};
const cellClasses = [];
let cellClass = '';
if (this.colorState.cell) {
style.backgroundColor = this.colorState.cell;
style.color = 'white';
this.colorState.cell = null;
} else if (this.colorState.value) {
style.color = this.colorState.value;
this.colorState.value = null;
}
if (value === undefined) {
style.display = 'none';
column.hidden = true;
} else {
column.hidden = false;
}
if (column.style && column.style.preserveFormat) {
cellClasses.push('table-panel-cell-pre');
}
let columnHtml: JSX.Element;
if (column.style && column.style.link) {
// Render cell as link
const { replaceVariables } = this.props;
const scopedVars = this.renderRowVariables(rowIndex);
scopedVars['__cell'] = { value: value };
const cellLink = replaceVariables(column.style.linkUrl, scopedVars, encodeURIComponent);
const cellLinkTooltip = replaceVariables(column.style.linkTooltip, scopedVars);
const cellTarget = column.style.linkTargetBlank ? '_blank' : '';
cellClasses.push('table-panel-cell-link');
columnHtml = (
<a
href={cellLink}
target={cellTarget}
data-link-tooltip
data-original-title={cellLinkTooltip}
data-placement="right"
>
{value}
</a>
);
} else {
columnHtml = <span>{value}</span>;
}
let filterLink: JSX.Element | null = null;
if (column.filterable) {
cellClasses.push('table-panel-cell-filterable');
filterLink = (
<span>
<a
className="table-panel-filter-link"
data-link-tooltip
data-original-title="Filter out value"
data-placement="bottom"
data-row={rowIndex}
data-column={columnIndex}
data-operator="!="
>
<i className="fa fa-search-minus" />
</a>
<a
className="table-panel-filter-link"
data-link-tooltip
data-original-title="Filter for value"
data-placement="bottom"
data-row={rowIndex}
data-column={columnIndex}
data-operator="="
>
<i className="fa fa-search-plus" />
</a>
</span>
);
}
if (cellClasses.length) {
cellClass = cellClasses.join(' ');
}
style.width = '100%';
style.height = '100%';
columnHtml = (
<div className={cellClass} style={style}>
{columnHtml}
{filterLink}
</div>
);
return columnHtml;
}
initRenderer() {}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
rowGetter = ({ index }: Index) => {
return this.state.data!.rows[index];
};
doSort = (info: any) => {
let dir = info.sortDirection;
let sort = info.sortBy;
if (sort !== this.state.sortBy) {
doSort = (columnIndex: number) => {
let sort: any = this.state.sortBy;
let dir = this.state.sortDirection;
if (sort !== columnIndex) {
dir = 'DESC';
sort = columnIndex;
} else if (dir === 'DESC') {
dir = 'ASC';
} else {
@@ -425,58 +136,107 @@ export class Table extends Component<Props, State> {
this.setState({ sortBy: sort, sortDirection: dir });
};
headerRenderer = (header: TableHeaderProps): ReactNode => {
const dataKey = header.dataKey as any; // types say string, but it is number!
handelClick = (rowIndex: number, columnIndex: number) => {
const { showHeader } = this.props;
const { data } = this.state;
const realRowIndex = rowIndex - (showHeader ? 1 : 0);
if (realRowIndex < 0) {
this.doSort(columnIndex);
} else {
const row = data!.rows[realRowIndex];
const value = row[columnIndex];
console.log('CLICK', rowIndex, columnIndex, value);
}
};
headerRenderer = (columnIndex: number): ReactNode => {
const { data, sortBy, sortDirection } = this.state;
const col = data!.columns[dataKey];
const col = data!.columns[columnIndex];
const sorting = sortBy === columnIndex;
return (
<div>
{col.text} {sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />}
{col.text}{' '}
{sorting && (
<span>
{sortDirection}
<SortIndicator sortDirection={sortDirection} />
</span>
)}
</div>
);
};
cellRenderer = (cell: TableCellProps) => {
const { columnIndex, rowIndex } = cell;
const row = this.state.data!.rows[rowIndex];
const val = row[columnIndex];
return this.renderCell(columnIndex, rowIndex, val);
cellRenderer = (props: GridCellProps): React.ReactNode => {
const { rowIndex, columnIndex, key, parent, style } = props;
const { showHeader } = this.props;
const { data } = this.state;
if (!data) {
return <div>?</div>;
}
const realRowIndex = rowIndex - (showHeader ? 1 : 0);
let classNames = 'gf-table-cell';
let content = null;
if (realRowIndex < 0) {
content = this.headerRenderer(columnIndex);
classNames = 'gf-table-header';
} else {
const row = data.rows[realRowIndex];
const value = row[columnIndex];
content = (
<div>
{rowIndex}/{columnIndex}: {value}
</div>
);
}
return (
<CellMeasurer cache={this._cache} columnIndex={columnIndex} key={key} parent={parent} rowIndex={rowIndex}>
<div
onClick={() => this.handelClick(rowIndex, columnIndex)}
className={classNames}
style={{
...style,
whiteSpace: 'nowrap',
}}
>
{content}
</div>
</CellMeasurer>
);
};
render() {
const { width, height, showHeader } = this.props;
const { data } = this.props;
const { data, showHeader, width, height, fixedColumnCount, fixedRowCount } = this.props;
if (!data) {
return <div>NO Data</div>;
}
return (
<RVTable
disableHeader={!showHeader}
headerHeight={30}
height={height}
overscanRowCount={10}
rowHeight={30}
rowGetter={this.rowGetter}
rowCount={data.rows.length}
sort={this.doSort}
<MultiGrid
{
...this.state /** Force MultiGrid to update when any property updates */
}
columnCount={data.columns.length}
rowCount={data.rows.length + (showHeader ? 1 : 0)}
overscanColumnCount={2}
overscanRowCount={2}
columnWidth={this._cache.columnWidth}
deferredMeasurementCache={this._cache}
cellRenderer={this.cellRenderer}
rowHeight={this._cache.rowHeight}
width={width}
>
{data.columns.map((col, index) => {
return (
<RVColumn
key={index}
dataKey={index}
headerRenderer={this.headerRenderer}
cellRenderer={this.cellRenderer}
width={150}
minWidth={50}
flexGrow={1}
/>
);
})}
</RVTable>
height={height}
fixedColumnCount={fixedColumnCount}
fixedRowCount={fixedRowCount}
classNameTopLeftGrid="gf-table-fixed-row-and-column"
classNameTopRightGrid="gf-table-fixed-row"
classNameBottomLeftGrid="gf-table-fixed-column"
classNameBottomRightGrid="gf-table-normal-cell"
/>
);
}
}
@@ -0,0 +1,456 @@
// Libraries
import _ from 'lodash';
import React, { Component, CSSProperties, ReactNode } from 'react';
import {
Table as RVTable,
SortDirectionType,
SortIndicator,
Column as RVColumn,
TableHeaderProps,
TableCellProps,
} from 'react-virtualized';
import { Themeable } from '../../types/theme';
import { sortTableData } from '../../utils/processTimeSeries';
import moment from 'moment';
import { getValueFormat, TableData, getColorFromHexRgbOrName, InterpolateFunction, Column } from '@grafana/ui';
import { Index } from 'react-virtualized';
import { ColumnStyle } from './Table';
type CellFormatter = (v: any, style?: ColumnStyle) => ReactNode;
interface ColumnInfo {
header: string;
accessor: string; // the field name
style?: ColumnStyle;
hidden?: boolean;
formatter: CellFormatter;
filterable?: boolean;
}
interface Props extends Themeable {
data?: TableData;
showHeader: boolean;
styles: ColumnStyle[];
replaceVariables: InterpolateFunction;
width: number;
height: number;
isUTC?: boolean;
}
interface State {
sortBy?: number;
sortDirection?: SortDirectionType;
data?: TableData;
}
export class TableXXXX extends Component<Props, State> {
columns: ColumnInfo[] = [];
colorState: any;
static defaultProps = {
showHeader: true,
};
constructor(props: Props) {
super(props);
this.state = {
data: props.data,
};
this.initRenderer();
}
componentDidUpdate(prevProps: Props, prevState: State) {
const { data, styles } = this.props;
const { sortBy, sortDirection } = this.state;
const dataChanged = data !== prevProps.data;
// Update the renderer if options change
if (dataChanged || styles !== prevProps.styles) {
this.initRenderer();
}
// Update the data when data or sort changes
if (dataChanged || sortBy !== prevState.sortBy || sortDirection !== prevState.sortDirection) {
const sorted = data ? sortTableData(data, sortBy, sortDirection === 'DESC') : data;
this.setState({ data: sorted });
}
}
initRenderer() {
const { styles } = this.props;
const { data } = this.state;
this.colorState = {};
if (!data || !data.columns) {
this.columns = [];
return;
}
this.columns = data.columns.map((col, index) => {
let title = col.text;
let style; // ColumnStyle
// Find the style based on the text
for (let i = 0; i < styles.length; i++) {
const s = styles[i];
const regex = 'XXX'; //kbn.stringToJsRegex(s.pattern);
if (title.match(regex)) {
style = s;
if (s.alias) {
title = title.replace(regex, s.alias);
}
break;
}
}
return {
header: title,
accessor: col.text, // unique?
style: style,
formatter: this.createColumnFormatter(col, style),
};
});
}
//----------------------------------------------------------------------
// renderer.ts copy (taken from angular version!!!)
//----------------------------------------------------------------------
getColorForValue(value: any, style: ColumnStyle) {
if (!style.thresholds || !style.colors) {
return null;
}
const { theme } = this.props;
for (let i = style.thresholds.length; i > 0; i--) {
if (value >= style.thresholds[i - 1]) {
return getColorFromHexRgbOrName(style.colors[i], theme.type);
}
}
return getColorFromHexRgbOrName(_.first(style.colors), theme.type);
}
defaultCellFormatter(v: any, style?: ColumnStyle): string {
if (v === null || v === void 0 || v === undefined) {
return '';
}
if (_.isArray(v)) {
v = v.join(', ');
}
return v; // react will sanitize
}
createColumnFormatter(schema: Column, style?: ColumnStyle): CellFormatter {
if (!style) {
return this.defaultCellFormatter;
}
if (style.type === 'hidden') {
return v => {
return undefined;
};
}
if (style.type === 'date') {
return v => {
if (v === undefined || v === null) {
return '-';
}
if (_.isArray(v)) {
v = v[0];
}
let date = moment(v);
if (this.props.isUTC) {
date = date.utc();
}
return date.format(style.dateFormat);
};
}
if (style.type === 'string') {
return v => {
if (_.isArray(v)) {
v = v.join(', ');
}
const mappingType = style.mappingType || 0;
if (mappingType === 1 && style.valueMaps) {
for (let i = 0; i < style.valueMaps.length; i++) {
const map = style.valueMaps[i];
if (v === null) {
if (map.value === 'null') {
return map.text;
}
continue;
}
// Allow both numeric and string values to be mapped
if ((!_.isString(v) && Number(map.value) === Number(v)) || map.value === v) {
this.setColorState(v, style);
return this.defaultCellFormatter(map.text, style);
}
}
}
if (mappingType === 2 && style.rangeMaps) {
for (let i = 0; i < style.rangeMaps.length; i++) {
const map = style.rangeMaps[i];
if (v === null) {
if (map.from === 'null' && map.to === 'null') {
return map.text;
}
continue;
}
if (Number(map.from) <= Number(v) && Number(map.to) >= Number(v)) {
this.setColorState(v, style);
return this.defaultCellFormatter(map.text, style);
}
}
}
if (v === null || v === void 0) {
return '-';
}
this.setColorState(v, style);
return this.defaultCellFormatter(v, style);
};
}
if (style.type === 'number') {
const valueFormatter = getValueFormat(style.unit || schema.unit || 'none');
return v => {
if (v === null || v === void 0) {
return '-';
}
if (_.isString(v) || _.isArray(v)) {
return this.defaultCellFormatter(v, style);
}
this.setColorState(v, style);
return valueFormatter(v, style.decimals, null);
};
}
return value => {
return this.defaultCellFormatter(value, style);
};
}
setColorState(value: any, style: ColumnStyle) {
if (!style.colorMode) {
return;
}
if (value === null || value === void 0 || _.isArray(value)) {
return;
}
if (_.isNaN(value)) {
return;
}
const numericValue = Number(value);
this.colorState[style.colorMode] = this.getColorForValue(numericValue, style);
}
renderRowVariables(rowIndex: number) {
const scopedVars: any = {};
const row = this.rowGetter({ index: rowIndex });
for (let i = 0; i < row.length; i++) {
scopedVars[`__cell_${i}`] = { value: row[i] };
}
return scopedVars;
}
renderCell(columnIndex: number, rowIndex: number, value: any): ReactNode {
const column = this.columns[columnIndex];
if (column.formatter) {
value = column.formatter(value, column.style);
}
const style: CSSProperties = {};
const cellClasses = [];
let cellClass = '';
if (this.colorState.cell) {
style.backgroundColor = this.colorState.cell;
style.color = 'white';
this.colorState.cell = null;
} else if (this.colorState.value) {
style.color = this.colorState.value;
this.colorState.value = null;
}
if (value === undefined) {
style.display = 'none';
column.hidden = true;
} else {
column.hidden = false;
}
if (column.style && column.style.preserveFormat) {
cellClasses.push('table-panel-cell-pre');
}
let columnHtml: JSX.Element;
if (column.style && column.style.link) {
// Render cell as link
const { replaceVariables } = this.props;
const scopedVars = this.renderRowVariables(rowIndex);
scopedVars['__cell'] = { value: value };
const cellLink = replaceVariables(column.style.linkUrl, scopedVars, encodeURIComponent);
const cellLinkTooltip = replaceVariables(column.style.linkTooltip, scopedVars);
const cellTarget = column.style.linkTargetBlank ? '_blank' : '';
cellClasses.push('table-panel-cell-link');
columnHtml = (
<a
href={cellLink}
target={cellTarget}
data-link-tooltip
data-original-title={cellLinkTooltip}
data-placement="right"
>
{value}
</a>
);
} else {
columnHtml = <span>{value}</span>;
}
let filterLink: JSX.Element | null = null;
if (column.filterable) {
cellClasses.push('table-panel-cell-filterable');
filterLink = (
<span>
<a
className="table-panel-filter-link"
data-link-tooltip
data-original-title="Filter out value"
data-placement="bottom"
data-row={rowIndex}
data-column={columnIndex}
data-operator="!="
>
<i className="fa fa-search-minus" />
</a>
<a
className="table-panel-filter-link"
data-link-tooltip
data-original-title="Filter for value"
data-placement="bottom"
data-row={rowIndex}
data-column={columnIndex}
data-operator="="
>
<i className="fa fa-search-plus" />
</a>
</span>
);
}
if (cellClasses.length) {
cellClass = cellClasses.join(' ');
}
style.width = '100%';
style.height = '100%';
columnHtml = (
<div className={cellClass} style={style}>
{columnHtml}
{filterLink}
</div>
);
return columnHtml;
}
//----------------------------------------------------------------------
//----------------------------------------------------------------------
rowGetter = ({ index }: Index) => {
return this.state.data!.rows[index];
};
doSort = (info: any) => {
let dir = info.sortDirection;
let sort = info.sortBy;
if (sort !== this.state.sortBy) {
dir = 'DESC';
} else if (dir === 'DESC') {
dir = 'ASC';
} else {
sort = null;
}
this.setState({ sortBy: sort, sortDirection: dir });
};
headerRenderer = (header: TableHeaderProps): ReactNode => {
const dataKey = header.dataKey as any; // types say string, but it is number!
const { data, sortBy, sortDirection } = this.state;
const col = data!.columns[dataKey];
return (
<div>
{col.text} {sortBy === dataKey && <SortIndicator sortDirection={sortDirection} />}
</div>
);
};
cellRenderer = (cell: TableCellProps) => {
const { columnIndex, rowIndex } = cell;
const row = this.state.data!.rows[rowIndex];
const val = row[columnIndex];
return this.renderCell(columnIndex, rowIndex, val);
};
render() {
const { width, height, showHeader } = this.props;
const { data } = this.props;
if (!data) {
return <div>NO Data</div>;
}
return (
<RVTable
disableHeader={!showHeader}
headerHeight={30}
height={height}
overscanRowCount={10}
rowHeight={30}
rowGetter={this.rowGetter}
rowCount={data.rows.length}
sort={this.doSort}
width={width}
>
{data.columns.map((col, index) => {
return (
<RVColumn
key={index}
dataKey={index}
headerRenderer={this.headerRenderer}
cellRenderer={this.cellRenderer}
width={150}
minWidth={50}
flexGrow={1}
/>
);
})}
</RVTable>
);
}
}
export default TableXXXX;
@@ -9,12 +9,6 @@
display: flex;
flex-direction: row;
align-items: left;
background: $list-item-bg;
border-top: 2px solid $body-bg;
border-bottom: 2px solid $body-bg;
color: $blue;
}
.ReactVirtualized__Table__row {
display: flex;
@@ -37,12 +31,6 @@
margin-right: 10px;
min-width: 0px;
}
.ReactVirtualized__Table__rowColumn {
text-overflow: ellipsis;
white-space: nowrap;
border-right: 2px solid $body-bg;
}
.ReactVirtualized__Table__headerColumn:first-of-type,
.ReactVirtualized__Table__rowColumn:first-of-type {
@@ -62,3 +50,25 @@
width: 1em;
fill: currentColor;
}
.gf-table-header {
padding: 3px 10px;
background: $list-item-bg;
border-top: 2px solid $body-bg;
border-bottom: 2px solid $body-bg;
cursor: pointer;
color: $blue;
}
.gf-table-cell {
padding: 3px 10px;
text-overflow: ellipsis;
white-space: nowrap;
border-right: 2px solid $body-bg;
border-bottom: 2px solid $body-bg;
}
@@ -0,0 +1,23 @@
import React from 'react';
import { AutoSizer } from 'react-virtualized';
export const withFullSizeStory = (component: React.ComponentType<any>, props: any) => (
<div
style={{
height: '100vh',
width: '100%',
}}
>
<AutoSizer>
{({ width, height }) => (
<>
{React.createElement(component, {
...props,
width,
height,
})}
</>
)}
</AutoSizer>
</div>
);