mirror of
https://github.com/Readarr/Readarr.git
synced 2026-04-22 22:14:44 -04:00
Renames in Frontend
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
$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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.posterContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.link {
|
||||
composes: link from '~Components/Link/Link.css';
|
||||
|
||||
position: relative;
|
||||
display: block;
|
||||
height: 70px;
|
||||
background-color: $defaultColor;
|
||||
}
|
||||
|
||||
.overlayTitle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 5px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
color: $offWhite;
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
z-index: 1;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,292 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import AuthorPoster from 'Author/AuthorPoster';
|
||||
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
|
||||
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
|
||||
import AuthorIndexProgressBar from 'Author/Index/ProgressBar/AuthorIndexProgressBar';
|
||||
import AuthorIndexPosterInfo from './AuthorIndexPosterInfo';
|
||||
import styles from './AuthorIndexPoster.css';
|
||||
|
||||
class AuthorIndexPoster extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasPosterError: false,
|
||||
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 });
|
||||
}
|
||||
|
||||
onPosterLoad = () => {
|
||||
if (this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: false });
|
||||
}
|
||||
}
|
||||
|
||||
onPosterLoadError = () => {
|
||||
if (!this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: true });
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
id,
|
||||
authorName,
|
||||
monitored,
|
||||
titleSlug,
|
||||
status,
|
||||
nextAiring,
|
||||
statistics,
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
detailedProgressBar,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile,
|
||||
qualityProfile,
|
||||
showSearchAction,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat,
|
||||
isRefreshingAuthor,
|
||||
isSearchingAuthor,
|
||||
onRefreshAuthorPress,
|
||||
onSearchPress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
bookCount,
|
||||
sizeOnDisk,
|
||||
bookFileCount,
|
||||
totalBookCount
|
||||
} = statistics;
|
||||
|
||||
const {
|
||||
hasPosterError,
|
||||
isEditAuthorModalOpen,
|
||||
isDeleteAuthorModalOpen
|
||||
} = this.state;
|
||||
|
||||
const link = `/author/${titleSlug}`;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`
|
||||
};
|
||||
elementStyle.objectFit = 'contain';
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.posterContainer}>
|
||||
<Label className={styles.controls}>
|
||||
<SpinnerIconButton
|
||||
className={styles.action}
|
||||
name={icons.REFRESH}
|
||||
title="Refresh Author"
|
||||
isSpinning={isRefreshingAuthor}
|
||||
onPress={onRefreshAuthorPress}
|
||||
/>
|
||||
|
||||
{
|
||||
showSearchAction &&
|
||||
<SpinnerIconButton
|
||||
className={styles.action}
|
||||
name={icons.SEARCH}
|
||||
title="Search for monitored books"
|
||||
isSpinning={isSearchingAuthor}
|
||||
onPress={onSearchPress}
|
||||
/>
|
||||
}
|
||||
|
||||
<IconButton
|
||||
className={styles.action}
|
||||
name={icons.EDIT}
|
||||
title="Edit Author"
|
||||
onPress={this.onEditAuthorPress}
|
||||
/>
|
||||
</Label>
|
||||
|
||||
{
|
||||
status === 'ended' &&
|
||||
<div
|
||||
className={styles.ended}
|
||||
title="Ended"
|
||||
/>
|
||||
}
|
||||
|
||||
<Link
|
||||
className={styles.link}
|
||||
style={elementStyle}
|
||||
to={link}
|
||||
>
|
||||
<AuthorPoster
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
onError={this.onPosterLoadError}
|
||||
onLoad={this.onPosterLoad}
|
||||
/>
|
||||
|
||||
{
|
||||
hasPosterError &&
|
||||
<div className={styles.overlayTitle}>
|
||||
{authorName}
|
||||
</div>
|
||||
}
|
||||
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<AuthorIndexProgressBar
|
||||
monitored={monitored}
|
||||
status={status}
|
||||
bookCount={bookCount}
|
||||
bookFileCount={bookFileCount}
|
||||
totalBookCount={totalBookCount}
|
||||
posterWidth={posterWidth}
|
||||
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>
|
||||
}
|
||||
<AuthorIndexPosterInfo
|
||||
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>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AuthorIndexPoster.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,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: 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
|
||||
};
|
||||
|
||||
AuthorIndexPoster.defaultProps = {
|
||||
statistics: {
|
||||
bookCount: 0,
|
||||
bookFileCount: 0,
|
||||
totalBookCount: 0
|
||||
}
|
||||
};
|
||||
|
||||
export default AuthorIndexPoster;
|
||||
@@ -0,0 +1,5 @@
|
||||
.info {
|
||||
background-color: #fafbfc;
|
||||
text-align: center;
|
||||
font-size: $smallFontSize;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import getRelativeDate from 'Utilities/Date/getRelativeDate';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import styles from './AuthorIndexPosterInfo.css';
|
||||
|
||||
function AuthorIndexPosterInfo(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;
|
||||
}
|
||||
|
||||
AuthorIndexPosterInfo.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 AuthorIndexPosterInfo;
|
||||
@@ -0,0 +1,3 @@
|
||||
.grid {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
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 Measure from 'Components/Measure';
|
||||
import AuthorIndexItemConnector from 'Author/Index/AuthorIndexItemConnector';
|
||||
import AuthorIndexPoster from './AuthorIndexPoster';
|
||||
import styles from './AuthorIndexPosters.css';
|
||||
|
||||
// Poster 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, posterSize, isSmallScreen) {
|
||||
const maxiumColumnWidth = isSmallScreen ? 172 : 182;
|
||||
const columns = Math.floor(width / maxiumColumnWidth);
|
||||
const remainder = width % maxiumColumnWidth;
|
||||
|
||||
if (remainder === 0 && posterSize === 'large') {
|
||||
return maxiumColumnWidth;
|
||||
}
|
||||
|
||||
return Math.floor(width / (columns + additionalColumnCount[posterSize]));
|
||||
}
|
||||
|
||||
function calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions) {
|
||||
const {
|
||||
detailedProgressBar,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile
|
||||
} = posterOptions;
|
||||
|
||||
const nextAiringHeight = 19;
|
||||
|
||||
const heights = [
|
||||
posterHeight,
|
||||
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 calculatePosterHeight(posterWidth) {
|
||||
return Math.ceil(posterWidth);
|
||||
}
|
||||
|
||||
class AuthorIndexPosters extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
width: 0,
|
||||
columnWidth: 182,
|
||||
columnCount: 1,
|
||||
posterWidth: 238,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, null, props.isSmallScreen, {})
|
||||
};
|
||||
|
||||
this._isInitialized = false;
|
||||
this._grid = null;
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const {
|
||||
items,
|
||||
sortKey,
|
||||
posterOptions,
|
||||
jumpToCharacter
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
width,
|
||||
columnWidth,
|
||||
columnCount,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
if (prevProps.sortKey !== sortKey ||
|
||||
prevProps.posterOptions !== posterOptions) {
|
||||
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 (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,
|
||||
posterOptions
|
||||
} = this.props;
|
||||
|
||||
const padding = isSmallScreen ? columnPaddingSmallScreen : columnPadding;
|
||||
const columnWidth = calculateColumnWidth(width, posterOptions.size, isSmallScreen);
|
||||
const columnCount = Math.max(Math.floor(width / columnWidth), 1);
|
||||
const posterWidth = columnWidth - padding;
|
||||
const posterHeight = calculatePosterHeight(posterWidth);
|
||||
const rowHeight = calculateRowHeight(posterHeight, sortKey, isSmallScreen, posterOptions);
|
||||
|
||||
this.setState({
|
||||
width,
|
||||
columnWidth,
|
||||
columnCount,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
rowHeight
|
||||
});
|
||||
}
|
||||
|
||||
cellRenderer = ({ key, rowIndex, columnIndex, style }) => {
|
||||
const {
|
||||
items,
|
||||
sortKey,
|
||||
posterOptions,
|
||||
showRelativeDates,
|
||||
shortDateFormat,
|
||||
timeFormat
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
columnCount
|
||||
} = this.state;
|
||||
|
||||
const {
|
||||
detailedProgressBar,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile
|
||||
} = posterOptions;
|
||||
|
||||
const authorIdx = rowIndex * columnCount + columnIndex;
|
||||
const author = items[authorIdx];
|
||||
|
||||
if (!author) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
key={key}
|
||||
style={style}
|
||||
>
|
||||
<AuthorIndexItemConnector
|
||||
key={author.id}
|
||||
component={AuthorIndexPoster}
|
||||
sortKey={sortKey}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
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
|
||||
whitelist={['width']}
|
||||
onMeasure={this.onMeasure}
|
||||
>
|
||||
<WindowScroller
|
||||
scrollElement={isSmallScreen ? undefined : scroller}
|
||||
>
|
||||
{({ height, registerChild, onChildScroll, scrollTop }) => {
|
||||
if (!height) {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
return (
|
||||
<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>
|
||||
);
|
||||
}
|
||||
}
|
||||
</WindowScroller>
|
||||
</Measure>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AuthorIndexPosters.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
sortKey: PropTypes.string,
|
||||
posterOptions: PropTypes.object.isRequired,
|
||||
jumpToCharacter: PropTypes.string,
|
||||
scroller: PropTypes.instanceOf(Element).isRequired,
|
||||
showRelativeDates: PropTypes.bool.isRequired,
|
||||
shortDateFormat: PropTypes.string.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired,
|
||||
timeFormat: PropTypes.string.isRequired
|
||||
};
|
||||
|
||||
export default AuthorIndexPosters;
|
||||
@@ -0,0 +1,24 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
|
||||
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
|
||||
import AuthorIndexPosters from './AuthorIndexPosters';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.authorIndex.posterOptions,
|
||||
createUISettingsSelector(),
|
||||
createDimensionsSelector(),
|
||||
(posterOptions, uiSettings, dimensions) => {
|
||||
return {
|
||||
posterOptions,
|
||||
showRelativeDates: uiSettings.showRelativeDates,
|
||||
shortDateFormat: uiSettings.shortDateFormat,
|
||||
timeFormat: uiSettings.timeFormat,
|
||||
isSmallScreen: dimensions.isSmallScreen
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(AuthorIndexPosters);
|
||||
@@ -0,0 +1,25 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import AuthorIndexPosterOptionsModalContentConnector from './AuthorIndexPosterOptionsModalContentConnector';
|
||||
|
||||
function AuthorIndexPosterOptionsModal({ isOpen, onModalClose, ...otherProps }) {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<AuthorIndexPosterOptionsModalContentConnector
|
||||
{...otherProps}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
AuthorIndexPosterOptionsModal.propTypes = {
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AuthorIndexPosterOptionsModal;
|
||||
@@ -0,0 +1,213 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
|
||||
const posterSizeOptions = [
|
||||
{ key: 'small', value: 'Small' },
|
||||
{ key: 'medium', value: 'Medium' },
|
||||
{ key: 'large', value: 'Large' }
|
||||
];
|
||||
|
||||
class AuthorIndexPosterOptionsModalContent 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
|
||||
|
||||
onChangePosterOption = ({ name, value }) => {
|
||||
this.setState({
|
||||
[name]: value
|
||||
}, () => {
|
||||
this.props.onChangePosterOption({ [name]: value });
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
detailedProgressBar,
|
||||
size,
|
||||
showTitle,
|
||||
showMonitored,
|
||||
showQualityProfile,
|
||||
showSearchAction
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
Poster Options
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>Poster Size</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="size"
|
||||
value={size}
|
||||
values={posterSizeOptions}
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Detailed Progress Bar</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="detailedProgressBar"
|
||||
value={detailedProgressBar}
|
||||
helpText="Show text on progess bar"
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Name</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showTitle"
|
||||
value={showTitle}
|
||||
helpText="Show author name under poster"
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Monitored</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showMonitored"
|
||||
value={showMonitored}
|
||||
helpText="Show monitored status under poster"
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Quality Profile</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showQualityProfile"
|
||||
value={showQualityProfile}
|
||||
helpText="Show quality profile under poster"
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>Show Search</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="showSearchAction"
|
||||
value={showSearchAction}
|
||||
helpText="Show search button on hover"
|
||||
onChange={this.onChangePosterOption}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button
|
||||
onPress={onModalClose}
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
AuthorIndexPosterOptionsModalContent.propTypes = {
|
||||
size: PropTypes.string.isRequired,
|
||||
showTitle: PropTypes.bool.isRequired,
|
||||
showMonitored: PropTypes.bool.isRequired,
|
||||
showQualityProfile: PropTypes.bool.isRequired,
|
||||
detailedProgressBar: PropTypes.bool.isRequired,
|
||||
showSearchAction: PropTypes.bool.isRequired,
|
||||
onChangePosterOption: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default AuthorIndexPosterOptionsModalContent;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { setAuthorPosterOption } from 'Store/Actions/authorIndexActions';
|
||||
import AuthorIndexPosterOptionsModalContent from './AuthorIndexPosterOptionsModalContent';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.authorIndex,
|
||||
(authorIndex) => {
|
||||
return authorIndex.posterOptions;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createMapDispatchToProps(dispatch, props) {
|
||||
return {
|
||||
onChangePosterOption(payload) {
|
||||
dispatch(setAuthorPosterOption(payload));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps, createMapDispatchToProps)(AuthorIndexPosterOptionsModalContent);
|
||||
Reference in New Issue
Block a user