Fixed: Speed up AlbumStudio

pull/1689/head
ta264 4 years ago committed by Qstick
parent dce69212fc
commit e681469857

@ -0,0 +1,36 @@
.pageContentBodyWrapper {
display: flex;
flex: 1 0 1px;
overflow: hidden;
}
.contentBody {
composes: contentBody from '~Components/Page/PageContentBody.css';
display: flex;
flex-direction: column;
}
.tableInnerContentBody {
composes: innerContentBody from '~Components/Page/PageContentBody.css';
display: flex;
flex-direction: column;
flex-grow: 1;
}
.contentBodyContainer {
display: flex;
flex-direction: column;
flex-grow: 1;
}
@media only screen and (max-width: $breakpointSmall) {
.pageContentBodyWrapper {
flex-basis: auto;
}
.contentBody {
flex-basis: 1px;
}
}

@ -1,5 +1,8 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { CellMeasurer, CellMeasurerCache } from 'react-virtualized';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import getErrorMessage from 'Utilities/Object/getErrorMessage'; import getErrorMessage from 'Utilities/Object/getErrorMessage';
import getSelectedIds from 'Utilities/Table/getSelectedIds'; import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll'; import selectAll from 'Utilities/Table/selectAll';
@ -8,17 +11,24 @@ import { align, sortDirections } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody'; import PageContentBody from 'Components/Page/PageContentBody';
import PageJumpBar from 'Components/Page/PageJumpBar';
import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import FilterMenu from 'Components/Menu/FilterMenu'; import FilterMenu from 'Components/Menu/FilterMenu';
import Table from 'Components/Table/Table'; import VirtualTable from 'Components/Table/VirtualTable';
import TableBody from 'Components/Table/TableBody'; import VirtualTableRow from 'Components/Table/VirtualTableRow';
import NoArtist from 'Artist/NoArtist'; import NoArtist from 'Artist/NoArtist';
import AlbumStudioFilterModalConnector from './AlbumStudioFilterModalConnector'; import AlbumStudioFilterModalConnector from './AlbumStudioFilterModalConnector';
import AlbumStudioRowConnector from './AlbumStudioRowConnector'; import AlbumStudioRowConnector from './AlbumStudioRowConnector';
import AlbumStudioTableHeader from './AlbumStudioTableHeader';
import AlbumStudioFooter from './AlbumStudioFooter'; import AlbumStudioFooter from './AlbumStudioFooter';
import styles from './AlbumStudio.css';
const columns = [ const columns = [
{
name: 'monitored',
isVisible: true
},
{ {
name: 'status', name: 'status',
isVisible: true isVisible: true
@ -29,14 +39,10 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'monitored',
isVisible: true
},
{ {
name: 'albumCount', name: 'albumCount',
label: 'Albums', label: 'Albums',
isSortable: true, isSortable: false,
isVisible: true isVisible: true
} }
]; ];
@ -50,11 +56,25 @@ class AlbumStudio extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
estimatedRowSize: 100,
scroller: null,
jumpBarItems: { order: [] },
scrollIndex: null,
jumpCount: 0,
allSelected: false, allSelected: false,
allUnselected: false, allUnselected: false,
lastToggled: null, lastToggled: null,
selectedState: {} selectedState: {}
}; };
this.cache = new CellMeasurerCache({
defaultHeight: 100,
fixedWidth: true
});
}
componentDidMount() {
this.setSelectedState();
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
@ -63,18 +83,186 @@ class AlbumStudio extends Component {
saveError saveError
} = this.props; } = this.props;
const {
scrollIndex,
jumpCount
} = this.state;
if (prevProps.isSaving && !isSaving && !saveError) { if (prevProps.isSaving && !isSaving && !saveError) {
this.onSelectAllChange({ value: false }); this.onSelectAllChange({ value: false });
} }
// nasty hack to fix react-virtualized jumping incorrectly
// due to variable row heights
if (scrollIndex != null) {
if (jumpCount === 0) {
this.setState({
scrollIndex: scrollIndex + 1,
jumpCount: 1
});
} else if (jumpCount === 1) {
this.setState({
scrollIndex: scrollIndex - 1,
jumpCount: 2
});
} else {
this.setState({
scrollIndex: null,
jumpCount: 0
});
}
}
} }
// //
// Control // Control
setScrollerRef = (ref) => {
this.setState({ scroller: ref });
}
setJumpBarItems() {
const {
items,
sortKey,
sortDirection
} = this.props;
// Reset if not sorting by sortName
if (sortKey !== 'sortName') {
this.setState({ jumpBarItems: { order: [] } });
return;
}
const characters = _.reduce(items, (acc, item) => {
let char = item.sortName.charAt(0);
if (!isNaN(char)) {
char = '#';
}
if (char in acc) {
acc[char] = acc[char] + 1;
} else {
acc[char] = 1;
}
return acc;
}, {});
const order = Object.keys(characters).sort();
// Reverse if sorting descending
if (sortDirection === sortDirections.DESCENDING) {
order.reverse();
}
const jumpBarItems = {
characters,
order
};
this.setState({ jumpBarItems });
}
getSelectedIds = () => { getSelectedIds = () => {
if (this.state.allUnselected) {
return [];
}
return getSelectedIds(this.state.selectedState); return getSelectedIds(this.state.selectedState);
} }
setSelectedState = () => {
const {
items
} = this.props;
const {
selectedState
} = this.state;
const newSelectedState = {};
items.forEach((artist) => {
const isItemSelected = selectedState[artist.id];
if (isItemSelected) {
newSelectedState[artist.id] = isItemSelected;
} else {
newSelectedState[artist.id] = false;
}
});
const selectedCount = getSelectedIds(newSelectedState).length;
const newStateCount = Object.keys(newSelectedState).length;
let isAllSelected = false;
let isAllUnselected = false;
if (selectedCount === 0) {
isAllUnselected = true;
} else if (selectedCount === newStateCount) {
isAllSelected = true;
}
this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
}
estimateRowHeight = (width) => {
const {
albumCount,
items
} = this.props;
if (albumCount === undefined || albumCount === 0 || items.length === 0) {
return 100;
}
// guess 250px per album entry
// available width is total width less 186px for select, status etc
const cols = Math.max(Math.floor((width - 186) / 250), 1);
const albumsPerArtist = albumCount / items.length;
const albumRowsPerArtist = albumsPerArtist / cols;
// each row is 23px per album row plus 16px padding
return albumRowsPerArtist * 23 + 16;
}
rowRenderer = ({ key, rowIndex, parent, style }) => {
const {
items
} = this.props;
const {
selectedState
} = this.state;
const item = items[rowIndex];
return (
<CellMeasurer
key={key}
cache={this.cache}
parent={parent}
columnIndex={0}
rowIndex={rowIndex}
>
{({ registerChild }) => (
<VirtualTableRow
ref={registerChild}
style={style}
>
<AlbumStudioRowConnector
key={item.id}
artistId={item.id}
isSelected={selectedState[item.id]}
onSelectedChange={this.onSelectedChange}
/>
</VirtualTableRow>
)}
</CellMeasurer>
);
}
// //
// Listeners // Listeners
@ -88,6 +276,10 @@ class AlbumStudio extends Component {
}); });
} }
onSelectAllPress = () => {
this.onSelectAllChange({ value: !this.state.allSelected });
}
onUpdateSelectedPress = (changes) => { onUpdateSelectedPress = (changes) => {
this.props.onUpdateSelectedPress({ this.props.onUpdateSelectedPress({
artistIds: this.getSelectedIds(), artistIds: this.getSelectedIds(),
@ -95,6 +287,21 @@ class AlbumStudio extends Component {
}); });
} }
onJumpBarItemPress = (jumpToCharacter) => {
const scrollIndex = getIndexOfFirstCharacter(this.props.items, jumpToCharacter);
if (scrollIndex != null) {
this.setState({ scrollIndex });
}
}
onGridRecompute = (width) => {
this.setJumpBarItems();
this.setSelectedState();
this.setState({ estimatedRowSize: this.estimateRowHeight(width) });
this.cache.clearAll();
}
// //
// Render // Render
@ -112,6 +319,7 @@ class AlbumStudio extends Component {
sortDirection, sortDirection,
isSaving, isSaving,
saveError, saveError,
isSmallScreen,
onSortPress, onSortPress,
onFilterSelect onFilterSelect
} = this.props; } = this.props;
@ -119,7 +327,10 @@ class AlbumStudio extends Component {
const { const {
allSelected, allSelected,
allUnselected, allUnselected,
selectedState estimatedRowSize,
scroller,
jumpBarItems,
scrollIndex
} = this.state; } = this.state;
return ( return (
@ -138,53 +349,68 @@ class AlbumStudio extends Component {
</PageToolbarSection> </PageToolbarSection>
</PageToolbar> </PageToolbar>
<PageContentBody> <div className={styles.pageContentBodyWrapper}>
{ <PageContentBody
isFetching && !isPopulated && registerScroller={this.setScrollerRef}
<LoadingIndicator /> className={styles.contentBody}
} innerClassName={styles.innerContentBody}
>
{
isFetching && !isPopulated &&
<LoadingIndicator />
}
{ {
!isFetching && !!error && !isFetching && !!error &&
<div>{getErrorMessage(error, 'Failed to load artist from API')}</div> <div>{getErrorMessage(error, 'Failed to load artist from API')}</div>
} }
{ {
!error && isPopulated && !!items.length && !error && isPopulated && !!items.length &&
<div> <div className={styles.contentBodyContainer}>
<Table <VirtualTable
columns={columns} items={items}
sortKey={sortKey} scrollIndex={scrollIndex}
sortDirection={sortDirection} columns={columns}
selectAll={true} scroller={scroller}
allSelected={allSelected} isSmallScreen={isSmallScreen}
allUnselected={allUnselected} overscanRowCount={5}
onSortPress={onSortPress} rowRenderer={this.rowRenderer}
onSelectAllChange={this.onSelectAllChange} header={
> <AlbumStudioTableHeader
<TableBody> columns={columns}
{ sortKey={sortKey}
items.map((item) => { sortDirection={sortDirection}
return ( onSortPress={onSortPress}
<AlbumStudioRowConnector allSelected={allSelected}
key={item.id} allUnselected={allUnselected}
artistId={item.id} onSelectAllChange={this.onSelectAllChange}
isSelected={selectedState[item.id]} />
onSelectedChange={this.onSelectedChange}
/>
);
})
} }
</TableBody> sortKey={sortKey}
</Table> sortDirection={sortDirection}
</div> deferredMeasurementCache={this.cache}
} rowHeight={this.cache.rowHeight}
estimatedRowSize={estimatedRowSize}
onRecompute={this.onGridRecompute}
/>
</div>
}
{
!error && isPopulated && !items.length &&
<NoArtist totalItems={totalItems} />
}
</PageContentBody>
{ {
!error && isPopulated && !items.length && isPopulated && !!jumpBarItems.order.length &&
<NoArtist totalItems={totalItems} /> <PageJumpBar
items={jumpBarItems}
onItemPress={this.onJumpBarItemPress}
/>
} }
</PageContentBody> </div>
<AlbumStudioFooter <AlbumStudioFooter
selectedCount={this.getSelectedIds().length} selectedCount={this.getSelectedIds().length}
@ -203,6 +429,7 @@ AlbumStudio.propTypes = {
error: PropTypes.object, error: PropTypes.object,
totalItems: PropTypes.number.isRequired, totalItems: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
albumCount: PropTypes.number.isRequired,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all), sortDirection: PropTypes.oneOf(sortDirections.all),
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired, selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
@ -210,6 +437,7 @@ AlbumStudio.propTypes = {
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired, customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object, saveError: PropTypes.object,
isSmallScreen: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,
onUpdateSelectedPress: PropTypes.func.isRequired onUpdateSelectedPress: PropTypes.func.isRequired

@ -2,17 +2,42 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions'; import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions';
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions'; import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
import AlbumStudio from './AlbumStudio'; import AlbumStudio from './AlbumStudio';
function createAlbumFetchStateSelector() {
return createSelector(
(state) => state.albums.items.length,
(state) => state.albums.isFetching,
(state) => state.albums.isPopulated,
(length, isFetching, isPopulated) => {
const albumCount = (!isFetching && isPopulated) ? length : 0;
return {
albumCount,
isFetching,
isPopulated
};
}
);
}
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createClientSideCollectionSelector('artist', 'albumStudio'), createAlbumFetchStateSelector(),
(artist) => { createArtistClientSideCollectionItemsSelector('albumStudio'),
createDimensionsSelector(),
(albums, artist, dimensionsState) => {
const isPopulated = albums.isPopulated && artist.isPopulated;
const isFetching = artist.isFetching || albums.isFetching;
return { return {
...artist ...artist,
isPopulated,
isFetching,
albumCount: albums.albumCount,
isSmallScreen: dimensionsState.isSmallScreen
}; };
} }
); );

@ -1,20 +1,46 @@
.cell {
composes: cell from '~Components/Table/Cells/VirtualTableRowCell.css';
display: flex;
align-items: center;
}
.selectCell {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
display: flex;
align-items: center;
}
.status, .status,
.monitored { .monitored {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px; padding: 0;
display: flex;
align-items: center;
width: 20px;
}
.statusIcon {
width: 20px !important;
} }
.title { .title {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 1px; display: flex;
white-space: nowrap; align-items: center;
flex-shrink: 0;
min-width: 110px;
} }
.albums { .albums {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell;
display: flex; display: flex;
flex-grow: 4;
flex-wrap: wrap; flex-wrap: wrap;
min-width: 400px;
} }

@ -3,9 +3,8 @@ import React, { Component } from 'react';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import MonitorToggleButton from 'Components/MonitorToggleButton'; import MonitorToggleButton from 'Components/MonitorToggleButton';
import TableRow from 'Components/Table/TableRow'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import ArtistNameLink from 'Artist/ArtistNameLink'; import ArtistNameLink from 'Artist/ArtistNameLink';
import AlbumStudioAlbum from './AlbumStudioAlbum'; import AlbumStudioAlbum from './AlbumStudioAlbum';
import styles from './AlbumStudioRow.css'; import styles from './AlbumStudioRow.css';
@ -31,37 +30,40 @@ class AlbumStudioRow extends Component {
} = this.props; } = this.props;
return ( return (
<TableRow> <>
<TableSelectCell <VirtualTableSelectCell
className={styles.selectCell}
id={artistId} id={artistId}
isSelected={isSelected} isSelected={isSelected}
onSelectedChange={onSelectedChange} onSelectedChange={onSelectedChange}
isDisabled={false}
/> />
<TableRowCell className={styles.status}> <VirtualTableRowCell className={styles.monitored}>
<MonitorToggleButton
monitored={monitored}
size={14}
isSaving={isSaving}
onPress={onArtistMonitoredPress}
/>
</VirtualTableRowCell>
<VirtualTableRowCell className={styles.status}>
<Icon <Icon
className={styles.statusIcon} className={styles.statusIcon}
name={status === 'ended' ? icons.ARTIST_ENDED : icons.ARTIST_CONTINUING} name={status === 'ended' ? icons.ARTIST_ENDED : icons.ARTIST_CONTINUING}
title={status === 'ended' ? 'Ended' : 'Continuing'} title={status === 'ended' ? 'Ended' : 'Continuing'}
/> />
</TableRowCell> </VirtualTableRowCell>
<TableRowCell className={styles.title}> <VirtualTableRowCell className={styles.title}>
<ArtistNameLink <ArtistNameLink
foreignArtistId={foreignArtistId} foreignArtistId={foreignArtistId}
artistName={artistName} artistName={artistName}
/> />
</TableRowCell> </VirtualTableRowCell>
<TableRowCell className={styles.monitored}>
<MonitorToggleButton
monitored={monitored}
isSaving={isSaving}
onPress={onArtistMonitoredPress}
/>
</TableRowCell>
<TableRowCell className={styles.albums}> <VirtualTableRowCell className={styles.albums}>
{ {
albums.map((album) => { albums.map((album) => {
return ( return (
@ -73,8 +75,8 @@ class AlbumStudioRow extends Component {
); );
}) })
} }
</TableRowCell> </VirtualTableRowCell>
</TableRow> </>
); );
} }
} }

