From 0a7f18e84387878e27356ae1ac4cf9826b67bc76 Mon Sep 17 00:00:00 2001 From: Qstick Date: Thu, 14 Sep 2017 23:35:12 -0400 Subject: [PATCH] [UI Work] Artist Detail, Album Dialog, Album Search, Album Missing Search --- frontend/src/AlbumStudio/AlbumStudioAlbum.js | 8 +- frontend/src/AlbumStudio/AlbumStudioRow.js | 14 +- .../AlbumStudio/AlbumStudioRowConnector.js | 22 +- frontend/src/App/App.js | 4 +- .../Details/{EpisodeRow.css => AlbumRow.css} | 9 - frontend/src/Artist/Details/AlbumRow.js | 219 ++++++++++++++ ...deRowConnector.js => AlbumRowConnector.js} | 6 +- ...teTitles.css => ArtistAlternateTitles.css} | 0 ...nateTitles.js => ArtistAlternateTitles.js} | 8 +- .../{SeriesDetails.css => ArtistDetails.css} | 5 +- .../{SeriesDetails.js => ArtistDetails.js} | 113 ++++---- ...Connector.js => ArtistDetailsConnector.js} | 38 +-- ...etailsLinks.css => ArtistDetailsLinks.css} | 0 .../src/Artist/Details/ArtistDetailsLinks.js | 36 +++ ...ector.js => ArtistDetailsPageConnector.js} | 10 +- ...ailsSeason.css => ArtistDetailsSeason.css} | 14 +- ...etailsSeason.js => ArtistDetailsSeason.js} | 176 ++++++------ ...tor.js => ArtistDetailsSeasonConnector.js} | 39 ++- .../{SeriesTags.css => ArtistTags.css} | 0 .../Details/{SeriesTags.js => ArtistTags.js} | 8 +- ...agsConnector.js => ArtistTagsConnector.js} | 4 +- frontend/src/Artist/Details/EpisodeRow.js | 266 ------------------ .../src/Artist/Details/SeriesDetailsLinks.js | 70 ----- .../Artist/Index/Table/ArtistStatusCell.js | 2 +- frontend/src/Commands/commandNames.js | 4 +- .../Components/Page/Sidebar/PageSidebar.js | 2 +- .../src/Episode/EpisodeDetailsModalContent.js | 53 ++-- .../EpisodeDetailsModalContentConnector.js | 14 +- .../src/Episode/EpisodeSearchCellConnector.js | 6 +- .../src/Episode/History/EpisodeHistory.js | 4 +- .../Episode/Search/EpisodeSearchConnector.js | 4 +- .../InteractiveEpisodeSearchConnector.js | 6 +- frontend/src/Episode/Summary/EpisodeAiring.js | 30 +- .../src/Episode/Summary/EpisodeSummary.js | 17 +- frontend/src/Helpers/Props/icons.js | 4 +- .../Store/Actions/episodeActionHandlers.js | 16 +- .../src/Store/Reducers/episodeReducers.js | 29 +- .../CutoffUnmet/CutoffUnmetConnector.js | 2 +- .../src/Wanted/Missing/MissingConnector.js | 6 +- 39 files changed, 559 insertions(+), 709 deletions(-) rename frontend/src/Artist/Details/{EpisodeRow.css => AlbumRow.css} (69%) create mode 100644 frontend/src/Artist/Details/AlbumRow.js rename frontend/src/Artist/Details/{EpisodeRowConnector.js => AlbumRowConnector.js} (87%) rename frontend/src/Artist/Details/{SeriesAlternateTitles.css => ArtistAlternateTitles.css} (100%) rename frontend/src/Artist/Details/{SeriesAlternateTitles.js => ArtistAlternateTitles.js} (71%) rename frontend/src/Artist/Details/{SeriesDetails.css => ArtistDetails.css} (96%) rename frontend/src/Artist/Details/{SeriesDetails.js => ArtistDetails.js} (84%) rename frontend/src/Artist/Details/{SeriesDetailsConnector.js => ArtistDetailsConnector.js} (83%) rename frontend/src/Artist/Details/{SeriesDetailsLinks.css => ArtistDetailsLinks.css} (100%) create mode 100644 frontend/src/Artist/Details/ArtistDetailsLinks.js rename frontend/src/Artist/Details/{SeriesDetailsPageConnector.js => ArtistDetailsPageConnector.js} (87%) rename frontend/src/Artist/Details/{SeriesDetailsSeason.css => ArtistDetailsSeason.css} (91%) rename frontend/src/Artist/Details/{SeriesDetailsSeason.js => ArtistDetailsSeason.js} (66%) rename frontend/src/Artist/Details/{SeriesDetailsSeasonConnector.js => ArtistDetailsSeasonConnector.js} (74%) rename frontend/src/Artist/Details/{SeriesTags.css => ArtistTags.css} (100%) rename frontend/src/Artist/Details/{SeriesTags.js => ArtistTags.js} (80%) rename frontend/src/Artist/Details/{SeriesTagsConnector.js => ArtistTagsConnector.js} (87%) delete mode 100644 frontend/src/Artist/Details/EpisodeRow.js delete mode 100644 frontend/src/Artist/Details/SeriesDetailsLinks.js diff --git a/frontend/src/AlbumStudio/AlbumStudioAlbum.js b/frontend/src/AlbumStudio/AlbumStudioAlbum.js index 4e4be3ac1..d1c9e393d 100644 --- a/frontend/src/AlbumStudio/AlbumStudioAlbum.js +++ b/frontend/src/AlbumStudio/AlbumStudioAlbum.js @@ -10,13 +10,13 @@ class AlbumStudioAlbum extends Component { // // Listeners - onSeasonMonitoredPress = () => { + onAlbumMonitoredPress = () => { const { id, monitored } = this.props; - this.props.onSeasonMonitoredPress(id, !monitored); + this.props.onAlbumMonitoredPress(id, !monitored); } // @@ -43,7 +43,7 @@ class AlbumStudioAlbum extends Component { @@ -75,7 +75,7 @@ AlbumStudioAlbum.propTypes = { monitored: PropTypes.bool.isRequired, statistics: PropTypes.object.isRequired, isSaving: PropTypes.bool.isRequired, - onSeasonMonitoredPress: PropTypes.func.isRequired + onAlbumMonitoredPress: PropTypes.func.isRequired }; AlbumStudioAlbum.defaultProps = { diff --git a/frontend/src/AlbumStudio/AlbumStudioRow.js b/frontend/src/AlbumStudio/AlbumStudioRow.js index 0f0711335..d63dc83ee 100644 --- a/frontend/src/AlbumStudio/AlbumStudioRow.js +++ b/frontend/src/AlbumStudio/AlbumStudioRow.js @@ -26,8 +26,8 @@ class AlbumStudioRow extends Component { isSaving, isSelected, onSelectedChange, - onSeriesMonitoredPress, - onSeasonMonitoredPress + onArtistMonitoredPress, + onAlbumMonitoredPress } = this.props; return ( @@ -41,7 +41,7 @@ class AlbumStudioRow extends Component { @@ -58,7 +58,7 @@ class AlbumStudioRow extends Component { @@ -69,7 +69,7 @@ class AlbumStudioRow extends Component { ); }) @@ -90,8 +90,8 @@ AlbumStudioRow.propTypes = { isSaving: PropTypes.bool.isRequired, isSelected: PropTypes.bool, onSelectedChange: PropTypes.func.isRequired, - onSeriesMonitoredPress: PropTypes.func.isRequired, - onSeasonMonitoredPress: PropTypes.func.isRequired + onArtistMonitoredPress: PropTypes.func.isRequired, + onAlbumMonitoredPress: PropTypes.func.isRequired }; AlbumStudioRow.defaultProps = { diff --git a/frontend/src/AlbumStudio/AlbumStudioRowConnector.js b/frontend/src/AlbumStudio/AlbumStudioRowConnector.js index ac7b79927..1a0ae45ad 100644 --- a/frontend/src/AlbumStudio/AlbumStudioRowConnector.js +++ b/frontend/src/AlbumStudio/AlbumStudioRowConnector.js @@ -5,6 +5,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createArtistSelector from 'Store/Selectors/createArtistSelector'; import { toggleSeriesMonitored, toggleSeasonMonitored } from 'Store/Actions/artistActions'; +import { toggleEpisodeMonitored } from 'Store/Actions/episodeActions'; import AlbumStudioRow from './AlbumStudioRow'; function createMapStateToProps() { @@ -25,7 +26,8 @@ function createMapStateToProps() { const mapDispatchToProps = { toggleSeriesMonitored, - toggleSeasonMonitored + toggleSeasonMonitored, + toggleEpisodeMonitored }; class AlbumStudioRowConnector extends Component { @@ -33,7 +35,7 @@ class AlbumStudioRowConnector extends Component { // // Listeners - onSeriesMonitoredPress = () => { + onArtistMonitoredPress = () => { const { artistId, monitored @@ -45,11 +47,10 @@ class AlbumStudioRowConnector extends Component { }); } - onSeasonMonitoredPress = (seasonNumber, monitored) => { - this.props.toggleSeasonMonitored({ - artistId: this.props.artistId, - seasonNumber, - monitored + onAlbumMonitoredPress = (episodeId, monitored) => { + this.props.toggleEpisodeMonitored({ + episodeId, + monitored: !monitored }); } @@ -60,8 +61,8 @@ class AlbumStudioRowConnector extends Component { return ( ); } @@ -71,7 +72,8 @@ AlbumStudioRowConnector.propTypes = { artistId: PropTypes.number.isRequired, monitored: PropTypes.bool.isRequired, toggleSeriesMonitored: PropTypes.func.isRequired, - toggleSeasonMonitored: PropTypes.func.isRequired + toggleSeasonMonitored: PropTypes.func.isRequired, + toggleEpisodeMonitored: PropTypes.func.isRequired }; export default connect(createMapStateToProps, mapDispatchToProps)(AlbumStudioRowConnector); diff --git a/frontend/src/App/App.js b/frontend/src/App/App.js index 4295ab2af..5a8d6a95c 100644 --- a/frontend/src/App/App.js +++ b/frontend/src/App/App.js @@ -13,7 +13,7 @@ import AddNewArtistConnector from 'AddArtist/AddNewArtist/AddNewArtistConnector' import ImportArtist from 'AddArtist/ImportArtist/ImportArtist'; import ArtistEditorConnector from 'Artist/Editor/ArtistEditorConnector'; import AlbumStudioConnector from 'AlbumStudio/AlbumStudioConnector'; -import SeriesDetailsPageConnector from 'Artist/Details/SeriesDetailsPageConnector'; +import ArtistDetailsPageConnector from 'Artist/Details/ArtistDetailsPageConnector'; import CalendarPageConnector from 'Calendar/CalendarPageConnector'; import HistoryConnector from 'Activity/History/HistoryConnector'; import QueueConnector from 'Activity/Queue/QueueConnector'; @@ -93,7 +93,7 @@ function App({ store, history }) { {/* diff --git a/frontend/src/Artist/Details/EpisodeRow.css b/frontend/src/Artist/Details/AlbumRow.css similarity index 69% rename from frontend/src/Artist/Details/EpisodeRow.css rename to frontend/src/Artist/Details/AlbumRow.css index fc7bf0397..255ee76c9 100644 --- a/frontend/src/Artist/Details/EpisodeRow.css +++ b/frontend/src/Artist/Details/AlbumRow.css @@ -10,15 +10,6 @@ width: 42px; } -.episodeNumber { - composes: cell from 'Components/Table/Cells/TableRowCell.css'; - - width: 50px; -} - -.language, -.audio, -.video, .status { composes: cell from 'Components/Table/Cells/TableRowCell.css'; diff --git a/frontend/src/Artist/Details/AlbumRow.js b/frontend/src/Artist/Details/AlbumRow.js new file mode 100644 index 000000000..8c0e2b804 --- /dev/null +++ b/frontend/src/Artist/Details/AlbumRow.js @@ -0,0 +1,219 @@ +import PropTypes from 'prop-types'; +import React, { Component } from 'react'; +import MonitorToggleButton from 'Components/MonitorToggleButton'; +import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; +import { kinds, sizes } from 'Helpers/Props'; +import TableRow from 'Components/Table/TableRow'; +import Label from 'Components/Label'; +import TableRowCell from 'Components/Table/Cells/TableRowCell'; +import formatTimeSpan from 'Utilities/Date/formatTimeSpan'; +import EpisodeSearchCellConnector from 'Episode/EpisodeSearchCellConnector'; +import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; + +import styles from './AlbumRow.css'; + +function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) { + if (episodeFileCount === episodeCount && episodeCount > 0) { + return kinds.SUCCESS; + } + + if (!monitored) { + return kinds.WARNING; + } + + return kinds.DANGER; +} + +class AlbumRow extends Component { + + // + // Lifecycle + + constructor(props, context) { + super(props, context); + + this.state = { + isDetailsModalOpen: false + }; + } + + // + // Listeners + + onManualSearchPress = () => { + this.setState({ isDetailsModalOpen: true }); + } + + onDetailsModalClose = () => { + this.setState({ isDetailsModalOpen: false }); + } + + onMonitorAlbumPress = (monitored, options) => { + this.props.onMonitorAlbumPress(this.props.id, monitored, options); + } + + // + // Render + + render() { + const { + id, + artistId, + monitored, + statistics, + duration, + releaseDate, + title, + isSaving, + artistMonitored, + path, + columns + } = this.props; + + const { + trackCount, + trackFileCount, + totalTrackCount + } = statistics; + + return ( + + { + columns.map((column) => { + const { + name, + isVisible + } = column; + + if (!isVisible) { + return null; + } + + if (name === 'monitored') { + return ( + + + + ); + } + + if (name === 'title') { + return ( + + + + ); + } + + if (name === 'path') { + return ( + + { + path + } + + ); + } + + if (name === 'trackCount') { + return ( + + { + statistics.trackCount + } + + ); + } + + if (name === 'duration') { + return ( + + { + formatTimeSpan(duration) + } + + ); + } + + if (name === 'releaseDate') { + return ( + + ); + } + + if (name === 'status') { + return ( + + + + ); + } + + if (name === 'actions') { + return ( + + ); + } + + return null; + }) + } + + ); + } +} + +AlbumRow.propTypes = { + id: PropTypes.number.isRequired, + artistId: PropTypes.number.isRequired, + monitored: PropTypes.bool.isRequired, + releaseDate: PropTypes.string.isRequired, + duration: PropTypes.number.isRequired, + title: PropTypes.string.isRequired, + isSaving: PropTypes.bool, + unverifiedSceneNumbering: PropTypes.bool, + artistMonitored: PropTypes.bool.isRequired, + statistics: PropTypes.object.isRequired, + path: PropTypes.string, + mediaInfo: PropTypes.object, + alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired, + columns: PropTypes.arrayOf(PropTypes.object).isRequired, + onMonitorAlbumPress: PropTypes.func.isRequired +}; + +export default AlbumRow; diff --git a/frontend/src/Artist/Details/EpisodeRowConnector.js b/frontend/src/Artist/Details/AlbumRowConnector.js similarity index 87% rename from frontend/src/Artist/Details/EpisodeRowConnector.js rename to frontend/src/Artist/Details/AlbumRowConnector.js index e827c17b5..eba950ab3 100644 --- a/frontend/src/Artist/Details/EpisodeRowConnector.js +++ b/frontend/src/Artist/Details/AlbumRowConnector.js @@ -4,7 +4,7 @@ import { createSelector } from 'reselect'; import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector'; import createCommandsSelector from 'Store/Selectors/createCommandsSelector'; -import EpisodeRow from './EpisodeRow'; +import AlbumRow from './AlbumRow'; function createMapStateToProps() { return createSelector( @@ -17,7 +17,7 @@ function createMapStateToProps() { const alternateTitles = sceneSeasonNumber ? _.filter(series.alternateTitles, { sceneSeasonNumber }) : []; return { - seriesMonitored: series.monitored, + artistMonitored: series.monitored, seriesType: series.seriesType, episodeFilePath: episodeFile ? episodeFile.path : null, episodeFileRelativePath: episodeFile ? episodeFile.relativePath : null, @@ -26,4 +26,4 @@ function createMapStateToProps() { } ); } -export default connect(createMapStateToProps)(EpisodeRow); +export default connect(createMapStateToProps)(AlbumRow); diff --git a/frontend/src/Artist/Details/SeriesAlternateTitles.css b/frontend/src/Artist/Details/ArtistAlternateTitles.css similarity index 100% rename from frontend/src/Artist/Details/SeriesAlternateTitles.css rename to frontend/src/Artist/Details/ArtistAlternateTitles.css diff --git a/frontend/src/Artist/Details/SeriesAlternateTitles.js b/frontend/src/Artist/Details/ArtistAlternateTitles.js similarity index 71% rename from frontend/src/Artist/Details/SeriesAlternateTitles.js rename to frontend/src/Artist/Details/ArtistAlternateTitles.js index 18d016579..e1fde52e6 100644 --- a/frontend/src/Artist/Details/SeriesAlternateTitles.js +++ b/frontend/src/Artist/Details/ArtistAlternateTitles.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; -import styles from './SeriesAlternateTitles.css'; +import styles from './ArtistAlternateTitles.css'; -function SeriesAlternateTitles({ alternateTitles }) { +function ArtistAlternateTitles({ alternateTitles }) { return (
    { @@ -21,8 +21,8 @@ function SeriesAlternateTitles({ alternateTitles }) { ); } -SeriesAlternateTitles.propTypes = { +ArtistAlternateTitles.propTypes = { alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired }; -export default SeriesAlternateTitles; +export default ArtistAlternateTitles; diff --git a/frontend/src/Artist/Details/SeriesDetails.css b/frontend/src/Artist/Details/ArtistDetails.css similarity index 96% rename from frontend/src/Artist/Details/SeriesDetails.css rename to frontend/src/Artist/Details/ArtistDetails.css index 7a2daccb2..20b8d911f 100644 --- a/frontend/src/Artist/Details/SeriesDetails.css +++ b/frontend/src/Artist/Details/ArtistDetails.css @@ -61,11 +61,11 @@ line-height: 50px; } -.seriesNavigationButtons { +.artistNavigationButtons { white-space: no-wrap; } -.seriesNavigationButton { +.artistNavigationButton { composes: button from 'Components/Link/IconButton.css'; margin-left: 5px; @@ -90,7 +90,6 @@ .sizeOnDisk, .qualityProfileName, -.network, .links, .tags { margin-left: 8px; diff --git a/frontend/src/Artist/Details/SeriesDetails.js b/frontend/src/Artist/Details/ArtistDetails.js similarity index 84% rename from frontend/src/Artist/Details/SeriesDetails.js rename to frontend/src/Artist/Details/ArtistDetails.js index f9b33cb42..14a1e6b3e 100644 --- a/frontend/src/Artist/Details/SeriesDetails.js +++ b/frontend/src/Artist/Details/ArtistDetails.js @@ -24,11 +24,29 @@ import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfil import ArtistPoster from 'Artist/ArtistPoster'; import EditArtistModalConnector from 'Artist/Edit/EditArtistModalConnector'; import DeleteArtistModal from 'Artist/Delete/DeleteArtistModal'; -import SeriesAlternateTitles from './SeriesAlternateTitles'; -import SeriesDetailsSeasonConnector from './SeriesDetailsSeasonConnector'; -import SeriesTagsConnector from './SeriesTagsConnector'; -import SeriesDetailsLinks from './SeriesDetailsLinks'; -import styles from './SeriesDetails.css'; +import ArtistAlternateTitles from './ArtistAlternateTitles'; +import ArtistDetailsSeasonConnector from './ArtistDetailsSeasonConnector'; +import ArtistTagsConnector from './ArtistTagsConnector'; +import ArtistDetailsLinks from './ArtistDetailsLinks'; +import styles from './ArtistDetails.css'; + +const albumTypes = [ + { + name: 'album', + label: 'Album', + isVisible: true + }, + { + name: 'single', + label: 'Single', + isVisible: true + }, + { + name: 'ep', + label: 'EP', + isVisible: true + } +]; function getFanartUrl(images) { const fanartImage = _.find(images, { coverType: 'fanart' }); @@ -46,7 +64,7 @@ function getExpandedState(newState) { }; } -class SeriesDetails extends Component { +class ArtistDetails extends Component { // // Lifecycle @@ -133,8 +151,6 @@ class SeriesDetails extends Component { const { id, foreignArtistId, - tvMazeId, - imdbId, artistName, ratings, sizeOnDisk, @@ -142,7 +158,6 @@ class SeriesDetails extends Component { qualityProfileId, monitored, status, - network, overview, images, albums, @@ -154,8 +169,8 @@ class SeriesDetails extends Component { isPopulated, episodesError, episodeFilesError, - previousSeries, - nextSeries, + previousArtist, + nextArtist, onRefreshPress, onSearchPress } = this.props; @@ -172,12 +187,12 @@ class SeriesDetails extends Component { const continuing = status === 'continuing'; - let episodeFilesCountMessage = 'No episode files'; + let episodeFilesCountMessage = 'No track files'; if (trackFileCount === 1) { - episodeFilesCountMessage = '1 episode file'; + episodeFilesCountMessage = '1 track file'; } else if (trackFileCount > 1) { - episodeFilesCountMessage = `${trackFileCount} episode files`; + episodeFilesCountMessage = `${trackFileCount} track files`; } let expandIcon = icons.EXPAND_INDETERMINATE; @@ -217,7 +232,7 @@ class SeriesDetails extends Component { /> @@ -281,28 +296,28 @@ class SeriesDetails extends Component { /> } title="Alternate Titles" - body={} + body={} position={tooltipPositions.BOTTOM} /> } -
    +
    @@ -369,11 +384,11 @@ class SeriesDetails extends Component { - { - !!network && - - } - } tooltip={ - } kind={kinds.INVERSE} @@ -445,7 +440,7 @@ class SeriesDetails extends Component { } - tooltip={} + tooltip={} kind={kinds.INVERSE} position={tooltipPositions.BOTTOM} /> @@ -477,18 +472,17 @@ class SeriesDetails extends Component { } { - isPopulated && !!albums.length && + isPopulated && !!albumTypes.length &&
    { - albums.slice(0).reverse().map((season) => { + albumTypes.slice(0).map((season) => { return ( - ); @@ -536,11 +530,9 @@ class SeriesDetails extends Component { } } -SeriesDetails.propTypes = { +ArtistDetails.propTypes = { id: PropTypes.number.isRequired, foreignArtistId: PropTypes.string.isRequired, - tvMazeId: PropTypes.number, - imdbId: PropTypes.string, artistName: PropTypes.string.isRequired, ratings: PropTypes.object.isRequired, sizeOnDisk: PropTypes.number.isRequired, @@ -548,7 +540,6 @@ SeriesDetails.propTypes = { qualityProfileId: PropTypes.number.isRequired, monitored: PropTypes.bool.isRequired, status: PropTypes.string.isRequired, - network: PropTypes.string, overview: PropTypes.string.isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired, albums: PropTypes.arrayOf(PropTypes.object).isRequired, @@ -560,10 +551,10 @@ SeriesDetails.propTypes = { isPopulated: PropTypes.bool.isRequired, episodesError: PropTypes.object, episodeFilesError: PropTypes.object, - previousSeries: PropTypes.object.isRequired, - nextSeries: PropTypes.object.isRequired, + previousArtist: PropTypes.object.isRequired, + nextArtist: PropTypes.object.isRequired, onRefreshPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired }; -export default SeriesDetails; +export default ArtistDetails; diff --git a/frontend/src/Artist/Details/SeriesDetailsConnector.js b/frontend/src/Artist/Details/ArtistDetailsConnector.js similarity index 83% rename from frontend/src/Artist/Details/SeriesDetailsConnector.js rename to frontend/src/Artist/Details/ArtistDetailsConnector.js index 7ec84f95e..d223ea1d7 100644 --- a/frontend/src/Artist/Details/SeriesDetailsConnector.js +++ b/frontend/src/Artist/Details/ArtistDetailsConnector.js @@ -11,7 +11,7 @@ import { fetchEpisodeFiles, clearEpisodeFiles } from 'Store/Actions/episodeFileA import { fetchQueueDetails, clearQueueDetails } from 'Store/Actions/queueActions'; import { executeCommand } from 'Store/Actions/commandActions'; import * as commandNames from 'Commands/commandNames'; -import SeriesDetails from './SeriesDetails'; +import ArtistDetails from './ArtistDetails'; function createMapStateToProps() { return createSelector( @@ -21,7 +21,7 @@ function createMapStateToProps() { createAllArtistSelector(), createCommandsSelector(), (nameSlug, episodes, episodeFiles, allSeries, commands) => { - const sortedArtist = _.orderBy(allSeries, 'sortTitle'); + const sortedArtist = _.orderBy(allSeries, 'sortName'); const seriesIndex = _.findIndex(sortedArtist, { nameSlug }); const series = sortedArtist[seriesIndex]; @@ -29,15 +29,15 @@ function createMapStateToProps() { return {}; } - 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 previousArtist = sortedArtist[seriesIndex - 1] || _.last(sortedArtist); + const nextArtist = sortedArtist[seriesIndex + 1] || _.first(sortedArtist); + const isArtistRefreshing = !!findCommand(commands, { name: commandNames.REFRESH_ARTIST, artistId: series.id }); + const allArtistRefreshing = _.some(commands, (command) => command.name === commandNames.REFRESH_ARTIST && !command.body.artistId); + const isRefreshing = isArtistRefreshing || allArtistRefreshing; 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_ARTIST }); - const isRenamingSeries = !!(isRenamingSeriesCommand && isRenamingSeriesCommand.body.artistId.indexOf(series.id) > -1); + const isRenamingArtistCommand = findCommand(commands, { name: commandNames.RENAME_ARTIST }); + const isRenamingArtist = !!(isRenamingArtistCommand && isRenamingArtistCommand.body.artistId.indexOf(series.id) > -1); const isFetching = episodes.isFetching || episodeFiles.isFetching; const isPopulated = episodes.isPopulated && episodeFiles.isPopulated; @@ -58,13 +58,13 @@ function createMapStateToProps() { isRefreshing, isSearching, isRenamingFiles, - isRenamingSeries, + isRenamingArtist, isFetching, isPopulated, episodesError, episodeFilesError, - previousSeries, - nextSeries + previousArtist, + nextArtist }; } ); @@ -80,7 +80,7 @@ const mapDispatchToProps = { executeCommand }; -class SeriesDetailsConnector extends Component { +class ArtistDetailsConnector extends Component { // // Lifecycle @@ -94,13 +94,13 @@ class SeriesDetailsConnector extends Component { id, isRefreshing, isRenamingFiles, - isRenamingSeries + isRenamingArtist } = this.props; if ( (prevProps.isRefreshing && !isRefreshing) || (prevProps.isRenamingFiles && !isRenamingFiles) || - (prevProps.isRenamingSeries && !isRenamingSeries) + (prevProps.isRenamingArtist && !isRenamingArtist) ) { this._populate(); } @@ -157,7 +157,7 @@ class SeriesDetailsConnector extends Component { render() { return ( - + + + + +
    + ); +} + +ArtistDetailsLinks.propTypes = { + foreignArtistId: PropTypes.string.isRequired +}; + +export default ArtistDetailsLinks; diff --git a/frontend/src/Artist/Details/SeriesDetailsPageConnector.js b/frontend/src/Artist/Details/ArtistDetailsPageConnector.js similarity index 87% rename from frontend/src/Artist/Details/SeriesDetailsPageConnector.js rename to frontend/src/Artist/Details/ArtistDetailsPageConnector.js index aa6f29f5d..29817c512 100644 --- a/frontend/src/Artist/Details/SeriesDetailsPageConnector.js +++ b/frontend/src/Artist/Details/ArtistDetailsPageConnector.js @@ -6,7 +6,7 @@ import { createSelector } from 'reselect'; import { push } from 'react-router-redux'; import createAllArtistSelector from 'Store/Selectors/createAllArtistSelector'; import NotFound from 'Components/NotFound'; -import SeriesDetailsConnector from './SeriesDetailsConnector'; +import ArtistDetailsConnector from './ArtistDetailsConnector'; function createMapStateToProps() { return createSelector( @@ -31,7 +31,7 @@ const mapDispatchToProps = { push }; -class SeriesDetailsPageConnector extends Component { +class ArtistDetailsPageConnector extends Component { // // Lifecycle @@ -60,17 +60,17 @@ class SeriesDetailsPageConnector extends Component { } return ( - ); } } -SeriesDetailsPageConnector.propTypes = { +ArtistDetailsPageConnector.propTypes = { nameSlug: PropTypes.string, match: PropTypes.shape({ params: PropTypes.shape({ nameSlug: PropTypes.string.isRequired }).isRequired }).isRequired, push: PropTypes.func.isRequired }; -export default connect(createMapStateToProps, mapDispatchToProps)(SeriesDetailsPageConnector); +export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsPageConnector); diff --git a/frontend/src/Artist/Details/SeriesDetailsSeason.css b/frontend/src/Artist/Details/ArtistDetailsSeason.css similarity index 91% rename from frontend/src/Artist/Details/SeriesDetailsSeason.css rename to frontend/src/Artist/Details/ArtistDetailsSeason.css index 31a9da431..4cb0065e9 100644 --- a/frontend/src/Artist/Details/SeriesDetailsSeason.css +++ b/frontend/src/Artist/Details/ArtistDetailsSeason.css @@ -1,4 +1,4 @@ -.season { +.albumType { margin-bottom: 20px; border: 1px solid $borderColor; border-radius: 4px; @@ -17,11 +17,17 @@ font-size: 24px; } -.seasonNumber { - margin-right: 10px; +.albumTypeLabel { + margin-right: 5px; margin-left: 5px; } +.albumCount { + font-size: 18px; + font-style: italic; + color: #8895aa; +} + .episodeCountContainer { margin-left: 10px; vertical-align: text-bottom; @@ -38,7 +44,7 @@ .left { display: flex; align-items: center; - flex: 0 1 600px; + flex: 0 1 300px; } .left, diff --git a/frontend/src/Artist/Details/SeriesDetailsSeason.js b/frontend/src/Artist/Details/ArtistDetailsSeason.js similarity index 66% rename from frontend/src/Artist/Details/SeriesDetailsSeason.js rename to frontend/src/Artist/Details/ArtistDetailsSeason.js index 77858f53f..224d6d442 100644 --- a/frontend/src/Artist/Details/SeriesDetailsSeason.js +++ b/frontend/src/Artist/Details/ArtistDetailsSeason.js @@ -7,9 +7,7 @@ import getToggledRange from 'Utilities/Table/getToggledRange'; import { align, icons, kinds, sizes } from 'Helpers/Props'; import Icon from 'Components/Icon'; import IconButton from 'Components/Link/IconButton'; -import Label from 'Components/Label'; import Link from 'Components/Link/Link'; -import MonitorToggleButton from 'Components/MonitorToggleButton'; import SpinnerIcon from 'Components/SpinnerIcon'; import SpinnerIconButton from 'Components/Link/SpinnerIconButton'; import Menu from 'Components/Menu/Menu'; @@ -20,22 +18,10 @@ import Table from 'Components/Table/Table'; import TableBody from 'Components/Table/TableBody'; import EpisodeFileEditorModal from 'EpisodeFile/Editor/EpisodeFileEditorModal'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; -import EpisodeRowConnector from './EpisodeRowConnector'; -import styles from './SeriesDetailsSeason.css'; +import AlbumRowConnector from './AlbumRowConnector'; +import styles from './ArtistDetailsSeason.css'; -function getEpisodeCountKind(monitored, episodeFileCount, episodeCount) { - if (episodeFileCount === episodeCount && episodeCount > 0) { - return kinds.SUCCESS; - } - - if (!monitored) { - return kinds.WARNING; - } - - return kinds.DANGER; -} - -class SeriesDetailsSeason extends Component { +class ArtistDetailsSeason extends Component { // // Lifecycle @@ -65,7 +51,7 @@ class SeriesDetailsSeason extends Component { _expandByDefault() { const { - albumId, + name, onExpandPress, items } = this.props; @@ -75,7 +61,7 @@ class SeriesDetailsSeason extends Component { isAfter(item.airDateUtc, { days: -30 }); }); - onExpandPress(albumId, expand && albumId > 0); + onExpandPress(name, expand && name > 0); } // @@ -99,29 +85,29 @@ class SeriesDetailsSeason extends Component { onExpandPress = () => { const { - albumId, + name, isExpanded } = this.props; - this.props.onExpandPress(albumId, !isExpanded); + this.props.onExpandPress(name, !isExpanded); } - onMonitorEpisodePress = (episodeId, monitored, { shiftKey }) => { + onMonitorAlbumPress = (albumId, monitored, { shiftKey }) => { const lastToggled = this.state.lastToggledEpisode; - const episodeIds = [episodeId]; + const albumIds = [albumId]; if (shiftKey && lastToggled) { - const { lower, upper } = getToggledRange(this.props.items, episodeId, lastToggled); + const { lower, upper } = getToggledRange(this.props.items, albumId, lastToggled); const items = this.props.items; for (let i = lower; i < upper; i++) { - episodeIds.push(items[i].id); + albumIds.push(items[i].id); } } - this.setState({ lastToggledEpisode: episodeId }); + this.setState({ lastToggledEpisode: albumId }); - this.props.onMonitorEpisodePress(_.uniq(episodeIds), monitored); + this.props.onMonitorAlbumPress(_.uniq(albumIds), monitored); } // @@ -130,29 +116,19 @@ class SeriesDetailsSeason extends Component { render() { const { artistId, - monitored, - title, - releaseDate, - albumId, - statistics, + label, items, columns, isSaving, isExpanded, isSearching, - seriesMonitored, + artistMonitored, isSmallScreen, onTableOptionChange, onMonitorSeasonPress, onSearchPress } = this.props; - const { - trackCount, - trackFileCount, - totalTrackCount - } = statistics; - const { isOrganizeModalOpen, isManageEpisodesOpen @@ -160,37 +136,22 @@ class SeriesDetailsSeason extends Component { return (
    - - { - albumId === 0 ? - - Specials - : - - {title} +
    + + {label} + + + + ({items.length} Releases) +
    } -
    + + { !isSmallScreen &&   @@ -266,37 +234,62 @@ class SeriesDetailsSeason extends Component { onPress={onSearchPress} /> - - -
    }
    +
    + { + isExpanded && +
    + { + items.length ? + + + { + items.map((item) => { + return ( + + ); + }) + } + +
    : + +
    + No albums in this group +
    + } +
    + +
    +
    + } +
    + @@ -304,33 +297,22 @@ class SeriesDetailsSeason extends Component { } } -SeriesDetailsSeason.propTypes = { +ArtistDetailsSeason.propTypes = { artistId: PropTypes.number.isRequired, - monitored: PropTypes.bool.isRequired, - title: PropTypes.string.isRequired, - releaseDate: PropTypes.string.isRequired, - albumId: PropTypes.number.isRequired, - statistics: PropTypes.object.isRequired, + name: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired, columns: PropTypes.arrayOf(PropTypes.object).isRequired, isSaving: PropTypes.bool, isExpanded: PropTypes.bool, isSearching: PropTypes.bool.isRequired, - seriesMonitored: PropTypes.bool.isRequired, + artistMonitored: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired, onTableOptionChange: PropTypes.func.isRequired, onMonitorSeasonPress: PropTypes.func.isRequired, onExpandPress: PropTypes.func.isRequired, - onMonitorEpisodePress: PropTypes.func.isRequired, + onMonitorAlbumPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired }; -SeriesDetailsSeason.defaultProps = { - statistics: { - trackFileCount: 0, - totalTrackCount: 0, - percentOfTracks: 0 - } -}; - -export default SeriesDetailsSeason; +export default ArtistDetailsSeason; diff --git a/frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js b/frontend/src/Artist/Details/ArtistDetailsSeasonConnector.js similarity index 74% rename from frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js rename to frontend/src/Artist/Details/ArtistDetailsSeasonConnector.js index 6035eb73e..f24fc8fc6 100644 --- a/frontend/src/Artist/Details/SeriesDetailsSeasonConnector.js +++ b/frontend/src/Artist/Details/ArtistDetailsSeasonConnector.js @@ -11,30 +11,30 @@ 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'; -import SeriesDetailsSeason from './SeriesDetailsSeason'; +import ArtistDetailsSeason from './ArtistDetailsSeason'; function createMapStateToProps() { return createSelector( - (state, { seasonNumber }) => seasonNumber, + (state, { label }) => label, (state) => state.episodes, createArtistSelector(), createCommandsSelector(), createDimensionsSelector(), - (seasonNumber, episodes, series, commands, dimensions) => { + (label, episodes, series, commands, dimensions) => { const isSearching = !!findCommand(commands, { name: commandNames.SEASON_SEARCH, artistId: series.id, - seasonNumber + label }); - const episodesInSeason = _.filter(episodes.items, { seasonNumber }); - const sortedEpisodes = _.orderBy(episodesInSeason, 'episodeNumber', 'desc'); + const episodesInSeason = _.filter(episodes.items, { albumType: label }); + const sortedEpisodes = _.orderBy(episodesInSeason, 'releaseDate', 'desc'); return { items: sortedEpisodes, columns: episodes.columns, isSearching, - seriesMonitored: series.monitored, + artistMonitored: series.monitored, isSmallScreen: dimensions.isSmallScreen }; } @@ -48,7 +48,7 @@ const mapDispatchToProps = { executeCommand }; -class SeriesDetailsSeasonConnector extends Component { +class ArtistDetailsSeasonConnector extends Component { // // Listeners @@ -59,33 +59,29 @@ class SeriesDetailsSeasonConnector extends Component { onMonitorSeasonPress = (monitored) => { const { - artistId, - albumId + artistId } = this.props; this.props.toggleSeasonMonitored({ artistId, - albumId, monitored }); } onSearchPress = () => { const { - artistId, - albumId + artistId } = this.props; this.props.executeCommand({ name: commandNames.SEASON_SEARCH, - artistId, - albumIds: [albumId] + artistId }); } - onMonitorEpisodePress = (episodeIds, monitored) => { + onMonitorAlbumPress = (albumIds, monitored) => { this.props.toggleEpisodesMonitored({ - episodeIds, + albumIds, monitored }); } @@ -95,24 +91,23 @@ class SeriesDetailsSeasonConnector extends Component { render() { return ( - ); } } -SeriesDetailsSeasonConnector.propTypes = { +ArtistDetailsSeasonConnector.propTypes = { artistId: PropTypes.number.isRequired, - albumId: PropTypes.number.isRequired, toggleSeasonMonitored: PropTypes.func.isRequired, toggleEpisodesMonitored: PropTypes.func.isRequired, setEpisodesTableOption: PropTypes.func.isRequired, executeCommand: PropTypes.func.isRequired }; -export default connect(createMapStateToProps, mapDispatchToProps)(SeriesDetailsSeasonConnector); +export default connect(createMapStateToProps, mapDispatchToProps)(ArtistDetailsSeasonConnector); diff --git a/frontend/src/Artist/Details/SeriesTags.css b/frontend/src/Artist/Details/ArtistTags.css similarity index 100% rename from frontend/src/Artist/Details/SeriesTags.css rename to frontend/src/Artist/Details/ArtistTags.css diff --git a/frontend/src/Artist/Details/SeriesTags.js b/frontend/src/Artist/Details/ArtistTags.js similarity index 80% rename from frontend/src/Artist/Details/SeriesTags.js rename to frontend/src/Artist/Details/ArtistTags.js index 4897937dd..efac57d65 100644 --- a/frontend/src/Artist/Details/SeriesTags.js +++ b/frontend/src/Artist/Details/ArtistTags.js @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import { kinds, sizes } from 'Helpers/Props'; import Label from 'Components/Label'; -import styles from './SeriesTags.css'; +import styles from './ArtistTags.css'; -function SeriesTags({ tags }) { +function ArtistTags({ tags }) { return (
    { @@ -24,8 +24,8 @@ function SeriesTags({ tags }) { ); } -SeriesTags.propTypes = { +ArtistTags.propTypes = { tags: PropTypes.arrayOf(PropTypes.string).isRequired }; -export default SeriesTags; +export default ArtistTags; diff --git a/frontend/src/Artist/Details/SeriesTagsConnector.js b/frontend/src/Artist/Details/ArtistTagsConnector.js similarity index 87% rename from frontend/src/Artist/Details/SeriesTagsConnector.js rename to frontend/src/Artist/Details/ArtistTagsConnector.js index 354b2cec2..8c8b035ea 100644 --- a/frontend/src/Artist/Details/SeriesTagsConnector.js +++ b/frontend/src/Artist/Details/ArtistTagsConnector.js @@ -3,7 +3,7 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import createArtistSelector from 'Store/Selectors/createArtistSelector'; import createTagsSelector from 'Store/Selectors/createTagsSelector'; -import SeriesTags from './SeriesTags'; +import ArtistTags from './ArtistTags'; function createMapStateToProps() { return createSelector( @@ -27,4 +27,4 @@ function createMapStateToProps() { ); } -export default connect(createMapStateToProps)(SeriesTags); +export default connect(createMapStateToProps)(ArtistTags); diff --git a/frontend/src/Artist/Details/EpisodeRow.js b/frontend/src/Artist/Details/EpisodeRow.js deleted file mode 100644 index 5063db63b..000000000 --- a/frontend/src/Artist/Details/EpisodeRow.js +++ /dev/null @@ -1,266 +0,0 @@ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import MonitorToggleButton from 'Components/MonitorToggleButton'; -import RelativeDateCellConnector from 'Components/Table/Cells/RelativeDateCellConnector'; -import TableRow from 'Components/Table/TableRow'; -import TableRowCell from 'Components/Table/Cells/TableRowCell'; -import EpisodeSearchCellConnector from 'Episode/EpisodeSearchCellConnector'; -import EpisodeNumber from 'Episode/EpisodeNumber'; -import EpisodeTitleLink from 'Episode/EpisodeTitleLink'; -import EpisodeStatusConnector from 'Episode/EpisodeStatusConnector'; -import EpisodeFileLanguageConnector from 'EpisodeFile/EpisodeFileLanguageConnector'; -import MediaInfoConnector from 'EpisodeFile/MediaInfoConnector'; -import * as mediaInfoTypes from 'EpisodeFile/mediaInfoTypes'; - -import styles from './EpisodeRow.css'; - -class EpisodeRow extends Component { - - // - // Lifecycle - - constructor(props, context) { - super(props, context); - - this.state = { - isDetailsModalOpen: false - }; - } - - // - // Listeners - - onManualSearchPress = () => { - this.setState({ isDetailsModalOpen: true }); - } - - onDetailsModalClose = () => { - this.setState({ isDetailsModalOpen: false }); - } - - onMonitorEpisodePress = (monitored, options) => { - this.props.onMonitorEpisodePress(this.props.id, monitored, options); - } - - // - // Render - - render() { - const { - id, - artistId, - episodeFileId, - monitored, - seasonNumber, - episodeNumber, - absoluteEpisodeNumber, - sceneSeasonNumber, - sceneEpisodeNumber, - sceneAbsoluteEpisodeNumber, - airDateUtc, - title, - unverifiedSceneNumbering, - isSaving, - seriesMonitored, - seriesType, - episodeFilePath, - episodeFileRelativePath, - alternateTitles, - columns - } = this.props; - - return ( - - { - columns.map((column) => { - const { - name, - isVisible - } = column; - - if (!isVisible) { - return null; - } - - if (name === 'monitored') { - return ( - - - - ); - } - - if (name === 'episodeNumber') { - return ( - - - - ); - } - - if (name === 'title') { - return ( - - - - ); - } - - if (name === 'path') { - return ( - - { - episodeFilePath - } - - ); - } - - if (name === 'relativePath') { - return ( - - { - episodeFileRelativePath - } - - ); - } - - if (name === 'airDateUtc') { - return ( - - ); - } - - if (name === 'language') { - return ( - - - - ); - } - - if (name === 'audioInfo') { - return ( - - - - ); - } - - if (name === 'videoCodec') { - return ( - - - - ); - } - - if (name === 'status') { - return ( - - - - ); - } - - if (name === 'actions') { - return ( - - ); - } - - return null; - }) - } - - ); - } -} - -EpisodeRow.propTypes = { - id: PropTypes.number.isRequired, - artistId: PropTypes.number.isRequired, - episodeFileId: PropTypes.number, - monitored: PropTypes.bool.isRequired, - seasonNumber: PropTypes.number.isRequired, - episodeNumber: PropTypes.number.isRequired, - absoluteEpisodeNumber: PropTypes.number, - sceneSeasonNumber: PropTypes.number, - sceneEpisodeNumber: PropTypes.number, - sceneAbsoluteEpisodeNumber: PropTypes.number, - airDateUtc: PropTypes.string, - title: PropTypes.string.isRequired, - isSaving: PropTypes.bool, - unverifiedSceneNumbering: PropTypes.bool, - seriesMonitored: PropTypes.bool.isRequired, - seriesType: PropTypes.string.isRequired, - episodeFilePath: PropTypes.string, - episodeFileRelativePath: PropTypes.string, - mediaInfo: PropTypes.object, - alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired, - columns: PropTypes.arrayOf(PropTypes.object).isRequired, - onMonitorEpisodePress: PropTypes.func.isRequired -}; - -export default EpisodeRow; diff --git a/frontend/src/Artist/Details/SeriesDetailsLinks.js b/frontend/src/Artist/Details/SeriesDetailsLinks.js deleted file mode 100644 index d1be3d409..000000000 --- a/frontend/src/Artist/Details/SeriesDetailsLinks.js +++ /dev/null @@ -1,70 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import { kinds, sizes } from 'Helpers/Props'; -import Label from 'Components/Label'; -import Link from 'Components/Link/Link'; -import styles from './SeriesDetailsLinks.css'; - -function SeriesDetailsLinks(props) { - const { - foreignArtistId, - tvMazeId, - imdbId - } = props; - - return ( -
    - - - - { - !!tvMazeId && - - - - } - - { - !!imdbId && - - - - } -
    - ); -} - -SeriesDetailsLinks.propTypes = { - foreignArtistId: PropTypes.string.isRequired, - tvMazeId: PropTypes.number, - imdbId: PropTypes.string -}; - -export default SeriesDetailsLinks; diff --git a/frontend/src/Artist/Index/Table/ArtistStatusCell.js b/frontend/src/Artist/Index/Table/ArtistStatusCell.js index 734e50722..ab3042a10 100644 --- a/frontend/src/Artist/Index/Table/ArtistStatusCell.js +++ b/frontend/src/Artist/Index/Table/ArtistStatusCell.js @@ -27,7 +27,7 @@ function ArtistStatusCell(props) { diff --git a/frontend/src/Commands/commandNames.js b/frontend/src/Commands/commandNames.js index 47b273248..4d564afb4 100644 --- a/frontend/src/Commands/commandNames.js +++ b/frontend/src/Commands/commandNames.js @@ -7,9 +7,9 @@ export const CUTOFF_UNMET_EPISODE_SEARCH = 'CutoffUnmetEpisodeSearch'; export const DELETE_LOG_FILES = 'DeleteLogFiles'; export const DELETE_UPDATE_LOG_FILES = 'DeleteUpdateLogFiles'; export const DOWNLOADED_EPSIODES_SCAN = 'DownloadedEpisodesScan'; -export const EPISODE_SEARCH = 'EpisodeSearch'; +export const EPISODE_SEARCH = 'AlbumSearch'; export const INTERACTIVE_IMPORT = 'ManualImport'; -export const MISSING_EPISODE_SEARCH = 'MissingEpisodeSearch'; +export const MISSING_ALBUM_SEARCH = 'MissingAlbumSearch'; export const REFRESH_ARTIST = 'RefreshArtist'; export const RENAME_FILES = 'RenameFiles'; export const RENAME_ARTIST = 'RenameArtist'; diff --git a/frontend/src/Components/Page/Sidebar/PageSidebar.js b/frontend/src/Components/Page/Sidebar/PageSidebar.js index 4d616dfd0..965f7d4d6 100644 --- a/frontend/src/Components/Page/Sidebar/PageSidebar.js +++ b/frontend/src/Components/Page/Sidebar/PageSidebar.js @@ -19,7 +19,7 @@ const SIDEBAR_WIDTH = parseInt(dimensions.sidebarWidth); const links = [ { - iconName: icons.SERIES_CONTINUING, + iconName: icons.ARTIST_CONTINUING, title: 'Artist', to: '/', alias: '/series', diff --git a/frontend/src/Episode/EpisodeDetailsModalContent.js b/frontend/src/Episode/EpisodeDetailsModalContent.js index 60e5d6d81..24ca04c16 100644 --- a/frontend/src/Episode/EpisodeDetailsModalContent.js +++ b/frontend/src/Episode/EpisodeDetailsModalContent.js @@ -11,7 +11,6 @@ import MonitorToggleButton from 'Components/MonitorToggleButton'; import EpisodeSummaryConnector from './Summary/EpisodeSummaryConnector'; import EpisodeHistoryConnector from './History/EpisodeHistoryConnector'; import EpisodeSearchConnector from './Search/EpisodeSearchConnector'; -import SeasonEpisodeNumber from './SeasonEpisodeNumber'; import styles from './EpisodeDetailsModalContent.css'; const tabs = [ @@ -47,26 +46,22 @@ class EpisodeDetailsModalContent extends Component { const { episodeId, episodeEntity, - episodeFileId, artistId, seriesTitle, - titleSlug, - seriesMonitored, - seriesType, - seasonNumber, - episodeNumber, - absoluteEpisodeNumber, + nameSlug, + albumLabel, + artistMonitored, episodeTitle, - airDate, + releaseDate, monitored, isSaving, showOpenSeriesButton, startInteractiveSearch, - onMonitorEpisodePress, + onMonitorAlbumPress, onModalClose } = this.props; - const seriesLink = `/artist/${titleSlug}`; + const seriesLink = `/artist/${nameSlug}`; return ( @@ -89,16 +84,6 @@ class EpisodeDetailsModalContent extends Component { - - - - - - {episodeTitle} @@ -137,7 +122,8 @@ class EpisodeDetailsModalContent extends Component { @@ -150,7 +136,7 @@ class EpisodeDetailsModalContent extends Component { @@ -166,7 +152,7 @@ class EpisodeDetailsModalContent extends Component { to={seriesLink} onPress={onModalClose} > - Open Series + Open Artist } @@ -184,28 +170,25 @@ class EpisodeDetailsModalContent extends Component { EpisodeDetailsModalContent.propTypes = { episodeId: PropTypes.number.isRequired, episodeEntity: PropTypes.string.isRequired, - episodeFileId: PropTypes.number, artistId: PropTypes.number.isRequired, seriesTitle: PropTypes.string.isRequired, - titleSlug: PropTypes.string.isRequired, - seriesMonitored: PropTypes.bool.isRequired, - seriesType: PropTypes.string.isRequired, - seasonNumber: PropTypes.number.isRequired, - episodeNumber: PropTypes.number.isRequired, - absoluteEpisodeNumber: PropTypes.number, - airDate: PropTypes.string.isRequired, + nameSlug: PropTypes.string.isRequired, + artistMonitored: PropTypes.bool.isRequired, + releaseDate: PropTypes.string.isRequired, + albumLabel: PropTypes.string.isRequired, episodeTitle: PropTypes.string.isRequired, monitored: PropTypes.bool.isRequired, isSaving: PropTypes.bool, showOpenSeriesButton: PropTypes.bool, selectedTab: PropTypes.string.isRequired, startInteractiveSearch: PropTypes.bool.isRequired, - onMonitorEpisodePress: PropTypes.func.isRequired, + onMonitorAlbumPress: PropTypes.func.isRequired, onModalClose: PropTypes.func.isRequired }; EpisodeDetailsModalContent.defaultProps = { selectedTab: 'details', + albumLabel: 'Unknown', episodeEntity: episodeEntities.EPISODES, startInteractiveSearch: false }; diff --git a/frontend/src/Episode/EpisodeDetailsModalContentConnector.js b/frontend/src/Episode/EpisodeDetailsModalContentConnector.js index 59129e142..f32da84fe 100644 --- a/frontend/src/Episode/EpisodeDetailsModalContentConnector.js +++ b/frontend/src/Episode/EpisodeDetailsModalContentConnector.js @@ -15,16 +15,16 @@ function createMapStateToProps() { createArtistSelector(), (episode, series) => { const { - title: seriesTitle, - titleSlug, - monitored: seriesMonitored, + artistName: seriesTitle, + nameSlug, + monitored: artistMonitored, seriesType } = series; return { seriesTitle, - titleSlug, - seriesMonitored, + nameSlug, + artistMonitored, seriesType, ...episode }; @@ -52,7 +52,7 @@ class EpisodeDetailsModalContentConnector extends Component { // // Listeners - onMonitorEpisodePress = (monitored) => { + onMonitorAlbumPress = (monitored) => { const { episodeId, episodeEntity @@ -72,7 +72,7 @@ class EpisodeDetailsModalContentConnector extends Component { return ( ); } diff --git a/frontend/src/Episode/EpisodeSearchCellConnector.js b/frontend/src/Episode/EpisodeSearchCellConnector.js index cc4cce8ee..89fdd9925 100644 --- a/frontend/src/Episode/EpisodeSearchCellConnector.js +++ b/frontend/src/Episode/EpisodeSearchCellConnector.js @@ -21,11 +21,11 @@ function createMapStateToProps() { return false; } - return command.body.episodeIds.indexOf(episodeId) > -1; + return command.body.albumIds.indexOf(episodeId) > -1; }); return { - seriesMonitored: series.monitored, + artistMonitored: series.monitored, seriesType: series.seriesType, isSearching }; @@ -38,7 +38,7 @@ function createMapDispatchToProps(dispatch, props) { onSearchPress(name, path) { dispatch(executeCommand({ name: commandNames.EPISODE_SEARCH, - episodeIds: [props.episodeId] + albumIds: [props.episodeId] })); } }; diff --git a/frontend/src/Episode/History/EpisodeHistory.js b/frontend/src/Episode/History/EpisodeHistory.js index fc284096e..c2690275d 100644 --- a/frontend/src/Episode/History/EpisodeHistory.js +++ b/frontend/src/Episode/History/EpisodeHistory.js @@ -61,13 +61,13 @@ class EpisodeHistory extends Component { if (!isFetching && !!error) { return ( -
    Unable to load episode history.
    +
    Unable to load album history.
    ); } if (isPopulated && !hasItems && !error) { return ( -
    No episode history.
    +
    No album history.
    ); } diff --git a/frontend/src/Episode/Search/EpisodeSearchConnector.js b/frontend/src/Episode/Search/EpisodeSearchConnector.js index 748cb9660..8f511bfa0 100644 --- a/frontend/src/Episode/Search/EpisodeSearchConnector.js +++ b/frontend/src/Episode/Search/EpisodeSearchConnector.js @@ -47,7 +47,7 @@ class EpisodeSearchConnector extends Component { onQuickSearchPress = () => { this.props.executeCommand({ name: commandNames.EPISODE_SEARCH, - episodeIds: [this.props.episodeId] + albumIds: [this.props.albumId] }); this.props.onModalClose(); @@ -80,7 +80,7 @@ class EpisodeSearchConnector extends Component { } EpisodeSearchConnector.propTypes = { - episodeId: PropTypes.number.isRequired, + albumId: PropTypes.number.isRequired, isPopulated: PropTypes.bool.isRequired, startInteractiveSearch: PropTypes.bool.isRequired, onModalClose: PropTypes.func.isRequired, diff --git a/frontend/src/Episode/Search/InteractiveEpisodeSearchConnector.js b/frontend/src/Episode/Search/InteractiveEpisodeSearchConnector.js index 20c93813c..cff2595c8 100644 --- a/frontend/src/Episode/Search/InteractiveEpisodeSearchConnector.js +++ b/frontend/src/Episode/Search/InteractiveEpisodeSearchConnector.js @@ -34,7 +34,7 @@ class InteractiveEpisodeSearchConnector extends Component { componentDidMount() { const { - episodeId, + albumId, isPopulated } = this.props; @@ -43,7 +43,7 @@ class InteractiveEpisodeSearchConnector extends Component { if (!isPopulated) { this.props.fetchReleases({ - episodeId + albumId }); } } @@ -74,7 +74,7 @@ class InteractiveEpisodeSearchConnector extends Component { } InteractiveEpisodeSearchConnector.propTypes = { - episodeId: PropTypes.number.isRequired, + albumId: PropTypes.number.isRequired, isPopulated: PropTypes.bool.isRequired, fetchReleases: PropTypes.func.isRequired, setReleasesSort: PropTypes.func.isRequired, diff --git a/frontend/src/Episode/Summary/EpisodeAiring.js b/frontend/src/Episode/Summary/EpisodeAiring.js index 54ca64325..6ec8d887b 100644 --- a/frontend/src/Episode/Summary/EpisodeAiring.js +++ b/frontend/src/Episode/Summary/EpisodeAiring.js @@ -10,8 +10,8 @@ import Label from 'Components/Label'; function EpisodeAiring(props) { const { - airDateUtc, - network, + releaseDate, + albumLabel, shortDateFormat, showRelativeDates, timeFormat @@ -22,11 +22,11 @@ function EpisodeAiring(props) { kind={kinds.INFO} size={sizes.MEDIUM} > - {network} + {albumLabel} ); - if (!airDateUtc) { + if (!releaseDate) { return ( TBA on {networkLabel} @@ -34,50 +34,48 @@ function EpisodeAiring(props) { ); } - const time = formatTime(airDateUtc, timeFormat); - if (!showRelativeDates) { return ( - {moment(airDateUtc).format(shortDateFormat)} at {time} on {networkLabel} + {moment(releaseDate).format(shortDateFormat)} on {networkLabel} ); } - if (isToday(airDateUtc)) { + if (isToday(releaseDate)) { return ( - {time} on {networkLabel} + Today on {networkLabel} ); } - if (isTomorrow(airDateUtc)) { + if (isTomorrow(releaseDate)) { return ( - Tomorrow at {time} on {networkLabel} + Tomorrow on {networkLabel} ); } - if (isInNextWeek(airDateUtc)) { + if (isInNextWeek(releaseDate)) { return ( - {moment(airDateUtc).format('dddd')} at {time} on {networkLabel} + {moment(releaseDate).format('dddd')} on {networkLabel} ); } return ( - {moment(airDateUtc).format(shortDateFormat)} at {time} on {networkLabel} + {moment(releaseDate).format(shortDateFormat)} on {networkLabel} ); } EpisodeAiring.propTypes = { - airDateUtc: PropTypes.string.isRequired, - network: PropTypes.string.isRequired, + releaseDate: PropTypes.string.isRequired, + albumLabel: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired, showRelativeDates: PropTypes.bool.isRequired, timeFormat: PropTypes.string.isRequired diff --git a/frontend/src/Episode/Summary/EpisodeSummary.js b/frontend/src/Episode/Summary/EpisodeSummary.js index 8b8388744..f6ce4d747 100644 --- a/frontend/src/Episode/Summary/EpisodeSummary.js +++ b/frontend/src/Episode/Summary/EpisodeSummary.js @@ -45,9 +45,9 @@ class EpisodeSummary extends Component { render() { const { qualityProfileId, - network, overview, - airDateUtc, + releaseDate, + albumLabel, path, size, quality, @@ -59,11 +59,11 @@ class EpisodeSummary extends Component { return (
    - Airs + Releases
    @@ -84,7 +84,7 @@ class EpisodeSummary extends Component { { hasOverview ? overview : - 'No episode overview.' + 'No album overview.' }
    @@ -151,11 +151,10 @@ class EpisodeSummary extends Component { } EpisodeSummary.propTypes = { - episodeFileId: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired, - network: PropTypes.string.isRequired, overview: PropTypes.string, - airDateUtc: PropTypes.string.isRequired, + albumLabel: PropTypes.string, + releaseDate: PropTypes.string.isRequired, path: PropTypes.string, size: PropTypes.number, quality: PropTypes.object, diff --git a/frontend/src/Helpers/Props/icons.js b/frontend/src/Helpers/Props/icons.js index fbd0b8c6d..1ca253a4b 100644 --- a/frontend/src/Helpers/Props/icons.js +++ b/frontend/src/Helpers/Props/icons.js @@ -67,8 +67,8 @@ export const RSS = 'fa fa-rss'; export const SAVE = 'fa fa-floppy-o'; export const SCHEDULED = 'fa fa-clock-o'; export const SEARCH = 'fa fa-search'; -export const SERIES_CONTINUING = 'fa fa-play'; -export const SERIES_ENDED = 'fa fa-stop'; +export const ARTIST_CONTINUING = 'fa fa-play'; +export const ARTIST_ENDED = 'fa fa-stop'; export const SETTINGS = 'fa fa-cogs'; export const SHUTDOWN = 'fa fa-power-off'; export const SORT = 'fa fa-sort'; diff --git a/frontend/src/Store/Actions/episodeActionHandlers.js b/frontend/src/Store/Actions/episodeActionHandlers.js index 1fa8034bd..52a441321 100644 --- a/frontend/src/Store/Actions/episodeActionHandlers.js +++ b/frontend/src/Store/Actions/episodeActionHandlers.js @@ -9,7 +9,7 @@ import { updateItem } from './baseActions'; const section = 'episodes'; const episodeActionHandlers = { - [types.FETCH_EPISODES]: createFetchHandler(section, '/track'), + [types.FETCH_EPISODES]: createFetchHandler(section, '/album'), [types.TOGGLE_EPISODE_MONITORED]: function(payload) { return function(dispatch, getState) { @@ -28,7 +28,7 @@ const episodeActionHandlers = { })); const promise = $.ajax({ - url: `/episode/${id}`, + url: `/album/${id}`, method: 'PUT', data: JSON.stringify({ monitored }), dataType: 'json' @@ -56,7 +56,7 @@ const episodeActionHandlers = { [types.TOGGLE_EPISODES_MONITORED]: function(payload) { return function(dispatch, getState) { const { - episodeIds, + albumIds, episodeEntity = episodeEntities.EPISODES, monitored } = payload; @@ -64,7 +64,7 @@ const episodeActionHandlers = { const episodeSection = _.last(episodeEntity.split('.')); dispatch(batchActions( - episodeIds.map((episodeId) => { + albumIds.map((episodeId) => { return updateItem({ id: episodeId, section: episodeSection, @@ -74,15 +74,15 @@ const episodeActionHandlers = { )); const promise = $.ajax({ - url: '/episode/monitor', + url: '/album/monitor', method: 'PUT', - data: JSON.stringify({ episodeIds, monitored }), + data: JSON.stringify({ albumIds, monitored }), dataType: 'json' }); promise.done((data) => { dispatch(batchActions( - episodeIds.map((episodeId) => { + albumIds.map((episodeId) => { return updateItem({ id: episodeId, section: episodeSection, @@ -95,7 +95,7 @@ const episodeActionHandlers = { promise.fail((xhr) => { dispatch(batchActions( - episodeIds.map((episodeId) => { + albumIds.map((episodeId) => { return updateItem({ id: episodeId, section: episodeSection, diff --git a/frontend/src/Store/Reducers/episodeReducers.js b/frontend/src/Store/Reducers/episodeReducers.js index 4c21e4fc3..479ea3fe0 100644 --- a/frontend/src/Store/Reducers/episodeReducers.js +++ b/frontend/src/Store/Reducers/episodeReducers.js @@ -11,7 +11,7 @@ export const defaultState = { isFetching: false, isPopulated: false, error: null, - sortKey: 'episodeNumber', + sortKey: 'releaseDate', sortDirection: sortDirections.DESCENDING, items: [], @@ -22,11 +22,6 @@ export const defaultState = { isVisible: true, isModifiable: false }, - { - name: 'episodeNumber', - label: '#', - isVisible: true - }, { name: 'title', label: 'Title', @@ -38,28 +33,18 @@ export const defaultState = { isVisible: false }, { - name: 'relativePath', - label: 'Relative Path', - isVisible: false - }, - { - name: 'airDateUtc', - label: 'Air Date', + name: 'releaseDate', + label: 'Release Date', isVisible: true }, { - name: 'language', - label: 'Language', - isVisible: false - }, - { - name: 'audioInfo', - label: 'Audio Info', + name: 'trackCount', + label: 'Track Count', isVisible: false }, { - name: 'videoCodec', - label: 'Video Codec', + name: 'duration', + label: 'Duration', isVisible: false }, { diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js index 473c041e9..8a03e366d 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetConnector.js @@ -110,7 +110,7 @@ class CutoffUnmetConnector extends Component { onSearchSelectedPress = (selected) => { this.props.executeCommand({ name: commandNames.EPISODE_SEARCH, - episodeIds: selected + albumIds: selected }); } diff --git a/frontend/src/Wanted/Missing/MissingConnector.js b/frontend/src/Wanted/Missing/MissingConnector.js index 9142ad001..be74f4f12 100644 --- a/frontend/src/Wanted/Missing/MissingConnector.js +++ b/frontend/src/Wanted/Missing/MissingConnector.js @@ -18,7 +18,7 @@ function createMapStateToProps() { createCommandsSelector(), (missing, commands) => { const isSearchingForAlbums = _.some(commands, { name: commandNames.EPISODE_SEARCH }); - const isSearchingForMissingAlbums = _.some(commands, { name: commandNames.MISSING_EPISODE_SEARCH }); + const isSearchingForMissingAlbums = _.some(commands, { name: commandNames.MISSING_ALBUM_SEARCH }); return { isSearchingForAlbums, @@ -100,7 +100,7 @@ class MissingConnector extends Component { onSearchSelectedPress = (selected) => { this.props.executeCommand({ name: commandNames.EPISODE_SEARCH, - episodeIds: selected + albumIds: selected }); } @@ -118,7 +118,7 @@ class MissingConnector extends Component { onSearchAllMissingPress = () => { this.props.executeCommand({ - name: commandNames.MISSING_EPISODE_SEARCH + name: commandNames.MISSING_ALBUM_SEARCH }); }