From 9902434057676e81695a7c1902977e3aa46aa0fc Mon Sep 17 00:00:00 2001 From: ta264 Date: Mon, 9 Dec 2019 22:02:02 +0000 Subject: [PATCH] Fixed: Rework Artist Index and VirtualTable --- .../ImportArtist/Import/ImportArtist.js | 21 +-- .../ImportArtist/Import/ImportArtistRow.js | 7 +- .../ImportArtist/Import/ImportArtistTable.js | 35 ++-- frontend/src/Artist/Index/ArtistIndex.js | 54 ++---- .../src/Artist/Index/ArtistIndexConnector.js | 60 +------ .../Index/Banners/ArtistIndexBanners.js | 86 ++++------ .../Index/Overview/ArtistIndexOverviews.js | 132 ++++++--------- .../Index/Posters/ArtistIndexPosters.js | 118 ++++++------- .../Artist/Index/Table/ArtistIndexTable.js | 27 +-- .../Components/Scroller/OverlayScroller.js | 10 +- frontend/src/Components/Scroller/Scroller.js | 9 +- .../src/Components/Table/VirtualTable.css | 4 + frontend/src/Components/Table/VirtualTable.js | 160 ++++++++++-------- .../src/Components/Table/VirtualTableBody.css | 3 - .../src/Components/Table/VirtualTableBody.js | 40 ----- .../src/UnmappedFiles/UnmappedFilesTable.js | 43 ++--- .../UnmappedFiles/UnmappedFilesTableRow.js | 9 +- 17 files changed, 315 insertions(+), 503 deletions(-) delete mode 100644 frontend/src/Components/Table/VirtualTableBody.css delete mode 100644 frontend/src/Components/Table/VirtualTableBody.js diff --git a/frontend/src/AddArtist/ImportArtist/Import/ImportArtist.js b/frontend/src/AddArtist/ImportArtist/Import/ImportArtist.js index 3ed2459d1..6c49a8fce 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/ImportArtist.js +++ b/frontend/src/AddArtist/ImportArtist/Import/ImportArtist.js @@ -22,16 +22,15 @@ class ImportArtist extends Component { allUnselected: false, lastToggled: null, selectedState: {}, - contentBody: null, - scrollTop: 0 + scroller: null }; } // // Control - setContentBodyRef = (ref) => { - this.setState({ contentBody: ref }); + setScrollerRef = (ref) => { + this.setState({ scroller: ref }); } // @@ -72,10 +71,6 @@ class ImportArtist extends Component { this.props.onImportPress(this.getSelectedIds()); } - onScroll = ({ scrollTop }) => { - this.setState({ scrollTop }); - } - // // Render @@ -94,13 +89,13 @@ class ImportArtist extends Component { allSelected, allUnselected, selectedState, - contentBody + scroller } = this.state; return ( { @@ -121,20 +116,18 @@ class ImportArtist extends Component { } { - !rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && contentBody && + !rootFoldersError && rootFoldersPopulated && !!unmappedFolders.length && scroller && } diff --git a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRow.js b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRow.js index ca3f32132..338cab5fe 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRow.js +++ b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistRow.js @@ -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 ImportArtistSelectArtistConnector from './SelectArtist/ImportArtistSelectArtistConnector'; @@ -10,7 +9,6 @@ import styles from './ImportArtistRow.css'; function ImportArtistRow(props) { const { - style, id, monitor, qualityProfileId, @@ -25,7 +23,7 @@ function ImportArtistRow(props) { } = props; return ( - + <> - + ); } ImportArtistRow.propTypes = { - style: PropTypes.object.isRequired, id: PropTypes.string.isRequired, monitor: PropTypes.string.isRequired, qualityProfileId: PropTypes.number.isRequired, diff --git a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistTable.js b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistTable.js index f2c5f92eb..4b31b1e0e 100644 --- a/frontend/src/AddArtist/ImportArtist/Import/ImportArtistTable.js +++ b/frontend/src/AddArtist/ImportArtist/Import/ImportArtistTable.js @@ -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 ImportArtistHeader from './ImportArtistHeader'; import ImportArtistRowConnector from './ImportArtistRowConnector'; @@ -110,15 +111,20 @@ class ImportArtistTable extends Component { const item = items[rowIndex]; return ( - + > + + ); } @@ -131,12 +137,10 @@ class ImportArtistTable extends Component { allSelected, allUnselected, isSmallScreen, - contentBody, showMetadataProfile, - scrollTop, + scroller, selectedState, - onSelectAllChange, - onScroll + onSelectAllChange } = this.props; if (!items.length) { @@ -146,10 +150,9 @@ class ImportArtistTable extends Component { return ( } selectedState={selectedState} - onScroll={onScroll} /> ); } @@ -180,15 +182,14 @@ ImportArtistTable.propTypes = { selectedState: PropTypes.object.isRequired, isSmallScreen: PropTypes.bool.isRequired, allArtists: PropTypes.arrayOf(PropTypes.object), - contentBody: PropTypes.object.isRequired, + scroller: PropTypes.instanceOf(Element).isRequired, showMetadataProfile: PropTypes.bool.isRequired, scrollTop: PropTypes.number.isRequired, onSelectAllChange: PropTypes.func.isRequired, onSelectedChange: PropTypes.func.isRequired, onRemoveSelectedStateItem: PropTypes.func.isRequired, onArtistLookup: PropTypes.func.isRequired, - onSetImportArtistValue: PropTypes.func.isRequired, - onScroll: PropTypes.func.isRequired + onSetImportArtistValue: PropTypes.func.isRequired }; export default ImportArtistTable; diff --git a/frontend/src/Artist/Index/ArtistIndex.js b/frontend/src/Artist/Index/ArtistIndex.js index e6159571e..1fa9388b0 100644 --- a/frontend/src/Artist/Index/ArtistIndex.js +++ b/frontend/src/Artist/Index/ArtistIndex.js @@ -53,13 +53,12 @@ class ArtistIndex extends Component { super(props, context); this.state = { - contentBody: null, + scroller: null, jumpBarItems: { order: [] }, jumpToCharacter: null, isPosterOptionsModalOpen: false, isBannerOptionsModalOpen: false, - isOverviewOptionsModalOpen: false, - isRendered: false + isOverviewOptionsModalOpen: false }; } @@ -71,8 +70,7 @@ class ArtistIndex extends Component { const { items, sortKey, - sortDirection, - scrollTop + sortDirection } = this.props; if (sortKey !== prevProps.sortKey || @@ -82,7 +80,7 @@ class ArtistIndex extends Component { this.setJumpBarItems(); } - if (this.state.jumpToCharacter != null && scrollTop !== prevProps.scrollTop) { + if (this.state.jumpToCharacter != null) { this.setState({ jumpToCharacter: null }); } } @@ -90,8 +88,8 @@ class ArtistIndex extends Component { // // Control - setContentBodyRef = (ref) => { - this.setState({ contentBody: ref }); + setScrollerRef = (ref) => { + this.setState({ scroller: ref }); } setJumpBarItems() { @@ -169,27 +167,6 @@ class ArtistIndex 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 @@ -209,7 +186,7 @@ class ArtistIndex extends Component { view, isRefreshingArtist, isRssSyncExecuting, - scrollTop, + onScroll, onSortSelect, onFilterSelect, onViewSelect, @@ -219,17 +196,16 @@ class ArtistIndex extends Component { } = this.props; const { - contentBody, + scroller, jumpBarItems, jumpToCharacter, isPosterOptionsModalOpen, isBannerOptionsModalOpen, - isOverviewOptionsModalOpen, - isRendered + isOverviewOptionsModalOpen } = this.state; const ViewComponent = getViewComponent(view); - const isLoaded = !!(!error && isPopulated && items.length && contentBody); + const isLoaded = !!(!error && isPopulated && items.length && scroller); const hasNoArtist = !totalItems; return ( @@ -338,11 +314,10 @@ class ArtistIndex extends Component {
{ isFetching && !isPopulated && @@ -360,14 +335,12 @@ class ArtistIndex extends Component { isLoaded &&
@@ -426,7 +399,6 @@ ArtistIndex.propTypes = { view: PropTypes.string.isRequired, isRefreshingArtist: PropTypes.bool.isRequired, isRssSyncExecuting: PropTypes.bool.isRequired, - scrollTop: PropTypes.number.isRequired, isSmallScreen: PropTypes.bool.isRequired, onSortSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired, diff --git a/frontend/src/Artist/Index/ArtistIndexConnector.js b/frontend/src/Artist/Index/ArtistIndexConnector.js index a9e0b7dcc..6696c917f 100644 --- a/frontend/src/Artist/Index/ArtistIndexConnector.js +++ b/frontend/src/Artist/Index/ArtistIndexConnector.js @@ -4,7 +4,6 @@ import React, { Component } from 'react'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector'; -import dimensions from 'Styles/Variables/dimensions'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import scrollPositions from 'Store/scrollPositions'; @@ -14,35 +13,6 @@ import * as commandNames from 'Commands/commandNames'; import withScrollPosition from 'Components/withScrollPosition'; 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() { return createSelector( createArtistClientSideCollectionItemsSelector('artistIndex'), @@ -99,39 +69,15 @@ function createMapDispatchToProps(dispatch, props) { class ArtistIndexConnector 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.dispatchSetArtistView(view); - }); + this.props.dispatchSetArtistView(view); } onScroll = ({ scrollTop }) => { - this.setState({ - scrollTop - }, () => { - scrollPositions.artistIndex = scrollTop; - }); + scrollPositions.artistIndex = scrollTop; } // @@ -141,7 +87,6 @@ class ArtistIndexConnector extends Component { return ( @@ -152,7 +97,6 @@ class ArtistIndexConnector extends Component { ArtistIndexConnector.propTypes = { isSmallScreen: PropTypes.bool.isRequired, view: PropTypes.string.isRequired, - scrollTop: PropTypes.number.isRequired, dispatchSetArtistView: PropTypes.func.isRequired }; diff --git a/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js b/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js index fa5708022..b0c67a174 100644 --- a/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js +++ b/frontend/src/Artist/Index/Banners/ArtistIndexBanners.js @@ -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 ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexBanner from './ArtistIndexBanner'; @@ -109,52 +107,46 @@ class ArtistIndexBanners extends Component { this._grid = null; } - componentDidMount() { - this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody); - } - - componentDidUpdate(prevProps) { + componentDidUpdate(prevProps, prevState) { const { items, - filters, sortKey, - sortDirection, bannerOptions, jumpToCharacter } = this.props; - const itemsChanged = hasDifferentItemsOrOrder(prevProps.items, items); + const { + width, + columnWidth, + columnCount, + rowHeight + } = this.state; - if ( - prevProps.sortKey !== sortKey || - prevProps.bannerOptions !== bannerOptions || - itemsChanged - ) { + if (prevProps.sortKey !== sortKey || + prevProps.bannerOptions !== bannerOptions) { 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 + }); } } } @@ -251,22 +243,14 @@ class ArtistIndexBanners extends Component { this.calculateGrid(width, this.props.isSmallScreen); } - onSectionRendered = () => { - if (!this._isInitialized && this._contentBodyNode) { - this.props.onRender(); - this._isInitialized = true; - } - } - // // Render render() { const { items, - scrollTop, isSmallScreen, - onScroll + scroller } = this.props; const { @@ -279,12 +263,18 @@ class ArtistIndexBanners extends Component { const rowCount = Math.ceil(items.length / columnCount); return ( - + - {({ height, isScrolling }) => { + {({ height, registerChild, onChildScroll, scrollTop }) => { + if (!height) { + return
; + } + return ( ); @@ -313,19 +304,14 @@ class ArtistIndexBanners extends Component { ArtistIndexBanners.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, - filters: PropTypes.arrayOf(PropTypes.object).isRequired, sortKey: PropTypes.string, - sortDirection: PropTypes.oneOf(sortDirections.all), bannerOptions: 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 ArtistIndexBanners; diff --git a/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js index 868ba4f84..ccd23755f 100644 --- a/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js +++ b/frontend/src/Artist/Index/Overview/ArtistIndexOverviews.js @@ -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 ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexOverview from './ArtistIndexOverview'; @@ -66,56 +63,44 @@ class ArtistIndexOverviews 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 ArtistIndexOverviews 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; } @@ -217,22 +187,14 @@ class ArtistIndexOverviews extends Component { this.calculateGrid(width, this.props.isSmallScreen); } - onSectionRendered = () => { - if (!this._isInitialized && this._contentBodyNode) { - this.props.onRender(); - this._isInitialized = true; - } - } - // // Render render() { const { items, - scrollTop, isSmallScreen, - onScroll + scroller } = this.props; const { @@ -241,29 +203,39 @@ class ArtistIndexOverviews extends Component { } = this.state; return ( - + - {({ height, isScrolling }) => { + {({ height, registerChild, onChildScroll, scrollTop }) => { + if (!height) { + return
; + } + return ( - +
+ +
); } } @@ -275,20 +247,16 @@ class ArtistIndexOverviews extends Component { ArtistIndexOverviews.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 ArtistIndexOverviews; diff --git a/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js b/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js index efa0fa24a..1bada1b67 100644 --- a/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js +++ b/frontend/src/Artist/Index/Posters/ArtistIndexPosters.js @@ -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 ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexPoster from './ArtistIndexPoster'; @@ -109,52 +107,46 @@ class ArtistIndexPosters 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 + }); } } } @@ -252,22 +244,14 @@ class ArtistIndexPosters extends Component { this.calculateGrid(width, this.props.isSmallScreen); } - onSectionRendered = () => { - if (!this._isInitialized && this._contentBodyNode) { - this.props.onRender(); - this._isInitialized = true; - } - } - // // Render render() { const { items, - scrollTop, isSmallScreen, - onScroll + scroller } = this.props; const { @@ -280,29 +264,38 @@ class ArtistIndexPosters extends Component { const rowCount = Math.ceil(items.length / columnCount); return ( - + - {({ height, isScrolling }) => { + {({ height, registerChild, onChildScroll, scrollTop }) => { + if (!height) { + return
; + } + return ( - +
+ +
); } } @@ -314,19 +307,14 @@ class ArtistIndexPosters extends Component { ArtistIndexPosters.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 ArtistIndexPosters; diff --git a/frontend/src/Artist/Index/Table/ArtistIndexTable.js b/frontend/src/Artist/Index/Table/ArtistIndexTable.js index 47b7fef1c..6ce2a761a 100644 --- a/frontend/src/Artist/Index/Table/ArtistIndexTable.js +++ b/frontend/src/Artist/Index/Table/ArtistIndexTable.js @@ -23,10 +23,12 @@ class ArtistIndexTable 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); @@ -76,26 +78,21 @@ class ArtistIndexTable extends Component { const { items, columns, - filters, sortKey, sortDirection, showBanners, isSmallScreen, - scrollTop, - contentBody, onSortPress, - onRender, - onScroll + scroller } = this.props; return ( } columns={columns} - filters={filters} sortKey={sortKey} sortDirection={sortDirection} - onRender={onRender} - onScroll={onScroll} - isScrollingOptOut={true} /> ); } @@ -123,17 +116,13 @@ class ArtistIndexTable extends Component { ArtistIndexTable.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 ArtistIndexTable; diff --git a/frontend/src/Components/Scroller/OverlayScroller.js b/frontend/src/Components/Scroller/OverlayScroller.js index e2a269bdc..bb8120ff8 100644 --- a/frontend/src/Components/Scroller/OverlayScroller.js +++ b/frontend/src/Components/Scroller/OverlayScroller.js @@ -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; diff --git a/frontend/src/Components/Scroller/Scroller.js b/frontend/src/Components/Scroller/Scroller.js index f4ce7781f..22fae04c6 100644 --- a/frontend/src/Components/Scroller/Scroller.js +++ b/frontend/src/Components/Scroller/Scroller.js @@ -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; diff --git a/frontend/src/Components/Table/VirtualTable.css b/frontend/src/Components/Table/VirtualTable.css index 3287c5643..81111bcf9 100644 --- a/frontend/src/Components/Table/VirtualTable.css +++ b/frontend/src/Components/Table/VirtualTable.css @@ -1,3 +1,7 @@ .tableContainer { width: 100%; } + +.tableBodyContainer { + position: relative; +} diff --git a/frontend/src/Components/Table/VirtualTable.js b/frontend/src/Components/Table/VirtualTable.js index 258d31b00..18b2bbb78 100644 --- a/frontend/src/Components/Table/VirtualTable.js +++ b/frontend/src/Components/Table/VirtualTable.js @@ -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,39 @@ 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 = this.props.scrollIndex; + const { + width + } = this.state; - if (scrollIndex != null && scrollIndex !== prevProps.scrollIndex) { - const scrollTop = (scrollIndex + 1) * ROW_HEIGHT + 20; + if (this._grid && + (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 - rowGetter = ({ index }) => { - return this.props.items[index]; + setGridRef = (ref) => { + 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() { const { + isSmallScreen, className, items, - isSmallScreen, + scroller, header, headerHeight, - scrollTop, rowRenderer, - onScroll, ...otherProps } = this.props; @@ -114,65 +105,88 @@ 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 ( - - - {({ height, isScrolling }) => { - return ( + + {({ height, registerChild, onChildScroll, scrollTop }) => { + if (!height) { + return null; + } + return ( + {header} - - +
+ +
- ); - } - } -
-
+ + ); + } + } + ); } } 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, - onRender: PropTypes.func.isRequired, - onScroll: PropTypes.func.isRequired + rowRenderer: PropTypes.func.isRequired }; VirtualTable.defaultProps = { className: styles.tableContainer, - headerHeight: 38, - onRender: () => {} + headerHeight: 38 }; export default VirtualTable; diff --git a/frontend/src/Components/Table/VirtualTableBody.css b/frontend/src/Components/Table/VirtualTableBody.css deleted file mode 100644 index 12768646d..000000000 --- a/frontend/src/Components/Table/VirtualTableBody.css +++ /dev/null @@ -1,3 +0,0 @@ -.tableBodyContainer { - position: relative; -} diff --git a/frontend/src/Components/Table/VirtualTableBody.js b/frontend/src/Components/Table/VirtualTableBody.js deleted file mode 100644 index de88bd03c..000000000 --- a/frontend/src/Components/Table/VirtualTableBody.js +++ /dev/null @@ -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 ( - - ); - } -} - -VirtualTableBody.propTypes = { - className: PropTypes.string.isRequired -}; - -VirtualTableBody.defaultProps = { - className: styles.tableBodyContainer -}; - -export default VirtualTableBody; diff --git a/frontend/src/UnmappedFiles/UnmappedFilesTable.js b/frontend/src/UnmappedFiles/UnmappedFilesTable.js index e6a02fee6..94822299f 100644 --- a/frontend/src/UnmappedFiles/UnmappedFilesTable.js +++ b/frontend/src/UnmappedFiles/UnmappedFilesTable.js @@ -3,6 +3,7 @@ import React, { Component } from 'react'; import { align, icons, sortDirections } from 'Helpers/Props'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import VirtualTable from 'Components/Table/VirtualTable'; +import VirtualTableRow from 'Components/Table/VirtualTableRow'; import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper'; import PageContent from 'Components/Page/PageContent'; import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector'; @@ -21,16 +22,15 @@ class UnmappedFilesTable extends Component { super(props, context); this.state = { - contentBody: null, - scrollTop: 0 + scroller: null }; } // // Control - setContentBodyRef = (ref) => { - this.setState({ contentBody: ref }); + setScrollerRef = (ref) => { + this.setState({ scroller: ref }); } rowRenderer = ({ key, rowIndex, style }) => { @@ -43,23 +43,20 @@ class UnmappedFilesTable extends Component { const item = items[rowIndex]; return ( - + style={style} + > + + ); } - // - // Listeners - - onScroll = ({ scrollTop }) => { - this.setState({ scrollTop }); - } - render() { const { @@ -77,8 +74,7 @@ class UnmappedFilesTable extends Component { } = this.props; const { - scrollTop, - contentBody + scroller } = this.state; return ( @@ -100,8 +96,7 @@ class UnmappedFilesTable extends Component { { isFetching && !isPopulated && @@ -116,14 +111,12 @@ class UnmappedFilesTable extends Component { } { - isPopulated && !error && !!items.length && contentBody && + isPopulated && !error && !!items.length && scroller && + <> { columns.map((column) => { const { @@ -198,14 +194,13 @@ class UnmappedFilesTableRow extends Component { onCancel={this.onConfirmDeleteModalClose} /> - + ); } } UnmappedFilesTableRow.propTypes = { - style: PropTypes.object.isRequired, id: PropTypes.number.isRequired, path: PropTypes.string.isRequired, size: PropTypes.number.isRequired,