mirror of
https://github.com/fergalmoran/Readarr.git
synced 2025-12-30 13:28:04 +00:00
Fixed: Better book status column in author details
This commit is contained in:
@@ -2,28 +2,14 @@ import PropTypes from 'prop-types';
|
|||||||
import React, { Component } from 'react';
|
import React, { Component } from 'react';
|
||||||
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
|
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
|
||||||
import BookTitleLink from 'Book/BookTitleLink';
|
import BookTitleLink from 'Book/BookTitleLink';
|
||||||
import Label from 'Components/Label';
|
|
||||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||||
import StarRating from 'Components/StarRating';
|
import StarRating from 'Components/StarRating';
|
||||||
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
import TableRow from 'Components/Table/TableRow';
|
import TableRow from 'Components/Table/TableRow';
|
||||||
import { kinds, sizes } from 'Helpers/Props';
|
import BookStatus from './BookStatus';
|
||||||
import translate from 'Utilities/String/translate';
|
|
||||||
import styles from './BookRow.css';
|
import styles from './BookRow.css';
|
||||||
|
|
||||||
function getBookCountKind(monitored, bookFileCount, bookCount) {
|
|
||||||
if (bookFileCount === bookCount && bookCount > 0) {
|
|
||||||
return kinds.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!monitored) {
|
|
||||||
return kinds.WARNING;
|
|
||||||
}
|
|
||||||
|
|
||||||
return kinds.DANGER;
|
|
||||||
}
|
|
||||||
|
|
||||||
class BookRow extends Component {
|
class BookRow extends Component {
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -69,7 +55,6 @@ class BookRow extends Component {
|
|||||||
id,
|
id,
|
||||||
authorId,
|
authorId,
|
||||||
monitored,
|
monitored,
|
||||||
statistics,
|
|
||||||
releaseDate,
|
releaseDate,
|
||||||
title,
|
title,
|
||||||
seriesTitle,
|
seriesTitle,
|
||||||
@@ -79,14 +64,12 @@ class BookRow extends Component {
|
|||||||
isSaving,
|
isSaving,
|
||||||
authorMonitored,
|
authorMonitored,
|
||||||
titleSlug,
|
titleSlug,
|
||||||
|
bookFiles,
|
||||||
columns
|
columns
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const {
|
const bookFile = bookFiles[0];
|
||||||
bookCount,
|
const isAvailable = Date.parse(releaseDate) < new Date();
|
||||||
bookFileCount,
|
|
||||||
totalBookCount
|
|
||||||
} = statistics;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<TableRow>
|
<TableRow>
|
||||||
@@ -196,15 +179,11 @@ class BookRow extends Component {
|
|||||||
key={name}
|
key={name}
|
||||||
className={styles.status}
|
className={styles.status}
|
||||||
>
|
>
|
||||||
<Label
|
<BookStatus
|
||||||
title={translate('TotalBookCountBooksTotalBookFileCountBooksWithFilesInterp', [totalBookCount, bookFileCount])}
|
isAvailable={isAvailable}
|
||||||
kind={getBookCountKind(monitored, bookFileCount, bookCount)}
|
monitored={monitored}
|
||||||
size={sizes.MEDIUM}
|
bookFile={bookFile}
|
||||||
>
|
/>
|
||||||
{
|
|
||||||
<span>{bookFileCount} / {bookCount}</span>
|
|
||||||
}
|
|
||||||
</Label>
|
|
||||||
</TableRowCell>
|
</TableRowCell>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -240,16 +219,9 @@ BookRow.propTypes = {
|
|||||||
titleSlug: PropTypes.string.isRequired,
|
titleSlug: PropTypes.string.isRequired,
|
||||||
isSaving: PropTypes.bool,
|
isSaving: PropTypes.bool,
|
||||||
authorMonitored: PropTypes.bool.isRequired,
|
authorMonitored: PropTypes.bool.isRequired,
|
||||||
statistics: PropTypes.object.isRequired,
|
bookFiles: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||||
onMonitorBookPress: PropTypes.func.isRequired
|
onMonitorBookPress: PropTypes.func.isRequired
|
||||||
};
|
};
|
||||||
|
|
||||||
BookRow.defaultProps = {
|
|
||||||
statistics: {
|
|
||||||
bookCount: 0,
|
|
||||||
bookFileCount: 0
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default BookRow;
|
export default BookRow;
|
||||||
|
|||||||
@@ -2,17 +2,38 @@
|
|||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
import { createSelector } from 'reselect';
|
import { createSelector } from 'reselect';
|
||||||
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
|
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
|
||||||
import createBookFileSelector from 'Store/Selectors/createBookFileSelector';
|
|
||||||
import BookRow from './BookRow';
|
import BookRow from './BookRow';
|
||||||
|
|
||||||
|
const selectBookFiles = createSelector(
|
||||||
|
(state) => state.bookFiles,
|
||||||
|
(bookFiles) => {
|
||||||
|
const {
|
||||||
|
items
|
||||||
|
} = bookFiles;
|
||||||
|
|
||||||
|
const bookFileDict = items.reduce((acc, file) => {
|
||||||
|
const bookId = file.bookId;
|
||||||
|
if (!acc.hasOwnProperty(bookId)) {
|
||||||
|
acc[bookId] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
acc[bookId].push(file);
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return bookFileDict;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
function createMapStateToProps() {
|
function createMapStateToProps() {
|
||||||
return createSelector(
|
return createSelector(
|
||||||
createAuthorSelector(),
|
createAuthorSelector(),
|
||||||
createBookFileSelector(),
|
selectBookFiles,
|
||||||
(author = {}, bookFile) => {
|
(state, { id }) => id,
|
||||||
|
(author = {}, bookFiles, bookId) => {
|
||||||
return {
|
return {
|
||||||
authorMonitored: author.monitored,
|
authorMonitored: author.monitored,
|
||||||
bookFilePath: bookFile ? bookFile.path : null
|
bookFiles: bookFiles[bookId] ?? []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
5
frontend/src/Author/Details/BookStatus.css
Normal file
5
frontend/src/Author/Details/BookStatus.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
|
||||||
|
.center {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
78
frontend/src/Author/Details/BookStatus.js
Normal file
78
frontend/src/Author/Details/BookStatus.js
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import React from 'react';
|
||||||
|
import BookQuality from 'Book/BookQuality';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './BookStatus.css';
|
||||||
|
|
||||||
|
function BookStatus(props) {
|
||||||
|
const {
|
||||||
|
isAvailable,
|
||||||
|
monitored,
|
||||||
|
bookFile
|
||||||
|
} = props;
|
||||||
|
|
||||||
|
const hasBookFile = !!bookFile;
|
||||||
|
|
||||||
|
if (hasBookFile) {
|
||||||
|
const quality = bookFile.quality;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.center}>
|
||||||
|
<BookQuality
|
||||||
|
title={quality.quality.name}
|
||||||
|
size={bookFile.size}
|
||||||
|
quality={quality}
|
||||||
|
isMonitored={monitored}
|
||||||
|
isCutoffNotMet={bookFile.qualityCutoffNotMet}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!monitored) {
|
||||||
|
return (
|
||||||
|
<div className={styles.center}>
|
||||||
|
<Label
|
||||||
|
title={translate('NotMonitored')}
|
||||||
|
kind={kinds.WARNING}
|
||||||
|
>
|
||||||
|
{translate('NotMonitored')}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAvailable) {
|
||||||
|
return (
|
||||||
|
<div className={styles.center}>
|
||||||
|
<Label
|
||||||
|
title={translate('BookAvailableButMissing')}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
>
|
||||||
|
{translate('Missing')}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.center}>
|
||||||
|
<Label
|
||||||
|
title={translate('NotAvailable')}
|
||||||
|
kind={kinds.INFO}
|
||||||
|
>
|
||||||
|
{translate('NotAvailable')}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BookStatus.propTypes = {
|
||||||
|
isAvailable: PropTypes.bool,
|
||||||
|
monitored: PropTypes.bool.isRequired,
|
||||||
|
bookFile: PropTypes.object
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BookStatus;
|
||||||
@@ -4,11 +4,7 @@ import Label from 'Components/Label';
|
|||||||
import { kinds } from 'Helpers/Props';
|
import { kinds } from 'Helpers/Props';
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
|
||||||
function getTooltip(title, quality, size) {
|
function getTooltip(title, quality, size, isMonitored, isCutoffNotMet) {
|
||||||
if (!title) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const revision = quality.revision;
|
const revision = quality.revision;
|
||||||
|
|
||||||
if (revision.real && revision.real > 0) {
|
if (revision.real && revision.real > 0) {
|
||||||
@@ -23,6 +19,12 @@ function getTooltip(title, quality, size) {
|
|||||||
title += ` - ${formatBytes(size)}`;
|
title += ` - ${formatBytes(size)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isMonitored) {
|
||||||
|
title += ' [Not Monitored]';
|
||||||
|
} else if (isCutoffNotMet) {
|
||||||
|
title += ' [Cutoff Not Met]';
|
||||||
|
}
|
||||||
|
|
||||||
return title;
|
return title;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -32,14 +34,26 @@ function BookQuality(props) {
|
|||||||
title,
|
title,
|
||||||
quality,
|
quality,
|
||||||
size,
|
size,
|
||||||
|
isMonitored,
|
||||||
isCutoffNotMet
|
isCutoffNotMet
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
let kind = kinds.DEFAULT;
|
||||||
|
if (!isMonitored) {
|
||||||
|
kind = kinds.DISABLED;
|
||||||
|
} else if (isCutoffNotMet) {
|
||||||
|
kind = kinds.INVERSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!quality) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Label
|
<Label
|
||||||
className={className}
|
className={className}
|
||||||
kind={isCutoffNotMet ? kinds.INVERSE : kinds.DEFAULT}
|
kind={kind}
|
||||||
title={getTooltip(title, quality, size)}
|
title={getTooltip(title, quality, size, isMonitored, isCutoffNotMet)}
|
||||||
>
|
>
|
||||||
{quality.quality.name}
|
{quality.quality.name}
|
||||||
</Label>
|
</Label>
|
||||||
@@ -51,11 +65,13 @@ BookQuality.propTypes = {
|
|||||||
title: PropTypes.string,
|
title: PropTypes.string,
|
||||||
quality: PropTypes.object.isRequired,
|
quality: PropTypes.object.isRequired,
|
||||||
size: PropTypes.number,
|
size: PropTypes.number,
|
||||||
|
isMonitored: PropTypes.bool,
|
||||||
isCutoffNotMet: PropTypes.bool
|
isCutoffNotMet: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
BookQuality.defaultProps = {
|
BookQuality.defaultProps = {
|
||||||
title: ''
|
title: '',
|
||||||
|
isMonitored: true
|
||||||
};
|
};
|
||||||
|
|
||||||
export default BookQuality;
|
export default BookQuality;
|
||||||
|
|||||||
@@ -105,20 +105,6 @@ export const filterPredicates = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const sortPredicates = {
|
export const sortPredicates = {
|
||||||
status: function(item) {
|
|
||||||
let result = 0;
|
|
||||||
|
|
||||||
if (item.monitored) {
|
|
||||||
result += 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.status === 'continuing') {
|
|
||||||
result++;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
sizeOnDisk: function(item) {
|
sizeOnDisk: function(item) {
|
||||||
const { statistics = {} } = item;
|
const { statistics = {} } = item;
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,7 @@
|
|||||||
"BlacklistHelpText": "Prevents Readarr from automatically grabbing these files again",
|
"BlacklistHelpText": "Prevents Readarr from automatically grabbing these files again",
|
||||||
"BlacklistRelease": "Blacklist Release",
|
"BlacklistRelease": "Blacklist Release",
|
||||||
"Book": "Book",
|
"Book": "Book",
|
||||||
|
"BookAvailableButMissing": "Book Available, but Missing",
|
||||||
"BookDownloaded": "Book Downloaded",
|
"BookDownloaded": "Book Downloaded",
|
||||||
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total: {2})",
|
"BookFileCountBookCountTotalTotalBookCountInterp": "{0} / {1} (Total: {2})",
|
||||||
"BookFileCounttotalBookCountBooksDownloadedInterp": "{0}/{1} books downloaded",
|
"BookFileCounttotalBookCountBooksDownloadedInterp": "{0}/{1} books downloaded",
|
||||||
@@ -402,6 +403,8 @@
|
|||||||
"NoMinimumForAnyRuntime": "No minimum for any runtime",
|
"NoMinimumForAnyRuntime": "No minimum for any runtime",
|
||||||
"NoName": "Do not show name",
|
"NoName": "Do not show name",
|
||||||
"None": "None",
|
"None": "None",
|
||||||
|
"NotAvailable": "Not Available",
|
||||||
|
"NotMonitored": "Not Monitored",
|
||||||
"NoTagsHaveBeenAddedYet": "No tags have been added yet. Add tags to link authors with delay profiles, restrictions, or notifications. Click {0} to find out more about tags in Readarr.",
|
"NoTagsHaveBeenAddedYet": "No tags have been added yet. Add tags to link authors with delay profiles, restrictions, or notifications. Click {0} to find out more about tags in Readarr.",
|
||||||
"NotificationTriggers": "Notification Triggers",
|
"NotificationTriggers": "Notification Triggers",
|
||||||
"NoUpdatesAreAvailable": "No updates are available",
|
"NoUpdatesAreAvailable": "No updates are available",
|
||||||
|
|||||||
Reference in New Issue
Block a user