Fixed: Rework Artist Index and VirtualTable

pull/1689/head
ta264 5 years ago committed by Qstick
parent 4036b2ade2
commit 9902434057

@ -22,16 +22,15 @@ class ImportArtist extends Component {
allUnselected: false, allUnselected: false,
lastToggled: null, lastToggled: null,
selectedState: {}, selectedState: {},
contentBody: null, scroller: null
scrollTop: 0
}; };
} }
// //
// Control // Control
setContentBodyRef = (ref) => { setScrollerRef = (ref) => {
this.setState({ contentBody: ref }); this.setState({ scroller: ref });
} }
// //
@ -72,10 +71,6 @@ class ImportArtist extends Component {
this.props.onImportPress(this.getSelectedIds()); this.props.onImportPress(this.getSelectedIds());
} }
onScroll = ({ scrollTop }) => {
this.setState({ scrollTop });
}
// //
// Render // Render
@ -94,13 +89,13 @@ class ImportArtist extends Component {
allSelected, allSelected,
allUnselected, allUnselected,
selectedState, selectedState,
contentBody scroller
} = this.state; } = this.state;
return ( return (
<PageContent title="Import Artist"> <PageContent title="Import Artist">
<PageContentBodyConnector <PageContentBodyConnector
ref={this.setContentBodyRef} registerScroller={this.setScrollerRef}
onScroll={this.onScroll} onScroll={this.onScroll}
> >
{ {
@ -121,20 +116,18 @@ class ImportArtist extends Component {
} }
{ {
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && contentBody && !rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && scroller &&
<ImportArtistTableConnector <ImportArtistTableConnector
rootFolderId={rootFolderId} rootFolderId={rootFolderId}
unmappedFolders={unmappedFolders} unmappedFolders={unmappedFolders}
allSelected={allSelected} allSelected={allSelected}
allUnselected={allUnselected} allUnselected={allUnselected}
selectedState={selectedState} selectedState={selectedState}
contentBody={contentBody} scroller={scroller}
showMetadataProfile={showMetadataProfile} showMetadataProfile={showMetadataProfile}
scrollTop={this.state.scrollTop}
onSelectAllChange={this.onSelectAllChange} onSelectAllChange={this.onSelectAllChange}
onSelectedChange={this.onSelectedChange} onSelectedChange={this.onSelectedChange}
onRemoveSelectedStateItem={this.onRemoveSelectedStateItem} onRemoveSelectedStateItem={this.onRemoveSelectedStateItem}
onScroll={this.onScroll}
/> />
} }
</PageContentBodyConnector> </PageContentBodyConnector>

@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { inputTypes } from 'Helpers/Props'; import { inputTypes } from 'Helpers/Props';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell'; import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
import ImportArtistSelectArtistConnector from './SelectArtist/ImportArtistSelectArtistConnector'; import ImportArtistSelectArtistConnector from './SelectArtist/ImportArtistSelectArtistConnector';
@ -10,7 +9,6 @@ import styles from './ImportArtistRow.css';
function ImportArtistRow(props) { function ImportArtistRow(props) {
const { const {
style,
id, id,
monitor, monitor,
qualityProfileId, qualityProfileId,
@ -25,7 +23,7 @@ function ImportArtistRow(props) {
} = props; } = props;
return ( return (
<VirtualTableRow style={style}> <>
<VirtualTableSelectCell <VirtualTableSelectCell
inputClassName={styles.selectInput} inputClassName={styles.selectInput}
id={id} id={id}
@ -82,12 +80,11 @@ function ImportArtistRow(props) {
isExistingArtist={isExistingArtist} isExistingArtist={isExistingArtist}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
</VirtualTableRow> </>
); );
} }
ImportArtistRow.propTypes = { ImportArtistRow.propTypes = {
style: PropTypes.object.isRequired,
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
monitor: PropTypes.string.isRequired, monitor: PropTypes.string.isRequired,
qualityProfileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired,

@ -2,6 +2,7 @@ import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import VirtualTable from 'Components/Table/VirtualTable'; import VirtualTable from 'Components/Table/VirtualTable';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
import ImportArtistHeader from './ImportArtistHeader'; import ImportArtistHeader from './ImportArtistHeader';
import ImportArtistRowConnector from './ImportArtistRowConnector'; import ImportArtistRowConnector from './ImportArtistRowConnector';
@ -110,15 +111,20 @@ class ImportArtistTable extends Component {
const item = items[rowIndex]; const item = items[rowIndex];
return ( return (
<ImportArtistRowConnector <VirtualTableRow
key={key} key={key}
style={style} style={style}
rootFolderId={rootFolderId} >
showMetadataProfile={showMetadataProfile} <ImportArtistRowConnector
isSelected={selectedState[item.id]} key={item.id}
onSelectedChange={onSelectedChange} style={style}
id={item.id} rootFolderId={rootFolderId}
/> showMetadataProfile={showMetadataProfile}
isSelected={selectedState[item.id]}
onSelectedChange={onSelectedChange}
id={item.id}
/>
</VirtualTableRow>
); );
} }
@ -131,12 +137,10 @@ class ImportArtistTable extends Component {
allSelected, allSelected,
allUnselected, allUnselected,
isSmallScreen, isSmallScreen,
contentBody,
showMetadataProfile, showMetadataProfile,
scrollTop, scroller,
selectedState, selectedState,
onSelectAllChange, onSelectAllChange
onScroll
} = this.props; } = this.props;
if (!items.length) { if (!items.length) {
@ -146,10 +150,9 @@ class ImportArtistTable extends Component {
return ( return (
<VirtualTable <VirtualTable
items={items} items={items}
contentBody={contentBody}
isSmallScreen={isSmallScreen} isSmallScreen={isSmallScreen}
scroller={scroller}
rowHeight={52} rowHeight={52}
scrollTop={scrollTop}
overscanRowCount={2} overscanRowCount={2}
rowRenderer={this.rowRenderer} rowRenderer={this.rowRenderer}
header={ header={
@ -161,7 +164,6 @@ class ImportArtistTable extends Component {
/> />
} }
selectedState={selectedState} selectedState={selectedState}
onScroll={onScroll}
/> />
); );
} }
@ -180,15 +182,14 @@ ImportArtistTable.propTypes = {
selectedState: PropTypes.object.isRequired, selectedState: PropTypes.object.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
allArtists: PropTypes.arrayOf(PropTypes.object), allArtists: PropTypes.arrayOf(PropTypes.object),
contentBody: PropTypes.object.isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
showMetadataProfile: PropTypes.bool.isRequired, showMetadataProfile: PropTypes.bool.isRequired,
scrollTop: PropTypes.number.isRequired, scrollTop: PropTypes.number.isRequired,
onSelectAllChange: PropTypes.func.isRequired, onSelectAllChange: PropTypes.func.isRequired,
onSelectedChange: PropTypes.func.isRequired, onSelectedChange: PropTypes.func.isRequired,
onRemoveSelectedStateItem: PropTypes.func.isRequired, onRemoveSelectedStateItem: PropTypes.func.isRequired,
onArtistLookup: PropTypes.func.isRequired, onArtistLookup: PropTypes.func.isRequired,
onSetImportArtistValue: PropTypes.func.isRequired, onSetImportArtistValue: PropTypes.func.isRequired
onScroll: PropTypes.func.isRequired
}; };
export default ImportArtistTable; export default ImportArtistTable;

@ -53,13 +53,12 @@ class ArtistIndex extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
contentBody: null, scroller: null,
jumpBarItems: { order: [] }, jumpBarItems: { order: [] },
jumpToCharacter: null, jumpToCharacter: null,
isPosterOptionsModalOpen: false, isPosterOptionsModalOpen: false,
isBannerOptionsModalOpen: false, isBannerOptionsModalOpen: false,
isOverviewOptionsModalOpen: false, isOverviewOptionsModalOpen: false
isRendered: false
}; };
} }
@ -71,8 +70,7 @@ class ArtistIndex extends Component {
const { const {
items, items,
sortKey, sortKey,
sortDirection, sortDirection
scrollTop
} = this.props; } = this.props;
if (sortKey !== prevProps.sortKey || if (sortKey !== prevProps.sortKey ||
@ -82,7 +80,7 @@ class ArtistIndex extends Component {
this.setJumpBarItems(); this.setJumpBarItems();
} }
if (this.state.jumpToCharacter != null && scrollTop !== prevProps.scrollTop) { if (this.state.jumpToCharacter != null) {
this.setState({ jumpToCharacter: null }); this.setState({ jumpToCharacter: null });
} }
} }
@ -90,8 +88,8 @@ class ArtistIndex extends Component {
// //
// Control // Control
setContentBodyRef = (ref) => { setScrollerRef = (ref) => {
this.setState({ contentBody: ref }); this.setState({ scroller: ref });
} }
setJumpBarItems() { setJumpBarItems() {
@ -169,27 +167,6 @@ class ArtistIndex extends Component {
this.setState({ jumpToCharacter }); this.setState({ jumpToCharacter });
} }
onRender = () => {
this.setState({ isRendered: true }, () => {
const {
scrollTop,
isSmallScreen
} = this.props;
if (isSmallScreen) {
// Seems to result in the view being off by 125px (distance to the top of the page)
// document.documentElement.scrollTop = document.body.scrollTop = scrollTop;
// This works, but then jumps another 1px after scrolling
document.documentElement.scrollTop = scrollTop;
}
});
}
onScroll = ({ scrollTop }) => {
this.props.onScroll({ scrollTop });
}
// //
// Render // Render
@ -209,7 +186,7 @@ class ArtistIndex extends Component {
view, view,
isRefreshingArtist, isRefreshingArtist,
isRssSyncExecuting, isRssSyncExecuting,
scrollTop, onScroll,
onSortSelect, onSortSelect,
onFilterSelect, onFilterSelect,
onViewSelect, onViewSelect,
@ -219,17 +196,16 @@ class ArtistIndex extends Component {
} = this.props; } = this.props;
const { const {
contentBody, scroller,
jumpBarItems, jumpBarItems,
jumpToCharacter, jumpToCharacter,
isPosterOptionsModalOpen, isPosterOptionsModalOpen,
isBannerOptionsModalOpen, isBannerOptionsModalOpen,
isOverviewOptionsModalOpen, isOverviewOptionsModalOpen
isRendered
} = this.state; } = this.state;
const ViewComponent = getViewComponent(view); const ViewComponent = getViewComponent(view);
const isLoaded = !!(!error && isPopulated && items.length && contentBody); const isLoaded = !!(!error && isPopulated && items.length && scroller);
const hasNoArtist = !totalItems; const hasNoArtist = !totalItems;
return ( return (
@ -338,11 +314,10 @@ class ArtistIndex extends Component {
<div className={styles.pageContentBodyWrapper}> <div className={styles.pageContentBodyWrapper}>
<PageContentBodyConnector <PageContentBodyConnector
ref={this.setContentBodyRef} registerScroller={this.setScrollerRef}
className={styles.contentBody} className={styles.contentBody}
innerClassName={styles[`${view}InnerContentBody`]} innerClassName={styles[`${view}InnerContentBody`]}
scrollTop={isRendered ? scrollTop : 0} onScroll={onScroll}
onScroll={this.onScroll}
> >
{ {
isFetching && !isPopulated && isFetching && !isPopulated &&
@ -360,14 +335,12 @@ class ArtistIndex extends Component {
isLoaded && isLoaded &&
<div className={styles.contentBodyContainer}> <div className={styles.contentBodyContainer}>
<ViewComponent <ViewComponent
contentBody={contentBody} scroller={scroller}
items={items} items={items}
filters={filters} filters={filters}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
scrollTop={scrollTop}
jumpToCharacter={jumpToCharacter} jumpToCharacter={jumpToCharacter}
onRender={this.onRender}
{...otherProps} {...otherProps}
/> />
@ -426,7 +399,6 @@ ArtistIndex.propTypes = {
view: PropTypes.string.isRequired, view: PropTypes.string.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired, isRefreshingArtist: PropTypes.bool.isRequired,
isRssSyncExecuting: PropTypes.bool.isRequired, isRssSyncExecuting: PropTypes.bool.isRequired,
scrollTop: PropTypes.number.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired, onSortSelect: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,

@ -4,7 +4,6 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector'; import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import dimensions from 'Styles/Variables/dimensions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import scrollPositions from 'Store/scrollPositions'; import scrollPositions from 'Store/scrollPositions';
@ -14,35 +13,6 @@ import * as commandNames from 'Commands/commandNames';
import withScrollPosition from 'Components/withScrollPosition'; import withScrollPosition from 'Components/withScrollPosition';
import ArtistIndex from './ArtistIndex'; import ArtistIndex from './ArtistIndex';
const POSTERS_PADDING = 15;
const POSTERS_PADDING_SMALL_SCREEN = 5;
const BANNERS_PADDING = 15;
const BANNERS_PADDING_SMALL_SCREEN = 5;
const TABLE_PADDING = parseInt(dimensions.pageContentBodyPadding);
const TABLE_PADDING_SMALL_SCREEN = parseInt(dimensions.pageContentBodyPaddingSmallScreen);
// If the scrollTop is greater than zero it needs to be offset
// by the padding so when it is set initially so it is correct
// after React Virtualized takes the padding into account.
function getScrollTop(view, scrollTop, isSmallScreen) {
if (scrollTop === 0) {
return 0;
}
let padding = isSmallScreen ? TABLE_PADDING_SMALL_SCREEN : TABLE_PADDING;
if (view === 'posters') {
padding = isSmallScreen ? POSTERS_PADDING_SMALL_SCREEN : POSTERS_PADDING;
}
if (view === 'banners') {
padding = isSmallScreen ? BANNERS_PADDING_SMALL_SCREEN : BANNERS_PADDING;
}
return scrollTop + padding;
}
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistClientSideCollectionItemsSelector('artistIndex'), createArtistClientSideCollectionItemsSelector('artistIndex'),
@ -99,39 +69,15 @@ function createMapDispatchToProps(dispatch, props) {
class ArtistIndexConnector extends Component { class ArtistIndexConnector extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
const {
view,
scrollTop,
isSmallScreen
} = props;
this.state = {
scrollTop: getScrollTop(view, scrollTop, isSmallScreen)
};
}
// //
// Listeners // Listeners
onViewSelect = (view) => { onViewSelect = (view) => {
// Reset the scroll position before changing the view this.props.dispatchSetArtistView(view);
this.setState({ scrollTop: 0 }, () => {
this.props.dispatchSetArtistView(view);
});
} }
onScroll = ({ scrollTop }) => { onScroll = ({ scrollTop }) => {
this.setState({ scrollPositions.artistIndex = scrollTop;
scrollTop
}, () => {
scrollPositions.artistIndex = scrollTop;
});
} }
// //
@ -141,7 +87,6 @@ class ArtistIndexConnector extends Component {
return ( return (
<ArtistIndex <ArtistIndex
{...this.props} {...this.props}
scrollTop={this.state.scrollTop}
onViewSelect={this.onViewSelect} onViewSelect={this.onViewSelect}
onScroll={this.onScroll} onScroll={this.onScroll}
/> />
@ -152,7 +97,6 @@ class ArtistIndexConnector extends Component {
ArtistIndexConnector.propTypes = { ArtistIndexConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
view: PropTypes.string.isRequired, view: PropTypes.string.isRequired,
scrollTop: PropTypes.number.isRequired,
dispatchSetArtistView: PropTypes.func.isRequired dispatchSetArtistView: PropTypes.func.isRequired
}; };

@ -1,11 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Grid, WindowScroller } from 'react-virtualized'; import { Grid, WindowScroller } from 'react-virtualized';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import { sortDirections } from 'Helpers/Props';
import Measure from 'Components/Measure'; import Measure from 'Components/Measure';
import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector';
import ArtistIndexBanner from './ArtistIndexBanner'; import ArtistIndexBanner from './ArtistIndexBanner';
@ -109,52 +107,46 @@ class ArtistIndexBanners extends Component {
this._grid = null; this._grid = null;
} }
componentDidMount() { componentDidUpdate(prevProps, prevState) {
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
}
componentDidUpdate(prevProps) {
const { const {
items, items,
filters,
sortKey, sortKey,
sortDirection,
bannerOptions, bannerOptions,
jumpToCharacter jumpToCharacter
} = this.props; } = this.props;
const itemsChanged = hasDifferentItemsOrOrder(prevProps.items, items); const {
width,
columnWidth,
columnCount,
rowHeight
} = this.state;
if ( if (prevProps.sortKey !== sortKey ||
prevProps.sortKey !== sortKey || prevProps.bannerOptions !== bannerOptions) {
prevProps.bannerOptions !== bannerOptions ||
itemsChanged
) {
this.calculateGrid(); this.calculateGrid();
} }
if ( if (this._grid &&
prevProps.filters !== filters || (prevState.width !== width ||
prevProps.sortKey !== sortKey || prevState.columnWidth !== columnWidth ||
prevProps.sortDirection !== sortDirection || prevState.columnCount !== columnCount ||
itemsChanged prevState.rowHeight !== rowHeight ||
) { hasDifferentItemsOrOrder(prevProps.items, items))) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize(); this._grid.recomputeGridSize();
} }
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) { if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter); const index = getIndexOfFirstCharacter(items, jumpToCharacter);
if (index != null) { if (this._grid && index != null) {
const {
columnCount,
rowHeight
} = this.state;
const row = Math.floor(index / columnCount); const row = Math.floor(index / columnCount);
const scrollTop = rowHeight * row;
this.props.onScroll({ scrollTop }); this._grid.scrollToCell({
rowIndex: row,
columnIndex: 0
});
} }
} }
} }
@ -251,22 +243,14 @@ class ArtistIndexBanners extends Component {
this.calculateGrid(width, this.props.isSmallScreen); this.calculateGrid(width, this.props.isSmallScreen);
} }
onSectionRendered = () => {
if (!this._isInitialized && this._contentBodyNode) {
this.props.onRender();
this._isInitialized = true;
}
}
// //
// Render // Render
render() { render() {
const { const {
items, items,
scrollTop,
isSmallScreen, isSmallScreen,
onScroll scroller
} = this.props; } = this.props;
const { const {
@ -279,12 +263,18 @@ class ArtistIndexBanners extends Component {
const rowCount = Math.ceil(items.length / columnCount); const rowCount = Math.ceil(items.length / columnCount);
return ( return (
<Measure onMeasure={this.onMeasure}> <Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<WindowScroller <WindowScroller
scrollElement={isSmallScreen ? undefined : this._contentBodyNode} scrollElement={isSmallScreen ? undefined : scroller}
onScroll={onScroll}
> >
{({ height, isScrolling }) => { {({ height, registerChild, onChildScroll, scrollTop }) => {
if (!height) {
return <div />;
}
return ( return (
<Grid <Grid
ref={this.setGridRef} ref={this.setGridRef}
@ -296,10 +286,11 @@ class ArtistIndexBanners extends Component {
rowCount={rowCount} rowCount={rowCount}
rowHeight={rowHeight} rowHeight={rowHeight}
width={width} width={width}
onScroll={onChildScroll}
scrollTop={scrollTop} scrollTop={scrollTop}
overscanRowCount={2} overscanRowCount={2}
cellRenderer={this.cellRenderer} cellRenderer={this.cellRenderer}
onSectionRendered={this.onSectionRendered} scrollToAlignment={'start'}
isScrollingOptOut={true} isScrollingOptOut={true}
/> />
); );
@ -313,19 +304,14 @@ class ArtistIndexBanners extends Component {
ArtistIndexBanners.propTypes = { ArtistIndexBanners.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
bannerOptions: PropTypes.object.isRequired, bannerOptions: PropTypes.object.isRequired,
scrollTop: PropTypes.number.isRequired,
jumpToCharacter: PropTypes.string, jumpToCharacter: PropTypes.string,
contentBody: PropTypes.object.isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired, showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
}; };
export default ArtistIndexBanners; export default ArtistIndexBanners;

@ -1,12 +1,9 @@
import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Grid, WindowScroller } from 'react-virtualized'; import { Grid, WindowScroller } from 'react-virtualized';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import { sortDirections } from 'Helpers/Props';
import Measure from 'Components/Measure'; import Measure from 'Components/Measure';
import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector';
import ArtistIndexOverview from './ArtistIndexOverview'; import ArtistIndexOverview from './ArtistIndexOverview';
@ -66,56 +63,44 @@ class ArtistIndexOverviews extends Component {
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {}) rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
}; };
this._isInitialized = false;
this._grid = null; this._grid = null;
} }
componentDidMount() { componentDidUpdate(prevProps, prevState) {
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
}
componentDidUpdate(prevProps) {
const { const {
items, items,
filters,
sortKey, sortKey,
sortDirection,
overviewOptions, overviewOptions,
jumpToCharacter jumpToCharacter
} = this.props; } = this.props;
const itemsChanged = hasDifferentItemsOrOrder(prevProps.items, items); const {
const overviewOptionsChanged = !_.isMatch(prevProps.overviewOptions, overviewOptions); width,
rowHeight
} = this.state;
if ( if (prevProps.sortKey !== sortKey ||
prevProps.sortKey !== sortKey || prevProps.overviewOptions !== overviewOptions) {
prevProps.overviewOptions !== overviewOptions ||
itemsChanged
) {
this.calculateGrid(); this.calculateGrid();
} }
if ( if (this._grid &&
prevProps.filters !== filters || (prevState.width !== width ||
prevProps.sortKey !== sortKey || prevState.rowHeight !== rowHeight ||
prevProps.sortDirection !== sortDirection || hasDifferentItemsOrOrder(prevProps.items, items))) {
itemsChanged || // recomputeGridSize also forces Grid to discard its cache of rendered cells
overviewOptionsChanged
) {
this._grid.recomputeGridSize(); this._grid.recomputeGridSize();
} }
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) { if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter); const index = getIndexOfFirstCharacter(items, jumpToCharacter);
if (index != null) { if (this._grid && index != null) {
const {
rowHeight
} = this.state;
const scrollTop = rowHeight * index; this._grid.scrollToCell({
rowIndex: index,
this.props.onScroll({ scrollTop }); columnIndex: 0
});
} }
} }
} }
@ -123,21 +108,6 @@ class ArtistIndexOverviews extends Component {
// //
// Control // Control
scrollToFirstCharacter(character) {
const items = this.props.items;
const {
rowHeight
} = this.state;
const index = getIndexOfFirstCharacter(items, character);
if (index != null) {
const scrollTop = rowHeight * index;
this.props.onScroll({ scrollTop });
}
}
setGridRef = (ref) => { setGridRef = (ref) => {
this._grid = ref; this._grid = ref;
} }
@ -217,22 +187,14 @@ class ArtistIndexOverviews extends Component {
this.calculateGrid(width, this.props.isSmallScreen); this.calculateGrid(width, this.props.isSmallScreen);
} }
onSectionRendered = () => {
if (!this._isInitialized && this._contentBodyNode) {
this.props.onRender();
this._isInitialized = true;
}
}
// //
// Render // Render
render() { render() {
const { const {
items, items,
scrollTop,
isSmallScreen, isSmallScreen,
onScroll scroller
} = this.props; } = this.props;
const { const {
@ -241,29 +203,39 @@ class ArtistIndexOverviews extends Component {
} = this.state; } = this.state;
return ( return (
<Measure onMeasure={this.onMeasure}> <Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<WindowScroller <WindowScroller
scrollElement={isSmallScreen ? undefined : this._contentBodyNode} scrollElement={isSmallScreen ? undefined : scroller}
onScroll={onScroll}
> >
{({ height, isScrolling }) => { {({ height, registerChild, onChildScroll, scrollTop }) => {
if (!height) {
return <div />;
}
return ( return (
<Grid <div ref={registerChild}>
ref={this.setGridRef} <Grid
className={styles.grid} ref={this.setGridRef}
autoHeight={true} className={styles.grid}
height={height} autoHeight={true}
columnCount={1} height={height}
columnWidth={width} columnCount={1}
rowCount={items.length} columnWidth={width}
rowHeight={rowHeight} rowCount={items.length}
width={width} rowHeight={rowHeight}
scrollTop={scrollTop} width={width}
overscanRowCount={2} onScroll={onChildScroll}
cellRenderer={this.cellRenderer} scrollTop={scrollTop}
onSectionRendered={this.onSectionRendered} overscanRowCount={2}
isScrollingOptOut={true} cellRenderer={this.cellRenderer}
/> onSectionRendered={this.onSectionRendered}
scrollToAlignment={'start'}
isScrollingOptOut={true}
/>
</div>
); );
} }
} }
@ -275,20 +247,16 @@ class ArtistIndexOverviews extends Component {
ArtistIndexOverviews.propTypes = { ArtistIndexOverviews.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
overviewOptions: PropTypes.object.isRequired, overviewOptions: PropTypes.object.isRequired,
scrollTop: PropTypes.number.isRequired, scrollTop: PropTypes.number.isRequired,
jumpToCharacter: PropTypes.string, jumpToCharacter: PropTypes.string,
contentBody: PropTypes.object.isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired, showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
longDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
}; };
export default ArtistIndexOverviews; export default ArtistIndexOverviews;

@ -1,11 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { Grid, WindowScroller } from 'react-virtualized'; import { Grid, WindowScroller } from 'react-virtualized';
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter'; import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import { sortDirections } from 'Helpers/Props';
import Measure from 'Components/Measure'; import Measure from 'Components/Measure';
import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector';
import ArtistIndexPoster from './ArtistIndexPoster'; import ArtistIndexPoster from './ArtistIndexPoster';
@ -109,52 +107,46 @@ class ArtistIndexPosters extends Component {
this._grid = null; this._grid = null;
} }
componentDidMount() { componentDidUpdate(prevProps, prevState) {
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
}
componentDidUpdate(prevProps) {
const { const {
items, items,
filters,
sortKey, sortKey,
sortDirection,
posterOptions, posterOptions,
jumpToCharacter jumpToCharacter
} = this.props; } = this.props;
const itemsChanged = hasDifferentItemsOrOrder(prevProps.items, items); const {
width,
columnWidth,
columnCount,
rowHeight
} = this.state;
if ( if (prevProps.sortKey !== sortKey ||
prevProps.sortKey !== sortKey || prevProps.posterOptions !== posterOptions) {
prevProps.posterOptions !== posterOptions ||
itemsChanged
) {
this.calculateGrid(); this.calculateGrid();
} }
if ( if (this._grid &&
prevProps.filters !== filters || (prevState.width !== width ||
prevProps.sortKey !== sortKey || prevState.columnWidth !== columnWidth ||
prevProps.sortDirection !== sortDirection || prevState.columnCount !== columnCount ||
itemsChanged prevState.rowHeight !== rowHeight ||
) { hasDifferentItemsOrOrder(prevProps.items, items))) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize(); this._grid.recomputeGridSize();
} }
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) { if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const index = getIndexOfFirstCharacter(items, jumpToCharacter); const index = getIndexOfFirstCharacter(items, jumpToCharacter);
if (index != null) { if (this._grid && index != null) {
const {
columnCount,
rowHeight
} = this.state;
const row = Math.floor(index / columnCount); const row = Math.floor(index / columnCount);
const scrollTop = rowHeight * row;
this.props.onScroll({ scrollTop }); this._grid.scrollToCell({
rowIndex: row,
columnIndex: 0
});
} }
} }
} }
@ -252,22 +244,14 @@ class ArtistIndexPosters extends Component {
this.calculateGrid(width, this.props.isSmallScreen); this.calculateGrid(width, this.props.isSmallScreen);
} }
onSectionRendered = () => {
if (!this._isInitialized && this._contentBodyNode) {
this.props.onRender();
this._isInitialized = true;
}
}
// //
// Render // Render
render() { render() {
const { const {
items, items,
scrollTop,
isSmallScreen, isSmallScreen,
onScroll scroller
} = this.props; } = this.props;
const { const {
@ -280,29 +264,38 @@ class ArtistIndexPosters extends Component {
const rowCount = Math.ceil(items.length / columnCount); const rowCount = Math.ceil(items.length / columnCount);
return ( return (
<Measure onMeasure={this.onMeasure}> <Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<WindowScroller <WindowScroller
scrollElement={isSmallScreen ? undefined : this._contentBodyNode} scrollElement={isSmallScreen ? undefined : scroller}
onScroll={onScroll}
> >
{({ height, isScrolling }) => { {({ height, registerChild, onChildScroll, scrollTop }) => {
if (!height) {
return <div />;
}
return ( return (
<Grid <div ref={registerChild}>
ref={this.setGridRef} <Grid
className={styles.grid} ref={this.setGridRef}
autoHeight={true} className={styles.grid}
height={height} autoHeight={true}
columnCount={columnCount} height={height}
columnWidth={columnWidth} columnCount={columnCount}
rowCount={rowCount} columnWidth={columnWidth}
rowHeight={rowHeight} rowCount={rowCount}
width={width} rowHeight={rowHeight}
scrollTop={scrollTop} width={width}
overscanRowCount={2} onScroll={onChildScroll}
cellRenderer={this.cellRenderer} scrollTop={scrollTop}
onSectionRendered={this.onSectionRendered} overscanRowCount={2}
isScrollingOptOut={true} cellRenderer={this.cellRenderer}
/> scrollToAlignment={'start'}
isScrollingOptOut={true}
/>
</div>
); );
} }
} }
@ -314,19 +307,14 @@ class ArtistIndexPosters extends Component {
ArtistIndexPosters.propTypes = { ArtistIndexPosters.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
posterOptions: PropTypes.object.isRequired, posterOptions: PropTypes.object.isRequired,
scrollTop: PropTypes.number.isRequired,
jumpToCharacter: PropTypes.string, jumpToCharacter: PropTypes.string,
contentBody: PropTypes.object.isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
showRelativeDates: PropTypes.bool.isRequired, showRelativeDates: PropTypes.bool.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
}; };
export default ArtistIndexPosters; export default ArtistIndexPosters;

@ -23,10 +23,12 @@ class ArtistIndexTable extends Component {
} }
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const jumpToCharacter = this.props.jumpToCharacter; const {
items,
jumpToCharacter
} = this.props;
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) { if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
const items = this.props.items;
const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter); const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter);
@ -76,26 +78,21 @@ class ArtistIndexTable extends Component {
const { const {
items, items,
columns, columns,
filters,
sortKey, sortKey,
sortDirection, sortDirection,
showBanners, showBanners,
isSmallScreen, isSmallScreen,
scrollTop,
contentBody,
onSortPress, onSortPress,
onRender, scroller
onScroll
} = this.props; } = this.props;
return ( return (
<VirtualTable <VirtualTable
className={styles.tableContainer} className={styles.tableContainer}
items={items} items={items}
scrollTop={scrollTop}
scrollIndex={this.state.scrollIndex} scrollIndex={this.state.scrollIndex}
contentBody={contentBody}
isSmallScreen={isSmallScreen} isSmallScreen={isSmallScreen}
scroller={scroller}
rowHeight={showBanners ? 70 : 38} rowHeight={showBanners ? 70 : 38}
overscanRowCount={2} overscanRowCount={2}
rowRenderer={this.rowRenderer} rowRenderer={this.rowRenderer}
@ -109,12 +106,8 @@ class ArtistIndexTable extends Component {
/> />
} }
columns={columns} columns={columns}
filters={filters}
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
onRender={onRender}
onScroll={onScroll}
isScrollingOptOut={true}
/> />
); );
} }
@ -123,17 +116,13 @@ class ArtistIndexTable extends Component {
ArtistIndexTable.propTypes = { ArtistIndexTable.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all), sortDirection: PropTypes.oneOf(sortDirections.all),
showBanners: PropTypes.bool.isRequired, showBanners: PropTypes.bool.isRequired,
scrollTop: PropTypes.number.isRequired,
jumpToCharacter: PropTypes.string, jumpToCharacter: PropTypes.string,
contentBody: PropTypes.object.isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
}; };
export default ArtistIndexTable; export default ArtistIndexTable;

@ -37,6 +37,10 @@ class OverlayScroller extends Component {
_setScrollRef = (ref) => { _setScrollRef = (ref) => {
this._scroller = ref; this._scroller = ref;
if (ref) {
this.props.registerScroller(ref.view);
}
} }
_renderThumb = (props) => { _renderThumb = (props) => {
@ -157,7 +161,8 @@ OverlayScroller.propTypes = {
autoHide: PropTypes.bool.isRequired, autoHide: PropTypes.bool.isRequired,
autoScroll: PropTypes.bool.isRequired, autoScroll: PropTypes.bool.isRequired,
children: PropTypes.node, children: PropTypes.node,
onScroll: PropTypes.func onScroll: PropTypes.func,
registerScroller: PropTypes.func
}; };
OverlayScroller.defaultProps = { OverlayScroller.defaultProps = {
@ -165,7 +170,8 @@ OverlayScroller.defaultProps = {
trackClassName: styles.thumb, trackClassName: styles.thumb,
scrollDirection: scrollDirections.VERTICAL, scrollDirection: scrollDirections.VERTICAL,
autoHide: false, autoHide: false,
autoScroll: true autoScroll: true,
registerScroller: () => {}
}; };
export default OverlayScroller; export default OverlayScroller;

@ -30,6 +30,8 @@ class Scroller extends Component {
_setScrollerRef = (ref) => { _setScrollerRef = (ref) => {
this._scroller = ref; this._scroller = ref;
this.props.registerScroller(ref);
} }
// //
@ -43,6 +45,7 @@ class Scroller extends Component {
children, children,
scrollTop, scrollTop,
onScroll, onScroll,
registerScroller,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -70,12 +73,14 @@ Scroller.propTypes = {
autoScroll: PropTypes.bool.isRequired, autoScroll: PropTypes.bool.isRequired,
scrollTop: PropTypes.number, scrollTop: PropTypes.number,
children: PropTypes.node, children: PropTypes.node,
onScroll: PropTypes.func onScroll: PropTypes.func,
registerScroller: PropTypes.func
}; };
Scroller.defaultProps = { Scroller.defaultProps = {
scrollDirection: scrollDirections.VERTICAL, scrollDirection: scrollDirections.VERTICAL,
autoScroll: true autoScroll: true,
registerScroller: () => {}
}; };
export default Scroller; export default Scroller;

@ -1,3 +1,7 @@
.tableContainer { .tableContainer {
width: 100%; width: 100%;
} }
.tableBodyContainer {
position: relative;
}

@ -1,12 +1,10 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { WindowScroller } from 'react-virtualized';
import { isLocked } from 'Utilities/scrollLock';
import { scrollDirections } from 'Helpers/Props'; import { scrollDirections } from 'Helpers/Props';
import Measure from 'Components/Measure'; import Measure from 'Components/Measure';
import Scroller from 'Components/Scroller/Scroller'; import Scroller from 'Components/Scroller/Scroller';
import VirtualTableBody from './VirtualTableBody'; import { WindowScroller, Grid } from 'react-virtualized';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import styles from './VirtualTable.css'; import styles from './VirtualTable.css';
const ROW_HEIGHT = 38; const ROW_HEIGHT = 38;
@ -44,28 +42,39 @@ class VirtualTable extends Component {
width: 0 width: 0
}; };
this._isInitialized = false; this._grid = null;
} }
componentDidMount() { componentDidUpdate(prevProps, prevState) {
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody); const {
} items,
scrollIndex
} = this.props;
componentDidUpdate(prevProps, preState) { const {
const scrollIndex = this.props.scrollIndex; width
} = this.state;
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) { if (this._grid &&
const scrollTop = (scrollIndex + 1) * ROW_HEIGHT + 20; (prevState.width !== width ||
hasDifferentItemsOrOrder(prevProps.items, items))) {
// recomputeGridSize also forces Grid to discard its cache of rendered cells
this._grid.recomputeGridSize();
}
this.props.onScroll({ scrollTop }); if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
this._grid.scrollToCell({
rowIndex: scrollIndex,
columnIndex: 0
});
} }
} }
// //
// Control // Control
rowGetter = ({ index }) => { setGridRef = (ref) => {
return this.props.items[index]; this._grid = ref;
} }
// //
@ -77,36 +86,18 @@ class VirtualTable extends Component {
}); });
} }
onSectionRendered = () => {
if (!this._isInitialized && this._contentBodyNode) {
this.props.onRender();
this._isInitialized = true;
}
}
onScroll = (props) => {
if (isLocked()) {
return;
}
const { onScroll } = this.props;
onScroll(props);
}
// //
// Render // Render
render() { render() {
const { const {
isSmallScreen,
className, className,
items, items,
isSmallScreen, scroller,
header, header,
headerHeight, headerHeight,
scrollTop,
rowRenderer, rowRenderer,
onScroll,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -114,65 +105,88 @@ class VirtualTable extends Component {
width width
} = this.state; } = this.state;
const gridStyle = {
boxSizing: undefined,
direction: undefined,
height: undefined,
position: undefined,
willChange: undefined,
overflow: undefined,
width: undefined
};
const containerStyle = {
position: undefined
};
return ( return (
<Measure onMeasure={this.onMeasure}> <WindowScroller
<WindowScroller scrollElement={isSmallScreen ? undefined : scroller}
scrollElement={isSmallScreen ? undefined : this._contentBodyNode} >
onScroll={this.onScroll} {({ height, registerChild, onChildScroll, scrollTop }) => {
> if (!height) {
{({ height, isScrolling }) => { return null;
return ( }
return (
<Measure
whitelist={['width']}
onMeasure={this.onMeasure}
>
<Scroller <Scroller
className={className} className={className}
scrollDirection={scrollDirections.HORIZONTAL} scrollDirection={scrollDirections.HORIZONTAL}
> >
{header} {header}
<div ref={registerChild}>
<VirtualTableBody <Grid
autoContainerWidth={true} ref={this.setGridRef}
width={width} autoContainerWidth={true}
height={height} autoHeight={true}
headerHeight={height - headerHeight} autoWidth={true}
rowHeight={ROW_HEIGHT} width={width}
rowCount={items.length} height={height}
columnCount={1} headerHeight={height - headerHeight}
scrollTop={scrollTop} rowHeight={ROW_HEIGHT}
autoHeight={true} rowCount={items.length}
overscanRowCount={2} columnCount={1}
cellRenderer={rowRenderer} columnWidth={width}
columnWidth={width} scrollTop={scrollTop}
overscanIndicesGetter={overscanIndicesGetter} onScroll={onChildScroll}
onSectionRendered={this.onSectionRendered} overscanRowCount={2}
{...otherProps} cellRenderer={rowRenderer}
/> overscanIndicesGetter={overscanIndicesGetter}
scrollToAlignment={'start'}
isScrollingOptout={true}
className={styles.tableBodyContainer}
style={gridStyle}
containerStyle={containerStyle}
{...otherProps}
/>
</div>
</Scroller> </Scroller>
); </Measure>
} );
} }
</WindowScroller> }
</Measure> </WindowScroller>
); );
} }
} }
VirtualTable.propTypes = { VirtualTable.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
className: PropTypes.string.isRequired, className: PropTypes.string.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
scrollTop: PropTypes.number.isRequired,
scrollIndex: PropTypes.number, scrollIndex: PropTypes.number,
contentBody: PropTypes.object.isRequired, scroller: PropTypes.instanceOf(Element).isRequired,
isSmallScreen: PropTypes.bool.isRequired,
header: PropTypes.node.isRequired, header: PropTypes.node.isRequired,
headerHeight: PropTypes.number.isRequired, headerHeight: PropTypes.number.isRequired,
rowRenderer: PropTypes.func.isRequired, rowRenderer: PropTypes.func.isRequired
onRender: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
}; };
VirtualTable.defaultProps = { VirtualTable.defaultProps = {
className: styles.tableContainer, className: styles.tableContainer,
headerHeight: 38, headerHeight: 38
onRender: () => {}
}; };
export default VirtualTable; export default VirtualTable;

@ -1,3 +0,0 @@
.tableBodyContainer {
position: relative;
}

@ -1,40 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Grid } from 'react-virtualized';
import styles from './VirtualTableBody.css';
class VirtualTableBody extends Component {
//
// Render
render() {
return (
<Grid
{...this.props}
style={{
boxSizing: undefined,
direction: undefined,
height: undefined,
position: undefined,
willChange: undefined,
overflow: undefined,
width: undefined
}}
containerStyle={{
position: undefined
}}
/>
);
}
}
VirtualTableBody.propTypes = {
className: PropTypes.string.isRequired
};
VirtualTableBody.defaultProps = {
className: styles.tableBodyContainer
};
export default VirtualTableBody;

@ -3,6 +3,7 @@ import React, { Component } from 'react';
import { align, icons, sortDirections } from 'Helpers/Props'; import { align, icons, sortDirections } from 'Helpers/Props';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import VirtualTable from 'Components/Table/VirtualTable'; import VirtualTable from 'Components/Table/VirtualTable';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import PageContent from 'Components/Page/PageContent'; import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
@ -21,16 +22,15 @@ class UnmappedFilesTable extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
contentBody: null, scroller: null
scrollTop: 0
}; };
} }
// //
// Control // Control
setContentBodyRef = (ref) => { setScrollerRef = (ref) => {
this.setState({ contentBody: ref }); this.setState({ scroller: ref });
} }
rowRenderer = ({ key, rowIndex, style }) => { rowRenderer = ({ key, rowIndex, style }) => {
@ -43,23 +43,20 @@ class UnmappedFilesTable extends Component {
const item = items[rowIndex]; const item = items[rowIndex];
return ( return (
<UnmappedFilesTableRow <VirtualTableRow
style={style}
key={key} key={key}
columns={columns} style={style}
deleteUnmappedFile={deleteUnmappedFile} >
{...item} <UnmappedFilesTableRow
/> key={item.id}
columns={columns}
deleteUnmappedFile={deleteUnmappedFile}
{...item}
/>
</VirtualTableRow>
); );
} }
//
// Listeners
onScroll = ({ scrollTop }) => {
this.setState({ scrollTop });
}
render() { render() {
const { const {
@ -77,8 +74,7 @@ class UnmappedFilesTable extends Component {
} = this.props; } = this.props;
const { const {
scrollTop, scroller
contentBody
} = this.state; } = this.state;
return ( return (
@ -100,8 +96,7 @@ class UnmappedFilesTable extends Component {
</PageToolbar> </PageToolbar>
<PageContentBodyConnector <PageContentBodyConnector
ref={this.setContentBodyRef} registerScroller={this.setScrollerRef}
onScroll={this.onScroll}
> >
{ {
isFetching && !isPopulated && isFetching && !isPopulated &&
@ -116,14 +111,12 @@ class UnmappedFilesTable extends Component {
} }
{ {
isPopulated && !error && !!items.length && contentBody && isPopulated && !error && !!items.length && scroller &&
<VirtualTable <VirtualTable
items={items} items={items}
columns={columns} columns={columns}
contentBody={contentBody} scroller={scroller}
isSmallScreen={false} isSmallScreen={false}
scrollTop={scrollTop}
onScroll={this.onScroll}
overscanRowCount={10} overscanRowCount={10}
rowRenderer={this.rowRenderer} rowRenderer={this.rowRenderer}
header={ header={

@ -5,7 +5,6 @@ import formatBytes from 'Utilities/Number/formatBytes';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import ConfirmModal from 'Components/Modal/ConfirmModal'; import ConfirmModal from 'Components/Modal/ConfirmModal';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell'; import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import TrackQuality from 'Album/TrackQuality'; import TrackQuality from 'Album/TrackQuality';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
@ -64,7 +63,6 @@ class UnmappedFilesTableRow extends Component {
render() { render() {
const { const {
style,
id, id,
path, path,
size, size,
@ -82,9 +80,7 @@ class UnmappedFilesTableRow extends Component {
} = this.state; } = this.state;
return ( return (
<VirtualTableRow <>
style={style}
>
{ {
columns.map((column) => { columns.map((column) => {
const { const {
@ -198,14 +194,13 @@ class UnmappedFilesTableRow extends Component {
onCancel={this.onConfirmDeleteModalClose} onCancel={this.onConfirmDeleteModalClose}
/> />
</VirtualTableRow> </>
); );
} }
} }
UnmappedFilesTableRow.propTypes = { UnmappedFilesTableRow.propTypes = {
style: PropTypes.object.isRequired,
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
size: PropTypes.number.isRequired, size: PropTypes.number.isRequired,

Loading…
Cancel
Save