import _ from 'lodash'; import moment from 'moment'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import TextTruncate from 'react-text-truncate'; import Alert from 'Components/Alert'; import HeartRating from 'Components/HeartRating'; import Icon from 'Components/Icon'; import Label from 'Components/Label'; import IconButton from 'Components/Link/IconButton'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import Measure from 'Components/Measure'; import MetadataAttribution from 'Components/MetadataAttribution'; import MonitorToggleButton from 'Components/MonitorToggleButton'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import Popover from 'Components/Tooltip/Popover'; import Tooltip from 'Components/Tooltip/Tooltip'; import { align, icons, kinds, sizes, sortDirections, tooltipPositions } from 'Helpers/Props'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import DeleteSeriesModal from 'Series/Delete/DeleteSeriesModal'; import EditSeriesModalConnector from 'Series/Edit/EditSeriesModalConnector'; import SeriesHistoryModal from 'Series/History/SeriesHistoryModal'; import MonitoringOptionsModal from 'Series/MonitoringOptions/MonitoringOptionsModal'; import SeriesPoster from 'Series/SeriesPoster'; import { getSeriesStatusDetails } from 'Series/SeriesStatus'; import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector'; import fonts from 'Styles/Variables/fonts'; import formatBytes from 'Utilities/Number/formatBytes'; import translate from 'Utilities/String/translate'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; import SeriesAlternateTitles from './SeriesAlternateTitles'; import SeriesDetailsLinks from './SeriesDetailsLinks'; import SeriesDetailsSeasonConnector from './SeriesDetailsSeasonConnector'; import SeriesGenres from './SeriesGenres'; import SeriesTagsConnector from './SeriesTagsConnector'; import styles from './SeriesDetails.css'; const defaultFontSize = parseInt(fonts.defaultFontSize); const lineHeight = parseFloat(fonts.lineHeight); function getFanartUrl(images) { return _.find(images, { coverType: 'fanart' })?.url; } function getExpandedState(newState) { return { allExpanded: newState.allSelected, allCollapsed: newState.allUnselected, expandedState: newState.selectedState }; } function getDateYear(date) { const dateDate = moment.utc(date); return dateDate.format('YYYY'); } class SeriesDetails extends Component { // // Lifecycle constructor(props, context) { super(props, context); this.state = { isOrganizeModalOpen: false, isManageEpisodesOpen: false, isEditSeriesModalOpen: false, isDeleteSeriesModalOpen: false, isSeriesHistoryModalOpen: false, isMonitorOptionsModalOpen: false, allExpanded: false, allCollapsed: false, expandedState: {}, overviewHeight: 0 }; } // // Listeners onOrganizePress = () => { this.setState({ isOrganizeModalOpen: true }); }; onOrganizeModalClose = () => { this.setState({ isOrganizeModalOpen: false }); }; onManageEpisodesPress = () => { this.setState({ isManageEpisodesOpen: true }); }; onManageEpisodesModalClose = () => { this.setState({ isManageEpisodesOpen: false }); }; onEditSeriesPress = () => { this.setState({ isEditSeriesModalOpen: true }); }; onEditSeriesModalClose = () => { this.setState({ isEditSeriesModalOpen: false }); }; onDeleteSeriesPress = () => { this.setState({ isEditSeriesModalOpen: false, isDeleteSeriesModalOpen: true }); }; onDeleteSeriesModalClose = () => { this.setState({ isDeleteSeriesModalOpen: false }); }; onSeriesHistoryPress = () => { this.setState({ isSeriesHistoryModalOpen: true }); }; onSeriesHistoryModalClose = () => { this.setState({ isSeriesHistoryModalOpen: false }); }; onMonitorOptionsPress = () => { this.setState({ isMonitorOptionsModalOpen: true }); }; onMonitorOptionsClose = () => { this.setState({ isMonitorOptionsModalOpen: false }); }; onExpandAllPress = () => { const { allExpanded, expandedState } = this.state; this.setState(getExpandedState(selectAll(expandedState, !allExpanded))); }; onExpandPress = (seasonNumber, isExpanded) => { this.setState((state) => { const convertedState = { allSelected: state.allExpanded, allUnselected: state.allCollapsed, selectedState: state.expandedState }; const newState = toggleSelected(convertedState, [], seasonNumber, isExpanded, false); return getExpandedState(newState); }); }; onMeasure = ({ height }) => { this.setState({ overviewHeight: height }); }; // // Render render() { const { id, tvdbId, tvMazeId, imdbId, title, runtime, ratings, path, statistics, qualityProfileId, monitored, status, network, overview, images, seasons, alternateTitles, genres, tags, year, lastAired, isSaving, isRefreshing, isSearching, isFetching, isPopulated, episodesError, episodeFilesError, hasEpisodes, hasMonitoredEpisodes, hasEpisodeFiles, previousSeries, nextSeries, onMonitorTogglePress, onRefreshPress, onSearchPress } = this.props; const { episodeFileCount, sizeOnDisk } = statistics; const { isOrganizeModalOpen, isManageEpisodesOpen, isEditSeriesModalOpen, isDeleteSeriesModalOpen, isSeriesHistoryModalOpen, isMonitorOptionsModalOpen, allExpanded, allCollapsed, expandedState, overviewHeight } = this.state; const statusDetails = getSeriesStatusDetails(status); const runningYears = statusDetails.title === translate('Ended') ? `${year}-${getDateYear(lastAired)}` : `${year}-`; let episodeFilesCountMessage = translate('SeriesDetailsNoEpisodeFiles'); if (episodeFileCount === 1) { episodeFilesCountMessage = translate('SeriesDetailsOneEpisodeFile'); } else if (episodeFileCount > 1) { episodeFilesCountMessage = translate('SeriesDetailsCountEpisodeFiles', { episodeFileCount }); } let expandIcon = icons.EXPAND_INDETERMINATE; if (allExpanded) { expandIcon = icons.COLLAPSE; } else if (allCollapsed) { expandIcon = icons.EXPAND; } const fanartUrl = getFanartUrl(images); return (
{title}
{ !!alternateTitles.length &&
} title={translate('AlternateTitles')} body={} position={tooltipPositions.BOTTOM} />
}
{ !!runtime && {translate('SeriesDetailsRuntime', { runtime })} } { ratings.value ? : null } {runningYears}
{ formatBytes(sizeOnDisk || 0) } } tooltip={ {episodeFilesCountMessage} } kind={kinds.INVERSE} position={tooltipPositions.BOTTOM} /> { !!network && } {translate('Links')} } tooltip={ } kind={kinds.INVERSE} position={tooltipPositions.BOTTOM} /> { !!tags.length && {translate('Tags')} } tooltip={} kind={kinds.INVERSE} position={tooltipPositions.BOTTOM} /> }
{ !isPopulated && !episodesError && !episodeFilesError && } { !isFetching && episodesError ? {translate('EpisodesLoadError')} : null } { !isFetching && episodeFilesError ? {translate('EpisodeFilesLoadError')} : null } { isPopulated && !!seasons.length &&
{ seasons.slice(0).reverse().map((season) => { return ( ); }) }
} { isPopulated && !seasons.length ? {translate('NoEpisodeInformation')} : null }
); } } SeriesDetails.propTypes = { id: PropTypes.number.isRequired, tvdbId: PropTypes.number.isRequired, tvMazeId: PropTypes.number, imdbId: PropTypes.string, title: PropTypes.string.isRequired, runtime: PropTypes.number.isRequired, ratings: PropTypes.object.isRequired, path: PropTypes.string.isRequired, statistics: PropTypes.object.isRequired, qualityProfileId: PropTypes.number.isRequired, monitored: PropTypes.bool.isRequired, monitor: PropTypes.string, status: PropTypes.string.isRequired, network: PropTypes.string, overview: PropTypes.string.isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired, seasons: PropTypes.arrayOf(PropTypes.object).isRequired, alternateTitles: PropTypes.arrayOf(PropTypes.object).isRequired, genres: PropTypes.arrayOf(PropTypes.string).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired, year: PropTypes.number.isRequired, lastAired: PropTypes.string, previousAiring: PropTypes.string, isSaving: PropTypes.bool.isRequired, saveError: PropTypes.object, isRefreshing: PropTypes.bool.isRequired, isSearching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired, episodesError: PropTypes.object, episodeFilesError: PropTypes.object, hasEpisodes: PropTypes.bool.isRequired, hasMonitoredEpisodes: PropTypes.bool.isRequired, hasEpisodeFiles: PropTypes.bool.isRequired, previousSeries: PropTypes.object.isRequired, nextSeries: PropTypes.object.isRequired, onMonitorTogglePress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired }; SeriesDetails.defaultProps = { statistics: {}, tags: [], isSaving: false }; export default SeriesDetails;