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

This commit is contained in:
Qstick 2017-09-11 23:11:32 -04:00
parent 456ead09da
commit 0054226307
93 changed files with 590 additions and 603 deletions

View File

@ -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) {

View File

@ -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 });

View File

@ -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.

View File

@ -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,

View File

@ -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);
} }
// //

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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}
/> />

View File

@ -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}>

View File

@ -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() {

View File

@ -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,

View File

@ -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,

View File

@ -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
}; };

View File

@ -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
}; };

View File

@ -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';

View File

@ -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() {

View File

@ -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;

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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 &&

View File

@ -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
}; };
}); });

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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) => {

View File

@ -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
}; };

View File

@ -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}
/> />
); );

View File

@ -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}
/> />
); );
} }

View File

@ -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 = {

View File

@ -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;

View File

@ -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 = {

View File

@ -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';

View File

@ -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) {

View File

@ -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);

View File

@ -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);

View File

@ -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;

View File

@ -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

View File

@ -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';

View File

@ -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;

View File

@ -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'
} }
] ]
}, },

View File

@ -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'
}, },

View File

@ -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

View File

@ -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

View File

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

View File

@ -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
}; };

View File

@ -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,

View File

@ -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';

View File

@ -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;

View File

@ -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];

View File

@ -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;

View File

@ -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];

View File

@ -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;

View File

@ -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];

View File

@ -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];

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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,

View File

@ -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',

View File

@ -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;

View File

@ -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 });
} }

View File

@ -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 });
} }

View File

@ -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,

View File

@ -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;

View File

@ -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>
); );

View File

@ -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,

View File

@ -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

View File

@ -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]),

View File

@ -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
}; };

View File

@ -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,

View File

@ -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)

View File

@ -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,

View File

@ -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

View File

@ -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);

View File

@ -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");
} }
} }