[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
} = this.props;
const linkProps = isExistingArtist ? { to: `/series/${nameSlug}` } : { onPress: this.onPress };
const linkProps = isExistingArtist ? { to: `/artist/${nameSlug}` } : { onPress: this.onPress };
let seasons = '1 Season';
if (seasonCount > 1) {

View File

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

View File

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

View File

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

View File

@ -99,10 +99,10 @@ class ImportArtistSelectArtist extends Component {
});
}
onSeriesSelect = (tvdbId) => {
onSeriesSelect = (foreignArtistId) => {
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 TableBody from 'Components/Table/TableBody';
import NoArtist from 'Artist/NoArtist';
import SeasonPassRowConnector from './SeasonPassRowConnector';
import SeasonPassFooter from './SeasonPassFooter';
import AlbumStudioRowConnector from './AlbumStudioRowConnector';
import AlbumStudioFooter from './AlbumStudioFooter';
const columns = [
{
@ -24,8 +24,8 @@ const columns = [
isVisible: true
},
{
name: 'sortTitle',
label: 'Title',
name: 'sortName',
label: 'Name',
isSortable: true,
isVisible: true
},
@ -34,14 +34,14 @@ const columns = [
isVisible: true
},
{
name: 'seasonCount',
label: 'Seasons',
name: 'albumCount',
label: 'Albums',
isSortable: true,
isVisible: true
}
];
class SeasonPass extends Component {
class AlbumStudio extends Component {
//
// Lifecycle
@ -121,7 +121,7 @@ class SeasonPass extends Component {
} = this.state;
return (
<PageContent title="Season Pass">
<PageContent title="Album Studio">
<PageToolbar>
<PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}>
@ -207,7 +207,7 @@ class SeasonPass extends Component {
{
items.map((item) => {
return (
<SeasonPassRowConnector
<AlbumStudioRowConnector
key={item.id}
artistId={item.id}
isSelected={selectedState[item.id]}
@ -227,7 +227,7 @@ class SeasonPass extends Component {
}
</PageContentBodyConnector>
<SeasonPassFooter
<AlbumStudioFooter
selectedCount={this.getSelectedIds().length}
isSaving={isSaving}
saveError={saveError}
@ -238,7 +238,7 @@ class SeasonPass extends Component {
}
}
SeasonPass.propTypes = {
AlbumStudio.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
@ -254,4 +254,4 @@ SeasonPass.propTypes = {
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 padNumber from 'Utilities/Number/padNumber';
import MonitorToggleButton from 'Components/MonitorToggleButton';
import styles from './SeasonPassSeason.css';
import styles from './AlbumStudioAlbum.css';
class SeasonPassSeason extends Component {
class AlbumStudioAlbum extends Component {
//
// Listeners
onSeasonMonitoredPress = () => {
const {
seasonNumber,
id,
monitored
} = this.props;
this.props.onSeasonMonitoredPress(seasonNumber, !monitored);
this.props.onSeasonMonitoredPress(id, !monitored);
}
//
@ -24,16 +24,17 @@ class SeasonPassSeason extends Component {
render() {
const {
seasonNumber,
id,
title,
monitored,
statistics,
isSaving
} = this.props;
const {
episodeFileCount,
totalEpisodeCount,
percentOfEpisodes
trackFileCount,
totalTrackCount,
percentOfTracks
} = statistics;
return (
@ -47,7 +48,7 @@ class SeasonPassSeason extends Component {
<span>
{
seasonNumber === 0 ? 'Specials' : `S${padNumber(seasonNumber, 2)}`
`${title}`
}
</span>
</div>
@ -55,12 +56,12 @@ class SeasonPassSeason extends Component {
<div
className={classNames(
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>
@ -68,21 +69,22 @@ class SeasonPassSeason extends Component {
}
}
SeasonPassSeason.propTypes = {
seasonNumber: PropTypes.number.isRequired,
AlbumStudioAlbum.propTypes = {
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
statistics: PropTypes.object.isRequired,
isSaving: PropTypes.bool.isRequired,
onSeasonMonitoredPress: PropTypes.func.isRequired
};
SeasonPassSeason.defaultProps = {
AlbumStudioAlbum.defaultProps = {
isSaving: false,
statistics: {
episodeFileCount: 0,
totalEpisodeCount: 0,
percentOfEpisodes: 0
trackFileCount: 0,
totalTrackCount: 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 connectSection from 'Store/connectSection';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import { setSeasonPassSort, setSeasonPassFilter, saveSeasonPass } from 'Store/Actions/seasonPassActions';
import SeasonPass from './SeasonPass';
import { setAlbumStudioSort, setAlbumStudioFilter, saveAlbumStudio } from 'Store/Actions/albumStudioActions';
import AlbumStudio from './AlbumStudio';
function createMapStateToProps() {
return createSelector(
@ -18,26 +18,26 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
setSeasonPassSort,
setSeasonPassFilter,
saveSeasonPass
setAlbumStudioSort,
setAlbumStudioFilter,
saveAlbumStudio
};
class SeasonPassConnector extends Component {
class AlbumStudioConnector extends Component {
//
// Listeners
onSortPress = (sortKey) => {
this.props.setSeasonPassSort({ sortKey });
this.props.setAlbumStudioSort({ sortKey });
}
onFilterSelect = (filterKey, filterValue, filterType) => {
this.props.setSeasonPassFilter({ filterKey, filterValue, filterType });
this.props.setAlbumStudioFilter({ filterKey, filterValue, filterType });
}
onUpdateSelectedPress = (payload) => {
this.props.saveSeasonPass(payload);
this.props.saveAlbumStudio(payload);
}
//
@ -45,7 +45,7 @@ class SeasonPassConnector extends Component {
render() {
return (
<SeasonPass
<AlbumStudio
{...this.props}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
@ -55,10 +55,10 @@ class SeasonPassConnector extends Component {
}
}
SeasonPassConnector.propTypes = {
setSeasonPassSort: PropTypes.func.isRequired,
setSeasonPassFilter: PropTypes.func.isRequired,
saveSeasonPass: PropTypes.func.isRequired
AlbumStudioConnector.propTypes = {
setAlbumStudioSort: PropTypes.func.isRequired,
setAlbumStudioFilter: PropTypes.func.isRequired,
saveAlbumStudio: PropTypes.func.isRequired
};
export default connectSection(
@ -66,5 +66,5 @@ export default connectSection(
mapDispatchToProps,
undefined,
undefined,
{ section: 'series', uiSection: 'seasonPass' }
)(SeasonPassConnector);
{ section: 'series', uiSection: 'albumStudio' }
)(AlbumStudioConnector);

View File

@ -5,11 +5,11 @@ import SpinnerButton from 'Components/Link/SpinnerButton';
import MonitorAlbumsSelectInput from 'Components/Form/MonitorAlbumsSelectInput';
import SelectInput from 'Components/Form/SelectInput';
import PageContentFooter from 'Components/Page/PageContentFooter';
import styles from './SeasonPassFooter.css';
import styles from './AlbumStudioFooter.css';
const NO_CHANGE = 'noChange';
class SeasonPassFooter extends Component {
class AlbumStudioFooter extends Component {
//
// Lifecycle
@ -89,7 +89,7 @@ class SeasonPassFooter extends Component {
<PageContentFooter>
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor Series
Monitor Artist
</div>
<SelectInput
@ -103,7 +103,7 @@ class SeasonPassFooter extends Component {
<div className={styles.inputContainer}>
<div className={styles.label}>
Monitor Episodes
Monitor Albums
</div>
<MonitorAlbumsSelectInput
@ -117,7 +117,7 @@ class SeasonPassFooter extends Component {
<div>
<div className={styles.label}>
{selectedCount} Series Selected
{selectedCount} Artist(s) Selected
</div>
<SpinnerButton
@ -135,11 +135,11 @@ class SeasonPassFooter extends Component {
}
}
SeasonPassFooter.propTypes = {
AlbumStudioFooter.propTypes = {
selectedCount: PropTypes.number.isRequired,
isSaving: PropTypes.bool.isRequired,
saveError: PropTypes.object,
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 TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import ArtistNameLink from 'Artist/ArtistNameLink';
import SeasonPassSeason from './SeasonPassSeason';
import styles from './SeasonPassRow.css';
import AlbumStudioAlbum from './AlbumStudioAlbum';
import styles from './AlbumStudioRow.css';
class SeasonPassRow extends Component {
class AlbumStudioRow extends Component {
//
// Render
@ -19,10 +19,10 @@ class SeasonPassRow extends Component {
const {
artistId,
status,
titleSlug,
title,
nameSlug,
artistName,
monitored,
seasons,
albums,
isSaving,
isSelected,
onSelectedChange,
@ -49,8 +49,8 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.title}>
<ArtistNameLink
titleSlug={titleSlug}
title={title}
nameSlug={nameSlug}
artistName={artistName}
/>
</TableRowCell>
@ -64,10 +64,10 @@ class SeasonPassRow extends Component {
<TableRowCell className={styles.seasons}>
{
seasons.map((season) => {
albums.map((season) => {
return (
<SeasonPassSeason
key={season.seasonNumber}
<AlbumStudioAlbum
key={season.id}
{...season}
onSeasonMonitoredPress={onSeasonMonitoredPress}
/>
@ -80,13 +80,13 @@ class SeasonPassRow extends Component {
}
}
SeasonPassRow.propTypes = {
AlbumStudioRow.propTypes = {
artistId: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
nameSlug: PropTypes.string.isRequired,
artistName: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
seasons: PropTypes.arrayOf(PropTypes.object).isRequired,
albums: PropTypes.arrayOf(PropTypes.object).isRequired,
isSaving: PropTypes.bool.isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired,
@ -94,8 +94,8 @@ SeasonPassRow.propTypes = {
onSeasonMonitoredPress: PropTypes.func.isRequired
};
SeasonPassRow.defaultProps = {
AlbumStudioRow.defaultProps = {
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 { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/seriesActions';
import SeasonPassRow from './SeasonPassRow';
import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/artistActions';
import AlbumStudioRow from './AlbumStudioRow';
function createMapStateToProps() {
return createSelector(
@ -13,10 +13,10 @@ function createMapStateToProps() {
(series) => {
return _.pick(series, [
'status',
'titleSlug',
'title',
'nameSlug',
'artistName',
'monitored',
'seasons',
'albums',
'isSaving'
]);
}
@ -28,7 +28,7 @@ const mapDispatchToProps = {
toggleSeasonMonitored
};
class SeasonPassRowConnector extends Component {
class AlbumStudioRowConnector extends Component {
//
// Listeners
@ -58,7 +58,7 @@ class SeasonPassRowConnector extends Component {
render() {
return (
<SeasonPassRow
<AlbumStudioRow
{...this.props}
onSeriesMonitoredPress={this.onSeriesMonitoredPress}
onSeasonMonitoredPress={this.onSeasonMonitoredPress}
@ -67,11 +67,11 @@ class SeasonPassRowConnector extends Component {
}
}
SeasonPassRowConnector.propTypes = {
AlbumStudioRowConnector.propTypes = {
artistId: PropTypes.number.isRequired,
monitored: PropTypes.bool.isRequired,
toggleSeriesMonitored: 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 AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector';
import ImportArtist from 'AddArtist/ImportArtist/ImportArtist';
import SeriesEditorConnector from 'Artist/Editor/SeriesEditorConnector';
import SeasonPassConnector from 'SeasonPass/SeasonPassConnector';
import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector';
import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector';
import SeriesDetailsPageConnector from 'Artist/Details/SeriesDetailsPageConnector';
import CalendarPageConnector from 'Calendar/CalendarPageConnector';
import HistoryConnector from 'Activity/History/HistoryConnector';
@ -82,17 +82,17 @@ function App({ store, history }) {
/>
<Route
path="/serieseditor"
component={SeriesEditorConnector}
path="/artisteditor"
component={ArtistEditorConnector}
/>
<Route
path="/seasonpass"
component={SeasonPassConnector}
path="/albumstudio"
component={AlbumStudioConnector}
/>
<Route
path="/series/:titleSlug"
path="/artist/:nameSlug"
component={SeriesDetailsPageConnector}
/>

View File

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

View File

@ -3,7 +3,7 @@ import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { deleteArtist } from 'Store/Actions/seriesActions';
import { deleteArtist } from 'Store/Actions/artistActions';
import DeleteArtistModalContent from './DeleteArtistModalContent';
function createMapStateToProps() {

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@ import { findCommand } from 'Utilities/Command';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
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 { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';

View File

@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import selectSettings from 'Store/Selectors/selectSettings';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { setSeriesValue, saveArtist } from 'Store/Actions/seriesActions';
import { setSeriesValue, saveArtist } from 'Store/Actions/artistActions';
import EditArtistModalContent from './EditArtistModalContent';
function createMapStateToProps() {

View File

@ -15,9 +15,9 @@ import FilterMenuItem from 'Components/Menu/FilterMenuItem';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import NoArtist from 'Artist/NoArtist';
import SeriesEditorRowConnector from './SeriesEditorRowConnector';
import SeriesEditorFooter from './SeriesEditorFooter';
import OrganizeSeriesModal from './Organize/OrganizeSeriesModal';
import ArtistEditorRowConnector from './ArtistEditorRowConnector';
import ArtistEditorFooter from './ArtistEditorFooter';
import OrganizeArtistModal from './Organize/OrganizeArtistModal';
function getColumns(showLanguageProfile) {
return [
@ -26,8 +26,8 @@ function getColumns(showLanguageProfile) {
isVisible: true
},
{
name: 'sortTitle',
label: 'Title',
name: 'sortName',
label: 'Name',
isSortable: true,
isVisible: true
},
@ -44,14 +44,8 @@ function getColumns(showLanguageProfile) {
isVisible: showLanguageProfile
},
{
name: 'seriesType',
label: 'Series Type',
isSortable: false,
isVisible: true
},
{
name: 'seasonFolder',
label: 'Season Folder',
name: 'albumFolder',
label: 'Album Folder',
isSortable: true,
isVisible: true
},
@ -70,7 +64,7 @@ function getColumns(showLanguageProfile) {
];
}
class SeriesEditor extends Component {
class ArtistEditor extends Component {
//
// Lifecycle
@ -83,7 +77,7 @@ class SeriesEditor extends Component {
allUnselected: false,
lastToggled: null,
selectedState: {},
isOrganizingSeriesModalOpen: false,
isOrganizingArtistModalOpen: false,
columns: getColumns(props.showLanguageProfile)
};
}
@ -130,12 +124,12 @@ class SeriesEditor extends Component {
});
}
onOrganizeSeriesPress = () => {
this.setState({ isOrganizingSeriesModalOpen: true });
onOrganizeArtistPress = () => {
this.setState({ isOrganizingArtistModalOpen: true });
}
onOrganizeSeriesModalClose = (organized) => {
this.setState({ isOrganizingSeriesModalOpen: false });
onOrganizeArtistModalClose = (organized) => {
this.setState({ isOrganizingArtistModalOpen: false });
if (organized === true) {
this.onSelectAllChange({ value: false });
@ -159,7 +153,7 @@ class SeriesEditor extends Component {
saveError,
isDeleting,
deleteError,
isOrganizingSeries,
isOrganizingArtist,
showLanguageProfile,
onSortPress,
onFilterSelect
@ -172,10 +166,10 @@ class SeriesEditor extends Component {
columns
} = this.state;
const selectedSeriesIds = this.getSelectedIds();
const selectedArtistIds = this.getSelectedIds();
return (
<PageContent title="Series Editor">
<PageContent title="Artist Editor">
<PageToolbar>
<PageToolbarSection />
<PageToolbarSection alignContent={align.RIGHT}>
@ -261,7 +255,7 @@ class SeriesEditor extends Component {
{
items.map((item) => {
return (
<SeriesEditorRowConnector
<ArtistEditorRowConnector
key={item.id}
{...item}
columns={columns}
@ -282,30 +276,30 @@ class SeriesEditor extends Component {
}
</PageContentBodyConnector>
<SeriesEditorFooter
artistIds={selectedSeriesIds}
selectedCount={selectedSeriesIds.length}
<ArtistEditorFooter
artistIds={selectedArtistIds}
selectedCount={selectedArtistIds.length}
isSaving={isSaving}
saveError={saveError}
isDeleting={isDeleting}
deleteError={deleteError}
isOrganizingSeries={isOrganizingSeries}
isOrganizingArtist={isOrganizingArtist}
showLanguageProfile={showLanguageProfile}
onSaveSelected={this.onSaveSelected}
onOrganizeSeriesPress={this.onOrganizeSeriesPress}
onOrganizeArtistPress={this.onOrganizeArtistPress}
/>
<OrganizeSeriesModal
isOpen={this.state.isOrganizingSeriesModalOpen}
artistIds={selectedSeriesIds}
onModalClose={this.onOrganizeSeriesModalClose}
<OrganizeArtistModal
isOpen={this.state.isOrganizingArtistModalOpen}
artistIds={selectedArtistIds}
onModalClose={this.onOrganizeArtistModalClose}
/>
</PageContent>
);
}
}
SeriesEditor.propTypes = {
ArtistEditor.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
@ -318,11 +312,11 @@ SeriesEditor.propTypes = {
saveError: PropTypes.object,
isDeleting: PropTypes.bool.isRequired,
deleteError: PropTypes.object,
isOrganizingSeries: PropTypes.bool.isRequired,
isOrganizingArtist: PropTypes.bool.isRequired,
showLanguageProfile: PropTypes.bool.isRequired,
onSortPress: PropTypes.func.isRequired,
onFilterSelect: 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 createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
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 * as commandNames from 'Commands/commandNames';
import SeriesEditor from './SeriesEditor';
import ArtistEditor from './ArtistEditor';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.languageProfiles,
createClientSideCollectionSelector(),
createCommandSelector(commandNames.RENAME_SERIES),
(languageProfiles, series, isOrganizingSeries) => {
createCommandSelector(commandNames.RENAME_ARTIST),
(languageProfiles, series, isOrganizingArtist) => {
return {
isOrganizingSeries,
isOrganizingArtist,
showLanguageProfile: languageProfiles.items.length > 1,
...series
};
@ -25,13 +25,13 @@ function createMapStateToProps() {
}
const mapDispatchToProps = {
setSeriesEditorSort,
setSeriesEditorFilter,
setArtistEditorSort,
setArtistEditorFilter,
saveArtistEditor,
fetchRootFolders
};
class SeriesEditorConnector extends Component {
class ArtistEditorConnector extends Component {
//
// Lifecycle
@ -44,11 +44,11 @@ class SeriesEditorConnector extends Component {
// Listeners
onSortPress = (sortKey) => {
this.props.setSeriesEditorSort({ sortKey });
this.props.setArtistEditorSort({ sortKey });
}
onFilterSelect = (filterKey, filterValue, filterType) => {
this.props.setSeriesEditorFilter({ filterKey, filterValue, filterType });
this.props.setArtistEditorFilter({ filterKey, filterValue, filterType });
}
onSaveSelected = (payload) => {
@ -60,7 +60,7 @@ class SeriesEditorConnector extends Component {
render() {
return (
<SeriesEditor
<ArtistEditor
{...this.props}
onSortPress={this.onSortPress}
onFilterSelect={this.onFilterSelect}
@ -70,9 +70,9 @@ class SeriesEditorConnector extends Component {
}
}
SeriesEditorConnector.propTypes = {
setSeriesEditorSort: PropTypes.func.isRequired,
setSeriesEditorFilter: PropTypes.func.isRequired,
ArtistEditorConnector.propTypes = {
setArtistEditorSort: PropTypes.func.isRequired,
setArtistEditorFilter: PropTypes.func.isRequired,
saveArtistEditor: PropTypes.func.isRequired,
fetchRootFolders: PropTypes.func.isRequired
};
@ -82,5 +82,5 @@ export default connectSection(
mapDispatchToProps,
undefined,
undefined,
{ section: 'series', uiSection: 'seriesEditor' }
)(SeriesEditorConnector);
{ section: 'series', uiSection: 'artistEditor' }
)(ArtistEditorConnector);

View File

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

View File

@ -9,9 +9,9 @@ import TableRowCell from 'Components/Table/Cells/TableRowCell';
import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
import ArtistNameLink from 'Artist/ArtistNameLink';
import ArtistStatusCell from 'Artist/Index/Table/ArtistStatusCell';
import styles from './SeriesEditorRow.css';
import styles from './ArtistEditorRow.css';
class SeriesEditorRow extends Component {
class ArtistEditorRow extends Component {
//
// Listeners
@ -28,13 +28,12 @@ class SeriesEditorRow extends Component {
const {
id,
status,
titleSlug,
title,
nameSlug,
artistName,
monitored,
languageProfile,
qualityProfile,
seriesType,
seasonFolder,
albumFolder,
path,
tags,
columns,
@ -57,8 +56,8 @@ class SeriesEditorRow extends Component {
<TableRowCell className={styles.title}>
<ArtistNameLink
titleSlug={titleSlug}
title={title}
nameSlug={nameSlug}
artistName={artistName}
/>
</TableRowCell>
@ -73,14 +72,10 @@ class SeriesEditorRow extends Component {
</TableRowCell>
}
<TableRowCell>
{titleCase(seriesType)}
</TableRowCell>
<TableRowCell className={styles.seasonFolder}>
<TableRowCell className={styles.albumFolder}>
<CheckInput
name="seasonFolder"
value={seasonFolder}
name="albumFolder"
value={albumFolder}
isDisabled={true}
onChange={this.onSeasonFolderChange}
/>
@ -100,16 +95,15 @@ class SeriesEditorRow extends Component {
}
}
SeriesEditorRow.propTypes = {
ArtistEditorRow.propTypes = {
id: PropTypes.number.isRequired,
status: PropTypes.string.isRequired,
titleSlug: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
nameSlug: PropTypes.string.isRequired,
artistName: PropTypes.string.isRequired,
monitored: PropTypes.bool.isRequired,
languageProfile: PropTypes.object.isRequired,
qualityProfile: PropTypes.object.isRequired,
seriesType: PropTypes.string.isRequired,
seasonFolder: PropTypes.bool.isRequired,
albumFolder: PropTypes.bool.isRequired,
path: PropTypes.string.isRequired,
tags: PropTypes.arrayOf(PropTypes.number).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
@ -117,4 +111,4 @@ SeriesEditorRow.propTypes = {
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 createLanguageProfileSelector from 'Store/Selectors/createLanguageProfileSelector';
import createQualityProfileSelector from 'Store/Selectors/createQualityProfileSelector';
import SeriesEditorRow from './SeriesEditorRow';
import ArtistEditorRow from './ArtistEditorRow';
function createMapStateToProps() {
return createSelector(
@ -19,16 +19,16 @@ function createMapStateToProps() {
);
}
function SeriesEditorRowConnector(props) {
function ArtistEditorRowConnector(props) {
return (
<SeriesEditorRow
<ArtistEditorRow
{...props}
/>
);
}
SeriesEditorRowConnector.propTypes = {
ArtistEditorRowConnector.propTypes = {
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 (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Delete Selected Series
Delete Selected Artist
</ModalHeader>
<ModalBody>
<div>
<FormGroup>
<FormLabel>{`Delete Series Folder${series.length > 1 ? 's' : ''}`}</FormLabel>
<FormLabel>{`Delete Artist Folder${series.length > 1 ? 's' : ''}`}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="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}
onChange={this.onDeleteFilesChange}
/>
@ -71,15 +71,15 @@ class DeleteArtistModalContent extends Component {
</div>
<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>
<ul>
{
series.map((s) => {
return (
<li key={s.title}>
<span>{s.title}</span>
<li key={s.artistName}>
<span>{s.artistName}</span>
{
deleteFiles &&

View File

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

View File

@ -1,9 +1,9 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import OrganizeSeriesModalContentConnector from './OrganizeSeriesModalContentConnector';
import OrganizeArtistModalContentConnector from './OrganizeArtistModalContentConnector';
function OrganizeSeriesModal(props) {
function OrganizeArtistModal(props) {
const {
isOpen,
onModalClose,
@ -15,7 +15,7 @@ function OrganizeSeriesModal(props) {
isOpen={isOpen}
onModalClose={onModalClose}
>
<OrganizeSeriesModalContentConnector
<OrganizeArtistModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
@ -23,9 +23,9 @@ function OrganizeSeriesModal(props) {
);
}
OrganizeSeriesModal.propTypes = {
OrganizeArtistModal.propTypes = {
isOpen: PropTypes.bool.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 ModalBody from 'Components/Modal/ModalBody';
import ModalFooter from 'Components/Modal/ModalFooter';
import styles from './OrganizeSeriesModalContent.css';
import styles from './OrganizeArtistModalContent.css';
function OrganizeSeriesModalContent(props) {
function OrganizeArtistModalContent(props) {
const {
seriesTitles,
artistNames,
onModalClose,
onOrganizeSeriesPress
onOrganizeArtistPress
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Organize Selected Series
Organize Selected Artist
</ModalHeader>
<ModalBody>
<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
className={styles.renameIcon}
name={icons.ORGANIZE}
@ -33,15 +33,15 @@ function OrganizeSeriesModalContent(props) {
</Alert>
<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>
<ul>
{
seriesTitles.map((title) => {
artistNames.map((artistName) => {
return (
<li key={title}>
{title}
<li key={artistName}>
{artistName}
</li>
);
})
@ -56,7 +56,7 @@ function OrganizeSeriesModalContent(props) {
<Button
kind={kinds.DANGER}
onPress={onOrganizeSeriesPress}
onPress={onOrganizeArtistPress}
>
Organize
</Button>
@ -65,10 +65,10 @@ function OrganizeSeriesModalContent(props) {
);
}
OrganizeSeriesModalContent.propTypes = {
seriesTitles: PropTypes.arrayOf(PropTypes.string).isRequired,
OrganizeArtistModalContent.propTypes = {
artistNames: PropTypes.arrayOf(PropTypes.string).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 { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import OrganizeSeriesModalContent from './OrganizeSeriesModalContent';
import OrganizeArtistModalContent from './OrganizeArtistModalContent';
function createMapStateToProps() {
return createSelector(
(state, { artistIds }) => artistIds,
createAllSeriesSelector(),
createAllArtistSelector(),
(artistIds, allSeries) => {
const series = _.intersectionWith(allSeries, artistIds, (s, id) => {
return s.id === id;
});
const sortedSeries = _.orderBy(series, 'sortTitle');
const seriesTitles = _.map(sortedSeries, 'title');
const sortedArtist = _.orderBy(series, 'sortName');
const artistNames = _.map(sortedArtist, 'artistName');
return {
seriesTitles
artistNames
};
}
);
@ -31,14 +31,14 @@ const mapDispatchToProps = {
executeCommand
};
class OrganizeSeriesModalContentConnector extends Component {
class OrganizeArtistModalContentConnector extends Component {
//
// Listeners
onOrganizeSeriesPress = () => {
onOrganizeArtistPress = () => {
this.props.executeCommand({
name: commandNames.RENAME_SERIES,
name: commandNames.RENAME_ARTIST,
artistIds: this.props.artistIds
});
@ -50,18 +50,18 @@ class OrganizeSeriesModalContentConnector extends Component {
render(props) {
return (
<OrganizeSeriesModalContent
<OrganizeArtistModalContent
{...this.props}
onOrganizeSeriesPress={this.onOrganizeSeriesPress}
onOrganizeArtistPress={this.onOrganizeArtistPress}
/>
);
}
}
OrganizeSeriesModalContentConnector.propTypes = {
OrganizeArtistModalContentConnector.propTypes = {
artistIds: PropTypes.arrayOf(PropTypes.number).isRequired,
onModalClose: 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 { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import TagsModalContent from './TagsModalContent';
function createMapStateToProps() {
return createSelector(
(state, { artistIds }) => artistIds,
createAllSeriesSelector(),
createAllArtistSelector(),
createTagsSelector(),
(artistIds, allSeries, tagList) => {
const series = _.intersectionWith(allSeries, artistIds, (s, id) => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,10 +10,10 @@ export const DOWNLOADED_EPSIODES_SCAN = 'DownloadedEpisodesScan';
export const EPISODE_SEARCH = 'EpisodeSearch';
export const INTERACTIVE_IMPORT = 'ManualImport';
export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch';
export const REFRESH_SERIES = 'RefreshSeries';
export const REFRESH_ARTIST = 'RefreshArtist';
export const RENAME_FILES = 'RenameFiles';
export const RENAME_SERIES = 'RenameSeries';
export const RENAME_ARTIST = 'RenameArtist';
export const RESET_API_KEY = 'ResetApiKey';
export const RSS_SYNC = 'RssSync';
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) {
errorMessage = 'Local Storage is not supported or disabled. A plugin or private browsing may have disabled it.';
} else if (seriesError) {
errorMessage = getErrorMessage(seriesError, 'Failed to load series from API');
errorMessage = getErrorMessage(seriesError, 'Failed to load artist from API');
} else if (tagsError) {
errorMessage = getErrorMessage(seriesError, 'Failed to load series from API');
errorMessage = getErrorMessage(seriesError, 'Failed to load artist from API');
} else if (qualityProfilesError) {
errorMessage = getErrorMessage(qualityProfilesError, 'Failed to load quality profiles from API');
} else if (uiSettingsError) {

View File

@ -6,12 +6,12 @@ import jdu from 'jdu';
import { icons } from 'Helpers/Props';
import Icon from 'Components/Icon';
import keyboardShortcuts, { shortcuts } from 'Components/keyboardShortcuts';
import SeriesSearchResult from './SeriesSearchResult';
import styles from './SeriesSearchInput.css';
import ArtistSearchResult from './ArtistSearchResult';
import styles from './ArtistSearchInput.css';
const ADD_NEW_TYPE = 'addNew';
class SeriesSearchInput extends Component {
class ArtistSearchInput extends Component {
//
// Lifecycle
@ -28,7 +28,7 @@ class SeriesSearchInput extends Component {
}
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 (
<SeriesSearchResult
<ArtistSearchResult
query={query}
{...item}
/>
@ -78,7 +78,7 @@ class SeriesSearchInput extends Component {
goToSeries(series) {
this.setState({ value: '' });
this.props.onGoToSeries(series.titleSlug);
this.props.onGoToSeries(series.nameSlug);
}
reset() {
@ -137,7 +137,7 @@ class SeriesSearchInput extends Component {
const suggestions = _.filter(this.props.series, (series) => {
// 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 jdu.replace(alternateTitle.title).toLowerCase().contains(lowerCaseValue);
@ -172,14 +172,14 @@ class SeriesSearchInput extends Component {
if (suggestions.length) {
suggestionGroups.push({
title: 'Existing Series',
title: 'Existing Artist',
suggestions
});
}
if (suggestions.length <= 3) {
suggestionGroups.push({
title: 'Add New Series',
title: 'Add New Artist',
suggestions: [
{
type: ADD_NEW_TYPE,
@ -240,11 +240,11 @@ class SeriesSearchInput extends Component {
}
}
SeriesSearchInput.propTypes = {
ArtistSearchInput.propTypes = {
series: PropTypes.arrayOf(PropTypes.object).isRequired,
onGoToSeries: PropTypes.func.isRequired,
onGoToAddNewArtist: 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 { push } from 'react-router-redux';
import { createSelector } from 'reselect';
import createAllSeriesSelector from 'Store/Selectors/createAllSeriesSelector';
import SeriesSearchInput from './SeriesSearchInput';
import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector';
import ArtistSearchInput from './ArtistSearchInput';
function createMapStateToProps() {
return createSelector(
createAllSeriesSelector(),
createAllArtistSelector(),
(series) => {
return {
series: _.sortBy(series, 'sortTitle')
series: _.sortBy(series, 'sortName')
};
}
);
@ -18,8 +18,8 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
onGoToSeries(titleSlug) {
dispatch(push(`${window.Sonarr.urlBase}/series/${titleSlug}`));
onGoToSeries(nameSlug) {
dispatch(push(`${window.Sonarr.urlBase}/artist/${nameSlug}`));
},
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 React from 'react';
import ArtistPoster from 'Artist/ArtistPoster';
import styles from './SeriesSearchResult.css';
import styles from './ArtistSearchResult.css';
function getMatchingAlternateTile(alternateTitles, query) {
return _.first(alternateTitles, (alternateTitle) => {
@ -10,18 +10,18 @@ function getMatchingAlternateTile(alternateTitles, query) {
});
}
function SeriesSearchResult(props) {
function ArtistSearchResult(props) {
const {
query,
title,
alternateTitles,
artistName,
// alternateTitles,
images
} = props;
const index = title.toLowerCase().indexOf(query.toLowerCase());
const alternateTitle = index === -1 ?
getMatchingAlternateTile(alternateTitles, query) :
null;
const index = artistName.toLowerCase().indexOf(query.toLowerCase());
// const alternateTitle = index === -1 ?
// getMatchingAlternateTile(alternateTitles, query) :
// null;
return (
<div className={styles.result}>
@ -35,25 +35,25 @@ function SeriesSearchResult(props) {
<div className={styles.titles}>
<div className={styles.title}>
{title}
{artistName}
</div>
{
!!alternateTitle &&
<div className={styles.alternateTitle}>
{alternateTitle.title}
</div>
// !!alternateTitle &&
// <div className={styles.alternateTitle}>
// {alternateTitle.title}
// </div>
}
</div>
</div>
);
}
SeriesSearchResult.propTypes = {
ArtistSearchResult.propTypes = {
query: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired,
artistName: PropTypes.string.isRequired,
// alternateTitles: 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 IconButton from 'Components/Link/IconButton';
import Link from 'Components/Link/Link';
import SeriesSearchInputConnector from './SeriesSearchInputConnector';
import ArtistSearchInputConnector from './ArtistSearchInputConnector';
import PageHeaderActionsMenuConnector from './PageHeaderActionsMenuConnector';
import KeyboardShortcutsModal from './KeyboardShortcutsModal';
import styles from './PageHeader.css';
@ -68,7 +68,7 @@ class PageHeader extends Component {
/>
</div>
<SeriesSearchInputConnector />
<ArtistSearchInputConnector />
<div className={styles.right}>
<IconButton

View File

@ -5,7 +5,7 @@ import { withRouter } from 'react-router-dom';
import { createSelector } from 'reselect';
import createDimensionsSelector from 'Store/Selectors/createDimensionsSelector';
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 { fetchQualityProfiles, fetchLanguageProfiles, fetchUISettings } from 'Store/Actions/settingsActions';
import { fetchStatus } from 'Store/Actions/systemActions';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,11 +3,11 @@ import $ from 'jquery';
import getMonitoringOptions from 'Utilities/Series/getMonitoringOptions';
import * as types from './actionTypes';
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) {
return function(dispatch, getState) {
const {
@ -50,7 +50,7 @@ const seasonPassActionHandlers = {
}));
const promise = $.ajax({
url: '/seasonPass',
url: '/albumStudio',
method: 'POST',
data: JSON.stringify({
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 { set, updateItem } from './baseActions';
const section = 'seriesEditor';
const section = 'artistEditor';
const seriesEditorActionHandlers = {
const artistEditorActionHandlers = {
[types.SAVE_ARTIST_EDITOR]: function(payload) {
return function(dispatch, getState) {
dispatch(set({
@ -14,7 +14,7 @@ const seriesEditorActionHandlers = {
}));
const promise = $.ajax({
url: '/series/editor',
url: '/artist/editor',
method: 'PUT',
data: JSON.stringify(payload),
dataType: 'json'
@ -56,7 +56,7 @@ const seriesEditorActionHandlers = {
}));
const promise = $.ajax({
url: '/series/editor',
url: '/artist/editor',
method: 'DELETE',
data: JSON.stringify(payload),
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
// 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);
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 episodeReducers from 'Store/Reducers/episodeReducers';
import * as artistIndexReducers from 'Store/Reducers/artistIndexReducers';
import * as seriesEditorReducers from 'Store/Reducers/seriesEditorReducers';
import * as seasonPassReducers from 'Store/Reducers/seasonPassReducers';
import * as artistEditorReducers from 'Store/Reducers/artistEditorReducers';
import * as albumStudioReducers from 'Store/Reducers/albumStudioReducers';
import * as calendarReducers from 'Store/Reducers/calendarReducers';
import * as historyReducers from 'Store/Reducers/historyReducers';
import * as blacklistReducers from 'Store/Reducers/blacklistReducers';
@ -18,8 +18,8 @@ const reducers = [
addArtistReducers,
episodeReducers,
artistIndexReducers,
seriesEditorReducers,
seasonPassReducers,
artistEditorReducers,
albumStudioReducers,
calendarReducers,
historyReducers,
blacklistReducers,

View File

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

View File

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

View File

@ -22,7 +22,7 @@ export const defaultState = {
const reducerSection = 'series';
const seriesReducers = handleActions({
const artistReducers = handleActions({
[types.SET]: createSetReducer(reducerSection),
[types.UPDATE]: createUpdateReducer(reducerSection),
@ -34,4 +34,4 @@ const seriesReducers = handleActions({
}, 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 addArtist, { defaultState as defaultAddSeriesState } from './addArtistReducers';
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 seriesEditor, { defaultState as defaultSeriesEditorState } from './seriesEditorReducers';
import seasonPass, { defaultState as defaultSeasonPassState } from './seasonPassReducers';
import artistEditor, { defaultState as defaultArtistEditorState } from './artistEditorReducers';
import albumStudio, { defaultState as defaultAlbumStudioState } from './albumStudioReducers';
import calendar, { defaultState as defaultCalendarState } from './calendarReducers';
import history, { defaultState as defaultHistoryState } from './historyReducers';
import queue, { defaultState as defaultQueueState } from './queueReducers';
@ -34,8 +34,8 @@ export const defaultState = {
importArtist: defaultImportArtistState,
series: defaultSeriesState,
seriesIndex: defaultSeriesIndexState,
seriesEditor: defaultSeriesEditorState,
seasonPass: defaultSeasonPassState,
artistEditor: defaultArtistEditorState,
albumStudio: defaultAlbumStudioState,
calendar: defaultCalendarState,
history: defaultHistoryState,
queue: defaultQueueState,
@ -63,8 +63,8 @@ export default enableBatching(combineReducers({
importArtist,
series,
seriesIndex,
seriesEditor,
seasonPass,
artistEditor,
albumStudio,
calendar,
history,
queue,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -18,11 +18,11 @@ function createMapStateToProps() {
(state) => state.wanted.cutoffUnmet,
createCommandsSelector(),
(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 });
return {
isSearchingForEpisodes,
isSearchingForAlbums,
isSearchingForCutoffUnmetEpisodes,
isSaving: _.some(cutoffUnmet.items, { isSaving: true }),
...cutoffUnmet

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -60,10 +60,10 @@ namespace NzbDrone.Core.Organizer
public static readonly Regex SeriesTitleRegex = new Regex(@"(?<token>\{(?:Series)(?<separator>[- ._])(Clean)?Title\})",
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);
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);
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.Validators;
@ -56,7 +56,7 @@ namespace NzbDrone.Core.Organizer
public static IRuleBuilderOptions<T, string> ValidAlbumFolderFormat<T>(this IRuleBuilder<T, string> ruleBuilder)
{
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");
}
}