[UI Work] Artist Detail Page, Album Studio, Wanted, NavSearch, Rename

pull/84/head
Qstick 7 years ago
parent 456ead09da
commit 0054226307

@ -58,7 +58,7 @@ class AddNewArtistSearchResult extends Component {
isSmallScreen isSmallScreen
} = this.props; } = this.props;
const linkProps = isExistingArtist ? { to: `/series/${nameSlug}` } : { onPress: this.onPress }; const linkProps = isExistingArtist ? { to: `/artist/${nameSlug}` } : { onPress: this.onPress };
let seasons = '1 Season'; let seasons = '1 Season';
if (seasonCount > 1) { if (seasonCount > 1) {

@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { queueLookupSeries, setImportArtistValue } from 'Store/Actions/importArtistActions'; import { queueLookupSeries, setImportArtistValue } from 'Store/Actions/importArtistActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import ImportArtistRow from './ImportArtistRow'; import ImportArtistRow from './ImportArtistRow';
function createImportArtistItemSelector() { function createImportArtistItemSelector() {
@ -20,7 +20,7 @@ function createImportArtistItemSelector() {
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createImportArtistItemSelector(), createImportArtistItemSelector(),
createAllSeriesSelector(), createAllArtistSelector(),
(item, series) => { (item, series) => {
const selectedSeries = item && item.selectedSeries; const selectedSeries = item && item.selectedSeries;
const isExistingArtist = !!selectedSeries && _.some(series, { foreignArtistId: selectedSeries.foreignArtistId }); const isExistingArtist = !!selectedSeries && _.some(series, { foreignArtistId: selectedSeries.foreignArtistId });

@ -76,7 +76,7 @@ class ImportArtistTable extends Component {
const isSelected = selectedState[id]; const isSelected = selectedState[id];
const isExistingArtist = !!selectedSeries && const isExistingArtist = !!selectedSeries &&
_.some(prevProps.allSeries, { tvdbId: selectedSeries.tvdbId }); _.some(prevProps.allSeries, { foreignArtistId: selectedSeries.foreignArtistId });
// Props doesn't have a selected series or // Props doesn't have a selected series or
// the selected series is an existing series. // the selected series is an existing series.

@ -1,7 +1,7 @@
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { queueLookupSeries, setImportArtistValue } from 'Store/Actions/importArtistActions'; import { queueLookupSeries, setImportArtistValue } from 'Store/Actions/importArtistActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import ImportArtistTable from './ImportArtistTable'; import ImportArtistTable from './ImportArtistTable';
function createMapStateToProps() { function createMapStateToProps() {
@ -9,7 +9,7 @@ function createMapStateToProps() {
(state) => state.addArtist, (state) => state.addArtist,
(state) => state.importArtist, (state) => state.importArtist,
(state) => state.app.dimensions, (state) => state.app.dimensions,
createAllSeriesSelector(), createAllArtistSelector(),
(addArtist, importArtist, dimensions, allSeries) => { (addArtist, importArtist, dimensions, allSeries) => {
return { return {
defaultMonitor: addArtist.defaults.monitor, defaultMonitor: addArtist.defaults.monitor,

@ -99,10 +99,10 @@ class ImportArtistSelectArtist extends Component {
}); });
} }
onSeriesSelect = (tvdbId) => { onSeriesSelect = (foreignArtistId) => {
this.setState({ isOpen: false }); this.setState({ isOpen: false });
this.props.onSeriesSelect(tvdbId); this.props.onSeriesSelect(foreignArtistId);
} }
// //

@ -15,8 +15,8 @@ import FilterMenuItem from 'Components/Menu/FilterMenuItem';
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 NoArtist from 'Artist/NoArtist';
import SeasonPassRowConnector from './SeasonPassRowConnector'; import AlbumStudioRowConnector from './AlbumStudioRowConnector';
import SeasonPassFooter from './SeasonPassFooter'; import AlbumStudioFooter from './AlbumStudioFooter';
const columns = [ const columns = [
{ {
@ -24,8 +24,8 @@ const columns = [
isVisible: true isVisible: true
}, },
{ {
name: 'sortTitle', name: 'sortName',
label: 'Title', label: 'Name',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
@ -34,14 +34,14 @@ const columns = [
isVisible: true isVisible: true
}, },
{ {
name: 'seasonCount', name: 'albumCount',
label: 'Seasons', label: 'Albums',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
} }
]; ];
class SeasonPass extends Component { class AlbumStudio extends Component {
// //
// Lifecycle // Lifecycle
@ -121,7 +121,7 @@ class SeasonPass extends Component {
} = this.state; } = this.state;
return ( return (
<PageContent title="Season Pass"> <PageContent title="Album Studio">
<PageToolbar> <PageToolbar>
<PageToolbarSection /> <PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}> <PageToolbarSection alignContent={align.RIGHT}>
@ -207,7 +207,7 @@ class SeasonPass extends Component {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<SeasonPassRowConnector <AlbumStudioRowConnector
key={item.id} key={item.id}
artistId={item.id} artistId={item.id}
isSelected={selectedState[item.id]} isSelected={selectedState[item.id]}
@ -227,7 +227,7 @@ class SeasonPass extends Component {
} }
</PageContentBodyConnector> </PageContentBodyConnector>
<SeasonPassFooter <AlbumStudioFooter
selectedCount={this.getSelectedIds().length} selectedCount={this.getSelectedIds().length}
isSaving={isSaving} isSaving={isSaving}
saveError={saveError} saveError={saveError}
@ -238,7 +238,7 @@ class SeasonPass extends Component {
} }
} }
SeasonPass.propTypes = { AlbumStudio.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@ -254,4 +254,4 @@ SeasonPass.propTypes = {
onUpdateSelectedPress: PropTypes.func.isRequired onUpdateSelectedPress: PropTypes.func.isRequired
}; };
export default SeasonPass; export default AlbumStudio;

@ -3,20 +3,20 @@ import React, { Component } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import padNumber from 'Utilities/Number/padNumber'; import padNumber from 'Utilities/Number/padNumber';
import MonitorToggleButton from 'Components/MonitorToggleButton'; import MonitorToggleButton from 'Components/MonitorToggleButton';
import styles from './SeasonPassSeason.css'; import styles from './AlbumStudioAlbum.css';
class SeasonPassSeason extends Component { class AlbumStudioAlbum extends Component {
// //
// Listeners // Listeners
onSeasonMonitoredPress = () => { onSeasonMonitoredPress = () => {
const { const {
seasonNumber, id,
monitored monitored
} = this.props; } = this.props;
this.props.onSeasonMonitoredPress(seasonNumber, !monitored); this.props.onSeasonMonitoredPress(id, !monitored);
} }
// //
@ -24,16 +24,17 @@ class SeasonPassSeason extends Component {
render() { render() {
const { const {
seasonNumber, id,
title,
monitored, monitored,
statistics, statistics,
isSaving isSaving
} = this.props; } = this.props;
const { const {
episodeFileCount, trackFileCount,
totalEpisodeCount, totalTrackCount,
percentOfEpisodes percentOfTracks
} = statistics; } = statistics;
return ( return (
@ -47,7 +48,7 @@ class SeasonPassSeason extends Component {
<span> <span>
{ {
seasonNumber === 0 ? 'Specials' : `S${padNumber(seasonNumber, 2)}` `${title}`
} }
</span> </span>
</div> </div>
@ -55,12 +56,12 @@ class SeasonPassSeason extends Component {
<div <div
className={classNames( className={classNames(
styles.episodes, styles.episodes,
percentOfEpisodes === 100 && styles.allEpisodes percentOfTracks === 100 && styles.allEpisodes
)} )}
title={`${episodeFileCount}/${totalEpisodeCount} episodes downloaded`} title={`${trackFileCount}/${totalTrackCount} tracks downloaded`}
> >
{ {
totalEpisodeCount === 0 ? '0/0' : `${episodeFileCount}/${totalEpisodeCount}` totalTrackCount === 0 ? '0/0' : `${trackFileCount}/${totalTrackCount}`
} }
</div> </div>
</div> </div>
@ -68,21 +69,22 @@ class SeasonPassSeason extends Component {
} }
} }
SeasonPassSeason.propTypes = { AlbumStudioAlbum.propTypes = {
seasonNumber: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired, statistics: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
onSeasonMonitoredPress: PropTypes.func.isRequired onSeasonMonitoredPress: PropTypes.func.isRequired
}; };
SeasonPassSeason.defaultProps = { AlbumStudioAlbum.defaultProps = {
isSaving: false, isSaving: false,
statistics: { statistics: {
episodeFileCount: 0, trackFileCount: 0,
totalEpisodeCount: 0, totalTrackCount: 0,
percentOfEpisodes: 0 percentOfTracks: 0
} }
}; };
export default SeasonPassSeason; export default AlbumStudioAlbum;

@ -3,8 +3,8 @@ import React, { Component } from 'react';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import connectSection from 'Store/connectSection'; import connectSection from 'Store/connectSection';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { setSeasonPassSort, setSeasonPassFilter, saveSeasonPass } from 'Store/Actions/seasonPassActions'; import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions';
import SeasonPass from './SeasonPass'; import AlbumStudio from './AlbumStudio';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@ -18,26 +18,26 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
setSeasonPassSort, setAlbumStudioSort,
setSeasonPassFilter, setAlbumStudioFilter,
saveSeasonPass saveAlbumStudio
}; };
class SeasonPassConnector extends Component { class AlbumStudioConnector extends Component {
// //
// Listeners // Listeners
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.setSeasonPassSort({ sortKey }); this.props.setAlbumStudioSort({ sortKey });
} }
onFilterSelect = (filterKey, filterValue, filterType) => { onFilterSelect = (filterKey, filterValue, filterType) => {
this.props.setSeasonPassFilter({ filterKey, filterValue, filterType }); this.props.setAlbumStudioFilter({ filterKey, filterValue, filterType });
} }
onUpdateSelectedPress = (payload) => { onUpdateSelectedPress = (payload) => {
this.props.saveSeasonPass(payload); this.props.saveAlbumStudio(payload);
} }
// //
@ -45,7 +45,7 @@ class SeasonPassConnector extends Component {
render() { render() {
return ( return (
<SeasonPass <AlbumStudio
{...this.props} {...this.props}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect} onFilterSelect={this.onFilterSelect}
@ -55,10 +55,10 @@ class SeasonPassConnector extends Component {
} }
} }
SeasonPassConnector.propTypes = { AlbumStudioConnector.propTypes = {
setSeasonPassSort: PropTypes.func.isRequired, setAlbumStudioSort: PropTypes.func.isRequired,
setSeasonPassFilter: PropTypes.func.isRequired, setAlbumStudioFilter: PropTypes.func.isRequired,
saveSeasonPass: PropTypes.func.isRequired saveAlbumStudio: PropTypes.func.isRequired
}; };
export default connectSection( export default connectSection(
@ -66,5 +66,5 @@ export default connectSection(
mapDispatchToProps, mapDispatchToProps,
undefined, undefined,
undefined, undefined,
{ section: 'series', uiSection: 'seasonPass' } { section: 'series', uiSection: 'albumStudio' }
)(SeasonPassConnector); )(AlbumStudioConnector);

@ -5,11 +5,11 @@ import SpinnerButton from 'Components/Link/SpinnerButton';
import MonitorAlbumsSelectInput from 'Components/Form/MonitorAlbumsSelectInput'; import MonitorAlbumsSelectInput from 'Components/Form/MonitorAlbumsSelectInput';
import SelectInput from 'Components/Form/SelectInput'; import SelectInput from 'Components/Form/SelectInput';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
import styles from './SeasonPassFooter.css'; import styles from './AlbumStudioFooter.css';
const NO_CHANGE = 'noChange'; const NO_CHANGE = 'noChange';
class SeasonPassFooter extends Component { class AlbumStudioFooter extends Component {
// //
// Lifecycle // Lifecycle
@ -89,7 +89,7 @@ class SeasonPassFooter extends Component {
<PageContentFooter> <PageContentFooter>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.label}> <div className={styles.label}>
Monitor Series Monitor Artist
</div> </div>
<SelectInput <SelectInput
@ -103,7 +103,7 @@ class SeasonPassFooter extends Component {
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<div className={styles.label}> <div className={styles.label}>
Monitor Episodes Monitor Albums
</div> </div>
<MonitorAlbumsSelectInput <MonitorAlbumsSelectInput
@ -117,7 +117,7 @@ class SeasonPassFooter extends Component {
<div> <div>
<div className={styles.label}> <div className={styles.label}>
{selectedCount} Series Selected {selectedCount} Artist(s) Selected
</div> </div>
<SpinnerButton <SpinnerButton
@ -135,11 +135,11 @@ class SeasonPassFooter extends Component {
} }
} }
SeasonPassFooter.propTypes = { AlbumStudioFooter.propTypes = {
selectedCount: PropTypes.number.isRequired, selectedCount: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object, saveError: PropTypes.object,
onUpdateSelectedPress: PropTypes.func.isRequired onUpdateSelectedPress: PropTypes.func.isRequired
}; };
export default SeasonPassFooter; export default AlbumStudioFooter;

@ -7,10 +7,10 @@ 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 ArtistNameLink from 'Artist/ArtistNameLink';
import SeasonPassSeason from './SeasonPassSeason'; import AlbumStudioAlbum from './AlbumStudioAlbum';
import styles from './SeasonPassRow.css'; import styles from './AlbumStudioRow.css';
class SeasonPassRow extends Component { class AlbumStudioRow extends Component {
// //
// Render // Render
@ -19,10 +19,10 @@ class SeasonPassRow extends Component {
const { const {
artistId, artistId,
status, status,
titleSlug, nameSlug,
title, artistName,
monitored, monitored,
seasons, albums,
isSaving, isSaving,
isSelected, isSelected,
onSelectedChange, onSelectedChange,
@ -49,8 +49,8 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.title}> <TableRowCell className={styles.title}>
<ArtistNameLink <ArtistNameLink
titleSlug={titleSlug} nameSlug={nameSlug}
title={title} artistName={artistName}
/> />
</TableRowCell> </TableRowCell>
@ -64,10 +64,10 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.seasons}> <TableRowCell className={styles.seasons}>
{ {
seasons.map((season) => { albums.map((season) => {
return ( return (
<SeasonPassSeason <AlbumStudioAlbum
key={season.seasonNumber} key={season.id}
{...season} {...season}
onSeasonMonitoredPress={onSeasonMonitoredPress} onSeasonMonitoredPress={onSeasonMonitoredPress}
/> />
@ -80,13 +80,13 @@ class SeasonPassRow extends Component {
} }
} }
SeasonPassRow.propTypes = { AlbumStudioRow.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired, nameSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
seasons: PropTypes.arrayOf(PropTypes.object).isRequired, albums: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired, onSelectedChange: PropTypes.func.isRequired,
@ -94,8 +94,8 @@ SeasonPassRow.propTypes = {
onSeasonMonitoredPress: PropTypes.func.isRequired onSeasonMonitoredPress: PropTypes.func.isRequired
}; };
SeasonPassRow.defaultProps = { AlbumStudioRow.defaultProps = {
isSaving: false isSaving: false
}; };
export default SeasonPassRow; export default AlbumStudioRow;

@ -4,8 +4,8 @@ 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 createArtistSelector from 'Store/Selectors/createArtistSelector';
import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/seriesActions'; import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/artistActions';
import SeasonPassRow from './SeasonPassRow'; import AlbumStudioRow from './AlbumStudioRow';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
@ -13,10 +13,10 @@ function createMapStateToProps() {
(series) => { (series) => {
return _.pick(series, [ return _.pick(series, [
'status', 'status',
'titleSlug', 'nameSlug',
'title', 'artistName',
'monitored', 'monitored',
'seasons', 'albums',
'isSaving' 'isSaving'
]); ]);
} }
@ -28,7 +28,7 @@ const mapDispatchToProps = {
toggleSeasonMonitored toggleSeasonMonitored
}; };
class SeasonPassRowConnector extends Component { class AlbumStudioRowConnector extends Component {
// //
// Listeners // Listeners
@ -58,7 +58,7 @@ class SeasonPassRowConnector extends Component {
render() { render() {
return ( return (
<SeasonPassRow <AlbumStudioRow
{...this.props} {...this.props}
onSeriesMonitoredPress={this.onSeriesMonitoredPress} onSeriesMonitoredPress={this.onSeriesMonitoredPress}
onSeasonMonitoredPress={this.onSeasonMonitoredPress} onSeasonMonitoredPress={this.onSeasonMonitoredPress}
@ -67,11 +67,11 @@ class SeasonPassRowConnector extends Component {
} }
} }
SeasonPassRowConnector.propTypes = { AlbumStudioRowConnector.propTypes = {
artistId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
toggleSeriesMonitored: PropTypes.func.isRequired, toggleSeriesMonitored: PropTypes.func.isRequired,
toggleSeasonMonitored: PropTypes.func.isRequired toggleSeasonMonitored: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(SeasonPassRowConnector); export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector);

@ -11,8 +11,8 @@ import PageConnector from 'Components/Page/PageConnector';
import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector'; import ArtistIndexConnector from 'Artist/Index/ArtistIndexConnector';
import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector'; import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector';
import ImportArtist from 'AddArtist/ImportArtist/ImportArtist'; import ImportArtist from 'AddArtist/ImportArtist/ImportArtist';
import SeriesEditorConnector from 'Artist/Editor/SeriesEditorConnector'; import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
import SeasonPassConnector from 'SeasonPass/SeasonPassConnector'; import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
import SeriesDetailsPageConnector from 'Artist/Details/SeriesDetailsPageConnector'; import SeriesDetailsPageConnector from 'Artist/Details/SeriesDetailsPageConnector';
import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import HistoryConnector from 'Activity/History/HistoryConnector'; import HistoryConnector from 'Activity/History/HistoryConnector';
@ -82,17 +82,17 @@ function App({ store, history }) {
/> />
<Route <Route
path="/serieseditor" path="/artisteditor"
component={SeriesEditorConnector} component={ArtistEditorConnector}
/> />
<Route <Route
path="/seasonpass" path="/albumstudio"
component={SeasonPassConnector} component={AlbumStudioConnector}
/> />
<Route <Route
path="/series/:titleSlug" path="/artist/:nameSlug"
component={SeriesDetailsPageConnector} component={SeriesDetailsPageConnector}
/> />

@ -3,7 +3,7 @@ import React from 'react';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
function ArtistNameLink({ nameSlug, artistName }) { function ArtistNameLink({ nameSlug, artistName }) {
const link = `/series/${nameSlug}`; const link = `/artist/${nameSlug}`;
return ( return (
<Link to={link}> <Link to={link}>

@ -3,7 +3,7 @@ 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 createArtistSelector from 'Store/Selectors/createArtistSelector';
import { deleteArtist } from 'Store/Actions/seriesActions'; import { deleteArtist } from 'Store/Actions/artistActions';
import DeleteArtistModalContent from './DeleteArtistModalContent'; import DeleteArtistModalContent from './DeleteArtistModalContent';
function createMapStateToProps() { function createMapStateToProps() {

@ -99,7 +99,7 @@ class SeriesDetails extends Component {
}); });
} }
onDeleteSeriesModalClose = () => { onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false }); this.setState({ isDeleteArtistModalOpen: false });
} }
@ -132,10 +132,10 @@ class SeriesDetails extends Component {
render() { render() {
const { const {
id, id,
tvdbId, foreignArtistId,
tvMazeId, tvMazeId,
imdbId, imdbId,
title, artistName,
runtime, runtime,
ratings, ratings,
sizeOnDisk, sizeOnDisk,
@ -146,7 +146,7 @@ class SeriesDetails extends Component {
network, network,
overview, overview,
images, images,
seasons, albums,
alternateTitles, alternateTitles,
tags, tags,
isRefreshing, isRefreshing,
@ -190,7 +190,7 @@ class SeriesDetails extends Component {
} }
return ( return (
<PageContent title={title}> <PageContent title={artistName}>
<PageToolbar> <PageToolbar>
<PageToolbarSection> <PageToolbarSection>
<PageToolbarButton <PageToolbarButton
@ -269,7 +269,7 @@ class SeriesDetails extends Component {
<div className={styles.info}> <div className={styles.info}>
<div className={styles.titleContainer}> <div className={styles.titleContainer}>
<div className={styles.title}> <div className={styles.title}>
{title} {artistName}
{ {
!!alternateTitles.length && !!alternateTitles.length &&
@ -294,16 +294,16 @@ class SeriesDetails extends Component {
className={styles.seriesNavigationButton} className={styles.seriesNavigationButton}
name={icons.ARROW_LEFT} name={icons.ARROW_LEFT}
size={30} size={30}
title={`Go to ${previousSeries.title}`} title={`Go to ${previousSeries.artistName}`}
to={`/series/${previousSeries.titleSlug}`} to={`/artist/${previousSeries.nameSlug}`}
/> />
<IconButton <IconButton
className={styles.seriesNavigationButton} className={styles.seriesNavigationButton}
name={icons.ARROW_RIGHT} name={icons.ARROW_RIGHT}
size={30} size={30}
title={`Go to ${nextSeries.title}`} title={`Go to ${nextSeries.artistName}`}
to={`/series/${nextSeries.titleSlug}`} to={`/artist/${nextSeries.nameSlug}`}
/> />
</div> </div>
</div> </div>
@ -426,7 +426,7 @@ class SeriesDetails extends Component {
} }
tooltip={ tooltip={
<SeriesDetailsLinks <SeriesDetailsLinks
tvdbId={tvdbId} foreignArtistId={foreignArtistId}
tvMazeId={tvMazeId} tvMazeId={tvMazeId}
imdbId={imdbId} imdbId={imdbId}
/> />
@ -485,10 +485,10 @@ class SeriesDetails extends Component {
} }
{ {
isPopulated && !!seasons.length && isPopulated && !!albums.length &&
<div> <div>
{ {
seasons.slice(0).reverse().map((season) => { albums.slice(0).reverse().map((season) => {
return ( return (
<SeriesDetailsSeasonConnector <SeriesDetailsSeasonConnector
key={season.seasonNumber} key={season.seasonNumber}
@ -504,7 +504,7 @@ class SeriesDetails extends Component {
} }
{ {
isPopulated && !seasons.length && isPopulated && !albums.length &&
<div> <div>
No episode information is available. No episode information is available.
</div> </div>
@ -534,7 +534,7 @@ class SeriesDetails extends Component {
<DeleteArtistModal <DeleteArtistModal
isOpen={isDeleteArtistModalOpen} isOpen={isDeleteArtistModalOpen}
artistId={id} artistId={id}
onModalClose={this.onDeleteSeriesModalClose} onModalClose={this.onDeleteArtistModalClose}
/> />
</PageContentBodyConnector> </PageContentBodyConnector>
</PageContent> </PageContent>
@ -544,10 +544,10 @@ class SeriesDetails extends Component {
SeriesDetails.propTypes = { SeriesDetails.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
tvdbId: PropTypes.number.isRequired, foreignArtistId: PropTypes.string.isRequired,
tvMazeId: PropTypes.number, tvMazeId: PropTypes.number,
imdbId: PropTypes.string, imdbId: PropTypes.string,
title: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
runtime: PropTypes.number.isRequired, runtime: PropTypes.number.isRequired,
ratings: PropTypes.object.isRequired, ratings: PropTypes.object.isRequired,
sizeOnDisk: PropTypes.number.isRequired, sizeOnDisk: PropTypes.number.isRequired,
@ -558,7 +558,7 @@ SeriesDetails.propTypes = {
network: PropTypes.string, network: PropTypes.string,
overview: PropTypes.string.isRequired, overview: PropTypes.string.isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired,
seasons: PropTypes.arrayOf(PropTypes.object).isRequired, albums: PropTypes.arrayOf(PropTypes.object).isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired, alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired,
isRefreshing: PropTypes.bool.isRequired, isRefreshing: PropTypes.bool.isRequired,

@ -4,7 +4,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { findCommand } from 'Utilities/Command'; import { findCommand } from 'Utilities/Command';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector'; import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import { fetchEpisodes, clearEpisodes } from 'Store/Actions/episodeActions'; import { fetchEpisodes, clearEpisodes } from 'Store/Actions/episodeActions';
import { fetchEpisodeFiles, clearEpisodeFiles } from 'Store/Actions/episodeFileActions'; import { fetchEpisodeFiles, clearEpisodeFiles } from 'Store/Actions/episodeFileActions';
@ -15,28 +15,28 @@ import SeriesDetails from './SeriesDetails';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { titleSlug }) => titleSlug, (state, { nameSlug }) => nameSlug,
(state) => state.episodes, (state) => state.episodes,
(state) => state.episodeFiles, (state) => state.episodeFiles,
createAllSeriesSelector(), createAllArtistSelector(),
createCommandsSelector(), createCommandsSelector(),
(titleSlug, episodes, episodeFiles, allSeries, commands) => { (nameSlug, episodes, episodeFiles, allSeries, commands) => {
const sortedSeries = _.orderBy(allSeries, 'sortTitle'); const sortedArtist = _.orderBy(allSeries, 'sortTitle');
const seriesIndex = _.findIndex(sortedSeries, { titleSlug }); const seriesIndex = _.findIndex(sortedArtist, { nameSlug });
const series = sortedSeries[seriesIndex]; const series = sortedArtist[seriesIndex];
if (!series) { if (!series) {
return {}; return {};
} }
const previousSeries = sortedSeries[seriesIndex - 1] || _.last(sortedSeries); const previousSeries = sortedArtist[seriesIndex - 1] || _.last(sortedArtist);
const nextSeries = sortedSeries[seriesIndex + 1] || _.first(sortedSeries); const nextSeries = sortedArtist[seriesIndex + 1] || _.first(sortedArtist);
const isSeriesRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_SERIES, artistId: series.id }); const isSeriesRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: series.id });
const allSeriesRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_SERIES && !command.body.artistId); const allSeriesRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_ARTIST && !command.body.artistId);
const isRefreshing = isSeriesRefreshing || allSeriesRefreshing; const isRefreshing = isSeriesRefreshing || allSeriesRefreshing;
const isSearching = !!findCommand(commands, { name: commandNames.SERIES_SEARCH, artistId: series.id }); const isSearching = !!findCommand(commands, { name: commandNames.ARTIST_SEARCH, artistId: series.id });
const isRenamingFiles = !!findCommand(commands, { name: commandNames.RENAME_FILES, artistId: series.id }); const isRenamingFiles = !!findCommand(commands, { name: commandNames.RENAME_FILES, artistId: series.id });
const isRenamingSeriesCommand = findCommand(commands, { name: commandNames.RENAME_SERIES }); const isRenamingSeriesCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST });
const isRenamingSeries = !!(isRenamingSeriesCommand && isRenamingSeriesCommand.body.artistId.indexOf(series.id) > -1); const isRenamingSeries = !!(isRenamingSeriesCommand && isRenamingSeriesCommand.body.artistId.indexOf(series.id) > -1);
const isFetching = episodes.isFetching || episodeFiles.isFetching; const isFetching = episodes.isFetching || episodeFiles.isFetching;
@ -140,14 +140,14 @@ class SeriesDetailsConnector extends Component {
onRefreshPress = () => { onRefreshPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.REFRESH_SERIES, name: commandNames.REFRESH_ARTIST,
artistId: this.props.id artistId: this.props.id
}); });
} }
onSearchPress = () => { onSearchPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.SERIES_SEARCH, name: commandNames.ARTIST_SEARCH,
artistId: this.props.id artistId: this.props.id
}); });
} }
@ -168,7 +168,7 @@ class SeriesDetailsConnector extends Component {
SeriesDetailsConnector.propTypes = { SeriesDetailsConnector.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
titleSlug: PropTypes.string.isRequired, nameSlug: PropTypes.string.isRequired,
isRefreshing: PropTypes.bool.isRequired, isRefreshing: PropTypes.bool.isRequired,
isRenamingFiles: PropTypes.bool.isRequired, isRenamingFiles: PropTypes.bool.isRequired,
isRenamingSeries: PropTypes.bool.isRequired, isRenamingSeries: PropTypes.bool.isRequired,

@ -7,7 +7,7 @@ import styles from './SeriesDetailsLinks.css';
function SeriesDetailsLinks(props) { function SeriesDetailsLinks(props) {
const { const {
tvdbId, foreignArtistId,
tvMazeId, tvMazeId,
imdbId imdbId
} = props; } = props;
@ -16,7 +16,7 @@ function SeriesDetailsLinks(props) {
<div className={styles.links}> <div className={styles.links}>
<Link <Link
className={styles.link} className={styles.link}
to={`http://www.thetvdb.com/?tab=series&id=${tvdbId}`} to={`http://www.thetvdb.com/?tab=series&id=${foreignArtistId}`}
> >
<Label <Label
className={styles.linkLabel} className={styles.linkLabel}
@ -29,7 +29,7 @@ function SeriesDetailsLinks(props) {
<Link <Link
className={styles.link} className={styles.link}
to={`http://trakt.tv/search/tvdb/${tvdbId}?id_type=show`} to={`http://trakt.tv/search/tvdb/${foreignArtistId}?id_type=show`}
> >
<Label <Label
className={styles.linkLabel} className={styles.linkLabel}
@ -76,7 +76,7 @@ function SeriesDetailsLinks(props) {
} }
SeriesDetailsLinks.propTypes = { SeriesDetailsLinks.propTypes = {
tvdbId: PropTypes.number.isRequired, foreignArtistId: PropTypes.string.isRequired,
tvMazeId: PropTypes.number, tvMazeId: PropTypes.number,
imdbId: PropTypes.string imdbId: PropTypes.string
}; };

@ -4,21 +4,21 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { push } from 'react-router-redux'; import { push } from 'react-router-redux';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import NotFound from 'Components/NotFound'; import NotFound from 'Components/NotFound';
import SeriesDetailsConnector from './SeriesDetailsConnector'; import SeriesDetailsConnector from './SeriesDetailsConnector';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { match }) => match, (state, { match }) => match,
createAllSeriesSelector(), createAllArtistSelector(),
(match, allSeries) => { (match, allSeries) => {
const titleSlug = match.params.titleSlug; const nameSlug = match.params.nameSlug;
const seriesIndex = _.findIndex(allSeries, { titleSlug }); const seriesIndex = _.findIndex(allSeries, { nameSlug });
if (seriesIndex > -1) { if (seriesIndex > -1) {
return { return {
titleSlug nameSlug
}; };
} }
@ -37,7 +37,7 @@ class SeriesDetailsPageConnector extends Component {
// Lifecycle // Lifecycle
componentDidUpdate(prevProps) { componentDidUpdate(prevProps) {
if (!this.props.titleSlug) { if (!this.props.nameSlug) {
this.props.push(`${window.Sonarr.urlBase}/`); this.props.push(`${window.Sonarr.urlBase}/`);
return; return;
} }
@ -48,10 +48,10 @@ class SeriesDetailsPageConnector extends Component {
render() { render() {
const { const {
titleSlug nameSlug
} = this.props; } = this.props;
if (!titleSlug) { if (!nameSlug) {
return ( return (
<NotFound <NotFound
message="Sorry, that series cannot be found." message="Sorry, that series cannot be found."
@ -61,15 +61,15 @@ class SeriesDetailsPageConnector extends Component {
return ( return (
<SeriesDetailsConnector <SeriesDetailsConnector
titleSlug={titleSlug} nameSlug={nameSlug}
/> />
); );
} }
} }
SeriesDetailsPageConnector.propTypes = { SeriesDetailsPageConnector.propTypes = {
titleSlug: PropTypes.string, nameSlug: PropTypes.string,
match: PropTypes.shape({ params: PropTypes.shape({ titleSlug: PropTypes.string.isRequired }).isRequired }).isRequired, match: PropTypes.shape({ params: PropTypes.shape({ nameSlug: PropTypes.string.isRequired }).isRequired }).isRequired,
push: PropTypes.func.isRequired push: PropTypes.func.isRequired
}; };

@ -7,7 +7,7 @@ import { findCommand } from 'Utilities/Command';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createArtistSelector from 'Store/Selectors/createArtistSelector';
import createCommandsSelector from 'Store/Selectors/createCommandsSelector'; import createCommandsSelector from 'Store/Selectors/createCommandsSelector';
import { toggleSeasonMonitored } from 'Store/Actions/seriesActions'; import { toggleSeasonMonitored } from 'Store/Actions/artistActions';
import { toggleEpisodesMonitored, setEpisodesTableOption } from 'Store/Actions/episodeActions'; import { toggleEpisodesMonitored, setEpisodesTableOption } from 'Store/Actions/episodeActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';

@ -5,7 +5,7 @@ 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 createArtistSelector from 'Store/Selectors/createArtistSelector';
import { setSeriesValue, saveArtist } from 'Store/Actions/seriesActions'; import { setSeriesValue, saveArtist } from 'Store/Actions/artistActions';
import EditArtistModalContent from './EditArtistModalContent'; import EditArtistModalContent from './EditArtistModalContent';
function createMapStateToProps() { function createMapStateToProps() {

@ -15,9 +15,9 @@ import FilterMenuItem from 'Components/Menu/FilterMenuItem';
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 NoArtist from 'Artist/NoArtist';
import SeriesEditorRowConnector from './SeriesEditorRowConnector'; import ArtistEditorRowConnector from './ArtistEditorRowConnector';
import SeriesEditorFooter from './SeriesEditorFooter'; import ArtistEditorFooter from './ArtistEditorFooter';
import OrganizeSeriesModal from './Organize/OrganizeSeriesModal'; import OrganizeArtistModal from './Organize/OrganizeArtistModal';
function getColumns(showLanguageProfile) { function getColumns(showLanguageProfile) {
return [ return [
@ -26,8 +26,8 @@ function getColumns(showLanguageProfile) {
isVisible: true isVisible: true
}, },
{ {
name: 'sortTitle', name: 'sortName',
label: 'Title', label: 'Name',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
@ -44,14 +44,8 @@ function getColumns(showLanguageProfile) {
isVisible: showLanguageProfile isVisible: showLanguageProfile
}, },
{ {
name: 'seriesType', name: 'albumFolder',
label: 'Series Type', label: 'Album Folder',
isSortable: false,
isVisible: true
},
{
name: 'seasonFolder',
label: 'Season Folder',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
@ -70,7 +64,7 @@ function getColumns(showLanguageProfile) {
]; ];
} }
class SeriesEditor extends Component { class ArtistEditor extends Component {
// //
// Lifecycle // Lifecycle
@ -83,7 +77,7 @@ class SeriesEditor extends Component {
allUnselected: false, allUnselected: false,
lastToggled: null, lastToggled: null,
selectedState: {}, selectedState: {},
isOrganizingSeriesModalOpen: false, isOrganizingArtistModalOpen: false,
columns: getColumns(props.showLanguageProfile) columns: getColumns(props.showLanguageProfile)
}; };
} }
@ -130,12 +124,12 @@ class SeriesEditor extends Component {
}); });
} }
onOrganizeSeriesPress = () => { onOrganizeArtistPress = () => {
this.setState({ isOrganizingSeriesModalOpen: true }); this.setState({ isOrganizingArtistModalOpen: true });
} }
onOrganizeSeriesModalClose = (organized) => { onOrganizeArtistModalClose = (organized) => {
this.setState({ isOrganizingSeriesModalOpen: false }); this.setState({ isOrganizingArtistModalOpen: false });
if (organized === true) { if (organized === true) {
this.onSelectAllChange({ value: false }); this.onSelectAllChange({ value: false });
@ -159,7 +153,7 @@ class SeriesEditor extends Component {
saveError, saveError,
isDeleting, isDeleting,
deleteError, deleteError,
isOrganizingSeries, isOrganizingArtist,
showLanguageProfile, showLanguageProfile,
onSortPress, onSortPress,
onFilterSelect onFilterSelect
@ -172,10 +166,10 @@ class SeriesEditor extends Component {
columns columns
} = this.state; } = this.state;
const selectedSeriesIds = this.getSelectedIds(); const selectedArtistIds = this.getSelectedIds();
return ( return (
<PageContent title="Series Editor"> <PageContent title="Artist Editor">
<PageToolbar> <PageToolbar>
<PageToolbarSection /> <PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}> <PageToolbarSection alignContent={align.RIGHT}>
@ -261,7 +255,7 @@ class SeriesEditor extends Component {
{ {
items.map((item) => { items.map((item) => {
return ( return (
<SeriesEditorRowConnector <ArtistEditorRowConnector
key={item.id} key={item.id}
{...item} {...item}
columns={columns} columns={columns}
@ -282,30 +276,30 @@ class SeriesEditor extends Component {
} }
</PageContentBodyConnector> </PageContentBodyConnector>
<SeriesEditorFooter <ArtistEditorFooter
artistIds={selectedSeriesIds} artistIds={selectedArtistIds}
selectedCount={selectedSeriesIds.length} selectedCount={selectedArtistIds.length}
isSaving={isSaving} isSaving={isSaving}
saveError={saveError} saveError={saveError}
isDeleting={isDeleting} isDeleting={isDeleting}
deleteError={deleteError} deleteError={deleteError}
isOrganizingSeries={isOrganizingSeries} isOrganizingArtist={isOrganizingArtist}
showLanguageProfile={showLanguageProfile} showLanguageProfile={showLanguageProfile}
onSaveSelected={this.onSaveSelected} onSaveSelected={this.onSaveSelected}
onOrganizeSeriesPress={this.onOrganizeSeriesPress} onOrganizeArtistPress={this.onOrganizeArtistPress}
/> />
<OrganizeSeriesModal <OrganizeArtistModal
isOpen={this.state.isOrganizingSeriesModalOpen} isOpen={this.state.isOrganizingArtistModalOpen}
artistIds={selectedSeriesIds} artistIds={selectedArtistIds}
onModalClose={this.onOrganizeSeriesModalClose} onModalClose={this.onOrganizeArtistModalClose}
/> />
</PageContent> </PageContent>
); );
} }
} }
SeriesEditor.propTypes = { ArtistEditor.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
@ -318,11 +312,11 @@ SeriesEditor.propTypes = {
saveError: PropTypes.object, saveError: PropTypes.object,
isDeleting: PropTypes.bool.isRequired, isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object, deleteError: PropTypes.object,
isOrganizingSeries: PropTypes.bool.isRequired, isOrganizingArtist: PropTypes.bool.isRequired,
showLanguageProfile: PropTypes.bool.isRequired, showLanguageProfile: 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 SeriesEditor; export default ArtistEditor;

@ -4,19 +4,19 @@ import { createSelector } from 'reselect';
import connectSection from 'Store/connectSection'; import connectSection from 'Store/connectSection';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandSelector from 'Store/Selectors/createCommandSelector'; import createCommandSelector from 'Store/Selectors/createCommandSelector';
import { setSeriesEditorSort, setSeriesEditorFilter, saveArtistEditor } from 'Store/Actions/seriesEditorActions'; import { setArtistEditorSort, setArtistEditorFilter, saveArtistEditor } from 'Store/Actions/artistEditorActions';
import { fetchRootFolders } from 'Store/Actions/rootFolderActions'; import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
import * as commandNames from 'Commands/commandNames'; import * as commandNames from 'Commands/commandNames';
import SeriesEditor from './SeriesEditor'; import ArtistEditor from './ArtistEditor';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.settings.languageProfiles, (state) => state.settings.languageProfiles,
createClientSideCollectionSelector(), createClientSideCollectionSelector(),
createCommandSelector(commandNames.RENAME_SERIES), createCommandSelector(commandNames.RENAME_ARTIST),
(languageProfiles, series, isOrganizingSeries) => { (languageProfiles, series, isOrganizingArtist) => {
return { return {
isOrganizingSeries, isOrganizingArtist,
showLanguageProfile: languageProfiles.items.length > 1, showLanguageProfile: languageProfiles.items.length > 1,
...series ...series
}; };
@ -25,13 +25,13 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
setSeriesEditorSort, setArtistEditorSort,
setSeriesEditorFilter, setArtistEditorFilter,
saveArtistEditor, saveArtistEditor,
fetchRootFolders fetchRootFolders
}; };
class SeriesEditorConnector extends Component { class ArtistEditorConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -44,11 +44,11 @@ class SeriesEditorConnector extends Component {
// Listeners // Listeners
onSortPress = (sortKey) => { onSortPress = (sortKey) => {
this.props.setSeriesEditorSort({ sortKey }); this.props.setArtistEditorSort({ sortKey });
} }
onFilterSelect = (filterKey, filterValue, filterType) => { onFilterSelect = (filterKey, filterValue, filterType) => {
this.props.setSeriesEditorFilter({ filterKey, filterValue, filterType }); this.props.setArtistEditorFilter({ filterKey, filterValue, filterType });
} }
onSaveSelected = (payload) => { onSaveSelected = (payload) => {
@ -60,7 +60,7 @@ class SeriesEditorConnector extends Component {
render() { render() {
return ( return (
<SeriesEditor <ArtistEditor
{...this.props} {...this.props}
onSortPress={this.onSortPress} onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect} onFilterSelect={this.onFilterSelect}
@ -70,9 +70,9 @@ class SeriesEditorConnector extends Component {
} }
} }
SeriesEditorConnector.propTypes = { ArtistEditorConnector.propTypes = {
setSeriesEditorSort: PropTypes.func.isRequired, setArtistEditorSort: PropTypes.func.isRequired,
setSeriesEditorFilter: PropTypes.func.isRequired, setArtistEditorFilter: PropTypes.func.isRequired,
saveArtistEditor: PropTypes.func.isRequired, saveArtistEditor: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired fetchRootFolders: PropTypes.func.isRequired
}; };
@ -82,5 +82,5 @@ export default connectSection(
mapDispatchToProps, mapDispatchToProps,
undefined, undefined,
undefined, undefined,
{ section: 'series', uiSection: 'seriesEditor' } { section: 'series', uiSection: 'artistEditor' }
)(SeriesEditorConnector); )(ArtistEditorConnector);

@ -10,12 +10,12 @@ import SpinnerButton from 'Components/Link/SpinnerButton';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
import TagsModal from './Tags/TagsModal'; import TagsModal from './Tags/TagsModal';
import DeleteArtistModal from './Delete/DeleteArtistModal'; import DeleteArtistModal from './Delete/DeleteArtistModal';
import SeriesEditorFooterLabel from './SeriesEditorFooterLabel'; import ArtistEditorFooterLabel from './ArtistEditorFooterLabel';
import styles from './SeriesEditorFooter.css'; import styles from './ArtistEditorFooter.css';
const NO_CHANGE = 'noChange'; const NO_CHANGE = 'noChange';
class SeriesEditorFooter extends Component { class ArtistEditorFooter extends Component {
// //
// Lifecycle // Lifecycle
@ -27,8 +27,7 @@ class SeriesEditorFooter extends Component {
monitored: NO_CHANGE, monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE, qualityProfileId: NO_CHANGE,
languageProfileId: NO_CHANGE, languageProfileId: NO_CHANGE,
seriesType: NO_CHANGE, albumFolder: NO_CHANGE,
seasonFolder: NO_CHANGE,
rootFolderPath: NO_CHANGE, rootFolderPath: NO_CHANGE,
savingTags: false, savingTags: false,
isDeleteArtistModalOpen: false, isDeleteArtistModalOpen: false,
@ -47,8 +46,7 @@ class SeriesEditorFooter extends Component {
monitored: NO_CHANGE, monitored: NO_CHANGE,
qualityProfileId: NO_CHANGE, qualityProfileId: NO_CHANGE,
languageProfileId: NO_CHANGE, languageProfileId: NO_CHANGE,
seriesType: NO_CHANGE, albumFolder: NO_CHANGE,
seasonFolder: NO_CHANGE,
rootFolderPath: NO_CHANGE, rootFolderPath: NO_CHANGE,
savingTags: false savingTags: false
}); });
@ -93,7 +91,7 @@ class SeriesEditorFooter extends Component {
this.setState({ isDeleteArtistModalOpen: true }); this.setState({ isDeleteArtistModalOpen: true });
} }
onDeleteSeriesModalClose = () => { onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false }); this.setState({ isDeleteArtistModalOpen: false });
} }
@ -114,17 +112,16 @@ class SeriesEditorFooter extends Component {
selectedCount, selectedCount,
isSaving, isSaving,
isDeleting, isDeleting,
isOrganizingSeries, isOrganizingArtist,
showLanguageProfile, showLanguageProfile,
onOrganizeSeriesPress onOrganizeArtistPress
} = this.props; } = this.props;
const { const {
monitored, monitored,
qualityProfileId, qualityProfileId,
languageProfileId, languageProfileId,
seriesType, albumFolder,
seasonFolder,
rootFolderPath, rootFolderPath,
savingTags, savingTags,
isTagsModalOpen, isTagsModalOpen,
@ -137,7 +134,7 @@ class SeriesEditorFooter extends Component {
{ key: 'unmonitored', value: 'Unmonitored' } { key: 'unmonitored', value: 'Unmonitored' }
]; ];
const seasonFolderOptions = [ const albumFolderOptions = [
{ key: NO_CHANGE, value: 'No Change', disabled: true }, { key: NO_CHANGE, value: 'No Change', disabled: true },
{ key: 'yes', value: 'Yes' }, { key: 'yes', value: 'Yes' },
{ key: 'no', value: 'No' } { key: 'no', value: 'No' }
@ -146,8 +143,8 @@ class SeriesEditorFooter extends Component {
return ( return (
<PageContentFooter> <PageContentFooter>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<SeriesEditorFooterLabel <ArtistEditorFooterLabel
label="Monitor Series" label="Monitor Artist"
isSaving={isSaving && monitored !== NO_CHANGE} isSaving={isSaving && monitored !== NO_CHANGE}
/> />
@ -161,7 +158,7 @@ class SeriesEditorFooter extends Component {
</div> </div>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<SeriesEditorFooterLabel <ArtistEditorFooterLabel
label="Quality Profile" label="Quality Profile"
isSaving={isSaving && qualityProfileId !== NO_CHANGE} isSaving={isSaving && qualityProfileId !== NO_CHANGE}
/> />
@ -178,7 +175,7 @@ class SeriesEditorFooter extends Component {
{ {
showLanguageProfile && showLanguageProfile &&
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<SeriesEditorFooterLabel <ArtistEditorFooterLabel
label="Language Profile" label="Language Profile"
isSaving={isSaving && languageProfileId !== NO_CHANGE} isSaving={isSaving && languageProfileId !== NO_CHANGE}
/> />
@ -194,37 +191,22 @@ class SeriesEditorFooter extends Component {
} }
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<SeriesEditorFooterLabel <ArtistEditorFooterLabel
label="Series Type" label="Album Folder"
isSaving={isSaving && seriesType !== NO_CHANGE} isSaving={isSaving && albumFolder !== NO_CHANGE}
/>
<SeriesTypeSelectInput
name="seriesType"
value={seriesType}
includeNoChange={true}
isDisabled={!selectedCount}
onChange={this.onInputChange}
/>
</div>
<div className={styles.inputContainer}>
<SeriesEditorFooterLabel
label="Season Folder"
isSaving={isSaving && seasonFolder !== NO_CHANGE}
/> />
<SelectInput <SelectInput
name="seasonFolder" name="albumFolder"
value={seasonFolder} value={albumFolder}
values={seasonFolderOptions} values={albumFolderOptions}
isDisabled={!selectedCount} isDisabled={!selectedCount}
onChange={this.onInputChange} onChange={this.onInputChange}
/> />
</div> </div>
<div className={styles.inputContainer}> <div className={styles.inputContainer}>
<SeriesEditorFooterLabel <ArtistEditorFooterLabel
label="Root Folder" label="Root Folder"
isSaving={isSaving && rootFolderPath !== NO_CHANGE} isSaving={isSaving && rootFolderPath !== NO_CHANGE}
/> />
@ -241,8 +223,8 @@ class SeriesEditorFooter extends Component {
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<div className={styles.buttonContainerContent}> <div className={styles.buttonContainerContent}>
<SeriesEditorFooterLabel <ArtistEditorFooterLabel
label={`${selectedCount} Series Selected`} label={`${selectedCount} Artist(s) Selected`}
isSaving={false} isSaving={false}
/> />
@ -251,9 +233,9 @@ class SeriesEditorFooter extends Component {
<SpinnerButton <SpinnerButton
className={styles.organizeSelectedButton} className={styles.organizeSelectedButton}
kind={kinds.WARNING} kind={kinds.WARNING}
isSpinning={isOrganizingSeries} isSpinning={isOrganizingArtist}
isDisabled={!selectedCount || isOrganizingSeries} isDisabled={!selectedCount || isOrganizingArtist}
onPress={onOrganizeSeriesPress} onPress={onOrganizeArtistPress}
> >
Rename Files Rename Files
</SpinnerButton> </SpinnerButton>
@ -261,7 +243,7 @@ class SeriesEditorFooter extends Component {
<SpinnerButton <SpinnerButton
className={styles.tagsButton} className={styles.tagsButton}
isSpinning={isSaving && savingTags} isSpinning={isSaving && savingTags}
isDisabled={!selectedCount || isOrganizingSeries} isDisabled={!selectedCount || isOrganizingArtist}
onPress={this.onTagsPress} onPress={this.onTagsPress}
> >
Set Tags Set Tags
@ -291,24 +273,24 @@ class SeriesEditorFooter extends Component {
<DeleteArtistModal <DeleteArtistModal
isOpen={isDeleteArtistModalOpen} isOpen={isDeleteArtistModalOpen}
artistIds={artistIds} artistIds={artistIds}
onModalClose={this.onDeleteSeriesModalClose} onModalClose={this.onDeleteArtistModalClose}
/> />
</PageContentFooter> </PageContentFooter>
); );
} }
} }
SeriesEditorFooter.propTypes = { ArtistEditorFooter.propTypes = {
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired, artistIds: 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,
isOrganizingSeries: PropTypes.bool.isRequired, isOrganizingArtist: PropTypes.bool.isRequired,
showLanguageProfile: PropTypes.bool.isRequired, showLanguageProfile: PropTypes.bool.isRequired,
onSaveSelected: PropTypes.func.isRequired, onSaveSelected: PropTypes.func.isRequired,
onOrganizeSeriesPress: PropTypes.func.isRequired onOrganizeArtistPress: PropTypes.func.isRequired
}; };
export default SeriesEditorFooter; export default ArtistEditorFooter;

@ -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 './SeriesEditorFooterLabel.css'; import styles from './ArtistEditorFooterLabel.css';
function SeriesEditorFooterLabel(props) { function ArtistEditorFooterLabel(props) {
const { const {
className, className,
label, label,
@ -27,14 +27,14 @@ function SeriesEditorFooterLabel(props) {
); );
} }
SeriesEditorFooterLabel.propTypes = { ArtistEditorFooterLabel.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
}; };
SeriesEditorFooterLabel.defaultProps = { ArtistEditorFooterLabel.defaultProps = {
className: styles.label className: styles.label
}; };
export default SeriesEditorFooterLabel; export default ArtistEditorFooterLabel;

@ -1,4 +1,4 @@
.seasonFolder { .albumFolder {
composes: cell from 'Components/Table/Cells/TableRowCell.css'; composes: cell from 'Components/Table/Cells/TableRowCell.css';
width: 150px; width: 150px;

@ -9,9 +9,9 @@ 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 ArtistNameLink from 'Artist/ArtistNameLink';
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell'; import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
import styles from './SeriesEditorRow.css'; import styles from './ArtistEditorRow.css';
class SeriesEditorRow extends Component { class ArtistEditorRow extends Component {
// //
// Listeners // Listeners
@ -28,13 +28,12 @@ class SeriesEditorRow extends Component {
const { const {
id, id,
status, status,
titleSlug, nameSlug,
title, artistName,
monitored, monitored,
languageProfile, languageProfile,
qualityProfile, qualityProfile,
seriesType, albumFolder,
seasonFolder,
path, path,
tags, tags,
columns, columns,
@ -57,8 +56,8 @@ class SeriesEditorRow extends Component {
<TableRowCell className={styles.title}> <TableRowCell className={styles.title}>
<ArtistNameLink <ArtistNameLink
titleSlug={titleSlug} nameSlug={nameSlug}
title={title} artistName={artistName}
/> />
</TableRowCell> </TableRowCell>
@ -73,14 +72,10 @@ class SeriesEditorRow extends Component {
</TableRowCell> </TableRowCell>
} }
<TableRowCell> <TableRowCell className={styles.albumFolder}>
{titleCase(seriesType)}
</TableRowCell>
<TableRowCell className={styles.seasonFolder}>
<CheckInput <CheckInput
name="seasonFolder" name="albumFolder"
value={seasonFolder} value={albumFolder}
isDisabled={true} isDisabled={true}
onChange={this.onSeasonFolderChange} onChange={this.onSeasonFolderChange}
/> />
@ -100,16 +95,15 @@ class SeriesEditorRow extends Component {
} }
} }
SeriesEditorRow.propTypes = { ArtistEditorRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired, status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired, nameSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired, monitored: PropTypes.bool.isRequired,
languageProfile: PropTypes.object.isRequired, languageProfile: PropTypes.object.isRequired,
qualityProfile: PropTypes.object.isRequired, qualityProfile: PropTypes.object.isRequired,
seriesType: PropTypes.string.isRequired, albumFolder: PropTypes.bool.isRequired,
seasonFolder: PropTypes.bool.isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -117,4 +111,4 @@ SeriesEditorRow.propTypes = {
onSelectedChange: PropTypes.func.isRequired onSelectedChange: PropTypes.func.isRequired
}; };
export default SeriesEditorRow; export default ArtistEditorRow;

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

@ -51,19 +51,19 @@ class DeleteArtistModalContent extends Component {
return ( return (
<ModalContent onModalClose={onModalClose}> <ModalContent onModalClose={onModalClose}>
<ModalHeader> <ModalHeader>
Delete Selected Series Delete Selected Artist
</ModalHeader> </ModalHeader>
<ModalBody> <ModalBody>
<div> <div>
<FormGroup> <FormGroup>
<FormLabel>{`Delete Series Folder${series.length > 1 ? 's' : ''}`}</FormLabel> <FormLabel>{`Delete Artist Folder${series.length > 1 ? 's' : ''}`}</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="deleteFiles" name="deleteFiles"
value={deleteFiles} value={deleteFiles}
helpText={`Delete Series Folder${series.length > 1 ? 's' : ''} and all contents`} helpText={`Delete Artist Folder${series.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 ${series.length} selected series${deleteFiles ? ' and all contents' : ''}?`} {`Are you sure you want to delete ${series.length} selected artist${series.length > 1 ? 's' : ''}${deleteFiles ? ' and all contents' : ''}?`}
</div> </div>
<ul> <ul>
{ {
series.map((s) => { series.map((s) => {
return ( return (
<li key={s.title}> <li key={s.artistName}>
<span>{s.title}</span> <span>{s.artistName}</span>
{ {
deleteFiles && deleteFiles &&

@ -1,23 +1,23 @@
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 createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import { bulkDeleteArtist } from 'Store/Actions/seriesEditorActions'; import { bulkDeleteArtist } from 'Store/Actions/artistEditorActions';
import DeleteArtistModalContent from './DeleteArtistModalContent'; import DeleteArtistModalContent from './DeleteArtistModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { artistIds }) => artistIds, (state, { artistIds }) => artistIds,
createAllSeriesSelector(), createAllArtistSelector(),
(artistIds, allSeries) => { (artistIds, allSeries) => {
const selectedSeries = _.intersectionWith(allSeries, artistIds, (s, id) => { const selectedSeries = _.intersectionWith(allSeries, artistIds, (s, id) => {
return s.id === id; return s.id === id;
}); });
const sortedSeries = _.orderBy(selectedSeries, 'sortTitle'); const sortedArtist = _.orderBy(selectedSeries, 'sortName');
const series = _.map(sortedSeries, (s) => { const series = _.map(sortedArtist, (s) => {
return { return {
title: s.title, artistName: s.artistName,
path: s.path path: s.path
}; };
}); });

@ -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 OrganizeSeriesModalContentConnector from './OrganizeSeriesModalContentConnector'; import OrganizeArtistModalContentConnector from './OrganizeArtistModalContentConnector';
function OrganizeSeriesModal(props) { function OrganizeArtistModal(props) {
const { const {
isOpen, isOpen,
onModalClose, onModalClose,
@ -15,7 +15,7 @@ function OrganizeSeriesModal(props) {
isOpen={isOpen} isOpen={isOpen}
onModalClose={onModalClose} onModalClose={onModalClose}
> >
<OrganizeSeriesModalContentConnector <OrganizeArtistModalContentConnector
{...otherProps} {...otherProps}
onModalClose={onModalClose} onModalClose={onModalClose}
/> />
@ -23,9 +23,9 @@ function OrganizeSeriesModal(props) {
); );
} }
OrganizeSeriesModal.propTypes = { OrganizeArtistModal.propTypes = {
isOpen: PropTypes.bool.isRequired, isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };
export default OrganizeSeriesModal; export default OrganizeArtistModal;

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

@ -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 createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
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 OrganizeSeriesModalContent from './OrganizeSeriesModalContent'; import OrganizeArtistModalContent from './OrganizeArtistModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state, { artistIds }) => artistIds, (state, { artistIds }) => artistIds,
createAllSeriesSelector(), createAllArtistSelector(),
(artistIds, allSeries) => { (artistIds, allSeries) => {
const series = _.intersectionWith(allSeries, artistIds, (s, id) => { const series = _.intersectionWith(allSeries, artistIds, (s, id) => {
return s.id === id; return s.id === id;
}); });
const sortedSeries = _.orderBy(series, 'sortTitle'); const sortedArtist = _.orderBy(series, 'sortName');
const seriesTitles = _.map(sortedSeries, 'title'); const artistNames = _.map(sortedArtist, 'artistName');
return { return {
seriesTitles artistNames
}; };
} }
); );
@ -31,14 +31,14 @@ const mapDispatchToProps = {
executeCommand executeCommand
}; };
class OrganizeSeriesModalContentConnector extends Component { class OrganizeArtistModalContentConnector extends Component {
// //
// Listeners // Listeners
onOrganizeSeriesPress = () => { onOrganizeArtistPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.RENAME_SERIES, name: commandNames.RENAME_ARTIST,
artistIds: this.props.artistIds artistIds: this.props.artistIds
}); });
@ -50,18 +50,18 @@ class OrganizeSeriesModalContentConnector extends Component {
render(props) { render(props) {
return ( return (
<OrganizeSeriesModalContent <OrganizeArtistModalContent
{...this.props} {...this.props}
onOrganizeSeriesPress={this.onOrganizeSeriesPress} onOrganizeArtistPress={this.onOrganizeArtistPress}
/> />
); );
} }
} }
OrganizeSeriesModalContentConnector.propTypes = { OrganizeArtistModalContentConnector.propTypes = {
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired, artistIds: 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)(OrganizeSeriesModalContentConnector); export default connect(createMapStateToProps, mapDispatchToProps)(OrganizeArtistModalContentConnector);

@ -1,14 +1,14 @@
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 createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
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, { artistIds }) => artistIds, (state, { artistIds }) => artistIds,
createAllSeriesSelector(), createAllArtistSelector(),
createTagsSelector(), createTagsSelector(),
(artistIds, allSeries, tagList) => { (artistIds, allSeries, tagList) => {
const series = _.intersectionWith(allSeries, artistIds, (s, id) => { const series = _.intersectionWith(allSeries, artistIds, (s, id) => {

@ -168,7 +168,7 @@ class ArtistIndex extends Component {
onSortSelect, onSortSelect,
onFilterSelect, onFilterSelect,
onViewSelect, onViewSelect,
onRefreshSeriesPress, onRefreshArtistPress,
onRssSyncPress, onRssSyncPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -192,7 +192,7 @@ class ArtistIndex extends Component {
iconName={icons.REFRESH} iconName={icons.REFRESH}
spinningName={icons.REFRESH} spinningName={icons.REFRESH}
isSpinning={isRefreshingSeries} isSpinning={isRefreshingSeries}
onPress={onRefreshSeriesPress} onPress={onRefreshArtistPress}
/> />
<PageToolbarButton <PageToolbarButton
@ -257,7 +257,7 @@ class ArtistIndex extends Component {
{ {
!isFetching && !!error && !isFetching && !!error &&
<div>Unable to load series</div> <div>Unable to load artist</div>
} }
{ {
@ -318,7 +318,7 @@ ArtistIndex.propTypes = {
onSortSelect: PropTypes.func.isRequired, onSortSelect: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired, onFilterSelect: PropTypes.func.isRequired,
onViewSelect: PropTypes.func.isRequired, onViewSelect: PropTypes.func.isRequired,
onRefreshSeriesPress: PropTypes.func.isRequired, onRefreshArtistPress: PropTypes.func.isRequired,
onRssSyncPress: PropTypes.func.isRequired, onRssSyncPress: PropTypes.func.isRequired,
onScroll: PropTypes.func.isRequired onScroll: PropTypes.func.isRequired
}; };

@ -6,7 +6,7 @@ import { createSelector } from 'reselect';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import createCommandSelector from 'Store/Selectors/createCommandSelector'; import createCommandSelector from 'Store/Selectors/createCommandSelector';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import { fetchArtist } from 'Store/Actions/seriesActions'; import { fetchArtist } from 'Store/Actions/artistActions';
import scrollPositions from 'Store/scrollPositions'; import scrollPositions from 'Store/scrollPositions';
import { setArtistSort, setArtistFilter, setArtistView } from 'Store/Actions/artistIndexActions'; import { setArtistSort, setArtistFilter, setArtistView } from 'Store/Actions/artistIndexActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
@ -41,7 +41,7 @@ function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.series, (state) => state.series,
(state) => state.seriesIndex, (state) => state.seriesIndex,
createCommandSelector(commandNames.REFRESH_SERIES), createCommandSelector(commandNames.REFRESH_ARTIST),
createCommandSelector(commandNames.RSS_SYNC), createCommandSelector(commandNames.RSS_SYNC),
createDimensionsSelector(), createDimensionsSelector(),
(series, seriesIndex, isRefreshingSeries, isRssSyncExecuting, dimensionsState) => { (series, seriesIndex, isRefreshingSeries, isRssSyncExecuting, dimensionsState) => {
@ -113,9 +113,9 @@ class ArtistIndexConnector extends Component {
}); });
} }
onRefreshSeriesPress = () => { onRefreshArtistPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.REFRESH_SERIES name: commandNames.REFRESH_ARTIST
}); });
} }
@ -137,7 +137,7 @@ class ArtistIndexConnector extends Component {
onFilterSelect={this.onFilterSelect} onFilterSelect={this.onFilterSelect}
onViewSelect={this.onViewSelect} onViewSelect={this.onViewSelect}
onScroll={this.onScroll} onScroll={this.onScroll}
onRefreshSeriesPress={this.onRefreshSeriesPress} onRefreshArtistPress={this.onRefreshArtistPress}
onRssSyncPress={this.onRssSyncPress} onRssSyncPress={this.onRssSyncPress}
/> />
); );

@ -18,7 +18,7 @@ function createMapStateToProps() {
createCommandsSelector(), createCommandsSelector(),
(artistId, seasons, qualityProfile, languageProfile, commands) => { (artistId, seasons, qualityProfile, languageProfile, commands) => {
const isRefreshingSeries = _.some(commands, (command) => { const isRefreshingSeries = _.some(commands, (command) => {
return command.name === commandNames.REFRESH_SERIES && return command.name === commandNames.REFRESH_ARTIST &&
command.body.artistId === artistId; command.body.artistId === artistId;
}); });
@ -43,9 +43,9 @@ class ArtistIndexItemConnector extends Component {
// //
// Listeners // Listeners
onRefreshSeriesPress = () => { onRefreshArtistPress = () => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.REFRESH_SERIES, name: commandNames.REFRESH_ARTIST,
artistId: this.props.id artistId: this.props.id
}); });
} }
@ -62,7 +62,7 @@ class ArtistIndexItemConnector extends Component {
return ( return (
<ItemComponent <ItemComponent
{...otherProps} {...otherProps}
onRefreshSeriesPress={this.onRefreshSeriesPress} onRefreshArtistPress={this.onRefreshArtistPress}
/> />
); );
} }

@ -45,7 +45,7 @@ class ArtistIndexPoster extends Component {
}); });
} }
onDeleteSeriesModalClose = () => { onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false }); this.setState({ isDeleteArtistModalOpen: false });
} }
@ -74,7 +74,7 @@ class ArtistIndexPoster extends Component {
shortDateFormat, shortDateFormat,
timeFormat, timeFormat,
isRefreshingSeries, isRefreshingSeries,
onRefreshSeriesPress, onRefreshArtistPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -83,7 +83,7 @@ class ArtistIndexPoster extends Component {
isDeleteArtistModalOpen isDeleteArtistModalOpen
} = this.state; } = this.state;
const link = `/series/${nameSlug}`; const link = `/artist/${nameSlug}`;
const elementStyle = { const elementStyle = {
width: `${posterWidth}px`, width: `${posterWidth}px`,
@ -100,7 +100,7 @@ class ArtistIndexPoster extends Component {
name={icons.REFRESH} name={icons.REFRESH}
title="Refresh Artist" title="Refresh Artist"
isSpinning={isRefreshingSeries} isSpinning={isRefreshingSeries}
onPress={onRefreshSeriesPress} onPress={onRefreshArtistPress}
/> />
<IconButton <IconButton
@ -190,7 +190,7 @@ class ArtistIndexPoster extends Component {
<DeleteArtistModal <DeleteArtistModal
isOpen={isDeleteArtistModalOpen} isOpen={isDeleteArtistModalOpen}
artistId={id} artistId={id}
onModalClose={this.onDeleteSeriesModalClose} onModalClose={this.onDeleteArtistModalClose}
/> />
</div> </div>
</div> </div>
@ -219,7 +219,7 @@ ArtistIndexPoster.propTypes = {
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
isRefreshingSeries: PropTypes.bool.isRequired, isRefreshingSeries: PropTypes.bool.isRequired,
onRefreshSeriesPress: PropTypes.func.isRequired onRefreshArtistPress: PropTypes.func.isRequired
}; };
ArtistIndexPoster.defaultProps = { ArtistIndexPoster.defaultProps = {

@ -39,7 +39,7 @@ class ArtistIndexActionsCell extends Component {
}); });
} }
onDeleteSeriesModalClose = () => { onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false }); this.setState({ isDeleteArtistModalOpen: false });
} }
@ -50,7 +50,7 @@ class ArtistIndexActionsCell extends Component {
const { const {
id, id,
isRefreshingSeries, isRefreshingSeries,
onRefreshSeriesPress, onRefreshArtistPress,
...otherProps ...otherProps
} = this.props; } = this.props;
@ -67,7 +67,7 @@ class ArtistIndexActionsCell extends Component {
name={icons.REFRESH} name={icons.REFRESH}
title="Refresh Artist" title="Refresh Artist"
isSpinning={isRefreshingSeries} isSpinning={isRefreshingSeries}
onPress={onRefreshSeriesPress} onPress={onRefreshArtistPress}
/> />
<IconButton <IconButton
@ -86,7 +86,7 @@ class ArtistIndexActionsCell extends Component {
<DeleteArtistModal <DeleteArtistModal
isOpen={isDeleteArtistModalOpen} isOpen={isDeleteArtistModalOpen}
artistId={id} artistId={id}
onModalClose={this.onDeleteSeriesModalClose} onModalClose={this.onDeleteArtistModalClose}
/> />
</VirtualTableRowCell> </VirtualTableRowCell>
); );
@ -96,7 +96,7 @@ class ArtistIndexActionsCell extends Component {
ArtistIndexActionsCell.propTypes = { ArtistIndexActionsCell.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
isRefreshingSeries: PropTypes.bool.isRequired, isRefreshingSeries: PropTypes.bool.isRequired,
onRefreshSeriesPress: PropTypes.func.isRequired onRefreshArtistPress: PropTypes.func.isRequired
}; };
export default ArtistIndexActionsCell; export default ArtistIndexActionsCell;

@ -46,7 +46,7 @@ class ArtistIndexRow extends Component {
}); });
} }
onDeleteSeriesModalClose = () => { onDeleteArtistModalClose = () => {
this.setState({ isDeleteArtistModalOpen: false }); this.setState({ isDeleteArtistModalOpen: false });
} }
@ -83,7 +83,7 @@ class ArtistIndexRow extends Component {
// useSceneNumbering, // useSceneNumbering,
columns, columns,
isRefreshingSeries, isRefreshingSeries,
onRefreshSeriesPress onRefreshArtistPress
} = this.props; } = this.props;
const { const {
@ -321,7 +321,7 @@ class ArtistIndexRow extends Component {
name={icons.REFRESH} name={icons.REFRESH}
title="Refresh Artist" title="Refresh Artist"
isSpinning={isRefreshingSeries} isSpinning={isRefreshingSeries}
onPress={onRefreshSeriesPress} onPress={onRefreshArtistPress}
/> />
<IconButton <IconButton
@ -347,7 +347,7 @@ class ArtistIndexRow extends Component {
<DeleteArtistModal <DeleteArtistModal
isOpen={isDeleteArtistModalOpen} isOpen={isDeleteArtistModalOpen}
artistId={id} artistId={id}
onModalClose={this.onDeleteSeriesModalClose} onModalClose={this.onDeleteArtistModalClose}
/> />
</VirtualTableRow> </VirtualTableRow>
); );
@ -378,7 +378,7 @@ ArtistIndexRow.propTypes = {
// useSceneNumbering: PropTypes.bool.isRequired, // useSceneNumbering: PropTypes.bool.isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
isRefreshingSeries: PropTypes.bool.isRequired, isRefreshingSeries: PropTypes.bool.isRequired,
onRefreshSeriesPress: PropTypes.func.isRequired onRefreshArtistPress: PropTypes.func.isRequired
}; };
ArtistIndexRow.defaultProps = { ArtistIndexRow.defaultProps = {

@ -10,10 +10,10 @@ export const DOWNLOADED_EPSIODES_SCAN = 'DownloadedEpisodesScan';
export const EPISODE_SEARCH = 'EpisodeSearch'; export const EPISODE_SEARCH = 'EpisodeSearch';
export const INTERACTIVE_IMPORT = 'ManualImport'; export const INTERACTIVE_IMPORT = 'ManualImport';
export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch'; export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch';
export const REFRESH_SERIES = 'RefreshSeries'; export const REFRESH_ARTIST = 'RefreshArtist';
export const RENAME_FILES = 'RenameFiles'; export const RENAME_FILES = 'RenameFiles';
export const RENAME_SERIES = 'RenameSeries'; export const RENAME_ARTIST = 'RenameArtist';
export const RESET_API_KEY = 'ResetApiKey'; export const RESET_API_KEY = 'ResetApiKey';
export const RSS_SYNC = 'RssSync'; export const RSS_SYNC = 'RssSync';
export const SEASON_SEARCH = 'SeasonSearch'; export const SEASON_SEARCH = 'SeasonSearch';
export const SERIES_SEARCH = 'SeriesSearch'; export const ARTIST_SEARCH = 'ArtistSearch';

@ -18,9 +18,9 @@ function ErrorPage(props) {
if (!isLocalStorageSupported) { if (!isLocalStorageSupported) {
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.'; errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
} else if (seriesError) { } else if (seriesError) {
errorMessage = getErrorMessage(seriesError, 'Failed to load series from API'); errorMessage = getErrorMessage(seriesError, 'Failed to load artist from API');
} else if (tagsError) { } else if (tagsError) {
errorMessage = getErrorMessage(seriesError, 'Failed to load series from API'); errorMessage = getErrorMessage(seriesError, 'Failed to load artist from API');
} else if (qualityProfilesError) { } else if (qualityProfilesError) {
errorMessage = getErrorMessage(qualityProfilesError, 'Failed to load quality profiles from API'); errorMessage = getErrorMessage(qualityProfilesError, 'Failed to load quality profiles from API');
} else if (uiSettingsError) { } else if (uiSettingsError) {

@ -6,12 +6,12 @@ import jdu from 'jdu';
import { icons } from 'Helpers/Props'; import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts'; import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
import SeriesSearchResult from './SeriesSearchResult'; import ArtistSearchResult from './ArtistSearchResult';
import styles from './SeriesSearchInput.css'; import styles from './ArtistSearchInput.css';
const ADD_NEW_TYPE = 'addNew'; const ADD_NEW_TYPE = 'addNew';
class SeriesSearchInput extends Component { class ArtistSearchInput extends Component {
// //
// Lifecycle // Lifecycle
@ -28,7 +28,7 @@ class SeriesSearchInput extends Component {
} }
componentDidMount() { componentDidMount() {
this.props.bindShortcut(shortcuts.SERIES_SEARCH_INPUT.key, this.focusInput); this.props.bindShortcut(shortcuts.ARTIST_SEARCH_INPUT.key, this.focusInput);
} }
// //
@ -69,7 +69,7 @@ class SeriesSearchInput extends Component {
} }
return ( return (
<SeriesSearchResult <ArtistSearchResult
query={query} query={query}
{...item} {...item}
/> />
@ -78,7 +78,7 @@ class SeriesSearchInput extends Component {
goToSeries(series) { goToSeries(series) {
this.setState({ value: '' }); this.setState({ value: '' });
this.props.onGoToSeries(series.titleSlug); this.props.onGoToSeries(series.nameSlug);
} }
reset() { reset() {
@ -137,7 +137,7 @@ class SeriesSearchInput extends Component {
const suggestions = _.filter(this.props.series, (series) => { const suggestions = _.filter(this.props.series, (series) => {
// Check the title first and if there isn't a match fallback to the alternate titles // Check the title first and if there isn't a match fallback to the alternate titles
const titleMatch = jdu.replace(series.title).toLowerCase().contains(lowerCaseValue); const titleMatch = jdu.replace(series.artistName).toLowerCase().contains(lowerCaseValue);
return titleMatch || _.some(series.alternateTitles, (alternateTitle) => { return titleMatch || _.some(series.alternateTitles, (alternateTitle) => {
return jdu.replace(alternateTitle.title).toLowerCase().contains(lowerCaseValue); return jdu.replace(alternateTitle.title).toLowerCase().contains(lowerCaseValue);
@ -172,14 +172,14 @@ class SeriesSearchInput extends Component {
if (suggestions.length) { if (suggestions.length) {
suggestionGroups.push({ suggestionGroups.push({
title: 'Existing Series', title: 'Existing Artist',
suggestions suggestions
}); });
} }
if (suggestions.length <= 3) { if (suggestions.length <= 3) {
suggestionGroups.push({ suggestionGroups.push({
title: 'Add New Series', title: 'Add New Artist',
suggestions: [ suggestions: [
{ {
type: ADD_NEW_TYPE, type: ADD_NEW_TYPE,
@ -240,11 +240,11 @@ class SeriesSearchInput extends Component {
} }
} }
SeriesSearchInput.propTypes = { ArtistSearchInput.propTypes = {
series: PropTypes.arrayOf(PropTypes.object).isRequired, series: PropTypes.arrayOf(PropTypes.object).isRequired,
onGoToSeries: PropTypes.func.isRequired, onGoToSeries: PropTypes.func.isRequired,
onGoToAddNewArtist: PropTypes.func.isRequired, onGoToAddNewArtist: PropTypes.func.isRequired,
bindShortcut: PropTypes.func.isRequired bindShortcut: PropTypes.func.isRequired
}; };
export default keyboardShortcuts(SeriesSearchInput); export default keyboardShortcuts(ArtistSearchInput);

@ -2,15 +2,15 @@ import _ from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { push } from 'react-router-redux'; import { push } from 'react-router-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import SeriesSearchInput from './SeriesSearchInput'; import ArtistSearchInput from './ArtistSearchInput';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createAllSeriesSelector(), createAllArtistSelector(),
(series) => { (series) => {
return { return {
series: _.sortBy(series, 'sortTitle') series: _.sortBy(series, 'sortName')
}; };
} }
); );
@ -18,8 +18,8 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) { function createMapDispatchToProps(dispatch, props) {
return { return {
onGoToSeries(titleSlug) { onGoToSeries(nameSlug) {
dispatch(push(`${window.Sonarr.urlBase}/series/${titleSlug}`)); dispatch(push(`${window.Sonarr.urlBase}/artist/${nameSlug}`));
}, },
onGoToAddNewArtist(query) { onGoToAddNewArtist(query) {
@ -28,4 +28,4 @@ function createMapDispatchToProps(dispatch, props) {
}; };
} }
export default connect(createMapStateToProps, createMapDispatchToProps)(SeriesSearchInput); export default connect(createMapStateToProps, createMapDispatchToProps)(ArtistSearchInput);

@ -2,7 +2,7 @@ import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import ArtistPoster from 'Artist/ArtistPoster'; import ArtistPoster from 'Artist/ArtistPoster';
import styles from './SeriesSearchResult.css'; import styles from './ArtistSearchResult.css';
function getMatchingAlternateTile(alternateTitles, query) { function getMatchingAlternateTile(alternateTitles, query) {
return _.first(alternateTitles, (alternateTitle) => { return _.first(alternateTitles, (alternateTitle) => {
@ -10,18 +10,18 @@ function getMatchingAlternateTile(alternateTitles, query) {
}); });
} }
function SeriesSearchResult(props) { function ArtistSearchResult(props) {
const { const {
query, query,
title, artistName,
alternateTitles, // alternateTitles,
images images
} = props; } = props;
const index = title.toLowerCase().indexOf(query.toLowerCase()); const index = artistName.toLowerCase().indexOf(query.toLowerCase());
const alternateTitle = index === -1 ? // const alternateTitle = index === -1 ?
getMatchingAlternateTile(alternateTitles, query) : // getMatchingAlternateTile(alternateTitles, query) :
null; // null;
return ( return (
<div className={styles.result}> <div className={styles.result}>
@ -35,25 +35,25 @@ function SeriesSearchResult(props) {
<div className={styles.titles}> <div className={styles.titles}>
<div className={styles.title}> <div className={styles.title}>
{title} {artistName}
</div> </div>
{ {
!!alternateTitle && // !!alternateTitle &&
<div className={styles.alternateTitle}> // <div className={styles.alternateTitle}>
{alternateTitle.title} // {alternateTitle.title}
</div> // </div>
} }
</div> </div>
</div> </div>
); );
} }
SeriesSearchResult.propTypes = { ArtistSearchResult.propTypes = {
query: PropTypes.string.isRequired, query: PropTypes.string.isRequired,
title: PropTypes.string.isRequired, artistName: PropTypes.string.isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired, // alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
images: PropTypes.arrayOf(PropTypes.object).isRequired images: PropTypes.arrayOf(PropTypes.object).isRequired
}; };
export default SeriesSearchResult; export default ArtistSearchResult;

@ -4,7 +4,7 @@ import { icons } from 'Helpers/Props';
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts'; import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
import IconButton from 'Components/Link/IconButton'; import IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link'; import Link from 'Components/Link/Link';
import SeriesSearchInputConnector from './SeriesSearchInputConnector'; import ArtistSearchInputConnector from './ArtistSearchInputConnector';
import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector'; import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector';
import KeyboardShortcutsModal from './KeyboardShortcutsModal'; import KeyboardShortcutsModal from './KeyboardShortcutsModal';
import styles from './PageHeader.css'; import styles from './PageHeader.css';
@ -68,7 +68,7 @@ class PageHeader extends Component {
/> />
</div> </div>
<SeriesSearchInputConnector /> <ArtistSearchInputConnector />
<div className={styles.right}> <div className={styles.right}>
<IconButton <IconButton

@ -5,7 +5,7 @@ import { withRouter } from 'react-router-dom';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector'; import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions'; import { saveDimensions, setIsSidebarVisible } from 'Store/Actions/appActions';
import { fetchArtist } from 'Store/Actions/seriesActions'; import { fetchArtist } from 'Store/Actions/artistActions';
import { fetchTags } from 'Store/Actions/tagActions'; import { fetchTags } from 'Store/Actions/tagActions';
import { fetchQualityProfiles, fetchLanguageProfiles, fetchUISettings } from 'Store/Actions/settingsActions'; import { fetchQualityProfiles, fetchLanguageProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions'; import { fetchStatus } from 'Store/Actions/systemActions';

@ -17,7 +17,7 @@ function getIconName(name) {
return icons.SEARCH; return icons.SEARCH;
case 'Housekeeping': case 'Housekeeping':
return icons.HOUSEKEEPING; return icons.HOUSEKEEPING;
case 'RefreshSeries': case 'RefreshArtist':
return icons.REFRESH; return icons.REFRESH;
case 'RssSync': case 'RssSync':
return icons.RSS; return icons.RSS;

@ -34,11 +34,11 @@ const links = [
}, },
{ {
title: 'Mass Editor', title: 'Mass Editor',
to: '/serieseditor' to: '/artisteditor'
}, },
{ {
title: 'Album Studio', title: 'Album Studio',
to: '/seasonpass' to: '/albumstudio'
} }
] ]
}, },

@ -8,7 +8,7 @@ export const shortcuts = {
name: 'Open This Modal' name: 'Open This Modal'
}, },
SERIES_SEARCH_INPUT: { ARTIST_SEARCH_INPUT: {
key: 's', key: 's',
name: 'Focus Search Box' name: 'Focus Search Box'
}, },

@ -66,7 +66,7 @@ class EpisodeDetailsModalContent extends Component {
onModalClose onModalClose
} = this.props; } = this.props;
const seriesLink = `/series/${titleSlug}`; const seriesLink = `/artist/${titleSlug}`;
return ( return (
<ModalContent <ModalContent

@ -4,12 +4,12 @@ import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions'; import { updateInteractiveImportItem } from 'Store/Actions/interactiveImportActions';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import SelectArtistModalContent from './SelectArtistModalContent'; import SelectArtistModalContent from './SelectArtistModalContent';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
createAllSeriesSelector(), createAllArtistSelector(),
(items) => { (items) => {
return { return {
items items

@ -3,7 +3,7 @@
font-weight: bold; font-weight: bold;
} }
.episodeFormat { .trackFormat {
margin-left: 5px; margin-left: 5px;
font-family: $monoSpaceFontFamily; font-family: $monoSpaceFontFamily;
} }

@ -75,7 +75,7 @@ class OrganizePreviewModalContent extends Component {
error, error,
items, items,
renameTracks, renameTracks,
episodeFormat, trackFormat,
path, path,
onModalClose onModalClose
} = this.props; } = this.props;
@ -129,8 +129,8 @@ class OrganizePreviewModalContent extends Component {
<div> <div>
Naming pattern: Naming pattern:
<span className={styles.episodeFormat}> <span className={styles.trackFormat}>
{episodeFormat} {trackFormat}
</span> </span>
</div> </div>
</Alert> </Alert>
@ -140,11 +140,11 @@ class OrganizePreviewModalContent extends Component {
items.map((item) => { items.map((item) => {
return ( return (
<OrganizePreviewRow <OrganizePreviewRow
key={item.episodeFileId} key={item.trackFileId}
id={item.episodeFileId} id={item.trackFileId}
existingPath={item.existingPath} existingPath={item.existingPath}
newPath={item.newPath} newPath={item.newPath}
isSelected={selectedState[item.episodeFileId]} isSelected={selectedState[item.trackFileId]}
onSelectedChange={this.onSelectedChange} onSelectedChange={this.onSelectedChange}
/> />
); );
@ -192,7 +192,7 @@ OrganizePreviewModalContent.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
path: PropTypes.string.isRequired, path: PropTypes.string.isRequired,
renameTracks: PropTypes.bool, renameTracks: PropTypes.bool,
episodeFormat: PropTypes.string, trackFormat: PropTypes.string,
onOrganizePress: PropTypes.func.isRequired, onOrganizePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired onModalClose: PropTypes.func.isRequired
}; };

@ -20,7 +20,7 @@ function createMapStateToProps() {
props.isPopulated = organizePreview.isPopulated && naming.isPopulated; props.isPopulated = organizePreview.isPopulated && naming.isPopulated;
props.error = organizePreview.error || naming.error; props.error = organizePreview.error || naming.error;
props.renameTracks = naming.item.renameTracks; props.renameTracks = naming.item.renameTracks;
props.episodeFormat = naming.item[`${series.seriesType}EpisodeFormat`]; props.trackFormat = naming.item['standardTrackFormat'];
props.path = series.path; props.path = series.path;
return props; return props;
@ -41,13 +41,13 @@ class OrganizePreviewModalContentConnector extends Component {
componentDidMount() { componentDidMount() {
const { const {
seriesId, artistId,
seasonNumber albumId
} = this.props; } = this.props;
this.props.fetchOrganizePreview({ this.props.fetchOrganizePreview({
seriesId, artistId,
seasonNumber albumId
}); });
this.props.fetchNamingSettings(); this.props.fetchNamingSettings();
@ -59,7 +59,7 @@ class OrganizePreviewModalContentConnector extends Component {
onOrganizePress = (files) => { onOrganizePress = (files) => {
this.props.executeCommand({ this.props.executeCommand({
name: commandNames.RENAME_FILES, name: commandNames.RENAME_FILES,
seriesId: this.props.seriesId, artistId: this.props.artistId,
files files
}); });
@ -80,8 +80,8 @@ class OrganizePreviewModalContentConnector extends Component {
} }
OrganizePreviewModalContentConnector.propTypes = { OrganizePreviewModalContentConnector.propTypes = {
seriesId: PropTypes.number.isRequired, artistId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number, albumId: PropTypes.number,
fetchOrganizePreview: PropTypes.func.isRequired, fetchOrganizePreview: PropTypes.func.isRequired,
fetchNamingSettings: PropTypes.func.isRequired, fetchNamingSettings: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired, executeCommand: PropTypes.func.isRequired,

@ -59,10 +59,10 @@ export const TOGGLE_ARTIST_MONITORED = 'TOGGLE_ARTIST_MONITORED';
export const TOGGLE_ALBUM_MONITORED = 'TOGGLE_ALBUM_MONITORED'; export const TOGGLE_ALBUM_MONITORED = 'TOGGLE_ALBUM_MONITORED';
// //
// Series Editor // Artist Editor
export const SET_SERIES_EDITOR_SORT = 'SET_SERIES_EDITOR_SORT'; export const SET_ARTIST_EDITOR_SORT = 'SET_ARTIST_EDITOR_SORT';
export const SET_SERIES_EDITOR_FILTER = 'SET_SERIES_EDITOR_FILTER'; export const SET_ARTIST_EDITOR_FILTER = 'SET_ARTIST_EDITOR_FILTER';
export const SAVE_ARTIST_EDITOR = 'SAVE_ARTIST_EDITOR'; export const SAVE_ARTIST_EDITOR = 'SAVE_ARTIST_EDITOR';
export const BULK_DELETE_ARTIST = 'BULK_DELETE_ARTIST'; export const BULK_DELETE_ARTIST = 'BULK_DELETE_ARTIST';

@ -3,11 +3,11 @@ import $ from 'jquery';
import getMonitoringOptions from 'Utilities/Series/getMonitoringOptions'; import getMonitoringOptions from 'Utilities/Series/getMonitoringOptions';
import * as types from './actionTypes'; import * as types from './actionTypes';
import { set } from './baseActions'; import { set } from './baseActions';
import { fetchArtist } from './seriesActions'; import { fetchArtist } from './artistActions';
const section = 'seasonPass'; const section = 'albumStudio';
const seasonPassActionHandlers = { const albumStudioActionHandlers = {
[types.SAVE_SEASON_PASS]: function(payload) { [types.SAVE_SEASON_PASS]: function(payload) {
return function(dispatch, getState) { return function(dispatch, getState) {
const { const {
@ -50,7 +50,7 @@ const seasonPassActionHandlers = {
})); }));
const promise = $.ajax({ const promise = $.ajax({
url: '/seasonPass', url: '/albumStudio',
method: 'POST', method: 'POST',
data: JSON.stringify({ data: JSON.stringify({
series, series,
@ -80,4 +80,4 @@ const seasonPassActionHandlers = {
} }
}; };
export default seasonPassActionHandlers; export default albumStudioActionHandlers;

@ -0,0 +1,7 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import albumStudioActionHandlers from './albumStudioActionHandlers';
export const setAlbumStudioSort = createAction(types.SET_SEASON_PASS_SORT);
export const setAlbumStudioFilter = createAction(types.SET_SEASON_PASS_FILTER);
export const saveAlbumStudio = albumStudioActionHandlers[types.SAVE_SEASON_PASS];

@ -3,9 +3,9 @@ import { batchActions } from 'redux-batched-actions';
import * as types from './actionTypes'; import * as types from './actionTypes';
import { set, updateItem } from './baseActions'; import { set, updateItem } from './baseActions';
const section = 'seriesEditor'; const section = 'artistEditor';
const seriesEditorActionHandlers = { const artistEditorActionHandlers = {
[types.SAVE_ARTIST_EDITOR]: function(payload) { [types.SAVE_ARTIST_EDITOR]: function(payload) {
return function(dispatch, getState) { return function(dispatch, getState) {
dispatch(set({ dispatch(set({
@ -14,7 +14,7 @@ const seriesEditorActionHandlers = {
})); }));
const promise = $.ajax({ const promise = $.ajax({
url: '/series/editor', url: '/artist/editor',
method: 'PUT', method: 'PUT',
data: JSON.stringify(payload), data: JSON.stringify(payload),
dataType: 'json' dataType: 'json'
@ -56,7 +56,7 @@ const seriesEditorActionHandlers = {
})); }));
const promise = $.ajax({ const promise = $.ajax({
url: '/series/editor', url: '/artist/editor',
method: 'DELETE', method: 'DELETE',
data: JSON.stringify(payload), data: JSON.stringify(payload),
dataType: 'json' dataType: 'json'
@ -83,4 +83,4 @@ const seriesEditorActionHandlers = {
} }
}; };
export default seriesEditorActionHandlers; export default artistEditorActionHandlers;

@ -0,0 +1,8 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import artistEditorActionHandlers from './artistEditorActionHandlers';
export const setArtistEditorSort = createAction(types.SET_ARTIST_EDITOR_SORT);
export const setArtistEditorFilter = createAction(types.SET_ARTIST_EDITOR_FILTER);
export const saveArtistEditor = artistEditorActionHandlers[types.SAVE_ARTIST_EDITOR];
export const bulkDeleteArtist = artistEditorActionHandlers[types.BULK_DELETE_ARTIST];

@ -116,7 +116,7 @@ const importArtistActionHandlers = {
// Make sure we have a selected series and // Make sure we have a selected series and
// the same series hasn't been added yet. // the same series hasn't been added yet.
if (selectedSeries && !_.some(acc, { tvdbId: selectedSeries.tvdbId })) { if (selectedSeries && !_.some(acc, { foreignArtistId: selectedSeries.foreignArtistId })) {
const newSeries = getNewSeries(_.cloneDeep(selectedSeries), item); const newSeries = getNewSeries(_.cloneDeep(selectedSeries), item);
newSeries.path = item.path; newSeries.path = item.path;

@ -1,7 +0,0 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import seasonPassActionHandlers from './seasonPassActionHandlers';
export const setSeasonPassSort = createAction(types.SET_SEASON_PASS_SORT);
export const setSeasonPassFilter = createAction(types.SET_SEASON_PASS_FILTER);
export const saveSeasonPass = seasonPassActionHandlers[types.SAVE_SEASON_PASS];

@ -1,8 +0,0 @@
import { createAction } from 'redux-actions';
import * as types from './actionTypes';
import seriesEditorActionHandlers from './seriesEditorActionHandlers';
export const setSeriesEditorSort = createAction(types.SET_SERIES_EDITOR_SORT);
export const setSeriesEditorFilter = createAction(types.SET_SERIES_EDITOR_FILTER);
export const saveArtistEditor = seriesEditorActionHandlers[types.SAVE_ARTIST_EDITOR];
export const bulkDeleteArtist = seriesEditorActionHandlers[types.BULK_DELETE_ARTIST];

@ -3,8 +3,8 @@ import persistState from 'redux-localstorage';
import * as addArtistReducers from 'Store/Reducers/addArtistReducers'; import * as addArtistReducers from 'Store/Reducers/addArtistReducers';
import * as episodeReducers from 'Store/Reducers/episodeReducers'; import * as episodeReducers from 'Store/Reducers/episodeReducers';
import * as artistIndexReducers from 'Store/Reducers/artistIndexReducers'; import * as artistIndexReducers from 'Store/Reducers/artistIndexReducers';
import * as seriesEditorReducers from 'Store/Reducers/seriesEditorReducers'; import * as artistEditorReducers from 'Store/Reducers/artistEditorReducers';
import * as seasonPassReducers from 'Store/Reducers/seasonPassReducers'; import * as albumStudioReducers from 'Store/Reducers/albumStudioReducers';
import * as calendarReducers from 'Store/Reducers/calendarReducers'; import * as calendarReducers from 'Store/Reducers/calendarReducers';
import * as historyReducers from 'Store/Reducers/historyReducers'; import * as historyReducers from 'Store/Reducers/historyReducers';
import * as blacklistReducers from 'Store/Reducers/blacklistReducers'; import * as blacklistReducers from 'Store/Reducers/blacklistReducers';
@ -18,8 +18,8 @@ const reducers = [
addArtistReducers, addArtistReducers,
episodeReducers, episodeReducers,
artistIndexReducers, artistIndexReducers,
seriesEditorReducers, artistEditorReducers,
seasonPassReducers, albumStudioReducers,
calendarReducers, calendarReducers,
historyReducers, historyReducers,
blacklistReducers, blacklistReducers,

@ -18,16 +18,16 @@ export const defaultState = {
}; };
export const persistState = [ export const persistState = [
'seasonPass.sortKey', 'albumStudio.sortKey',
'seasonPass.sortDirection', 'albumStudio.sortDirection',
'seasonPass.filterKey', 'albumStudio.filterKey',
'seasonPass.filterValue', 'albumStudio.filterValue',
'seasonPass.filterType' 'albumStudio.filterType'
]; ];
const reducerSection = 'seasonPass'; const reducerSection = 'albumStudio';
const seasonPassReducers = handleActions({ const albumStudioReducers = handleActions({
[types.SET]: createSetReducer(reducerSection), [types.SET]: createSetReducer(reducerSection),
@ -36,4 +36,4 @@ const seasonPassReducers = handleActions({
}, defaultState); }, defaultState);
export default seasonPassReducers; export default albumStudioReducers;

@ -20,22 +20,22 @@ export const defaultState = {
}; };
export const persistState = [ export const persistState = [
'seriesEditor.sortKey', 'artistEditor.sortKey',
'seriesEditor.sortDirection', 'artistEditor.sortDirection',
'seriesEditor.filterKey', 'artistEditor.filterKey',
'seriesEditor.filterValue', 'artistEditor.filterValue',
'seriesEditor.filterType' 'artistEditor.filterType'
]; ];
const reducerSection = 'seriesEditor'; const reducerSection = 'artistEditor';
const seriesEditorReducers = handleActions({ const artistEditorReducers = handleActions({
[types.SET]: createSetReducer(reducerSection), [types.SET]: createSetReducer(reducerSection),
[types.SET_SERIES_EDITOR_SORT]: createSetClientSideCollectionSortReducer(reducerSection), [types.SET_ARTIST_EDITOR_SORT]: createSetClientSideCollectionSortReducer(reducerSection),
[types.SET_SERIES_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(reducerSection) [types.SET_ARTIST_EDITOR_FILTER]: createSetClientSideCollectionFilterReducer(reducerSection)
}, defaultState); }, defaultState);
export default seriesEditorReducers; export default artistEditorReducers;

@ -22,7 +22,7 @@ export const defaultState = {
const reducerSection = 'series'; const reducerSection = 'series';
const seriesReducers = handleActions({ const artistReducers = handleActions({
[types.SET]: createSetReducer(reducerSection), [types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection), [types.UPDATE]: createUpdateReducer(reducerSection),
@ -34,4 +34,4 @@ const seriesReducers = handleActions({
}, defaultState); }, defaultState);
export default seriesReducers; export default artistReducers;

@ -4,10 +4,10 @@ import { routerReducer } from 'react-router-redux';
import app, { defaultState as defaultappState } from './appReducers'; import app, { defaultState as defaultappState } from './appReducers';
import addArtist, { defaultState as defaultAddSeriesState } from './addArtistReducers'; import addArtist, { defaultState as defaultAddSeriesState } from './addArtistReducers';
import importArtist, { defaultState as defaultImportArtistState } from './importArtistReducers'; import importArtist, { defaultState as defaultImportArtistState } from './importArtistReducers';
import series, { defaultState as defaultSeriesState } from './seriesReducers'; import series, { defaultState as defaultSeriesState } from './artistReducers';
import seriesIndex, { defaultState as defaultSeriesIndexState } from './artistIndexReducers'; import seriesIndex, { defaultState as defaultSeriesIndexState } from './artistIndexReducers';
import seriesEditor, { defaultState as defaultSeriesEditorState } from './seriesEditorReducers'; import artistEditor, { defaultState as defaultArtistEditorState } from './artistEditorReducers';
import seasonPass, { defaultState as defaultSeasonPassState } from './seasonPassReducers'; import albumStudio, { defaultState as defaultAlbumStudioState } from './albumStudioReducers';
import calendar, { defaultState as defaultCalendarState } from './calendarReducers'; import calendar, { defaultState as defaultCalendarState } from './calendarReducers';
import history, { defaultState as defaultHistoryState } from './historyReducers'; import history, { defaultState as defaultHistoryState } from './historyReducers';
import queue, { defaultState as defaultQueueState } from './queueReducers'; import queue, { defaultState as defaultQueueState } from './queueReducers';
@ -34,8 +34,8 @@ export const defaultState = {
importArtist: defaultImportArtistState, importArtist: defaultImportArtistState,
series: defaultSeriesState, series: defaultSeriesState,
seriesIndex: defaultSeriesIndexState, seriesIndex: defaultSeriesIndexState,
seriesEditor: defaultSeriesEditorState, artistEditor: defaultArtistEditorState,
seasonPass: defaultSeasonPassState, albumStudio: defaultAlbumStudioState,
calendar: defaultCalendarState, calendar: defaultCalendarState,
history: defaultHistoryState, history: defaultHistoryState,
queue: defaultQueueState, queue: defaultQueueState,
@ -63,8 +63,8 @@ export default enableBatching(combineReducers({
importArtist, importArtist,
series, series,
seriesIndex, seriesIndex,
seriesEditor, artistEditor,
seasonPass, albumStudio,
calendar, calendar,
history, history,
queue, queue,

@ -14,7 +14,7 @@ export const defaultState = {
isFetching: false, isFetching: false,
isPopulated: false, isPopulated: false,
pageSize: 20, pageSize: 20,
sortKey: 'airDateUtc', sortKey: 'releaseDate',
sortDirection: sortDirections.DESCENDING, sortDirection: sortDirections.DESCENDING,
filterKey: 'monitored', filterKey: 'monitored',
filterValue: 'true', filterValue: 'true',
@ -23,32 +23,32 @@ export const defaultState = {
columns: [ columns: [
{ {
name: 'series.sortTitle', name: 'artist.sortName',
label: 'Series Title', label: 'Artist Name',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
// {
// name: 'episode',
// label: 'Episode',
// isVisible: true
// },
{ {
name: 'episode', name: 'albumTitle',
label: 'Episode', label: 'Album Title',
isVisible: true isVisible: true
}, },
{ {
name: 'episodeTitle', name: 'releaseDate',
label: 'Episode Title', label: 'Release Date',
isVisible: true
},
{
name: 'airDateUtc',
label: 'Air Date',
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{ // {
name: 'status', // name: 'status',
label: 'Status', // label: 'Status',
isVisible: true // isVisible: true
}, // },
{ {
name: 'actions', name: 'actions',
columnLabel: 'Actions', columnLabel: 'Actions',

@ -1,6 +1,6 @@
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
function createAllSeriesSelector() { function createAllArtistSelector() {
return createSelector( return createSelector(
(state) => state.series, (state) => state.series,
(series) => { (series) => {
@ -9,4 +9,4 @@ function createAllSeriesSelector() {
); );
} }
export default createAllSeriesSelector; export default createAllArtistSelector;

@ -1,11 +1,11 @@
import _ from 'lodash'; import _ from 'lodash';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllSeriesSelector from './createAllSeriesSelector'; import createAllArtistSelector from './createAllArtistSelector';
function createArtistSelector() { function createArtistSelector() {
return createSelector( return createSelector(
(state, { artistId }) => artistId, (state, { artistId }) => artistId,
createAllSeriesSelector(), createAllArtistSelector(),
(artistId, series) => { (artistId, series) => {
return _.find(series, { id: artistId }); return _.find(series, { id: artistId });
} }

@ -1,11 +1,11 @@
import _ from 'lodash'; import _ from 'lodash';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllSeriesSelector from './createAllSeriesSelector'; import createAllArtistSelector from './createAllArtistSelector';
function createExistingArtistSelector() { function createExistingArtistSelector() {
return createSelector( return createSelector(
(state, { foreignArtistId }) => foreignArtistId, (state, { foreignArtistId }) => foreignArtistId,
createAllSeriesSelector(), createAllArtistSelector(),
(foreignArtistId, series) => { (foreignArtistId, series) => {
return _.some(series, { foreignArtistId }); return _.some(series, { foreignArtistId });
} }

@ -1,17 +1,17 @@
import _ from 'lodash'; import _ from 'lodash';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllSeriesSelector from './createAllSeriesSelector'; import createAllArtistSelector from './createAllArtistSelector';
function createImportArtistItemSelector() { function createImportArtistItemSelector() {
return createSelector( return createSelector(
(state, { id }) => id, (state, { id }) => id,
(state) => state.addArtist, (state) => state.addArtist,
(state) => state.importArtist, (state) => state.importArtist,
createAllSeriesSelector(), createAllArtistSelector(),
(id, addArtist, importArtist, series) => { (id, addArtist, importArtist, series) => {
const item = _.find(importArtist.items, { id }) || {}; const item = _.find(importArtist.items, { id }) || {};
const selectedSeries = item && item.selectedSeries; const selectedSeries = item && item.selectedSeries;
const isExistingArtist = !!selectedSeries && _.some(series, { tvdbId: selectedSeries.tvdbId }); const isExistingArtist = !!selectedSeries && _.some(series, { foreignArtistId: selectedSeries.foreignArtistId });
return { return {
defaultMonitor: addArtist.defaults.monitor, defaultMonitor: addArtist.defaults.monitor,

@ -1,11 +1,11 @@
import _ from 'lodash'; import _ from 'lodash';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import createAllSeriesSelector from './createAllSeriesSelector'; import createAllArtistSelector from './createAllArtistSelector';
function createProfileInUseSelector(profileProp) { function createProfileInUseSelector(profileProp) {
return createSelector( return createSelector(
(state, { id }) => id, (state, { id }) => id,
createAllSeriesSelector(), createAllArtistSelector(),
(id, series) => { (id, series) => {
if (!id) { if (!id) {
return false; return false;

@ -32,8 +32,8 @@ function getInternalLink(source) {
case 'RootFolderCheck': case 'RootFolderCheck':
return ( return (
<div> <div>
<Link to="/serieseditor"> <Link to="/artisteditor">
Series Editor Artist Editor
</Link> </Link>
</div> </div>
); );

@ -108,7 +108,7 @@ class CutoffUnmet extends Component {
items, items,
columns, columns,
totalRecords, totalRecords,
isSearchingForEpisodes, isSearchingForAlbums,
isSearchingForCutoffUnmetEpisodes, isSearchingForCutoffUnmetEpisodes,
isSaving, isSaving,
filterKey, filterKey,
@ -133,7 +133,7 @@ class CutoffUnmet extends Component {
label="Search Selected" label="Search Selected"
iconName={icons.SEARCH} iconName={icons.SEARCH}
isDisabled={!itemsSelected} isDisabled={!itemsSelected}
isSpinning={isSearchingForEpisodes} isSpinning={isSearchingForAlbums}
onPress={this.onSearchSelectedPress} onPress={this.onSearchSelectedPress}
/> />
@ -271,7 +271,7 @@ CutoffUnmet.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
isSearchingForEpisodes: PropTypes.bool.isRequired, isSearchingForAlbums: PropTypes.bool.isRequired,
isSearchingForCutoffUnmetEpisodes: PropTypes.bool.isRequired, isSearchingForCutoffUnmetEpisodes: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
filterKey: PropTypes.string, filterKey: PropTypes.string,

@ -18,11 +18,11 @@ function createMapStateToProps() {
(state) => state.wanted.cutoffUnmet, (state) => state.wanted.cutoffUnmet,
createCommandsSelector(), createCommandsSelector(),
(cutoffUnmet, commands) => { (cutoffUnmet, commands) => {
const isSearchingForEpisodes = _.some(commands, { name: commandNames.EPISODE_SEARCH }); const isSearchingForAlbums = _.some(commands, { name: commandNames.EPISODE_SEARCH });
const isSearchingForCutoffUnmetEpisodes = _.some(commands, { name: commandNames.CUTOFF_UNMET_EPISODE_SEARCH }); const isSearchingForCutoffUnmetEpisodes = _.some(commands, { name: commandNames.CUTOFF_UNMET_EPISODE_SEARCH });
return { return {
isSearchingForEpisodes, isSearchingForAlbums,
isSearchingForCutoffUnmetEpisodes, isSearchingForCutoffUnmetEpisodes,
isSaving: _.some(cutoffUnmet.items, { isSaving: true }), isSaving: _.some(cutoffUnmet.items, { isSaving: true }),
...cutoffUnmet ...cutoffUnmet

@ -117,8 +117,8 @@ class Missing extends Component {
items, items,
columns, columns,
totalRecords, totalRecords,
isSearchingForEpisodes, isSearchingForAlbums,
isSearchingForMissingEpisodes, isSearchingForMissingAlbums,
isSaving, isSaving,
filterKey, filterKey,
filterValue, filterValue,
@ -143,7 +143,7 @@ class Missing extends Component {
label="Search Selected" label="Search Selected"
iconName={icons.SEARCH} iconName={icons.SEARCH}
isDisabled={!itemsSelected} isDisabled={!itemsSelected}
isSpinning={isSearchingForEpisodes} isSpinning={isSearchingForAlbums}
onPress={this.onSearchSelectedPress} onPress={this.onSearchSelectedPress}
/> />
@ -160,7 +160,7 @@ class Missing extends Component {
<PageToolbarButton <PageToolbarButton
label="Search All" label="Search All"
iconName={icons.SEARCH} iconName={icons.SEARCH}
isSpinning={isSearchingForMissingEpisodes} isSpinning={isSearchingForMissingAlbums}
onPress={this.onSearchAllMissingPress} onPress={this.onSearchAllMissingPress}
/> />
@ -258,11 +258,11 @@ class Missing extends Component {
<ConfirmModal <ConfirmModal
isOpen={isConfirmSearchAllMissingModalOpen} isOpen={isConfirmSearchAllMissingModalOpen}
kind={kinds.DANGER} kind={kinds.DANGER}
title="Search for all missing episodes" title="Search for all missing albums"
message={ message={
<div> <div>
<div> <div>
Are you sure you want to search for all {totalRecords} missing episodes? Are you sure you want to search for all {totalRecords} missing albums?
</div> </div>
<div> <div>
This cannot be cancelled once started without restarting Lidarr. This cannot be cancelled once started without restarting Lidarr.
@ -293,8 +293,8 @@ Missing.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,
totalRecords: PropTypes.number, totalRecords: PropTypes.number,
isSearchingForEpisodes: PropTypes.bool.isRequired, isSearchingForAlbums: PropTypes.bool.isRequired,
isSearchingForMissingEpisodes: PropTypes.bool.isRequired, isSearchingForMissingAlbums: PropTypes.bool.isRequired,
isSaving: PropTypes.bool.isRequired, isSaving: PropTypes.bool.isRequired,
filterKey: PropTypes.string, filterKey: PropTypes.string,
filterValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]), filterValue: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]),

@ -17,12 +17,12 @@ function createMapStateToProps() {
(state) => state.wanted.missing, (state) => state.wanted.missing,
createCommandsSelector(), createCommandsSelector(),
(missing, commands) => { (missing, commands) => {
const isSearchingForEpisodes = _.some(commands, { name: commandNames.EPISODE_SEARCH }); const isSearchingForAlbums = _.some(commands, { name: commandNames.EPISODE_SEARCH });
const isSearchingForMissingEpisodes = _.some(commands, { name: commandNames.MISSING_EPISODE_SEARCH }); const isSearchingForMissingAlbums = _.some(commands, { name: commandNames.MISSING_EPISODE_SEARCH });
return { return {
isSearchingForEpisodes, isSearchingForAlbums,
isSearchingForMissingEpisodes, isSearchingForMissingAlbums,
isSaving: _.some(missing.items, { isSaving: true }), isSaving: _.some(missing.items, { isSaving: true }),
...missing ...missing
}; };

@ -15,15 +15,15 @@ import styles from './MissingRow.css';
function MissingRow(props) { function MissingRow(props) {
const { const {
id, id,
episodeFileId, // episodeFileId,
series, artist,
seasonNumber, // seasonNumber,
episodeNumber, // episodeNumber,
absoluteEpisodeNumber, // absoluteEpisodeNumber,
sceneSeasonNumber, // sceneSeasonNumber,
sceneEpisodeNumber, // sceneEpisodeNumber,
sceneAbsoluteEpisodeNumber, // sceneAbsoluteEpisodeNumber,
airDateUtc, releaseDate,
title, title,
isSelected, isSelected,
columns, columns,
@ -49,42 +49,42 @@ function MissingRow(props) {
return null; return null;
} }
if (name === 'series.sortTitle') { if (name === 'artist.sortName') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
<ArtistNameLink <ArtistNameLink
titleSlug={series.titleSlug} nameSlug={artist.nameSlug}
title={series.title} artistName={artist.artistName}
/> />
</TableRowCell> </TableRowCell>
); );
} }
if (name === 'episode') { // if (name === 'episode') {
return ( // return (
<TableRowCell // <TableRowCell
key={name} // key={name}
className={styles.episode} // className={styles.episode}
> // >
<SeasonEpisodeNumber // <SeasonEpisodeNumber
seasonNumber={seasonNumber} // seasonNumber={seasonNumber}
episodeNumber={episodeNumber} // episodeNumber={episodeNumber}
absoluteEpisodeNumber={absoluteEpisodeNumber} // absoluteEpisodeNumber={absoluteEpisodeNumber}
seriesType={series.seriesType} // seriesType={series.seriesType}
sceneSeasonNumber={sceneSeasonNumber} // sceneSeasonNumber={sceneSeasonNumber}
sceneEpisodeNumber={sceneEpisodeNumber} // sceneEpisodeNumber={sceneEpisodeNumber}
sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber} // sceneAbsoluteEpisodeNumber={sceneAbsoluteEpisodeNumber}
/> // />
</TableRowCell> // </TableRowCell>
); // );
} // }
if (name === 'episodeTitle') { if (name === 'albumTitle') {
return ( return (
<TableRowCell key={name}> <TableRowCell key={name}>
<EpisodeTitleLink <EpisodeTitleLink
episodeId={id} episodeId={id}
artistId={series.id} artistId={artist.id}
episodeEntity={episodeEntities.WANTED_MISSING} episodeEntity={episodeEntities.WANTED_MISSING}
episodeTitle={title} episodeTitle={title}
showOpenSeriesButton={true} showOpenSeriesButton={true}
@ -93,36 +93,36 @@ function MissingRow(props) {
); );
} }
if (name === 'airDateUtc') { if (name === 'releaseDate') {
return ( return (
<RelativeDateCellConnector <RelativeDateCellConnector
key={name} key={name}
date={airDateUtc} date={releaseDate}
/> />
); );
} }
if (name === 'status') { // if (name === 'status') {
return ( // return (
<TableRowCell // <TableRowCell
key={name} // key={name}
className={styles.status} // className={styles.status}
> // >
<EpisodeStatusConnector // <EpisodeStatusConnector
episodeId={id} // episodeId={id}
episodeFileId={episodeFileId} // episodeFileId={episodeFileId}
episodeEntity={episodeEntities.WANTED_MISSING} // episodeEntity={episodeEntities.WANTED_MISSING}
/> // />
</TableRowCell> // </TableRowCell>
); // );
} // }
if (name === 'actions') { if (name === 'actions') {
return ( return (
<EpisodeSearchCellConnector <EpisodeSearchCellConnector
key={name} key={name}
episodeId={id} episodeId={id}
artistId={series.id} artistId={artist.id}
episodeTitle={title} episodeTitle={title}
episodeEntity={episodeEntities.WANTED_MISSING} episodeEntity={episodeEntities.WANTED_MISSING}
showOpenSeriesButton={true} showOpenSeriesButton={true}
@ -137,15 +137,15 @@ function MissingRow(props) {
MissingRow.propTypes = { MissingRow.propTypes = {
id: PropTypes.number.isRequired, id: PropTypes.number.isRequired,
episodeFileId: PropTypes.number, // episodeFileId: PropTypes.number,
series: PropTypes.object.isRequired, artist: PropTypes.object.isRequired,
seasonNumber: PropTypes.number.isRequired, // seasonNumber: PropTypes.number.isRequired,
episodeNumber: PropTypes.number.isRequired, // episodeNumber: PropTypes.number.isRequired,
absoluteEpisodeNumber: PropTypes.number, // absoluteEpisodeNumber: PropTypes.number,
sceneSeasonNumber: PropTypes.number, // sceneSeasonNumber: PropTypes.number,
sceneEpisodeNumber: PropTypes.number, // sceneEpisodeNumber: PropTypes.number,
sceneAbsoluteEpisodeNumber: PropTypes.number, // sceneAbsoluteEpisodeNumber: PropTypes.number,
airDateUtc: PropTypes.string.isRequired, releaseDate: PropTypes.string.isRequired,
title: PropTypes.string.isRequired, title: PropTypes.string.isRequired,
isSelected: PropTypes.bool, isSelected: PropTypes.bool,
columns: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired,

@ -14,6 +14,7 @@ using NzbDrone.Core.Music;
using NzbDrone.Core.Music.Events; using NzbDrone.Core.Music.Events;
using NzbDrone.Core.Validation; using NzbDrone.Core.Validation;
using NzbDrone.Core.Validation.Paths; using NzbDrone.Core.Validation.Paths;
using Lidarr.Api.V3.Albums;
using NzbDrone.SignalR; using NzbDrone.SignalR;
using Lidarr.Http; using Lidarr.Http;
using Lidarr.Http.Extensions; using Lidarr.Http.Extensions;
@ -35,12 +36,14 @@ namespace Lidarr.Api.V3.Artist
private readonly IAddArtistService _addArtistService; private readonly IAddArtistService _addArtistService;
private readonly IArtistStatisticsService _artistStatisticsService; private readonly IArtistStatisticsService _artistStatisticsService;
private readonly IMapCoversToLocal _coverMapper; private readonly IMapCoversToLocal _coverMapper;
private readonly IAlbumService _albumService;
public ArtistModule(IBroadcastSignalRMessage signalRBroadcaster, public ArtistModule(IBroadcastSignalRMessage signalRBroadcaster,
IArtistService artistService, IArtistService artistService,
IAddArtistService addArtistService, IAddArtistService addArtistService,
IArtistStatisticsService artistStatisticsService, IArtistStatisticsService artistStatisticsService,
IMapCoversToLocal coverMapper, IMapCoversToLocal coverMapper,
IAlbumService albumService,
RootFolderValidator rootFolderValidator, RootFolderValidator rootFolderValidator,
ArtistPathValidator artistPathValidator, ArtistPathValidator artistPathValidator,
ArtistExistsValidator artistExistsValidator, ArtistExistsValidator artistExistsValidator,
@ -55,6 +58,7 @@ namespace Lidarr.Api.V3.Artist
_artistStatisticsService = artistStatisticsService; _artistStatisticsService = artistStatisticsService;
_coverMapper = coverMapper; _coverMapper = coverMapper;
_albumService = albumService;
GetResourceAll = AllArtists; GetResourceAll = AllArtists;
GetResourceById = GetArtist; GetResourceById = GetArtist;
@ -95,6 +99,7 @@ namespace Lidarr.Api.V3.Artist
var resource = artist.ToResource(); var resource = artist.ToResource();
MapCoversToLocal(resource); MapCoversToLocal(resource);
MapAlbums(resource);
FetchAndLinkArtistStatistics(resource); FetchAndLinkArtistStatistics(resource);
//PopulateAlternateTitles(resource); //PopulateAlternateTitles(resource);
@ -107,6 +112,7 @@ namespace Lidarr.Api.V3.Artist
var artistsResources = _artistService.GetAllArtists().ToResource(); var artistsResources = _artistService.GetAllArtists().ToResource();
MapCoversToLocal(artistsResources.ToArray()); MapCoversToLocal(artistsResources.ToArray());
MapAlbums(artistsResources.ToArray());
LinkArtistStatistics(artistsResources, artistStats); LinkArtistStatistics(artistsResources, artistStats);
//PopulateAlternateTitles(seriesResources); //PopulateAlternateTitles(seriesResources);
@ -144,6 +150,14 @@ namespace Lidarr.Api.V3.Artist
} }
} }
private void MapAlbums(params ArtistResource[] artists)
{
foreach (var artistResource in artists)
{
artistResource.Albums = _albumService.GetAlbumsByArtist(artistResource.Id).ToResource();
}
}
private void FetchAndLinkArtistStatistics(ArtistResource resource) private void FetchAndLinkArtistStatistics(ArtistResource resource)
{ {
LinkArtistStatistics(resource, _artistStatisticsService.ArtistStatistics(resource.Id)); LinkArtistStatistics(resource, _artistStatisticsService.ArtistStatistics(resource.Id));
@ -168,13 +182,13 @@ namespace Lidarr.Api.V3.Artist
resource.SizeOnDisk = artistStatistics.SizeOnDisk; resource.SizeOnDisk = artistStatistics.SizeOnDisk;
resource.AlbumCount = artistStatistics.AlbumCount; resource.AlbumCount = artistStatistics.AlbumCount;
//if (seriesStatistics.AlbumStatistics != null) if (artistStatistics.AlbumStatistics != null)
//{ {
// foreach (var album in resource.Albums) foreach (var album in resource.Albums)
// { {
// album.Statistics = seriesStatistics.AlbumStatistics.SingleOrDefault(s => s.AlbumId == album.Id).ToResource(); album.Statistics = artistStatistics.AlbumStatistics.SingleOrDefault(s => s.AlbumId == album.Id).ToResource();
// } }
//} }
} }
//private void PopulateAlternateTitles(List<ArtistResource> resources) //private void PopulateAlternateTitles(List<ArtistResource> resources)

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using NzbDrone.Core.MediaCover; using NzbDrone.Core.MediaCover;
using NzbDrone.Core.Music; using NzbDrone.Core.Music;
using Lidarr.Api.V3.Albums;
using Lidarr.Http.REST; using Lidarr.Http.REST;
namespace Lidarr.Api.V3.Artist namespace Lidarr.Api.V3.Artist
@ -97,7 +98,7 @@ namespace Lidarr.Api.V3.Artist
public List<Member> Members { get; set; } public List<Member> Members { get; set; }
public string RemotePoster { get; set; } public string RemotePoster { get; set; }
//public List<AlbumResource> Albums { get; set; } public List<AlbumResource> Albums { get; set; }
//View & Edit //View & Edit
@ -149,7 +150,7 @@ namespace Lidarr.Api.V3.Artist
//AirTime = model.AirTime, //AirTime = model.AirTime,
Images = model.Images, Images = model.Images,
//Albums = model.Albums.ToResource(), Albums = model.Albums.ToResource(),
//Year = model.Year, //Year = model.Year,
Path = model.Path, Path = model.Path,

@ -21,9 +21,9 @@ namespace Lidarr.Api.V3.Tracks
{ {
int artistId; int artistId;
if (Request.Query.SeriesId.HasValue) if (Request.Query.ArtistId.HasValue)
{ {
artistId = (int)Request.Query.SeriesId; artistId = (int)Request.Query.ArtistId;
} }
else else

@ -60,10 +60,10 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})", public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex ArtistNameRegex = new Regex(@"(?<token>\{(?:Artist)(?<separator>[- ._])(Clean)?Name(The)\})", public static readonly Regex ArtistNameRegex = new Regex(@"(?<token>\{(?:Artist)(?<separator>[- ._])(Clean)?Name(The)?\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
public static readonly Regex AlbumTitleRegex = new Regex(@"(?<token>\{(?:Album)(?<separator>[- ._])(Clean)?Title(The)\})", public static readonly Regex AlbumTitleRegex = new Regex(@"(?<token>\{(?:Album)(?<separator>[- ._])(Clean)?Title(The)?\})",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled); private static readonly Regex FileNameCleanupRegex = new Regex(@"([- ._])(\1)+", RegexOptions.Compiled);

@ -1,4 +1,4 @@
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using FluentValidation; using FluentValidation;
using FluentValidation.Validators; using FluentValidation.Validators;
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Organizer
public static IRuleBuilderOptions<T, string> ValidAlbumFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder) public static IRuleBuilderOptions<T, string> ValidAlbumFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{ {
ruleBuilder.SetValidator(new NotEmptyValidator(null)); ruleBuilder.SetValidator(new NotEmptyValidator(null));
return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album name"); return ruleBuilder.SetValidator(new RegularExpressionValidator(FileNameBuilder.AlbumTitleRegex)).WithMessage("Must contain Album title");
} }
} }

Loading…
Cancel
Save