Renames in Frontend

pull/33/head
Qstick 4 years ago committed by ta264
parent ee4e44b81a
commit ee43ccf620

@ -6,8 +6,8 @@ import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TrackQuality from 'Album/TrackQuality';
import ArtistNameLink from 'Artist/ArtistNameLink';
import BookQuality from 'Book/BookQuality';
import AuthorNameLink from 'Author/AuthorNameLink';
import BlacklistDetailsModal from './BlacklistDetailsModal';
import styles from './BlacklistRow.css';
@ -40,7 +40,7 @@ class BlacklistRow extends Component {
render() {
const {
artist,
author,
sourceTitle,
quality,
date,
@ -51,7 +51,7 @@ class BlacklistRow extends Component {
onRemovePress
} = this.props;
if (!artist) {
if (!author) {
return null;
}
@ -71,9 +71,9 @@ class BlacklistRow extends Component {
if (name === 'authors.sortName') {
return (
<TableRowCell key={name}>
<ArtistNameLink
titleSlug={artist.titleSlug}
artistName={artist.artistName}
<AuthorNameLink
titleSlug={author.titleSlug}
authorName={author.authorName}
/>
</TableRowCell>
);
@ -93,7 +93,7 @@ class BlacklistRow extends Component {
key={name}
className={styles.quality}
>
<TrackQuality
<BookQuality
quality={quality}
/>
</TableRowCell>
@ -161,7 +161,7 @@ class BlacklistRow extends Component {
BlacklistRow.propTypes = {
id: PropTypes.number.isRequired,
artist: PropTypes.object.isRequired,
author: PropTypes.object.isRequired,
sourceTitle: PropTypes.string.isRequired,
quality: PropTypes.object.isRequired,
date: PropTypes.string.isRequired,

@ -1,15 +1,15 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import { removeFromBlacklist } from 'Store/Actions/blacklistActions';
import BlacklistRow from './BlacklistRow';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
(artist) => {
createAuthorSelector(),
(author) => {
return {
artist
author
};
}
);

@ -170,7 +170,7 @@ function HistoryDetails(props) {
);
}
if (eventType === 'trackFileImported') {
if (eventType === 'bookFileImported') {
const {
droppedPath,
importedPath
@ -205,7 +205,7 @@ function HistoryDetails(props) {
);
}
if (eventType === 'trackFileDeleted') {
if (eventType === 'bookFileDeleted') {
const {
reason
} = data;
@ -241,7 +241,7 @@ function HistoryDetails(props) {
);
}
if (eventType === 'trackFileRenamed') {
if (eventType === 'bookFileRenamed') {
const {
sourcePath,
path
@ -262,7 +262,7 @@ function HistoryDetails(props) {
);
}
if (eventType === 'trackFileRetagged') {
if (eventType === 'bookFileRetagged') {
const {
diff,
tagsScrubbed
@ -293,7 +293,7 @@ function HistoryDetails(props) {
);
}
if (eventType === 'albumImportIncomplete') {
if (eventType === 'bookImportIncomplete') {
const {
statusMessages
} = data;

@ -17,16 +17,16 @@ function getHeaderTitle(eventType) {
return 'Grabbed';
case 'downloadFailed':
return 'Download Failed';
case 'trackFileImported':
return 'Track Imported';
case 'trackFileDeleted':
return 'Track File Deleted';
case 'trackFileRenamed':
return 'Track File Renamed';
case 'trackFileRetagged':
return 'Track File Tags Updated';
case 'albumImportIncomplete':
return 'Album Import Incomplete';
case 'bookFileImported':
return 'Book Imported';
case 'bookFileDeleted':
return 'Book File Deleted';
case 'bookFileRenamed':
return 'Book File Renamed';
case 'bookFileRetagged':
return 'Book File Tags Updated';
case 'bookImportIncomplete':
return 'Book Import Incomplete';
case 'downloadImported':
return 'Download Completed';
case 'downloadIgnored':

@ -22,7 +22,7 @@ class History extends Component {
shouldComponentUpdate(nextProps) {
// Don't update when fetching has completed if items have changed,
// before albums start fetching or when albums start fetching.
// before books start fetching or when books start fetching.
if (
(
@ -30,7 +30,7 @@ class History extends Component {
nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items)
) ||
(!this.props.isAlbumsFetching && nextProps.isAlbumsFetching)
(!this.props.isBooksFetching && nextProps.isBooksFetching)
) {
return false;
}
@ -51,17 +51,17 @@ class History extends Component {
selectedFilterKey,
filters,
totalRecords,
isAlbumsFetching,
isAlbumsPopulated,
albumsError,
isBooksFetching,
isBooksPopulated,
booksError,
onFilterSelect,
onFirstPagePress,
...otherProps
} = this.props;
const isFetchingAny = isFetching || isAlbumsFetching;
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length);
const hasError = error || albumsError;
const isFetchingAny = isFetching || isBooksFetching;
const isAllPopulated = isPopulated && (isBooksPopulated || !items.length);
const hasError = error || booksError;
return (
<PageContent title="History">
@ -109,7 +109,7 @@ class History extends Component {
{
// If history isPopulated and it's empty show no history found and don't
// wait for the albums to populate because they are never coming.
// wait for the books to populate because they are never coming.
isPopulated && !hasError && !items.length &&
<div>
@ -162,9 +162,9 @@ History.propTypes = {
selectedFilterKey: PropTypes.string.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isAlbumsFetching: PropTypes.bool.isRequired,
isAlbumsPopulated: PropTypes.bool.isRequired,
albumsError: PropTypes.object,
isBooksFetching: PropTypes.bool.isRequired,
isBooksPopulated: PropTypes.bool.isRequired,
booksError: PropTypes.object,
onFilterSelect: PropTypes.func.isRequired,
onFirstPagePress: PropTypes.func.isRequired
};

@ -7,23 +7,18 @@ import hasDifferentItems from 'Utilities/Object/hasDifferentItems';
import selectUniqueIds from 'Utilities/Object/selectUniqueIds';
import withCurrentPage from 'Components/withCurrentPage';
import * as historyActions from 'Store/Actions/historyActions';
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
import { fetchTracks, clearTracks } from 'Store/Actions/trackActions';
import { fetchBooks, clearBooks } from 'Store/Actions/bookActions';
import History from './History';
function createMapStateToProps() {
return createSelector(
(state) => state.history,
(state) => state.albums,
(state) => state.tracks,
(history, albums, tracks) => {
(state) => state.books,
(history, books) => {
return {
isAlbumsFetching: albums.isFetching,
isAlbumsPopulated: albums.isPopulated,
albumsError: albums.error,
isTracksFetching: tracks.isFetching,
isTracksPopulated: tracks.isPopulated,
tracksError: tracks.error,
isBooksFetching: books.isFetching,
isBooksPopulated: books.isPopulated,
booksError: books.error,
...history
};
}
@ -32,10 +27,8 @@ function createMapStateToProps() {
const mapDispatchToProps = {
...historyActions,
fetchAlbums,
clearAlbums,
fetchTracks,
clearTracks
fetchBooks,
clearBooks
};
class HistoryConnector extends Component {
@ -62,16 +55,10 @@ class HistoryConnector extends Component {
componentDidUpdate(prevProps) {
if (hasDifferentItems(prevProps.items, this.props.items)) {
const bookIds = selectUniqueIds(this.props.items, 'bookId');
const trackIds = selectUniqueIds(this.props.items, 'trackId');
if (bookIds.length) {
this.props.fetchAlbums({ bookIds });
this.props.fetchBooks({ bookIds });
} else {
this.props.clearAlbums();
}
if (trackIds.length) {
this.props.fetchTracks({ trackIds });
} else {
this.props.clearTracks();
this.props.clearBooks();
}
}
}
@ -79,8 +66,7 @@ class HistoryConnector extends Component {
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.clearHistory();
this.props.clearAlbums();
this.props.clearTracks();
this.props.clearBooks();
}
//
@ -162,10 +148,8 @@ HistoryConnector.propTypes = {
setHistoryFilter: PropTypes.func.isRequired,
setHistoryTableOption: PropTypes.func.isRequired,
clearHistory: PropTypes.func.isRequired,
fetchAlbums: PropTypes.func.isRequired,
clearAlbums: PropTypes.func.isRequired,
fetchTracks: PropTypes.func.isRequired,
clearTracks: PropTypes.func.isRequired
fetchBooks: PropTypes.func.isRequired,
clearBooks: PropTypes.func.isRequired
};
export default withCurrentPage(

@ -9,19 +9,19 @@ function getIconName(eventType) {
switch (eventType) {
case 'grabbed':
return icons.DOWNLOADING;
case 'artistFolderImported':
case 'authorFolderImported':
return icons.DRIVE;
case 'trackFileImported':
case 'bookFileImported':
return icons.DOWNLOADED;
case 'downloadFailed':
return icons.DOWNLOADING;
case 'trackFileDeleted':
case 'bookFileDeleted':
return icons.DELETE;
case 'trackFileRenamed':
case 'bookFileRenamed':
return icons.ORGANIZE;
case 'trackFileRetagged':
case 'bookFileRetagged':
return icons.RETAG;
case 'albumImportIncomplete':
case 'bookImportIncomplete':
return icons.DOWNLOADED;
case 'downloadImported':
return icons.DOWNLOADED;
@ -36,7 +36,7 @@ function getIconKind(eventType) {
switch (eventType) {
case 'downloadFailed':
return kinds.DANGER;
case 'albumImportIncomplete':
case 'bookImportIncomplete':
return kinds.WARNING;
default:
return kinds.DEFAULT;
@ -46,25 +46,25 @@ function getIconKind(eventType) {
function getTooltip(eventType, data) {
switch (eventType) {
case 'grabbed':
return `Album grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
case 'artistFolderImported':
return 'Track imported from artist folder';
case 'trackFileImported':
return 'Track downloaded successfully and picked up from download client';
return `Book grabbed from ${data.indexer} and sent to ${data.downloadClient}`;
case 'authorFolderImported':
return 'Book imported from author folder';
case 'bookFileImported':
return 'Book downloaded successfully and picked up from download client';
case 'downloadFailed':
return 'Album download failed';
case 'trackFileDeleted':
return 'Track file deleted';
case 'trackFileRenamed':
return 'Track file renamed';
case 'trackFileRetagged':
return 'Track file tags updated';
case 'albumImportIncomplete':
return 'Book download failed';
case 'bookFileDeleted':
return 'Book file deleted';
case 'bookFileRenamed':
return 'Book file renamed';
case 'bookFileRetagged':
return 'Book file tags updated';
case 'bookImportIncomplete':
return 'Files downloaded but not all could be imported';
case 'downloadImported':
return 'Download completed and successfully imported';
case 'downloadIgnored':
return 'Album Download Ignored';
return 'Book Download Ignored';
default:
return 'Unknown event';
}

@ -5,9 +5,9 @@ import IconButton from 'Components/Link/IconButton';
import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import TrackQuality from 'Album/TrackQuality';
import ArtistNameLink from 'Artist/ArtistNameLink';
import BookTitleLink from 'Book/BookTitleLink';
import BookQuality from 'Book/BookQuality';
import AuthorNameLink from 'Author/AuthorNameLink';
import HistoryEventTypeCell from './HistoryEventTypeCell';
import HistoryDetailsModal from './Details/HistoryDetailsModal';
import styles from './HistoryRow.css';
@ -51,8 +51,8 @@ class HistoryRow extends Component {
render() {
const {
artist,
album,
author,
book,
quality,
qualityCutoffNotMet,
eventType,
@ -66,7 +66,7 @@ class HistoryRow extends Component {
onMarkAsFailedPress
} = this.props;
if (!artist || !album) {
if (!author || !book) {
return null;
}
@ -96,9 +96,9 @@ class HistoryRow extends Component {
if (name === 'authors.sortName') {
return (
<TableRowCell key={name}>
<ArtistNameLink
titleSlug={artist.titleSlug}
artistName={artist.artistName}
<AuthorNameLink
titleSlug={author.titleSlug}
authorName={author.authorName}
/>
</TableRowCell>
);
@ -107,10 +107,10 @@ class HistoryRow extends Component {
if (name === 'books.title') {
return (
<TableRowCell key={name}>
<AlbumTitleLink
titleSlug={album.titleSlug}
title={album.title}
disambiguation={album.disambiguation}
<BookTitleLink
titleSlug={book.titleSlug}
title={book.title}
disambiguation={book.disambiguation}
/>
</TableRowCell>
);
@ -119,7 +119,7 @@ class HistoryRow extends Component {
if (name === 'quality') {
return (
<TableRowCell key={name}>
<TrackQuality
<BookQuality
quality={quality}
isCutoffMet={qualityCutoffNotMet}
/>
@ -206,8 +206,8 @@ class HistoryRow extends Component {
HistoryRow.propTypes = {
bookId: PropTypes.number,
artist: PropTypes.object.isRequired,
album: PropTypes.object,
author: PropTypes.object.isRequired,
book: PropTypes.object,
quality: PropTypes.object.isRequired,
qualityCutoffNotMet: PropTypes.bool.isRequired,
eventType: PropTypes.string.isRequired,

@ -3,23 +3,20 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAlbumSelector from 'Store/Selectors/createAlbumSelector';
import createTrackSelector from 'Store/Selectors/createTrackSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createBookSelector from 'Store/Selectors/createBookSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import HistoryRow from './HistoryRow';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createAlbumSelector(),
createTrackSelector(),
createAuthorSelector(),
createBookSelector(),
createUISettingsSelector(),
(artist, album, track, uiSettings) => {
(author, book, uiSettings) => {
return {
artist,
album,
track,
author,
book,
shortDateFormat: uiSettings.shortDateFormat,
timeFormat: uiSettings.timeFormat
};

@ -42,7 +42,7 @@ class Queue extends Component {
shouldComponentUpdate(nextProps) {
// Don't update when fetching has completed if items have changed,
// before albums start fetching or when albums start fetching.
// before books start fetching or when books start fetching.
if (
this.props.isFetching &&
@ -53,7 +53,7 @@ class Queue extends Component {
return false;
}
if (!this.props.isAlbumsFetching && nextProps.isAlbumsFetching) {
if (!this.props.isBooksFetching && nextProps.isBooksFetching) {
return false;
}
@ -125,9 +125,9 @@ class Queue extends Component {
isPopulated,
error,
items,
isAlbumsFetching,
isAlbumsPopulated,
albumsError,
isBooksFetching,
isBooksPopulated,
booksError,
columns,
totalRecords,
isGrabbing,
@ -145,9 +145,9 @@ class Queue extends Component {
isPendingSelected
} = this.state;
const isRefreshing = isFetching || isAlbumsFetching || isRefreshMonitoredDownloadsExecuting;
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length || items.every((e) => !e.bookId));
const hasError = error || albumsError;
const isRefreshing = isFetching || isBooksFetching || isRefreshMonitoredDownloadsExecuting;
const isAllPopulated = isPopulated && (isBooksPopulated || !items.length || items.every((e) => !e.bookId));
const hasError = error || booksError;
const selectedIds = this.getSelectedIds();
const selectedCount = selectedIds.length;
const disableSelectedActions = selectedCount === 0;
@ -280,9 +280,9 @@ Queue.propTypes = {
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
isAlbumsFetching: PropTypes.bool.isRequired,
isAlbumsPopulated: PropTypes.bool.isRequired,
albumsError: PropTypes.object,
isBooksFetching: PropTypes.bool.isRequired,
isBooksPopulated: PropTypes.bool.isRequired,
booksError: PropTypes.object,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number,
isGrabbing: PropTypes.bool.isRequired,

@ -9,21 +9,21 @@ import withCurrentPage from 'Components/withCurrentPage';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as queueActions from 'Store/Actions/queueActions';
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
import { fetchBooks, clearBooks } from 'Store/Actions/bookActions';
import * as commandNames from 'Commands/commandNames';
import Queue from './Queue';
function createMapStateToProps() {
return createSelector(
(state) => state.albums,
(state) => state.books,
(state) => state.queue.options,
(state) => state.queue.paged,
createCommandExecutingSelector(commandNames.REFRESH_MONITORED_DOWNLOADS),
(albums, options, queue, isRefreshMonitoredDownloadsExecuting) => {
(books, options, queue, isRefreshMonitoredDownloadsExecuting) => {
return {
isAlbumsFetching: albums.isFetching,
isAlbumsPopulated: albums.isPopulated,
albumsError: albums.error,
isBooksFetching: books.isFetching,
isBooksPopulated: books.isPopulated,
booksError: books.error,
isRefreshMonitoredDownloadsExecuting,
...options,
...queue
@ -34,8 +34,8 @@ function createMapStateToProps() {
const mapDispatchToProps = {
...queueActions,
fetchAlbums,
clearAlbums,
fetchBooks,
clearBooks,
executeCommand
};
@ -65,15 +65,15 @@ class QueueConnector extends Component {
const bookIds = selectUniqueIds(this.props.items, 'bookId');
if (bookIds.length) {
this.props.fetchAlbums({ bookIds });
this.props.fetchBooks({ bookIds });
} else {
this.props.clearAlbums();
this.props.clearBooks();
}
}
if (
this.props.includeUnknownArtistItems !==
prevProps.includeUnknownArtistItems
this.props.includeUnknownAuthorItems !==
prevProps.includeUnknownAuthorItems
) {
this.repopulate();
}
@ -82,7 +82,7 @@ class QueueConnector extends Component {
componentWillUnmount() {
unregisterPagePopulator(this.repopulate);
this.props.clearQueue();
this.props.clearAlbums();
this.props.clearBooks();
}
//
@ -166,7 +166,7 @@ class QueueConnector extends Component {
QueueConnector.propTypes = {
useCurrentPage: PropTypes.bool.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
includeUnknownArtistItems: PropTypes.bool.isRequired,
includeUnknownAuthorItems: PropTypes.bool.isRequired,
fetchQueue: PropTypes.func.isRequired,
gotoQueueFirstPage: PropTypes.func.isRequired,
gotoQueuePreviousPage: PropTypes.func.isRequired,
@ -178,8 +178,8 @@ QueueConnector.propTypes = {
clearQueue: PropTypes.func.isRequired,
grabQueueItems: PropTypes.func.isRequired,
removeQueueItems: PropTypes.func.isRequired,
fetchAlbums: PropTypes.func.isRequired,
clearAlbums: PropTypes.func.isRequired,
fetchBooks: PropTypes.func.isRequired,
clearBooks: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};

@ -76,7 +76,7 @@ function QueueDetails(props) {
return (
<Icon
name={icons.DOWNLOADING}
title={`Album is downloading - ${progress.toFixed(1)}% ${title}`}
title={`Book is downloading - ${progress.toFixed(1)}% ${title}`}
/>
);
}

@ -14,18 +14,18 @@ class QueueOptions extends Component {
super(props, context);
this.state = {
includeUnknownArtistItems: props.includeUnknownArtistItems
includeUnknownAuthorItems: props.includeUnknownAuthorItems
};
}
componentDidUpdate(prevProps) {
const {
includeUnknownArtistItems
includeUnknownAuthorItems
} = this.props;
if (includeUnknownArtistItems !== prevProps.includeUnknownArtistItems) {
if (includeUnknownAuthorItems !== prevProps.includeUnknownAuthorItems) {
this.setState({
includeUnknownArtistItems
includeUnknownAuthorItems
});
}
}
@ -48,19 +48,19 @@ class QueueOptions extends Component {
render() {
const {
includeUnknownArtistItems
includeUnknownAuthorItems
} = this.state;
return (
<Fragment>
<FormGroup>
<FormLabel>Show Unknown Artist Items</FormLabel>
<FormLabel>Show Unknown Author Items</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="includeUnknownArtistItems"
value={includeUnknownArtistItems}
helpText="Show items without a artist in the queue, this could include removed artists, movies or anything else in Readarr's category"
name="includeUnknownAuthorItems"
value={includeUnknownAuthorItems}
helpText="Show items without a author in the queue, this could include removed authors, movies or anything else in Readarr's category"
onChange={this.onOptionChange}
/>
</FormGroup>
@ -70,7 +70,7 @@ class QueueOptions extends Component {
}
QueueOptions.propTypes = {
includeUnknownArtistItems: PropTypes.bool.isRequired,
includeUnknownAuthorItems: PropTypes.bool.isRequired,
onOptionChange: PropTypes.func.isRequired
};

@ -11,10 +11,10 @@ import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import ProtocolLabel from 'Activity/Queue/ProtocolLabel';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import TrackQuality from 'Album/TrackQuality';
import BookTitleLink from 'Book/BookTitleLink';
import BookQuality from 'Book/BookQuality';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import ArtistNameLink from 'Artist/ArtistNameLink';
import AuthorNameLink from 'Author/AuthorNameLink';
import QueueStatusCell from './QueueStatusCell';
import TimeleftCell from './TimeleftCell';
import RemoveQueueItemModal from './RemoveQueueItemModal';
@ -71,8 +71,8 @@ class QueueRow extends Component {
trackedDownloadState,
statusMessages,
errorMessage,
artist,
album,
author,
book,
quality,
protocol,
indexer,
@ -141,10 +141,10 @@ class QueueRow extends Component {
return (
<TableRowCell key={name}>
{
artist ?
<ArtistNameLink
titleSlug={artist.titleSlug}
artistName={artist.artistName}
author ?
<AuthorNameLink
titleSlug={author.titleSlug}
authorName={author.authorName}
/> :
title
}
@ -156,11 +156,11 @@ class QueueRow extends Component {
return (
<TableRowCell key={name}>
{
album ?
<AlbumTitleLink
titleSlug={album.titleSlug}
title={album.title}
disambiguation={album.disambiguation}
book ?
<BookTitleLink
titleSlug={book.titleSlug}
title={book.title}
disambiguation={book.disambiguation}
/> :
'-'
}
@ -169,11 +169,11 @@ class QueueRow extends Component {
}
if (name === 'books.releaseDate') {
if (album) {
if (book) {
return (
<RelativeDateCellConnector
key={name}
date={album.releaseDate}
date={book.releaseDate}
/>
);
}
@ -188,7 +188,7 @@ class QueueRow extends Component {
if (name === 'quality') {
return (
<TableRowCell key={name}>
<TrackQuality
<BookQuality
quality={quality}
/>
</TableRowCell>
@ -335,7 +335,7 @@ class QueueRow extends Component {
<RemoveQueueItemModal
isOpen={isRemoveQueueItemModalOpen}
sourceTitle={title}
canIgnore={!!(artist && album)}
canIgnore={!!(author && book)}
onRemovePress={this.onRemoveQueueItemModalConfirmed}
onModalClose={this.onRemoveQueueItemModalClose}
/>
@ -354,8 +354,8 @@ QueueRow.propTypes = {
trackedDownloadState: PropTypes.string,
statusMessages: PropTypes.arrayOf(PropTypes.object),
errorMessage: PropTypes.string,
artist: PropTypes.object,
album: PropTypes.object,
author: PropTypes.object,
book: PropTypes.object,
quality: PropTypes.object.isRequired,
protocol: PropTypes.string.isRequired,
indexer: PropTypes.string,

@ -4,25 +4,25 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { grabQueueItem, removeQueueItem } from 'Store/Actions/queueActions';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAlbumSelector from 'Store/Selectors/createAlbumSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createBookSelector from 'Store/Selectors/createBookSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import QueueRow from './QueueRow';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createAlbumSelector(),
createAuthorSelector(),
createBookSelector(),
createUISettingsSelector(),
(artist, album, uiSettings) => {
(author, book, uiSettings) => {
const result = _.pick(uiSettings, [
'showRelativeDates',
'shortDateFormat',
'timeFormat'
]);
result.artist = artist;
result.album = album;
result.author = author;
result.book = book;
return result;
}
@ -63,7 +63,7 @@ class QueueRowConnector extends Component {
QueueRowConnector.propTypes = {
id: PropTypes.number.isRequired,
album: PropTypes.object,
book: PropTypes.object,
grabQueueItem: PropTypes.func.isRequired,
removeQueueItem: PropTypes.func.isRequired
};

@ -9,8 +9,8 @@ function createMapStateToProps() {
return createSelector(
(state) => state.app,
(state) => state.queue.status,
(state) => state.queue.options.includeUnknownArtistItems,
(app, status, includeUnknownArtistItems) => {
(state) => state.queue.options.includeUnknownAuthorItems,
(app, status, includeUnknownAuthorItems) => {
const {
errors,
warnings,
@ -25,9 +25,9 @@ function createMapStateToProps() {
isReconnecting: app.isReconnecting,
isPopulated: status.isPopulated,
...status.item,
count: includeUnknownArtistItems ? totalCount : count,
errors: includeUnknownArtistItems ? errors || unknownErrors : errors,
warnings: includeUnknownArtistItems ? warnings || unknownWarnings : warnings
count: includeUnknownAuthorItems ? totalCount : count,
errors: includeUnknownAuthorItems ? errors || unknownErrors : errors,
warnings: includeUnknownAuthorItems ? warnings || unknownWarnings : warnings
};
}
);

@ -1,6 +1,6 @@
import React from 'react';
function ArtistMetadataProfilePopoverContent() {
function AuthorMetadataProfilePopoverContent() {
return (
<div>
Select 'None' to only include items manually added via search or that match files on disk
@ -8,4 +8,4 @@ function ArtistMetadataProfilePopoverContent() {
);
}
export default ArtistMetadataProfilePopoverContent;
export default AuthorMetadataProfilePopoverContent;

@ -2,7 +2,7 @@ import React from 'react';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
function ArtistMonitoringOptionsPopoverContent() {
function AuthorMonitoringOptionsPopoverContent() {
return (
<DescriptionList>
<DescriptionListItem
@ -43,4 +43,4 @@ function ArtistMonitoringOptionsPopoverContent() {
);
}
export default ArtistMonitoringOptionsPopoverContent;
export default AuthorMonitoringOptionsPopoverContent;

@ -1,116 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions';
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
import AlbumStudio from './AlbumStudio';
function createAlbumFetchStateSelector() {
return createSelector(
(state) => state.albums.items.length,
(state) => state.albums.isFetching,
(state) => state.albums.isPopulated,
(length, isFetching, isPopulated) => {
const albumCount = (!isFetching && isPopulated) ? length : 0;
return {
albumCount,
isFetching,
isPopulated
};
}
);
}
function createMapStateToProps() {
return createSelector(
createAlbumFetchStateSelector(),
createArtistClientSideCollectionItemsSelector('albumStudio'),
createDimensionsSelector(),
(albums, artist, dimensionsState) => {
const isPopulated = albums.isPopulated && artist.isPopulated;
const isFetching = artist.isFetching || albums.isFetching;
return {
...artist,
isPopulated,
isFetching,
albumCount: albums.albumCount,
isSmallScreen: dimensionsState.isSmallScreen
};
}
);
}
const mapDispatchToProps = {
fetchAlbums,
clearAlbums,
setAlbumStudioSort,
setAlbumStudioFilter,
saveAlbumStudio
};
class AlbumStudioConnector extends Component {
//
// Lifecycle
componentDidMount() {
this.populate();
}
componentWillUnmount() {
this.unpopulate();
}
//
// Control
populate = () => {
this.props.fetchAlbums();
}
unpopulate = () => {
this.props.clearAlbums();
}
//
// Listeners
onSortPress = (sortKey) => {
this.props.setAlbumStudioSort({ sortKey });
}
onFilterSelect = (selectedFilterKey) => {
this.props.setAlbumStudioFilter({ selectedFilterKey });
}
onUpdateSelectedPress = (payload) => {
this.props.saveAlbumStudio(payload);
}
//
// Render
render() {
return (
<AlbumStudio
{...this.props}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
onUpdateSelectedPress={this.onUpdateSelectedPress}
/>
);
}
}
AlbumStudioConnector.propTypes = {
setAlbumStudioSort: PropTypes.func.isRequired,
setAlbumStudioFilter: PropTypes.func.isRequired,
fetchAlbums: PropTypes.func.isRequired,
clearAlbums: PropTypes.func.isRequired,
saveAlbumStudio: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioConnector);

@ -1,94 +0,0 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
import { toggleAlbumsMonitored } from 'Store/Actions/albumActions';
import AlbumStudioRow from './AlbumStudioRow';
// Use a const to share the reselect cache between instances
const getAlbumMap = createSelector(
(state) => state.albums.items,
(albums) => {
return albums.reduce((acc, curr) => {
(acc[curr.authorId] = acc[curr.authorId] || []).push(curr);
return acc;
}, {});
}
);
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
getAlbumMap,
(artist, albumMap) => {
const albumsInArtist = albumMap.hasOwnProperty(artist.id) ? albumMap[artist.id] : [];
const sortedAlbums = _.orderBy(albumsInArtist, 'releaseDate', 'desc');
return {
...artist,
authorId: artist.id,
artistName: artist.artistName,
monitored: artist.monitored,
status: artist.status,
isSaving: artist.isSaving,
albums: sortedAlbums
};
}
);
}
const mapDispatchToProps = {
toggleArtistMonitored,
toggleAlbumsMonitored
};
class AlbumStudioRowConnector extends Component {
//
// Listeners
onArtistMonitoredPress = () => {
const {
authorId,
monitored
} = this.props;
this.props.toggleArtistMonitored({
authorId,
monitored: !monitored
});
}
onAlbumMonitoredPress = (bookId, monitored) => {
const bookIds = [bookId];
this.props.toggleAlbumsMonitored({
bookIds,
monitored
});
}
//
// Render
render() {
return (
<AlbumStudioRow
{...this.props}
onArtistMonitoredPress={this.onArtistMonitoredPress}
onAlbumMonitoredPress={this.onAlbumMonitoredPress}
/>
);
}
}
AlbumStudioRowConnector.propTypes = {
authorId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
toggleArtistMonitored: PropTypes.func.isRequired,
toggleAlbumsMonitored: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector);

@ -4,13 +4,13 @@ import { Route, Redirect } from 'react-router-dom';
import getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch';
import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector';
import AuthorIndexConnector from 'Author/Index/AuthorIndexConnector';
import AddNewItemConnector from 'Search/AddNewItemConnector';
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
import AuthorEditorConnector from 'Author/Editor/AuthorEditorConnector';
import BookshelfConnector from 'Bookshelf/BookshelfConnector';
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector';
import AlbumDetailsPageConnector from 'Album/Details/AlbumDetailsPageConnector';
import AuthorDetailsPageConnector from 'Author/Details/AuthorDetailsPageConnector';
import BookDetailsPageConnector from 'Book/Details/BookDetailsPageConnector';
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import HistoryConnector from 'Activity/History/HistoryConnector';
import QueueConnector from 'Activity/Queue/QueueConnector';
@ -44,13 +44,13 @@ function AppRoutes(props) {
return (
<Switch>
{/*
Artist
Author
*/}
<Route
exact={true}
path="/"
component={ArtistIndexConnector}
component={AuthorIndexConnector}
/>
{
@ -76,13 +76,13 @@ function AppRoutes(props) {
/>
<Route
path="/artisteditor"
component={ArtistEditorConnector}
path="/authoreditor"
component={AuthorEditorConnector}
/>
<Route
path="/albumstudio"
component={AlbumStudioConnector}
path="/bookshelf"
component={BookshelfConnector}
/>
<Route
@ -92,12 +92,12 @@ function AppRoutes(props) {
<Route
path="/author/:titleSlug"
component={ArtistDetailsPageConnector}
component={AuthorDetailsPageConnector}
/>
<Route
path="/book/:titleSlug"
component={AlbumDetailsPageConnector}
component={BookDetailsPageConnector}
/>
{/*

@ -1,20 +0,0 @@
/* eslint max-params: 0 */
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createTrackFileSelector from 'Store/Selectors/createTrackFileSelector';
import AlbumRow from './AlbumRow';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createTrackFileSelector(),
(artist = {}, trackFile) => {
return {
artistMonitored: artist.monitored,
trackFilePath: trackFile ? trackFile.path : null
};
}
);
}
export default connect(createMapStateToProps)(AlbumRow);

@ -1,5 +0,0 @@
.albumFolder {
composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 150px;
}

@ -1,21 +0,0 @@
import React from 'react';
import ArtistHistoryContentConnector from 'Artist/History/ArtistHistoryContentConnector';
import ArtistHistoryTableContent from 'Artist/History/ArtistHistoryTableContent';
function ArtistHistoryTable(props) {
const {
...otherProps
} = props;
return (
<ArtistHistoryContentConnector
component={ArtistHistoryTableContent}
{...otherProps}
/>
);
}
ArtistHistoryTable.propTypes = {
};
export default ArtistHistoryTable;

@ -1,102 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons } from 'Helpers/Props';
import IconButton from 'Components/Link/IconButton';
import SpinnerIconButton from 'Components/Link/SpinnerIconButton';
import VirtualTableRowCell from 'Components/Table/Cells/VirtualTableRowCell';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
class ArtistIndexActionsCell extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
isEditArtistModalOpen: false,
isDeleteArtistModalOpen: false
};
}
//
// Listeners
onEditArtistPress = () => {
this.setState({ isEditArtistModalOpen: true });
}
onEditArtistModalClose = () => {
this.setState({ isEditArtistModalOpen: false });
}
onDeleteArtistPress = () => {
this.setState({
isEditArtistModalOpen: false,
isDeleteArtistModalOpen: true
});
}
onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false });
}
//
// Render
render() {
const {
id,
isRefreshingArtist,
onRefreshArtistPress,
...otherProps
} = this.props;
const {
isEditArtistModalOpen,
isDeleteArtistModalOpen
} = this.state;
return (
<VirtualTableRowCell
{...otherProps}
>
<SpinnerIconButton
name={icons.REFRESH}
title="Refresh Artist"
isSpinning={isRefreshingArtist}
onPress={onRefreshArtistPress}
/>
<IconButton
name={icons.EDIT}
title="Edit Artist"
onPress={this.onEditArtistPress}
/>
<EditArtistModalConnector
isOpen={isEditArtistModalOpen}
authorId={id}
onModalClose={this.onEditArtistModalClose}
onDeleteArtistPress={this.onDeleteArtistPress}
/>
<DeleteArtistModal
isOpen={isDeleteArtistModalOpen}
authorId={id}
onModalClose={this.onDeleteArtistModalClose}
/>
</VirtualTableRowCell>
);
}
}
ArtistIndexActionsCell.propTypes = {
id: PropTypes.number.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired
};
export default ArtistIndexActionsCell;

@ -1,13 +0,0 @@
import { connect } from 'react-redux';
import { setArtistTableOption } from 'Store/Actions/artistIndexActions';
import ArtistIndexHeader from './ArtistIndexHeader';
function createMapDispatchToProps(dispatch, props) {
return {
onTableOptionChange(payload) {
dispatch(setArtistTableOption(payload));
}
};
}
export default connect(undefined, createMapDispatchToProps)(ArtistIndexHeader);

@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import React from 'react';
import ArtistImage from './ArtistImage';
import AuthorImage from './AuthorImage';
const bannerPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+gAAAC5AgMAAADG9/24AAAADFBMVEUyMjI7Ozs1NTU4ODjgOsZvAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QkRBgAc5PUQ8QAAB7tJREFUeNrtnb1rHEsMwMdjArtrUrpf3uMg2cOl+5QpXWRvjQkXlyHVK8MVYXFl3F+/GAzrNfdSuXkQnH9ie/Oqw32aFM6bkTQfd85rHrwi0qjJJql+pxlJo9FISv0XqX8mKkmSJEmSJEmSJEmSJEmSJEmS5NeVYrh7HDqJ5DeY23kQB64/u7zW91amzgXqvRqjfGYvarnfxqncuaQlf72Zxv4gSOluudOfjRy1Hzh1L+nPj+KU3jh0MWr3Sp87dDFq98An/msmg3zPW/aFR6/vRaBPglML6Mci0H0g92MYfrjvRgJ5TrDvhuFyGIZv9NdTOev93dDry1Z59mM56/2hU8WZcefFjZgVT/Z90SlEV9VKio3HeKYZLLSGII6WPP+oBv3ZwkL3iE4JG/ZRjUYb16mAripUO/c4Hl3bd/juCF19FuHeJn6nK90VBh0s3SjBvcFW/wTrvfBa118EbHY8qmMOtmgdupoKOLTvAmOH1k059LKAX+Qra/TncExH4hcBXV3Zf/+Dv5Vb43cfod/wt3PLsN5VF6HDiudt56IbB23Ql5/oZ8A7CfZWbgF61sap62XdlOZTZ2rF3c7ltNW1+f7LuDez/uc6uDfO8dwkbPXcKF8vPS9sds527pC2utH0uCb0Jmz2t8wNvN3q2jj4ntDJna94m3g4sb7HH8Gue0RH4Je8z61g4GGr79Wz1qHjbi94m/jcH1IOccsj+lt/sOFr4m0EPz+3XyNueURvSmfn+Ebx1rctMvOxU8foqOwVa+9mfdvHDH+DdYQOxAesvZslvc/wo4vQZy6eY+vdrCVrut/AyTW9CWvO3E1ra4L6i5Fxoka7MDan45vTOmx2MPGc0QH52Tb6kTPxXNGtW5/Tnl+oGP2N/dstY8eeO2M+bqM3zvVxRT+gS8Vdl5/z6CaCzfx/c41o3pP2+030UzrAHDNGv8d4FvMVAf1I4c07V/QlnNsyG9TN23ID/ZjObnO+6CZmydS+y8oG9Dfk2Gd80WcW3Rv44e5bZOLtD8EUHbRq0V0F/DAMn8fQ2UUv2UayEMxplaFvM0G7QR/ufqBcmH/gG85Z9BM8rMO5bdiUDu4ceaLvUjqWfJveQm8hpvnKFv1jOLxUTtkoBWf0nIK5Cd6wO207dAznTlmjH+K630KvuKPbOHYffJsOexykx0iWJ7pNRf+ttEXvW1U49F7ZbJ26RHSe6ehn5NRGMPBVQAfP12EQf8QZPTMxXac30M1RxtaWXLBFn3j0eRsHNIBuCycLtqfWCQZrBcZ0W+gVhXts0RtEXyit4zDOoE/N/5yNzNF3oVB0A93I6ise7bijr1XwbYiurySg7xjfpp+g375mjj5D9HYD3fr6YvkKcxU80Q8duvVt2+gjob/ljX7yFH1a80dvDfpia8GXqp3Wr1XJXevljvFt2QZ6a6tJ2Gvd2Pa8Xuu2iNB7o++r+lUJlQas93oOl04be73Ut7y1Ts4tn/0EfaxZOzcKaXIsqNiI4QtE5x7N7Z08RZ9CKpb38cWs9V26b4sDWURnr3W9foq+gpM8a3S4V+rhKUCcqrBXTufMUxU2QWUTkFtZGls3pjgnqJ4FdB1lZAd8/qMEZGRtAlJvJqONb2syzuj2CuK+1YU5reg2CzGNyqq6fpOpku8VRI7lchX+7SIy8NdQSaKn3K8bsWJOlZGBX2Ed0bUIdBXftJpzG1h2vjetWFqgHXrhczTWtx2h8llXVTytI4FHnaes0bGMqNhC1+jUXmFMx7iCaq6qy4HqxR7dFfMUtc34LQCVDPppcM13Kie5RmTGJYNUKBraDL57xEKalS0UzTgXiiqn1X3fRxR0bBf6TLEvD4aKkslTdO5F4fQUIHseo5ugfrShewbWjvsriHxT65WAByAHVCT7u0fvoKDC+rZClZyf/eSAngUTj1pfCXjshU/8smDiPTr7J374sDPDfI1Hd4cX1g874TmvRc+30U8V9+e88Ig7U6V26Ofk21rzg1ScH3Hj033bTXIZab2iGG6PdWOaQzLx7cShQ0FFfdxqlfFu2DAhxw4vf5zWV3hYZ96m47l7u0+DMAgdu1Dxbs4St+Rx6MbAwzIveLfk8Y2Y9J5HP1tij3DmjZii9lujQy9GXO/M22/5pmvUVdWiV3RkYd50zbfaI7Vb9GmDD0C4t9qLGiy+JPTVKT4A4d5gMY866N4i+p9KuUM767aavpkqLnmDDl10S8W/mSq20O3I3n8x6AXufP4tdKlxcpkZ4HN43FZ0mT3GCmicTO2ysW2uVXmFWp/yb5cdN0kHrb/ADwFN0uPW+ICOt+0SWuPjkW2tPTr+CjcSphjiGIzWodPQGwljMGj4Se/Q0bfJGH4Sj7zx6DJG3tCgo4HQoYiukDHoKB5vZdCtb9MrIUM7DyK169Zu+mEUMtQsjLJrtT7vWl2IGWXnBxjaFwFnraQBhmFs5dDqi66SNLYyGlY6XMgaVip4RG00mHh2LWwwcRhHPVsJG0cdhpDProQNIQ+j52e30kbPa19B5T64H9Wfqn0mTelB7Y04pWMFfCQf5JDTjYOTuSClu5QUSa9EyU0gf5BF7iaP2zxdq6TJjUydgxTD3aPvm5wkSZIkSZIkSZIkSZIkSZIk+Z9kv/534U2+Uyf0XwP9H83PZBlAqkdjAAAAAElFTkSuQmCC';
function ArtistBanner(props) {
function AuthorBanner(props) {
return (
<ArtistImage
<AuthorImage
{...props}
coverType="banner"
placeholder={bannerPlaceholder}
@ -14,12 +14,12 @@ function ArtistBanner(props) {
);
}
ArtistBanner.propTypes = {
AuthorBanner.propTypes = {
size: PropTypes.number.isRequired
};
ArtistBanner.defaultProps = {
AuthorBanner.defaultProps = {
size: 70
};
export default ArtistBanner;
export default AuthorBanner;

@ -17,7 +17,7 @@ function getUrl(image, coverType, size) {
}
}
class ArtistImage extends Component {
class AuthorImage extends Component {
//
// Lifecycle
@ -179,7 +179,7 @@ class ArtistImage extends Component {
}
}
ArtistImage.propTypes = {
AuthorImage.propTypes = {
className: PropTypes.string,
style: PropTypes.object,
images: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -192,10 +192,10 @@ ArtistImage.propTypes = {
onLoad: PropTypes.func
};
ArtistImage.defaultProps = {
AuthorImage.defaultProps = {
size: 250,
lazy: true,
overflow: false
};
export default ArtistImage;
export default AuthorImage;

@ -2,19 +2,19 @@ import PropTypes from 'prop-types';
import React from 'react';
import Link from 'Components/Link/Link';
function ArtistNameLink({ titleSlug, artistName }) {
function AuthorNameLink({ titleSlug, authorName }) {
const link = `/author/${titleSlug}`;
return (
<Link to={link}>
{artistName}
{authorName}
</Link>
);
}
ArtistNameLink.propTypes = {
AuthorNameLink.propTypes = {
titleSlug: PropTypes.string.isRequired,
artistName: PropTypes.string.isRequired
authorName: PropTypes.string.isRequired
};
export default ArtistNameLink;
export default AuthorNameLink;

@ -1,12 +1,12 @@
import PropTypes from 'prop-types';
import React from 'react';
import ArtistImage from './ArtistImage';
import AuthorImage from './AuthorImage';
const posterPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAPcAAAD3AQMAAAD7QlAQAAAABlBMVEUnJychISEIs8G4AAAEFklEQVRYw+2YMdPOQBDH95KQDEVSMKOj1FFQJx9BQc0nkeuUvoLS+AQ6MQqlRu8xCjpUgsjK7iXz2t1LMsbo8i/ey5vfc3e7m7tk9+DQoUOHDpGe3bu7hS8BwJ117BoAOLfOb/Hf62s4EY1VNrcPVvjNua1WuJ/b8xqoeR3sqFkllx8+AYAra9PniDg1ydr07cT7FQMy6k7ycQMKgJr5F4BrhvI9ZA3xCDU8fJggs9gBXJ35acX8lil74CPmO5w1xhwoIMVFMQcqKCfynH3soLLuEfkB4O5TBArDPZlH05ZkYMxBigyJDEyseylHFjjK4CzPyS4IE3gTgIxuAyulHzbG/as0PYsifM24X8/TA19Vxn2efjagNwFoHE2/GDAKpm86HE2AfMrmLQbqADnI2bzFQPv8y7NlM7naORU+uid+62X4xJg0V6PC1+KfvvSghWMgnh0cVIArCO694Ib+qWR4HQ257F9oRxu+L2FpzK3h7D5vPwqA5k1OPOwA4iaAOYWnZM4XPhPYT3eWDXriX4sHROjpskF7cC2eBHfUdVjeDw6/4Uk9oHqEz18DH9se8IvgCdQDBS/oLUxcPcB24mnAv+jfXvCMOdwI9jNXDxiJp9w9DCd4Afgdz96fF5GGk3xSCFBHw+gF4PAz9SQCwE7K5UGculJHGuTdKPun+IYHrafAUPfPKJdP4OhL7ErDuf9jfnXn6Gu6+Kj654EPKQIG7iu5PMLacGPO7Qf0EOMvx3LhhRh/5l+GOsahnPkw4Mw7sXzLedzxV+DvscsMZ8X51W0Olp/+5P7qIPlLPMEWP+3z5G94rXinuen/RWzAbe6g7hVvRX/DO8FdjMPB9+O3yD5fwf1fc72+/jcfN/cHRPZPJva/7q/27z9zlPyVfL9Abrgv/oW/Nvyx5vL9rbl5f78R/I3iTnP7fRH83QjVDpfCb4Kr71uxz1FzkN9nxfX32XKVHyj+BfweV/mJkM5Pdnkpsc6PfK64BynDM8lTiU1+l+LPP2iLUJj8sj5z3uaXgMPZFDY/rQDHs/rLTRxMfkwx4mX4hPLjaza/TgIfI/l1xvl5y/wT5+dSCd8rmXf8W2/qgx5S5rRYvAMlri+Ic2MKME9FCdQT/wJ8Ga1vSnzE+Z3l06REJi7qI1VfOXw0xusrCPVZ+6aP12dFqO/qN6d4fZeF+rB804X6sInXl/lrT1vBFtAu1KcuCfWpi9e33VLfJjZAS33ckvlZpH4uedu2nOcWhleiPr9peLFT32fyfGD7fMGBlf/jfCLZOd8oIrw6q4/o2jogzlc2z2fAW8w2nwvd3eqp0YXxCcdiS1HzRC8fw2ezJjvHVtn2tPbhqnOzTgNp1/kdv6pV7ig4RQOruuDBCax1+94dOHTo0KFDk34DoJynpPus3GIAAAAASUVORK5CYII=';
function ArtistPoster(props) {
function AuthorPoster(props) {
return (
<ArtistImage
<AuthorImage
{...props}
coverType="poster"
placeholder={posterPlaceholder}
@ -14,12 +14,12 @@ function ArtistPoster(props) {
);
}
ArtistPoster.propTypes = {
AuthorPoster.propTypes = {
size: PropTypes.number.isRequired
};
ArtistPoster.defaultProps = {
AuthorPoster.defaultProps = {
size: 250
};
export default ArtistPoster;
export default AuthorPoster;

@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import { sizes } from 'Helpers/Props';
import Modal from 'Components/Modal/Modal';
import DeleteArtistModalContentConnector from './DeleteArtistModalContentConnector';
import DeleteAuthorModalContentConnector from './DeleteAuthorModalContentConnector';
function DeleteArtistModal(props) {
function DeleteAuthorModal(props) {
const {
isOpen,
onModalClose,
@ -17,7 +17,7 @@ function DeleteArtistModal(props) {
size={sizes.MEDIUM}
onModalClose={onModalClose}
>
<DeleteArtistModalContentConnector
<DeleteAuthorModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@ -25,9 +25,9 @@ function DeleteArtistModal(props) {
);
}
DeleteArtistModal.propTypes = {
DeleteAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default DeleteArtistModal;
export default DeleteAuthorModal;

@ -11,9 +11,9 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './DeleteArtistModalContent.css';
import styles from './DeleteAuthorModalContent.css';
class DeleteArtistModalContent extends Component {
class DeleteAuthorModalContent extends Component {
//
// Lifecycle
@ -38,7 +38,7 @@ class DeleteArtistModalContent extends Component {
this.setState({ addImportListExclusion: value });
}
onDeleteArtistConfirmed = () => {
onDeleteAuthorConfirmed = () => {
const deleteFiles = this.state.deleteFiles;
const addImportListExclusion = this.state.addImportListExclusion;
@ -52,26 +52,26 @@ class DeleteArtistModalContent extends Component {
render() {
const {
artistName,
authorName,
path,
statistics,
onModalClose
} = this.props;
const {
trackFileCount,
bookFileCount,
sizeOnDisk
} = statistics;
const deleteFiles = this.state.deleteFiles;
const addImportListExclusion = this.state.addImportListExclusion;
let deleteFilesLabel = `Delete ${trackFileCount} Track Files`;
let deleteFilesHelpText = 'Delete the track files and artist folder';
let deleteFilesLabel = `Delete ${bookFileCount} Book Files`;
let deleteFilesHelpText = 'Delete the book files and author folder';
if (trackFileCount === 0) {
deleteFilesLabel = 'Delete Artist Folder';
deleteFilesHelpText = 'Delete the artist folder and its contents';
if (bookFileCount === 0) {
deleteFilesLabel = 'Delete Author Folder';
deleteFilesHelpText = 'Delete the author folder and its contents';
}
return (
@ -79,7 +79,7 @@ class DeleteArtistModalContent extends Component {
onModalClose={onModalClose}
>
<ModalHeader>
Delete - {artistName}
Delete - {authorName}
</ModalHeader>
<ModalBody>
@ -112,7 +112,7 @@ class DeleteArtistModalContent extends Component {
type={inputTypes.CHECK}
name="addImportListExclusion"
value={addImportListExclusion}
helpText="Prevent artist from being added to Readarr by Import lists"
helpText="Prevent author from being added to Readarr by Import lists"
kind={kinds.DANGER}
onChange={this.onAddImportListExclusionChange}
/>
@ -121,11 +121,11 @@ class DeleteArtistModalContent extends Component {
{
deleteFiles &&
<div className={styles.deleteFilesMessage}>
<div>The artist folder <strong>{path}</strong> and all of its content will be deleted.</div>
<div>The author folder <strong>{path}</strong> and all of its content will be deleted.</div>
{
!!trackFileCount &&
<div>{trackFileCount} track files totaling {formatBytes(sizeOnDisk)}</div>
!!bookFileCount &&
<div>{bookFileCount} book files totaling {formatBytes(sizeOnDisk)}</div>
}
</div>
}
@ -139,7 +139,7 @@ class DeleteArtistModalContent extends Component {
<Button
kind={kinds.DANGER}
onPress={this.onDeleteArtistConfirmed}
onPress={this.onDeleteAuthorConfirmed}
>
Delete
</Button>
@ -149,18 +149,18 @@ class DeleteArtistModalContent extends Component {
}
}
DeleteArtistModalContent.propTypes = {
artistName: PropTypes.string.isRequired,
DeleteAuthorModalContent.propTypes = {
authorName: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
DeleteArtistModalContent.defaultProps = {
DeleteAuthorModalContent.defaultProps = {
statistics: {
trackFileCount: 0
bookFileCount: 0
}
};
export default DeleteArtistModalContent;
export default DeleteAuthorModalContent;

@ -2,30 +2,30 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { deleteArtist } from 'Store/Actions/artistActions';
import DeleteArtistModalContent from './DeleteArtistModalContent';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import { deleteAuthor } from 'Store/Actions/authorActions';
import DeleteAuthorModalContent from './DeleteAuthorModalContent';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
(artist) => {
return artist;
createAuthorSelector(),
(author) => {
return author;
}
);
}
const mapDispatchToProps = {
deleteArtist
deleteAuthor
};
class DeleteArtistModalContentConnector extends Component {
class DeleteAuthorModalContentConnector extends Component {
//
// Listeners
onDeletePress = (deleteFiles, addImportListExclusion) => {
this.props.deleteArtist({
this.props.deleteAuthor({
id: this.props.authorId,
deleteFiles,
addImportListExclusion
@ -39,7 +39,7 @@ class DeleteArtistModalContentConnector extends Component {
render() {
return (
<DeleteArtistModalContent
<DeleteAuthorModalContent
{...this.props}
onDeletePress={this.onDeletePress}
/>
@ -47,10 +47,10 @@ class DeleteArtistModalContentConnector extends Component {
}
}
DeleteArtistModalContentConnector.propTypes = {
DeleteAuthorModalContentConnector.propTypes = {
authorId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.isRequired,
deleteArtist: PropTypes.func.isRequired
deleteAuthor: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(DeleteArtistModalContentConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(DeleteAuthorModalContentConnector);

@ -1,8 +1,8 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './ArtistAlternateTitles.css';
import styles from './AuthorAlternateTitles.css';
function ArtistAlternateTitles({ alternateTitles }) {
function AuthorAlternateTitles({ alternateTitles }) {
return (
<ul>
{
@ -21,8 +21,8 @@ function ArtistAlternateTitles({ alternateTitles }) {
);
}
ArtistAlternateTitles.propTypes = {
AuthorAlternateTitles.propTypes = {
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
};
export default ArtistAlternateTitles;
export default AuthorAlternateTitles;

@ -99,11 +99,11 @@
float: right;
}
.artistNavigationButtons {
.authorNavigationButtons {
white-space: nowrap;
}
.artistNavigationButton {
.authorNavigationButton {
composes: button from '~Components/Link/IconButton.css';
margin-left: 5px;

@ -1,7 +1,7 @@
import _ from 'lodash';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Tab, Tabs, TabList, TabPanel } from 'react-tabs';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import TextTruncate from 'react-text-truncate';
import formatBytes from 'Utilities/Number/formatBytes';
import selectAll from 'Utilities/Table/selectAll';
@ -22,20 +22,20 @@ import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip';
import TrackFileEditorTable from 'TrackFile/Editor/TrackFileEditorTable';
import BookFileEditorTable from 'BookFile/Editor/BookFileEditorTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
import RetagPreviewModalConnector from 'Retag/RetagPreviewModalConnector';
import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector';
import ArtistPoster from 'Artist/ArtistPoster';
import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector';
import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal';
import ArtistHistoryTable from 'Artist/History/ArtistHistoryTable';
import ArtistAlternateTitles from './ArtistAlternateTitles';
import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector';
import AuthorPoster from 'Author/AuthorPoster';
import EditAuthorModalConnector from 'Author/Edit/EditAuthorModalConnector';
import DeleteAuthorModal from 'Author/Delete/DeleteAuthorModal';
import AuthorHistoryTable from 'Author/History/AuthorHistoryTable';
import AuthorAlternateTitles from './AuthorAlternateTitles';
import AuthorDetailsSeasonConnector from './AuthorDetailsSeasonConnector';
import AuthorDetailsSeriesConnector from './AuthorDetailsSeriesConnector';
import ArtistTagsConnector from './ArtistTagsConnector';
import ArtistDetailsLinks from './ArtistDetailsLinks';
import styles from './ArtistDetails.css';
import AuthorTagsConnector from './AuthorTagsConnector';
import AuthorDetailsLinks from './AuthorDetailsLinks';
import styles from './AuthorDetails.css';
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
import InteractiveImportModal from '../../InteractiveImport/InteractiveImportModal';
@ -60,7 +60,7 @@ function getExpandedState(newState) {
};
}
class ArtistDetails extends Component {
class AuthorDetails extends Component {
//
// Lifecycle
@ -71,8 +71,8 @@ class ArtistDetails extends Component {
this.state = {
isOrganizeModalOpen: false,
isRetagModalOpen: false,
isEditArtistModalOpen: false,
isDeleteArtistModalOpen: false,
isEditAuthorModalOpen: false,
isDeleteAuthorModalOpen: false,
isInteractiveImportModalOpen: false,
allExpanded: false,
allCollapsed: false,
@ -108,23 +108,23 @@ class ArtistDetails extends Component {
this.setState({ isInteractiveImportModalOpen: false });
}
onEditArtistPress = () => {
this.setState({ isEditArtistModalOpen: true });
onEditAuthorPress = () => {
this.setState({ isEditAuthorModalOpen: true });
}
onEditArtistModalClose = () => {
this.setState({ isEditArtistModalOpen: false });
onEditAuthorModalClose = () => {
this.setState({ isEditAuthorModalOpen: false });
}
onDeleteArtistPress = () => {
onDeleteAuthorPress = () => {
this.setState({
isEditArtistModalOpen: false,
isDeleteArtistModalOpen: true
isEditAuthorModalOpen: false,
isDeleteAuthorModalOpen: true
});
}
onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false });
onDeleteAuthorModalClose = () => {
this.setState({ isDeleteAuthorModalOpen: false });
}
onExpandAllPress = () => {
@ -156,7 +156,7 @@ class ArtistDetails extends Component {
render() {
const {
id,
artistName,
authorName,
ratings,
path,
statistics,
@ -166,7 +166,7 @@ class ArtistDetails extends Component {
overview,
links,
images,
artistType,
authorType,
alternateTitles,
tags,
isSaving,
@ -174,30 +174,30 @@ class ArtistDetails extends Component {
isSearching,
isFetching,
isPopulated,
albumsError,
trackFilesError,
hasAlbums,
hasMonitoredAlbums,
booksError,
bookFilesError,
hasBooks,
hasMonitoredBooks,
hasSeries,
series,
hasTrackFiles,
previousArtist,
nextArtist,
hasBookFiles,
previousAuthor,
nextAuthor,
onMonitorTogglePress,
onRefreshPress,
onSearchPress
} = this.props;
const {
trackFileCount,
bookFileCount,
sizeOnDisk
} = statistics;
const {
isOrganizeModalOpen,
isRetagModalOpen,
isEditArtistModalOpen,
isDeleteArtistModalOpen,
isEditAuthorModalOpen,
isDeleteAuthorModalOpen,
isInteractiveImportModalOpen,
allExpanded,
allCollapsed,
@ -206,14 +206,14 @@ class ArtistDetails extends Component {
} = this.state;
const continuing = status === 'continuing';
const endedString = artistType === 'Person' ? 'Deceased' : 'Ended';
const endedString = authorType === 'Person' ? 'Deceased' : 'Ended';
let trackFilesCountMessage = 'No track files';
let bookFilesCountMessage = 'No book files';
if (trackFileCount === 1) {
trackFilesCountMessage = '1 track file';
} else if (trackFileCount > 1) {
trackFilesCountMessage = `${trackFileCount} track files`;
if (bookFileCount === 1) {
bookFilesCountMessage = '1 book file';
} else if (bookFileCount > 1) {
bookFilesCountMessage = `${bookFileCount} book files`;
}
let expandIcon = icons.EXPAND_INDETERMINATE;
@ -225,7 +225,7 @@ class ArtistDetails extends Component {
}
return (
<PageContent title={artistName}>
<PageContent title={authorName}>
<PageToolbar>
<PageToolbarSection>
<PageToolbarButton
@ -240,9 +240,9 @@ class ArtistDetails extends Component {
<PageToolbarButton
label="Search Monitored"
iconName={icons.SEARCH}
isDisabled={!monitored || !hasMonitoredAlbums || !hasAlbums}
isDisabled={!monitored || !hasMonitoredBooks || !hasBooks}
isSpinning={isSearching}
title={hasMonitoredAlbums ? undefined : 'No monitored albums for this artist'}
title={hasMonitoredBooks ? undefined : 'No monitored books for this author'}
onPress={onSearchPress}
/>
@ -251,14 +251,14 @@ class ArtistDetails extends Component {
<PageToolbarButton
label="Preview Rename"
iconName={icons.ORGANIZE}
isDisabled={!hasTrackFiles}
isDisabled={!hasBookFiles}
onPress={this.onOrganizePress}
/>
{/* <PageToolbarButton */}
{/* label="Preview Retag" */}
{/* iconName={icons.RETAG} */}
{/* isDisabled={!hasTrackFiles} */}
{/* isDisabled={!hasBookFiles} */}
{/* onPress={this.onRetagPress} */}
{/* /> */}
@ -273,13 +273,13 @@ class ArtistDetails extends Component {
<PageToolbarButton
label="Edit"
iconName={icons.EDIT}
onPress={this.onEditArtistPress}
onPress={this.onEditAuthorPress}
/>
<PageToolbarButton
label="Delete"
iconName={icons.DELETE}
onPress={this.onDeleteArtistPress}
onPress={this.onDeleteAuthorPress}
/>
</PageToolbarSection>
@ -304,7 +304,7 @@ class ArtistDetails extends Component {
</div>
<div className={styles.headerContent}>
<ArtistPoster
<AuthorPoster
className={styles.poster}
images={images}
size={250}
@ -325,7 +325,7 @@ class ArtistDetails extends Component {
</div>
<div className={styles.title}>
{artistName}
{authorName}
</div>
{
@ -339,36 +339,36 @@ class ArtistDetails extends Component {
/>
}
title="Alternate Titles"
body={<ArtistAlternateTitles alternateTitles={alternateTitles} />}
body={<AuthorAlternateTitles alternateTitles={alternateTitles} />}
position={tooltipPositions.BOTTOM}
/>
</div>
}
</div>
<div className={styles.artistNavigationButtons}>
<div className={styles.authorNavigationButtons}>
<IconButton
className={styles.artistNavigationButton}
className={styles.authorNavigationButton}
name={icons.ARROW_LEFT}
size={30}
title={`Go to ${previousArtist.artistName}`}
to={`/author/${previousArtist.titleSlug}`}
title={`Go to ${previousAuthor.authorName}`}
to={`/author/${previousAuthor.titleSlug}`}
/>
<IconButton
className={styles.artistNavigationButton}
className={styles.authorNavigationButton}
name={icons.ARROW_UP}
size={30}
title={'Go to artist listing'}
title={'Go to author listing'}
to={'/'}
/>
<IconButton
className={styles.artistNavigationButton}
className={styles.authorNavigationButton}
name={icons.ARROW_RIGHT}
size={30}
title={`Go to ${nextArtist.artistName}`}
to={`/author/${nextArtist.titleSlug}`}
title={`Go to ${nextAuthor.authorName}`}
to={`/author/${nextAuthor.titleSlug}`}
/>
</div>
</div>
@ -399,7 +399,7 @@ class ArtistDetails extends Component {
<Label
className={styles.detailsLabel}
title={trackFilesCountMessage}
title={bookFilesCountMessage}
size={sizes.LARGE}
>
<Icon
@ -449,11 +449,11 @@ class ArtistDetails extends Component {
<Label
className={styles.detailsLabel}
title={continuing ? 'More albums are expected' : 'No additional albums are expected'}
title={continuing ? 'More books are expected' : 'No additional books are expected'}
size={sizes.LARGE}
>
<Icon
name={continuing ? icons.ARTIST_CONTINUING : icons.ARTIST_ENDED}
name={continuing ? icons.AUTHOR_CONTINUING : icons.AUTHOR_ENDED}
size={17}
/>
@ -479,7 +479,7 @@ class ArtistDetails extends Component {
</Label>
}
tooltip={
<ArtistDetailsLinks
<AuthorDetailsLinks
links={links}
/>
}
@ -505,7 +505,7 @@ class ArtistDetails extends Component {
</span>
</Label>
}
tooltip={<ArtistTagsConnector authorId={id} />}
tooltip={<AuthorTagsConnector authorId={id} />}
kind={kinds.INVERSE}
position={tooltipPositions.BOTTOM}
/>
@ -524,18 +524,18 @@ class ArtistDetails extends Component {
<div className={styles.contentContainer}>
{
!isPopulated && !albumsError && !trackFilesError &&
!isPopulated && !booksError && !bookFilesError &&
<LoadingIndicator />
}
{
!isFetching && albumsError &&
<div>Loading albums failed</div>
!isFetching && booksError &&
<div>Loading books failed</div>
}
{
!isFetching && trackFilesError &&
<div>Loading track files failed</div>
!isFetching && bookFilesError &&
<div>Loading book files failed</div>
}
{
@ -583,14 +583,14 @@ class ArtistDetails extends Component {
selectedTabIndex === 3 &&
<div className={styles.filterIcon}>
<InteractiveSearchFilterMenuConnector
type="artist"
type="author"
/>
</div>
}
</TabList>
<TabPanel>
<ArtistDetailsSeasonConnector
<AuthorDetailsSeasonConnector
authorId={id}
isExpanded={true}
onExpandPress={this.onExpandPress}
@ -619,20 +619,20 @@ class ArtistDetails extends Component {
</TabPanel>
<TabPanel>
<ArtistHistoryTable
<AuthorHistoryTable
authorId={id}
/>
</TabPanel>
<TabPanel>
<InteractiveSearchTable
type="artist"
type="author"
authorId={id}
/>
</TabPanel>
<TabPanel>
<TrackFileEditorTable
<BookFileEditorTable
authorId={id}
/>
</TabPanel>
@ -645,7 +645,7 @@ class ArtistDetails extends Component {
Missing or too many books? Modify or create a new
<Link to='/settings/profiles'> Metadata Profile </Link>
or manually
<Link to={`/add/search?term=${encodeURIComponent(artistName)}`}> Search </Link>
<Link to={`/add/search?term=${encodeURIComponent(authorName)}`}> Search </Link>
for new items!
</div>
@ -661,23 +661,23 @@ class ArtistDetails extends Component {
onModalClose={this.onRetagModalClose}
/>
<EditArtistModalConnector
isOpen={isEditArtistModalOpen}
<EditAuthorModalConnector
isOpen={isEditAuthorModalOpen}
authorId={id}
onModalClose={this.onEditArtistModalClose}
onDeleteArtistPress={this.onDeleteArtistPress}
onModalClose={this.onEditAuthorModalClose}
onDeleteAuthorPress={this.onDeleteAuthorPress}
/>
<DeleteArtistModal
isOpen={isDeleteArtistModalOpen}
<DeleteAuthorModal
isOpen={isDeleteAuthorModalOpen}
authorId={id}
onModalClose={this.onDeleteArtistModalClose}
onModalClose={this.onDeleteAuthorModalClose}
/>
<InteractiveImportModal
isOpen={isInteractiveImportModalOpen}
folder={path}
allowArtistChange={false}
allowAuthorChange={false}
showFilterExistingFiles={true}
showImportMode={false}
onModalClose={this.onInteractiveImportModalClose}
@ -688,15 +688,15 @@ class ArtistDetails extends Component {
}
}
ArtistDetails.propTypes = {
AuthorDetails.propTypes = {
id: PropTypes.number.isRequired,
artistName: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
ratings: PropTypes.object.isRequired,
path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
qualityProfileId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
artistType: PropTypes.string,
authorType: PropTypes.string,
status: PropTypes.string.isRequired,
overview: PropTypes.string.isRequired,
links: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -708,24 +708,24 @@ ArtistDetails.propTypes = {
isSearching: PropTypes.bool.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
albumsError: PropTypes.object,
trackFilesError: PropTypes.object,
hasAlbums: PropTypes.bool.isRequired,
hasMonitoredAlbums: PropTypes.bool.isRequired,
booksError: PropTypes.object,
bookFilesError: PropTypes.object,
hasBooks: PropTypes.bool.isRequired,
hasMonitoredBooks: PropTypes.bool.isRequired,
hasSeries: PropTypes.bool.isRequired,
series: PropTypes.arrayOf(PropTypes.object).isRequired,
hasTrackFiles: PropTypes.bool.isRequired,
previousArtist: PropTypes.object.isRequired,
nextArtist: PropTypes.object.isRequired,
hasBookFiles: PropTypes.bool.isRequired,
previousAuthor: PropTypes.object.isRequired,
nextAuthor: PropTypes.object.isRequired,
onMonitorTogglePress: PropTypes.func.isRequired,
onRefreshPress: PropTypes.func.isRequired,
onSearchPress: PropTypes.func.isRequired
};
ArtistDetails.defaultProps = {
AuthorDetails.defaultProps = {
statistics: {},
tags: [],
isSaving: false
};
export default ArtistDetails;
export default AuthorDetails;

@ -7,37 +7,37 @@ import { createSelector } from 'reselect';
import { findCommand, isCommandExecuting } from 'Utilities/Command';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions';
import { fetchSeries, clearSeries } from 'Store/Actions/seriesActions';
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions';
import { toggleArtistMonitored } from 'Store/Actions/artistActions';
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions';
import { clearReleases, cancelFetchReleases } from 'Store/Actions/releaseActions';
import { clearBooks, fetchBooks } from 'Store/Actions/bookActions';
import { clearSeries, fetchSeries } from 'Store/Actions/seriesActions';
import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
import { toggleAuthorMonitored } from 'Store/Actions/authorActions';
import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import ArtistDetails from './ArtistDetails';
import AuthorDetails from './AuthorDetails';
const selectAlbums = createSelector(
(state) => state.albums,
(albums) => {
const selectBooks = createSelector(
(state) => state.books,
(books) => {
const {
items,
isFetching,
isPopulated,
error
} = albums;
} = books;
const hasAlbums = !!items.length;
const hasMonitoredAlbums = items.some((e) => e.monitored);
const hasBooks = !!items.length;
const hasMonitoredBooks = items.some((e) => e.monitored);
return {
isAlbumsFetching: isFetching,
isAlbumsPopulated: isPopulated,
albumsError: error,
hasAlbums,
hasMonitoredAlbums
isBooksFetching: isFetching,
isBooksPopulated: isPopulated,
booksError: error,
hasBooks,
hasMonitoredBooks
};
}
);
@ -65,23 +65,23 @@ const selectSeries = createSelector(
}
);
const selectTrackFiles = createSelector(
(state) => state.trackFiles,
(trackFiles) => {
const selectBookFiles = createSelector(
(state) => state.bookFiles,
(bookFiles) => {
const {
items,
isFetching,
isPopulated,
error
} = trackFiles;
} = bookFiles;
const hasTrackFiles = !!items.length;
const hasBookFiles = !!items.length;
return {
isTrackFilesFetching: isFetching,
isTrackFilesPopulated: isPopulated,
trackFilesError: error,
hasTrackFiles
isBookFilesFetching: isFetching,
isBookFilesPopulated: isPopulated,
bookFilesError: error,
hasBookFiles
};
}
);
@ -89,27 +89,27 @@ const selectTrackFiles = createSelector(
function createMapStateToProps() {
return createSelector(
(state, { titleSlug }) => titleSlug,
selectAlbums,
selectBooks,
selectSeries,
selectTrackFiles,
createAllArtistSelector(),
selectBookFiles,
createAllAuthorSelector(),
createCommandsSelector(),
(titleSlug, albums, series, trackFiles, allArtists, commands) => {
const sortedArtist = _.orderBy(allArtists, 'sortName');
const artistIndex = _.findIndex(sortedArtist, { titleSlug });
const artist = sortedArtist[artistIndex];
(titleSlug, books, series, bookFiles, allAuthors, commands) => {
const sortedAuthor = _.orderBy(allAuthors, 'sortName');
const authorIndex = _.findIndex(sortedAuthor, { titleSlug });
const author = sortedAuthor[authorIndex];
if (!artist) {
if (!author) {
return {};
}
const {
isAlbumsFetching,
isAlbumsPopulated,
albumsError,
hasAlbums,
hasMonitoredAlbums
} = albums;
isBooksFetching,
isBooksPopulated,
booksError,
hasBooks,
hasMonitoredBooks
} = books;
const {
isSeriesFetching,
@ -120,34 +120,34 @@ function createMapStateToProps() {
} = series;
const {
isTrackFilesFetching,
isTrackFilesPopulated,
trackFilesError,
hasTrackFiles
} = trackFiles;
const previousArtist = sortedArtist[artistIndex - 1] || _.last(sortedArtist);
const nextArtist = sortedArtist[artistIndex + 1] || _.first(sortedArtist);
const isArtistRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_ARTIST, authorId: artist.id }));
const artistRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_ARTIST });
const allArtistRefreshing = (
isCommandExecuting(artistRefreshingCommand) &&
!artistRefreshingCommand.body.authorId
isBookFilesFetching,
isBookFilesPopulated,
bookFilesError,
hasBookFiles
} = bookFiles;
const previousAuthor = sortedAuthor[authorIndex - 1] || _.last(sortedAuthor);
const nextAuthor = sortedAuthor[authorIndex + 1] || _.first(sortedAuthor);
const isAuthorRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_AUTHOR, authorId: author.id }));
const authorRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_AUTHOR });
const allAuthorRefreshing = (
isCommandExecuting(authorRefreshingCommand) &&
!authorRefreshingCommand.body.authorId
);
const isRefreshing = isArtistRefreshing || allArtistRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.ARTIST_SEARCH, authorId: artist.id }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: artist.id }));
const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
const isRenamingArtist = (
isCommandExecuting(isRenamingArtistCommand) &&
isRenamingArtistCommand.body.authorIds.indexOf(artist.id) > -1
const isRefreshing = isAuthorRefreshing || allAuthorRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
const isRenamingAuthor = (
isCommandExecuting(isRenamingAuthorCommand) &&
isRenamingAuthorCommand.body.authorIds.indexOf(author.id) > -1
);
const isFetching = isAlbumsFetching || isSeriesFetching || isTrackFilesFetching;
const isPopulated = isAlbumsPopulated && isSeriesPopulated && isTrackFilesPopulated;
const isFetching = isBooksFetching || isSeriesFetching || isBookFilesFetching;
const isPopulated = isBooksPopulated && isSeriesPopulated && isBookFilesPopulated;
const alternateTitles = _.reduce(artist.alternateTitles, (acc, alternateTitle) => {
const alternateTitles = _.reduce(author.alternateTitles, (acc, alternateTitle) => {
if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
(alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) {
acc.push(alternateTitle.title);
@ -157,39 +157,39 @@ function createMapStateToProps() {
}, []);
return {
...artist,
...author,
alternateTitles,
isArtistRefreshing,
allArtistRefreshing,
isAuthorRefreshing,
allAuthorRefreshing,
isRefreshing,
isSearching,
isRenamingFiles,
isRenamingArtist,
isRenamingAuthor,
isFetching,
isPopulated,
albumsError,
booksError,
seriesError,
trackFilesError,
hasAlbums,
hasMonitoredAlbums,
bookFilesError,
hasBooks,
hasMonitoredBooks,
hasSeries,
series: seriesItems,
hasTrackFiles,
previousArtist,
nextArtist
hasBookFiles,
previousAuthor,
nextAuthor
};
}
);
}
const mapDispatchToProps = {
fetchAlbums,
clearAlbums,
fetchBooks,
clearBooks,
fetchSeries,
clearSeries,
fetchTrackFiles,
clearTrackFiles,
toggleArtistMonitored,
fetchBookFiles,
clearBookFiles,
toggleAuthorMonitored,
fetchQueueDetails,
clearQueueDetails,
clearReleases,
@ -197,7 +197,7 @@ const mapDispatchToProps = {
executeCommand
};
class ArtistDetailsConnector extends Component {
class AuthorDetailsConnector extends Component {
//
// Lifecycle
@ -210,22 +210,22 @@ class ArtistDetailsConnector extends Component {
componentDidUpdate(prevProps) {
const {
id,
isArtistRefreshing,
allArtistRefreshing,
isAuthorRefreshing,
allAuthorRefreshing,
isRenamingFiles,
isRenamingArtist
isRenamingAuthor
} = this.props;
if (
(prevProps.isArtistRefreshing && !isArtistRefreshing) ||
(prevProps.allArtistRefreshing && !allArtistRefreshing) ||
(prevProps.isAuthorRefreshing && !isAuthorRefreshing) ||
(prevProps.allAuthorRefreshing && !allAuthorRefreshing) ||
(prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingArtist && !isRenamingArtist)
(prevProps.isRenamingAuthor && !isRenamingAuthor)
) {
this.populate();
}
// If the id has changed we need to clear the albums
// If the id has changed we need to clear the books
// files and fetch from the server.
if (prevProps.id !== id) {
@ -245,17 +245,17 @@ class ArtistDetailsConnector extends Component {
populate = () => {
const authorId = this.props.id;
this.props.fetchAlbums({ authorId });
this.props.fetchBooks({ authorId });
this.props.fetchSeries({ authorId });
this.props.fetchTrackFiles({ authorId });
this.props.fetchBookFiles({ authorId });
this.props.fetchQueueDetails({ authorId });
}
unpopulate = () => {
this.props.cancelFetchReleases();
this.props.clearAlbums();
this.props.clearBooks();
this.props.clearSeries();
this.props.clearTrackFiles();
this.props.clearBookFiles();
this.props.clearQueueDetails();
this.props.clearReleases();
}
@ -264,7 +264,7 @@ class ArtistDetailsConnector extends Component {
// Listeners
onMonitorTogglePress = (monitored) => {
this.props.toggleArtistMonitored({
this.props.toggleAuthorMonitored({
authorId: this.props.id,
monitored
});
@ -272,14 +272,14 @@ class ArtistDetailsConnector extends Component {
onRefreshPress = () => {
this.props.executeCommand({
name: commandNames.REFRESH_ARTIST,
name: commandNames.REFRESH_AUTHOR,
authorId: this.props.id
});
}
onSearchPress = () => {
this.props.executeCommand({
name: commandNames.ARTIST_SEARCH,
name: commandNames.AUTHOR_SEARCH,
authorId: this.props.id
});
}
@ -289,7 +289,7 @@ class ArtistDetailsConnector extends Component {
render() {
return (
<ArtistDetails
<AuthorDetails
{...this.props}
onMonitorTogglePress={this.onMonitorTogglePress}
onRefreshPress={this.onRefreshPress}
@ -299,21 +299,21 @@ class ArtistDetailsConnector extends Component {
}
}
ArtistDetailsConnector.propTypes = {
AuthorDetailsConnector.propTypes = {
id: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired,
isArtistRefreshing: PropTypes.bool.isRequired,
allArtistRefreshing: PropTypes.bool.isRequired,
isAuthorRefreshing: PropTypes.bool.isRequired,
allAuthorRefreshing: PropTypes.bool.isRequired,
isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired,
isRenamingArtist: PropTypes.bool.isRequired,
fetchAlbums: PropTypes.func.isRequired,
clearAlbums: PropTypes.func.isRequired,
isRenamingAuthor: PropTypes.bool.isRequired,
fetchBooks: PropTypes.func.isRequired,
clearBooks: PropTypes.func.isRequired,
fetchSeries: PropTypes.func.isRequired,
clearSeries: PropTypes.func.isRequired,
fetchTrackFiles: PropTypes.func.isRequired,
clearTrackFiles: PropTypes.func.isRequired,
toggleArtistMonitored: PropTypes.func.isRequired,
fetchBookFiles: PropTypes.func.isRequired,
clearBookFiles: PropTypes.func.isRequired,
toggleAuthorMonitored: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired,
clearReleases: PropTypes.func.isRequired,
@ -321,4 +321,4 @@ ArtistDetailsConnector.propTypes = {
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorDetailsConnector);

@ -3,9 +3,9 @@ import React from 'react';
import { kinds, sizes } from 'Helpers/Props';
import Label from 'Components/Label';
import Link from 'Components/Link/Link';
import styles from './ArtistDetailsLinks.css';
import styles from './AuthorDetailsLinks.css';
function ArtistDetailsLinks(props) {
function AuthorDetailsLinks(props) {
const {
links
} = props;
@ -41,8 +41,8 @@ function ArtistDetailsLinks(props) {
);
}
ArtistDetailsLinks.propTypes = {
AuthorDetailsLinks.propTypes = {
links: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default ArtistDetailsLinks;
export default AuthorDetailsLinks;

@ -9,25 +9,25 @@ import PageContent from 'Components/Page/PageContent';
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import NotFound from 'Components/NotFound';
import ArtistDetailsConnector from './ArtistDetailsConnector';
import styles from './ArtistDetails.css';
import AuthorDetailsConnector from './AuthorDetailsConnector';
import styles from './AuthorDetails.css';
function createMapStateToProps() {
return createSelector(
(state, { match }) => match,
(state) => state.artist,
(match, artist) => {
(state) => state.authors,
(match, authors) => {
const titleSlug = match.params.titleSlug;
const {
isFetching,
isPopulated,
error,
items
} = artist;
} = authors;
const artistIndex = _.findIndex(items, { titleSlug });
const authorIndex = _.findIndex(items, { titleSlug });
if (artistIndex > -1) {
if (authorIndex > -1) {
return {
isFetching,
isPopulated,
@ -48,7 +48,7 @@ const mapDispatchToProps = {
push
};
class ArtistDetailsPageConnector extends Component {
class AuthorDetailsPageConnector extends Component {
//
// Lifecycle
@ -98,14 +98,14 @@ class ArtistDetailsPageConnector extends Component {
}
return (
<ArtistDetailsConnector
<AuthorDetailsConnector
titleSlug={titleSlug}
/>
);
}
}
ArtistDetailsPageConnector.propTypes = {
AuthorDetailsPageConnector.propTypes = {
titleSlug: PropTypes.string,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
@ -114,4 +114,4 @@ ArtistDetailsPageConnector.propTypes = {
push: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsPageConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorDetailsPageConnector);

@ -1,4 +1,4 @@
.albumType {
.bookType {
margin-bottom: 20px;
border: 1px solid $borderColor;
border-radius: 4px;
@ -18,12 +18,12 @@
cursor: pointer;
}
.albumTypeLabel {
.bookTypeLabel {
margin-right: 5px;
margin-left: 5px;
}
.albumCount {
.bookCount {
color: #8895aa;
font-style: italic;
font-size: 18px;
@ -75,7 +75,7 @@
width: 30px;
}
.albums {
.books {
padding-top: 15px;
border-top: 1px solid $borderColor;
}
@ -106,13 +106,13 @@
margin-left: -15px;
}
.noAlbums {
.noBooks {
margin-bottom: 15px;
text-align: center;
}
@media only screen and (max-width: $breakpointSmall) {
.albumType {
.bookType {
border-right: 0;
border-left: 0;
border-radius: 0;

@ -5,10 +5,10 @@ import getToggledRange from 'Utilities/Table/getToggledRange';
import { sortDirections } from 'Helpers/Props';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import AlbumRowConnector from './AlbumRowConnector';
import styles from './ArtistDetailsSeason.css';
import BookRowConnector from './BookRowConnector';
import styles from './AuthorDetailsSeason.css';
class ArtistDetailsSeason extends Component {
class AuthorDetailsSeason extends Component {
//
// Lifecycle
@ -17,15 +17,15 @@ class ArtistDetailsSeason extends Component {
super(props, context);
this.state = {
lastToggledAlbum: null
lastToggledBook: null
};
}
//
// Listeners
onMonitorAlbumPress = (bookId, monitored, { shiftKey }) => {
const lastToggled = this.state.lastToggledAlbum;
onMonitorBookPress = (bookId, monitored, { shiftKey }) => {
const lastToggled = this.state.lastToggledBook;
const bookIds = [bookId];
if (shiftKey && lastToggled) {
@ -37,9 +37,9 @@ class ArtistDetailsSeason extends Component {
}
}
this.setState({ lastToggledAlbum: bookId });
this.setState({ lastToggledBook: bookId });
this.props.onMonitorAlbumPress(_.uniq(bookIds), monitored);
this.props.onMonitorBookPress(_.uniq(bookIds), monitored);
}
//
@ -57,9 +57,9 @@ class ArtistDetailsSeason extends Component {
return (
<div
className={styles.albumType}
className={styles.bookType}
>
<div className={styles.albums}>
<div className={styles.books}>
<Table
columns={columns}
sortKey={sortKey}
@ -71,11 +71,11 @@ class ArtistDetailsSeason extends Component {
{
items.map((item) => {
return (
<AlbumRowConnector
<BookRowConnector
key={item.id}
columns={columns}
{...item}
onMonitorAlbumPress={this.onMonitorAlbumPress}
onMonitorBookPress={this.onMonitorBookPress}
/>
);
})
@ -88,7 +88,7 @@ class ArtistDetailsSeason extends Component {
}
}
ArtistDetailsSeason.propTypes = {
AuthorDetailsSeason.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
items: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -96,8 +96,8 @@ ArtistDetailsSeason.propTypes = {
onTableOptionChange: PropTypes.func.isRequired,
onExpandPress: PropTypes.func.isRequired,
onSortPress: PropTypes.func.isRequired,
onMonitorAlbumPress: PropTypes.func.isRequired,
onMonitorBookPress: PropTypes.func.isRequired,
uiSettings: PropTypes.object.isRequired
};
export default ArtistDetailsSeason;
export default AuthorDetailsSeason;

@ -5,40 +5,40 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { toggleAlbumsMonitored, setAlbumsTableOption, setAlbumsSort } from 'Store/Actions/albumActions';
import { setBooksSort, setBooksTableOption, toggleBooksMonitored } from 'Store/Actions/bookActions';
import { executeCommand } from 'Store/Actions/commandActions';
import ArtistDetailsSeason from './ArtistDetailsSeason';
import AuthorDetailsSeason from './AuthorDetailsSeason';
function createMapStateToProps() {
return createSelector(
(state, { label }) => label,
createClientSideCollectionSelector('albums'),
createArtistSelector(),
createClientSideCollectionSelector('books'),
createAuthorSelector(),
createCommandsSelector(),
createDimensionsSelector(),
createUISettingsSelector(),
(label, albums, artist, commands, dimensions, uiSettings) => {
(label, books, author, commands, dimensions, uiSettings) => {
const albumsInGroup = albums.items;
const booksInGroup = books.items;
let sortDir = 'asc';
if (albums.sortDirection === 'descending') {
if (books.sortDirection === 'descending') {
sortDir = 'desc';
}
const sortedAlbums = _.orderBy(albumsInGroup, albums.sortKey, sortDir);
const sortedBooks = _.orderBy(booksInGroup, books.sortKey, sortDir);
return {
items: sortedAlbums,
columns: albums.columns,
sortKey: albums.sortKey,
sortDirection: albums.sortDirection,
artistMonitored: artist.monitored,
items: sortedBooks,
columns: books.columns,
sortKey: books.sortKey,
sortDirection: books.sortDirection,
authorMonitored: author.monitored,
isSmallScreen: dimensions.isSmallScreen,
uiSettings
};
@ -47,27 +47,27 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
toggleAlbumsMonitored,
setAlbumsTableOption,
dispatchSetAlbumSort: setAlbumsSort,
toggleBooksMonitored,
setBooksTableOption,
dispatchSetBookSort: setBooksSort,
executeCommand
};
class ArtistDetailsSeasonConnector extends Component {
class AuthorDetailsSeasonConnector extends Component {
//
// Listeners
onTableOptionChange = (payload) => {
this.props.setAlbumsTableOption(payload);
this.props.setBooksTableOption(payload);
}
onSortPress = (sortKey) => {
this.props.dispatchSetAlbumSort({ sortKey });
this.props.dispatchSetBookSort({ sortKey });
}
onMonitorAlbumPress = (bookIds, monitored) => {
this.props.toggleAlbumsMonitored({
onMonitorBookPress = (bookIds, monitored) => {
this.props.toggleBooksMonitored({
bookIds,
monitored
});
@ -78,22 +78,22 @@ class ArtistDetailsSeasonConnector extends Component {
render() {
return (
<ArtistDetailsSeason
<AuthorDetailsSeason
{...this.props}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onMonitorAlbumPress={this.onMonitorAlbumPress}
onMonitorBookPress={this.onMonitorBookPress}
/>
);
}
}
ArtistDetailsSeasonConnector.propTypes = {
AuthorDetailsSeasonConnector.propTypes = {
authorId: PropTypes.number.isRequired,
toggleAlbumsMonitored: PropTypes.func.isRequired,
setAlbumsTableOption: PropTypes.func.isRequired,
dispatchSetAlbumSort: PropTypes.func.isRequired,
toggleBooksMonitored: PropTypes.func.isRequired,
setBooksTableOption: PropTypes.func.isRequired,
dispatchSetBookSort: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsSeasonConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorDetailsSeasonConnector);

@ -1,4 +1,4 @@
.albumType {
.bookType {
margin-bottom: 20px;
border: 1px solid $borderColor;
border-radius: 4px;
@ -18,12 +18,12 @@
cursor: pointer;
}
.albumTypeLabel {
.bookTypeLabel {
margin-right: 5px;
margin-left: 5px;
}
.albumCount {
.bookCount {
color: #8895aa;
font-style: italic;
font-size: 18px;
@ -75,7 +75,7 @@
width: 30px;
}
.albums {
.books {
padding-top: 15px;
border-top: 1px solid $borderColor;
}
@ -108,13 +108,13 @@
/* margin-left: -15px; */
}
.noAlbums {
.noBooks {
margin-bottom: 15px;
text-align: center;
}
@media only screen and (max-width: $breakpointSmall) {
.albumType {
.bookType {
border-right: 0;
border-left: 0;
border-radius: 0;

@ -8,7 +8,7 @@ import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import AlbumRowConnector from './AlbumRowConnector';
import BookRowConnector from './BookRowConnector';
import styles from './AuthorDetailsSeries.css';
class AuthorDetailsSeries extends Component {
@ -21,8 +21,8 @@ class AuthorDetailsSeries extends Component {
this.state = {
isOrganizeModalOpen: false,
isManageTracksOpen: false,
lastToggledAlbum: null
isManageBooksOpen: false,
lastToggledBook: null
};
}
@ -65,22 +65,22 @@ class AuthorDetailsSeries extends Component {
this.props.onExpandPress(id, !isExpanded);
}
onMonitorAlbumPress = (albumId, monitored, { shiftKey }) => {
const lastToggled = this.state.lastToggledAlbum;
const albumIds = [albumId];
onMonitorBookPress = (bookId, monitored, { shiftKey }) => {
const lastToggled = this.state.lastToggledBook;
const bookIds = [bookId];
if (shiftKey && lastToggled) {
const { lower, upper } = getToggledRange(this.props.items, albumId, lastToggled);
const { lower, upper } = getToggledRange(this.props.items, bookId, lastToggled);
const items = this.props.items;
for (let i = lower; i < upper; i++) {
albumIds.push(items[i].id);
bookIds.push(items[i].id);
}
}
this.setState({ lastToggledAlbum: albumId });
this.setState({ lastToggledBook: bookId });
this.props.onMonitorAlbumPress(_.uniq(albumIds), monitored);
this.props.onMonitorBookPress(_.uniq(bookIds), monitored);
}
//
@ -102,7 +102,7 @@ class AuthorDetailsSeries extends Component {
return (
<div
className={styles.albumType}
className={styles.bookType}
>
<Link
className={styles.expandButton}
@ -112,11 +112,11 @@ class AuthorDetailsSeries extends Component {
<div className={styles.left}>
{
<div>
<span className={styles.albumTypeLabel}>
<span className={styles.bookTypeLabel}>
{label}
</span>
<span className={styles.albumCount}>
<span className={styles.bookCount}>
({items.length} Books)
</span>
</div>
@ -142,7 +142,7 @@ class AuthorDetailsSeries extends Component {
<div>
{
isExpanded &&
<div className={styles.albums}>
<div className={styles.books}>
<Table
columns={columns}
sortKey={sortKey}
@ -154,12 +154,12 @@ class AuthorDetailsSeries extends Component {
{
items.map((item) => {
return (
<AlbumRowConnector
<BookRowConnector
key={item.id}
columns={columns}
{...item}
position={positionMap[item.id]}
onMonitorAlbumPress={this.onMonitorAlbumPress}
onMonitorBookPress={this.onMonitorBookPress}
/>
);
})
@ -198,7 +198,7 @@ AuthorDetailsSeries.propTypes = {
onTableOptionChange: PropTypes.func.isRequired,
onExpandPress: PropTypes.func.isRequired,
onSortPress: PropTypes.func.isRequired,
onMonitorAlbumPress: PropTypes.func.isRequired,
onMonitorBookPress: PropTypes.func.isRequired,
uiSettings: PropTypes.object.isRequired
};

@ -5,11 +5,11 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
// import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import { toggleAlbumsMonitored, setAlbumsTableOption } from 'Store/Actions/albumActions';
import { setBooksTableOption, toggleBooksMonitored } from 'Store/Actions/bookActions';
import { setSeriesSort } from 'Store/Actions/seriesActions';
import { executeCommand } from 'Store/Actions/commandActions';
import AuthorDetailsSeries from './AuthorDetailsSeries';
@ -17,8 +17,8 @@ import AuthorDetailsSeries from './AuthorDetailsSeries';
function createMapStateToProps() {
return createSelector(
(state, { seriesId }) => seriesId,
(state) => state.albums,
createArtistSelector(),
(state) => state.books,
createAuthorSelector(),
(state) => state.series,
createCommandsSelector(),
createDimensionsSelector(),
@ -60,7 +60,7 @@ function createMapStateToProps() {
columns: series.columns,
sortKey: series.sortKey,
sortDirection: series.sortDirection,
artistMonitored: author.monitored,
authorMonitored: author.monitored,
isSmallScreen: dimensions.isSmallScreen,
uiSettings
};
@ -69,27 +69,27 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
toggleAlbumsMonitored,
setAlbumsTableOption,
toggleBooksMonitored,
setBooksTableOption,
dispatchSetSeriesSort: setSeriesSort,
executeCommand
};
class ArtistDetailsSeasonConnector extends Component {
class AuthorDetailsSeasonConnector extends Component {
//
// Listeners
onTableOptionChange = (payload) => {
this.props.setAlbumsTableOption(payload);
this.props.setBooksTableOption(payload);
}
onSortPress = (sortKey) => {
this.props.dispatchSetSeriesSort({ sortKey });
}
onMonitorAlbumPress = (bookIds, monitored) => {
this.props.toggleAlbumsMonitored({
onMonitorBookPress = (bookIds, monitored) => {
this.props.toggleBooksMonitored({
bookIds,
monitored
});
@ -104,18 +104,18 @@ class ArtistDetailsSeasonConnector extends Component {
{...this.props}
onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange}
onMonitorAlbumPress={this.onMonitorAlbumPress}
onMonitorBookPress={this.onMonitorBookPress}
/>
);
}
}
ArtistDetailsSeasonConnector.propTypes = {
AuthorDetailsSeasonConnector.propTypes = {
authorId: PropTypes.number.isRequired,
toggleAlbumsMonitored: PropTypes.func.isRequired,
setAlbumsTableOption: PropTypes.func.isRequired,
toggleBooksMonitored: PropTypes.func.isRequired,
setBooksTableOption: PropTypes.func.isRequired,
dispatchSetSeriesSort: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsSeasonConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorDetailsSeasonConnector);

@ -3,7 +3,7 @@ import React from 'react';
import { kinds, sizes } from 'Helpers/Props';
import Label from 'Components/Label';
function ArtistTags({ tags }) {
function AuthorTags({ tags }) {
return (
<div>
{
@ -23,8 +23,8 @@ function ArtistTags({ tags }) {
);
}
ArtistTags.propTypes = {
AuthorTags.propTypes = {
tags: PropTypes.arrayOf(PropTypes.string).isRequired
};
export default ArtistTags;
export default AuthorTags;

@ -1,16 +1,16 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import ArtistTags from './ArtistTags';
import AuthorTags from './AuthorTags';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createAuthorSelector(),
createTagsSelector(),
(artist, tagList) => {
const tags = _.reduce(artist.tags, (acc, tag) => {
(author, tagList) => {
const tags = _.reduce(author.tags, (acc, tag) => {
const matchingTag = _.find(tagList, { id: tag });
if (matchingTag) {
@ -27,4 +27,4 @@ function createMapStateToProps() {
);
}
export default connect(createMapStateToProps)(ArtistTags);
export default connect(createMapStateToProps)(AuthorTags);

@ -6,13 +6,13 @@ import { kinds, sizes } from 'Helpers/Props';
import TableRow from 'Components/Table/TableRow';
import Label from 'Components/Label';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import AlbumSearchCellConnector from 'Album/AlbumSearchCellConnector';
import AlbumTitleLink from 'Album/AlbumTitleLink';
import BookSearchCellConnector from 'Book/BookSearchCellConnector';
import BookTitleLink from 'Book/BookTitleLink';
import StarRating from 'Components/StarRating';
import styles from './AlbumRow.css';
import styles from './BookRow.css';
function getTrackCountKind(monitored, trackFileCount, trackCount) {
if (trackFileCount === trackCount && trackCount > 0) {
function getBookCountKind(monitored, bookFileCount, bookCount) {
if (bookFileCount === bookCount && bookCount > 0) {
return kinds.SUCCESS;
}
@ -23,7 +23,7 @@ function getTrackCountKind(monitored, trackFileCount, trackCount) {
return kinds.DANGER;
}
class AlbumRow extends Component {
class BookRow extends Component {
//
// Lifecycle
@ -33,7 +33,7 @@ class AlbumRow extends Component {
this.state = {
isDetailsModalOpen: false,
isEditAlbumModalOpen: false
isEditBookModalOpen: false
};
}
@ -48,16 +48,16 @@ class AlbumRow extends Component {
this.setState({ isDetailsModalOpen: false });
}
onEditAlbumPress = () => {
this.setState({ isEditAlbumModalOpen: true });
onEditBookPress = () => {
this.setState({ isEditBookModalOpen: true });
}
onEditAlbumModalClose = () => {
this.setState({ isEditAlbumModalOpen: false });
onEditBookModalClose = () => {
this.setState({ isEditBookModalOpen: false });
}
onMonitorAlbumPress = (monitored, options) => {
this.props.onMonitorAlbumPress(this.props.id, monitored, options);
onMonitorBookPress = (monitored, options) => {
this.props.onMonitorBookPress(this.props.id, monitored, options);
}
//
@ -75,15 +75,15 @@ class AlbumRow extends Component {
ratings,
disambiguation,
isSaving,
artistMonitored,
authorMonitored,
titleSlug,
columns
} = this.props;
const {
trackCount,
trackFileCount,
totalTrackCount
bookCount,
bookFileCount,
totalBookCount
} = statistics;
return (
@ -107,9 +107,9 @@ class AlbumRow extends Component {
>
<MonitorToggleButton
monitored={monitored}
isDisabled={!artistMonitored}
isDisabled={!authorMonitored}
isSaving={isSaving}
onPress={this.onMonitorAlbumPress}
onPress={this.onMonitorBookPress}
/>
</TableRowCell>
);
@ -121,7 +121,7 @@ class AlbumRow extends Component {
key={name}
className={styles.title}
>
<AlbumTitleLink
<BookTitleLink
titleSlug={titleSlug}
title={title}
disambiguation={disambiguation}
@ -170,12 +170,12 @@ class AlbumRow extends Component {
className={styles.status}
>
<Label
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`}
kind={getTrackCountKind(monitored, trackFileCount, trackCount)}
title={`${totalBookCount} books total. ${bookFileCount} books with files.`}
kind={getBookCountKind(monitored, bookFileCount, bookCount)}
size={sizes.MEDIUM}
>
{
<span>{trackFileCount} / {trackCount}</span>
<span>{bookFileCount} / {bookCount}</span>
}
</Label>
</TableRowCell>
@ -184,11 +184,11 @@ class AlbumRow extends Component {
if (name === 'actions') {
return (
<AlbumSearchCellConnector
<BookSearchCellConnector
key={name}
bookId={id}
authorId={authorId}
albumTitle={title}
bookTitle={title}
/>
);
}
@ -200,7 +200,7 @@ class AlbumRow extends Component {
}
}
AlbumRow.propTypes = {
BookRow.propTypes = {
id: PropTypes.number.isRequired,
authorId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
@ -211,17 +211,17 @@ AlbumRow.propTypes = {
disambiguation: PropTypes.string,
titleSlug: PropTypes.string.isRequired,
isSaving: PropTypes.bool,
artistMonitored: PropTypes.bool.isRequired,
authorMonitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onMonitorAlbumPress: PropTypes.func.isRequired
onMonitorBookPress: PropTypes.func.isRequired
};
AlbumRow.defaultProps = {
BookRow.defaultProps = {
statistics: {
trackCount: 0,
trackFileCount: 0
bookCount: 0,
bookFileCount: 0
}
};
export default AlbumRow;
export default BookRow;

@ -0,0 +1,20 @@
/* eslint max-params: 0 */
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createBookFileSelector from 'Store/Selectors/createBookFileSelector';
import BookRow from './BookRow';
function createMapStateToProps() {
return createSelector(
createAuthorSelector(),
createBookFileSelector(),
(author = {}, bookFile) => {
return {
authorMonitored: author.monitored,
bookFilePath: bookFile ? bookFile.path : null
};
}
);
}
export default connect(createMapStateToProps)(BookRow);

@ -1,15 +1,15 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import EditArtistModalContentConnector from './EditArtistModalContentConnector';
import EditAuthorModalContentConnector from './EditAuthorModalContentConnector';
function EditArtistModal({ isOpen, onModalClose, ...otherProps }) {
function EditAuthorModal({ isOpen, onModalClose, ...otherProps }) {
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
<EditArtistModalContentConnector
<EditAuthorModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@ -17,9 +17,9 @@ function EditArtistModal({ isOpen, onModalClose, ...otherProps }) {
);
}
EditArtistModal.propTypes = {
EditAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default EditArtistModal;
export default EditAuthorModal;

@ -2,19 +2,19 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditArtistModal from './EditArtistModal';
import EditAuthorModal from './EditAuthorModal';
const mapDispatchToProps = {
clearPendingChanges
};
class EditArtistModalConnector extends Component {
class EditAuthorModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearPendingChanges({ section: 'artist' });
this.props.clearPendingChanges({ section: 'author' });
this.props.onModalClose();
}
@ -23,7 +23,7 @@ class EditArtistModalConnector extends Component {
render() {
return (
<EditArtistModal
<EditAuthorModal
{...this.props}
onModalClose={this.onModalClose}
/>
@ -31,9 +31,9 @@ class EditArtistModalConnector extends Component {
}
}
EditArtistModalConnector.propTypes = {
EditAuthorModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired,
clearPendingChanges: PropTypes.func.isRequired
};
export default connect(undefined, mapDispatchToProps)(EditArtistModalConnector);
export default connect(undefined, mapDispatchToProps)(EditAuthorModalConnector);

@ -13,11 +13,11 @@ import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover';
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal';
import ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent';
import styles from './EditArtistModalContent.css';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import AuthorMetadataProfilePopoverContent from 'AddAuthor/AuthorMetadataProfilePopoverContent';
import styles from './EditAuthorModalContent.css';
class EditArtistModalContent extends Component {
class EditAuthorModalContent extends Component {
//
// Lifecycle
@ -48,7 +48,7 @@ class EditArtistModalContent extends Component {
}
}
onMoveArtistPress = () => {
onMoveAuthorPress = () => {
this.setState({ isConfirmMoveModalOpen: false });
this.props.onSavePress(true);
@ -59,14 +59,14 @@ class EditArtistModalContent extends Component {
render() {
const {
artistName,
authorName,
item,
isSaving,
showMetadataProfile,
originalPath,
onInputChange,
onModalClose,
onDeleteArtistPress,
onDeleteAuthorPress,
...otherProps
} = this.props;
@ -81,7 +81,7 @@ class EditArtistModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Edit - {artistName}
Edit - {authorName}
</ModalHeader>
<ModalBody>
@ -92,7 +92,7 @@ class EditArtistModalContent extends Component {
<FormInputGroup
type={inputTypes.CHECK}
name="monitored"
helpText="Download monitored albums from this artist"
helpText="Download monitored books from this author"
{...monitored}
onChange={onInputChange}
/>
@ -123,7 +123,7 @@ class EditArtistModalContent extends Component {
/>
}
title="Metadata Profile"
body={<ArtistMetadataProfilePopoverContent />}
body={<AuthorMetadataProfilePopoverContent />}
position={tooltipPositions.RIGHT}
/>
@ -132,7 +132,7 @@ class EditArtistModalContent extends Component {
<FormInputGroup
type={inputTypes.METADATA_PROFILE_SELECT}
name="metadataProfileId"
helpText="Changes will take place on next artist refresh"
helpText="Changes will take place on next author refresh"
includeNone={true}
{...metadataProfileId}
onChange={onInputChange}
@ -167,7 +167,7 @@ class EditArtistModalContent extends Component {
<Button
className={styles.deleteButton}
kind={kinds.DANGER}
onPress={onDeleteArtistPress}
onPress={onDeleteAuthorPress}
>
Delete
</Button>
@ -186,12 +186,12 @@ class EditArtistModalContent extends Component {
</SpinnerButton>
</ModalFooter>
<MoveArtistModal
<MoveAuthorModal
originalPath={originalPath}
destinationPath={path.value}
isOpen={this.state.isConfirmMoveModalOpen}
onSavePress={this.onSavePress}
onMoveArtistPress={this.onMoveArtistPress}
onMoveAuthorPress={this.onMoveAuthorPress}
/>
</ModalContent>
@ -199,9 +199,9 @@ class EditArtistModalContent extends Component {
}
}
EditArtistModalContent.propTypes = {
EditAuthorModalContent.propTypes = {
authorId: PropTypes.number.isRequired,
artistName: PropTypes.string.isRequired,
authorName: PropTypes.string.isRequired,
item: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired,
@ -210,7 +210,7 @@ EditArtistModalContent.propTypes = {
onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteArtistPress: PropTypes.func.isRequired
onDeleteAuthorPress: PropTypes.func.isRequired
};
export default EditArtistModalContent;
export default EditAuthorModalContent;

@ -4,56 +4,55 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import selectSettings from 'Store/Selectors/selectSettings';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { setArtistValue, saveArtist } from 'Store/Actions/artistActions';
import EditArtistModalContent from './EditArtistModalContent';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import { saveAuthor, setAuthorValue } from 'Store/Actions/authorActions';
import EditAuthorModalContent from './EditAuthorModalContent';
function createIsPathChangingSelector() {
return createSelector(
(state) => state.artist.pendingChanges,
createArtistSelector(),
(pendingChanges, artist) => {
(state) => state.authors.pendingChanges,
createAuthorSelector(),
(pendingChanges, author) => {
const path = pendingChanges.path;
if (path == null) {
return false;
}
return artist.path !== path;
return author.path !== path;
}
);
}
function createMapStateToProps() {
return createSelector(
(state) => state.artist,
(state) => state.authors,
(state) => state.settings.metadataProfiles,
createArtistSelector(),
createAuthorSelector(),
createIsPathChangingSelector(),
(artistState, metadataProfiles, artist, isPathChanging) => {
(authorsState, metadataProfiles, author, isPathChanging) => {
const {
isSaving,
saveError,
pendingChanges
} = artistState;
} = authorsState;
const artistSettings = _.pick(artist, [
const authorSettings = _.pick(author, [
'monitored',
'albumFolder',
'qualityProfileId',
'metadataProfileId',
'path',
'tags'
]);
const settings = selectSettings(artistSettings, pendingChanges, saveError);
const settings = selectSettings(authorSettings, pendingChanges, saveError);
return {
artistName: artist.artistName,
authorName: author.authorName,
isSaving,
saveError,
isPathChanging,
originalPath: artist.path,
originalPath: author.path,
item: settings.settings,
showMetadataProfile: metadataProfiles.items.length > 1,
...settings
@ -63,11 +62,11 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
dispatchSetArtistValue: setArtistValue,
dispatchSaveArtist: saveArtist
dispatchSetAuthorValue: setAuthorValue,
dispatchSaveAuthor: saveAuthor
};
class EditArtistModalContentConnector extends Component {
class EditAuthorModalContentConnector extends Component {
//
// Lifecycle
@ -82,11 +81,11 @@ class EditArtistModalContentConnector extends Component {
// Listeners
onInputChange = ({ name, value }) => {
this.props.dispatchSetArtistValue({ name, value });
this.props.dispatchSetAuthorValue({ name, value });
}
onSavePress = (moveFiles) => {
this.props.dispatchSaveArtist({
this.props.dispatchSaveAuthor({
id: this.props.authorId,
moveFiles
});
@ -97,23 +96,23 @@ class EditArtistModalContentConnector extends Component {
render() {
return (
<EditArtistModalContent
<EditAuthorModalContent
{...this.props}
onInputChange={this.onInputChange}
onSavePress={this.onSavePress}
onMoveArtistPress={this.onMoveArtistPress}
onMoveAuthorPress={this.onMoveAuthorPress}
/>
);
}
}
EditArtistModalContentConnector.propTypes = {
EditAuthorModalContentConnector.propTypes = {
authorId: PropTypes.number,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
dispatchSetArtistValue: PropTypes.func.isRequired,
dispatchSaveArtist: PropTypes.func.isRequired,
dispatchSetAuthorValue: PropTypes.func.isRequired,
dispatchSaveAuthor: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(EditArtistModalContentConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(EditAuthorModalContentConnector);

@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import RetagArtistModalContentConnector from './RetagArtistModalContentConnector';
import RetagAuthorModalContentConnector from './RetagAuthorModalContentConnector';
function RetagArtistModal(props) {
function RetagAuthorModal(props) {
const {
isOpen,
onModalClose,
@ -15,7 +15,7 @@ function RetagArtistModal(props) {
isOpen={isOpen}
onModalClose={onModalClose}
>
<RetagArtistModalContentConnector
<RetagAuthorModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@ -23,9 +23,9 @@ function RetagArtistModal(props) {
);
}
RetagArtistModal.propTypes = {
RetagAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default RetagArtistModal;
export default RetagAuthorModal;

@ -8,24 +8,24 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './RetagArtistModalContent.css';
import styles from './RetagAuthorModalContent.css';
function RetagArtistModalContent(props) {
function RetagAuthorModalContent(props) {
const {
artistNames,
authorNames,
onModalClose,
onRetagArtistPress
onRetagAuthorPress
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Retag Selected Artist
Retag Selected Author
</ModalHeader>
<ModalBody>
<Alert>
Tip: To preview the tags that will be written... select "Cancel" then click any artist name and use the
Tip: To preview the tags that will be written... select "Cancel" then click any author name and use the
<Icon
className={styles.retagIcon}
name={icons.RETAG}
@ -33,14 +33,14 @@ function RetagArtistModalContent(props) {
</Alert>
<div className={styles.message}>
Are you sure you want to re-tag all files in the {artistNames.length} selected artist?
Are you sure you want to re-tag all files in the {authorNames.length} selected author?
</div>
<ul>
{
artistNames.map((artistName) => {
authorNames.map((authorName) => {
return (
<li key={artistName}>
{artistName}
<li key={authorName}>
{authorName}
</li>
);
})
@ -55,7 +55,7 @@ function RetagArtistModalContent(props) {
<Button
kind={kinds.DANGER}
onPress={onRetagArtistPress}
onPress={onRetagAuthorPress}
>
Retag
</Button>
@ -64,10 +64,10 @@ function RetagArtistModalContent(props) {
);
}
RetagArtistModalContent.propTypes = {
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired,
RetagAuthorModalContent.propTypes = {
authorNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onModalClose: PropTypes.func.isRequired,
onRetagArtistPress: PropTypes.func.isRequired
onRetagAuthorPress: PropTypes.func.isRequired
};
export default RetagArtistModalContent;
export default RetagAuthorModalContent;

@ -3,25 +3,25 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import RetagArtistModalContent from './RetagArtistModalContent';
import RetagAuthorModalContent from './RetagAuthorModalContent';
function createMapStateToProps() {
return createSelector(
(state, { authorIds }) => authorIds,
createAllArtistSelector(),
(authorIds, allArtists) => {
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
createAllAuthorSelector(),
(authorIds, allAuthors) => {
const author = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id;
});
const sortedArtist = _.orderBy(artist, 'sortName');
const artistNames = _.map(sortedArtist, 'artistName');
const sortedAuthor = _.orderBy(author, 'sortName');
const authorNames = _.map(sortedAuthor, 'authorName');
return {
artistNames
authorNames
};
}
);
@ -31,14 +31,14 @@ const mapDispatchToProps = {
executeCommand
};
class RetagArtistModalContentConnector extends Component {
class RetagAuthorModalContentConnector extends Component {
//
// Listeners
onRetagArtistPress = () => {
onRetagAuthorPress = () => {
this.props.executeCommand({
name: commandNames.RETAG_ARTIST,
name: commandNames.RETAG_AUTHOR,
authorIds: this.props.authorIds
});
@ -50,18 +50,18 @@ class RetagArtistModalContentConnector extends Component {
render(props) {
return (
<RetagArtistModalContent
<RetagAuthorModalContent
{...this.props}
onRetagArtistPress={this.onRetagArtistPress}
onRetagAuthorPress={this.onRetagAuthorPress}
/>
);
}
}
RetagArtistModalContentConnector.propTypes = {
RetagAuthorModalContentConnector.propTypes = {
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(RetagArtistModalContentConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(RetagAuthorModalContentConnector);

@ -13,12 +13,12 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import FilterMenu from 'Components/Menu/FilterMenu';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import NoArtist from 'Artist/NoArtist';
import OrganizeArtistModal from './Organize/OrganizeArtistModal';
import RetagArtistModal from './AudioTags/RetagArtistModal';
import ArtistEditorRowConnector from './ArtistEditorRowConnector';
import ArtistEditorFooter from './ArtistEditorFooter';
import ArtistEditorFilterModalConnector from './ArtistEditorFilterModalConnector';
import NoAuthor from 'Author/NoAuthor';
import OrganizeAuthorModal from './Organize/OrganizeAuthorModal';
import RetagAuthorModal from './AudioTags/RetagAuthorModal';
import AuthorEditorRowConnector from './AuthorEditorRowConnector';
import AuthorEditorFooter from './AuthorEditorFooter';
import AuthorEditorFilterModalConnector from './AuthorEditorFilterModalConnector';
function getColumns(showMetadataProfile) {
return [
@ -60,7 +60,7 @@ function getColumns(showMetadataProfile) {
];
}
class ArtistEditor extends Component {
class AuthorEditor extends Component {
//
// Lifecycle
@ -73,8 +73,8 @@ class ArtistEditor extends Component {
allUnselected: false,
lastToggled: null,
selectedState: {},
isOrganizingArtistModalOpen: false,
isRetaggingArtistModalOpen: false,
isOrganizingAuthorModalOpen: false,
isRetaggingAuthorModalOpen: false,
columns: getColumns(props.showMetadataProfile)
};
}
@ -121,24 +121,24 @@ class ArtistEditor extends Component {
});
}
onOrganizeArtistPress = () => {
this.setState({ isOrganizingArtistModalOpen: true });
onOrganizeAuthorPress = () => {
this.setState({ isOrganizingAuthorModalOpen: true });
}
onOrganizeArtistModalClose = (organized) => {
this.setState({ isOrganizingArtistModalOpen: false });
onOrganizeAuthorModalClose = (organized) => {
this.setState({ isOrganizingAuthorModalOpen: false });
if (organized === true) {
this.onSelectAllChange({ value: false });
}
}
onRetagArtistPress = () => {
this.setState({ isRetaggingArtistModalOpen: true });
onRetagAuthorPress = () => {
this.setState({ isRetaggingAuthorModalOpen: true });
}
onRetagArtistModalClose = (organized) => {
this.setState({ isRetaggingArtistModalOpen: false });
onRetagAuthorModalClose = (organized) => {
this.setState({ isRetaggingAuthorModalOpen: false });
if (organized === true) {
this.onSelectAllChange({ value: false });
@ -164,8 +164,8 @@ class ArtistEditor extends Component {
saveError,
isDeleting,
deleteError,
isOrganizingArtist,
isRetaggingArtist,
isOrganizingAuthor,
isRetaggingAuthor,
showMetadataProfile,
onSortPress,
onFilterSelect
@ -181,7 +181,7 @@ class ArtistEditor extends Component {
const selectedAuthorIds = this.getSelectedIds();
return (
<PageContent title="Artist Editor">
<PageContent title="Author Editor">
<PageToolbar>
<PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}>
@ -190,7 +190,7 @@ class ArtistEditor extends Component {
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
filterModalConnectorComponent={ArtistEditorFilterModalConnector}
filterModalConnectorComponent={AuthorEditorFilterModalConnector}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
@ -204,7 +204,7 @@ class ArtistEditor extends Component {
{
!isFetching && !!error &&
<div>{getErrorMessage(error, 'Failed to load artist from API')}</div>
<div>{getErrorMessage(error, 'Failed to load author from API')}</div>
}
{
@ -224,7 +224,7 @@ class ArtistEditor extends Component {
{
items.map((item) => {
return (
<ArtistEditorRowConnector
<AuthorEditorRowConnector
key={item.id}
{...item}
columns={columns}
@ -241,35 +241,35 @@ class ArtistEditor extends Component {
{
!error && isPopulated && !items.length &&
<NoArtist totalItems={totalItems} />
<NoAuthor totalItems={totalItems} />
}
</PageContentBodyConnector>
<ArtistEditorFooter
<AuthorEditorFooter
authorIds={selectedAuthorIds}
selectedCount={selectedAuthorIds.length}
isSaving={isSaving}
saveError={saveError}
isDeleting={isDeleting}
deleteError={deleteError}
isOrganizingArtist={isOrganizingArtist}
isRetaggingArtist={isRetaggingArtist}
isOrganizingAuthor={isOrganizingAuthor}
isRetaggingAuthor={isRetaggingAuthor}
showMetadataProfile={showMetadataProfile}
onSaveSelected={this.onSaveSelected}
onOrganizeArtistPress={this.onOrganizeArtistPress}
onRetagArtistPress={this.onRetagArtistPress}
onOrganizeAuthorPress={this.onOrganizeAuthorPress}
onRetagAuthorPress={this.onRetagAuthorPress}
/>
<OrganizeArtistModal
isOpen={this.state.isOrganizingArtistModalOpen}
<OrganizeAuthorModal
isOpen={this.state.isOrganizingAuthorModalOpen}
authorIds={selectedAuthorIds}
onModalClose={this.onOrganizeArtistModalClose}
onModalClose={this.onOrganizeAuthorModalClose}
/>
<RetagArtistModal
isOpen={this.state.isRetaggingArtistModalOpen}
<RetagAuthorModal
isOpen={this.state.isRetaggingAuthorModalOpen}
authorIds={selectedAuthorIds}
onModalClose={this.onRetagArtistModalClose}
onModalClose={this.onRetagAuthorModalClose}
/>
</PageContent>
@ -277,7 +277,7 @@ class ArtistEditor extends Component {
}
}
ArtistEditor.propTypes = {
AuthorEditor.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
@ -292,12 +292,12 @@ ArtistEditor.propTypes = {
saveError: PropTypes.object,
isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object,
isOrganizingArtist: PropTypes.bool.isRequired,
isRetaggingArtist: PropTypes.bool.isRequired,
isOrganizingAuthor: PropTypes.bool.isRequired,
isRetaggingAuthor: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired,
onSaveSelected: PropTypes.func.isRequired
};
export default ArtistEditor;
export default AuthorEditor;

@ -4,38 +4,38 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import { setArtistEditorSort, setArtistEditorFilter, saveArtistEditor } from 'Store/Actions/artistEditorActions';
import { saveAuthorEditor, setAuthorEditorFilter, setAuthorEditorSort } from 'Store/Actions/authorEditorActions';
import { fetchRootFolders } from 'Store/Actions/settingsActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import ArtistEditor from './ArtistEditor';
import AuthorEditor from './AuthorEditor';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.metadataProfiles,
createClientSideCollectionSelector('artist', 'artistEditor'),
createCommandExecutingSelector(commandNames.RENAME_ARTIST),
createCommandExecutingSelector(commandNames.RETAG_ARTIST),
(metadataProfiles, artist, isOrganizingArtist, isRetaggingArtist) => {
createClientSideCollectionSelector('authors', 'authorEditor'),
createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
(metadataProfiles, author, isOrganizingAuthor, isRetaggingAuthor) => {
return {
isOrganizingArtist,
isRetaggingArtist,
isOrganizingAuthor,
isRetaggingAuthor,
showMetadataProfile: metadataProfiles.items.length > 1,
...artist
...author
};
}
);
}
const mapDispatchToProps = {
dispatchSetArtistEditorSort: setArtistEditorSort,
dispatchSetArtistEditorFilter: setArtistEditorFilter,
dispatchSaveArtistEditor: saveArtistEditor,
dispatchSetAuthorEditorSort: setAuthorEditorSort,
dispatchSetAuthorEditorFilter: setAuthorEditorFilter,
dispatchSaveAuthorEditor: saveAuthorEditor,
dispatchFetchRootFolders: fetchRootFolders,
dispatchExecuteCommand: executeCommand
};
class ArtistEditorConnector extends Component {
class AuthorEditorConnector extends Component {
//
// Lifecycle
@ -48,20 +48,20 @@ class ArtistEditorConnector extends Component {
// Listeners
onSortPress = (sortKey) => {
this.props.dispatchSetArtistEditorSort({ sortKey });
this.props.dispatchSetAuthorEditorSort({ sortKey });
}
onFilterSelect = (selectedFilterKey) => {
this.props.dispatchSetArtistEditorFilter({ selectedFilterKey });
this.props.dispatchSetAuthorEditorFilter({ selectedFilterKey });
}
onSaveSelected = (payload) => {
this.props.dispatchSaveArtistEditor(payload);
this.props.dispatchSaveAuthorEditor(payload);
}
onMoveSelected = (payload) => {
this.props.dispatchExecuteCommand({
name: commandNames.MOVE_ARTIST,
name: commandNames.MOVE_AUTHOR,
...payload
});
}
@ -71,7 +71,7 @@ class ArtistEditorConnector extends Component {
render() {
return (
<ArtistEditor
<AuthorEditor
{...this.props}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
@ -81,12 +81,12 @@ class ArtistEditorConnector extends Component {
}
}
ArtistEditorConnector.propTypes = {
dispatchSetArtistEditorSort: PropTypes.func.isRequired,
dispatchSetArtistEditorFilter: PropTypes.func.isRequired,
dispatchSaveArtistEditor: PropTypes.func.isRequired,
AuthorEditorConnector.propTypes = {
dispatchSetAuthorEditorSort: PropTypes.func.isRequired,
dispatchSetAuthorEditorFilter: PropTypes.func.isRequired,
dispatchSaveAuthorEditor: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistEditorConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorEditorConnector);

@ -1,24 +1,24 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setArtistEditorFilter } from 'Store/Actions/artistEditorActions';
import { setAuthorEditorFilter } from 'Store/Actions/authorEditorActions';
import FilterModal from 'Components/Filter/FilterModal';
function createMapStateToProps() {
return createSelector(
(state) => state.artist.items,
(state) => state.artistEditor.filterBuilderProps,
(state) => state.authors.items,
(state) => state.authorEditor.filterBuilderProps,
(sectionItems, filterBuilderProps) => {
return {
sectionItems,
filterBuilderProps,
customFilterType: 'artistEditor'
customFilterType: 'authorEditor'
};
}
);
}
const mapDispatchToProps = {
dispatchSetFilter: setArtistEditorFilter
dispatchSetFilter: setAuthorEditorFilter
};
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);

@ -64,7 +64,7 @@
justify-content: space-between;
}
.selectedArtistLabel {
.selectedAuthorLabel {
text-align: left;
}
}

@ -7,15 +7,15 @@ import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSe
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter';
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal';
import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import TagsModal from './Tags/TagsModal';
import DeleteArtistModal from './Delete/DeleteArtistModal';
import ArtistEditorFooterLabel from './ArtistEditorFooterLabel';
import styles from './ArtistEditorFooter.css';
import DeleteAuthorModal from './Delete/DeleteAuthorModal';
import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
import styles from './AuthorEditorFooter.css';
const NO_CHANGE = 'noChange';
class ArtistEditorFooter extends Component {
class AuthorEditorFooter extends Component {
//
// Lifecycle
@ -27,10 +27,9 @@ class ArtistEditorFooter extends Component {
monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE,
albumFolder: NO_CHANGE,
rootFolderPath: NO_CHANGE,
savingTags: false,
isDeleteArtistModalOpen: false,
isDeleteAuthorModalOpen: false,
isTagsModalOpen: false,
isConfirmMoveModalOpen: false,
destinationRootFolder: null
@ -48,7 +47,6 @@ class ArtistEditorFooter extends Component {
monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE,
albumFolder: NO_CHANGE,
rootFolderPath: NO_CHANGE,
savingTags: false
});
@ -75,9 +73,6 @@ class ArtistEditorFooter extends Component {
case 'monitored':
this.props.onSaveSelected({ [name]: value === 'monitored' });
break;
case 'albumFolder':
this.props.onSaveSelected({ [name]: value === 'yes' });
break;
default:
this.props.onSaveSelected({ [name]: value });
}
@ -96,11 +91,11 @@ class ArtistEditorFooter extends Component {
}
onDeleteSelectedPress = () => {
this.setState({ isDeleteArtistModalOpen: true });
this.setState({ isDeleteAuthorModalOpen: true });
}
onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false });
onDeleteAuthorModalClose = () => {
this.setState({ isDeleteAuthorModalOpen: false });
}
onTagsPress = () => {
@ -120,7 +115,7 @@ class ArtistEditorFooter extends Component {
this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder });
}
onMoveArtistPress = () => {
onMoveAuthorPress = () => {
this.setState({
isConfirmMoveModalOpen: false,
destinationRootFolder: null
@ -141,22 +136,21 @@ class ArtistEditorFooter extends Component {
selectedCount,
isSaving,
isDeleting,
isOrganizingArtist,
isRetaggingArtist,
isOrganizingAuthor,
isRetaggingAuthor,
showMetadataProfile,
onOrganizeArtistPress,
onRetagArtistPress
onOrganizeAuthorPress,
onRetagAuthorPress
} = this.props;
const {
monitored,
qualityProfileId,
metadataProfileId,
albumFolder,
rootFolderPath,
savingTags,
isTagsModalOpen,
isDeleteArtistModalOpen,
isDeleteAuthorModalOpen,
isConfirmMoveModalOpen,
destinationRootFolder
} = this.state;
@ -167,17 +161,11 @@ class ArtistEditorFooter extends Component {
{ key: 'unmonitored', value: 'Unmonitored' }
];
const albumFolderOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' }
];
return (
<PageContentFooter>
<div className={styles.inputContainer}>
<ArtistEditorFooterLabel
label="Monitor Artist"
<AuthorEditorFooterLabel
label="Monitor Author"
isSaving={isSaving && monitored !== NO_CHANGE}
/>
@ -191,7 +179,7 @@ class ArtistEditorFooter extends Component {
</div>
<div className={styles.inputContainer}>
<ArtistEditorFooterLabel
<AuthorEditorFooterLabel
label="Quality Profile"
isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/>
@ -208,7 +196,7 @@ class ArtistEditorFooter extends Component {
{
showMetadataProfile &&
<div className={styles.inputContainer}>
<ArtistEditorFooterLabel
<AuthorEditorFooterLabel
label="Metadata Profile"
isSaving={isSaving && metadataProfileId !== NO_CHANGE}
/>
@ -225,22 +213,7 @@ class ArtistEditorFooter extends Component {
}
<div className={styles.inputContainer}>
<ArtistEditorFooterLabel
label="Album Folder"
isSaving={isSaving && albumFolder !== NO_CHANGE}
/>
<SelectInput
name="albumFolder"
value={albumFolder}
values={albumFolderOptions}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<ArtistEditorFooterLabel
<AuthorEditorFooterLabel
label="Root Folder"
isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/>
@ -257,8 +230,8 @@ class ArtistEditorFooter extends Component {
<div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}>
<ArtistEditorFooterLabel
label={`${selectedCount} Artist(s) Selected`}
<AuthorEditorFooterLabel
label={`${selectedCount} Author(s) Selected`}
isSaving={false}
/>
@ -267,9 +240,9 @@ class ArtistEditorFooter extends Component {
<SpinnerButton
className={styles.organizeSelectedButton}
kind={kinds.WARNING}
isSpinning={isOrganizingArtist}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
onPress={onOrganizeArtistPress}
isSpinning={isOrganizingAuthor}
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
onPress={onOrganizeAuthorPress}
>
Rename Files
</SpinnerButton>
@ -277,9 +250,9 @@ class ArtistEditorFooter extends Component {
<SpinnerButton
className={styles.organizeSelectedButton}
kind={kinds.WARNING}
isSpinning={isRetaggingArtist}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
onPress={onRetagArtistPress}
isSpinning={isRetaggingAuthor}
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
onPress={onRetagAuthorPress}
>
Write Metadata Tags
</SpinnerButton>
@ -287,7 +260,7 @@ class ArtistEditorFooter extends Component {
<SpinnerButton
className={styles.tagsButton}
isSpinning={isSaving && savingTags}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist}
isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
onPress={this.onTagsPress}
>
Set Readarr Tags
@ -314,17 +287,17 @@ class ArtistEditorFooter extends Component {
onModalClose={this.onTagsModalClose}
/>
<DeleteArtistModal
isOpen={isDeleteArtistModalOpen}
<DeleteAuthorModal
isOpen={isDeleteAuthorModalOpen}
authorIds={authorIds}
onModalClose={this.onDeleteArtistModalClose}
onModalClose={this.onDeleteAuthorModalClose}
/>
<MoveArtistModal
<MoveAuthorModal
destinationRootFolder={destinationRootFolder}
isOpen={isConfirmMoveModalOpen}
onSavePress={this.onSaveRootFolderPress}
onMoveArtistPress={this.onMoveArtistPress}
onMoveAuthorPress={this.onMoveAuthorPress}
/>
</PageContentFooter>
@ -332,19 +305,19 @@ class ArtistEditorFooter extends Component {
}
}
ArtistEditorFooter.propTypes = {
AuthorEditorFooter.propTypes = {
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
selectedCount: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object,
isOrganizingArtist: PropTypes.bool.isRequired,
isRetaggingArtist: PropTypes.bool.isRequired,
isOrganizingAuthor: PropTypes.bool.isRequired,
isRetaggingAuthor: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired,
onSaveSelected: PropTypes.func.isRequired,
onOrganizeArtistPress: PropTypes.func.isRequired,
onRetagArtistPress: PropTypes.func.isRequired
onOrganizeAuthorPress: PropTypes.func.isRequired,
onRetagAuthorPress: PropTypes.func.isRequired
};
export default ArtistEditorFooter;
export default AuthorEditorFooter;

@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react';
import { icons } from 'Helpers/Props';
import SpinnerIcon from 'Components/SpinnerIcon';
import styles from './ArtistEditorFooterLabel.css';
import styles from './AuthorEditorFooterLabel.css';
function ArtistEditorFooterLabel(props) {
function AuthorEditorFooterLabel(props) {
const {
className,
label,
@ -27,14 +27,14 @@ function ArtistEditorFooterLabel(props) {
);
}
ArtistEditorFooterLabel.propTypes = {
AuthorEditorFooterLabel.propTypes = {
className: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
isSaving: PropTypes.bool.isRequired
};
ArtistEditorFooterLabel.defaultProps = {
AuthorEditorFooterLabel.defaultProps = {
className: styles.label
};
export default ArtistEditorFooterLabel;
export default AuthorEditorFooterLabel;

@ -5,16 +5,15 @@ import TagListConnector from 'Components/TagListConnector';
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import ArtistNameLink from 'Artist/ArtistNameLink';
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
import styles from './ArtistEditorRow.css';
import AuthorNameLink from 'Author/AuthorNameLink';
import AuthorStatusCell from 'Author/Index/Table/AuthorStatusCell';
class ArtistEditorRow extends Component {
class AuthorEditorRow extends Component {
//
// Listeners
onAlbumFolderChange = () => {
onBookFolderChange = () => {
// Mock handler to satisfy `onChange` being required for `CheckInput`.
//
}
@ -27,8 +26,8 @@ class ArtistEditorRow extends Component {
id,
status,
titleSlug,
artistName,
artistType,
authorName,
authorType,
monitored,
metadataProfile,
qualityProfile,
@ -47,16 +46,16 @@ class ArtistEditorRow extends Component {
onSelectedChange={onSelectedChange}
/>
<ArtistStatusCell
artistType={artistType}
<AuthorStatusCell
authorType={authorType}
monitored={monitored}
status={status}
/>
<TableRowCell className={styles.title}>
<ArtistNameLink
<TableRowCell>
<AuthorNameLink
titleSlug={titleSlug}
artistName={artistName}
authorName={authorName}
/>
</TableRowCell>
@ -85,12 +84,12 @@ class ArtistEditorRow extends Component {
}
}
ArtistEditorRow.propTypes = {
AuthorEditorRow.propTypes = {
id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
artistName: PropTypes.string.isRequired,
artistType: PropTypes.string,
authorName: PropTypes.string.isRequired,
authorType: PropTypes.string,
monitored: PropTypes.bool.isRequired,
metadataProfile: PropTypes.object.isRequired,
qualityProfile: PropTypes.object.isRequired,
@ -101,8 +100,8 @@ ArtistEditorRow.propTypes = {
onSelectedChange: PropTypes.func.isRequired
};
ArtistEditorRow.defaultProps = {
AuthorEditorRow.defaultProps = {
tags: []
};
export default ArtistEditorRow;
export default AuthorEditorRow;

@ -4,7 +4,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createMetadataProfileSelector from 'Store/Selectors/createMetadataProfileSelector';
import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector';
import ArtistEditorRow from './ArtistEditorRow';
import AuthorEditorRow from './AuthorEditorRow';
function createMapStateToProps() {
return createSelector(
@ -19,16 +19,16 @@ function createMapStateToProps() {
);
}
function ArtistEditorRowConnector(props) {
function AuthorEditorRowConnector(props) {
return (
<ArtistEditorRow
<AuthorEditorRow
{...props}
/>
);
}
ArtistEditorRowConnector.propTypes = {
AuthorEditorRowConnector.propTypes = {
qualityProfileId: PropTypes.number.isRequired
};
export default connect(createMapStateToProps)(ArtistEditorRowConnector);
export default connect(createMapStateToProps)(AuthorEditorRowConnector);

@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import AddNewArtistModalContentConnector from './AddNewArtistModalContentConnector';
import DeleteAuthorModalContentConnector from './DeleteAuthorModalContentConnector';
function AddNewArtistModal(props) {
function DeleteAuthorModal(props) {
const {
isOpen,
onModalClose,
@ -15,7 +15,7 @@ function AddNewArtistModal(props) {
isOpen={isOpen}
onModalClose={onModalClose}
>
<AddNewArtistModalContentConnector
<DeleteAuthorModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@ -23,9 +23,9 @@ function AddNewArtistModal(props) {
);
}
AddNewArtistModal.propTypes = {
DeleteAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default AddNewArtistModal;
export default DeleteAuthorModal;

@ -9,9 +9,9 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './DeleteArtistModalContent.css';
import styles from './DeleteAuthorModalContent.css';
class DeleteArtistModalContent extends Component {
class DeleteAuthorModalContent extends Component {
//
// Lifecycle
@ -31,7 +31,7 @@ class DeleteArtistModalContent extends Component {
this.setState({ deleteFiles: value });
}
onDeleteArtistConfirmed = () => {
onDeleteAuthorConfirmed = () => {
const deleteFiles = this.state.deleteFiles;
this.setState({ deleteFiles: false });
@ -43,7 +43,7 @@ class DeleteArtistModalContent extends Component {
render() {
const {
artist,
author,
onModalClose
} = this.props;
const deleteFiles = this.state.deleteFiles;
@ -51,19 +51,19 @@ class DeleteArtistModalContent extends Component {
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Delete Selected Artist
Delete Selected Author
</ModalHeader>
<ModalBody>
<div>
<FormGroup>
<FormLabel>{`Delete Artist Folder${artist.length > 1 ? 's' : ''}`}</FormLabel>
<FormLabel>{`Delete Author Folder${author.length > 1 ? 's' : ''}`}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="deleteFiles"
value={deleteFiles}
helpText={`Delete Artist Folder${artist.length > 1 ? 's' : ''} and all contents`}
helpText={`Delete Author Folder${author.length > 1 ? 's' : ''} and all contents`}
kind={kinds.DANGER}
onChange={this.onDeleteFilesChange}
/>
@ -71,15 +71,15 @@ class DeleteArtistModalContent extends Component {
</div>
<div className={styles.message}>
{`Are you sure you want to delete ${artist.length} selected artist${artist.length > 1 ? 's' : ''}${deleteFiles ? ' and all contents' : ''}?`}
{`Are you sure you want to delete ${author.length} selected author${author.length > 1 ? 's' : ''}${deleteFiles ? ' and all contents' : ''}?`}
</div>
<ul>
{
artist.map((s) => {
author.map((s) => {
return (
<li key={s.artistName}>
<span>{s.artistName}</span>
<li key={s.authorName}>
<span>{s.authorName}</span>
{
deleteFiles &&
@ -104,7 +104,7 @@ class DeleteArtistModalContent extends Component {
<Button
kind={kinds.DANGER}
onPress={this.onDeleteArtistConfirmed}
onPress={this.onDeleteAuthorConfirmed}
>
Delete
</Button>
@ -114,10 +114,10 @@ class DeleteArtistModalContent extends Component {
}
}
DeleteArtistModalContent.propTypes = {
artist: PropTypes.arrayOf(PropTypes.object).isRequired,
DeleteAuthorModalContent.propTypes = {
author: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onDeleteSelectedPress: PropTypes.func.isRequired
};
export default DeleteArtistModalContent;
export default DeleteAuthorModalContent;

@ -1,29 +1,29 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import { bulkDeleteArtist } from 'Store/Actions/artistEditorActions';
import DeleteArtistModalContent from './DeleteArtistModalContent';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import { bulkDeleteAuthor } from 'Store/Actions/authorEditorActions';
import DeleteAuthorModalContent from './DeleteAuthorModalContent';
function createMapStateToProps() {
return createSelector(
(state, { authorIds }) => authorIds,
createAllArtistSelector(),
(authorIds, allArtists) => {
const selectedArtist = _.intersectionWith(allArtists, authorIds, (s, id) => {
createAllAuthorSelector(),
(authorIds, allAuthors) => {
const selectedAuthor = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id;
});
const sortedArtist = _.orderBy(selectedArtist, 'sortName');
const artist = _.map(sortedArtist, (s) => {
const sortedAuthor = _.orderBy(selectedAuthor, 'sortName');
const author = _.map(sortedAuthor, (s) => {
return {
artistName: s.artistName,
authorName: s.authorName,
path: s.path
};
});
return {
artist
author
};
}
);
@ -32,7 +32,7 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
onDeleteSelectedPress(deleteFiles) {
dispatch(bulkDeleteArtist({
dispatch(bulkDeleteAuthor({
authorIds: props.authorIds,
deleteFiles
}));
@ -42,4 +42,4 @@ function createMapDispatchToProps(dispatch, props) {
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(DeleteArtistModalContent);
export default connect(createMapStateToProps, createMapDispatchToProps)(DeleteAuthorModalContent);

@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import OrganizeArtistModalContentConnector from './OrganizeArtistModalContentConnector';
import OrganizeAuthorModalContentConnector from './OrganizeAuthorModalContentConnector';
function OrganizeArtistModal(props) {
function OrganizeAuthorModal(props) {
const {
isOpen,
onModalClose,
@ -15,7 +15,7 @@ function OrganizeArtistModal(props) {
isOpen={isOpen}
onModalClose={onModalClose}
>
<OrganizeArtistModalContentConnector
<OrganizeAuthorModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@ -23,9 +23,9 @@ function OrganizeArtistModal(props) {
);
}
OrganizeArtistModal.propTypes = {
OrganizeAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default OrganizeArtistModal;
export default OrganizeAuthorModal;

@ -8,24 +8,24 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './OrganizeArtistModalContent.css';
import styles from './OrganizeAuthorModalContent.css';
function OrganizeArtistModalContent(props) {
function OrganizeAuthorModalContent(props) {
const {
artistNames,
authorNames,
onModalClose,
onOrganizeArtistPress
onOrganizeAuthorPress
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Organize Selected Artist
Organize Selected Author
</ModalHeader>
<ModalBody>
<Alert>
Tip: To preview a rename... select "Cancel" then click any artist name and use the
Tip: To preview a rename... select "Cancel" then click any author name and use the
<Icon
className={styles.renameIcon}
name={icons.ORGANIZE}
@ -33,15 +33,15 @@ function OrganizeArtistModalContent(props) {
</Alert>
<div className={styles.message}>
Are you sure you want to organize all files in the {artistNames.length} selected artist?
Are you sure you want to organize all files in the {authorNames.length} selected author?
</div>
<ul>
{
artistNames.map((artistName) => {
authorNames.map((authorName) => {
return (
<li key={artistName}>
{artistName}
<li key={authorName}>
{authorName}
</li>
);
})
@ -56,7 +56,7 @@ function OrganizeArtistModalContent(props) {
<Button
kind={kinds.DANGER}
onPress={onOrganizeArtistPress}
onPress={onOrganizeAuthorPress}
>
Organize
</Button>
@ -65,10 +65,10 @@ function OrganizeArtistModalContent(props) {
);
}
OrganizeArtistModalContent.propTypes = {
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired,
OrganizeAuthorModalContent.propTypes = {
authorNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onModalClose: PropTypes.func.isRequired,
onOrganizeArtistPress: PropTypes.func.isRequired
onOrganizeAuthorPress: PropTypes.func.isRequired
};
export default OrganizeArtistModalContent;
export default OrganizeAuthorModalContent;

@ -3,25 +3,25 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import OrganizeArtistModalContent from './OrganizeArtistModalContent';
import OrganizeAuthorModalContent from './OrganizeAuthorModalContent';
function createMapStateToProps() {
return createSelector(
(state, { authorIds }) => authorIds,
createAllArtistSelector(),
(authorIds, allArtists) => {
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
createAllAuthorSelector(),
(authorIds, allAuthors) => {
const author = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id;
});
const sortedArtist = _.orderBy(artist, 'sortName');
const artistNames = _.map(sortedArtist, 'artistName');
const sortedAuthor = _.orderBy(author, 'sortName');
const authorNames = _.map(sortedAuthor, 'authorName');
return {
artistNames
authorNames
};
}
);
@ -31,14 +31,14 @@ const mapDispatchToProps = {
executeCommand
};
class OrganizeArtistModalContentConnector extends Component {
class OrganizeAuthorModalContentConnector extends Component {
//
// Listeners
onOrganizeArtistPress = () => {
onOrganizeAuthorPress = () => {
this.props.executeCommand({
name: commandNames.RENAME_ARTIST,
name: commandNames.RENAME_AUTHOR,
authorIds: this.props.authorIds
});
@ -50,18 +50,18 @@ class OrganizeArtistModalContentConnector extends Component {
render(props) {
return (
<OrganizeArtistModalContent
<OrganizeAuthorModalContent
{...this.props}
onOrganizeArtistPress={this.onOrganizeArtistPress}
onOrganizeAuthorPress={this.onOrganizeAuthorPress}
/>
);
}
}
OrganizeArtistModalContentConnector.propTypes = {
OrganizeAuthorModalContentConnector.propTypes = {
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeArtistModalContentConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeAuthorModalContentConnector);

@ -49,7 +49,7 @@ class TagsModalContent extends Component {
render() {
const {
artistTags,
authorTags,
tagList,
onModalClose
} = this.props;
@ -93,7 +93,7 @@ class TagsModalContent extends Component {
value={applyTags}
values={applyTagsOptions}
helpTexts={[
'How to apply tags to the selected artist',
'How to apply tags to the selected author',
'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all tags)'
@ -107,7 +107,7 @@ class TagsModalContent extends Component {
<div className={styles.result}>
{
artistTags.map((t) => {
authorTags.map((t) => {
const tag = _.find(tagList, { id: t });
if (!tag) {
@ -139,7 +139,7 @@ class TagsModalContent extends Component {
return null;
}
if (artistTags.indexOf(t) > -1) {
if (authorTags.indexOf(t) > -1) {
return null;
}
@ -178,7 +178,7 @@ class TagsModalContent extends Component {
}
TagsModalContent.propTypes = {
artistTags: PropTypes.arrayOf(PropTypes.number).isRequired,
authorTags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired,
onApplyTagsPress: PropTypes.func.isRequired

@ -1,24 +1,24 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import TagsModalContent from './TagsModalContent';
function createMapStateToProps() {
return createSelector(
(state, { authorIds }) => authorIds,
createAllArtistSelector(),
createAllAuthorSelector(),
createTagsSelector(),
(authorIds, allArtists, tagList) => {
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => {
(authorIds, allAuthors, tagList) => {
const author = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id;
});
const artistTags = _.uniq(_.concat(..._.map(artist, 'tags')));
const authorTags = _.uniq(_.concat(..._.map(author, 'tags')));
return {
artistTags,
authorTags,
tagList
};
}

@ -2,24 +2,24 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchArtistHistory, clearArtistHistory, artistHistoryMarkAsFailed } from 'Store/Actions/artistHistoryActions';
import { authorHistoryMarkAsFailed, clearAuthorHistory, fetchAuthorHistory } from 'Store/Actions/authorHistoryActions';
function createMapStateToProps() {
return createSelector(
(state) => state.artistHistory,
(artistHistory) => {
return artistHistory;
(state) => state.authorHistory,
(authorHistory) => {
return authorHistory;
}
);
}
const mapDispatchToProps = {
fetchArtistHistory,
clearArtistHistory,
artistHistoryMarkAsFailed
fetchAuthorHistory,
clearAuthorHistory,
authorHistoryMarkAsFailed
};
class ArtistHistoryContentConnector extends Component {
class AuthorHistoryContentConnector extends Component {
//
// Lifecycle
@ -30,14 +30,14 @@ class ArtistHistoryContentConnector extends Component {
bookId
} = this.props;
this.props.fetchArtistHistory({
this.props.fetchAuthorHistory({
authorId,
bookId
});
}
componentWillUnmount() {
this.props.clearArtistHistory();
this.props.clearAuthorHistory();
}
//
@ -49,7 +49,7 @@ class ArtistHistoryContentConnector extends Component {
bookId
} = this.props;
this.props.artistHistoryMarkAsFailed({
this.props.authorHistoryMarkAsFailed({
historyId,
authorId,
bookId
@ -74,13 +74,13 @@ class ArtistHistoryContentConnector extends Component {
}
}
ArtistHistoryContentConnector.propTypes = {
AuthorHistoryContentConnector.propTypes = {
component: PropTypes.elementType.isRequired,
authorId: PropTypes.number.isRequired,
bookId: PropTypes.number,
fetchArtistHistory: PropTypes.func.isRequired,
clearArtistHistory: PropTypes.func.isRequired,
artistHistoryMarkAsFailed: PropTypes.func.isRequired
fetchAuthorHistory: PropTypes.func.isRequired,
clearAuthorHistory: PropTypes.func.isRequired,
authorHistoryMarkAsFailed: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistHistoryContentConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorHistoryContentConnector);

@ -1,10 +1,10 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import ArtistHistoryContentConnector from './ArtistHistoryContentConnector';
import ArtistHistoryModalContent from './ArtistHistoryModalContent';
import AuthorHistoryContentConnector from './AuthorHistoryContentConnector';
import AuthorHistoryModalContent from './AuthorHistoryModalContent';
function ArtistHistoryModal(props) {
function AuthorHistoryModal(props) {
const {
isOpen,
onModalClose,
@ -16,8 +16,8 @@ function ArtistHistoryModal(props) {
isOpen={isOpen}
onModalClose={onModalClose}
>
<ArtistHistoryContentConnector
component={ArtistHistoryModalContent}
<AuthorHistoryContentConnector
component={AuthorHistoryModalContent}
{...otherProps}
onModalClose={onModalClose}
/>
@ -25,9 +25,9 @@ function ArtistHistoryModal(props) {
);
}
ArtistHistoryModal.propTypes = {
AuthorHistoryModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default ArtistHistoryModal;
export default AuthorHistoryModal;

@ -5,9 +5,9 @@ import ModalContent from 'Components/Modal/ModalContent';
import ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import ArtistHistoryTableContent from './ArtistHistoryTableContent';
import AuthorHistoryTableContent from './AuthorHistoryTableContent';
class ArtistHistoryModalContent extends Component {
class AuthorHistoryModalContent extends Component {
//
// Render
@ -24,7 +24,7 @@ class ArtistHistoryModalContent extends Component {
</ModalHeader>
<ModalBody>
<ArtistHistoryTableContent
<AuthorHistoryTableContent
{...this.props}
/>
</ModalBody>
@ -39,8 +39,8 @@ class ArtistHistoryModalContent extends Component {
}
}
ArtistHistoryModalContent.propTypes = {
AuthorHistoryModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired
};
export default ArtistHistoryModalContent;
export default AuthorHistoryModalContent;

@ -8,10 +8,10 @@ import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellCo
import TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover';
import TrackQuality from 'Album/TrackQuality';
import BookQuality from 'Book/BookQuality';
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
import styles from './ArtistHistoryRow.css';
import styles from './AuthorHistoryRow.css';
function getTitle(eventType) {
switch (eventType) {
@ -19,24 +19,24 @@ function getTitle(eventType) {
return 'Grabbed';
case 'downloadImported':
return 'Download Completed';
case 'trackFileImported':
return 'Track Imported';
case 'bookFileImported':
return 'Book Imported';
case 'downloadFailed':
return 'Download Failed';
case 'trackFileDeleted':
return 'Track File Deleted';
case 'trackFileRenamed':
return 'Track File Renamed';
case 'trackFileRetagged':
return 'Track File Tags Updated';
case 'albumImportIncomplete':
return 'Album Import Incomplete';
case 'bookFileDeleted':
return 'Book File Deleted';
case 'bookFileRenamed':
return 'Book File Renamed';
case 'bookFileRetagged':
return 'Book File Tags Updated';
case 'bookImportIncomplete':
return 'Book Import Incomplete';
default:
return 'Unknown';
}
}
class ArtistHistoryRow extends Component {
class AuthorHistoryRow extends Component {
//
// Lifecycle
@ -76,7 +76,7 @@ class ArtistHistoryRow extends Component {
qualityCutoffNotMet,
date,
data,
album
book
} = this.props;
const {
@ -91,7 +91,7 @@ class ArtistHistoryRow extends Component {
/>
<TableRowCell key={name}>
{album.title}
{book.title}
</TableRowCell>
<TableRowCell>
@ -99,7 +99,7 @@ class ArtistHistoryRow extends Component {
</TableRowCell>
<TableRowCell>
<TrackQuality
<BookQuality
quality={quality}
isCutoffNotMet={qualityCutoffNotMet}
/>
@ -153,7 +153,7 @@ class ArtistHistoryRow extends Component {
}
}
ArtistHistoryRow.propTypes = {
AuthorHistoryRow.propTypes = {
id: PropTypes.number.isRequired,
eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired,
@ -161,10 +161,10 @@ ArtistHistoryRow.propTypes = {
qualityCutoffNotMet: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired,
fullArtist: PropTypes.bool.isRequired,
artist: PropTypes.object.isRequired,
album: PropTypes.object.isRequired,
fullAuthor: PropTypes.bool.isRequired,
author: PropTypes.object.isRequired,
book: PropTypes.object.isRequired,
onMarkAsFailedPress: PropTypes.func.isRequired
};
export default ArtistHistoryRow;
export default AuthorHistoryRow;

@ -1,18 +1,18 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAlbumSelector from 'Store/Selectors/createAlbumSelector';
import ArtistHistoryRow from './ArtistHistoryRow';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createBookSelector from 'Store/Selectors/createBookSelector';
import AuthorHistoryRow from './AuthorHistoryRow';
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createAlbumSelector(),
(artist, album) => {
createAuthorSelector(),
createBookSelector(),
(author, book) => {
return {
artist,
album
author,
book
};
}
);
@ -23,4 +23,4 @@ const mapDispatchToProps = {
markAsFailed
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistHistoryRow);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorHistoryRow);

@ -0,0 +1,21 @@
import React from 'react';
import AuthorHistoryContentConnector from 'Author/History/AuthorHistoryContentConnector';
import AuthorHistoryTableContent from 'Author/History/AuthorHistoryTableContent';
function AuthorHistoryTable(props) {
const {
...otherProps
} = props;
return (
<AuthorHistoryContentConnector
component={AuthorHistoryTableContent}
{...otherProps}
/>
);
}
AuthorHistoryTable.propTypes = {
};
export default AuthorHistoryTable;

@ -3,7 +3,7 @@ import React, { Component } from 'react';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import ArtistHistoryRowConnector from './ArtistHistoryRowConnector';
import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
const columns = [
{
@ -11,8 +11,8 @@ const columns = [
isVisible: true
},
{
name: 'album',
label: 'Album',
name: 'book',
label: 'Book',
isVisible: true
},
{
@ -42,7 +42,7 @@ const columns = [
}
];
class ArtistHistoryTableContent extends Component {
class AuthorHistoryTableContent extends Component {
//
// Render
@ -57,7 +57,7 @@ class ArtistHistoryTableContent extends Component {
onMarkAsFailedPress
} = this.props;
const fullArtist = bookId == null;
const fullAuthor = bookId == null;
const hasItems = !!items.length;
return (
@ -84,9 +84,9 @@ class ArtistHistoryTableContent extends Component {
{
items.map((item) => {
return (
<ArtistHistoryRowConnector
<AuthorHistoryRowConnector
key={item.id}
fullArtist={fullArtist}
fullAuthor={fullAuthor}
{...item}
onMarkAsFailedPress={onMarkAsFailedPress}
/>
@ -101,7 +101,7 @@ class ArtistHistoryTableContent extends Component {
}
}
ArtistHistoryTableContent.propTypes = {
AuthorHistoryTableContent.propTypes = {
bookId: PropTypes.number,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
@ -110,4 +110,4 @@ ArtistHistoryTableContent.propTypes = {
onMarkAsFailedPress: PropTypes.func.isRequired
};
export default ArtistHistoryTableContent;
export default AuthorHistoryTableContent;

@ -13,38 +13,38 @@ import PageToolbar from 'Components/Page/Toolbar/PageToolbar';
import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import NoArtist from 'Artist/NoArtist';
import ArtistIndexTableConnector from './Table/ArtistIndexTableConnector';
import ArtistIndexTableOptionsConnector from './Table/ArtistIndexTableOptionsConnector';
import ArtistIndexPosterOptionsModal from './Posters/Options/ArtistIndexPosterOptionsModal';
import ArtistIndexPostersConnector from './Posters/ArtistIndexPostersConnector';
import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOptionsModal';
import ArtistIndexBannersConnector from './Banners/ArtistIndexBannersConnector';
import ArtistIndexOverviewOptionsModal from './Overview/Options/ArtistIndexOverviewOptionsModal';
import ArtistIndexOverviewsConnector from './Overview/ArtistIndexOverviewsConnector';
import ArtistIndexFooterConnector from './ArtistIndexFooterConnector';
import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu';
import ArtistIndexSortMenu from './Menus/ArtistIndexSortMenu';
import ArtistIndexViewMenu from './Menus/ArtistIndexViewMenu';
import styles from './ArtistIndex.css';
import NoAuthor from 'Author/NoAuthor';
import AuthorIndexTableConnector from './Table/AuthorIndexTableConnector';
import AuthorIndexTableOptionsConnector from './Table/AuthorIndexTableOptionsConnector';
import AuthorIndexPosterOptionsModal from './Posters/Options/AuthorIndexPosterOptionsModal';
import AuthorIndexPostersConnector from './Posters/AuthorIndexPostersConnector';
import AuthorIndexBannerOptionsModal from './Banners/Options/AuthorIndexBannerOptionsModal';
import AuthorIndexBannersConnector from './Banners/AuthorIndexBannersConnector';
import AuthorIndexOverviewOptionsModal from './Overview/Options/AuthorIndexOverviewOptionsModal';
import AuthorIndexOverviewsConnector from './Overview/AuthorIndexOverviewsConnector';
import AuthorIndexFooterConnector from './AuthorIndexFooterConnector';
import AuthorIndexFilterMenu from './Menus/AuthorIndexFilterMenu';
import AuthorIndexSortMenu from './Menus/AuthorIndexSortMenu';
import AuthorIndexViewMenu from './Menus/AuthorIndexViewMenu';
import styles from './AuthorIndex.css';
function getViewComponent(view) {
if (view === 'posters') {
return ArtistIndexPostersConnector;
return AuthorIndexPostersConnector;
}
if (view === 'banners') {
return ArtistIndexBannersConnector;
return AuthorIndexBannersConnector;
}
if (view === 'overview') {
return ArtistIndexOverviewsConnector;
return AuthorIndexOverviewsConnector;
}
return ArtistIndexTableConnector;
return AuthorIndexTableConnector;
}
class ArtistIndex extends Component {
class AuthorIndex extends Component {
//
// Lifecycle
@ -184,13 +184,13 @@ class ArtistIndex extends Component {
sortKey,
sortDirection,
view,
isRefreshingArtist,
isRefreshingAuthor,
isRssSyncExecuting,
onScroll,
onSortSelect,
onFilterSelect,
onViewSelect,
onRefreshArtistPress,
onRefreshAuthorPress,
onRssSyncPress,
...otherProps
} = this.props;
@ -206,7 +206,7 @@ class ArtistIndex extends Component {
const ViewComponent = getViewComponent(view);
const isLoaded = !!(!error && isPopulated && items.length && scroller);
const hasNoArtist = !totalItems;
const hasNoAuthor = !totalItems;
return (
<PageContent>
@ -216,15 +216,15 @@ class ArtistIndex extends Component {
label="Update all"
iconName={icons.REFRESH}
spinningName={icons.REFRESH}
isSpinning={isRefreshingArtist}
onPress={onRefreshArtistPress}
isSpinning={isRefreshingAuthor}
onPress={onRefreshAuthorPress}
/>
<PageToolbarButton
label="RSS Sync"
iconName={icons.RSS}
isSpinning={isRssSyncExecuting}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onPress={onRssSyncPress}
/>
@ -239,7 +239,7 @@ class ArtistIndex extends Component {
<TableOptionsModalWrapper
{...otherProps}
columns={columns}
optionsComponent={ArtistIndexTableOptionsConnector}
optionsComponent={AuthorIndexTableOptionsConnector}
>
<PageToolbarButton
label="Options"
@ -254,7 +254,7 @@ class ArtistIndex extends Component {
<PageToolbarButton
label="Options"
iconName={icons.POSTER}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onPress={this.onPosterOptionsPress}
/> :
null
@ -265,7 +265,7 @@ class ArtistIndex extends Component {
<PageToolbarButton
label="Options"
iconName={icons.POSTER}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onPress={this.onBannerOptionsPress}
/> :
null
@ -276,7 +276,7 @@ class ArtistIndex extends Component {
<PageToolbarButton
label="Options"
iconName={icons.OVERVIEW}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onPress={this.onOverviewOptionsPress}
/> :
null
@ -288,24 +288,24 @@ class ArtistIndex extends Component {
<PageToolbarSeparator />
}
<ArtistIndexViewMenu
<AuthorIndexViewMenu
view={view}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onViewSelect={onViewSelect}
/>
<ArtistIndexSortMenu
<AuthorIndexSortMenu
sortKey={sortKey}
sortDirection={sortDirection}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onSortSelect={onSortSelect}
/>
<ArtistIndexFilterMenu
<AuthorIndexFilterMenu
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
isDisabled={hasNoArtist}
isDisabled={hasNoAuthor}
onFilterSelect={onFilterSelect}
/>
</PageToolbarSection>
@ -326,7 +326,7 @@ class ArtistIndex extends Component {
{
!isFetching && !!error &&
<div className={styles.errorMessage}>
{getErrorMessage(error, 'Failed to load artist from API')}
{getErrorMessage(error, 'Failed to load author from API')}
</div>
}
@ -343,13 +343,13 @@ class ArtistIndex extends Component {
{...otherProps}
/>
<ArtistIndexFooterConnector />
<AuthorIndexFooterConnector />
</div>
}
{
!error && isPopulated && !items.length &&
<NoArtist totalItems={totalItems} />
<NoAuthor totalItems={totalItems} />
}
</PageContentBodyConnector>
@ -362,18 +362,18 @@ class ArtistIndex extends Component {
}
</div>
<ArtistIndexPosterOptionsModal
<AuthorIndexPosterOptionsModal
isOpen={isPosterOptionsModalOpen}
onModalClose={this.onPosterOptionsModalClose}
/>
<ArtistIndexBannerOptionsModal
<AuthorIndexBannerOptionsModal
isOpen={isBannerOptionsModalOpen}
onModalClose={this.onBannerOptionsModalClose}
/>
<ArtistIndexOverviewOptionsModal
<AuthorIndexOverviewOptionsModal
isOpen={isOverviewOptionsModalOpen}
onModalClose={this.onOverviewOptionsModalClose}
@ -383,7 +383,7 @@ class ArtistIndex extends Component {
}
}
ArtistIndex.propTypes = {
AuthorIndex.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
@ -396,15 +396,15 @@ ArtistIndex.propTypes = {
sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all),
view: PropTypes.string.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired,
isRefreshingAuthor: PropTypes.bool.isRequired,
isRssSyncExecuting: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired,
onViewSelect: PropTypes.func.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired,
onRefreshAuthorPress: PropTypes.func.isRequired,
onRssSyncPress: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired
};
export default ArtistIndex;
export default AuthorIndex;

@ -3,31 +3,32 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector';
import createAuthorClientSideCollectionItemsSelector
from 'Store/Selectors/createAuthorClientSideCollectionItemsSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import scrollPositions from 'Store/scrollPositions';
import { setArtistSort, setArtistFilter, setArtistView, setArtistTableOption } from 'Store/Actions/artistIndexActions';
import { setAuthorFilter, setAuthorSort, setAuthorTableOption, setAuthorView } from 'Store/Actions/authorIndexActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import withScrollPosition from 'Components/withScrollPosition';
import ArtistIndex from './ArtistIndex';
import AuthorIndex from './AuthorIndex';
function createMapStateToProps() {
return createSelector(
createArtistClientSideCollectionItemsSelector('artistIndex'),
createCommandExecutingSelector(commandNames.REFRESH_ARTIST),
createAuthorClientSideCollectionItemsSelector('authorIndex'),
createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
createCommandExecutingSelector(commandNames.RSS_SYNC),
createDimensionsSelector(),
(
artist,
isRefreshingArtist,
author,
isRefreshingAuthor,
isRssSyncExecuting,
dimensionsState
) => {
return {
...artist,
isRefreshingArtist,
...author,
isRefreshingAuthor,
isRssSyncExecuting,
isSmallScreen: dimensionsState.isSmallScreen
};
@ -38,24 +39,24 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
onTableOptionChange(payload) {
dispatch(setArtistTableOption(payload));
dispatch(setAuthorTableOption(payload));
},
onSortSelect(sortKey) {
dispatch(setArtistSort({ sortKey }));
dispatch(setAuthorSort({ sortKey }));
},
onFilterSelect(selectedFilterKey) {
dispatch(setArtistFilter({ selectedFilterKey }));
dispatch(setAuthorFilter({ selectedFilterKey }));
},
dispatchSetArtistView(view) {
dispatch(setArtistView({ view }));
dispatchSetAuthorView(view) {
dispatch(setAuthorView({ view }));
},
onRefreshArtistPress() {
onRefreshAuthorPress() {
dispatch(executeCommand({
name: commandNames.REFRESH_ARTIST
name: commandNames.REFRESH_AUTHOR
}));
},
@ -67,17 +68,17 @@ function createMapDispatchToProps(dispatch, props) {
};
}
class ArtistIndexConnector extends Component {
class AuthorIndexConnector extends Component {
//
// Listeners
onViewSelect = (view) => {
this.props.dispatchSetArtistView(view);
this.props.dispatchSetAuthorView(view);
}
onScroll = ({ scrollTop }) => {
scrollPositions.artistIndex = scrollTop;
scrollPositions.authorIndex = scrollTop;
}
//
@ -85,7 +86,7 @@ class ArtistIndexConnector extends Component {
render() {
return (
<ArtistIndex
<AuthorIndex
{...this.props}
onViewSelect={this.onViewSelect}
onScroll={this.onScroll}
@ -94,13 +95,13 @@ class ArtistIndexConnector extends Component {
}
}
ArtistIndexConnector.propTypes = {
AuthorIndexConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired,
view: PropTypes.string.isRequired,
dispatchSetArtistView: PropTypes.func.isRequired
dispatchSetAuthorView: PropTypes.func.isRequired
};
export default withScrollPosition(
connect(createMapStateToProps, createMapDispatchToProps)(ArtistIndexConnector),
'artistIndex'
connect(createMapStateToProps, createMapDispatchToProps)(AuthorIndexConnector),
'authorIndex'
);

@ -1,24 +1,24 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { setArtistFilter } from 'Store/Actions/artistIndexActions';
import { setAuthorFilter } from 'Store/Actions/authorIndexActions';
import FilterModal from 'Components/Filter/FilterModal';
function createMapStateToProps() {
return createSelector(
(state) => state.artist.items,
(state) => state.artistIndex.filterBuilderProps,
(state) => state.authors.items,
(state) => state.authorIndex.filterBuilderProps,
(sectionItems, filterBuilderProps) => {
return {
sectionItems,
filterBuilderProps,
customFilterType: 'artistIndex'
customFilterType: 'authorIndex'
};
}
);
}
const mapDispatchToProps = {
dispatchSetFilter: setArtistFilter
dispatchSetFilter: setAuthorFilter
};
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);

@ -5,34 +5,34 @@ import formatBytes from 'Utilities/Number/formatBytes';
import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
import styles from './ArtistIndexFooter.css';
import styles from './AuthorIndexFooter.css';
class ArtistIndexFooter extends PureComponent {
class AuthorIndexFooter extends PureComponent {
//
// Render
render() {
const { artist } = this.props;
const count = artist.length;
let tracks = 0;
let trackFiles = 0;
const { author } = this.props;
const count = author.length;
let books = 0;
let bookFiles = 0;
let ended = 0;
let continuing = 0;
let monitored = 0;
let totalFileSize = 0;
artist.forEach((s) => {
author.forEach((s) => {
const { statistics = {} } = s;
const {
trackCount = 0,
trackFileCount = 0,
bookCount = 0,
bookFileCount = 0,
sizeOnDisk = 0
} = statistics;
tracks += trackCount;
trackFiles += trackFileCount;
books += bookCount;
bookFiles += bookFileCount;
if (s.status === 'ended') {
ended++;
@ -127,12 +127,12 @@ class ArtistIndexFooter extends PureComponent {
<DescriptionList>
<DescriptionListItem
title="Books"
data={tracks}
data={books}
/>
<DescriptionListItem
title="Files"
data={trackFiles}
data={bookFiles}
/>
</DescriptionList>
@ -151,8 +151,8 @@ class ArtistIndexFooter extends PureComponent {
}
}
ArtistIndexFooter.propTypes = {
artist: PropTypes.arrayOf(PropTypes.object).isRequired
AuthorIndexFooter.propTypes = {
author: PropTypes.arrayOf(PropTypes.object).isRequired
};
export default ArtistIndexFooter;
export default AuthorIndexFooter;

@ -2,13 +2,13 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import ArtistIndexFooter from './ArtistIndexFooter';
import AuthorIndexFooter from './AuthorIndexFooter';
function createUnoptimizedSelector() {
return createSelector(
createClientSideCollectionSelector('artist', 'artistIndex'),
(artist) => {
return artist.items.map((s) => {
createClientSideCollectionSelector('authors', 'authorIndex'),
(authors) => {
return authors.items.map((s) => {
const {
monitored,
status,
@ -25,22 +25,22 @@ function createUnoptimizedSelector() {
);
}
function createArtistSelector() {
function createAuthorSelector() {
return createDeepEqualSelector(
createUnoptimizedSelector(),
(artist) => artist
(author) => author
);
}
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
(artist) => {
createAuthorSelector(),
(author) => {
return {
artist
author
};
}
);
}
export default connect(createMapStateToProps)(ArtistIndexFooter);
export default connect(createMapStateToProps)(AuthorIndexFooter);

@ -4,28 +4,28 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
import createArtistQualityProfileSelector from 'Store/Selectors/createArtistQualityProfileSelector';
import createArtistMetadataProfileSelector from 'Store/Selectors/createArtistMetadataProfileSelector';
import createAuthorQualityProfileSelector from 'Store/Selectors/createAuthorQualityProfileSelector';
import createAuthorMetadataProfileSelector from 'Store/Selectors/createAuthorMetadataProfileSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
function selectShowSearchAction() {
return createSelector(
(state) => state.artistIndex,
(artistIndex) => {
const view = artistIndex.view;
(state) => state.authorIndex,
(authorIndex) => {
const view = authorIndex.view;
switch (view) {
case 'posters':
return artistIndex.posterOptions.showSearchAction;
return authorIndex.posterOptions.showSearchAction;
case 'banners':
return artistIndex.bannerOptions.showSearchAction;
return authorIndex.bannerOptions.showSearchAction;
case 'overview':
return artistIndex.overviewOptions.showSearchAction;
return authorIndex.overviewOptions.showSearchAction;
default:
return artistIndex.tableOptions.showSearchAction;
return authorIndex.tableOptions.showSearchAction;
}
}
);
@ -33,52 +33,52 @@ function selectShowSearchAction() {
function createMapStateToProps() {
return createSelector(
createArtistSelector(),
createArtistQualityProfileSelector(),
createArtistMetadataProfileSelector(),
createAuthorSelector(),
createAuthorQualityProfileSelector(),
createAuthorMetadataProfileSelector(),
selectShowSearchAction(),
createExecutingCommandsSelector(),
(
artist,
author,
qualityProfile,
metadataProfile,
showSearchAction,
executingCommands
) => {
// If an artist is deleted this selector may fire before the parent
// selectors, which will result in an undefined artist, if that happens
// If an author is deleted this selector may fire before the parent
// selectors, which will result in an undefined author, if that happens
// we want to return early here and again in the render function to avoid
// trying to show an artist that has no information available.
// trying to show an author that has no information available.
if (!artist) {
if (!author) {
return {};
}
const isRefreshingArtist = executingCommands.some((command) => {
const isRefreshingAuthor = executingCommands.some((command) => {
return (
command.name === commandNames.REFRESH_ARTIST &&
command.body.authorId === artist.id
command.name === commandNames.REFRESH_AUTHOR &&
command.body.authorId === author.id
);
});
const isSearchingArtist = executingCommands.some((command) => {
const isSearchingAuthor = executingCommands.some((command) => {
return (
command.name === commandNames.ARTIST_SEARCH &&
command.body.authorId === artist.id
command.name === commandNames.AUTHOR_SEARCH &&
command.body.authorId === author.id
);
});
const latestAlbum = _.maxBy(artist.albums, (album) => album.releaseDate);
const latestBook = _.maxBy(author.books, (book) => book.releaseDate);
return {
...artist,
...author,
qualityProfile,
metadataProfile,
latestAlbum,
latestBook,
showSearchAction,
isRefreshingArtist,
isSearchingArtist
isRefreshingAuthor,
isSearchingAuthor
};
}
);
@ -88,21 +88,21 @@ const mapDispatchToProps = {
dispatchExecuteCommand: executeCommand
};
class ArtistIndexItemConnector extends Component {
class AuthorIndexItemConnector extends Component {
//
// Listeners
onRefreshArtistPress = () => {
onRefreshAuthorPress = () => {
this.props.dispatchExecuteCommand({
name: commandNames.REFRESH_ARTIST,
name: commandNames.REFRESH_AUTHOR,
authorId: this.props.id
});
}
onSearchPress = () => {
this.props.dispatchExecuteCommand({
name: commandNames.ARTIST_SEARCH,
name: commandNames.AUTHOR_SEARCH,
authorId: this.props.id
});
}
@ -125,17 +125,17 @@ class ArtistIndexItemConnector extends Component {
<ItemComponent
{...otherProps}
id={id}
onRefreshArtistPress={this.onRefreshArtistPress}
onRefreshAuthorPress={this.onRefreshAuthorPress}
onSearchPress={this.onSearchPress}
/>
);
}
}
ArtistIndexItemConnector.propTypes = {
AuthorIndexItemConnector.propTypes = {
id: PropTypes.number,
component: PropTypes.elementType.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(ArtistIndexItemConnector);
export default connect(createMapStateToProps, mapDispatchToProps)(AuthorIndexItemConnector);

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save