parent
977c4e653b
commit
2ca55ae729
@ -1,85 +0,0 @@
|
|||||||
$hoverScale: 1.05;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
transition: all 200ms ease-in;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
z-index: 2;
|
|
||||||
box-shadow: 0 0 12px $black;
|
|
||||||
transition: all 200ms ease-in;
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
opacity: 0.9;
|
|
||||||
transition: opacity 200ms linear 150ms;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.bannerContainer {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
|
||||||
composes: link from '~Components/Link/Link.css';
|
|
||||||
|
|
||||||
display: block;
|
|
||||||
background-color: $defaultColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nextAiring {
|
|
||||||
background-color: #fafbfc;
|
|
||||||
text-align: center;
|
|
||||||
font-size: $smallFontSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
@add-mixin truncate;
|
|
||||||
|
|
||||||
background-color: $defaultColor;
|
|
||||||
color: $white;
|
|
||||||
text-align: center;
|
|
||||||
font-size: $smallFontSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ended {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0;
|
|
||||||
border-width: 0 25px 25px 0;
|
|
||||||
border-style: solid;
|
|
||||||
border-color: transparent $dangerColor transparent transparent;
|
|
||||||
color: $white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.controls {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 10px;
|
|
||||||
left: 10px;
|
|
||||||
z-index: 3;
|
|
||||||
border-radius: 4px;
|
|
||||||
background-color: $themeLightColor;
|
|
||||||
color: $white;
|
|
||||||
font-size: $smallFontSize;
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action {
|
|
||||||
composes: button from '~Components/Link/IconButton.css';
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: #ccc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: $breakpointSmall) {
|
|
||||||
.container {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,269 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import AuthorBanner from 'Author/AuthorBanner';
|
|
||||||
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
|
||||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
|
||||||
import AuthorIndexProgressBar from 'Author/Index/ProgressBar/AuthorIndexProgressBar';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import Link from 'Components/Link/Link';
|
|
||||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
|
||||||
import { icons } from 'Helpers/Props';
|
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import AuthorIndexBannerInfo from './AuthorIndexBannerInfo';
|
|
||||||
import styles from './AuthorIndexBanner.css';
|
|
||||||
|
|
||||||
class AuthorIndexBanner extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
isEditAuthorModalOpen: false,
|
|
||||||
isDeleteAuthorModalOpen: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onEditAuthorPress = () => {
|
|
||||||
this.setState({ isEditAuthorModalOpen: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
onEditAuthorModalClose = () => {
|
|
||||||
this.setState({ isEditAuthorModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteAuthorPress = () => {
|
|
||||||
this.setState({
|
|
||||||
isEditAuthorModalOpen: false,
|
|
||||||
isDeleteAuthorModalOpen: true
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onDeleteAuthorModalClose = () => {
|
|
||||||
this.setState({ isDeleteAuthorModalOpen: false });
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
authorName,
|
|
||||||
monitored,
|
|
||||||
status,
|
|
||||||
titleSlug,
|
|
||||||
nextAiring,
|
|
||||||
statistics,
|
|
||||||
images,
|
|
||||||
bannerWidth,
|
|
||||||
bannerHeight,
|
|
||||||
detailedProgressBar,
|
|
||||||
showTitle,
|
|
||||||
showMonitored,
|
|
||||||
showQualityProfile,
|
|
||||||
showSearchAction,
|
|
||||||
qualityProfile,
|
|
||||||
showRelativeDates,
|
|
||||||
shortDateFormat,
|
|
||||||
timeFormat,
|
|
||||||
isRefreshingAuthor,
|
|
||||||
isSearchingAuthor,
|
|
||||||
onRefreshAuthorPress,
|
|
||||||
onSearchPress,
|
|
||||||
...otherProps
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
bookCount,
|
|
||||||
sizeOnDisk,
|
|
||||||
bookFileCount,
|
|
||||||
totalBookCount
|
|
||||||
} = statistics;
|
|
||||||
|
|
||||||
const {
|
|
||||||
isEditAuthorModalOpen,
|
|
||||||
isDeleteAuthorModalOpen
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const link = `/author/${titleSlug}`;
|
|
||||||
|
|
||||||
const elementStyle = {
|
|
||||||
width: `${bannerWidth}px`,
|
|
||||||
height: `${bannerHeight}px`
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.container}>
|
|
||||||
<div className={styles.content}>
|
|
||||||
<div className={styles.bannerContainer}>
|
|
||||||
<Label className={styles.controls}>
|
|
||||||
<SpinnerIconButton
|
|
||||||
className={styles.action}
|
|
||||||
name={icons.REFRESH}
|
|
||||||
title={translate('RefreshAuthor')}
|
|
||||||
isSpinning={isRefreshingAuthor}
|
|
||||||
onPress={onRefreshAuthorPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
showSearchAction &&
|
|
||||||
<SpinnerIconButton
|
|
||||||
className={styles.action}
|
|
||||||
name={icons.SEARCH}
|
|
||||||
title={translate('SearchForMonitoredBooks')}
|
|
||||||
isSpinning={isSearchingAuthor}
|
|
||||||
onPress={onSearchPress}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<IconButton
|
|
||||||
className={styles.action}
|
|
||||||
name={icons.EDIT}
|
|
||||||
title={translate('EditAuthor')}
|
|
||||||
onPress={this.onEditAuthorPress}
|
|
||||||
/>
|
|
||||||
</Label>
|
|
||||||
|
|
||||||
{
|
|
||||||
status === 'ended' &&
|
|
||||||
<div
|
|
||||||
className={styles.ended}
|
|
||||||
title={translate('Ended')}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className={styles.link}
|
|
||||||
style={elementStyle}
|
|
||||||
to={link}
|
|
||||||
>
|
|
||||||
<AuthorBanner
|
|
||||||
className={styles.banner}
|
|
||||||
style={elementStyle}
|
|
||||||
images={images}
|
|
||||||
size={70}
|
|
||||||
lazy={false}
|
|
||||||
overflow={true}
|
|
||||||
/>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<AuthorIndexProgressBar
|
|
||||||
monitored={monitored}
|
|
||||||
status={status}
|
|
||||||
bookCount={bookCount}
|
|
||||||
bookFileCount={bookFileCount}
|
|
||||||
totalBookCount={totalBookCount}
|
|
||||||
posterWidth={bannerWidth}
|
|
||||||
detailedProgressBar={detailedProgressBar}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{
|
|
||||||
showTitle &&
|
|
||||||
<div className={styles.title}>
|
|
||||||
{authorName}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
showMonitored &&
|
|
||||||
<div className={styles.title}>
|
|
||||||
{monitored ? 'Monitored' : 'Unmonitored'}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
showQualityProfile &&
|
|
||||||
<div className={styles.title}>
|
|
||||||
{qualityProfile.name}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
{
|
|
||||||
nextAiring &&
|
|
||||||
<div className={styles.nextAiring}>
|
|
||||||
{
|
|
||||||
getRelativeDate(
|
|
||||||
nextAiring,
|
|
||||||
shortDateFormat,
|
|
||||||
showRelativeDates,
|
|
||||||
{
|
|
||||||
timeFormat,
|
|
||||||
timeForToday: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<AuthorIndexBannerInfo
|
|
||||||
bookCount={bookCount}
|
|
||||||
sizeOnDisk={sizeOnDisk}
|
|
||||||
qualityProfile={qualityProfile}
|
|
||||||
showQualityProfile={showQualityProfile}
|
|
||||||
showRelativeDates={showRelativeDates}
|
|
||||||
shortDateFormat={shortDateFormat}
|
|
||||||
timeFormat={timeFormat}
|
|
||||||
{...otherProps}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EditAuthorModalConnector
|
|
||||||
isOpen={isEditAuthorModalOpen}
|
|
||||||
authorId={id}
|
|
||||||
onModalClose={this.onEditAuthorModalClose}
|
|
||||||
onDeleteAuthorPress={this.onDeleteAuthorPress}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DeleteAuthorModal
|
|
||||||
isOpen={isDeleteAuthorModalOpen}
|
|
||||||
authorId={id}
|
|
||||||
onModalClose={this.onDeleteAuthorModalClose}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorIndexBanner.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
authorName: PropTypes.string.isRequired,
|
|
||||||
monitored: PropTypes.bool.isRequired,
|
|
||||||
status: PropTypes.string.isRequired,
|
|
||||||
titleSlug: PropTypes.string.isRequired,
|
|
||||||
nextAiring: PropTypes.string,
|
|
||||||
statistics: PropTypes.object.isRequired,
|
|
||||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
bannerWidth: PropTypes.number.isRequired,
|
|
||||||
bannerHeight: PropTypes.number.isRequired,
|
|
||||||
detailedProgressBar: PropTypes.bool.isRequired,
|
|
||||||
showTitle: PropTypes.bool.isRequired,
|
|
||||||
showMonitored: PropTypes.bool.isRequired,
|
|
||||||
showQualityProfile: PropTypes.bool.isRequired,
|
|
||||||
qualityProfile: PropTypes.object.isRequired,
|
|
||||||
showSearchAction: PropTypes.bool.isRequired,
|
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired,
|
|
||||||
isRefreshingAuthor: PropTypes.bool.isRequired,
|
|
||||||
isSearchingAuthor: PropTypes.bool.isRequired,
|
|
||||||
onRefreshAuthorPress: PropTypes.func.isRequired,
|
|
||||||
onSearchPress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
AuthorIndexBanner.defaultProps = {
|
|
||||||
statistics: {
|
|
||||||
bookCount: 0,
|
|
||||||
bookFileCount: 0,
|
|
||||||
totalBookCount: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthorIndexBanner;
|
|
@ -1,5 +0,0 @@
|
|||||||
.info {
|
|
||||||
background-color: #fafbfc;
|
|
||||||
text-align: center;
|
|
||||||
font-size: $smallFontSize;
|
|
||||||
}
|
|
@ -1,115 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
|
||||||
import styles from './AuthorIndexBannerInfo.css';
|
|
||||||
|
|
||||||
function AuthorIndexBannerInfo(props) {
|
|
||||||
const {
|
|
||||||
qualityProfile,
|
|
||||||
showQualityProfile,
|
|
||||||
previousAiring,
|
|
||||||
added,
|
|
||||||
bookCount,
|
|
||||||
path,
|
|
||||||
sizeOnDisk,
|
|
||||||
sortKey,
|
|
||||||
showRelativeDates,
|
|
||||||
shortDateFormat,
|
|
||||||
timeFormat
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
if (sortKey === 'qualityProfileId' && !showQualityProfile) {
|
|
||||||
return (
|
|
||||||
<div className={styles.info}>
|
|
||||||
{qualityProfile.name}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortKey === 'previousAiring' && previousAiring) {
|
|
||||||
return (
|
|
||||||
<div className={styles.info}>
|
|
||||||
{
|
|
||||||
getRelativeDate(
|
|
||||||
previousAiring,
|
|
||||||
shortDateFormat,
|
|
||||||
showRelativeDates,
|
|
||||||
{
|
|
||||||
timeFormat,
|
|
||||||
timeForToday: true
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortKey === 'added' && added) {
|
|
||||||
const addedDate = getRelativeDate(
|
|
||||||
added,
|
|
||||||
shortDateFormat,
|
|
||||||
showRelativeDates,
|
|
||||||
{
|
|
||||||
timeFormat,
|
|
||||||
timeForToday: false
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.info}>
|
|
||||||
{`Added ${addedDate}`}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortKey === 'bookCount') {
|
|
||||||
let books = '1 book';
|
|
||||||
|
|
||||||
if (bookCount === 0) {
|
|
||||||
books = 'No books';
|
|
||||||
} else if (bookCount > 1) {
|
|
||||||
books = `${bookCount} books`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={styles.info}>
|
|
||||||
{books}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortKey === 'path') {
|
|
||||||
return (
|
|
||||||
<div className={styles.info}>
|
|
||||||
{path}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sortKey === 'sizeOnDisk') {
|
|
||||||
return (
|
|
||||||
<div className={styles.info}>
|
|
||||||
{formatBytes(sizeOnDisk)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorIndexBannerInfo.propTypes = {
|
|
||||||
qualityProfile: PropTypes.object.isRequired,
|
|
||||||
showQualityProfile: PropTypes.bool.isRequired,
|
|
||||||
previousAiring: PropTypes.string,
|
|
||||||
added: PropTypes.string,
|
|
||||||
bookCount: PropTypes.number.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
|
||||||
sizeOnDisk: PropTypes.number,
|
|
||||||
sortKey: PropTypes.string.isRequired,
|
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthorIndexBannerInfo;
|
|
@ -1,3 +0,0 @@
|
|||||||
.grid {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
@ -1,326 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { Grid, WindowScroller } from 'react-virtualized';
|
|
||||||
import AuthorIndexItemConnector from 'Author/Index/AuthorIndexItemConnector';
|
|
||||||
import Measure from 'Components/Measure';
|
|
||||||
import dimensions from 'Styles/Variables/dimensions';
|
|
||||||
import getIndexOfFirstCharacter from 'Utilities/Array/getIndexOfFirstCharacter';
|
|
||||||
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
|
|
||||||
import AuthorIndexBanner from './AuthorIndexBanner';
|
|
||||||
import styles from './AuthorIndexBanners.css';
|
|
||||||
|
|
||||||
// container dimensions
|
|
||||||
const columnPadding = parseInt(dimensions.authorIndexColumnPadding);
|
|
||||||
const columnPaddingSmallScreen = parseInt(dimensions.authorIndexColumnPaddingSmallScreen);
|
|
||||||
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,
|
|
||||||
showMonitored,
|
|
||||||
showQualityProfile
|
|
||||||
} = bannerOptions;
|
|
||||||
|
|
||||||
const nextAiringHeight = 19;
|
|
||||||
|
|
||||||
const heights = [
|
|
||||||
bannerHeight,
|
|
||||||
detailedProgressBar ? detailedProgressBarHeight : progressBarHeight,
|
|
||||||
nextAiringHeight,
|
|
||||||
isSmallScreen ? columnPaddingSmallScreen : columnPadding
|
|
||||||
];
|
|
||||||
|
|
||||||
if (showTitle) {
|
|
||||||
heights.push(19);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showMonitored) {
|
|
||||||
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 AuthorIndexBanners 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, {}),
|
|
||||||
scrollRestored: false
|
|
||||||
};
|
|
||||||
|
|
||||||
this._isInitialized = false;
|
|
||||||
this._grid = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps, prevState) {
|
|
||||||
const {
|
|
||||||
items,
|
|
||||||
sortKey,
|
|
||||||
bannerOptions,
|
|
||||||
jumpToCharacter,
|
|
||||||
scrollTop
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
columnWidth,
|
|
||||||
columnCount,
|
|
||||||
rowHeight,
|
|
||||||
scrollRestored
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
if (prevProps.sortKey !== sortKey ||
|
|
||||||
prevProps.bannerOptions !== bannerOptions) {
|
|
||||||
this.calculateGrid();
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (this._grid && scrollTop !== 0 && !scrollRestored) {
|
|
||||||
this.setState({ scrollRestored: true });
|
|
||||||
this._grid.scrollToPosition({ scrollTop });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jumpToCharacter != null && jumpToCharacter !== prevProps.jumpToCharacter) {
|
|
||||||
const index = getIndexOfFirstCharacter(items, jumpToCharacter);
|
|
||||||
|
|
||||||
if (this._grid && index != null) {
|
|
||||||
const row = Math.floor(index / columnCount);
|
|
||||||
|
|
||||||
this._grid.scrollToCell({
|
|
||||||
rowIndex: row,
|
|
||||||
columnIndex: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Control
|
|
||||||
|
|
||||||
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, bannerOptions.size, isSmallScreen);
|
|
||||||
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,
|
|
||||||
showMonitored,
|
|
||||||
showQualityProfile
|
|
||||||
} = bannerOptions;
|
|
||||||
|
|
||||||
const author = items[rowIndex * columnCount + columnIndex];
|
|
||||||
|
|
||||||
if (!author) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
style={style}
|
|
||||||
key={key}
|
|
||||||
>
|
|
||||||
<AuthorIndexItemConnector
|
|
||||||
key={author.id}
|
|
||||||
component={AuthorIndexBanner}
|
|
||||||
sortKey={sortKey}
|
|
||||||
bannerWidth={bannerWidth}
|
|
||||||
bannerHeight={bannerHeight}
|
|
||||||
detailedProgressBar={detailedProgressBar}
|
|
||||||
showTitle={showTitle}
|
|
||||||
showMonitored={showMonitored}
|
|
||||||
showQualityProfile={showQualityProfile}
|
|
||||||
showRelativeDates={showRelativeDates}
|
|
||||||
shortDateFormat={shortDateFormat}
|
|
||||||
timeFormat={timeFormat}
|
|
||||||
authorId={author.id}
|
|
||||||
qualityProfileId={author.qualityProfileId}
|
|
||||||
metadataProfileId={author.metadataProfileId}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onMeasure = ({ width }) => {
|
|
||||||
this.calculateGrid(width, this.props.isSmallScreen);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
items,
|
|
||||||
isSmallScreen,
|
|
||||||
scroller
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
width,
|
|
||||||
columnWidth,
|
|
||||||
columnCount,
|
|
||||||
rowHeight
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
const rowCount = Math.ceil(items.length / columnCount);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Measure
|
|
||||||
onMeasure={this.onMeasure}
|
|
||||||
>
|
|
||||||
<WindowScroller
|
|
||||||
scrollElement={isSmallScreen ? undefined : scroller}
|
|
||||||
>
|
|
||||||
{({ 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}
|
|
||||||
onScroll={onChildScroll}
|
|
||||||
scrollTop={scrollTop}
|
|
||||||
overscanRowCount={2}
|
|
||||||
cellRenderer={this.cellRenderer}
|
|
||||||
scrollToAlignment={'start'}
|
|
||||||
isScrollingOptOut={true}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</WindowScroller>
|
|
||||||
</Measure>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorIndexBanners.propTypes = {
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
sortKey: PropTypes.string,
|
|
||||||
bannerOptions: PropTypes.object.isRequired,
|
|
||||||
jumpToCharacter: PropTypes.string,
|
|
||||||
scrollTop: PropTypes.number.isRequired,
|
|
||||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
|
||||||
showRelativeDates: PropTypes.bool.isRequired,
|
|
||||||
shortDateFormat: PropTypes.string.isRequired,
|
|
||||||
isSmallScreen: PropTypes.bool.isRequired,
|
|
||||||
timeFormat: PropTypes.string.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthorIndexBanners;
|
|
@ -1,24 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
|
||||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
|
||||||
import AuthorIndexBanners from './AuthorIndexBanners';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.authorIndex.bannerOptions,
|
|
||||||
createUISettingsSelector(),
|
|
||||||
createDimensionsSelector(),
|
|
||||||
(bannerOptions, uiSettings, dimensions) => {
|
|
||||||
return {
|
|
||||||
bannerOptions,
|
|
||||||
showRelativeDates: uiSettings.showRelativeDates,
|
|
||||||
shortDateFormat: uiSettings.shortDateFormat,
|
|
||||||
timeFormat: uiSettings.timeFormat,
|
|
||||||
isSmallScreen: dimensions.isSmallScreen
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps)(AuthorIndexBanners);
|
|
@ -1,25 +0,0 @@
|
|||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Modal from 'Components/Modal/Modal';
|
|
||||||
import AuthorIndexBannerOptionsModalContentConnector from './AuthorIndexBannerOptionsModalContentConnector';
|
|
||||||
|
|
||||||
function AuthorIndexBannerOptionsModal({ isOpen, onModalClose, ...otherProps }) {
|
|
||||||
return (
|
|
||||||
<Modal
|
|
||||||
isOpen={isOpen}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
>
|
|
||||||
<AuthorIndexBannerOptionsModalContentConnector
|
|
||||||
{...otherProps}
|
|
||||||
onModalClose={onModalClose}
|
|
||||||
/>
|
|
||||||
</Modal>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorIndexBannerOptionsModal.propTypes = {
|
|
||||||
isOpen: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthorIndexBannerOptionsModal;
|
|
@ -1,226 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import Form from 'Components/Form/Form';
|
|
||||||
import FormGroup from 'Components/Form/FormGroup';
|
|
||||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
|
||||||
import FormLabel from 'Components/Form/FormLabel';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import ModalBody from 'Components/Modal/ModalBody';
|
|
||||||
import ModalContent from 'Components/Modal/ModalContent';
|
|
||||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
|
||||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
|
||||||
import { inputTypes } from 'Helpers/Props';
|
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
|
|
||||||
const bannerSizeOptions = [
|
|
||||||
{ key: 'small', value: 'Small' },
|
|
||||||
{ key: 'medium', value: 'Medium' },
|
|
||||||
{ key: 'large', value: 'Large' }
|
|
||||||
];
|
|
||||||
|
|
||||||
class AuthorIndexBannerOptionsModalContent extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
constructor(props, context) {
|
|
||||||
super(props, context);
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
detailedProgressBar: props.detailedProgressBar,
|
|
||||||
size: props.size,
|
|
||||||
showTitle: props.showTitle,
|
|
||||||
showMonitored: props.showMonitored,
|
|
||||||
showQualityProfile: props.showQualityProfile,
|
|
||||||
showSearchAction: props.showSearchAction
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
|
||||||
const {
|
|
||||||
detailedProgressBar,
|
|
||||||
size,
|
|
||||||
showTitle,
|
|
||||||
showMonitored,
|
|
||||||
showQualityProfile,
|
|
||||||
showSearchAction
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const state = {};
|
|
||||||
|
|
||||||
if (detailedProgressBar !== prevProps.detailedProgressBar) {
|
|
||||||
state.detailedProgressBar = detailedProgressBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (size !== prevProps.size) {
|
|
||||||
state.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showTitle !== prevProps.showTitle) {
|
|
||||||
state.showTitle = showTitle;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showMonitored !== prevProps.showMonitored) {
|
|
||||||
state.showMonitored = showMonitored;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showQualityProfile !== prevProps.showQualityProfile) {
|
|
||||||
state.showQualityProfile = showQualityProfile;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showSearchAction !== prevProps.showSearchAction) {
|
|
||||||
state.showSearchAction = showSearchAction;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_.isEmpty(state)) {
|
|
||||||
this.setState(state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Listeners
|
|
||||||
|
|
||||||
onChangeBannerOption = ({ name, value }) => {
|
|
||||||
this.setState({
|
|
||||||
[name]: value
|
|
||||||
}, () => {
|
|
||||||
this.props.onChangeBannerOption({ [name]: value });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
onModalClose
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const {
|
|
||||||
detailedProgressBar,
|
|
||||||
size,
|
|
||||||
showTitle,
|
|
||||||
showMonitored,
|
|
||||||
showQualityProfile,
|
|
||||||
showSearchAction
|
|
||||||
} = this.state;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalContent onModalClose={onModalClose}>
|
|
||||||
<ModalHeader>
|
|
||||||
Options
|
|
||||||
</ModalHeader>
|
|
||||||
|
|
||||||
<ModalBody>
|
|
||||||
<Form>
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('Size')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.SELECT}
|
|
||||||
name="size"
|
|
||||||
value={size}
|
|
||||||
values={bannerSizeOptions}
|
|
||||||
onChange={this.onChangeBannerOption}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('DetailedProgressBar')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="detailedProgressBar"
|
|
||||||
value={detailedProgressBar}
|
|
||||||
helpText={translate('DetailedProgressBarHelpText')}
|
|
||||||
onChange={this.onChangeBannerOption}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('ShowName')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="showTitle"
|
|
||||||
value={showTitle}
|
|
||||||
helpText={translate('ShowTitleHelpText')}
|
|
||||||
onChange={this.onChangeBannerOption}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('ShowMonitored')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="showMonitored"
|
|
||||||
value={showMonitored}
|
|
||||||
helpText={translate('ShowMonitoredHelpText')}
|
|
||||||
onChange={this.onChangeBannerOption}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('ShowQualityProfile')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="showQualityProfile"
|
|
||||||
value={showQualityProfile}
|
|
||||||
helpText={translate('ShowQualityProfileHelpText')}
|
|
||||||
onChange={this.onChangeBannerOption}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
|
|
||||||
<FormGroup>
|
|
||||||
<FormLabel>
|
|
||||||
{translate('ShowSearch')}
|
|
||||||
</FormLabel>
|
|
||||||
|
|
||||||
<FormInputGroup
|
|
||||||
type={inputTypes.CHECK}
|
|
||||||
name="showSearchAction"
|
|
||||||
value={showSearchAction}
|
|
||||||
helpText={translate('ShowSearchActionHelpText')}
|
|
||||||
onChange={this.onChangeBannerOption}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
</Form>
|
|
||||||
</ModalBody>
|
|
||||||
|
|
||||||
<ModalFooter>
|
|
||||||
<Button
|
|
||||||
onPress={onModalClose}
|
|
||||||
>
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
</ModalFooter>
|
|
||||||
</ModalContent>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthorIndexBannerOptionsModalContent.propTypes = {
|
|
||||||
size: PropTypes.string.isRequired,
|
|
||||||
showTitle: PropTypes.bool.isRequired,
|
|
||||||
showQualityProfile: PropTypes.bool.isRequired,
|
|
||||||
detailedProgressBar: PropTypes.bool.isRequired,
|
|
||||||
showSearchAction: PropTypes.bool.isRequired,
|
|
||||||
onChangeBannerOption: PropTypes.func.isRequired,
|
|
||||||
showMonitored: PropTypes.bool.isRequired,
|
|
||||||
onModalClose: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AuthorIndexBannerOptionsModalContent;
|
|
@ -1,23 +0,0 @@
|
|||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { setAuthorBannerOption } from 'Store/Actions/authorIndexActions';
|
|
||||||
import AuthorIndexBannerOptionsModalContent from './AuthorIndexBannerOptionsModalContent';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.authorIndex,
|
|
||||||
(authorIndex) => {
|
|
||||||
return authorIndex.bannerOptions;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onChangeBannerOption(payload) {
|
|
||||||
dispatch(setAuthorBannerOption(payload));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AuthorIndexBannerOptionsModalContent);
|
|
Loading…
Reference in new issue