@ -8,12 +8,23 @@ import { toggleArtistMonitored } from 'Store/Actions/artistActions';
import { toggleAlbumsMonitored } from 'Store/Actions/albumActions'; import { toggleAlbumsMonitored } from 'Store/Actions/albumActions';
import AlbumStudioRow from './AlbumStudioRow'; import AlbumStudioRow from './AlbumStudioRow';
// Use a const to share the reselect cache between instances
const getAlbumMap = createSelector(
(state) => state.albums.items,
(albums) => {
return albums.reduce((acc, curr) => {
(acc[curr.artistId] = acc[curr.artistId] || []).push(curr);
return acc;
}, {});
}
);
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.albums,
createArtistSelector(), createArtistSelector(),
(albums, artist) => { getAlbumMap,
const albumsInArtist = _.filter(albums.items, { artistId: artist.id }); (artist, albumMap) => {
const albumsInArtist = albumMap.hasOwnProperty(artist.id) ? albumMap[artist.id] : [];
const sortedAlbums = _.orderBy(albumsInArtist, 'releaseDate', 'desc'); const sortedAlbums = _.orderBy(albumsInArtist, 'releaseDate', 'desc');
return { return {

@ -0,0 +1,19 @@
.monitored,
.status {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
width: 20px;
padding: 0;
}
.sortName {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
flex: 0 0 110px;
}
.albumCount {
composes: headerCell from '~Components/Table/VirtualTableHeaderCell.css';
padding: 12px;
}

@ -0,0 +1,61 @@
import PropTypes from 'prop-types';
import React from 'react';
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
import styles from './AlbumStudioTableHeader.css';
function AlbumStudioTableHeader(props) {
const {
columns,
allSelected,
allUnselected,
onSelectAllChange,
...otherProps
} = props;
return (
<VirtualTableHeader>
<VirtualTableSelectAllHeaderCell
allSelected={allSelected}
allUnselected={allUnselected}
onSelectAllChange={onSelectAllChange}
/>
{
columns.map((column) => {
const {
name,
label,
isSortable,
isVisible
} = column;
if (!isVisible) {
return null;
}
return (
<VirtualTableHeaderCell
key={name}
className={styles[name]}
name={name}
isSortable={isSortable}
{...otherProps}
>
{label}
</VirtualTableHeaderCell>
);
})
}
</VirtualTableHeader>
);
}
AlbumStudioTableHeader.propTypes = {
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
allSelected: PropTypes.bool.isRequired,
allUnselected: PropTypes.bool.isRequired,
onSelectAllChange: PropTypes.func.isRequired
};
export default AlbumStudioTableHeader;

@ -42,6 +42,7 @@ class VirtualTableSelectCell extends Component {
render() { render() {
const { const {
className,
inputClassName, inputClassName,
id, id,
isSelected, isSelected,
@ -51,7 +52,7 @@ class VirtualTableSelectCell extends Component {
return ( return (
<VirtualTableRowCell <VirtualTableRowCell
className={styles.cell} className={className}
{...otherProps} {...otherProps}
> >
<CheckInput <CheckInput
@ -67,6 +68,7 @@ class VirtualTableSelectCell extends Component {
} }
VirtualTableSelectCell.propTypes = { VirtualTableSelectCell.propTypes = {
className: PropTypes.string.isRequired,
inputClassName: PropTypes.string.isRequired, inputClassName: PropTypes.string.isRequired,
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
isSelected: PropTypes.bool.isRequired, isSelected: PropTypes.bool.isRequired,
@ -75,6 +77,7 @@ VirtualTableSelectCell.propTypes = {
}; };
VirtualTableSelectCell.defaultProps = { VirtualTableSelectCell.defaultProps = {
className: styles.cell,
inputClassName: styles.input, inputClassName: styles.input,
isSelected: false isSelected: false
}; };

@ -7,8 +7,6 @@ import { WindowScroller, Grid } from 'react-virtualized';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import styles from './VirtualTable.css'; import styles from './VirtualTable.css';
const ROW_HEIGHT = 38;
function overscanIndicesGetter(options) { function overscanIndicesGetter(options) {
const { const {
cellCount, cellCount,
@ -48,7 +46,8 @@ class VirtualTable extends Component {
componentDidUpdate(prevProps, prevState) { componentDidUpdate(prevProps, prevState) {
const { const {
items, items,
scrollIndex scrollIndex,
onRecompute
} = this.props; } = this.props;
const { const {
@ -58,6 +57,7 @@ class VirtualTable extends Component {
if (this._grid && if (this._grid &&
(prevState.width !== width || (prevState.width !== width ||
hasDifferentItemsOrOrder(prevProps.items, items))) { hasDifferentItemsOrOrder(prevProps.items, items))) {
onRecompute(width);
// recomputeGridSize also forces Grid to discard its cache of rendered cells // recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize(); this._grid.recomputeGridSize();
} }
@ -97,6 +97,7 @@ class VirtualTable extends Component {
scroller, scroller,
header, header,
headerHeight, headerHeight,
rowHeight,
rowRenderer, rowRenderer,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -146,7 +147,7 @@ class VirtualTable extends Component {
width={width} width={width}
height={height} height={height}
headerHeight={height - headerHeight} headerHeight={height - headerHeight}
rowHeight={ROW_HEIGHT} rowHeight={rowHeight}
rowCount={items.length} rowCount={items.length}
columnCount={1} columnCount={1}
columnWidth={width} columnWidth={width}
@ -181,12 +182,16 @@ VirtualTable.propTypes = {
scroller: PropTypes.instanceOf(Element).isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
header: PropTypes.node.isRequired, header: PropTypes.node.isRequired,
headerHeight: PropTypes.number.isRequired, headerHeight: PropTypes.number.isRequired,
rowRenderer: PropTypes.func.isRequired rowHeight: PropTypes.oneOfType([PropTypes.func, PropTypes.number]).isRequired,
rowRenderer: PropTypes.func.isRequired,
onRecompute: PropTypes.func.isRequired
}; };
VirtualTable.defaultProps = { VirtualTable.defaultProps = {
className: styles.tableContainer, className: styles.tableContainer,
headerHeight: 38 headerHeight: 38,
rowHeight: 38,
onRecompute: () => {}
}; };
export default VirtualTable; export default VirtualTable;

Loading…
Cancel
Save