Merge pull request #3378 from influxdata/migrate-to-simple-multi-grid
Use custom multigrid componentpull/10616/head
commit
f4830576d5
|
@ -16,6 +16,7 @@
|
|||
1. [#3245](https://github.com/influxdata/chronograf/pull/3245): Display 'no results' on cells without results
|
||||
1. [#3354](https://github.com/influxdata/chronograf/pull/3354): Disable template variables for non editing users
|
||||
1. [#3353](https://github.com/influxdata/chronograf/pull/3353): YAxisLabels in Dashboard Graph Builder not showing until graph is redrawn
|
||||
1. [#3378](https://github.com/influxdata/chronograf/pull/3378): Ensure table graphs have a consistent ux between chrome and firefox
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
|
|
|
@ -1,26 +1,64 @@
|
|||
import _ from 'lodash'
|
||||
import React, {Component} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import classnames from 'classnames'
|
||||
import {Scrollbars} from 'react-custom-scrollbars'
|
||||
import {ErrorHandling} from 'src/shared/decorators/errors'
|
||||
|
||||
@ErrorHandling
|
||||
class FancyScrollbar extends Component {
|
||||
constructor(props) {
|
||||
super(props)
|
||||
}
|
||||
interface DefaultProps {
|
||||
autoHide: boolean
|
||||
autoHeight: boolean
|
||||
maxHeight: number
|
||||
setScrollTop: (value: React.MouseEvent<JSX.Element>) => void
|
||||
style: React.CSSProperties
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
interface Props {
|
||||
className?: string
|
||||
scrollTop?: number
|
||||
scrollLeft?: number
|
||||
}
|
||||
|
||||
@ErrorHandling
|
||||
class FancyScrollbar extends Component<Props & Partial<DefaultProps>> {
|
||||
public static defaultProps = {
|
||||
autoHide: true,
|
||||
autoHeight: false,
|
||||
maxHeight: null,
|
||||
style: {},
|
||||
setScrollTop: () => {},
|
||||
}
|
||||
|
||||
handleMakeDiv = className => props => {
|
||||
private ref: React.RefObject<Scrollbars>
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.ref = React.createRef<Scrollbars>()
|
||||
}
|
||||
|
||||
public updateScroll() {
|
||||
const ref = this.ref.current
|
||||
if (ref && !_.isNil(this.props.scrollTop)) {
|
||||
ref.scrollTop(this.props.scrollTop)
|
||||
}
|
||||
|
||||
if (ref && !_.isNil(this.props.scrollLeft)) {
|
||||
ref.scrollLeft(this.props.scrollLeft)
|
||||
}
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
this.updateScroll()
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.updateScroll()
|
||||
}
|
||||
|
||||
public handleMakeDiv = (className: string) => (props): JSX.Element => {
|
||||
return <div {...props} className={`fancy-scroll--${className}`} />
|
||||
}
|
||||
|
||||
render() {
|
||||
public render() {
|
||||
const {
|
||||
autoHide,
|
||||
autoHeight,
|
||||
|
@ -28,6 +66,7 @@ class FancyScrollbar extends Component {
|
|||
className,
|
||||
maxHeight,
|
||||
setScrollTop,
|
||||
style,
|
||||
} = this.props
|
||||
|
||||
return (
|
||||
|
@ -35,6 +74,8 @@ class FancyScrollbar extends Component {
|
|||
className={classnames('fancy-scroll--container', {
|
||||
[className]: className,
|
||||
})}
|
||||
ref={this.ref}
|
||||
style={style}
|
||||
onScroll={setScrollTop}
|
||||
autoHide={autoHide}
|
||||
autoHideTimeout={1000}
|
||||
|
@ -53,15 +94,4 @@ class FancyScrollbar extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
const {bool, func, node, number, string} = PropTypes
|
||||
|
||||
FancyScrollbar.propTypes = {
|
||||
children: node.isRequired,
|
||||
className: string,
|
||||
autoHide: bool,
|
||||
autoHeight: bool,
|
||||
maxHeight: number,
|
||||
setScrollTop: func,
|
||||
}
|
||||
|
||||
export default FancyScrollbar
|
|
@ -0,0 +1,105 @@
|
|||
import {CellMeasurerCache} from 'react-virtualized'
|
||||
|
||||
interface CellMeasurerCacheDecoratorParams {
|
||||
cellMeasurerCache: CellMeasurerCache
|
||||
columnIndexOffset: number
|
||||
rowIndexOffset: number
|
||||
}
|
||||
|
||||
interface IndexParam {
|
||||
index: number
|
||||
}
|
||||
|
||||
class CellMeasurerCacheDecorator {
|
||||
private cellMeasurerCache: CellMeasurerCache
|
||||
private columnIndexOffset: number
|
||||
private rowIndexOffset: number
|
||||
|
||||
constructor(params: Partial<CellMeasurerCacheDecoratorParams> = {}) {
|
||||
const {
|
||||
cellMeasurerCache,
|
||||
columnIndexOffset = 0,
|
||||
rowIndexOffset = 0,
|
||||
} = params
|
||||
|
||||
this.cellMeasurerCache = cellMeasurerCache
|
||||
this.columnIndexOffset = columnIndexOffset
|
||||
this.rowIndexOffset = rowIndexOffset
|
||||
}
|
||||
|
||||
public clear(rowIndex: number, columnIndex: number): void {
|
||||
this.cellMeasurerCache.clear(
|
||||
rowIndex + this.rowIndexOffset,
|
||||
columnIndex + this.columnIndexOffset
|
||||
)
|
||||
}
|
||||
|
||||
public clearAll(): void {
|
||||
this.cellMeasurerCache.clearAll()
|
||||
}
|
||||
|
||||
public columnWidth = ({index}: IndexParam) => {
|
||||
this.cellMeasurerCache.columnWidth({
|
||||
index: index + this.columnIndexOffset,
|
||||
})
|
||||
}
|
||||
|
||||
get defaultHeight(): number {
|
||||
return this.cellMeasurerCache.defaultHeight
|
||||
}
|
||||
|
||||
get defaultWidth(): number {
|
||||
return this.cellMeasurerCache.defaultWidth
|
||||
}
|
||||
|
||||
public hasFixedHeight(): boolean {
|
||||
return this.cellMeasurerCache.hasFixedHeight()
|
||||
}
|
||||
|
||||
public hasFixedWidth(): boolean {
|
||||
return this.cellMeasurerCache.hasFixedWidth()
|
||||
}
|
||||
|
||||
public getHeight(rowIndex: number, columnIndex: number = 0): number | null {
|
||||
return this.cellMeasurerCache.getHeight(
|
||||
rowIndex + this.rowIndexOffset,
|
||||
columnIndex + this.columnIndexOffset
|
||||
)
|
||||
}
|
||||
|
||||
public getWidth(rowIndex: number, columnIndex: number = 0): number | null {
|
||||
return this.cellMeasurerCache.getWidth(
|
||||
rowIndex + this.rowIndexOffset,
|
||||
columnIndex + this.columnIndexOffset
|
||||
)
|
||||
}
|
||||
|
||||
public has(rowIndex: number, columnIndex: number = 0): boolean {
|
||||
return this.cellMeasurerCache.has(
|
||||
rowIndex + this.rowIndexOffset,
|
||||
columnIndex + this.columnIndexOffset
|
||||
)
|
||||
}
|
||||
|
||||
public rowHeight = ({index}: IndexParam) => {
|
||||
this.cellMeasurerCache.rowHeight({
|
||||
index: index + this.rowIndexOffset,
|
||||
})
|
||||
}
|
||||
|
||||
public set(
|
||||
rowIndex: number,
|
||||
columnIndex: number,
|
||||
width: number,
|
||||
height: number
|
||||
): void {
|
||||
this.cellMeasurerCache.set(
|
||||
rowIndex + this.rowIndexOffset,
|
||||
columnIndex + this.columnIndexOffset,
|
||||
width,
|
||||
height
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default CellMeasurerCacheDecorator
|
|
@ -0,0 +1,813 @@
|
|||
import * as React from 'react'
|
||||
import CellMeasurerCacheDecorator from './CellMeasurerCacheDecorator'
|
||||
import FancyScrollbar from 'src/shared/components/FancyScrollbar'
|
||||
import {Grid} from 'react-virtualized'
|
||||
|
||||
const SCROLLBAR_SIZE_BUFFER = 20
|
||||
|
||||
interface Props {
|
||||
columnCount?: number
|
||||
classNameBottomLeftGrid?: string
|
||||
classNameBottomRightGrid?: string
|
||||
classNameTopLeftGrid?: string
|
||||
classNameTopRightGrid?: string
|
||||
enableFixedColumnScroll?: boolean
|
||||
enableFixedRowScroll?: boolean
|
||||
fixedColumnCount?: number
|
||||
fixedRowCount?: number
|
||||
style?: object
|
||||
styleBottomLeftGrid?: object
|
||||
styleBottomRightGrid?: object
|
||||
styleTopLeftGrid?: object
|
||||
styleTopRightGrid?: object
|
||||
scrollTop?: number
|
||||
scrollLeft?: number
|
||||
rowCount?: number
|
||||
rowHeight?: (arg: {index: number}) => {} | number
|
||||
columnWidth?: (arg: object) => {} | number
|
||||
onScroll?: (arg: object) => {}
|
||||
width: number
|
||||
height: number
|
||||
scrollToRow?: () => {}
|
||||
onSectionRendered?: () => {}
|
||||
scrollToColumn?: () => {}
|
||||
cellRenderer?: (arg: object) => JSX.Element
|
||||
}
|
||||
|
||||
interface State {
|
||||
scrollLeft: number
|
||||
scrollTop: number
|
||||
scrollbarSize: number
|
||||
showHorizontalScrollbar: boolean
|
||||
showVerticalScrollbar: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders 1, 2, or 4 Grids depending on configuration.
|
||||
* A main (body) Grid will always be rendered.
|
||||
* Optionally, 1-2 Grids for sticky header rows will also be rendered.
|
||||
* If no sticky columns, only 1 sticky header Grid will be rendered.
|
||||
* If sticky columns, 2 sticky header Grids will be rendered.
|
||||
*/
|
||||
class MultiGrid extends React.PureComponent<Props, State> {
|
||||
public static defaultProps = {
|
||||
classNameBottomLeftGrid: '',
|
||||
classNameBottomRightGrid: '',
|
||||
classNameTopLeftGrid: '',
|
||||
classNameTopRightGrid: '',
|
||||
enableFixedColumnScroll: false,
|
||||
enableFixedRowScroll: false,
|
||||
fixedColumnCount: 0,
|
||||
fixedRowCount: 0,
|
||||
scrollToColumn: -1,
|
||||
scrollToRow: -1,
|
||||
style: {},
|
||||
styleBottomLeftGrid: {},
|
||||
styleBottomRightGrid: {},
|
||||
styleTopLeftGrid: {},
|
||||
styleTopRightGrid: {},
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (
|
||||
nextProps.scrollLeft !== prevState.scrollLeft ||
|
||||
nextProps.scrollTop !== prevState.scrollTop
|
||||
) {
|
||||
return {
|
||||
scrollLeft:
|
||||
nextProps.scrollLeft != null && nextProps.scrollLeft >= 0
|
||||
? nextProps.scrollLeft
|
||||
: prevState.scrollLeft,
|
||||
scrollTop:
|
||||
nextProps.scrollTop != null && nextProps.scrollTop >= 0
|
||||
? nextProps.scrollTop
|
||||
: prevState.scrollTop,
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private deferredInvalidateColumnIndex: number = 0
|
||||
private deferredInvalidateRowIndex: number = 0
|
||||
private bottomLeftGrid: Grid
|
||||
private bottomRightGrid: Grid
|
||||
private topLeftGrid: Grid
|
||||
private topRightGrid: Grid
|
||||
private deferredMeasurementCacheBottomLeftGrid: CellMeasurerCacheDecorator
|
||||
private deferredMeasurementCacheBottomRightGrid: CellMeasurerCacheDecorator
|
||||
private deferredMeasurementCacheTopRightGrid: CellMeasurerCacheDecorator
|
||||
private leftGridWidth: number | null = 0
|
||||
private topGridHeight: number | null = 0
|
||||
private lastRenderedColumnWidth: (arg: object) => {} | number
|
||||
private lastRenderedFixedColumnCount: number = 0
|
||||
private lastRenderedFixedRowCount: number = 0
|
||||
private lastRenderedRowHeight: (arg: {index: number}) => {} | number
|
||||
private bottomRightGridStyle: object | null
|
||||
private topRightGridStyle: object | null
|
||||
private lastRenderedStyle: object | null
|
||||
private lastRenderedHeight: number = 0
|
||||
private lastRenderedWidth: number = 0
|
||||
private containerTopStyle: object | null
|
||||
private containerBottomStyle: object | null
|
||||
private containerOuterStyle: object | null
|
||||
private lastRenderedStyleBottomLeftGrid: object | null
|
||||
private lastRenderedStyleBottomRightGrid: object | null
|
||||
private lastRenderedStyleTopLeftGrid: object | null
|
||||
private lastRenderedStyleTopRightGrid: object | null
|
||||
private bottomLeftGridStyle: object | null
|
||||
private topLeftGridStyle: object | null
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context)
|
||||
|
||||
this.state = {
|
||||
scrollLeft: 0,
|
||||
scrollTop: 0,
|
||||
scrollbarSize: 0,
|
||||
showHorizontalScrollbar: false,
|
||||
showVerticalScrollbar: false,
|
||||
}
|
||||
|
||||
const {deferredMeasurementCache, fixedColumnCount, fixedRowCount} = props
|
||||
|
||||
this.maybeCalculateCachedStyles(true)
|
||||
|
||||
if (deferredMeasurementCache) {
|
||||
this.deferredMeasurementCacheBottomLeftGrid =
|
||||
fixedRowCount > 0
|
||||
? new CellMeasurerCacheDecorator({
|
||||
cellMeasurerCache: deferredMeasurementCache,
|
||||
columnIndexOffset: 0,
|
||||
rowIndexOffset: fixedRowCount,
|
||||
})
|
||||
: deferredMeasurementCache
|
||||
|
||||
this.deferredMeasurementCacheBottomRightGrid =
|
||||
fixedColumnCount > 0 || fixedRowCount > 0
|
||||
? new CellMeasurerCacheDecorator({
|
||||
cellMeasurerCache: deferredMeasurementCache,
|
||||
columnIndexOffset: fixedColumnCount,
|
||||
rowIndexOffset: fixedRowCount,
|
||||
})
|
||||
: deferredMeasurementCache
|
||||
|
||||
this.deferredMeasurementCacheTopRightGrid =
|
||||
fixedColumnCount > 0
|
||||
? new CellMeasurerCacheDecorator({
|
||||
cellMeasurerCache: deferredMeasurementCache,
|
||||
columnIndexOffset: fixedColumnCount,
|
||||
rowIndexOffset: 0,
|
||||
})
|
||||
: deferredMeasurementCache
|
||||
}
|
||||
}
|
||||
|
||||
public forceUpdateGrids() {
|
||||
if (this.bottomLeftGrid) {
|
||||
this.bottomLeftGrid.forceUpdate()
|
||||
}
|
||||
if (this.bottomRightGrid) {
|
||||
this.bottomRightGrid.forceUpdate()
|
||||
}
|
||||
if (this.topLeftGrid) {
|
||||
this.topLeftGrid.forceUpdate()
|
||||
}
|
||||
if (this.topRightGrid) {
|
||||
this.topRightGrid.forceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
/** See Grid#invalidateCellSizeAfterRender */
|
||||
public invalidateCellSizeAfterRender({columnIndex = 0, rowIndex = 0} = {}) {
|
||||
this.deferredInvalidateColumnIndex =
|
||||
typeof this.deferredInvalidateColumnIndex === 'number'
|
||||
? Math.min(this.deferredInvalidateColumnIndex, columnIndex)
|
||||
: columnIndex
|
||||
this.deferredInvalidateRowIndex =
|
||||
typeof this.deferredInvalidateRowIndex === 'number'
|
||||
? Math.min(this.deferredInvalidateRowIndex, rowIndex)
|
||||
: rowIndex
|
||||
}
|
||||
|
||||
/** See Grid#measureAllCells */
|
||||
public measureAllCells() {
|
||||
if (this.bottomLeftGrid) {
|
||||
this.bottomLeftGrid.measureAllCells()
|
||||
}
|
||||
if (this.bottomRightGrid) {
|
||||
this.bottomRightGrid.measureAllCells()
|
||||
}
|
||||
if (this.topLeftGrid) {
|
||||
this.topLeftGrid.measureAllCells()
|
||||
}
|
||||
if (this.topRightGrid) {
|
||||
this.topRightGrid.measureAllCells()
|
||||
}
|
||||
}
|
||||
|
||||
public recomputeGridSize({columnIndex = 0, rowIndex = 0} = {}) {
|
||||
const {fixedColumnCount, fixedRowCount} = this.props
|
||||
|
||||
const adjustedColumnIndex = Math.max(0, columnIndex - fixedColumnCount)
|
||||
const adjustedRowIndex = Math.max(0, rowIndex - fixedRowCount)
|
||||
|
||||
if (this.bottomLeftGrid) {
|
||||
this.bottomLeftGrid.recomputeGridSize({
|
||||
columnIndex,
|
||||
rowIndex: adjustedRowIndex,
|
||||
})
|
||||
}
|
||||
if (this.bottomRightGrid) {
|
||||
this.bottomRightGrid.recomputeGridSize({
|
||||
columnIndex: adjustedColumnIndex,
|
||||
rowIndex: adjustedRowIndex,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.topLeftGrid) {
|
||||
this.topLeftGrid.recomputeGridSize({
|
||||
columnIndex,
|
||||
rowIndex,
|
||||
})
|
||||
}
|
||||
|
||||
if (this.topRightGrid) {
|
||||
this.topRightGrid.recomputeGridSize({
|
||||
columnIndex: adjustedColumnIndex,
|
||||
rowIndex,
|
||||
})
|
||||
}
|
||||
|
||||
this.leftGridWidth = null
|
||||
this.topGridHeight = null
|
||||
this.maybeCalculateCachedStyles(true)
|
||||
}
|
||||
|
||||
public componentDidMount() {
|
||||
const {scrollLeft, scrollTop} = this.props
|
||||
|
||||
if (scrollLeft > 0 || scrollTop > 0) {
|
||||
const newState: Partial<State> = {}
|
||||
|
||||
if (scrollLeft > 0) {
|
||||
newState.scrollLeft = scrollLeft
|
||||
}
|
||||
|
||||
if (scrollTop > 0) {
|
||||
newState.scrollTop = scrollTop
|
||||
}
|
||||
|
||||
this.setState({...this.state, ...newState})
|
||||
}
|
||||
this.handleInvalidatedGridSize()
|
||||
}
|
||||
|
||||
public componentDidUpdate() {
|
||||
this.handleInvalidatedGridSize()
|
||||
}
|
||||
|
||||
public render() {
|
||||
const {
|
||||
onScroll,
|
||||
scrollLeft: scrollLeftProp, // eslint-disable-line no-unused-vars
|
||||
onSectionRendered,
|
||||
scrollToRow,
|
||||
scrollToColumn,
|
||||
scrollTop: scrollTopProp, // eslint-disable-line no-unused-vars
|
||||
...rest
|
||||
} = this.props
|
||||
|
||||
this.prepareForRender()
|
||||
|
||||
// Don't render any of our Grids if there are no cells.
|
||||
// This mirrors what Grid does,
|
||||
// And prevents us from recording inaccurage measurements when used with CellMeasurer.
|
||||
if (this.props.width === 0 || this.props.height === 0) {
|
||||
return null
|
||||
}
|
||||
|
||||
// scrollTop and scrollLeft props are explicitly filtered out and ignored
|
||||
|
||||
const {scrollLeft, scrollTop} = this.state
|
||||
|
||||
return (
|
||||
<div style={this.containerOuterStyle}>
|
||||
<div style={this.containerTopStyle}>
|
||||
{this.renderTopLeftGrid(rest)}
|
||||
{this.renderTopRightGrid({
|
||||
...rest,
|
||||
...onScroll,
|
||||
scrollLeft,
|
||||
})}
|
||||
</div>
|
||||
<div style={this.containerBottomStyle}>
|
||||
{this.renderBottomLeftGrid({
|
||||
...rest,
|
||||
onScroll,
|
||||
scrollTop,
|
||||
})}
|
||||
{this.renderBottomRightGrid({
|
||||
...rest,
|
||||
onScroll,
|
||||
onSectionRendered,
|
||||
scrollLeft,
|
||||
scrollToColumn,
|
||||
scrollToRow,
|
||||
scrollTop,
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
public cellRendererBottomLeftGrid = ({
|
||||
rowIndex,
|
||||
...rest
|
||||
}: Partial<Props> & {rowIndex: number; key: string}): JSX.Element => {
|
||||
const {cellRenderer, fixedRowCount, rowCount} = this.props
|
||||
|
||||
if (rowIndex === rowCount - fixedRowCount) {
|
||||
return (
|
||||
<div
|
||||
key={rest.key}
|
||||
style={{
|
||||
...rest.style,
|
||||
height: SCROLLBAR_SIZE_BUFFER,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return cellRenderer({
|
||||
...rest,
|
||||
parent: this,
|
||||
rowIndex: rowIndex + fixedRowCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private getBottomGridHeight(props) {
|
||||
const {height} = props
|
||||
|
||||
const topGridHeight = this.getTopGridHeight(props)
|
||||
|
||||
return height - topGridHeight
|
||||
}
|
||||
|
||||
private getLeftGridWidth(props) {
|
||||
const {fixedColumnCount, columnWidth} = props
|
||||
|
||||
if (this.leftGridWidth == null) {
|
||||
if (typeof columnWidth === 'function') {
|
||||
let leftGridWidth = 0
|
||||
|
||||
for (let index = 0; index < fixedColumnCount; index++) {
|
||||
leftGridWidth += columnWidth({index})
|
||||
}
|
||||
|
||||
this.leftGridWidth = leftGridWidth
|
||||
} else {
|
||||
this.leftGridWidth = columnWidth * fixedColumnCount
|
||||
}
|
||||
}
|
||||
|
||||
return this.leftGridWidth
|
||||
}
|
||||
|
||||
private getRightGridWidth(props) {
|
||||
const {width} = props
|
||||
|
||||
const leftGridWidth = this.getLeftGridWidth(props)
|
||||
const result = width - leftGridWidth
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private getTopGridHeight(props) {
|
||||
const {fixedRowCount, rowHeight} = props
|
||||
|
||||
if (this.topGridHeight == null) {
|
||||
if (typeof rowHeight === 'function') {
|
||||
let topGridHeight = 0
|
||||
|
||||
for (let index = 0; index < fixedRowCount; index++) {
|
||||
topGridHeight += rowHeight({index})
|
||||
}
|
||||
|
||||
this.topGridHeight = topGridHeight
|
||||
} else {
|
||||
this.topGridHeight = rowHeight * fixedRowCount
|
||||
}
|
||||
}
|
||||
|
||||
return this.topGridHeight
|
||||
}
|
||||
|
||||
private onScrollbarsScroll = (e: React.MouseEvent<JSX.Element>) => {
|
||||
const {target} = e
|
||||
this.onScroll(target)
|
||||
}
|
||||
|
||||
private onScroll = scrollInfo => {
|
||||
const {scrollLeft, scrollTop} = scrollInfo
|
||||
this.setState({
|
||||
scrollLeft,
|
||||
scrollTop,
|
||||
})
|
||||
|
||||
const {onScroll} = this.props
|
||||
if (onScroll) {
|
||||
onScroll(scrollInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private onScrollLeft = scrollInfo => {
|
||||
const {scrollLeft} = scrollInfo
|
||||
this.onScroll({
|
||||
scrollLeft,
|
||||
scrollTop: this.state.scrollTop,
|
||||
})
|
||||
}
|
||||
|
||||
private renderBottomLeftGrid(props) {
|
||||
const {fixedColumnCount, fixedRowCount, rowCount} = props
|
||||
|
||||
if (!fixedColumnCount) {
|
||||
return null
|
||||
}
|
||||
|
||||
const width = this.getLeftGridWidth(props)
|
||||
const height = this.getBottomGridHeight(props)
|
||||
|
||||
return (
|
||||
<Grid
|
||||
{...props}
|
||||
cellRenderer={this.cellRendererBottomLeftGrid}
|
||||
className={this.props.classNameBottomLeftGrid}
|
||||
columnCount={fixedColumnCount}
|
||||
deferredMeasurementCache={this.deferredMeasurementCacheBottomLeftGrid}
|
||||
onScroll={this.onScroll}
|
||||
height={height}
|
||||
ref={this.bottomLeftGridRef}
|
||||
rowCount={Math.max(0, rowCount - fixedRowCount)}
|
||||
rowHeight={this.rowHeightBottomGrid}
|
||||
style={{
|
||||
...this.bottomLeftGridStyle,
|
||||
}}
|
||||
tabIndex={null}
|
||||
width={width}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private renderBottomRightGrid(props) {
|
||||
const {
|
||||
columnCount,
|
||||
fixedColumnCount,
|
||||
fixedRowCount,
|
||||
rowCount,
|
||||
scrollToColumn,
|
||||
scrollToRow,
|
||||
} = props
|
||||
|
||||
const width = this.getRightGridWidth(props)
|
||||
const height = this.getBottomGridHeight(props)
|
||||
|
||||
return (
|
||||
<FancyScrollbar
|
||||
style={{...this.bottomRightGridStyle, width, height}}
|
||||
autoHide={true}
|
||||
scrollTop={this.state.scrollTop}
|
||||
scrollLeft={this.state.scrollLeft}
|
||||
setScrollTop={this.onScrollbarsScroll}
|
||||
>
|
||||
<Grid
|
||||
{...props}
|
||||
cellRenderer={this.cellRendererBottomRightGrid}
|
||||
className={this.props.classNameBottomRightGrid}
|
||||
columnCount={Math.max(0, columnCount - fixedColumnCount)}
|
||||
columnWidth={this.columnWidthRightGrid}
|
||||
deferredMeasurementCache={
|
||||
this.deferredMeasurementCacheBottomRightGrid
|
||||
}
|
||||
height={height}
|
||||
ref={this.bottomRightGridRef}
|
||||
rowCount={Math.max(0, rowCount - fixedRowCount)}
|
||||
rowHeight={this.rowHeightBottomGrid}
|
||||
onScroll={this.onScroll}
|
||||
scrollToColumn={scrollToColumn - fixedColumnCount}
|
||||
scrollToRow={scrollToRow - fixedRowCount}
|
||||
style={{
|
||||
...this.bottomRightGridStyle,
|
||||
overflowX: false,
|
||||
overflowY: true,
|
||||
left: 0,
|
||||
}}
|
||||
width={width}
|
||||
/>
|
||||
</FancyScrollbar>
|
||||
)
|
||||
}
|
||||
|
||||
private renderTopLeftGrid(props) {
|
||||
const {fixedColumnCount, fixedRowCount} = props
|
||||
|
||||
if (!fixedColumnCount || !fixedRowCount) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid
|
||||
{...props}
|
||||
className={this.props.classNameTopLeftGrid}
|
||||
columnCount={fixedColumnCount}
|
||||
height={this.getTopGridHeight(props)}
|
||||
ref={this.topLeftGridRef}
|
||||
rowCount={fixedRowCount}
|
||||
style={this.topLeftGridStyle}
|
||||
tabIndex={null}
|
||||
width={this.getLeftGridWidth(props)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private renderTopRightGrid(props) {
|
||||
const {
|
||||
columnCount,
|
||||
enableFixedRowScroll,
|
||||
fixedColumnCount,
|
||||
fixedRowCount,
|
||||
scrollLeft,
|
||||
} = props
|
||||
|
||||
if (!fixedRowCount) {
|
||||
return null
|
||||
}
|
||||
|
||||
const width = this.getRightGridWidth(props)
|
||||
const height = this.getTopGridHeight(props)
|
||||
|
||||
return (
|
||||
<Grid
|
||||
{...props}
|
||||
cellRenderer={this.cellRendererTopRightGrid}
|
||||
className={this.props.classNameTopRightGrid}
|
||||
columnCount={Math.max(0, columnCount - fixedColumnCount)}
|
||||
columnWidth={this.columnWidthRightGrid}
|
||||
deferredMeasurementCache={this.deferredMeasurementCacheTopRightGrid}
|
||||
height={height}
|
||||
onScroll={enableFixedRowScroll ? this.onScrollLeft : undefined}
|
||||
ref={this.topRightGridRef}
|
||||
rowCount={fixedRowCount}
|
||||
scrollLeft={scrollLeft}
|
||||
style={this.topRightGridStyle}
|
||||
tabIndex={null}
|
||||
width={width}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
private rowHeightBottomGrid = ({index}) => {
|
||||
const {fixedRowCount, rowCount, rowHeight} = this.props
|
||||
const {scrollbarSize, showVerticalScrollbar} = this.state
|
||||
|
||||
// An extra cell is added to the count
|
||||
// This gives the smaller Grid extra room for offset,
|
||||
// In case the main (bottom right) Grid has a scrollbar
|
||||
// If no scrollbar, the extra space is overflow:hidden anyway
|
||||
if (showVerticalScrollbar && index === rowCount - fixedRowCount) {
|
||||
return scrollbarSize
|
||||
}
|
||||
|
||||
return typeof rowHeight === 'function'
|
||||
? rowHeight({index: index + fixedRowCount})
|
||||
: rowHeight
|
||||
}
|
||||
|
||||
private topLeftGridRef = ref => {
|
||||
this.topLeftGrid = ref
|
||||
}
|
||||
|
||||
private topRightGridRef = ref => {
|
||||
this.topRightGrid = ref
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoid recreating inline styles each render; this bypasses Grid's shallowCompare.
|
||||
* This method recalculates styles only when specific props change.
|
||||
*/
|
||||
private maybeCalculateCachedStyles(resetAll) {
|
||||
const {
|
||||
columnWidth,
|
||||
height,
|
||||
fixedColumnCount,
|
||||
fixedRowCount,
|
||||
rowHeight,
|
||||
style,
|
||||
styleBottomLeftGrid,
|
||||
styleBottomRightGrid,
|
||||
styleTopLeftGrid,
|
||||
styleTopRightGrid,
|
||||
width,
|
||||
} = this.props
|
||||
|
||||
const sizeChange =
|
||||
resetAll ||
|
||||
height !== this.lastRenderedHeight ||
|
||||
width !== this.lastRenderedWidth
|
||||
const leftSizeChange =
|
||||
resetAll ||
|
||||
columnWidth !== this.lastRenderedColumnWidth ||
|
||||
fixedColumnCount !== this.lastRenderedFixedColumnCount
|
||||
const topSizeChange =
|
||||
resetAll ||
|
||||
fixedRowCount !== this.lastRenderedFixedRowCount ||
|
||||
rowHeight !== this.lastRenderedRowHeight
|
||||
|
||||
if (resetAll || sizeChange || style !== this.lastRenderedStyle) {
|
||||
this.containerOuterStyle = {
|
||||
height,
|
||||
overflow: 'visible', // Let :focus outline show through
|
||||
width,
|
||||
...style,
|
||||
}
|
||||
}
|
||||
|
||||
if (resetAll || sizeChange || topSizeChange) {
|
||||
this.containerTopStyle = {
|
||||
height: this.getTopGridHeight(this.props),
|
||||
position: 'relative',
|
||||
width,
|
||||
}
|
||||
|
||||
this.containerBottomStyle = {
|
||||
height: height - this.getTopGridHeight(this.props),
|
||||
overflow: 'visible', // Let :focus outline show through
|
||||
position: 'relative',
|
||||
width,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
resetAll ||
|
||||
styleBottomLeftGrid !== this.lastRenderedStyleBottomLeftGrid
|
||||
) {
|
||||
this.bottomLeftGridStyle = {
|
||||
left: 0,
|
||||
overflowY: 'hidden',
|
||||
overflowX: 'hidden',
|
||||
position: 'absolute',
|
||||
...styleBottomLeftGrid,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
resetAll ||
|
||||
leftSizeChange ||
|
||||
styleBottomRightGrid !== this.lastRenderedStyleBottomRightGrid
|
||||
) {
|
||||
this.bottomRightGridStyle = {
|
||||
left: this.getLeftGridWidth(this.props),
|
||||
position: 'absolute',
|
||||
...styleBottomRightGrid,
|
||||
}
|
||||
}
|
||||
|
||||
if (resetAll || styleTopLeftGrid !== this.lastRenderedStyleTopLeftGrid) {
|
||||
this.topLeftGridStyle = {
|
||||
left: 0,
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
...styleTopLeftGrid,
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
resetAll ||
|
||||
leftSizeChange ||
|
||||
styleTopRightGrid !== this.lastRenderedStyleTopRightGrid
|
||||
) {
|
||||
this.topRightGridStyle = {
|
||||
left: this.getLeftGridWidth(this.props),
|
||||
overflowX: 'hidden',
|
||||
overflowY: 'hidden',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
...styleTopRightGrid,
|
||||
}
|
||||
}
|
||||
|
||||
this.lastRenderedColumnWidth = columnWidth
|
||||
this.lastRenderedFixedColumnCount = fixedColumnCount
|
||||
this.lastRenderedFixedRowCount = fixedRowCount
|
||||
this.lastRenderedHeight = height
|
||||
this.lastRenderedRowHeight = rowHeight
|
||||
this.lastRenderedStyle = style
|
||||
this.lastRenderedStyleBottomLeftGrid = styleBottomLeftGrid
|
||||
this.lastRenderedStyleBottomRightGrid = styleBottomRightGrid
|
||||
this.lastRenderedStyleTopLeftGrid = styleTopLeftGrid
|
||||
this.lastRenderedStyleTopRightGrid = styleTopRightGrid
|
||||
this.lastRenderedWidth = width
|
||||
}
|
||||
|
||||
private bottomLeftGridRef = ref => {
|
||||
this.bottomLeftGrid = ref
|
||||
}
|
||||
|
||||
private bottomRightGridRef = ref => {
|
||||
this.bottomRightGrid = ref
|
||||
}
|
||||
|
||||
private cellRendererBottomRightGrid = ({columnIndex, rowIndex, ...rest}) => {
|
||||
const {cellRenderer, fixedColumnCount, fixedRowCount} = this.props
|
||||
|
||||
return cellRenderer({
|
||||
...rest,
|
||||
columnIndex: columnIndex + fixedColumnCount,
|
||||
parent: this,
|
||||
rowIndex: rowIndex + fixedRowCount,
|
||||
})
|
||||
}
|
||||
|
||||
private cellRendererTopRightGrid = ({columnIndex, ...rest}) => {
|
||||
const {cellRenderer, columnCount, fixedColumnCount} = this.props
|
||||
|
||||
if (columnIndex === columnCount - fixedColumnCount) {
|
||||
return (
|
||||
<div
|
||||
key={rest.key}
|
||||
style={{
|
||||
...rest.style,
|
||||
width: SCROLLBAR_SIZE_BUFFER,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
} else {
|
||||
return cellRenderer({
|
||||
...rest,
|
||||
columnIndex: columnIndex + fixedColumnCount,
|
||||
parent: this,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private columnWidthRightGrid = ({index}) => {
|
||||
const {columnCount, fixedColumnCount, columnWidth} = this.props
|
||||
const {scrollbarSize, showHorizontalScrollbar} = this.state
|
||||
|
||||
// An extra cell is added to the count
|
||||
// This gives the smaller Grid extra room for offset,
|
||||
// In case the main (bottom right) Grid has a scrollbar
|
||||
// If no scrollbar, the extra space is overflow:hidden anyway
|
||||
if (showHorizontalScrollbar && index === columnCount - fixedColumnCount) {
|
||||
return scrollbarSize
|
||||
}
|
||||
|
||||
return typeof columnWidth === 'function'
|
||||
? columnWidth({index: index + fixedColumnCount})
|
||||
: columnWidth
|
||||
}
|
||||
|
||||
private handleInvalidatedGridSize() {
|
||||
if (typeof this.deferredInvalidateColumnIndex === 'number') {
|
||||
const columnIndex = this.deferredInvalidateColumnIndex
|
||||
const rowIndex = this.deferredInvalidateRowIndex
|
||||
|
||||
this.deferredInvalidateColumnIndex = null
|
||||
this.deferredInvalidateRowIndex = null
|
||||
|
||||
this.recomputeGridSize({
|
||||
columnIndex,
|
||||
rowIndex,
|
||||
})
|
||||
this.forceUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
private prepareForRender() {
|
||||
if (
|
||||
this.lastRenderedColumnWidth !== this.props.columnWidth ||
|
||||
this.lastRenderedFixedColumnCount !== this.props.fixedColumnCount
|
||||
) {
|
||||
this.leftGridWidth = null
|
||||
}
|
||||
|
||||
if (
|
||||
this.lastRenderedFixedRowCount !== this.props.fixedRowCount ||
|
||||
this.lastRenderedRowHeight !== this.props.rowHeight
|
||||
) {
|
||||
this.topGridHeight = null
|
||||
}
|
||||
|
||||
this.maybeCalculateCachedStyles(false)
|
||||
|
||||
this.lastRenderedColumnWidth = this.props.columnWidth
|
||||
this.lastRenderedFixedColumnCount = this.props.fixedColumnCount
|
||||
this.lastRenderedFixedRowCount = this.props.fixedRowCount
|
||||
this.lastRenderedRowHeight = this.props.rowHeight
|
||||
}
|
||||
}
|
||||
|
||||
export default MultiGrid
|
|
@ -0,0 +1,2 @@
|
|||
import MultiGrid from './MultiGrid'
|
||||
export {MultiGrid}
|
|
@ -4,7 +4,8 @@ import _ from 'lodash'
|
|||
import classnames from 'classnames'
|
||||
import {connect} from 'react-redux'
|
||||
|
||||
import {MultiGrid, ColumnSizer} from 'react-virtualized'
|
||||
import {ColumnSizer} from 'react-virtualized'
|
||||
import {MultiGrid} from 'src/shared/components/MultiGrid'
|
||||
import {bindActionCreators} from 'redux'
|
||||
import moment from 'moment'
|
||||
import {reduce} from 'fast.js'
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
// Highlight
|
||||
&:after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
@ -80,8 +80,8 @@
|
|||
padding-right: 17px;
|
||||
|
||||
&:before {
|
||||
font-family: 'icomoon';
|
||||
content: '\e902';
|
||||
font-family: "icomoon";
|
||||
content: "\e902";
|
||||
font-size: 17px;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
|
|
Loading…
Reference in New Issue