|
|
|
|
@ -14,21 +14,24 @@ import { Themeable } from '../../types/theme'; |
|
|
|
|
import { sortTableData } from '../../utils/processTimeSeries'; |
|
|
|
|
|
|
|
|
|
import { TableData, InterpolateFunction } from '@grafana/ui'; |
|
|
|
|
import { TableCellBuilder, ColumnStyle, getCellBuilder, TableCellBuilderOptions } from './TableCellBuilder'; |
|
|
|
|
import { |
|
|
|
|
TableCellBuilder, |
|
|
|
|
ColumnStyle, |
|
|
|
|
getCellBuilder, |
|
|
|
|
TableCellBuilderOptions, |
|
|
|
|
simpleCellBuilder, |
|
|
|
|
} from './TableCellBuilder'; |
|
|
|
|
import { stringToJsRegex } from '../../utils/index'; |
|
|
|
|
|
|
|
|
|
interface ColumnInfo { |
|
|
|
|
index: number; |
|
|
|
|
header: string; |
|
|
|
|
builder: TableCellBuilder; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export interface Props extends Themeable { |
|
|
|
|
data: TableData; |
|
|
|
|
|
|
|
|
|
showHeader: boolean; |
|
|
|
|
fixedColumnCount: number; |
|
|
|
|
fixedRowCount: number; |
|
|
|
|
fixedHeader: boolean; |
|
|
|
|
fixedColumns: number; |
|
|
|
|
rotate: boolean; |
|
|
|
|
styles: ColumnStyle[]; |
|
|
|
|
|
|
|
|
|
replaceVariables: InterpolateFunction; |
|
|
|
|
width: number; |
|
|
|
|
height: number; |
|
|
|
|
@ -41,15 +44,26 @@ interface State { |
|
|
|
|
data: TableData; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface ColumnRenderInfo { |
|
|
|
|
header: string; |
|
|
|
|
builder: TableCellBuilder; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
interface DataIndex { |
|
|
|
|
column: number; |
|
|
|
|
row: number; // -1 is the header!
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
export class Table extends Component<Props, State> { |
|
|
|
|
columns: ColumnInfo[]; |
|
|
|
|
renderer: ColumnRenderInfo[]; |
|
|
|
|
measurer: CellMeasurerCache; |
|
|
|
|
scrollToTop = false; |
|
|
|
|
|
|
|
|
|
static defaultProps = { |
|
|
|
|
showHeader: true, |
|
|
|
|
fixedRowCount: 1, |
|
|
|
|
fixedColumnCount: 0, |
|
|
|
|
fixedHeader: true, |
|
|
|
|
fixedColumns: 0, |
|
|
|
|
rotate: false, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
constructor(props: Props) { |
|
|
|
|
@ -59,7 +73,7 @@ export class Table extends Component<Props, State> { |
|
|
|
|
data: props.data, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.columns = this.initColumns(props); |
|
|
|
|
this.renderer = this.initColumns(props); |
|
|
|
|
this.measurer = new CellMeasurerCache({ |
|
|
|
|
defaultHeight: 30, |
|
|
|
|
defaultWidth: 150, |
|
|
|
|
@ -70,9 +84,11 @@ export class Table extends Component<Props, State> { |
|
|
|
|
const { data, styles, showHeader } = this.props; |
|
|
|
|
const { sortBy, sortDirection } = this.state; |
|
|
|
|
const dataChanged = data !== prevProps.data; |
|
|
|
|
const configsChanged = showHeader !== prevProps.showHeader; |
|
|
|
|
|
|
|
|
|
console.log('TABLE', this.props.theme); |
|
|
|
|
const configsChanged = |
|
|
|
|
showHeader !== prevProps.showHeader || |
|
|
|
|
this.props.rotate !== prevProps.rotate || |
|
|
|
|
this.props.fixedColumns !== prevProps.fixedColumns || |
|
|
|
|
this.props.fixedHeader !== prevProps.fixedHeader; |
|
|
|
|
|
|
|
|
|
// Reset the size cache
|
|
|
|
|
if (dataChanged || configsChanged) { |
|
|
|
|
@ -80,8 +96,9 @@ export class Table extends Component<Props, State> { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Update the renderer if options change
|
|
|
|
|
// We only *need* do to this if the header values changes, but this does every data update
|
|
|
|
|
if (dataChanged || styles !== prevProps.styles) { |
|
|
|
|
this.columns = this.initColumns(this.props); |
|
|
|
|
this.renderer = this.initColumns(this.props); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Update the data when data or sort changes
|
|
|
|
|
@ -91,9 +108,9 @@ export class Table extends Component<Props, State> { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
initColumns(props: Props): ColumnInfo[] { |
|
|
|
|
/** Given the configuration, setup how each column gets rendered */ |
|
|
|
|
initColumns(props: Props): ColumnRenderInfo[] { |
|
|
|
|
const { styles, data } = props; |
|
|
|
|
console.log('STYLES', styles); |
|
|
|
|
|
|
|
|
|
return data.columns.map((col, index) => { |
|
|
|
|
let title = col.text; |
|
|
|
|
@ -113,7 +130,6 @@ export class Table extends Component<Props, State> { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
index, |
|
|
|
|
header: title, |
|
|
|
|
builder: getCellBuilder(col, style, this.props), |
|
|
|
|
}; |
|
|
|
|
@ -137,27 +153,37 @@ export class Table extends Component<Props, State> { |
|
|
|
|
this.setState({ sortBy: sort, sortDirection: dir }); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** Converts the grid coordinates to TableData coordinates */ |
|
|
|
|
getCellRef = (rowIndex: number, columnIndex: number): DataIndex => { |
|
|
|
|
const { showHeader, rotate } = this.props; |
|
|
|
|
const rowOffset = showHeader ? -1 : 0; |
|
|
|
|
|
|
|
|
|
if (rotate) { |
|
|
|
|
return { column: rowIndex, row: columnIndex + rowOffset }; |
|
|
|
|
} else { |
|
|
|
|
return { column: columnIndex, row: rowIndex + rowOffset }; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
handleCellClick = (rowIndex: number, columnIndex: number) => { |
|
|
|
|
const { showHeader } = this.props; |
|
|
|
|
const { data } = this.state; |
|
|
|
|
const realRowIndex = rowIndex - (showHeader ? 1 : 0); |
|
|
|
|
if (realRowIndex < 0) { |
|
|
|
|
this.doSort(columnIndex); |
|
|
|
|
const { row, column } = this.getCellRef(rowIndex, columnIndex); |
|
|
|
|
if (row < 0) { |
|
|
|
|
this.doSort(column); |
|
|
|
|
} else { |
|
|
|
|
const row = data.rows[realRowIndex]; |
|
|
|
|
const value = row[columnIndex]; |
|
|
|
|
console.log('CLICK', rowIndex, columnIndex, value); |
|
|
|
|
const values = this.state.data.rows[row]; |
|
|
|
|
const value = values[column]; |
|
|
|
|
console.log('CLICK', value, row); |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
headerBuilder = (cell: TableCellBuilderOptions): ReactElement<'div'> => { |
|
|
|
|
const { data, sortBy, sortDirection } = this.state; |
|
|
|
|
const { columnIndex, rowIndex, style } = cell.props; |
|
|
|
|
const { column } = this.getCellRef(rowIndex, columnIndex); |
|
|
|
|
|
|
|
|
|
let col = data.columns[columnIndex]; |
|
|
|
|
const sorting = sortBy === columnIndex; |
|
|
|
|
let col = data.columns[column]; |
|
|
|
|
const sorting = sortBy === column; |
|
|
|
|
if (!col) { |
|
|
|
|
// NOT SURE Why this happens sometimes
|
|
|
|
|
col = { |
|
|
|
|
text: '??' + columnIndex + '???', |
|
|
|
|
}; |
|
|
|
|
@ -171,47 +197,60 @@ export class Table extends Component<Props, State> { |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
getTableCellBuilder = (column: number): TableCellBuilder => { |
|
|
|
|
const render = this.renderer[column]; |
|
|
|
|
if (render && render.builder) { |
|
|
|
|
return render.builder; |
|
|
|
|
} |
|
|
|
|
return simpleCellBuilder; // the default
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
cellRenderer = (props: GridCellProps): React.ReactNode => { |
|
|
|
|
const { rowIndex, columnIndex, key, parent } = props; |
|
|
|
|
const { showHeader } = this.props; |
|
|
|
|
const { row, column } = this.getCellRef(rowIndex, columnIndex); |
|
|
|
|
const { data } = this.state; |
|
|
|
|
|
|
|
|
|
const column = this.columns[columnIndex]; |
|
|
|
|
if (!column) { |
|
|
|
|
// NOT SURE HOW/WHY THIS HAPPENS!
|
|
|
|
|
// Without it it will crash in storybook when you cycle up/down the # of columns
|
|
|
|
|
// this cell is never visible in the output?
|
|
|
|
|
return ( |
|
|
|
|
<div key={key} style={props.style}> |
|
|
|
|
XXXXX |
|
|
|
|
</div> |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const realRowIndex = rowIndex - (showHeader ? 1 : 0); |
|
|
|
|
const isHeader = realRowIndex < 0; |
|
|
|
|
const row = isHeader ? data.columns : data.rows[realRowIndex]; |
|
|
|
|
const value = row[columnIndex]; |
|
|
|
|
const builder = isHeader ? this.headerBuilder : column.builder; |
|
|
|
|
const isHeader = row < 0; |
|
|
|
|
const rowData = isHeader ? data.columns : data.rows[row]; |
|
|
|
|
const value = rowData ? rowData[column] : `[${columnIndex}:${rowIndex}]`; |
|
|
|
|
const builder = isHeader ? this.headerBuilder : this.getTableCellBuilder(column); |
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
|
<CellMeasurer cache={this.measurer} columnIndex={columnIndex} key={key} parent={parent} rowIndex={rowIndex}> |
|
|
|
|
{builder({ value, row, table: this, props })} |
|
|
|
|
{builder({ |
|
|
|
|
value, |
|
|
|
|
row: rowData, |
|
|
|
|
column: data.columns[column], |
|
|
|
|
table: this, |
|
|
|
|
props, |
|
|
|
|
})} |
|
|
|
|
</CellMeasurer> |
|
|
|
|
); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
render() { |
|
|
|
|
const { data, showHeader, width, height } = this.props; |
|
|
|
|
const { showHeader, fixedHeader, fixedColumns, rotate, width, height } = this.props; |
|
|
|
|
const { data } = this.state; |
|
|
|
|
|
|
|
|
|
const columnCount = data.columns.length; |
|
|
|
|
const rowCount = data.rows.length + (showHeader ? 1 : 0); |
|
|
|
|
let columnCount = data.columns.length; |
|
|
|
|
let rowCount = data.rows.length + (showHeader ? 1 : 0); |
|
|
|
|
|
|
|
|
|
const fixedColumnCount = Math.min(this.props.fixedColumnCount, columnCount); |
|
|
|
|
const fixedRowCount = Math.min(this.props.fixedRowCount, rowCount); |
|
|
|
|
let fixedColumnCount = Math.min(fixedColumns, columnCount); |
|
|
|
|
let fixedRowCount = showHeader && fixedHeader ? 1 : 0; |
|
|
|
|
|
|
|
|
|
if (rotate) { |
|
|
|
|
const temp = columnCount; |
|
|
|
|
columnCount = rowCount; |
|
|
|
|
rowCount = temp; |
|
|
|
|
|
|
|
|
|
fixedRowCount = 0; |
|
|
|
|
fixedColumnCount = Math.min(fixedColumns, rowCount) + (showHeader && fixedHeader ? 1 : 0); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// Usually called after a sort or the data changes
|
|
|
|
|
const scrollToRow = this.scrollToTop ? 1 : -1; |
|
|
|
|
// Called after sort or the data changes
|
|
|
|
|
const scroll = this.scrollToTop ? 1 : -1; |
|
|
|
|
const scrollToRow = rotate ? -1 : scroll; |
|
|
|
|
const scrollToColumn = rotate ? scroll : -1; |
|
|
|
|
if (this.scrollToTop) { |
|
|
|
|
this.scrollToTop = false; |
|
|
|
|
} |
|
|
|
|
@ -226,9 +265,10 @@ export class Table extends Component<Props, State> { |
|
|
|
|
} |
|
|
|
|
scrollToRow={scrollToRow} |
|
|
|
|
columnCount={columnCount} |
|
|
|
|
scrollToColumn={scrollToColumn} |
|
|
|
|
rowCount={rowCount} |
|
|
|
|
overscanColumnCount={2} |
|
|
|
|
overscanRowCount={2} |
|
|
|
|
overscanColumnCount={8} |
|
|
|
|
overscanRowCount={8} |
|
|
|
|
columnWidth={this.measurer.columnWidth} |
|
|
|
|
deferredMeasurementCache={this.measurer} |
|
|
|
|
cellRenderer={this.cellRenderer} |
|
|
|
|
|