mirror of
https://github.com/Foundry376/Mailspring.git
synced 2025-03-14 01:04:18 +08:00
perf(mail-merge): Add lazy rendering to table
Summary: Add new LazyRenderedList component and updates Table to use it Test Plan: TODO Reviewers: bengotow, evan Reviewed By: evan Differential Revision: https://phab.nylas.com/D2936
This commit is contained in:
parent
e9ae4def9a
commit
38bf9080c2
5 changed files with 116 additions and 40 deletions
83
src/components/lazy-rendered-list.jsx
Normal file
83
src/components/lazy-rendered-list.jsx
Normal file
|
@ -0,0 +1,83 @@
|
|||
import React, {Component, PropTypes} from 'react'
|
||||
import {findDOMNode} from 'react-dom'
|
||||
|
||||
|
||||
const MIN_RANGE_SIZE = 2
|
||||
|
||||
function getRange({total, itemHeight, containerHeight, scrollTop}) {
|
||||
const itemsPerBody = Math.floor((containerHeight) / itemHeight);
|
||||
const start = Math.max(0, Math.floor(scrollTop / itemHeight) - (itemsPerBody * 2));
|
||||
const end = Math.max(MIN_RANGE_SIZE, Math.min(start + (4 * itemsPerBody), total));
|
||||
return {start, end}
|
||||
}
|
||||
|
||||
class LazyRenderedList extends Component {
|
||||
static propTypes = {
|
||||
items: PropTypes.array,
|
||||
itemHeight: PropTypes.number,
|
||||
containerHeight: PropTypes.number,
|
||||
BufferTag: PropTypes.string,
|
||||
ItemRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
||||
RootRenderer: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
itemHeight: 30,
|
||||
containerHeight: 150,
|
||||
BufferTag: 'div',
|
||||
}
|
||||
|
||||
constructor(props) {
|
||||
super(props)
|
||||
this.state = {start: 0, end: MIN_RANGE_SIZE}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
this.updateRangeState(nextProps)
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
this.updateRangeState(this.props)
|
||||
}
|
||||
|
||||
updateRangeState({itemHeight, items, containerHeight}) {
|
||||
const {scrollTop} = findDOMNode(this)
|
||||
this.setState(getRange({total: items.length, itemHeight, containerHeight, scrollTop}))
|
||||
}
|
||||
|
||||
renderItems() {
|
||||
const {items, itemHeight, BufferTag, ItemRenderer} = this.props
|
||||
const {start, end} = this.state
|
||||
const topHeight = start * itemHeight
|
||||
const bottomHeight = (items.length - end) * itemHeight
|
||||
|
||||
const top = <BufferTag key="lazy-top" style={{height: topHeight}} />
|
||||
const bottom = <BufferTag key="lazy-bottom" style={{height: bottomHeight}} />
|
||||
const elements = items.slice(start, end).map((item, idx) => (
|
||||
<ItemRenderer
|
||||
key={`item-${start + idx}`}
|
||||
item={item}
|
||||
idx={start + idx}
|
||||
/>
|
||||
))
|
||||
elements.unshift(top)
|
||||
elements.push(bottom)
|
||||
|
||||
return elements
|
||||
}
|
||||
|
||||
render() {
|
||||
const {RootRenderer, containerHeight} = this.props
|
||||
return (
|
||||
<RootRenderer
|
||||
style={{height: containerHeight, overflowX: 'hidden', overflowY: 'auto'}}
|
||||
onScroll={::this.onScroll}
|
||||
>
|
||||
{this.renderItems()}
|
||||
</RootRenderer>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export default LazyRenderedList
|
|
@ -13,8 +13,8 @@ export class SelectableCell extends Component {
|
|||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
tableData: Table.propTypes.tableData,
|
||||
rowIdx: TableCell.propTypes.rowIdx,
|
||||
colIdx: TableCell.propTypes.colIdx,
|
||||
rowIdx: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
colIdx: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
|
||||
selection: PropTypes.object,
|
||||
onSetSelection: PropTypes.func.isRequired,
|
||||
}
|
||||
|
|
|
@ -1,47 +1,28 @@
|
|||
import _ from 'underscore'
|
||||
import classnames from 'classnames'
|
||||
import React, {Component, PropTypes} from 'react'
|
||||
import LazyRenderedList from './lazy-rendered-list'
|
||||
|
||||
|
||||
// TODO Ugh gross. Use flow
|
||||
const RowDataType = PropTypes.arrayOf(PropTypes.node)
|
||||
const RendererType = PropTypes.oneOfType([PropTypes.func, PropTypes.string])
|
||||
const IndexType = PropTypes.oneOfType([PropTypes.number, PropTypes.string])
|
||||
const TablePropTypes = {
|
||||
idx: IndexType,
|
||||
renderer: RendererType,
|
||||
tableData: PropTypes.shape({
|
||||
rows: PropTypes.arrayOf(RowDataType),
|
||||
rows: PropTypes.array,
|
||||
}),
|
||||
}
|
||||
|
||||
|
||||
export class TableCell extends Component {
|
||||
|
||||
static propTypes = {
|
||||
className: PropTypes.string,
|
||||
isHeader: PropTypes.bool,
|
||||
tableData: TablePropTypes.tableData.isRequired,
|
||||
rowIdx: TablePropTypes.idx.isRequired,
|
||||
colIdx: TablePropTypes.idx.isRequired,
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
className: '',
|
||||
}
|
||||
|
||||
render() {
|
||||
const {className, isHeader, children, ...props} = this.props
|
||||
const CellTag = isHeader ? 'th' : 'td'
|
||||
return (
|
||||
<CellTag {...props} className={`table-cell ${className}`} >
|
||||
{children}
|
||||
</CellTag>
|
||||
)
|
||||
}
|
||||
export function TableCell({className = '', isHeader, children, ...props}) {
|
||||
const CellTag = isHeader ? 'th' : 'td'
|
||||
return (
|
||||
<CellTag {...props} className={`table-cell ${className}`} >
|
||||
{children}
|
||||
</CellTag>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
export class TableRow extends Component {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -102,6 +83,8 @@ export default class Table extends Component {
|
|||
className: PropTypes.string,
|
||||
displayHeader: PropTypes.bool,
|
||||
displayNumbers: PropTypes.bool,
|
||||
rowHeight: PropTypes.number,
|
||||
bodyHeight: PropTypes.number,
|
||||
tableData: TablePropTypes.tableData.isRequired,
|
||||
extraProps: PropTypes.object,
|
||||
RowRenderer: TablePropTypes.renderer,
|
||||
|
@ -116,10 +99,10 @@ export default class Table extends Component {
|
|||
}
|
||||
|
||||
renderBody() {
|
||||
const {tableData, displayNumbers, displayHeader, extraProps, RowRenderer, CellRenderer} = this.props
|
||||
const {tableData, rowHeight, bodyHeight, displayNumbers, displayHeader, extraProps, RowRenderer, CellRenderer} = this.props
|
||||
const rows = displayHeader ? tableData.rows.slice(1) : tableData.rows
|
||||
|
||||
const rowElements = rows.map((row, idx) => {
|
||||
const itemRenderer = ({idx}) => {
|
||||
const rowIdx = displayHeader ? idx + 1 : idx;
|
||||
return (
|
||||
<RowRenderer
|
||||
|
@ -132,12 +115,17 @@ export default class Table extends Component {
|
|||
{...extraProps}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<tbody>
|
||||
{rowElements}
|
||||
</tbody>
|
||||
<LazyRenderedList
|
||||
items={rows}
|
||||
itemHeight={rowHeight}
|
||||
containerHeight={bodyHeight}
|
||||
BufferTag="tr"
|
||||
ItemRenderer={itemRenderer}
|
||||
RootRenderer="tbody"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -161,10 +149,10 @@ export default class Table extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
const {className} = this.props
|
||||
const {className, ...otherProps} = this.props
|
||||
|
||||
return (
|
||||
<div className={`nylas-table ${className}`}>
|
||||
<div className={`nylas-table ${className}`} {...otherProps}>
|
||||
<table>
|
||||
{this.renderHeader()}
|
||||
{this.renderBody()}
|
||||
|
|
2
src/pro
2
src/pro
|
@ -1 +1 @@
|
|||
Subproject commit 91b58e912a7f2d9d68632eb2b434ba185af352ba
|
||||
Subproject commit a795839311c962f5e84fe29b753e91a1949f6999
|
|
@ -7,7 +7,12 @@
|
|||
.nylas-table {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
overflow-y: hidden;
|
||||
overflow-x: scroll;
|
||||
|
||||
thead, tbody {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.table-row {
|
||||
|
||||
|
|
Loading…
Reference in a new issue