New: Improved Series list performance
This commit is contained in:
parent
466d4fba9e
commit
5a79b8502e
|
@ -21,17 +21,15 @@ class ImportSeries extends Component {
|
|||
allSelected: false,
|
||||
allUnselected: false,
|
||||
lastToggled: null,
|
||||
selectedState: {},
|
||||
contentBody: null,
|
||||
scrollTop: 0
|
||||
selectedState: {}
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
setContentBodyRef = (ref) => {
|
||||
this.setState({ contentBody: ref });
|
||||
setScrollerRef = (ref) => {
|
||||
this.setState({ scroller: ref });
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -94,13 +92,13 @@ class ImportSeries extends Component {
|
|||
allSelected,
|
||||
allUnselected,
|
||||
selectedState,
|
||||
contentBody
|
||||
scroller
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<PageContent title="Import Series">
|
||||
<PageContentBodyConnector
|
||||
ref={this.setContentBodyRef}
|
||||
registerScroller={this.setScrollerRef}
|
||||
onScroll={this.onScroll}
|
||||
>
|
||||
{
|
||||
|
@ -121,20 +119,18 @@ class ImportSeries extends Component {
|
|||
}
|
||||
|
||||
{
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && contentBody &&
|
||||
!rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && scroller &&
|
||||
<ImportSeriesTableConnector
|
||||
rootFolderId={rootFolderId}
|
||||
unmappedFolders={unmappedFolders}
|
||||
allSelected={allSelected}
|
||||
allUnselected={allUnselected}
|
||||
selectedState={selectedState}
|
||||
contentBody={contentBody}
|
||||
scroller={scroller}
|
||||
showLanguageProfile={showLanguageProfile}
|
||||
scrollTop={this.state.scrollTop}
|
||||
onSelectAllChange={this.onSelectAllChange}
|
||||
onSelectedChange={this.onSelectedChange}
|
||||
onRemoveSelectedStateItem={this.onRemoveSelectedStateItem}
|
||||
onScroll={this.onScroll}
|
||||
/>
|
||||
}
|
||||
</PageContentBodyConnector>
|
||||
|
|
|
@ -2,7 +2,6 @@ import PropTypes from 'prop-types';
|
|||
import React from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
|
||||
import VirtualTableSelectCell from 'Components/Table/Cells/VirtualTableSelectCell';
|
||||
import ImportSeriesSelectSeriesConnector from './SelectSeries/ImportSeriesSelectSeriesConnector';
|
||||
|
@ -10,7 +9,6 @@ import styles from './ImportSeriesRow.css';
|
|||
|
||||
function ImportSeriesRow(props) {
|
||||
const {
|
||||
style,
|
||||
id,
|
||||
monitor,
|
||||
qualityProfileId,
|
||||
|
@ -26,7 +24,7 @@ function ImportSeriesRow(props) {
|
|||
} = props;
|
||||
|
||||
return (
|
||||
<VirtualTableRow style={style}>
|
||||
<>
|
||||
<VirtualTableSelectCell
|
||||
inputClassName={styles.selectInput}
|
||||
id={id}
|
||||
|
@ -93,12 +91,11 @@ function ImportSeriesRow(props) {
|
|||
onInputChange={onInputChange}
|
||||
/>
|
||||
</VirtualTableRowCell>
|
||||
</VirtualTableRow>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
ImportSeriesRow.propTypes = {
|
||||
style: PropTypes.object.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
monitor: PropTypes.string.isRequired,
|
||||
qualityProfileId: PropTypes.number.isRequired,
|
||||
|
|
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import VirtualTable from 'Components/Table/VirtualTable';
|
||||
import VirtualTableRow from 'Components/Table/VirtualTableRow';
|
||||
import ImportSeriesHeader from './ImportSeriesHeader';
|
||||
import ImportSeriesRowConnector from './ImportSeriesRowConnector';
|
||||
|
||||
|
@ -112,15 +113,19 @@ class ImportSeriesTable extends Component {
|
|||
const item = items[rowIndex];
|
||||
|
||||
return (
|
||||
<ImportSeriesRowConnector
|
||||
<VirtualTableRow
|
||||
key={key}
|
||||
style={style}
|
||||
rootFolderId={rootFolderId}
|
||||
showLanguageProfile={showLanguageProfile}
|
||||
isSelected={selectedState[item.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
id={item.id}
|
||||
/>
|
||||
>
|
||||
<ImportSeriesRowConnector
|
||||
key={item.id}
|
||||
rootFolderId={rootFolderId}
|
||||
showLanguageProfile={showLanguageProfile}
|
||||
isSelected={selectedState[item.id]}
|
||||
onSelectedChange={onSelectedChange}
|
||||
id={item.id}
|
||||
/>
|
||||
</VirtualTableRow>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -133,12 +138,10 @@ class ImportSeriesTable extends Component {
|
|||
allSelected,
|
||||
allUnselected,
|
||||
isSmallScreen,
|
||||
contentBody,
|
||||
scroller,
|
||||
showLanguageProfile,
|
||||
scrollTop,
|
||||
selectedState,
|
||||
onSelectAllChange,
|
||||
onScroll
|
||||
onSelectAllChange
|
||||
} = this.props;
|
||||
|
||||
if (!items.length) {
|
||||
|
@ -148,10 +151,9 @@ class ImportSeriesTable extends Component {
|
|||
return (
|
||||
<VirtualTable
|
||||
items={items}
|
||||
contentBody={contentBody}
|
||||
scroller={scroller}
|
||||
isSmallScreen={isSmallScreen}
|
||||
rowHeight={52}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
rowRenderer={this.rowRenderer}
|
||||
header={
|
||||
|
@ -163,7 +165,6 @@ class ImportSeriesTable extends Component {
|
|||
/>
|
||||
}
|
||||
selectedState={selectedState}
|
||||
onScroll={onScroll}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -183,15 +184,13 @@ ImportSeriesTable.propTypes = {
|
|||
selectedState: PropTypes.object.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
allSeries: PropTypes.arrayOf(PropTypes.object),
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showLanguageProfile: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
onSelectAllChange: PropTypes.func.isRequired,
|
||||
onSelectedChange: PropTypes.func.isRequired,
|
||||
onRemoveSelectedStateItem: PropTypes.func.isRequired,
|
||||
onSeriesLookup: PropTypes.func.isRequired,
|
||||
onSetImportSeriesValue: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
onSetImportSeriesValue: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ImportSeriesTable;
|
||||
|
|
|
@ -37,6 +37,10 @@ class OverlayScroller extends Component {
|
|||
|
||||
_setScrollRef = (ref) => {
|
||||
this._scroller = ref;
|
||||
|
||||
if (ref) {
|
||||
this.props.registerScroller(ref.view);
|
||||
}
|
||||
}
|
||||
|
||||
_renderThumb = (props) => {
|
||||
|
@ -157,7 +161,8 @@ OverlayScroller.propTypes = {
|
|||
autoHide: PropTypes.bool.isRequired,
|
||||
autoScroll: PropTypes.bool.isRequired,
|
||||
children: PropTypes.node,
|
||||
onScroll: PropTypes.func
|
||||
onScroll: PropTypes.func,
|
||||
registerScroller: PropTypes.func
|
||||
};
|
||||
|
||||
OverlayScroller.defaultProps = {
|
||||
|
@ -165,7 +170,8 @@ OverlayScroller.defaultProps = {
|
|||
trackClassName: styles.thumb,
|
||||
scrollDirection: scrollDirections.VERTICAL,
|
||||
autoHide: false,
|
||||
autoScroll: true
|
||||
autoScroll: true,
|
||||
registerScroller: () => {}
|
||||
};
|
||||
|
||||
export default OverlayScroller;
|
||||
|
|
|
@ -30,6 +30,8 @@ class Scroller extends Component {
|
|||
|
||||
_setScrollerRef = (ref) => {
|
||||
this._scroller = ref;
|
||||
|
||||
this.props.registerScroller(ref);
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -43,6 +45,7 @@ class Scroller extends Component {
|
|||
children,
|
||||
scrollTop,
|
||||
onScroll,
|
||||
registerScroller,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
|
@ -70,12 +73,14 @@ Scroller.propTypes = {
|
|||
autoScroll: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number,
|
||||
children: PropTypes.node,
|
||||
onScroll: PropTypes.func
|
||||
onScroll: PropTypes.func,
|
||||
registerScroller: PropTypes.func
|
||||
};
|
||||
|
||||
Scroller.defaultProps = {
|
||||
scrollDirection: scrollDirections.VERTICAL,
|
||||
autoScroll: true
|
||||
autoScroll: true,
|
||||
registerScroller: () => {}
|
||||
};
|
||||
|
||||
export default Scroller;
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
.tableContainer {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.tableBodyContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
import PropTypes from 'prop-types';
|
||||
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 Measure from 'Components/Measure';
|
||||
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';
|
||||
|
||||
const ROW_HEIGHT = 38;
|
||||
|
@ -44,28 +42,37 @@ class VirtualTable extends Component {
|
|||
width: 0
|
||||
};
|
||||
|
||||
this._isInitialized = false;
|
||||
this._grid = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
|
||||
}
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
items,
|
||||
scrollIndex
|
||||
} = this.props;
|
||||
|
||||
componentDidUpdate(prevProps, preState) {
|
||||
const { scrollIndex, rowHeight } = this.props;
|
||||
const {
|
||||
width
|
||||
} = this.state;
|
||||
|
||||
if (this._grid && (prevState.width !== width || hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) {
|
||||
const scrollTop = (scrollIndex + 1) * rowHeight + 20;
|
||||
|
||||
this.props.onScroll({ scrollTop });
|
||||
this._grid.scrollToCell({
|
||||
rowIndex: scrollIndex,
|
||||
columnIndex: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
rowGetter = ({ index }) => {
|
||||
return this.props.items[index];
|
||||
setGridRef = (ref) => {
|
||||
this._grid = ref;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -77,36 +84,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() {
|
||||
const {
|
||||
isSmallScreen,
|
||||
className,
|
||||
items,
|
||||
isSmallScreen,
|
||||
scroller,
|
||||
header,
|
||||
headerHeight,
|
||||
scrollTop,
|
||||
rowRenderer,
|
||||
onScroll,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
|
@ -114,66 +103,89 @@ class VirtualTable extends Component {
|
|||
width
|
||||
} = this.state;
|
||||
|
||||
const gridStyle = {
|
||||
boxSizing: undefined,
|
||||
direction: undefined,
|
||||
height: undefined,
|
||||
position: undefined,
|
||||
willChange: undefined,
|
||||
overflow: undefined,
|
||||
width: undefined
|
||||
};
|
||||
|
||||
const containerStyle = {
|
||||
position: undefined
|
||||
};
|
||||
|
||||
return (
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
<WindowScroller
|
||||
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
|
||||
onScroll={this.onScroll}
|
||||
>
|
||||
{({ height, isScrolling }) => {
|
||||
return (
|
||||
<WindowScroller
|
||||
scrollElement={isSmallScreen ? undefined : scroller}
|
||||
>
|
||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||
if (!height) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<Measure
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
<Scroller
|
||||
className={className}
|
||||
scrollDirection={scrollDirections.HORIZONTAL}
|
||||
>
|
||||
{header}
|
||||
|
||||
<VirtualTableBody
|
||||
autoContainerWidth={true}
|
||||
width={width}
|
||||
height={height}
|
||||
headerHeight={height - headerHeight}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
rowCount={items.length}
|
||||
columnCount={1}
|
||||
scrollTop={scrollTop}
|
||||
autoHeight={true}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={rowRenderer}
|
||||
columnWidth={width}
|
||||
overscanIndicesGetter={overscanIndicesGetter}
|
||||
onSectionRendered={this.onSectionRendered}
|
||||
{...otherProps}
|
||||
/>
|
||||
<div ref={registerChild}>
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
autoContainerWidth={true}
|
||||
autoHeight={true}
|
||||
autoWidth={true}
|
||||
width={width}
|
||||
height={height}
|
||||
headerHeight={height - headerHeight}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
rowCount={items.length}
|
||||
columnCount={1}
|
||||
columnWidth={width}
|
||||
scrollTop={scrollTop}
|
||||
onScroll={onChildScroll}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={rowRenderer}
|
||||
overscanIndicesGetter={overscanIndicesGetter}
|
||||
scrollToAlignment={'start'}
|
||||
isScrollingOptout={true}
|
||||
className={styles.tableBodyContainer}
|
||||
style={gridStyle}
|
||||
containerStyle={containerStyle}
|
||||
{...otherProps}
|
||||
/>
|
||||
</div>
|
||||
</Scroller>
|
||||
);
|
||||
}
|
||||
}
|
||||
</WindowScroller>
|
||||
</Measure>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
</WindowScroller>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
VirtualTable.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
className: PropTypes.string.isRequired,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
scrollIndex: PropTypes.number,
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
header: PropTypes.node.isRequired,
|
||||
headerHeight: PropTypes.number.isRequired,
|
||||
rowRenderer: PropTypes.func.isRequired,
|
||||
rowHeight: PropTypes.number.isRequired,
|
||||
onRender: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
rowHeight: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
VirtualTable.defaultProps = {
|
||||
className: styles.tableContainer,
|
||||
headerHeight: 38,
|
||||
onRender: () => {}
|
||||
headerHeight: 38
|
||||
};
|
||||
|
||||
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;
|
|
@ -1,12 +1,9 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Grid, WindowScroller } from 'react-virtualized';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import Measure from 'Components/Measure';
|
||||
import SeriesIndexItemConnector from 'Series/Index/SeriesIndexItemConnector';
|
||||
import SeriesIndexOverview from './SeriesIndexOverview';
|
||||
|
@ -66,56 +63,44 @@ class SeriesIndexOverviews extends Component {
|
|||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
};
|
||||
|
||||
this._isInitialized = false;
|
||||
this._grid = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
items,
|
||||
filters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
overviewOptions,
|
||||
jumpToCharacter
|
||||
} = this.props;
|
||||
|
||||
const itemsChanged = hasDifferentItemsOrOrder(prevProps.items, items);
|
||||
const overviewOptionsChanged = !_.isMatch(prevProps.overviewOptions, overviewOptions);
|
||||
const {
|
||||
width,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
if (
|
||||
prevProps.sortKey !== sortKey ||
|
||||
prevProps.overviewOptions !== overviewOptions ||
|
||||
itemsChanged
|
||||
) {
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
prevProps.overviewOptions !== overviewOptions) {
|
||||
this.calculateGrid();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.filters !== filters ||
|
||||
prevProps.sortKey !== sortKey ||
|
||||
prevProps.sortDirection !== sortDirection ||
|
||||
itemsChanged ||
|
||||
overviewOptionsChanged
|
||||
) {
|
||||
if (this._grid &&
|
||||
(prevState.width !== width ||
|
||||
prevState.rowHeight !== rowHeight ||
|
||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
if (index != null) {
|
||||
const {
|
||||
rowHeight
|
||||
} = this.state;
|
||||
if (this._grid && index != null) {
|
||||
|
||||
const scrollTop = rowHeight * index;
|
||||
|
||||
this.props.onScroll({ scrollTop });
|
||||
this._grid.scrollToCell({
|
||||
rowIndex: index,
|
||||
columnIndex: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -123,21 +108,6 @@ class SeriesIndexOverviews extends Component {
|
|||
//
|
||||
// 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) => {
|
||||
this._grid = ref;
|
||||
}
|
||||
|
@ -219,22 +189,14 @@ class SeriesIndexOverviews extends Component {
|
|||
this.calculateGrid(width, this.props.isSmallScreen);
|
||||
}
|
||||
|
||||
onSectionRendered = () => {
|
||||
if (!this._isInitialized && this._contentBodyNode) {
|
||||
this.props.onRender();
|
||||
this._isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
scroller,
|
||||
items,
|
||||
scrollTop,
|
||||
isSmallScreen,
|
||||
onScroll
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
|
@ -243,29 +205,38 @@ class SeriesIndexOverviews extends Component {
|
|||
} = this.state;
|
||||
|
||||
return (
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
<Measure
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
<WindowScroller
|
||||
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
|
||||
onScroll={onScroll}
|
||||
scrollElement={isSmallScreen ? undefined : scroller}
|
||||
>
|
||||
{({ height, isScrolling }) => {
|
||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||
if (!height) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
className={styles.grid}
|
||||
autoHeight={true}
|
||||
height={height}
|
||||
columnCount={1}
|
||||
columnWidth={width}
|
||||
rowCount={items.length}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
onSectionRendered={this.onSectionRendered}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
<div ref={registerChild}>
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
className={styles.grid}
|
||||
autoHeight={true}
|
||||
height={height}
|
||||
columnCount={1}
|
||||
columnWidth={width}
|
||||
rowCount={items.length}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
onScroll={onChildScroll}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
scrollToAlignment={'start'}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -277,20 +248,15 @@ class SeriesIndexOverviews extends Component {
|
|||
|
||||
SeriesIndexOverviews.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
overviewOptions: PropTypes.object.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
longDateFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onRender: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default SeriesIndexOverviews;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { Grid, WindowScroller } from 'react-virtualized';
|
||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import { sortDirections } from 'Helpers/Props';
|
||||
import Measure from 'Components/Measure';
|
||||
import SeriesIndexItemConnector from 'Series/Index/SeriesIndexItemConnector';
|
||||
import SeriesIndexPoster from './SeriesIndexPoster';
|
||||
|
@ -110,52 +108,46 @@ class SeriesIndexPosters extends Component {
|
|||
this._grid = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody);
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
items,
|
||||
filters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
posterOptions,
|
||||
jumpToCharacter
|
||||
} = this.props;
|
||||
|
||||
const itemsChanged = hasDifferentItemsOrOrder(prevProps.items, items);
|
||||
const {
|
||||
width,
|
||||
columnWidth,
|
||||
columnCount,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
if (
|
||||
prevProps.sortKey !== sortKey ||
|
||||
prevProps.posterOptions !== posterOptions ||
|
||||
itemsChanged
|
||||
) {
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
prevProps.posterOptions !== posterOptions) {
|
||||
this.calculateGrid();
|
||||
}
|
||||
|
||||
if (
|
||||
prevProps.filters !== filters ||
|
||||
prevProps.sortKey !== sortKey ||
|
||||
prevProps.sortDirection !== sortDirection ||
|
||||
itemsChanged
|
||||
) {
|
||||
if (this._grid &&
|
||||
(prevState.width !== width ||
|
||||
prevState.columnWidth !== columnWidth ||
|
||||
prevState.columnCount !== columnCount ||
|
||||
prevState.rowHeight !== rowHeight ||
|
||||
hasDifferentItemsOrOrder(prevProps.items, items))) {
|
||||
// recomputeGridSize also forces Grid to discard its cache of rendered cells
|
||||
this._grid.recomputeGridSize();
|
||||
}
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
if (index != null) {
|
||||
const {
|
||||
columnCount,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
if (this._grid && index != null) {
|
||||
const row = Math.floor(index / columnCount);
|
||||
const scrollTop = rowHeight * row;
|
||||
|
||||
this.props.onScroll({ scrollTop });
|
||||
this._grid.scrollToCell({
|
||||
rowIndex: row,
|
||||
columnIndex: 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -254,22 +246,14 @@ class SeriesIndexPosters extends Component {
|
|||
this.calculateGrid(width, this.props.isSmallScreen);
|
||||
}
|
||||
|
||||
onSectionRendered = () => {
|
||||
if (!this._isInitialized && this._contentBodyNode) {
|
||||
this.props.onRender();
|
||||
this._isInitialized = true;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
scroller,
|
||||
items,
|
||||
scrollTop,
|
||||
isSmallScreen,
|
||||
onScroll
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
|
@ -282,29 +266,38 @@ class SeriesIndexPosters extends Component {
|
|||
const rowCount = Math.ceil(items.length / columnCount);
|
||||
|
||||
return (
|
||||
<Measure onMeasure={this.onMeasure}>
|
||||
<Measure
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
<WindowScroller
|
||||
scrollElement={isSmallScreen ? undefined : this._contentBodyNode}
|
||||
onScroll={onScroll}
|
||||
scrollElement={isSmallScreen ? undefined : scroller}
|
||||
>
|
||||
{({ height, isScrolling }) => {
|
||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||
if (!height) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
className={styles.grid}
|
||||
autoHeight={true}
|
||||
height={height}
|
||||
columnCount={columnCount}
|
||||
columnWidth={columnWidth}
|
||||
rowCount={rowCount}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
onSectionRendered={this.onSectionRendered}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
<div ref={registerChild}>
|
||||
<Grid
|
||||
ref={this.setGridRef}
|
||||
className={styles.grid}
|
||||
autoHeight={true}
|
||||
height={height}
|
||||
columnCount={columnCount}
|
||||
columnWidth={columnWidth}
|
||||
rowCount={rowCount}
|
||||
rowHeight={rowHeight}
|
||||
width={width}
|
||||
onScroll={onChildScroll}
|
||||
scrollTop={scrollTop}
|
||||
overscanRowCount={2}
|
||||
cellRenderer={this.cellRenderer}
|
||||
scrollToAlignment={'start'}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -316,19 +309,14 @@ class SeriesIndexPosters extends Component {
|
|||
|
||||
SeriesIndexPosters.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
posterOptions: PropTypes.object.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired,
|
||||
onRender: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default SeriesIndexPosters;
|
||||
|
|
|
@ -46,12 +46,11 @@ class SeriesIndex extends Component {
|
|||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
contentBody: null,
|
||||
scroller: null,
|
||||
jumpBarItems: { order: [] },
|
||||
jumpToCharacter: null,
|
||||
isPosterOptionsModalOpen: false,
|
||||
isOverviewOptionsModalOpen: false,
|
||||
isRendered: false
|
||||
isOverviewOptionsModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -63,8 +62,7 @@ class SeriesIndex extends Component {
|
|||
const {
|
||||
items,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
scrollTop
|
||||
sortDirection
|
||||
} = this.props;
|
||||
|
||||
if (sortKey !== prevProps.sortKey ||
|
||||
|
@ -74,7 +72,7 @@ class SeriesIndex extends Component {
|
|||
this.setJumpBarItems();
|
||||
}
|
||||
|
||||
if (this.state.jumpToCharacter != null && scrollTop !== prevProps.scrollTop) {
|
||||
if (this.state.jumpToCharacter != null) {
|
||||
this.setState({ jumpToCharacter: null });
|
||||
}
|
||||
}
|
||||
|
@ -82,8 +80,8 @@ class SeriesIndex extends Component {
|
|||
//
|
||||
// Control
|
||||
|
||||
setContentBodyRef = (ref) => {
|
||||
this.setState({ contentBody: ref });
|
||||
setScrollerRef = (ref) => {
|
||||
this.setState({ scroller: ref });
|
||||
}
|
||||
|
||||
setJumpBarItems() {
|
||||
|
@ -153,27 +151,6 @@ class SeriesIndex extends Component {
|
|||
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
|
||||
|
||||
|
@ -193,7 +170,7 @@ class SeriesIndex extends Component {
|
|||
view,
|
||||
isRefreshingSeries,
|
||||
isRssSyncExecuting,
|
||||
scrollTop,
|
||||
onScroll,
|
||||
onSortSelect,
|
||||
onFilterSelect,
|
||||
onViewSelect,
|
||||
|
@ -203,16 +180,15 @@ class SeriesIndex extends Component {
|
|||
} = this.props;
|
||||
|
||||
const {
|
||||
contentBody,
|
||||
scroller,
|
||||
jumpBarItems,
|
||||
jumpToCharacter,
|
||||
isPosterOptionsModalOpen,
|
||||
isOverviewOptionsModalOpen,
|
||||
isRendered
|
||||
isOverviewOptionsModalOpen
|
||||
} = this.state;
|
||||
|
||||
const ViewComponent = getViewComponent(view);
|
||||
const isLoaded = !!(!error && isPopulated && items.length && contentBody);
|
||||
const isLoaded = !!(!error && isPopulated && items.length && scroller);
|
||||
const hasNoSeries = !totalItems;
|
||||
|
||||
return (
|
||||
|
@ -309,11 +285,10 @@ class SeriesIndex extends Component {
|
|||
|
||||
<div className={styles.pageContentBodyWrapper}>
|
||||
<PageContentBodyConnector
|
||||
ref={this.setContentBodyRef}
|
||||
registerScroller={this.setScrollerRef}
|
||||
className={styles.contentBody}
|
||||
innerClassName={styles[`${view}InnerContentBody`]}
|
||||
scrollTop={isRendered ? scrollTop : 0}
|
||||
onScroll={this.onScroll}
|
||||
onScroll={onScroll}
|
||||
>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
|
@ -329,14 +304,12 @@ class SeriesIndex extends Component {
|
|||
isLoaded &&
|
||||
<div className={styles.contentBodyContainer}>
|
||||
<ViewComponent
|
||||
contentBody={contentBody}
|
||||
scroller={scroller}
|
||||
items={items}
|
||||
filters={filters}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
scrollTop={scrollTop}
|
||||
jumpToCharacter={jumpToCharacter}
|
||||
onRender={this.onRender}
|
||||
{...otherProps}
|
||||
/>
|
||||
|
||||
|
@ -388,7 +361,6 @@ SeriesIndex.propTypes = {
|
|||
view: PropTypes.string.isRequired,
|
||||
isRefreshingSeries: PropTypes.bool.isRequired,
|
||||
isRssSyncExecuting: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onSortSelect: PropTypes.func.isRequired,
|
||||
onFilterSelect: PropTypes.func.isRequired,
|
||||
|
|
|
@ -3,7 +3,6 @@ import React, { Component } from 'react';
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSeriesClientSideCollectionItemsSelector from 'Store/Selectors/createSeriesClientSideCollectionItemsSelector';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import scrollPositions from 'Store/scrollPositions';
|
||||
|
@ -13,29 +12,6 @@ import * as commandNames from 'Commands/commandNames';
|
|||
import withScrollPosition from 'Components/withScrollPosition';
|
||||
import SeriesIndex from './SeriesIndex';
|
||||
|
||||
const POSTERS_PADDING = 15;
|
||||
const POSTERS_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;
|
||||
}
|
||||
|
||||
return scrollTop + padding;
|
||||
}
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createSeriesClientSideCollectionItemsSelector('seriesIndex'),
|
||||
|
@ -92,39 +68,15 @@ function createMapDispatchToProps(dispatch, props) {
|
|||
|
||||
class SeriesIndexConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
const {
|
||||
view,
|
||||
scrollTop,
|
||||
isSmallScreen
|
||||
} = props;
|
||||
|
||||
this.state = {
|
||||
scrollTop: getScrollTop(view, scrollTop, isSmallScreen)
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onViewSelect = (view) => {
|
||||
// Reset the scroll position before changing the view
|
||||
this.setState({ scrollTop: 0 }, () => {
|
||||
this.props.dispatchSetSeriesView(view);
|
||||
});
|
||||
this.props.dispatchSetSeriesView(view);
|
||||
}
|
||||
|
||||
onScroll = ({ scrollTop }) => {
|
||||
this.setState({
|
||||
scrollTop
|
||||
}, () => {
|
||||
scrollPositions.seriesIndex = scrollTop;
|
||||
});
|
||||
scrollPositions.seriesIndex = scrollTop;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -134,7 +86,6 @@ class SeriesIndexConnector extends Component {
|
|||
return (
|
||||
<SeriesIndex
|
||||
{...this.props}
|
||||
scrollTop={this.state.scrollTop}
|
||||
onViewSelect={this.onViewSelect}
|
||||
onScroll={this.onScroll}
|
||||
/>
|
||||
|
@ -145,7 +96,6 @@ class SeriesIndexConnector extends Component {
|
|||
SeriesIndexConnector.propTypes = {
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
view: PropTypes.string.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
dispatchSetSeriesView: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
|
|
|
@ -23,10 +23,12 @@ class SeriesIndexTable extends Component {
|
|||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const jumpToCharacter = this.props.jumpToCharacter;
|
||||
const {
|
||||
items,
|
||||
jumpToCharacter
|
||||
} = this.props;
|
||||
|
||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
||||
const items = this.props.items;
|
||||
|
||||
const scrollIndex = getIndexOfFirstCharacter(items, jumpToCharacter);
|
||||
|
||||
|
@ -75,25 +77,20 @@ class SeriesIndexTable extends Component {
|
|||
const {
|
||||
items,
|
||||
columns,
|
||||
filters,
|
||||
sortKey,
|
||||
sortDirection,
|
||||
showBanners,
|
||||
isSmallScreen,
|
||||
scrollTop,
|
||||
contentBody,
|
||||
onSortPress,
|
||||
onRender,
|
||||
onScroll
|
||||
scroller
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<VirtualTable
|
||||
className={styles.tableContainer}
|
||||
items={items}
|
||||
scrollTop={scrollTop}
|
||||
scrollIndex={this.state.scrollIndex}
|
||||
contentBody={contentBody}
|
||||
scroller={scroller}
|
||||
isSmallScreen={isSmallScreen}
|
||||
rowHeight={showBanners ? 70 : 38}
|
||||
overscanRowCount={2}
|
||||
|
@ -108,12 +105,6 @@ class SeriesIndexTable extends Component {
|
|||
/>
|
||||
}
|
||||
columns={columns}
|
||||
filters={filters}
|
||||
sortKey={sortKey}
|
||||
sortDirection={sortDirection}
|
||||
onRender={onRender}
|
||||
onScroll={onScroll}
|
||||
isScrollingOptOut={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -122,17 +113,13 @@ class SeriesIndexTable extends Component {
|
|||
SeriesIndexTable.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
sortDirection: PropTypes.oneOf(sortDirections.all),
|
||||
showBanners: PropTypes.bool.isRequired,
|
||||
scrollTop: PropTypes.number.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
contentBody: PropTypes.object.isRequired,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
onSortPress: PropTypes.func.isRequired,
|
||||
onRender: PropTypes.func.isRequired,
|
||||
onScroll: PropTypes.func.isRequired
|
||||
onSortPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default SeriesIndexTable;
|
||||
|
|
Loading…
Reference in New Issue