import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import Measure from 'react-measure'; import { Grid, WindowScroller } from 'react-virtualized'; import hasDifferentItems from 'Utilities/Object/hasDifferentItems'; import dimensions from 'Styles/Variables/dimensions'; import { sortDirections } from 'Helpers/Props'; import ArtistIndexItemConnector from 'Artist/Index/ArtistIndexItemConnector'; import ArtistIndexBanner from './ArtistIndexBanner'; import styles from './ArtistIndexBanners.css'; // container dimensions const columnPadding = 20; const columnPaddingSmallScreen = 10; const progressBarHeight = parseInt(dimensions.progressBarSmallHeight); const detailedProgressBarHeight = parseInt(dimensions.progressBarMediumHeight); const additionalColumnCount = { small: 3, medium: 2, large: 1 }; function calculateColumnWidth(width, bannerSize, isSmallScreen) { const maxiumColumnWidth = isSmallScreen ? 344 : 364; const columns = Math.floor(width / maxiumColumnWidth); const remainder = width % maxiumColumnWidth; if (remainder === 0 && bannerSize === 'large') { return maxiumColumnWidth; } return Math.floor(width / (columns + additionalColumnCount[bannerSize])); } function calculateRowHeight(bannerHeight, sortKey, isSmallScreen, bannerOptions) { const { detailedProgressBar, showTitle, showQualityProfile } = bannerOptions; const nextAiringHeight = 19; const heights = [ bannerHeight, detailedProgressBar ? detailedProgressBarHeight : progressBarHeight, nextAiringHeight, isSmallScreen ? columnPaddingSmallScreen : columnPadding ]; if (showTitle) { heights.push(19); } if (showQualityProfile) { heights.push(19); } switch (sortKey) { case 'seasons': case 'previousAiring': case 'added': case 'path': case 'sizeOnDisk': heights.push(19); break; case 'qualityProfileId': if (!showQualityProfile) { heights.push(19); } break; default: // No need to add a height of 0 } return heights.reduce((acc, height) => acc + height, 0); } function calculateHeight(bannerWidth) { return Math.ceil((88/476) * bannerWidth); } class ArtistIndexBanners extends Component { // // Lifecycle constructor(props, context) { super(props, context); this.state = { width: 0, columnWidth: 364, columnCount: 1, bannerWidth: 476, bannerHeight: 88, rowHeight: calculateRowHeight(88, null, props.isSmallScreen, {}) }; this._isInitialized = false; this._grid = null; } componentDidMount() { this._contentBodyNode = ReactDOM.findDOMNode(this.props.contentBody); } componentDidUpdate(prevProps) { const { items, filterKey, filterValue, sortKey, sortDirection, bannerOptions } = this.props; const itemsChanged = hasDifferentItems(prevProps.items, items); if ( prevProps.sortKey !== sortKey || prevProps.bannerOptions !== bannerOptions || itemsChanged ) { this.calculateGrid(); } if ( prevProps.filterKey !== filterKey || prevProps.filterValue !== filterValue || prevProps.sortKey !== sortKey || prevProps.sortDirection !== sortDirection || itemsChanged ) { this._grid.recomputeGridSize(); } } // // Control scrollToFirstCharacter(character) { const items = this.props.items; const { columnCount, rowHeight } = this.state; const index = _.findIndex(items, (item) => { const firstCharacter = item.sortName.charAt(0); if (character === '#') { return !isNaN(firstCharacter); } return firstCharacter === character; }); if (index != null) { const row = Math.floor(index / columnCount); const scrollTop = rowHeight * row; this.props.onScroll({ scrollTop }); } } setGridRef = (ref) => { this._grid = ref; } calculateGrid = (width = this.state.width, isSmallScreen) => { const { sortKey, bannerOptions } = this.props; const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding; const columnWidth = calculateColumnWidth(width, this.props.bannerOptions.size); const columnCount = Math.max(Math.floor(width / columnWidth), 1); const bannerWidth = columnWidth - padding; const bannerHeight = calculateHeight(bannerWidth); const rowHeight = calculateRowHeight(bannerHeight, sortKey, isSmallScreen, bannerOptions); this.setState({ width, columnWidth, columnCount, bannerWidth, bannerHeight, rowHeight }); } cellRenderer = ({ key, rowIndex, columnIndex, style }) => { const { items, sortKey, bannerOptions, showRelativeDates, shortDateFormat, timeFormat } = this.props; const { bannerWidth, bannerHeight, columnCount } = this.state; const { detailedProgressBar, showTitle, showQualityProfile } = bannerOptions; const series = items[rowIndex * columnCount + columnIndex]; if (!series) { return null; } return ( ); } // // Listeners onMeasure = ({ width }) => { 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 } = this.props; const { width, columnWidth, columnCount, rowHeight } = this.state; const rowCount = Math.ceil(items.length / columnCount); return ( {({ height, isScrolling }) => { return ( ); } } ); } } ArtistIndexBanners.propTypes = { items: PropTypes.arrayOf(PropTypes.object).isRequired, filterKey: PropTypes.string, filterValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]), sortKey: PropTypes.string, sortDirection: PropTypes.oneOf(sortDirections.all), bannerOptions: PropTypes.object.isRequired, scrollTop: PropTypes.number.isRequired, contentBody: PropTypes.object.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 }; export default ArtistIndexBanners;