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

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

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

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

@ -22,7 +22,7 @@ class History extends Component {
shouldComponentUpdate(nextProps) { shouldComponentUpdate(nextProps) {
// Don't update when fetching has completed if items have changed, // 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 ( if (
( (
@ -30,7 +30,7 @@ class History extends Component {
nextProps.isPopulated && nextProps.isPopulated &&
hasDifferentItems(this.props.items, nextProps.items) hasDifferentItems(this.props.items, nextProps.items)
) || ) ||
(!this.props.isAlbumsFetching && nextProps.isAlbumsFetching) (!this.props.isBooksFetching && nextProps.isBooksFetching)
) { ) {
return false; return false;
} }
@ -51,17 +51,17 @@ class History extends Component {
selectedFilterKey, selectedFilterKey,
filters, filters,
totalRecords, totalRecords,
isAlbumsFetching, isBooksFetching,
isAlbumsPopulated, isBooksPopulated,
albumsError, booksError,
onFilterSelect, onFilterSelect,
onFirstPagePress, onFirstPagePress,
...otherProps ...otherProps
} = this.props; } = this.props;
const isFetchingAny = isFetching || isAlbumsFetching; const isFetchingAny = isFetching || isBooksFetching;
const isAllPopulated = isPopulated && (isAlbumsPopulated || !items.length); const isAllPopulated = isPopulated && (isBooksPopulated || !items.length);
const hasError = error || albumsError; const hasError = error || booksError;
return ( return (
<PageContent title="History"> <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 // 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 && isPopulated && !hasError && !items.length &&
<div> <div>
@ -162,9 +162,9 @@ History.propTypes = {
selectedFilterKey: PropTypes.string.isRequired, selectedFilterKey: PropTypes.string.isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired, filters: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
isAlbumsFetching: PropTypes.bool.isRequired, isBooksFetching: PropTypes.bool.isRequired,
isAlbumsPopulated: PropTypes.bool.isRequired, isBooksPopulated: PropTypes.bool.isRequired,
albumsError: PropTypes.object, booksError: PropTypes.object,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,
onFirstPagePress: PropTypes.func.isRequired onFirstPagePress: PropTypes.func.isRequired
}; };

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

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

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

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

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

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

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

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

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

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

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
function ArtistMetadataProfilePopoverContent() { function AuthorMetadataProfilePopoverContent() {
return ( return (
<div> <div>
Select 'None' to only include items manually added via search or that match files on disk 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 DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
function ArtistMonitoringOptionsPopoverContent() { function AuthorMonitoringOptionsPopoverContent() {
return ( return (
<DescriptionList> <DescriptionList>
<DescriptionListItem <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 getPathWithUrlBase from 'Utilities/getPathWithUrlBase';
import NotFound from 'Components/NotFound'; import NotFound from 'Components/NotFound';
import Switch from 'Components/Router/Switch'; import Switch from 'Components/Router/Switch';
import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector'; import AuthorIndexConnector from 'Author/Index/AuthorIndexConnector';
import AddNewItemConnector from 'Search/AddNewItemConnector'; import AddNewItemConnector from 'Search/AddNewItemConnector';
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector'; import AuthorEditorConnector from 'Author/Editor/AuthorEditorConnector';
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector'; import BookshelfConnector from 'Bookshelf/BookshelfConnector';
import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector'; import UnmappedFilesTableConnector from 'UnmappedFiles/UnmappedFilesTableConnector';
import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector'; import AuthorDetailsPageConnector from 'Author/Details/AuthorDetailsPageConnector';
import AlbumDetailsPageConnector from 'Album/Details/AlbumDetailsPageConnector'; import BookDetailsPageConnector from 'Book/Details/BookDetailsPageConnector';
import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import HistoryConnector from 'Activity/History/HistoryConnector'; import HistoryConnector from 'Activity/History/HistoryConnector';
import QueueConnector from 'Activity/Queue/QueueConnector'; import QueueConnector from 'Activity/Queue/QueueConnector';
@ -44,13 +44,13 @@ function AppRoutes(props) {
return ( return (
<Switch> <Switch>
{/* {/*
Artist Author
*/} */}
<Route <Route
exact={true} exact={true}
path="/" path="/"
component={ArtistIndexConnector} component={AuthorIndexConnector}
/> />
{ {
@ -76,13 +76,13 @@ function AppRoutes(props) {
/> />
<Route <Route
path="/artisteditor" path="/authoreditor"
component={ArtistEditorConnector} component={AuthorEditorConnector}
/> />
<Route <Route
path="/albumstudio" path="/bookshelf"
component={AlbumStudioConnector} component={BookshelfConnector}
/> />
<Route <Route
@ -92,12 +92,12 @@ function AppRoutes(props) {
<Route <Route
path="/author/:titleSlug" path="/author/:titleSlug"
component={ArtistDetailsPageConnector} component={AuthorDetailsPageConnector}
/> />
<Route <Route
path="/book/:titleSlug" 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 PropTypes from 'prop-types';
import React from 'react'; 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'; 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 ( return (
<ArtistImage <AuthorImage
{...props} {...props}
coverType="banner" coverType="banner"
placeholder={bannerPlaceholder} placeholder={bannerPlaceholder}
@ -14,12 +14,12 @@ function ArtistBanner(props) {
); );
} }
ArtistBanner.propTypes = { AuthorBanner.propTypes = {
size: PropTypes.number.isRequired size: PropTypes.number.isRequired
}; };
ArtistBanner.defaultProps = { AuthorBanner.defaultProps = {
size: 70 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 // Lifecycle
@ -179,7 +179,7 @@ class ArtistImage extends Component {
} }
} }
ArtistImage.propTypes = { AuthorImage.propTypes = {
className: PropTypes.string, className: PropTypes.string,
style: PropTypes.object, style: PropTypes.object,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -192,10 +192,10 @@ ArtistImage.propTypes = {
onLoad: PropTypes.func onLoad: PropTypes.func
}; };
ArtistImage.defaultProps = { AuthorImage.defaultProps = {
size: 250, size: 250,
lazy: true, lazy: true,
overflow: false overflow: false
}; };
export default ArtistImage; export default AuthorImage;

@ -2,19 +2,19 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
function ArtistNameLink({ titleSlug, artistName }) { function AuthorNameLink({ titleSlug, authorName }) {
const link = `/author/${titleSlug}`; const link = `/author/${titleSlug}`;
return ( return (
<Link to={link}> <Link to={link}>
{artistName} {authorName}
</Link> </Link>
); );
} }
ArtistNameLink.propTypes = { AuthorNameLink.propTypes = {
titleSlug: PropTypes.string.isRequired, 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 PropTypes from 'prop-types';
import React from 'react'; 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='; 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 ( return (
<ArtistImage <AuthorImage
{...props} {...props}
coverType="poster" coverType="poster"
placeholder={posterPlaceholder} placeholder={posterPlaceholder}
@ -14,12 +14,12 @@ function ArtistPoster(props) {
); );
} }
ArtistPoster.propTypes = { AuthorPoster.propTypes = {
size: PropTypes.number.isRequired size: PropTypes.number.isRequired
}; };
ArtistPoster.defaultProps = { AuthorPoster.defaultProps = {
size: 250 size: 250
}; };
export default ArtistPoster; export default AuthorPoster;

@ -2,9 +2,9 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import { sizes } from 'Helpers/Props'; import { sizes } from 'Helpers/Props';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import DeleteArtistModalContentConnector from './DeleteArtistModalContentConnector'; import DeleteAuthorModalContentConnector from './DeleteAuthorModalContentConnector';
function DeleteArtistModal(props) { function DeleteAuthorModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
@ -17,7 +17,7 @@ function DeleteArtistModal(props) {
size={sizes.MEDIUM} size={sizes.MEDIUM}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<DeleteArtistModalContentConnector <DeleteAuthorModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -25,9 +25,9 @@ function DeleteArtistModal(props) {
); );
} }
DeleteArtistModal.propTypes = { DeleteAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.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 ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody'; import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; 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 // Lifecycle
@ -38,7 +38,7 @@ class DeleteArtistModalContent extends Component {
this.setState({ addImportListExclusion: value }); this.setState({ addImportListExclusion: value });
} }
onDeleteArtistConfirmed = () => { onDeleteAuthorConfirmed = () => {
const deleteFiles = this.state.deleteFiles; const deleteFiles = this.state.deleteFiles;
const addImportListExclusion = this.state.addImportListExclusion; const addImportListExclusion = this.state.addImportListExclusion;
@ -52,26 +52,26 @@ class DeleteArtistModalContent extends Component {
render() { render() {
const { const {
artistName, authorName,
path, path,
statistics, statistics,
onModalClose onModalClose
} = this.props; } = this.props;
const { const {
trackFileCount, bookFileCount,
sizeOnDisk sizeOnDisk
} = statistics; } = statistics;
const deleteFiles = this.state.deleteFiles; const deleteFiles = this.state.deleteFiles;
const addImportListExclusion = this.state.addImportListExclusion; const addImportListExclusion = this.state.addImportListExclusion;
let deleteFilesLabel = `Delete ${trackFileCount} Track Files`; let deleteFilesLabel = `Delete ${bookFileCount} Book Files`;
let deleteFilesHelpText = 'Delete the track files and artist folder'; let deleteFilesHelpText = 'Delete the book files and author folder';
if (trackFileCount === 0) { if (bookFileCount === 0) {
deleteFilesLabel = 'Delete Artist Folder'; deleteFilesLabel = 'Delete Author Folder';
deleteFilesHelpText = 'Delete the artist folder and its contents'; deleteFilesHelpText = 'Delete the author folder and its contents';
} }
return ( return (
@ -79,7 +79,7 @@ class DeleteArtistModalContent extends Component {
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<ModalHeader> <ModalHeader>
Delete - {artistName} Delete - {authorName}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@ -112,7 +112,7 @@ class DeleteArtistModalContent extends Component {
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="addImportListExclusion" name="addImportListExclusion"
value={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} kind={kinds.DANGER}
onChange={this.onAddImportListExclusionChange} onChange={this.onAddImportListExclusionChange}
/> />
@ -121,11 +121,11 @@ class DeleteArtistModalContent extends Component {
{ {
deleteFiles && deleteFiles &&
<div className={styles.deleteFilesMessage}> <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 && !!bookFileCount &&
<div>{trackFileCount} track files totaling {formatBytes(sizeOnDisk)}</div> <div>{bookFileCount} book files totaling {formatBytes(sizeOnDisk)}</div>
} }
</div> </div>
} }
@ -139,7 +139,7 @@ class DeleteArtistModalContent extends Component {
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={this.onDeleteArtistConfirmed} onPress={this.onDeleteAuthorConfirmed}
> >
Delete Delete
</Button> </Button>
@ -149,18 +149,18 @@ class DeleteArtistModalContent extends Component {
} }
} }
DeleteArtistModalContent.propTypes = { DeleteAuthorModalContent.propTypes = {
artistName: PropTypes.string.isRequired, authorName: PropTypes.string.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired, statistics: PropTypes.object.isRequired,
onDeletePress: PropTypes.func.isRequired, onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
DeleteArtistModalContent.defaultProps = { DeleteAuthorModalContent.defaultProps = {
statistics: { 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 React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import { deleteArtist } from 'Store/Actions/artistActions'; import { deleteAuthor } from 'Store/Actions/authorActions';
import DeleteArtistModalContent from './DeleteArtistModalContent'; import DeleteAuthorModalContent from './DeleteAuthorModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistSelector(), createAuthorSelector(),
(artist) => { (author) => {
return artist; return author;
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
deleteArtist deleteAuthor
}; };
class DeleteArtistModalContentConnector extends Component { class DeleteAuthorModalContentConnector extends Component {
// //
// Listeners // Listeners
onDeletePress = (deleteFiles, addImportListExclusion) => { onDeletePress = (deleteFiles, addImportListExclusion) => {
this.props.deleteArtist({ this.props.deleteAuthor({
id: this.props.authorId, id: this.props.authorId,
deleteFiles, deleteFiles,
addImportListExclusion addImportListExclusion
@ -39,7 +39,7 @@ class DeleteArtistModalContentConnector extends Component {
render() { render() {
return ( return (
<DeleteArtistModalContent <DeleteAuthorModalContent
{...this.props} {...this.props}
onDeletePress={this.onDeletePress} onDeletePress={this.onDeletePress}
/> />
@ -47,10 +47,10 @@ class DeleteArtistModalContentConnector extends Component {
} }
} }
DeleteArtistModalContentConnector.propTypes = { DeleteAuthorModalContentConnector.propTypes = {
authorId: PropTypes.number.isRequired, authorId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.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 PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import styles from './ArtistAlternateTitles.css'; import styles from './AuthorAlternateTitles.css';
function ArtistAlternateTitles({ alternateTitles }) { function AuthorAlternateTitles({ alternateTitles }) {
return ( return (
<ul> <ul>
{ {
@ -21,8 +21,8 @@ function ArtistAlternateTitles({ alternateTitles }) {
); );
} }
ArtistAlternateTitles.propTypes = { AuthorAlternateTitles.propTypes = {
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
}; };
export default ArtistAlternateTitles; export default AuthorAlternateTitles;

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

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

@ -7,37 +7,37 @@ import { createSelector } from 'reselect';
import { findCommand, isCommandExecuting } from 'Utilities/Command'; import { findCommand, isCommandExecuting } from 'Utilities/Command';
import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator'; import { registerPagePopulator, unregisterPagePopulator } from 'Utilities/pagePopulator';
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector'; 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 createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import { fetchAlbums, clearAlbums } from 'Store/Actions/albumActions'; import { clearBooks, fetchBooks } from 'Store/Actions/bookActions';
import { fetchSeries, clearSeries } from 'Store/Actions/seriesActions'; import { clearSeries, fetchSeries } from 'Store/Actions/seriesActions';
import { fetchTrackFiles, clearTrackFiles } from 'Store/Actions/trackFileActions'; import { clearBookFiles, fetchBookFiles } from 'Store/Actions/bookFileActions';
import { toggleArtistMonitored } from 'Store/Actions/artistActions'; import { toggleAuthorMonitored } from 'Store/Actions/authorActions';
import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions'; import { clearQueueDetails, fetchQueueDetails } from 'Store/Actions/queueActions';
import { clearReleases, cancelFetchReleases } from 'Store/Actions/releaseActions'; import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import ArtistDetails from './ArtistDetails'; import AuthorDetails from './AuthorDetails';
const selectAlbums = createSelector( const selectBooks = createSelector(
(state) => state.albums, (state) => state.books,
(albums) => { (books) => {
const { const {
items, items,
isFetching, isFetching,
isPopulated, isPopulated,
error error
} = albums; } = books;
const hasAlbums = !!items.length; const hasBooks = !!items.length;
const hasMonitoredAlbums = items.some((e) => e.monitored); const hasMonitoredBooks = items.some((e) => e.monitored);
return { return {
isAlbumsFetching: isFetching, isBooksFetching: isFetching,
isAlbumsPopulated: isPopulated, isBooksPopulated: isPopulated,
albumsError: error, booksError: error,
hasAlbums, hasBooks,
hasMonitoredAlbums hasMonitoredBooks
}; };
} }
); );
@ -65,23 +65,23 @@ const selectSeries = createSelector(
} }
); );
const selectTrackFiles = createSelector( const selectBookFiles = createSelector(
(state) => state.trackFiles, (state) => state.bookFiles,
(trackFiles) => { (bookFiles) => {
const { const {
items, items,
isFetching, isFetching,
isPopulated, isPopulated,
error error
} = trackFiles; } = bookFiles;
const hasTrackFiles = !!items.length; const hasBookFiles = !!items.length;
return { return {
isTrackFilesFetching: isFetching, isBookFilesFetching: isFetching,
isTrackFilesPopulated: isPopulated, isBookFilesPopulated: isPopulated,
trackFilesError: error, bookFilesError: error,
hasTrackFiles hasBookFiles
}; };
} }
); );
@ -89,27 +89,27 @@ const selectTrackFiles = createSelector(
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { titleSlug }) => titleSlug, (state, { titleSlug }) => titleSlug,
selectAlbums, selectBooks,
selectSeries, selectSeries,
selectTrackFiles, selectBookFiles,
createAllArtistSelector(), createAllAuthorSelector(),
createCommandsSelector(), createCommandsSelector(),
(titleSlug, albums, series, trackFiles, allArtists, commands) => { (titleSlug, books, series, bookFiles, allAuthors, commands) => {
const sortedArtist = _.orderBy(allArtists, 'sortName'); const sortedAuthor = _.orderBy(allAuthors, 'sortName');
const artistIndex = _.findIndex(sortedArtist, { titleSlug }); const authorIndex = _.findIndex(sortedAuthor, { titleSlug });
const artist = sortedArtist[artistIndex]; const author = sortedAuthor[authorIndex];
if (!artist) { if (!author) {
return {}; return {};
} }
const { const {
isAlbumsFetching, isBooksFetching,
isAlbumsPopulated, isBooksPopulated,
albumsError, booksError,
hasAlbums, hasBooks,
hasMonitoredAlbums hasMonitoredBooks
} = albums; } = books;
const { const {
isSeriesFetching, isSeriesFetching,
@ -120,34 +120,34 @@ function createMapStateToProps() {
} = series; } = series;
const { const {
isTrackFilesFetching, isBookFilesFetching,
isTrackFilesPopulated, isBookFilesPopulated,
trackFilesError, bookFilesError,
hasTrackFiles hasBookFiles
} = trackFiles; } = bookFiles;
const previousArtist = sortedArtist[artistIndex - 1] || _.last(sortedArtist); const previousAuthor = sortedAuthor[authorIndex - 1] || _.last(sortedAuthor);
const nextArtist = sortedArtist[artistIndex + 1] || _.first(sortedArtist); const nextAuthor = sortedAuthor[authorIndex + 1] || _.first(sortedAuthor);
const isArtistRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_ARTIST, authorId: artist.id })); const isAuthorRefreshing = isCommandExecuting(findCommand(commands, { name: commandNames.REFRESH_AUTHOR, authorId: author.id }));
const artistRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_ARTIST }); const authorRefreshingCommand = findCommand(commands, { name: commandNames.REFRESH_AUTHOR });
const allArtistRefreshing = ( const allAuthorRefreshing = (
isCommandExecuting(artistRefreshingCommand) && isCommandExecuting(authorRefreshingCommand) &&
!artistRefreshingCommand.body.authorId !authorRefreshingCommand.body.authorId
); );
const isRefreshing = isArtistRefreshing || allArtistRefreshing; const isRefreshing = isAuthorRefreshing || allAuthorRefreshing;
const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.ARTIST_SEARCH, authorId: artist.id })); const isSearching = isCommandExecuting(findCommand(commands, { name: commandNames.AUTHOR_SEARCH, authorId: author.id }));
const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: artist.id })); const isRenamingFiles = isCommandExecuting(findCommand(commands, { name: commandNames.RENAME_FILES, authorId: author.id }));
const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST }); const isRenamingAuthorCommand = findCommand(commands, { name: commandNames.RENAME_AUTHOR });
const isRenamingArtist = ( const isRenamingAuthor = (
isCommandExecuting(isRenamingArtistCommand) && isCommandExecuting(isRenamingAuthorCommand) &&
isRenamingArtistCommand.body.authorIds.indexOf(artist.id) > -1 isRenamingAuthorCommand.body.authorIds.indexOf(author.id) > -1
); );
const isFetching = isAlbumsFetching || isSeriesFetching || isTrackFilesFetching; const isFetching = isBooksFetching || isSeriesFetching || isBookFilesFetching;
const isPopulated = isAlbumsPopulated && isSeriesPopulated && isTrackFilesPopulated; 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) && if ((alternateTitle.seasonNumber === -1 || alternateTitle.seasonNumber === undefined) &&
(alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) { (alternateTitle.sceneSeasonNumber === -1 || alternateTitle.sceneSeasonNumber === undefined)) {
acc.push(alternateTitle.title); acc.push(alternateTitle.title);
@ -157,39 +157,39 @@ function createMapStateToProps() {
}, []); }, []);
return { return {
...artist, ...author,
alternateTitles, alternateTitles,
isArtistRefreshing, isAuthorRefreshing,
allArtistRefreshing, allAuthorRefreshing,
isRefreshing, isRefreshing,
isSearching, isSearching,
isRenamingFiles, isRenamingFiles,
isRenamingArtist, isRenamingAuthor,
isFetching, isFetching,
isPopulated, isPopulated,
albumsError, booksError,
seriesError, seriesError,
trackFilesError, bookFilesError,
hasAlbums, hasBooks,
hasMonitoredAlbums, hasMonitoredBooks,
hasSeries, hasSeries,
series: seriesItems, series: seriesItems,
hasTrackFiles, hasBookFiles,
previousArtist, previousAuthor,
nextArtist nextAuthor
}; };
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
fetchAlbums, fetchBooks,
clearAlbums, clearBooks,
fetchSeries, fetchSeries,
clearSeries, clearSeries,
fetchTrackFiles, fetchBookFiles,
clearTrackFiles, clearBookFiles,
toggleArtistMonitored, toggleAuthorMonitored,
fetchQueueDetails, fetchQueueDetails,
clearQueueDetails, clearQueueDetails,
clearReleases, clearReleases,
@ -197,7 +197,7 @@ const mapDispatchToProps = {
executeCommand executeCommand
}; };
class ArtistDetailsConnector extends Component { class AuthorDetailsConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -210,22 +210,22 @@ class ArtistDetailsConnector extends Component {
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
const { const {
id, id,
isArtistRefreshing, isAuthorRefreshing,
allArtistRefreshing, allAuthorRefreshing,
isRenamingFiles, isRenamingFiles,
isRenamingArtist isRenamingAuthor
} = this.props; } = this.props;
if ( if (
(prevProps.isArtistRefreshing && !isArtistRefreshing) || (prevProps.isAuthorRefreshing && !isAuthorRefreshing) ||
(prevProps.allArtistRefreshing && !allArtistRefreshing) || (prevProps.allAuthorRefreshing && !allAuthorRefreshing) ||
(prevProps.isRenamingFiles && !isRenamingFiles) || (prevProps.isRenamingFiles && !isRenamingFiles) ||
(prevProps.isRenamingArtist && !isRenamingArtist) (prevProps.isRenamingAuthor && !isRenamingAuthor)
) { ) {
this.populate(); 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. // files and fetch from the server.
if (prevProps.id !== id) { if (prevProps.id !== id) {
@ -245,17 +245,17 @@ class ArtistDetailsConnector extends Component {
populate = () => { populate = () => {
const authorId = this.props.id; const authorId = this.props.id;
this.props.fetchAlbums({ authorId }); this.props.fetchBooks({ authorId });
this.props.fetchSeries({ authorId }); this.props.fetchSeries({ authorId });
this.props.fetchTrackFiles({ authorId }); this.props.fetchBookFiles({ authorId });
this.props.fetchQueueDetails({ authorId }); this.props.fetchQueueDetails({ authorId });
} }
unpopulate = () => { unpopulate = () => {
this.props.cancelFetchReleases(); this.props.cancelFetchReleases();
this.props.clearAlbums(); this.props.clearBooks();
this.props.clearSeries(); this.props.clearSeries();
this.props.clearTrackFiles(); this.props.clearBookFiles();
this.props.clearQueueDetails(); this.props.clearQueueDetails();
this.props.clearReleases(); this.props.clearReleases();
} }
@ -264,7 +264,7 @@ class ArtistDetailsConnector extends Component {
// Listeners // Listeners
onMonitorTogglePress = (monitored) => { onMonitorTogglePress = (monitored) => {
this.props.toggleArtistMonitored({ this.props.toggleAuthorMonitored({
authorId: this.props.id, authorId: this.props.id,
monitored monitored
}); });
@ -272,14 +272,14 @@ class ArtistDetailsConnector extends Component {
onRefreshPress = () => { onRefreshPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.REFRESH_ARTIST, name: commandNames.REFRESH_AUTHOR,
authorId: this.props.id authorId: this.props.id
}); });
} }
onSearchPress = () => { onSearchPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.ARTIST_SEARCH, name: commandNames.AUTHOR_SEARCH,
authorId: this.props.id authorId: this.props.id
}); });
} }
@ -289,7 +289,7 @@ class ArtistDetailsConnector extends Component {
render() { render() {
return ( return (
<ArtistDetails <AuthorDetails
{...this.props} {...this.props}
onMonitorTogglePress={this.onMonitorTogglePress} onMonitorTogglePress={this.onMonitorTogglePress}
onRefreshPress={this.onRefreshPress} onRefreshPress={this.onRefreshPress}
@ -299,21 +299,21 @@ class ArtistDetailsConnector extends Component {
} }
} }
ArtistDetailsConnector.propTypes = { AuthorDetailsConnector.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
isArtistRefreshing: PropTypes.bool.isRequired, isAuthorRefreshing: PropTypes.bool.isRequired,
allArtistRefreshing: PropTypes.bool.isRequired, allAuthorRefreshing: PropTypes.bool.isRequired,
isRefreshing: PropTypes.bool.isRequired, isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired, isRenamingFiles: PropTypes.bool.isRequired,
isRenamingArtist: PropTypes.bool.isRequired, isRenamingAuthor: PropTypes.bool.isRequired,
fetchAlbums: PropTypes.func.isRequired, fetchBooks: PropTypes.func.isRequired,
clearAlbums: PropTypes.func.isRequired, clearBooks: PropTypes.func.isRequired,
fetchSeries: PropTypes.func.isRequired, fetchSeries: PropTypes.func.isRequired,
clearSeries: PropTypes.func.isRequired, clearSeries: PropTypes.func.isRequired,
fetchTrackFiles: PropTypes.func.isRequired, fetchBookFiles: PropTypes.func.isRequired,
clearTrackFiles: PropTypes.func.isRequired, clearBookFiles: PropTypes.func.isRequired,
toggleArtistMonitored: PropTypes.func.isRequired, toggleAuthorMonitored: PropTypes.func.isRequired,
fetchQueueDetails: PropTypes.func.isRequired, fetchQueueDetails: PropTypes.func.isRequired,
clearQueueDetails: PropTypes.func.isRequired, clearQueueDetails: PropTypes.func.isRequired,
clearReleases: PropTypes.func.isRequired, clearReleases: PropTypes.func.isRequired,
@ -321,4 +321,4 @@ ArtistDetailsConnector.propTypes = {
executeCommand: PropTypes.func.isRequired 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 { kinds, sizes } from 'Helpers/Props';
import Label from 'Components/Label'; import Label from 'Components/Label';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import styles from './ArtistDetailsLinks.css'; import styles from './AuthorDetailsLinks.css';
function ArtistDetailsLinks(props) { function AuthorDetailsLinks(props) {
const { const {
links links
} = props; } = props;
@ -41,8 +41,8 @@ function ArtistDetailsLinks(props) {
); );
} }
ArtistDetailsLinks.propTypes = { AuthorDetailsLinks.propTypes = {
links: PropTypes.arrayOf(PropTypes.object).isRequired 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 PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import NotFound from 'Components/NotFound'; import NotFound from 'Components/NotFound';
import ArtistDetailsConnector from './ArtistDetailsConnector'; import AuthorDetailsConnector from './AuthorDetailsConnector';
import styles from './ArtistDetails.css'; import styles from './AuthorDetails.css';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { match }) => match, (state, { match }) => match,
(state) => state.artist, (state) => state.authors,
(match, artist) => { (match, authors) => {
const titleSlug = match.params.titleSlug; const titleSlug = match.params.titleSlug;
const { const {
isFetching, isFetching,
isPopulated, isPopulated,
error, error,
items items
} = artist; } = authors;
const artistIndex = _.findIndex(items, { titleSlug }); const authorIndex = _.findIndex(items, { titleSlug });
if (artistIndex > -1) { if (authorIndex > -1) {
return { return {
isFetching, isFetching,
isPopulated, isPopulated,
@ -48,7 +48,7 @@ const mapDispatchToProps = {
push push
}; };
class ArtistDetailsPageConnector extends Component { class AuthorDetailsPageConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -98,14 +98,14 @@ class ArtistDetailsPageConnector extends Component {
} }
return ( return (
<ArtistDetailsConnector <AuthorDetailsConnector
titleSlug={titleSlug} titleSlug={titleSlug}
/> />
); );
} }
} }
ArtistDetailsPageConnector.propTypes = { AuthorDetailsPageConnector.propTypes = {
titleSlug: PropTypes.string, titleSlug: PropTypes.string,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
@ -114,4 +114,4 @@ ArtistDetailsPageConnector.propTypes = {
push: PropTypes.func.isRequired 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; margin-bottom: 20px;
border: 1px solid $borderColor; border: 1px solid $borderColor;
border-radius: 4px; border-radius: 4px;
@ -18,12 +18,12 @@
cursor: pointer; cursor: pointer;
} }
.albumTypeLabel { .bookTypeLabel {
margin-right: 5px; margin-right: 5px;
margin-left: 5px; margin-left: 5px;
} }
.albumCount { .bookCount {
color: #8895aa; color: #8895aa;
font-style: italic; font-style: italic;
font-size: 18px; font-size: 18px;
@ -75,7 +75,7 @@
width: 30px; width: 30px;
} }
.albums { .books {
padding-top: 15px; padding-top: 15px;
border-top: 1px solid $borderColor; border-top: 1px solid $borderColor;
} }
@ -106,13 +106,13 @@
margin-left: -15px; margin-left: -15px;
} }
.noAlbums { .noBooks {
margin-bottom: 15px; margin-bottom: 15px;
text-align: center; text-align: center;
} }
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.albumType { .bookType {
border-right: 0; border-right: 0;
border-left: 0; border-left: 0;
border-radius: 0; border-radius: 0;

@ -5,10 +5,10 @@ import getToggledRange from 'Utilities/Table/getToggledRange';
import { sortDirections } from 'Helpers/Props'; import { sortDirections } from 'Helpers/Props';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import AlbumRowConnector from './AlbumRowConnector'; import BookRowConnector from './BookRowConnector';
import styles from './ArtistDetailsSeason.css'; import styles from './AuthorDetailsSeason.css';
class ArtistDetailsSeason extends Component { class AuthorDetailsSeason extends Component {
// //
// Lifecycle // Lifecycle
@ -17,15 +17,15 @@ class ArtistDetailsSeason extends Component {
super(props, context); super(props, context);
this.state = { this.state = {
lastToggledAlbum: null lastToggledBook: null
}; };
} }
// //
// Listeners // Listeners
onMonitorAlbumPress = (bookId, monitored, { shiftKey }) => { onMonitorBookPress = (bookId, monitored, { shiftKey }) => {
const lastToggled = this.state.lastToggledAlbum; const lastToggled = this.state.lastToggledBook;
const bookIds = [bookId]; const bookIds = [bookId];
if (shiftKey && lastToggled) { 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 ( return (
<div <div
className={styles.albumType} className={styles.bookType}
> >
<div className={styles.albums}> <div className={styles.books}>
<Table <Table
columns={columns} columns={columns}
sortKey={sortKey} sortKey={sortKey}
@ -71,11 +71,11 @@ class ArtistDetailsSeason extends Component {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<AlbumRowConnector <BookRowConnector
key={item.id} key={item.id}
columns={columns} columns={columns}
{...item} {...item}
onMonitorAlbumPress={this.onMonitorAlbumPress} onMonitorBookPress={this.onMonitorBookPress}
/> />
); );
}) })
@ -88,7 +88,7 @@ class ArtistDetailsSeason extends Component {
} }
} }
ArtistDetailsSeason.propTypes = { AuthorDetailsSeason.propTypes = {
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all), sortDirection: PropTypes.oneOf(sortDirections.all),
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -96,8 +96,8 @@ ArtistDetailsSeason.propTypes = {
onTableOptionChange: PropTypes.func.isRequired, onTableOptionChange: PropTypes.func.isRequired,
onExpandPress: PropTypes.func.isRequired, onExpandPress: PropTypes.func.isRequired,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onMonitorAlbumPress: PropTypes.func.isRequired, onMonitorBookPress: PropTypes.func.isRequired,
uiSettings: PropTypes.object.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 { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; 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 createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; 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 { executeCommand } from 'Store/Actions/commandActions';
import ArtistDetailsSeason from './ArtistDetailsSeason'; import AuthorDetailsSeason from './AuthorDetailsSeason';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { label }) => label, (state, { label }) => label,
createClientSideCollectionSelector('albums'), createClientSideCollectionSelector('books'),
createArtistSelector(), createAuthorSelector(),
createCommandsSelector(), createCommandsSelector(),
createDimensionsSelector(), createDimensionsSelector(),
createUISettingsSelector(), createUISettingsSelector(),
(label, albums, artist, commands, dimensions, uiSettings) => { (label, books, author, commands, dimensions, uiSettings) => {
const albumsInGroup = albums.items; const booksInGroup = books.items;
let sortDir = 'asc'; let sortDir = 'asc';
if (albums.sortDirection === 'descending') { if (books.sortDirection === 'descending') {
sortDir = 'desc'; sortDir = 'desc';
} }
const sortedAlbums = _.orderBy(albumsInGroup, albums.sortKey, sortDir); const sortedBooks = _.orderBy(booksInGroup, books.sortKey, sortDir);
return { return {
items: sortedAlbums, items: sortedBooks,
columns: albums.columns, columns: books.columns,
sortKey: albums.sortKey, sortKey: books.sortKey,
sortDirection: albums.sortDirection, sortDirection: books.sortDirection,
artistMonitored: artist.monitored, authorMonitored: author.monitored,
isSmallScreen: dimensions.isSmallScreen, isSmallScreen: dimensions.isSmallScreen,
uiSettings uiSettings
}; };
@ -47,27 +47,27 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
toggleAlbumsMonitored, toggleBooksMonitored,
setAlbumsTableOption, setBooksTableOption,
dispatchSetAlbumSort: setAlbumsSort, dispatchSetBookSort: setBooksSort,
executeCommand executeCommand
}; };
class ArtistDetailsSeasonConnector extends Component { class AuthorDetailsSeasonConnector extends Component {
// //
// Listeners // Listeners
onTableOptionChange = (payload) => { onTableOptionChange = (payload) => {
this.props.setAlbumsTableOption(payload); this.props.setBooksTableOption(payload);
} }
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.dispatchSetAlbumSort({ sortKey }); this.props.dispatchSetBookSort({ sortKey });
} }
onMonitorAlbumPress = (bookIds, monitored) => { onMonitorBookPress = (bookIds, monitored) => {
this.props.toggleAlbumsMonitored({ this.props.toggleBooksMonitored({
bookIds, bookIds,
monitored monitored
}); });
@ -78,22 +78,22 @@ class ArtistDetailsSeasonConnector extends Component {
render() { render() {
return ( return (
<ArtistDetailsSeason <AuthorDetailsSeason
{...this.props} {...this.props}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onTableOptionChange={this.onTableOptionChange} onTableOptionChange={this.onTableOptionChange}
onMonitorAlbumPress={this.onMonitorAlbumPress} onMonitorBookPress={this.onMonitorBookPress}
/> />
); );
} }
} }
ArtistDetailsSeasonConnector.propTypes = { AuthorDetailsSeasonConnector.propTypes = {
authorId: PropTypes.number.isRequired, authorId: PropTypes.number.isRequired,
toggleAlbumsMonitored: PropTypes.func.isRequired, toggleBooksMonitored: PropTypes.func.isRequired,
setAlbumsTableOption: PropTypes.func.isRequired, setBooksTableOption: PropTypes.func.isRequired,
dispatchSetAlbumSort: PropTypes.func.isRequired, dispatchSetBookSort: PropTypes.func.isRequired,
executeCommand: 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; margin-bottom: 20px;
border: 1px solid $borderColor; border: 1px solid $borderColor;
border-radius: 4px; border-radius: 4px;
@ -18,12 +18,12 @@
cursor: pointer; cursor: pointer;
} }
.albumTypeLabel { .bookTypeLabel {
margin-right: 5px; margin-right: 5px;
margin-left: 5px; margin-left: 5px;
} }
.albumCount { .bookCount {
color: #8895aa; color: #8895aa;
font-style: italic; font-style: italic;
font-size: 18px; font-size: 18px;
@ -75,7 +75,7 @@
width: 30px; width: 30px;
} }
.albums { .books {
padding-top: 15px; padding-top: 15px;
border-top: 1px solid $borderColor; border-top: 1px solid $borderColor;
} }
@ -108,13 +108,13 @@
/* margin-left: -15px; */ /* margin-left: -15px; */
} }
.noAlbums { .noBooks {
margin-bottom: 15px; margin-bottom: 15px;
text-align: center; text-align: center;
} }
@media only screen and (max-width: $breakpointSmall) { @media only screen and (max-width: $breakpointSmall) {
.albumType { .bookType {
border-right: 0; border-right: 0;
border-left: 0; border-left: 0;
border-radius: 0; border-radius: 0;

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

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

@ -1,16 +1,16 @@
import _ from 'lodash'; import _ from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector';
import ArtistTags from './ArtistTags'; import AuthorTags from './AuthorTags';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistSelector(), createAuthorSelector(),
createTagsSelector(), createTagsSelector(),
(artist, tagList) => { (author, tagList) => {
const tags = _.reduce(artist.tags, (acc, tag) => { const tags = _.reduce(author.tags, (acc, tag) => {
const matchingTag = _.find(tagList, { id: tag }); const matchingTag = _.find(tagList, { id: tag });
if (matchingTag) { 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 TableRow from 'Components/Table/TableRow';
import Label from 'Components/Label'; import Label from 'Components/Label';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import AlbumSearchCellConnector from 'Album/AlbumSearchCellConnector'; import BookSearchCellConnector from 'Book/BookSearchCellConnector';
import AlbumTitleLink from 'Album/AlbumTitleLink'; import BookTitleLink from 'Book/BookTitleLink';
import StarRating from 'Components/StarRating'; import StarRating from 'Components/StarRating';
import styles from './AlbumRow.css'; import styles from './BookRow.css';
function getTrackCountKind(monitored, trackFileCount, trackCount) { function getBookCountKind(monitored, bookFileCount, bookCount) {
if (trackFileCount === trackCount && trackCount > 0) { if (bookFileCount === bookCount && bookCount > 0) {
return kinds.SUCCESS; return kinds.SUCCESS;
} }
@ -23,7 +23,7 @@ function getTrackCountKind(monitored, trackFileCount, trackCount) {
return kinds.DANGER; return kinds.DANGER;
} }
class AlbumRow extends Component { class BookRow extends Component {
// //
// Lifecycle // Lifecycle
@ -33,7 +33,7 @@ class AlbumRow extends Component {
this.state = { this.state = {
isDetailsModalOpen: false, isDetailsModalOpen: false,
isEditAlbumModalOpen: false isEditBookModalOpen: false
}; };
} }
@ -48,16 +48,16 @@ class AlbumRow extends Component {
this.setState({ isDetailsModalOpen: false }); this.setState({ isDetailsModalOpen: false });
} }
onEditAlbumPress = () => { onEditBookPress = () => {
this.setState({ isEditAlbumModalOpen: true }); this.setState({ isEditBookModalOpen: true });
} }
onEditAlbumModalClose = () => { onEditBookModalClose = () => {
this.setState({ isEditAlbumModalOpen: false }); this.setState({ isEditBookModalOpen: false });
} }
onMonitorAlbumPress = (monitored, options) => { onMonitorBookPress = (monitored, options) => {
this.props.onMonitorAlbumPress(this.props.id, monitored, options); this.props.onMonitorBookPress(this.props.id, monitored, options);
} }
// //
@ -75,15 +75,15 @@ class AlbumRow extends Component {
ratings, ratings,
disambiguation, disambiguation,
isSaving, isSaving,
artistMonitored, authorMonitored,
titleSlug, titleSlug,
columns columns
} = this.props; } = this.props;
const { const {
trackCount, bookCount,
trackFileCount, bookFileCount,
totalTrackCount totalBookCount
} = statistics; } = statistics;
return ( return (
@ -107,9 +107,9 @@ class AlbumRow extends Component {
> >
<MonitorToggleButton <MonitorToggleButton
monitored={monitored} monitored={monitored}
isDisabled={!artistMonitored} isDisabled={!authorMonitored}
isSaving={isSaving} isSaving={isSaving}
onPress={this.onMonitorAlbumPress} onPress={this.onMonitorBookPress}
/> />
</TableRowCell> </TableRowCell>
); );
@ -121,7 +121,7 @@ class AlbumRow extends Component {
key={name} key={name}
className={styles.title} className={styles.title}
> >
<AlbumTitleLink <BookTitleLink
titleSlug={titleSlug} titleSlug={titleSlug}
title={title} title={title}
disambiguation={disambiguation} disambiguation={disambiguation}
@ -170,12 +170,12 @@ class AlbumRow extends Component {
className={styles.status} className={styles.status}
> >
<Label <Label
title={`${totalTrackCount} tracks total. ${trackFileCount} tracks with files.`} title={`${totalBookCount} books total. ${bookFileCount} books with files.`}
kind={getTrackCountKind(monitored, trackFileCount, trackCount)} kind={getBookCountKind(monitored, bookFileCount, bookCount)}
size={sizes.MEDIUM} size={sizes.MEDIUM}
> >
{ {
<span>{trackFileCount} / {trackCount}</span> <span>{bookFileCount} / {bookCount}</span>
} }
</Label> </Label>
</TableRowCell> </TableRowCell>
@ -184,11 +184,11 @@ class AlbumRow extends Component {
if (name === 'actions') { if (name === 'actions') {
return ( return (
<AlbumSearchCellConnector <BookSearchCellConnector
key={name} key={name}
bookId={id} bookId={id}
authorId={authorId} authorId={authorId}
albumTitle={title} bookTitle={title}
/> />
); );
} }
@ -200,7 +200,7 @@ class AlbumRow extends Component {
} }
} }
AlbumRow.propTypes = { BookRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
authorId: PropTypes.number.isRequired, authorId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
@ -211,17 +211,17 @@ AlbumRow.propTypes = {
disambiguation: PropTypes.string, disambiguation: PropTypes.string,
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
isSaving: PropTypes.bool, isSaving: PropTypes.bool,
artistMonitored: PropTypes.bool.isRequired, authorMonitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired, statistics: PropTypes.object.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
onMonitorAlbumPress: PropTypes.func.isRequired onMonitorBookPress: PropTypes.func.isRequired
}; };
AlbumRow.defaultProps = { BookRow.defaultProps = {
statistics: { statistics: {
trackCount: 0, bookCount: 0,
trackFileCount: 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 PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; 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 ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<EditArtistModalContentConnector <EditAuthorModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -17,9 +17,9 @@ function EditArtistModal({ isOpen, onModalClose, ...otherProps }) {
); );
} }
EditArtistModal.propTypes = { EditAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.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 React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { clearPendingChanges } from 'Store/Actions/baseActions'; import { clearPendingChanges } from 'Store/Actions/baseActions';
import EditArtistModal from './EditArtistModal'; import EditAuthorModal from './EditAuthorModal';
const mapDispatchToProps = { const mapDispatchToProps = {
clearPendingChanges clearPendingChanges
}; };
class EditArtistModalConnector extends Component { class EditAuthorModalConnector extends Component {
// //
// Listeners // Listeners
onModalClose = () => { onModalClose = () => {
this.props.clearPendingChanges({ section: 'artist' }); this.props.clearPendingChanges({ section: 'author' });
this.props.onModalClose(); this.props.onModalClose();
} }
@ -23,7 +23,7 @@ class EditArtistModalConnector extends Component {
render() { render() {
return ( return (
<EditArtistModal <EditAuthorModal
{...this.props} {...this.props}
onModalClose={this.onModalClose} onModalClose={this.onModalClose}
/> />
@ -31,9 +31,9 @@ class EditArtistModalConnector extends Component {
} }
} }
EditArtistModalConnector.propTypes = { EditAuthorModalConnector.propTypes = {
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
clearPendingChanges: 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 FormInputGroup from 'Components/Form/FormInputGroup';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal'; import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import ArtistMetadataProfilePopoverContent from 'AddArtist/ArtistMetadataProfilePopoverContent'; import AuthorMetadataProfilePopoverContent from 'AddAuthor/AuthorMetadataProfilePopoverContent';
import styles from './EditArtistModalContent.css'; import styles from './EditAuthorModalContent.css';
class EditArtistModalContent extends Component { class EditAuthorModalContent extends Component {
// //
// Lifecycle // Lifecycle
@ -48,7 +48,7 @@ class EditArtistModalContent extends Component {
} }
} }
onMoveArtistPress = () => { onMoveAuthorPress = () => {
this.setState({ isConfirmMoveModalOpen: false }); this.setState({ isConfirmMoveModalOpen: false });
this.props.onSavePress(true); this.props.onSavePress(true);
@ -59,14 +59,14 @@ class EditArtistModalContent extends Component {
render() { render() {
const { const {
artistName, authorName,
item, item,
isSaving, isSaving,
showMetadataProfile, showMetadataProfile,
originalPath, originalPath,
onInputChange, onInputChange,
onModalClose, onModalClose,
onDeleteArtistPress, onDeleteAuthorPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -81,7 +81,7 @@ class EditArtistModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Edit - {artistName} Edit - {authorName}
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
@ -92,7 +92,7 @@ class EditArtistModalContent extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="monitored" name="monitored"
helpText="Download monitored albums from this artist" helpText="Download monitored books from this author"
{...monitored} {...monitored}
onChange={onInputChange} onChange={onInputChange}
/> />
@ -123,7 +123,7 @@ class EditArtistModalContent extends Component {
/> />
} }
title="Metadata Profile" title="Metadata Profile"
body={<ArtistMetadataProfilePopoverContent />} body={<AuthorMetadataProfilePopoverContent />}
position={tooltipPositions.RIGHT} position={tooltipPositions.RIGHT}
/> />
@ -132,7 +132,7 @@ class EditArtistModalContent extends Component {
<FormInputGroup <FormInputGroup
type={inputTypes.METADATA_PROFILE_SELECT} type={inputTypes.METADATA_PROFILE_SELECT}
name="metadataProfileId" name="metadataProfileId"
helpText="Changes will take place on next artist refresh" helpText="Changes will take place on next author refresh"
includeNone={true} includeNone={true}
{...metadataProfileId} {...metadataProfileId}
onChange={onInputChange} onChange={onInputChange}
@ -167,7 +167,7 @@ class EditArtistModalContent extends Component {
<Button <Button
className={styles.deleteButton} className={styles.deleteButton}
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={onDeleteArtistPress} onPress={onDeleteAuthorPress}
> >
Delete Delete
</Button> </Button>
@ -186,12 +186,12 @@ class EditArtistModalContent extends Component {
</SpinnerButton> </SpinnerButton>
</ModalFooter> </ModalFooter>
<MoveArtistModal <MoveAuthorModal
originalPath={originalPath} originalPath={originalPath}
destinationPath={path.value} destinationPath={path.value}
isOpen={this.state.isConfirmMoveModalOpen} isOpen={this.state.isConfirmMoveModalOpen}
onSavePress={this.onSavePress} onSavePress={this.onSavePress}
onMoveArtistPress={this.onMoveArtistPress} onMoveAuthorPress={this.onMoveAuthorPress}
/> />
</ModalContent> </ModalContent>
@ -199,9 +199,9 @@ class EditArtistModalContent extends Component {
} }
} }
EditArtistModalContent.propTypes = { EditAuthorModalContent.propTypes = {
authorId: PropTypes.number.isRequired, authorId: PropTypes.number.isRequired,
artistName: PropTypes.string.isRequired, authorName: PropTypes.string.isRequired,
item: PropTypes.object.isRequired, item: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired, showMetadataProfile: PropTypes.bool.isRequired,
@ -210,7 +210,7 @@ EditArtistModalContent.propTypes = {
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onSavePress: PropTypes.func.isRequired, onSavePress: PropTypes.func.isRequired,
onModalClose: 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 { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import selectSettings from 'Store/Selectors/selectSettings'; import selectSettings from 'Store/Selectors/selectSettings';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import { setArtistValue, saveArtist } from 'Store/Actions/artistActions'; import { saveAuthor, setAuthorValue } from 'Store/Actions/authorActions';
import EditArtistModalContent from './EditArtistModalContent'; import EditAuthorModalContent from './EditAuthorModalContent';
function createIsPathChangingSelector() { function createIsPathChangingSelector() {
return createSelector( return createSelector(
(state) => state.artist.pendingChanges, (state) => state.authors.pendingChanges,
createArtistSelector(), createAuthorSelector(),
(pendingChanges, artist) => { (pendingChanges, author) => {
const path = pendingChanges.path; const path = pendingChanges.path;
if (path == null) { if (path == null) {
return false; return false;
} }
return artist.path !== path; return author.path !== path;
} }
); );
} }
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.artist, (state) => state.authors,
(state) => state.settings.metadataProfiles, (state) => state.settings.metadataProfiles,
createArtistSelector(), createAuthorSelector(),
createIsPathChangingSelector(), createIsPathChangingSelector(),
(artistState, metadataProfiles, artist, isPathChanging) => { (authorsState, metadataProfiles, author, isPathChanging) => {
const { const {
isSaving, isSaving,
saveError, saveError,
pendingChanges pendingChanges
} = artistState; } = authorsState;
const artistSettings = _.pick(artist, [ const authorSettings = _.pick(author, [
'monitored', 'monitored',
'albumFolder',
'qualityProfileId', 'qualityProfileId',
'metadataProfileId', 'metadataProfileId',
'path', 'path',
'tags' 'tags'
]); ]);
const settings = selectSettings(artistSettings, pendingChanges, saveError); const settings = selectSettings(authorSettings, pendingChanges, saveError);
return { return {
artistName: artist.artistName, authorName: author.authorName,
isSaving, isSaving,
saveError, saveError,
isPathChanging, isPathChanging,
originalPath: artist.path, originalPath: author.path,
item: settings.settings, item: settings.settings,
showMetadataProfile: metadataProfiles.items.length > 1, showMetadataProfile: metadataProfiles.items.length > 1,
...settings ...settings
@ -63,11 +62,11 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchSetArtistValue: setArtistValue, dispatchSetAuthorValue: setAuthorValue,
dispatchSaveArtist: saveArtist dispatchSaveAuthor: saveAuthor
}; };
class EditArtistModalContentConnector extends Component { class EditAuthorModalContentConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -82,11 +81,11 @@ class EditArtistModalContentConnector extends Component {
// Listeners // Listeners
onInputChange = ({ name, value }) => { onInputChange = ({ name, value }) => {
this.props.dispatchSetArtistValue({ name, value }); this.props.dispatchSetAuthorValue({ name, value });
} }
onSavePress = (moveFiles) => { onSavePress = (moveFiles) => {
this.props.dispatchSaveArtist({ this.props.dispatchSaveAuthor({
id: this.props.authorId, id: this.props.authorId,
moveFiles moveFiles
}); });
@ -97,23 +96,23 @@ class EditArtistModalContentConnector extends Component {
render() { render() {
return ( return (
<EditArtistModalContent <EditAuthorModalContent
{...this.props} {...this.props}
onInputChange={this.onInputChange} onInputChange={this.onInputChange}
onSavePress={this.onSavePress} onSavePress={this.onSavePress}
onMoveArtistPress={this.onMoveArtistPress} onMoveAuthorPress={this.onMoveAuthorPress}
/> />
); );
} }
} }
EditArtistModalContentConnector.propTypes = { EditAuthorModalContentConnector.propTypes = {
authorId: PropTypes.number, authorId: PropTypes.number,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object, saveError: PropTypes.object,
dispatchSetArtistValue: PropTypes.func.isRequired, dispatchSetAuthorValue: PropTypes.func.isRequired,
dispatchSaveArtist: PropTypes.func.isRequired, dispatchSaveAuthor: PropTypes.func.isRequired,
onModalClose: 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 PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import RetagArtistModalContentConnector from './RetagArtistModalContentConnector'; import RetagAuthorModalContentConnector from './RetagAuthorModalContentConnector';
function RetagArtistModal(props) { function RetagAuthorModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
@ -15,7 +15,7 @@ function RetagArtistModal(props) {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<RetagArtistModalContentConnector <RetagAuthorModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -23,9 +23,9 @@ function RetagArtistModal(props) {
); );
} }
RetagArtistModal.propTypes = { RetagAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.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 ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody'; import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './RetagArtistModalContent.css'; import styles from './RetagAuthorModalContent.css';
function RetagArtistModalContent(props) { function RetagAuthorModalContent(props) {
const { const {
artistNames, authorNames,
onModalClose, onModalClose,
onRetagArtistPress onRetagAuthorPress
} = props; } = props;
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Retag Selected Artist Retag Selected Author
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<Alert> <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 <Icon
className={styles.retagIcon} className={styles.retagIcon}
name={icons.RETAG} name={icons.RETAG}
@ -33,14 +33,14 @@ function RetagArtistModalContent(props) {
</Alert> </Alert>
<div className={styles.message}> <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> </div>
<ul> <ul>
{ {
artistNames.map((artistName) => { authorNames.map((authorName) => {
return ( return (
<li key={artistName}> <li key={authorName}>
{artistName} {authorName}
</li> </li>
); );
}) })
@ -55,7 +55,7 @@ function RetagArtistModalContent(props) {
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={onRetagArtistPress} onPress={onRetagAuthorPress}
> >
Retag Retag
</Button> </Button>
@ -64,10 +64,10 @@ function RetagArtistModalContent(props) {
); );
} }
RetagArtistModalContent.propTypes = { RetagAuthorModalContent.propTypes = {
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired, authorNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onModalClose: PropTypes.func.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 React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import RetagArtistModalContent from './RetagArtistModalContent'; import RetagAuthorModalContent from './RetagAuthorModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { authorIds }) => authorIds, (state, { authorIds }) => authorIds,
createAllArtistSelector(), createAllAuthorSelector(),
(authorIds, allArtists) => { (authorIds, allAuthors) => {
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => { const author = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id; return s.id === id;
}); });
const sortedArtist = _.orderBy(artist, 'sortName'); const sortedAuthor = _.orderBy(author, 'sortName');
const artistNames = _.map(sortedArtist, 'artistName'); const authorNames = _.map(sortedAuthor, 'authorName');
return { return {
artistNames authorNames
}; };
} }
); );
@ -31,14 +31,14 @@ const mapDispatchToProps = {
executeCommand executeCommand
}; };
class RetagArtistModalContentConnector extends Component { class RetagAuthorModalContentConnector extends Component {
// //
// Listeners // Listeners
onRetagArtistPress = () => { onRetagAuthorPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.RETAG_ARTIST, name: commandNames.RETAG_AUTHOR,
authorIds: this.props.authorIds authorIds: this.props.authorIds
}); });
@ -50,18 +50,18 @@ class RetagArtistModalContentConnector extends Component {
render(props) { render(props) {
return ( return (
<RetagArtistModalContent <RetagAuthorModalContent
{...this.props} {...this.props}
onRetagArtistPress={this.onRetagArtistPress} onRetagAuthorPress={this.onRetagAuthorPress}
/> />
); );
} }
} }
RetagArtistModalContentConnector.propTypes = { RetagAuthorModalContentConnector.propTypes = {
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired, authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
executeCommand: 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 FilterMenu from 'Components/Menu/FilterMenu';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import NoArtist from 'Artist/NoArtist'; import NoAuthor from 'Author/NoAuthor';
import OrganizeArtistModal from './Organize/OrganizeArtistModal'; import OrganizeAuthorModal from './Organize/OrganizeAuthorModal';
import RetagArtistModal from './AudioTags/RetagArtistModal'; import RetagAuthorModal from './AudioTags/RetagAuthorModal';
import ArtistEditorRowConnector from './ArtistEditorRowConnector'; import AuthorEditorRowConnector from './AuthorEditorRowConnector';
import ArtistEditorFooter from './ArtistEditorFooter'; import AuthorEditorFooter from './AuthorEditorFooter';
import ArtistEditorFilterModalConnector from './ArtistEditorFilterModalConnector'; import AuthorEditorFilterModalConnector from './AuthorEditorFilterModalConnector';
function getColumns(showMetadataProfile) { function getColumns(showMetadataProfile) {
return [ return [
@ -60,7 +60,7 @@ function getColumns(showMetadataProfile) {
]; ];
} }
class ArtistEditor extends Component { class AuthorEditor extends Component {
// //
// Lifecycle // Lifecycle
@ -73,8 +73,8 @@ class ArtistEditor extends Component {
allUnselected: false, allUnselected: false,
lastToggled: null, lastToggled: null,
selectedState: {}, selectedState: {},
isOrganizingArtistModalOpen: false, isOrganizingAuthorModalOpen: false,
isRetaggingArtistModalOpen: false, isRetaggingAuthorModalOpen: false,
columns: getColumns(props.showMetadataProfile) columns: getColumns(props.showMetadataProfile)
}; };
} }
@ -121,24 +121,24 @@ class ArtistEditor extends Component {
}); });
} }
onOrganizeArtistPress = () => { onOrganizeAuthorPress = () => {
this.setState({ isOrganizingArtistModalOpen: true }); this.setState({ isOrganizingAuthorModalOpen: true });
} }
onOrganizeArtistModalClose = (organized) => { onOrganizeAuthorModalClose = (organized) => {
this.setState({ isOrganizingArtistModalOpen: false }); this.setState({ isOrganizingAuthorModalOpen: false });
if (organized === true) { if (organized === true) {
this.onSelectAllChange({ value: false }); this.onSelectAllChange({ value: false });
} }
} }
onRetagArtistPress = () => { onRetagAuthorPress = () => {
this.setState({ isRetaggingArtistModalOpen: true }); this.setState({ isRetaggingAuthorModalOpen: true });
} }
onRetagArtistModalClose = (organized) => { onRetagAuthorModalClose = (organized) => {
this.setState({ isRetaggingArtistModalOpen: false }); this.setState({ isRetaggingAuthorModalOpen: false });
if (organized === true) { if (organized === true) {
this.onSelectAllChange({ value: false }); this.onSelectAllChange({ value: false });
@ -164,8 +164,8 @@ class ArtistEditor extends Component {
saveError, saveError,
isDeleting, isDeleting,
deleteError, deleteError,
isOrganizingArtist, isOrganizingAuthor,
isRetaggingArtist, isRetaggingAuthor,
showMetadataProfile, showMetadataProfile,
onSortPress, onSortPress,
onFilterSelect onFilterSelect
@ -181,7 +181,7 @@ class ArtistEditor extends Component {
const selectedAuthorIds = this.getSelectedIds(); const selectedAuthorIds = this.getSelectedIds();
return ( return (
<PageContent title="Artist Editor"> <PageContent title="Author Editor">
<PageToolbar> <PageToolbar>
<PageToolbarSection /> <PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}> <PageToolbarSection alignContent={align.RIGHT}>
@ -190,7 +190,7 @@ class ArtistEditor extends Component {
selectedFilterKey={selectedFilterKey} selectedFilterKey={selectedFilterKey}
filters={filters} filters={filters}
customFilters={customFilters} customFilters={customFilters}
filterModalConnectorComponent={ArtistEditorFilterModalConnector} filterModalConnectorComponent={AuthorEditorFilterModalConnector}
onFilterSelect={onFilterSelect} onFilterSelect={onFilterSelect}
/> />
</PageToolbarSection> </PageToolbarSection>
@ -204,7 +204,7 @@ class ArtistEditor extends Component {
{ {
!isFetching && !!error && !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) => { items.map((item) => {
return ( return (
<ArtistEditorRowConnector <AuthorEditorRowConnector
key={item.id} key={item.id}
{...item} {...item}
columns={columns} columns={columns}
@ -241,35 +241,35 @@ class ArtistEditor extends Component {
{ {
!error && isPopulated && !items.length && !error && isPopulated && !items.length &&
<NoArtist totalItems={totalItems} /> <NoAuthor totalItems={totalItems} />
} }
</PageContentBodyConnector> </PageContentBodyConnector>
<ArtistEditorFooter <AuthorEditorFooter
authorIds={selectedAuthorIds} authorIds={selectedAuthorIds}
selectedCount={selectedAuthorIds.length} selectedCount={selectedAuthorIds.length}
isSaving={isSaving} isSaving={isSaving}
saveError={saveError} saveError={saveError}
isDeleting={isDeleting} isDeleting={isDeleting}
deleteError={deleteError} deleteError={deleteError}
isOrganizingArtist={isOrganizingArtist} isOrganizingAuthor={isOrganizingAuthor}
isRetaggingArtist={isRetaggingArtist} isRetaggingAuthor={isRetaggingAuthor}
showMetadataProfile={showMetadataProfile} showMetadataProfile={showMetadataProfile}
onSaveSelected={this.onSaveSelected} onSaveSelected={this.onSaveSelected}
onOrganizeArtistPress={this.onOrganizeArtistPress} onOrganizeAuthorPress={this.onOrganizeAuthorPress}
onRetagArtistPress={this.onRetagArtistPress} onRetagAuthorPress={this.onRetagAuthorPress}
/> />
<OrganizeArtistModal <OrganizeAuthorModal
isOpen={this.state.isOrganizingArtistModalOpen} isOpen={this.state.isOrganizingAuthorModalOpen}
authorIds={selectedAuthorIds} authorIds={selectedAuthorIds}
onModalClose={this.onOrganizeArtistModalClose} onModalClose={this.onOrganizeAuthorModalClose}
/> />
<RetagArtistModal <RetagAuthorModal
isOpen={this.state.isRetaggingArtistModalOpen} isOpen={this.state.isRetaggingAuthorModalOpen}
authorIds={selectedAuthorIds} authorIds={selectedAuthorIds}
onModalClose={this.onRetagArtistModalClose} onModalClose={this.onRetagAuthorModalClose}
/> />
</PageContent> </PageContent>
@ -277,7 +277,7 @@ class ArtistEditor extends Component {
} }
} }
ArtistEditor.propTypes = { AuthorEditor.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@ -292,12 +292,12 @@ ArtistEditor.propTypes = {
saveError: PropTypes.object, saveError: PropTypes.object,
isDeleting: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object, deleteError: PropTypes.object,
isOrganizingArtist: PropTypes.bool.isRequired, isOrganizingAuthor: PropTypes.bool.isRequired,
isRetaggingArtist: PropTypes.bool.isRequired, isRetaggingAuthor: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired, showMetadataProfile: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,
onSaveSelected: 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 { createSelector } from 'reselect';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; 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 { fetchRootFolders } from 'Store/Actions/settingsActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import ArtistEditor from './ArtistEditor'; import AuthorEditor from './AuthorEditor';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.settings.metadataProfiles, (state) => state.settings.metadataProfiles,
createClientSideCollectionSelector('artist', 'artistEditor'), createClientSideCollectionSelector('authors', 'authorEditor'),
createCommandExecutingSelector(commandNames.RENAME_ARTIST), createCommandExecutingSelector(commandNames.RENAME_AUTHOR),
createCommandExecutingSelector(commandNames.RETAG_ARTIST), createCommandExecutingSelector(commandNames.RETAG_AUTHOR),
(metadataProfiles, artist, isOrganizingArtist, isRetaggingArtist) => { (metadataProfiles, author, isOrganizingAuthor, isRetaggingAuthor) => {
return { return {
isOrganizingArtist, isOrganizingAuthor,
isRetaggingArtist, isRetaggingAuthor,
showMetadataProfile: metadataProfiles.items.length > 1, showMetadataProfile: metadataProfiles.items.length > 1,
...artist ...author
}; };
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchSetArtistEditorSort: setArtistEditorSort, dispatchSetAuthorEditorSort: setAuthorEditorSort,
dispatchSetArtistEditorFilter: setArtistEditorFilter, dispatchSetAuthorEditorFilter: setAuthorEditorFilter,
dispatchSaveArtistEditor: saveArtistEditor, dispatchSaveAuthorEditor: saveAuthorEditor,
dispatchFetchRootFolders: fetchRootFolders, dispatchFetchRootFolders: fetchRootFolders,
dispatchExecuteCommand: executeCommand dispatchExecuteCommand: executeCommand
}; };
class ArtistEditorConnector extends Component { class AuthorEditorConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -48,20 +48,20 @@ class ArtistEditorConnector extends Component {
// Listeners // Listeners
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.dispatchSetArtistEditorSort({ sortKey }); this.props.dispatchSetAuthorEditorSort({ sortKey });
} }
onFilterSelect = (selectedFilterKey) => { onFilterSelect = (selectedFilterKey) => {
this.props.dispatchSetArtistEditorFilter({ selectedFilterKey }); this.props.dispatchSetAuthorEditorFilter({ selectedFilterKey });
} }
onSaveSelected = (payload) => { onSaveSelected = (payload) => {
this.props.dispatchSaveArtistEditor(payload); this.props.dispatchSaveAuthorEditor(payload);
} }
onMoveSelected = (payload) => { onMoveSelected = (payload) => {
this.props.dispatchExecuteCommand({ this.props.dispatchExecuteCommand({
name: commandNames.MOVE_ARTIST, name: commandNames.MOVE_AUTHOR,
...payload ...payload
}); });
} }
@ -71,7 +71,7 @@ class ArtistEditorConnector extends Component {
render() { render() {
return ( return (
<ArtistEditor <AuthorEditor
{...this.props} {...this.props}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect} onFilterSelect={this.onFilterSelect}
@ -81,12 +81,12 @@ class ArtistEditorConnector extends Component {
} }
} }
ArtistEditorConnector.propTypes = { AuthorEditorConnector.propTypes = {
dispatchSetArtistEditorSort: PropTypes.func.isRequired, dispatchSetAuthorEditorSort: PropTypes.func.isRequired,
dispatchSetArtistEditorFilter: PropTypes.func.isRequired, dispatchSetAuthorEditorFilter: PropTypes.func.isRequired,
dispatchSaveArtistEditor: PropTypes.func.isRequired, dispatchSaveAuthorEditor: PropTypes.func.isRequired,
dispatchFetchRootFolders: PropTypes.func.isRequired, dispatchFetchRootFolders: PropTypes.func.isRequired,
dispatchExecuteCommand: 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 { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { setArtistEditorFilter } from 'Store/Actions/artistEditorActions'; import { setAuthorEditorFilter } from 'Store/Actions/authorEditorActions';
import FilterModal from 'Components/Filter/FilterModal'; import FilterModal from 'Components/Filter/FilterModal';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.artist.items, (state) => state.authors.items,
(state) => state.artistEditor.filterBuilderProps, (state) => state.authorEditor.filterBuilderProps,
(sectionItems, filterBuilderProps) => { (sectionItems, filterBuilderProps) => {
return { return {
sectionItems, sectionItems,
filterBuilderProps, filterBuilderProps,
customFilterType: 'artistEditor' customFilterType: 'authorEditor'
}; };
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
dispatchSetFilter: setArtistEditorFilter dispatchSetFilter: setAuthorEditorFilter
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal); export default connect(createMapStateToProps, mapDispatchToProps)(FilterModal);

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

@ -7,15 +7,15 @@ import QualityProfileSelectInputConnector from 'Components/Form/QualityProfileSe
import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector'; import RootFolderSelectInputConnector from 'Components/Form/RootFolderSelectInputConnector';
import SpinnerButton from 'Components/Link/SpinnerButton'; import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
import MoveArtistModal from 'Artist/MoveArtist/MoveArtistModal'; import MoveAuthorModal from 'Author/MoveAuthor/MoveAuthorModal';
import TagsModal from './Tags/TagsModal'; import TagsModal from './Tags/TagsModal';
import DeleteArtistModal from './Delete/DeleteArtistModal'; import DeleteAuthorModal from './Delete/DeleteAuthorModal';
import ArtistEditorFooterLabel from './ArtistEditorFooterLabel'; import AuthorEditorFooterLabel from './AuthorEditorFooterLabel';
import styles from './ArtistEditorFooter.css'; import styles from './AuthorEditorFooter.css';
const NO_CHANGE = 'noChange'; const NO_CHANGE = 'noChange';
class ArtistEditorFooter extends Component { class AuthorEditorFooter extends Component {
// //
// Lifecycle // Lifecycle
@ -27,10 +27,9 @@ class ArtistEditorFooter extends Component {
monitored: NO_CHANGE, monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE, qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE, metadataProfileId: NO_CHANGE,
albumFolder: NO_CHANGE,
rootFolderPath: NO_CHANGE, rootFolderPath: NO_CHANGE,
savingTags: false, savingTags: false,
isDeleteArtistModalOpen: false, isDeleteAuthorModalOpen: false,
isTagsModalOpen: false, isTagsModalOpen: false,
isConfirmMoveModalOpen: false, isConfirmMoveModalOpen: false,
destinationRootFolder: null destinationRootFolder: null
@ -48,7 +47,6 @@ class ArtistEditorFooter extends Component {
monitored: NO_CHANGE, monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE, qualityProfileId: NO_CHANGE,
metadataProfileId: NO_CHANGE, metadataProfileId: NO_CHANGE,
albumFolder: NO_CHANGE,
rootFolderPath: NO_CHANGE, rootFolderPath: NO_CHANGE,
savingTags: false savingTags: false
}); });
@ -75,9 +73,6 @@ class ArtistEditorFooter extends Component {
case 'monitored': case 'monitored':
this.props.onSaveSelected({ [name]: value === 'monitored' }); this.props.onSaveSelected({ [name]: value === 'monitored' });
break; break;
case 'albumFolder':
this.props.onSaveSelected({ [name]: value === 'yes' });
break;
default: default:
this.props.onSaveSelected({ [name]: value }); this.props.onSaveSelected({ [name]: value });
} }
@ -96,11 +91,11 @@ class ArtistEditorFooter extends Component {
} }
onDeleteSelectedPress = () => { onDeleteSelectedPress = () => {
this.setState({ isDeleteArtistModalOpen: true }); this.setState({ isDeleteAuthorModalOpen: true });
} }
onDeleteArtistModalClose = () => { onDeleteAuthorModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false }); this.setState({ isDeleteAuthorModalOpen: false });
} }
onTagsPress = () => { onTagsPress = () => {
@ -120,7 +115,7 @@ class ArtistEditorFooter extends Component {
this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder }); this.props.onSaveSelected({ rootFolderPath: this.state.destinationRootFolder });
} }
onMoveArtistPress = () => { onMoveAuthorPress = () => {
this.setState({ this.setState({
isConfirmMoveModalOpen: false, isConfirmMoveModalOpen: false,
destinationRootFolder: null destinationRootFolder: null
@ -141,22 +136,21 @@ class ArtistEditorFooter extends Component {
selectedCount, selectedCount,
isSaving, isSaving,
isDeleting, isDeleting,
isOrganizingArtist, isOrganizingAuthor,
isRetaggingArtist, isRetaggingAuthor,
showMetadataProfile, showMetadataProfile,
onOrganizeArtistPress, onOrganizeAuthorPress,
onRetagArtistPress onRetagAuthorPress
} = this.props; } = this.props;
const { const {
monitored, monitored,
qualityProfileId, qualityProfileId,
metadataProfileId, metadataProfileId,
albumFolder,
rootFolderPath, rootFolderPath,
savingTags, savingTags,
isTagsModalOpen, isTagsModalOpen,
isDeleteArtistModalOpen, isDeleteAuthorModalOpen,
isConfirmMoveModalOpen, isConfirmMoveModalOpen,
destinationRootFolder destinationRootFolder
} = this.state; } = this.state;
@ -167,17 +161,11 @@ class ArtistEditorFooter extends Component {
{ key: 'unmonitored', value: 'Unmonitored' } { key: 'unmonitored', value: 'Unmonitored' }
]; ];
const albumFolderOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' }
];
return ( return (
<PageContentFooter> <PageContentFooter>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<ArtistEditorFooterLabel <AuthorEditorFooterLabel
label="Monitor Artist" label="Monitor Author"
isSaving={isSaving && monitored !== NO_CHANGE} isSaving={isSaving && monitored !== NO_CHANGE}
/> />
@ -191,7 +179,7 @@ class ArtistEditorFooter extends Component {
</div> </div>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<ArtistEditorFooterLabel <AuthorEditorFooterLabel
label="Quality Profile" label="Quality Profile"
isSaving={isSaving && qualityProfileId !== NO_CHANGE} isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/> />
@ -208,7 +196,7 @@ class ArtistEditorFooter extends Component {
{ {
showMetadataProfile && showMetadataProfile &&
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<ArtistEditorFooterLabel <AuthorEditorFooterLabel
label="Metadata Profile" label="Metadata Profile"
isSaving={isSaving && metadataProfileId !== NO_CHANGE} isSaving={isSaving && metadataProfileId !== NO_CHANGE}
/> />
@ -225,22 +213,7 @@ class ArtistEditorFooter extends Component {
} }
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<ArtistEditorFooterLabel <AuthorEditorFooterLabel
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
label="Root Folder" label="Root Folder"
isSaving={isSaving && rootFolderPath !== NO_CHANGE} isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/> />
@ -257,8 +230,8 @@ class ArtistEditorFooter extends Component {
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}> <div className={styles.buttonContainerContent}>
<ArtistEditorFooterLabel <AuthorEditorFooterLabel
label={`${selectedCount} Artist(s) Selected`} label={`${selectedCount} Author(s) Selected`}
isSaving={false} isSaving={false}
/> />
@ -267,9 +240,9 @@ class ArtistEditorFooter extends Component {
<SpinnerButton <SpinnerButton
className={styles.organizeSelectedButton} className={styles.organizeSelectedButton}
kind={kinds.WARNING} kind={kinds.WARNING}
isSpinning={isOrganizingArtist} isSpinning={isOrganizingAuthor}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist} isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
onPress={onOrganizeArtistPress} onPress={onOrganizeAuthorPress}
> >
Rename Files Rename Files
</SpinnerButton> </SpinnerButton>
@ -277,9 +250,9 @@ class ArtistEditorFooter extends Component {
<SpinnerButton <SpinnerButton
className={styles.organizeSelectedButton} className={styles.organizeSelectedButton}
kind={kinds.WARNING} kind={kinds.WARNING}
isSpinning={isRetaggingArtist} isSpinning={isRetaggingAuthor}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist} isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
onPress={onRetagArtistPress} onPress={onRetagAuthorPress}
> >
Write Metadata Tags Write Metadata Tags
</SpinnerButton> </SpinnerButton>
@ -287,7 +260,7 @@ class ArtistEditorFooter extends Component {
<SpinnerButton <SpinnerButton
className={styles.tagsButton} className={styles.tagsButton}
isSpinning={isSaving && savingTags} isSpinning={isSaving && savingTags}
isDisabled={!selectedCount || isOrganizingArtist || isRetaggingArtist} isDisabled={!selectedCount || isOrganizingAuthor || isRetaggingAuthor}
onPress={this.onTagsPress} onPress={this.onTagsPress}
> >
Set Readarr Tags Set Readarr Tags
@ -314,17 +287,17 @@ class ArtistEditorFooter extends Component {
onModalClose={this.onTagsModalClose} onModalClose={this.onTagsModalClose}
/> />
<DeleteArtistModal <DeleteAuthorModal
isOpen={isDeleteArtistModalOpen} isOpen={isDeleteAuthorModalOpen}
authorIds={authorIds} authorIds={authorIds}
onModalClose={this.onDeleteArtistModalClose} onModalClose={this.onDeleteAuthorModalClose}
/> />
<MoveArtistModal <MoveAuthorModal
destinationRootFolder={destinationRootFolder} destinationRootFolder={destinationRootFolder}
isOpen={isConfirmMoveModalOpen} isOpen={isConfirmMoveModalOpen}
onSavePress={this.onSaveRootFolderPress} onSavePress={this.onSaveRootFolderPress}
onMoveArtistPress={this.onMoveArtistPress} onMoveAuthorPress={this.onMoveAuthorPress}
/> />
</PageContentFooter> </PageContentFooter>
@ -332,19 +305,19 @@ class ArtistEditorFooter extends Component {
} }
} }
ArtistEditorFooter.propTypes = { AuthorEditorFooter.propTypes = {
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired, authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
selectedCount: PropTypes.number.isRequired, selectedCount: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object, saveError: PropTypes.object,
isDeleting: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object, deleteError: PropTypes.object,
isOrganizingArtist: PropTypes.bool.isRequired, isOrganizingAuthor: PropTypes.bool.isRequired,
isRetaggingArtist: PropTypes.bool.isRequired, isRetaggingAuthor: PropTypes.bool.isRequired,
showMetadataProfile: PropTypes.bool.isRequired, showMetadataProfile: PropTypes.bool.isRequired,
onSaveSelected: PropTypes.func.isRequired, onSaveSelected: PropTypes.func.isRequired,
onOrganizeArtistPress: PropTypes.func.isRequired, onOrganizeAuthorPress: PropTypes.func.isRequired,
onRetagArtistPress: 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 React from 'react';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import SpinnerIcon from 'Components/SpinnerIcon'; import SpinnerIcon from 'Components/SpinnerIcon';
import styles from './ArtistEditorFooterLabel.css'; import styles from './AuthorEditorFooterLabel.css';
function ArtistEditorFooterLabel(props) { function AuthorEditorFooterLabel(props) {
const { const {
className, className,
label, label,
@ -27,14 +27,14 @@ function ArtistEditorFooterLabel(props) {
); );
} }
ArtistEditorFooterLabel.propTypes = { AuthorEditorFooterLabel.propTypes = {
className: PropTypes.string.isRequired, className: PropTypes.string.isRequired,
label: PropTypes.string.isRequired, label: PropTypes.string.isRequired,
isSaving: PropTypes.bool.isRequired isSaving: PropTypes.bool.isRequired
}; };
ArtistEditorFooterLabel.defaultProps = { AuthorEditorFooterLabel.defaultProps = {
className: styles.label 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 TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell'; import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import ArtistNameLink from 'Artist/ArtistNameLink'; import AuthorNameLink from 'Author/AuthorNameLink';
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell'; import AuthorStatusCell from 'Author/Index/Table/AuthorStatusCell';
import styles from './ArtistEditorRow.css';
class ArtistEditorRow extends Component { class AuthorEditorRow extends Component {
// //
// Listeners // Listeners
onAlbumFolderChange = () => { onBookFolderChange = () => {
// Mock handler to satisfy `onChange` being required for `CheckInput`. // Mock handler to satisfy `onChange` being required for `CheckInput`.
// //
} }
@ -27,8 +26,8 @@ class ArtistEditorRow extends Component {
id, id,
status, status,
titleSlug, titleSlug,
artistName, authorName,
artistType, authorType,
monitored, monitored,
metadataProfile, metadataProfile,
qualityProfile, qualityProfile,
@ -47,16 +46,16 @@ class ArtistEditorRow extends Component {
onSelectedChange={onSelectedChange} onSelectedChange={onSelectedChange}
/> />
<ArtistStatusCell <AuthorStatusCell
artistType={artistType} authorType={authorType}
monitored={monitored} monitored={monitored}
status={status} status={status}
/> />
<TableRowCell className={styles.title}> <TableRowCell>
<ArtistNameLink <AuthorNameLink
titleSlug={titleSlug} titleSlug={titleSlug}
artistName={artistName} authorName={authorName}
/> />
</TableRowCell> </TableRowCell>
@ -85,12 +84,12 @@ class ArtistEditorRow extends Component {
} }
} }
ArtistEditorRow.propTypes = { AuthorEditorRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired, titleSlug: PropTypes.string.isRequired,
artistName: PropTypes.string.isRequired, authorName: PropTypes.string.isRequired,
artistType: PropTypes.string, authorType: PropTypes.string,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
metadataProfile: PropTypes.object.isRequired, metadataProfile: PropTypes.object.isRequired,
qualityProfile: PropTypes.object.isRequired, qualityProfile: PropTypes.object.isRequired,
@ -101,8 +100,8 @@ ArtistEditorRow.propTypes = {
onSelectedChange: PropTypes.func.isRequired onSelectedChange: PropTypes.func.isRequired
}; };
ArtistEditorRow.defaultProps = { AuthorEditorRow.defaultProps = {
tags: [] tags: []
}; };
export default ArtistEditorRow; export default AuthorEditorRow;

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

@ -1,9 +1,9 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import AddNewArtistModalContentConnector from './AddNewArtistModalContentConnector'; import DeleteAuthorModalContentConnector from './DeleteAuthorModalContentConnector';
function AddNewArtistModal(props) { function DeleteAuthorModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
@ -15,7 +15,7 @@ function AddNewArtistModal(props) {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<AddNewArtistModalContentConnector <DeleteAuthorModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -23,9 +23,9 @@ function AddNewArtistModal(props) {
); );
} }
AddNewArtistModal.propTypes = { DeleteAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.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 ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody'; import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; 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 // Lifecycle
@ -31,7 +31,7 @@ class DeleteArtistModalContent extends Component {
this.setState({ deleteFiles: value }); this.setState({ deleteFiles: value });
} }
onDeleteArtistConfirmed = () => { onDeleteAuthorConfirmed = () => {
const deleteFiles = this.state.deleteFiles; const deleteFiles = this.state.deleteFiles;
this.setState({ deleteFiles: false }); this.setState({ deleteFiles: false });
@ -43,7 +43,7 @@ class DeleteArtistModalContent extends Component {
render() { render() {
const { const {
artist, author,
onModalClose onModalClose
} = this.props; } = this.props;
const deleteFiles = this.state.deleteFiles; const deleteFiles = this.state.deleteFiles;
@ -51,19 +51,19 @@ class DeleteArtistModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Delete Selected Artist Delete Selected Author
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div> <div>
<FormGroup> <FormGroup>
<FormLabel>{`Delete Artist Folder${artist.length > 1 ? 's' : ''}`}</FormLabel> <FormLabel>{`Delete Author Folder${author.length > 1 ? 's' : ''}`}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="deleteFiles" name="deleteFiles"
value={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} kind={kinds.DANGER}
onChange={this.onDeleteFilesChange} onChange={this.onDeleteFilesChange}
/> />
@ -71,15 +71,15 @@ class DeleteArtistModalContent extends Component {
</div> </div>
<div className={styles.message}> <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> </div>
<ul> <ul>
{ {
artist.map((s) => { author.map((s) => {
return ( return (
<li key={s.artistName}> <li key={s.authorName}>
<span>{s.artistName}</span> <span>{s.authorName}</span>
{ {
deleteFiles && deleteFiles &&
@ -104,7 +104,7 @@ class DeleteArtistModalContent extends Component {
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={this.onDeleteArtistConfirmed} onPress={this.onDeleteAuthorConfirmed}
> >
Delete Delete
</Button> </Button>
@ -114,10 +114,10 @@ class DeleteArtistModalContent extends Component {
} }
} }
DeleteArtistModalContent.propTypes = { DeleteAuthorModalContent.propTypes = {
artist: PropTypes.arrayOf(PropTypes.object).isRequired, author: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
onDeleteSelectedPress: PropTypes.func.isRequired onDeleteSelectedPress: PropTypes.func.isRequired
}; };
export default DeleteArtistModalContent; export default DeleteAuthorModalContent;

@ -1,29 +1,29 @@
import _ from 'lodash'; import _ from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import { bulkDeleteArtist } from 'Store/Actions/artistEditorActions'; import { bulkDeleteAuthor } from 'Store/Actions/authorEditorActions';
import DeleteArtistModalContent from './DeleteArtistModalContent'; import DeleteAuthorModalContent from './DeleteAuthorModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { authorIds }) => authorIds, (state, { authorIds }) => authorIds,
createAllArtistSelector(), createAllAuthorSelector(),
(authorIds, allArtists) => { (authorIds, allAuthors) => {
const selectedArtist = _.intersectionWith(allArtists, authorIds, (s, id) => { const selectedAuthor = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id; return s.id === id;
}); });
const sortedArtist = _.orderBy(selectedArtist, 'sortName'); const sortedAuthor = _.orderBy(selectedAuthor, 'sortName');
const artist = _.map(sortedArtist, (s) => { const author = _.map(sortedAuthor, (s) => {
return { return {
artistName: s.artistName, authorName: s.authorName,
path: s.path path: s.path
}; };
}); });
return { return {
artist author
}; };
} }
); );
@ -32,7 +32,7 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) { function createMapDispatchToProps(dispatch, props) {
return { return {
onDeleteSelectedPress(deleteFiles) { onDeleteSelectedPress(deleteFiles) {
dispatch(bulkDeleteArtist({ dispatch(bulkDeleteAuthor({
authorIds: props.authorIds, authorIds: props.authorIds,
deleteFiles 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 PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import OrganizeArtistModalContentConnector from './OrganizeArtistModalContentConnector'; import OrganizeAuthorModalContentConnector from './OrganizeAuthorModalContentConnector';
function OrganizeArtistModal(props) { function OrganizeAuthorModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
@ -15,7 +15,7 @@ function OrganizeArtistModal(props) {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<OrganizeArtistModalContentConnector <OrganizeAuthorModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -23,9 +23,9 @@ function OrganizeArtistModal(props) {
); );
} }
OrganizeArtistModal.propTypes = { OrganizeAuthorModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.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 ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody'; import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './OrganizeArtistModalContent.css'; import styles from './OrganizeAuthorModalContent.css';
function OrganizeArtistModalContent(props) { function OrganizeAuthorModalContent(props) {
const { const {
artistNames, authorNames,
onModalClose, onModalClose,
onOrganizeArtistPress onOrganizeAuthorPress
} = props; } = props;
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Organize Selected Artist Organize Selected Author
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<Alert> <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 <Icon
className={styles.renameIcon} className={styles.renameIcon}
name={icons.ORGANIZE} name={icons.ORGANIZE}
@ -33,15 +33,15 @@ function OrganizeArtistModalContent(props) {
</Alert> </Alert>
<div className={styles.message}> <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> </div>
<ul> <ul>
{ {
artistNames.map((artistName) => { authorNames.map((authorName) => {
return ( return (
<li key={artistName}> <li key={authorName}>
{artistName} {authorName}
</li> </li>
); );
}) })
@ -56,7 +56,7 @@ function OrganizeArtistModalContent(props) {
<Button <Button
kind={kinds.DANGER} kind={kinds.DANGER}
onPress={onOrganizeArtistPress} onPress={onOrganizeAuthorPress}
> >
Organize Organize
</Button> </Button>
@ -65,10 +65,10 @@ function OrganizeArtistModalContent(props) {
); );
} }
OrganizeArtistModalContent.propTypes = { OrganizeAuthorModalContent.propTypes = {
artistNames: PropTypes.arrayOf(PropTypes.string).isRequired, authorNames: PropTypes.arrayOf(PropTypes.string).isRequired,
onModalClose: PropTypes.func.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 React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import createAllAuthorSelector from 'Store/Selectors/createAllAuthorsSelector';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import OrganizeArtistModalContent from './OrganizeArtistModalContent'; import OrganizeAuthorModalContent from './OrganizeAuthorModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { authorIds }) => authorIds, (state, { authorIds }) => authorIds,
createAllArtistSelector(), createAllAuthorSelector(),
(authorIds, allArtists) => { (authorIds, allAuthors) => {
const artist = _.intersectionWith(allArtists, authorIds, (s, id) => { const author = _.intersectionWith(allAuthors, authorIds, (s, id) => {
return s.id === id; return s.id === id;
}); });
const sortedArtist = _.orderBy(artist, 'sortName'); const sortedAuthor = _.orderBy(author, 'sortName');
const artistNames = _.map(sortedArtist, 'artistName'); const authorNames = _.map(sortedAuthor, 'authorName');
return { return {
artistNames authorNames
}; };
} }
); );
@ -31,14 +31,14 @@ const mapDispatchToProps = {
executeCommand executeCommand
}; };
class OrganizeArtistModalContentConnector extends Component { class OrganizeAuthorModalContentConnector extends Component {
// //
// Listeners // Listeners
onOrganizeArtistPress = () => { onOrganizeAuthorPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.RENAME_ARTIST, name: commandNames.RENAME_AUTHOR,
authorIds: this.props.authorIds authorIds: this.props.authorIds
}); });
@ -50,18 +50,18 @@ class OrganizeArtistModalContentConnector extends Component {
render(props) { render(props) {
return ( return (
<OrganizeArtistModalContent <OrganizeAuthorModalContent
{...this.props} {...this.props}
onOrganizeArtistPress={this.onOrganizeArtistPress} onOrganizeAuthorPress={this.onOrganizeAuthorPress}
/> />
); );
} }
} }
OrganizeArtistModalContentConnector.propTypes = { OrganizeAuthorModalContentConnector.propTypes = {
authorIds: PropTypes.arrayOf(PropTypes.number).isRequired, authorIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
executeCommand: 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() { render() {
const { const {
artistTags, authorTags,
tagList, tagList,
onModalClose onModalClose
} = this.props; } = this.props;
@ -93,7 +93,7 @@ class TagsModalContent extends Component {
value={applyTags} value={applyTags}
values={applyTagsOptions} values={applyTagsOptions}
helpTexts={[ 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', 'Add: Add the tags the existing list of tags',
'Remove: Remove the entered tags', 'Remove: Remove the entered tags',
'Replace: Replace the tags with the entered tags (enter no tags to clear all 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}> <div className={styles.result}>
{ {
artistTags.map((t) => { authorTags.map((t) => {
const tag = _.find(tagList, { id: t }); const tag = _.find(tagList, { id: t });
if (!tag) { if (!tag) {
@ -139,7 +139,7 @@ class TagsModalContent extends Component {
return null; return null;
} }
if (artistTags.indexOf(t) > -1) { if (authorTags.indexOf(t) > -1) {
return null; return null;
} }
@ -178,7 +178,7 @@ class TagsModalContent extends Component {
} }
TagsModalContent.propTypes = { TagsModalContent.propTypes = {
artistTags: PropTypes.arrayOf(PropTypes.number).isRequired, authorTags: PropTypes.arrayOf(PropTypes.number).isRequired,
tagList: PropTypes.arrayOf(PropTypes.object).isRequired, tagList: PropTypes.arrayOf(PropTypes.object).isRequired,
onModalClose: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired,
onApplyTagsPress: PropTypes.func.isRequired onApplyTagsPress: PropTypes.func.isRequired

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

@ -2,24 +2,24 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchArtistHistory, clearArtistHistory, artistHistoryMarkAsFailed } from 'Store/Actions/artistHistoryActions'; import { authorHistoryMarkAsFailed, clearAuthorHistory, fetchAuthorHistory } from 'Store/Actions/authorHistoryActions';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.artistHistory, (state) => state.authorHistory,
(artistHistory) => { (authorHistory) => {
return artistHistory; return authorHistory;
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
fetchArtistHistory, fetchAuthorHistory,
clearArtistHistory, clearAuthorHistory,
artistHistoryMarkAsFailed authorHistoryMarkAsFailed
}; };
class ArtistHistoryContentConnector extends Component { class AuthorHistoryContentConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -30,14 +30,14 @@ class ArtistHistoryContentConnector extends Component {
bookId bookId
} = this.props; } = this.props;
this.props.fetchArtistHistory({ this.props.fetchAuthorHistory({
authorId, authorId,
bookId bookId
}); });
} }
componentWillUnmount() { componentWillUnmount() {
this.props.clearArtistHistory(); this.props.clearAuthorHistory();
} }
// //
@ -49,7 +49,7 @@ class ArtistHistoryContentConnector extends Component {
bookId bookId
} = this.props; } = this.props;
this.props.artistHistoryMarkAsFailed({ this.props.authorHistoryMarkAsFailed({
historyId, historyId,
authorId, authorId,
bookId bookId
@ -74,13 +74,13 @@ class ArtistHistoryContentConnector extends Component {
} }
} }
ArtistHistoryContentConnector.propTypes = { AuthorHistoryContentConnector.propTypes = {
component: PropTypes.elementType.isRequired, component: PropTypes.elementType.isRequired,
authorId: PropTypes.number.isRequired, authorId: PropTypes.number.isRequired,
bookId: PropTypes.number, bookId: PropTypes.number,
fetchArtistHistory: PropTypes.func.isRequired, fetchAuthorHistory: PropTypes.func.isRequired,
clearArtistHistory: PropTypes.func.isRequired, clearAuthorHistory: PropTypes.func.isRequired,
artistHistoryMarkAsFailed: 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 PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Modal from 'Components/Modal/Modal'; import Modal from 'Components/Modal/Modal';
import ArtistHistoryContentConnector from './ArtistHistoryContentConnector'; import AuthorHistoryContentConnector from './AuthorHistoryContentConnector';
import ArtistHistoryModalContent from './ArtistHistoryModalContent'; import AuthorHistoryModalContent from './AuthorHistoryModalContent';
function ArtistHistoryModal(props) { function AuthorHistoryModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
@ -16,8 +16,8 @@ function ArtistHistoryModal(props) {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<ArtistHistoryContentConnector <AuthorHistoryContentConnector
component={ArtistHistoryModalContent} component={AuthorHistoryModalContent}
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -25,9 +25,9 @@ function ArtistHistoryModal(props) {
); );
} }
ArtistHistoryModal.propTypes = { AuthorHistoryModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.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 ModalHeader from 'Components/Modal/ModalHeader';
import ModalBody from 'Components/Modal/ModalBody'; import ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter'; import ModalFooter from 'Components/Modal/ModalFooter';
import ArtistHistoryTableContent from './ArtistHistoryTableContent'; import AuthorHistoryTableContent from './AuthorHistoryTableContent';
class ArtistHistoryModalContent extends Component { class AuthorHistoryModalContent extends Component {
// //
// Render // Render
@ -24,7 +24,7 @@ class ArtistHistoryModalContent extends Component {
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<ArtistHistoryTableContent <AuthorHistoryTableContent
{...this.props} {...this.props}
/> />
</ModalBody> </ModalBody>
@ -39,8 +39,8 @@ class ArtistHistoryModalContent extends Component {
} }
} }
ArtistHistoryModalContent.propTypes = { AuthorHistoryModalContent.propTypes = {
onModalClose: PropTypes.func.isRequired 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 TableRow from 'Components/Table/TableRow';
import TableRowCell from 'Components/Table/Cells/TableRowCell'; import TableRowCell from 'Components/Table/Cells/TableRowCell';
import Popover from 'Components/Tooltip/Popover'; import Popover from 'Components/Tooltip/Popover';
import TrackQuality from 'Album/TrackQuality'; import BookQuality from 'Book/BookQuality';
import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector'; import HistoryDetailsConnector from 'Activity/History/Details/HistoryDetailsConnector';
import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell'; import HistoryEventTypeCell from 'Activity/History/HistoryEventTypeCell';
import styles from './ArtistHistoryRow.css'; import styles from './AuthorHistoryRow.css';
function getTitle(eventType) { function getTitle(eventType) {
switch (eventType) { switch (eventType) {
@ -19,24 +19,24 @@ function getTitle(eventType) {
return 'Grabbed'; return 'Grabbed';
case 'downloadImported': case 'downloadImported':
return 'Download Completed'; return 'Download Completed';
case 'trackFileImported': case 'bookFileImported':
return 'Track Imported'; return 'Book Imported';
case 'downloadFailed': case 'downloadFailed':
return 'Download Failed'; return 'Download Failed';
case 'trackFileDeleted': case 'bookFileDeleted':
return 'Track File Deleted'; return 'Book File Deleted';
case 'trackFileRenamed': case 'bookFileRenamed':
return 'Track File Renamed'; return 'Book File Renamed';
case 'trackFileRetagged': case 'bookFileRetagged':
return 'Track File Tags Updated'; return 'Book File Tags Updated';
case 'albumImportIncomplete': case 'bookImportIncomplete':
return 'Album Import Incomplete'; return 'Book Import Incomplete';
default: default:
return 'Unknown'; return 'Unknown';
} }
} }
class ArtistHistoryRow extends Component { class AuthorHistoryRow extends Component {
// //
// Lifecycle // Lifecycle
@ -76,7 +76,7 @@ class ArtistHistoryRow extends Component {
qualityCutoffNotMet, qualityCutoffNotMet,
date, date,
data, data,
album book
} = this.props; } = this.props;
const { const {
@ -91,7 +91,7 @@ class ArtistHistoryRow extends Component {
/> />
<TableRowCell key={name}> <TableRowCell key={name}>
{album.title} {book.title}
</TableRowCell> </TableRowCell>
<TableRowCell> <TableRowCell>
@ -99,7 +99,7 @@ class ArtistHistoryRow extends Component {
</TableRowCell> </TableRowCell>
<TableRowCell> <TableRowCell>
<TrackQuality <BookQuality
quality={quality} quality={quality}
isCutoffNotMet={qualityCutoffNotMet} isCutoffNotMet={qualityCutoffNotMet}
/> />
@ -153,7 +153,7 @@ class ArtistHistoryRow extends Component {
} }
} }
ArtistHistoryRow.propTypes = { AuthorHistoryRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
eventType: PropTypes.string.isRequired, eventType: PropTypes.string.isRequired,
sourceTitle: PropTypes.string.isRequired, sourceTitle: PropTypes.string.isRequired,
@ -161,10 +161,10 @@ ArtistHistoryRow.propTypes = {
qualityCutoffNotMet: PropTypes.bool.isRequired, qualityCutoffNotMet: PropTypes.bool.isRequired,
date: PropTypes.string.isRequired, date: PropTypes.string.isRequired,
data: PropTypes.object.isRequired, data: PropTypes.object.isRequired,
fullArtist: PropTypes.bool.isRequired, fullAuthor: PropTypes.bool.isRequired,
artist: PropTypes.object.isRequired, author: PropTypes.object.isRequired,
album: PropTypes.object.isRequired, book: PropTypes.object.isRequired,
onMarkAsFailedPress: PropTypes.func.isRequired onMarkAsFailedPress: PropTypes.func.isRequired
}; };
export default ArtistHistoryRow; export default AuthorHistoryRow;

@ -1,18 +1,18 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions'; import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createAlbumSelector from 'Store/Selectors/createAlbumSelector'; import createBookSelector from 'Store/Selectors/createBookSelector';
import ArtistHistoryRow from './ArtistHistoryRow'; import AuthorHistoryRow from './AuthorHistoryRow';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistSelector(), createAuthorSelector(),
createAlbumSelector(), createBookSelector(),
(artist, album) => { (author, book) => {
return { return {
artist, author,
album book
}; };
} }
); );
@ -23,4 +23,4 @@ const mapDispatchToProps = {
markAsFailed 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 LoadingIndicator from 'Components/Loading/LoadingIndicator';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import ArtistHistoryRowConnector from './ArtistHistoryRowConnector'; import AuthorHistoryRowConnector from './AuthorHistoryRowConnector';
const columns = [ const columns = [
{ {
@ -11,8 +11,8 @@ const columns = [
isVisible: true isVisible: true
}, },
{ {
name: 'album', name: 'book',
label: 'Album', label: 'Book',
isVisible: true isVisible: true
}, },
{ {
@ -42,7 +42,7 @@ const columns = [
} }
]; ];
class ArtistHistoryTableContent extends Component { class AuthorHistoryTableContent extends Component {
// //
// Render // Render
@ -57,7 +57,7 @@ class ArtistHistoryTableContent extends Component {
onMarkAsFailedPress onMarkAsFailedPress
} = this.props; } = this.props;
const fullArtist = bookId == null; const fullAuthor = bookId == null;
const hasItems = !!items.length; const hasItems = !!items.length;
return ( return (
@ -84,9 +84,9 @@ class ArtistHistoryTableContent extends Component {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<ArtistHistoryRowConnector <AuthorHistoryRowConnector
key={item.id} key={item.id}
fullArtist={fullArtist} fullAuthor={fullAuthor}
{...item} {...item}
onMarkAsFailedPress={onMarkAsFailedPress} onMarkAsFailedPress={onMarkAsFailedPress}
/> />
@ -101,7 +101,7 @@ class ArtistHistoryTableContent extends Component {
} }
} }
ArtistHistoryTableContent.propTypes = { AuthorHistoryTableContent.propTypes = {
bookId: PropTypes.number, bookId: PropTypes.number,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
@ -110,4 +110,4 @@ ArtistHistoryTableContent.propTypes = {
onMarkAsFailedPress: PropTypes.func.isRequired 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 PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator';
import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
import NoArtist from 'Artist/NoArtist'; import NoAuthor from 'Author/NoAuthor';
import ArtistIndexTableConnector from './Table/ArtistIndexTableConnector'; import AuthorIndexTableConnector from './Table/AuthorIndexTableConnector';
import ArtistIndexTableOptionsConnector from './Table/ArtistIndexTableOptionsConnector'; import AuthorIndexTableOptionsConnector from './Table/AuthorIndexTableOptionsConnector';
import ArtistIndexPosterOptionsModal from './Posters/Options/ArtistIndexPosterOptionsModal'; import AuthorIndexPosterOptionsModal from './Posters/Options/AuthorIndexPosterOptionsModal';
import ArtistIndexPostersConnector from './Posters/ArtistIndexPostersConnector'; import AuthorIndexPostersConnector from './Posters/AuthorIndexPostersConnector';
import ArtistIndexBannerOptionsModal from './Banners/Options/ArtistIndexBannerOptionsModal'; import AuthorIndexBannerOptionsModal from './Banners/Options/AuthorIndexBannerOptionsModal';
import ArtistIndexBannersConnector from './Banners/ArtistIndexBannersConnector'; import AuthorIndexBannersConnector from './Banners/AuthorIndexBannersConnector';
import ArtistIndexOverviewOptionsModal from './Overview/Options/ArtistIndexOverviewOptionsModal'; import AuthorIndexOverviewOptionsModal from './Overview/Options/AuthorIndexOverviewOptionsModal';
import ArtistIndexOverviewsConnector from './Overview/ArtistIndexOverviewsConnector'; import AuthorIndexOverviewsConnector from './Overview/AuthorIndexOverviewsConnector';
import ArtistIndexFooterConnector from './ArtistIndexFooterConnector'; import AuthorIndexFooterConnector from './AuthorIndexFooterConnector';
import ArtistIndexFilterMenu from './Menus/ArtistIndexFilterMenu'; import AuthorIndexFilterMenu from './Menus/AuthorIndexFilterMenu';
import ArtistIndexSortMenu from './Menus/ArtistIndexSortMenu'; import AuthorIndexSortMenu from './Menus/AuthorIndexSortMenu';
import ArtistIndexViewMenu from './Menus/ArtistIndexViewMenu'; import AuthorIndexViewMenu from './Menus/AuthorIndexViewMenu';
import styles from './ArtistIndex.css'; import styles from './AuthorIndex.css';
function getViewComponent(view) { function getViewComponent(view) {
if (view === 'posters') { if (view === 'posters') {
return ArtistIndexPostersConnector; return AuthorIndexPostersConnector;
} }
if (view === 'banners') { if (view === 'banners') {
return ArtistIndexBannersConnector; return AuthorIndexBannersConnector;
} }
if (view === 'overview') { if (view === 'overview') {
return ArtistIndexOverviewsConnector; return AuthorIndexOverviewsConnector;
} }
return ArtistIndexTableConnector; return AuthorIndexTableConnector;
} }
class ArtistIndex extends Component { class AuthorIndex extends Component {
// //
// Lifecycle // Lifecycle
@ -184,13 +184,13 @@ class ArtistIndex extends Component {
sortKey, sortKey,
sortDirection, sortDirection,
view, view,
isRefreshingArtist, isRefreshingAuthor,
isRssSyncExecuting, isRssSyncExecuting,
onScroll, onScroll,
onSortSelect, onSortSelect,
onFilterSelect, onFilterSelect,
onViewSelect, onViewSelect,
onRefreshArtistPress, onRefreshAuthorPress,
onRssSyncPress, onRssSyncPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -206,7 +206,7 @@ class ArtistIndex extends Component {
const ViewComponent = getViewComponent(view); const ViewComponent = getViewComponent(view);
const isLoaded = !!(!error && isPopulated && items.length && scroller); const isLoaded = !!(!error && isPopulated && items.length && scroller);
const hasNoArtist = !totalItems; const hasNoAuthor = !totalItems;
return ( return (
<PageContent> <PageContent>
@ -216,15 +216,15 @@ class ArtistIndex extends Component {
label="Update all" label="Update all"
iconName={icons.REFRESH} iconName={icons.REFRESH}
spinningName={icons.REFRESH} spinningName={icons.REFRESH}
isSpinning={isRefreshingArtist} isSpinning={isRefreshingAuthor}
onPress={onRefreshArtistPress} onPress={onRefreshAuthorPress}
/> />
<PageToolbarButton <PageToolbarButton
label="RSS Sync" label="RSS Sync"
iconName={icons.RSS} iconName={icons.RSS}
isSpinning={isRssSyncExecuting} isSpinning={isRssSyncExecuting}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onPress={onRssSyncPress} onPress={onRssSyncPress}
/> />
@ -239,7 +239,7 @@ class ArtistIndex extends Component {
<TableOptionsModalWrapper <TableOptionsModalWrapper
{...otherProps} {...otherProps}
columns={columns} columns={columns}
optionsComponent={ArtistIndexTableOptionsConnector} optionsComponent={AuthorIndexTableOptionsConnector}
> >
<PageToolbarButton <PageToolbarButton
label="Options" label="Options"
@ -254,7 +254,7 @@ class ArtistIndex extends Component {
<PageToolbarButton <PageToolbarButton
label="Options" label="Options"
iconName={icons.POSTER} iconName={icons.POSTER}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onPress={this.onPosterOptionsPress} onPress={this.onPosterOptionsPress}
/> : /> :
null null
@ -265,7 +265,7 @@ class ArtistIndex extends Component {
<PageToolbarButton <PageToolbarButton
label="Options" label="Options"
iconName={icons.POSTER} iconName={icons.POSTER}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onPress={this.onBannerOptionsPress} onPress={this.onBannerOptionsPress}
/> : /> :
null null
@ -276,7 +276,7 @@ class ArtistIndex extends Component {
<PageToolbarButton <PageToolbarButton
label="Options" label="Options"
iconName={icons.OVERVIEW} iconName={icons.OVERVIEW}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onPress={this.onOverviewOptionsPress} onPress={this.onOverviewOptionsPress}
/> : /> :
null null
@ -288,24 +288,24 @@ class ArtistIndex extends Component {
<PageToolbarSeparator /> <PageToolbarSeparator />
} }
<ArtistIndexViewMenu <AuthorIndexViewMenu
view={view} view={view}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onViewSelect={onViewSelect} onViewSelect={onViewSelect}
/> />
<ArtistIndexSortMenu <AuthorIndexSortMenu
sortKey={sortKey} sortKey={sortKey}
sortDirection={sortDirection} sortDirection={sortDirection}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onSortSelect={onSortSelect} onSortSelect={onSortSelect}
/> />
<ArtistIndexFilterMenu <AuthorIndexFilterMenu
selectedFilterKey={selectedFilterKey} selectedFilterKey={selectedFilterKey}
filters={filters} filters={filters}
customFilters={customFilters} customFilters={customFilters}
isDisabled={hasNoArtist} isDisabled={hasNoAuthor}
onFilterSelect={onFilterSelect} onFilterSelect={onFilterSelect}
/> />
</PageToolbarSection> </PageToolbarSection>
@ -326,7 +326,7 @@ class ArtistIndex extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div className={styles.errorMessage}> <div className={styles.errorMessage}>
{getErrorMessage(error, 'Failed to load artist from API')} {getErrorMessage(error, 'Failed to load author from API')}
</div> </div>
} }
@ -343,13 +343,13 @@ class ArtistIndex extends Component {
{...otherProps} {...otherProps}
/> />
<ArtistIndexFooterConnector /> <AuthorIndexFooterConnector />
</div> </div>
} }
{ {
!error && isPopulated && !items.length && !error && isPopulated && !items.length &&
<NoArtist totalItems={totalItems} /> <NoAuthor totalItems={totalItems} />
} }
</PageContentBodyConnector> </PageContentBodyConnector>
@ -362,18 +362,18 @@ class ArtistIndex extends Component {
} }
</div> </div>
<ArtistIndexPosterOptionsModal <AuthorIndexPosterOptionsModal
isOpen={isPosterOptionsModalOpen} isOpen={isPosterOptionsModalOpen}
onModalClose={this.onPosterOptionsModalClose} onModalClose={this.onPosterOptionsModalClose}
/> />
<ArtistIndexBannerOptionsModal <AuthorIndexBannerOptionsModal
isOpen={isBannerOptionsModalOpen} isOpen={isBannerOptionsModalOpen}
onModalClose={this.onBannerOptionsModalClose} onModalClose={this.onBannerOptionsModalClose}
/> />
<ArtistIndexOverviewOptionsModal <AuthorIndexOverviewOptionsModal
isOpen={isOverviewOptionsModalOpen} isOpen={isOverviewOptionsModalOpen}
onModalClose={this.onOverviewOptionsModalClose} onModalClose={this.onOverviewOptionsModalClose}
@ -383,7 +383,7 @@ class ArtistIndex extends Component {
} }
} }
ArtistIndex.propTypes = { AuthorIndex.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@ -396,15 +396,15 @@ ArtistIndex.propTypes = {
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.oneOf(sortDirections.all), sortDirection: PropTypes.oneOf(sortDirections.all),
view: PropTypes.string.isRequired, view: PropTypes.string.isRequired,
isRefreshingArtist: PropTypes.bool.isRequired, isRefreshingAuthor: PropTypes.bool.isRequired,
isRssSyncExecuting: PropTypes.bool.isRequired, isRssSyncExecuting: PropTypes.bool.isRequired,
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
onSortSelect: PropTypes.func.isRequired, onSortSelect: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,
onViewSelect: PropTypes.func.isRequired, onViewSelect: PropTypes.func.isRequired,
onRefreshArtistPress: PropTypes.func.isRequired, onRefreshAuthorPress: PropTypes.func.isRequired,
onRssSyncPress: PropTypes.func.isRequired, onRssSyncPress: PropTypes.func.isRequired,
onScroll: 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 React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createArtistClientSideCollectionItemsSelector from 'Store/Selectors/createArtistClientSideCollectionItemsSelector'; import createAuthorClientSideCollectionItemsSelector
from 'Store/Selectors/createAuthorClientSideCollectionItemsSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import scrollPositions from 'Store/scrollPositions'; 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 { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import withScrollPosition from 'Components/withScrollPosition'; import withScrollPosition from 'Components/withScrollPosition';
import ArtistIndex from './ArtistIndex'; import AuthorIndex from './AuthorIndex';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistClientSideCollectionItemsSelector('artistIndex'), createAuthorClientSideCollectionItemsSelector('authorIndex'),
createCommandExecutingSelector(commandNames.REFRESH_ARTIST), createCommandExecutingSelector(commandNames.REFRESH_AUTHOR),
createCommandExecutingSelector(commandNames.RSS_SYNC), createCommandExecutingSelector(commandNames.RSS_SYNC),
createDimensionsSelector(), createDimensionsSelector(),
( (
artist, author,
isRefreshingArtist, isRefreshingAuthor,
isRssSyncExecuting, isRssSyncExecuting,
dimensionsState dimensionsState
) => { ) => {
return { return {
...artist, ...author,
isRefreshingArtist, isRefreshingAuthor,
isRssSyncExecuting, isRssSyncExecuting,
isSmallScreen: dimensionsState.isSmallScreen isSmallScreen: dimensionsState.isSmallScreen
}; };
@ -38,24 +39,24 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) { function createMapDispatchToProps(dispatch, props) {
return { return {
onTableOptionChange(payload) { onTableOptionChange(payload) {
dispatch(setArtistTableOption(payload)); dispatch(setAuthorTableOption(payload));
}, },
onSortSelect(sortKey) { onSortSelect(sortKey) {
dispatch(setArtistSort({ sortKey })); dispatch(setAuthorSort({ sortKey }));
}, },
onFilterSelect(selectedFilterKey) { onFilterSelect(selectedFilterKey) {
dispatch(setArtistFilter({ selectedFilterKey })); dispatch(setAuthorFilter({ selectedFilterKey }));
}, },
dispatchSetArtistView(view) { dispatchSetAuthorView(view) {
dispatch(setArtistView({ view })); dispatch(setAuthorView({ view }));
}, },
onRefreshArtistPress() { onRefreshAuthorPress() {
dispatch(executeCommand({ 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 // Listeners
onViewSelect = (view) => { onViewSelect = (view) => {
this.props.dispatchSetArtistView(view); this.props.dispatchSetAuthorView(view);
} }
onScroll = ({ scrollTop }) => { onScroll = ({ scrollTop }) => {
scrollPositions.artistIndex = scrollTop; scrollPositions.authorIndex = scrollTop;
} }
// //
@ -85,7 +86,7 @@ class ArtistIndexConnector extends Component {
render() { render() {
return ( return (
<ArtistIndex <AuthorIndex
{...this.props} {...this.props}
onViewSelect={this.onViewSelect} onViewSelect={this.onViewSelect}
onScroll={this.onScroll} onScroll={this.onScroll}
@ -94,13 +95,13 @@ class ArtistIndexConnector extends Component {
} }
} }
ArtistIndexConnector.propTypes = { AuthorIndexConnector.propTypes = {
isSmallScreen: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired,
view: PropTypes.string.isRequired, view: PropTypes.string.isRequired,
dispatchSetArtistView: PropTypes.func.isRequired dispatchSetAuthorView: PropTypes.func.isRequired
}; };
export default withScrollPosition( export default withScrollPosition(
connect(createMapStateToProps, createMapDispatchToProps)(ArtistIndexConnector), connect(createMapStateToProps, createMapDispatchToProps)(AuthorIndexConnector),
'artistIndex' 'authorIndex'
); );

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

@ -5,34 +5,34 @@ import formatBytes from 'Utilities/Number/formatBytes';
import { ColorImpairedConsumer } from 'App/ColorImpairedContext'; import { ColorImpairedConsumer } from 'App/ColorImpairedContext';
import DescriptionList from 'Components/DescriptionList/DescriptionList'; import DescriptionList from 'Components/DescriptionList/DescriptionList';
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; 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
render() { render() {
const { artist } = this.props; const { author } = this.props;
const count = artist.length; const count = author.length;
let tracks = 0; let books = 0;
let trackFiles = 0; let bookFiles = 0;
let ended = 0; let ended = 0;
let continuing = 0; let continuing = 0;
let monitored = 0; let monitored = 0;
let totalFileSize = 0; let totalFileSize = 0;
artist.forEach((s) => { author.forEach((s) => {
const { statistics = {} } = s; const { statistics = {} } = s;
const { const {
trackCount = 0, bookCount = 0,
trackFileCount = 0, bookFileCount = 0,
sizeOnDisk = 0 sizeOnDisk = 0
} = statistics; } = statistics;
tracks += trackCount; books += bookCount;
trackFiles += trackFileCount; bookFiles += bookFileCount;
if (s.status === 'ended') { if (s.status === 'ended') {
ended++; ended++;
@ -127,12 +127,12 @@ class ArtistIndexFooter extends PureComponent {
<DescriptionList> <DescriptionList>
<DescriptionListItem <DescriptionListItem
title="Books" title="Books"
data={tracks} data={books}
/> />
<DescriptionListItem <DescriptionListItem
title="Files" title="Files"
data={trackFiles} data={bookFiles}
/> />
</DescriptionList> </DescriptionList>
@ -151,8 +151,8 @@ class ArtistIndexFooter extends PureComponent {
} }
} }
ArtistIndexFooter.propTypes = { AuthorIndexFooter.propTypes = {
artist: PropTypes.arrayOf(PropTypes.object).isRequired 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 { createSelector } from 'reselect';
import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector'; import createDeepEqualSelector from 'Store/Selectors/createDeepEqualSelector';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import ArtistIndexFooter from './ArtistIndexFooter'; import AuthorIndexFooter from './AuthorIndexFooter';
function createUnoptimizedSelector() { function createUnoptimizedSelector() {
return createSelector( return createSelector(
createClientSideCollectionSelector('artist', 'artistIndex'), createClientSideCollectionSelector('authors', 'authorIndex'),
(artist) => { (authors) => {
return artist.items.map((s) => { return authors.items.map((s) => {
const { const {
monitored, monitored,
status, status,
@ -25,22 +25,22 @@ function createUnoptimizedSelector() {
); );
} }
function createArtistSelector() { function createAuthorSelector() {
return createDeepEqualSelector( return createDeepEqualSelector(
createUnoptimizedSelector(), createUnoptimizedSelector(),
(artist) => artist (author) => author
); );
} }
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistSelector(), createAuthorSelector(),
(artist) => { (author) => {
return { 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 React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createAuthorSelector from 'Store/Selectors/createAuthorSelector';
import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector'; import createExecutingCommandsSelector from 'Store/Selectors/createExecutingCommandsSelector';
import createArtistQualityProfileSelector from 'Store/Selectors/createArtistQualityProfileSelector'; import createAuthorQualityProfileSelector from 'Store/Selectors/createAuthorQualityProfileSelector';
import createArtistMetadataProfileSelector from 'Store/Selectors/createArtistMetadataProfileSelector'; import createAuthorMetadataProfileSelector from 'Store/Selectors/createAuthorMetadataProfileSelector';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
function selectShowSearchAction() { function selectShowSearchAction() {
return createSelector( return createSelector(
(state) => state.artistIndex, (state) => state.authorIndex,
(artistIndex) => { (authorIndex) => {
const view = artistIndex.view; const view = authorIndex.view;
switch (view) { switch (view) {
case 'posters': case 'posters':
return artistIndex.posterOptions.showSearchAction; return authorIndex.posterOptions.showSearchAction;
case 'banners': case 'banners':
return artistIndex.bannerOptions.showSearchAction; return authorIndex.bannerOptions.showSearchAction;
case 'overview': case 'overview':
return artistIndex.overviewOptions.showSearchAction; return authorIndex.overviewOptions.showSearchAction;
default: default:
return artistIndex.tableOptions.showSearchAction; return authorIndex.tableOptions.showSearchAction;
} }
} }
); );
@ -33,52 +33,52 @@ function selectShowSearchAction() {
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createArtistSelector(), createAuthorSelector(),
createArtistQualityProfileSelector(), createAuthorQualityProfileSelector(),
createArtistMetadataProfileSelector(), createAuthorMetadataProfileSelector(),
selectShowSearchAction(), selectShowSearchAction(),
createExecutingCommandsSelector(), createExecutingCommandsSelector(),
( (
artist, author,
qualityProfile, qualityProfile,
metadataProfile, metadataProfile,
showSearchAction, showSearchAction,
executingCommands executingCommands
) => { ) => {
// If an artist is deleted this selector may fire before the parent // If an author is deleted this selector may fire before the parent
// selectors, which will result in an undefined artist, if that happens // 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 // 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 {}; return {};
} }
const isRefreshingArtist = executingCommands.some((command) => { const isRefreshingAuthor = executingCommands.some((command) => {
return ( return (
command.name === commandNames.REFRESH_ARTIST && command.name === commandNames.REFRESH_AUTHOR &&
command.body.authorId === artist.id command.body.authorId === author.id
); );
}); });
const isSearchingArtist = executingCommands.some((command) => { const isSearchingAuthor = executingCommands.some((command) => {
return ( return (
command.name === commandNames.ARTIST_SEARCH && command.name === commandNames.AUTHOR_SEARCH &&
command.body.authorId === artist.id command.body.authorId === author.id
); );
}); });
const latestAlbum = _.maxBy(artist.albums, (album) => album.releaseDate); const latestBook = _.maxBy(author.books, (book) => book.releaseDate);
return { return {
...artist, ...author,
qualityProfile, qualityProfile,
metadataProfile, metadataProfile,
latestAlbum, latestBook,
showSearchAction, showSearchAction,
isRefreshingArtist, isRefreshingAuthor,
isSearchingArtist isSearchingAuthor
}; };
} }
); );
@ -88,21 +88,21 @@ const mapDispatchToProps = {
dispatchExecuteCommand: executeCommand dispatchExecuteCommand: executeCommand
}; };
class ArtistIndexItemConnector extends Component { class AuthorIndexItemConnector extends Component {
// //
// Listeners // Listeners
onRefreshArtistPress = () => { onRefreshAuthorPress = () => {
this.props.dispatchExecuteCommand({ this.props.dispatchExecuteCommand({
name: commandNames.REFRESH_ARTIST, name: commandNames.REFRESH_AUTHOR,
authorId: this.props.id authorId: this.props.id
}); });
} }
onSearchPress = () => { onSearchPress = () => {
this.props.dispatchExecuteCommand({ this.props.dispatchExecuteCommand({
name: commandNames.ARTIST_SEARCH, name: commandNames.AUTHOR_SEARCH,
authorId: this.props.id authorId: this.props.id
}); });
} }
@ -125,17 +125,17 @@ class ArtistIndexItemConnector extends Component {
<ItemComponent <ItemComponent
{...otherProps} {...otherProps}
id={id} id={id}
onRefreshArtistPress={this.onRefreshArtistPress} onRefreshAuthorPress={this.onRefreshAuthorPress}
onSearchPress={this.onSearchPress} onSearchPress={this.onSearchPress}
/> />
); );
} }
} }
ArtistIndexItemConnector.propTypes = { AuthorIndexItemConnector.propTypes = {
id: PropTypes.number, id: PropTypes.number,
component: PropTypes.elementType.isRequired, component: PropTypes.elementType.isRequired,
dispatchExecuteCommand: PropTypes.func.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