import _ from 'lodash'; import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { Tab, Tabs, TabList, TabPanel } from 'react-tabs'; import TextTruncate from 'react-text-truncate'; import formatBytes from 'Utilities/Number/formatBytes'; import selectAll from 'Utilities/Table/selectAll'; import toggleSelected from 'Utilities/Table/toggleSelected'; import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; import fonts from 'Styles/Variables/fonts'; import HeartRating from 'Components/HeartRating'; import Icon from 'Components/Icon'; import IconButton from 'Components/Link/IconButton'; import InfoLabel from 'Components/InfoLabel'; import Marquee from 'Components/Marquee'; import MovieStatusLabel from './MovieStatusLabel'; import Measure from 'Components/Measure'; import MonitorToggleButton from 'Components/MonitorToggleButton'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import PageContent from 'Components/Page/PageContent'; import PageContentBody from 'Components/Page/PageContentBody'; import PageToolbar from 'Components/Page/Toolbar/PageToolbar'; import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection'; import PageToolbarSeparator from 'Components/Page/Toolbar/PageToolbarSeparator'; import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton'; import Popover from 'Components/Tooltip/Popover'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable'; import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import QualityProfileNameConnector from 'Settings/Profiles/Quality/QualityProfileNameConnector'; import MoviePoster from 'Movie/MoviePoster'; import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector'; import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal'; import MovieHistoryTable from 'Movie/History/MovieHistoryTable'; import MovieTitlesTable from './Titles/MovieTitlesTable'; import MovieCastPostersConnector from './Credits/Cast/MovieCastPostersConnector'; import MovieCrewPostersConnector from './Credits/Crew/MovieCrewPostersConnector'; import MovieDetailsLinks from './MovieDetailsLinks'; import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable'; import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector'; import MovieTagsConnector from './MovieTagsConnector'; import styles from './MovieDetails.css'; const defaultFontSize = parseInt(fonts.defaultFontSize); const lineHeight = parseFloat(fonts.lineHeight); function getFanartUrl(images) { const fanartImage = _.find(images, { coverType: 'fanart' }); if (fanartImage) { // Remove protocol return fanartImage.url.replace(/^https?:/, ''); } } function getExpandedState(newState) { return { allExpanded: newState.allSelected, allCollapsed: newState.allUnselected, expandedState: newState.selectedState }; } class MovieDetails extends Component { // // Lifecycle constructor(props, context) { super(props, context); this.state = { isOrganizeModalOpen: false, isEditMovieModalOpen: false, isDeleteMovieModalOpen: false, isInteractiveImportModalOpen: false, allExpanded: false, allCollapsed: false, expandedState: {}, selectedTabIndex: 0, overviewHeight: 0, titleWidth: 0 }; } componentDidMount() { window.addEventListener('touchstart', this.onTouchStart); window.addEventListener('touchend', this.onTouchEnd); window.addEventListener('touchcancel', this.onTouchCancel); window.addEventListener('touchmove', this.onTouchMove); } componentWillUnmount() { window.removeEventListener('touchstart', this.onTouchStart); window.removeEventListener('touchend', this.onTouchEnd); window.removeEventListener('touchcancel', this.onTouchCancel); window.removeEventListener('touchmove', this.onTouchMove); } // // Listeners onOrganizePress = () => { this.setState({ isOrganizeModalOpen: true }); } onOrganizeModalClose = () => { this.setState({ isOrganizeModalOpen: false }); } onManageEpisodesPress = () => { this.setState({ isManageEpisodesOpen: true }); } onInteractiveImportPress = () => { this.setState({ isInteractiveImportModalOpen: true }); } onInteractiveImportModalClose = () => { this.setState({ isInteractiveImportModalOpen: false }); } onEditMoviePress = () => { this.setState({ isEditMovieModalOpen: true }); } onEditMovieModalClose = () => { this.setState({ isEditMovieModalOpen: false }); } onDeleteMoviePress = () => { this.setState({ isEditMovieModalOpen: false, isDeleteMovieModalOpen: true }); } onDeleteMovieModalClose = () => { this.setState({ isDeleteMovieModalOpen: 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 }); } onTitleMeasure = ({ width }) => { this.setState({ titleWidth: width }); } onTouchStart = (event) => { const touches = event.touches; const touchStart = touches[0].pageX; const touchY = touches[0].pageY; // Only change when swipe is on header, we need horizontal scroll on tables if (touchY > 470) { return; } if (touches.length !== 1) { return; } if ( touchStart < 50 || this.props.isSidebarVisible || this.state.isEventModalOpen ) { return; } this._touchStart = touchStart; } onTouchEnd = (event) => { const touches = event.changedTouches; const currentTouch = touches[0].pageX; if (!this._touchStart) { return; } if (currentTouch > this._touchStart && currentTouch - this._touchStart > 100) { this.props.onGoToMovie(this.props.previousMovie.titleSlug); } else if (currentTouch < this._touchStart && this._touchStart - currentTouch > 100) { this.props.onGoToMovie(this.props.nextMovie.titleSlug); } this._touchStart = null; } onTouchCancel = (event) => { this._touchStart = null; } onTouchMove = (event) => { if (!this._touchStart) { return; } } // // Render render() { const { id, tmdbId, imdbId, title, year, runtime, certification, ratings, path, sizeOnDisk, qualityProfileId, monitored, studio, collection, overview, youTubeTrailerId, isAvailable, images, tags, isSaving, isRefreshing, isSearching, isFetching, isPopulated, isSmallScreen, movieFilesError, movieCreditsError, extraFilesError, hasMovieFiles, previousMovie, nextMovie, onMonitorTogglePress, onRefreshPress, onSearchPress } = this.props; const { isOrganizeModalOpen, isEditMovieModalOpen, isDeleteMovieModalOpen, isInteractiveImportModalOpen, overviewHeight, titleWidth, selectedTabIndex } = this.state; const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150); return (
{ !!certification && {certification} } { year > 0 && {year} } { !!runtime && {runtime} Minutes } { !!ratings && } { } title="Links" body={ } position={tooltipPositions.BOTTOM} /> } { !!tags.length && } title="Tags" body={ } position={tooltipPositions.BOTTOM} /> }
{path} { } { formatBytes(sizeOnDisk) } { !!collection && {collection.name} } { !!studio && !isSmallScreen && {studio} }
{ !isPopulated && !movieFilesError && !movieCreditsError && !extraFilesError && } { !isFetching && movieFilesError &&
Loading movie files failed
} { !isFetching && movieCreditsError &&
Loading movie credits failed
} { !isFetching && extraFilesError &&
Loading movie extra files failed
} this.setState({ selectedTabIndex: tabIndex })}> History Search Files Titles Cast Crew { selectedTabIndex === 1 &&
}
); } } MovieDetails.propTypes = { id: PropTypes.number.isRequired, tmdbId: PropTypes.number.isRequired, imdbId: PropTypes.string, title: PropTypes.string.isRequired, year: PropTypes.number.isRequired, runtime: PropTypes.number.isRequired, certification: PropTypes.string, ratings: PropTypes.object.isRequired, path: PropTypes.string.isRequired, sizeOnDisk: PropTypes.number.isRequired, qualityProfileId: PropTypes.number.isRequired, monitored: PropTypes.bool.isRequired, status: PropTypes.string.isRequired, studio: PropTypes.string, collection: PropTypes.object, youTubeTrailerId: PropTypes.string, isAvailable: PropTypes.bool.isRequired, inCinemas: PropTypes.string, overview: PropTypes.string.isRequired, images: PropTypes.arrayOf(PropTypes.object).isRequired, alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired, tags: PropTypes.arrayOf(PropTypes.number).isRequired, isSaving: PropTypes.bool.isRequired, isRefreshing: PropTypes.bool.isRequired, isSearching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired, isSmallScreen: PropTypes.bool.isRequired, isSidebarVisible: PropTypes.bool.isRequired, movieFilesError: PropTypes.object, movieCreditsError: PropTypes.object, extraFilesError: PropTypes.object, hasMovieFiles: PropTypes.bool.isRequired, previousMovie: PropTypes.object.isRequired, nextMovie: PropTypes.object.isRequired, onMonitorTogglePress: PropTypes.func.isRequired, onRefreshPress: PropTypes.func.isRequired, onSearchPress: PropTypes.func.isRequired, onGoToMovie: PropTypes.func.isRequired }; MovieDetails.defaultProps = { tags: [], isSaving: false, sizeOnDisk: 0 }; export default MovieDetails;