New: Rework Movie Details view

movie-details-rework
Qstick 2 years ago
parent 5acd41a7ea
commit 155b8f774b

@ -1,8 +1,8 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import Slider from 'react-slick';
import TextTruncate from 'react-text-truncate'; import TextTruncate from 'react-text-truncate';
import EditCollectionModalConnector from 'Collection/Edit/EditCollectionModalConnector'; import EditCollectionModalConnector from 'Collection/Edit/EditCollectionModalConnector';
import Carousel from 'Components/Carousel';
import CheckInput from 'Components/Form/CheckInput'; import CheckInput from 'Components/Form/CheckInput';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import Label from 'Components/Label'; import Label from 'Components/Label';
@ -16,9 +16,6 @@ import translate from 'Utilities/String/translate';
import CollectionMovieConnector from './CollectionMovieConnector'; import CollectionMovieConnector from './CollectionMovieConnector';
import styles from './CollectionOverview.css'; import styles from './CollectionOverview.css';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
const columnPadding = parseInt(dimensions.movieIndexColumnPadding); const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen); const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
const defaultFontSize = parseInt(fonts.defaultFontSize); const defaultFontSize = parseInt(fonts.defaultFontSize);
@ -118,15 +115,6 @@ class CollectionOverview extends Component {
const contentHeight = getContentHeight(rowHeight, isSmallScreen); const contentHeight = getContentHeight(rowHeight, isSmallScreen);
const overviewHeight = contentHeight - titleRowHeight - posterHeight; const overviewHeight = contentHeight - titleRowHeight - posterHeight;
const sliderSettings = {
arrows: false,
dots: false,
infinite: false,
slidesToShow: 1,
slidesToScroll: 1,
variableWidth: true
};
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.content}> <div className={styles.content}>
@ -262,7 +250,7 @@ class CollectionOverview extends Component {
} }
<div className={styles.sliderContainer}> <div className={styles.sliderContainer}>
<Slider ref={this.setSliderRef} {...sliderSettings}> <Carousel setRef={this.setSliderRef}>
{movies.map((movie) => ( {movies.map((movie) => (
<div className={styles.movie} key={movie.tmdbId}> <div className={styles.movie} key={movie.tmdbId}>
<CollectionMovieConnector <CollectionMovieConnector
@ -275,7 +263,7 @@ class CollectionOverview extends Component {
/> />
</div> </div>
))} ))}
</Slider> </Carousel>
</div> </div>
</div> </div>
</div> </div>

@ -0,0 +1,37 @@
import PropTypes from 'prop-types';
import React from 'react';
import Slider from 'react-slick';
import styles from './Alert.css';
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';
function Carousel({ className, setRef, children, ...otherProps }) {
const sliderSettings = {
arrows: false,
dots: false,
infinite: false,
slidesToShow: 1,
slidesToScroll: 1,
variableWidth: true
};
return (
<Slider ref={setRef} {...sliderSettings}>
{children}
</Slider>
);
}
Carousel.propTypes = {
className: PropTypes.string.isRequired,
setRef: PropTypes.func.isRequired,
children: PropTypes.node.isRequired
};
Carousel.defaultProps = {
className: styles.alert
};
export default Carousel;

@ -2,12 +2,15 @@ import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
import PageMenuButton from 'Components/Menu/PageMenuButton';
import Table from 'Components/Table/Table'; import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody'; import TableBody from 'Components/Table/TableBody';
import { icons, sortDirections } from 'Helpers/Props'; import { align, icons, sortDirections } from 'Helpers/Props';
import translate from 'Utilities/String/translate'; import translate from 'Utilities/String/translate';
import InteractiveSearchFilterModalConnector from './InteractiveSearchFilterModalConnector';
import InteractiveSearchRowConnector from './InteractiveSearchRowConnector'; import InteractiveSearchRowConnector from './InteractiveSearchRowConnector';
import styles from './InteractiveSearchContent.css'; import styles from './InteractiveSearch.css';
const columns = [ const columns = [
{ {
@ -22,20 +25,6 @@ const columns = [
isSortable: true, isSortable: true,
isVisible: true isVisible: true
}, },
{
name: 'releaseWeight',
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, { name: icons.DANGER }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{ {
name: 'title', name: 'title',
label: translate('Title'), label: translate('Title'),
@ -99,10 +88,24 @@ const columns = [
label: React.createElement(Icon, { name: icons.FLAG }), label: React.createElement(Icon, { name: icons.FLAG }),
isSortable: true, isSortable: true,
isVisible: true isVisible: true
},
{
name: 'rejections',
label: React.createElement(Icon, { name: icons.DANGER }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
},
{
name: 'releaseWeight',
label: React.createElement(Icon, { name: icons.DOWNLOAD }),
isSortable: true,
fixedSortDirection: sortDirections.ASCENDING,
isVisible: true
} }
]; ];
function InteractiveSearchContent(props) { function InteractiveSearch(props) {
const { const {
searchPayload, searchPayload,
isFetching, isFetching,
@ -110,44 +113,63 @@ function InteractiveSearchContent(props) {
error, error,
totalReleasesCount, totalReleasesCount,
items, items,
selectedFilterKey,
filters,
customFilters,
sortKey, sortKey,
sortDirection, sortDirection,
longDateFormat, longDateFormat,
timeFormat, timeFormat,
onSortPress, onSortPress,
onFilterSelect,
onGrabPress onGrabPress
} = props; } = props;
return ( return (
<div> <div>
<div className={styles.filterMenuContainer}>
<FilterMenu
alignMenu={align.RIGHT}
selectedFilterKey={selectedFilterKey}
filters={filters}
customFilters={customFilters}
buttonComponent={PageMenuButton}
filterModalConnectorComponent={InteractiveSearchFilterModalConnector}
filterModalConnectorComponentProps={'movies'}
onFilterSelect={onFilterSelect}
/>
</div>
{ {
isFetching && isFetching ? <LoadingIndicator /> : null
<LoadingIndicator />
} }
{ {
!isFetching && !!error && !isFetching && error ?
<div> <div>
{translate('UnableToLoadResultsIntSearch')} {translate('UnableToLoadResultsIntSearch')}
</div> </div> :
null
} }
{ {
!isFetching && isPopulated && !totalReleasesCount && !isFetching && isPopulated && !totalReleasesCount ?
<div> <div>
{translate('NoResultsFound')} {translate('NoResultsFound')}
</div> </div> :
null
} }
{ {
!!totalReleasesCount && isPopulated && !items.length && !!totalReleasesCount && isPopulated && !items.length ?
<div> <div>
{translate('AllResultsHiddenFilter')} {translate('AllResultsHiddenFilter')}
</div> </div> :
null
} }
{ {
isPopulated && !!items.length && isPopulated && !!items.length ?
<Table <Table
columns={columns} columns={columns}
sortKey={sortKey} sortKey={sortKey}
@ -170,32 +192,38 @@ function InteractiveSearchContent(props) {
}) })
} }
</TableBody> </TableBody>
</Table> </Table> :
null
} }
{ {
totalReleasesCount !== items.length && !!items.length && totalReleasesCount !== items.length && !!items.length ?
<div className={styles.filteredMessage}> <div className={styles.filteredMessage}>
{translate('SomeResultsHiddenFilter')} {translate('SomeResultsHiddenFilter')}
</div> </div> :
null
} }
</div> </div>
); );
} }
InteractiveSearchContent.propTypes = { InteractiveSearch.propTypes = {
searchPayload: PropTypes.object.isRequired, searchPayload: PropTypes.object.isRequired,
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, error: PropTypes.object,
totalReleasesCount: PropTypes.number.isRequired, totalReleasesCount: PropTypes.number.isRequired,
items: PropTypes.arrayOf(PropTypes.object).isRequired, items: PropTypes.arrayOf(PropTypes.object).isRequired,
selectedFilterKey: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
filters: PropTypes.arrayOf(PropTypes.object).isRequired,
customFilters: PropTypes.arrayOf(PropTypes.object).isRequired,
sortKey: PropTypes.string, sortKey: PropTypes.string,
sortDirection: PropTypes.string, sortDirection: PropTypes.string,
longDateFormat: PropTypes.string.isRequired, longDateFormat: PropTypes.string.isRequired,
timeFormat: PropTypes.string.isRequired, timeFormat: PropTypes.string.isRequired,
onSortPress: PropTypes.func.isRequired, onSortPress: PropTypes.func.isRequired,
onFilterSelect: PropTypes.func.isRequired,
onGrabPress: PropTypes.func.isRequired onGrabPress: PropTypes.func.isRequired
}; };
export default InteractiveSearchContent; export default InteractiveSearch;

@ -5,7 +5,7 @@ import { createSelector } from 'reselect';
import * as releaseActions from 'Store/Actions/releaseActions'; import * as releaseActions from 'Store/Actions/releaseActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector'; import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector'; import createUISettingsSelector from 'Store/Selectors/createUISettingsSelector';
import InteractiveSearchContent from './InteractiveSearchContent'; import InteractiveSearch from './InteractiveSearch';
function createMapStateToProps(appState) { function createMapStateToProps(appState) {
return createSelector( return createSelector(
@ -48,7 +48,7 @@ function createMapDispatchToProps(dispatch, props) {
}; };
} }
class InteractiveSearchContentConnector extends Component { class InteractiveSearchConnector extends Component {
// //
// Lifecycle // Lifecycle
@ -79,18 +79,18 @@ class InteractiveSearchContentConnector extends Component {
return ( return (
<InteractiveSearchContent <InteractiveSearch
{...otherProps} {...otherProps}
/> />
); );
} }
} }
InteractiveSearchContentConnector.propTypes = { InteractiveSearchConnector.propTypes = {
searchPayload: PropTypes.object.isRequired, searchPayload: PropTypes.object.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
dispatchFetchReleases: PropTypes.func.isRequired, dispatchFetchReleases: PropTypes.func.isRequired,
dispatchClearReleases: PropTypes.func.isRequired dispatchClearReleases: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchContentConnector); export default connect(createMapStateToProps, createMapDispatchToProps)(InteractiveSearchConnector);

@ -1,15 +1,20 @@
.cell { .protocol {
composes: cell from '~Components/Table/Cells/TableRowCell.css'; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px;
} }
.protocol { .title {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 80px; display: flex;
align-items: center;
justify-content: space-between;
word-break: break-all;
} }
.indexer { .indexer {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 85px; width: 85px;
} }
@ -17,7 +22,9 @@
.quality, .quality,
.customFormat, .customFormat,
.language { .language {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
text-align: center;
} }
.language { .language {
@ -25,7 +32,7 @@
} }
.customFormatScore { .customFormatScore {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 55px; width: 55px;
font-weight: bold; font-weight: bold;
@ -35,34 +42,26 @@
.rejected, .rejected,
.indexerFlags, .indexerFlags,
.download { .download {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 50px; width: 50px;
} }
.age, .age,
.size { .size {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
white-space: nowrap; white-space: nowrap;
} }
.peers { .peers {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 75px; width: 75px;
} }
.title {
composes: cell;
}
.title div {
overflow-wrap: break-word;
}
.history { .history {
composes: cell; composes: cell from '~Components/Table/Cells/TableRowCell.css';
width: 75px; width: 75px;
} }

@ -145,46 +145,6 @@ class InteractiveSearchRow extends Component {
{formatAge(age, ageHours, ageMinutes)} {formatAge(age, ageHours, ageMinutes)}
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.download}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
/>
</TableRowCell>
<TableRowCell className={styles.rejected}>
{
!!rejections.length &&
<Popover
anchor={
<Icon
name={icons.DANGER}
kind={kinds.DANGER}
/>
}
title={translate('ReleaseRejected')}
body={
<ul>
{
rejections.map((rejection, index) => {
return (
<li key={index}>
{rejection}
</li>
);
})
}
</ul>
}
position={tooltipPositions.BOTTOM}
/>
}
</TableRowCell>
<TableRowCell className={styles.title}> <TableRowCell className={styles.title}>
<Link <Link
to={infoUrl} to={infoUrl}
@ -297,6 +257,46 @@ class InteractiveSearchRow extends Component {
} }
</TableRowCell> </TableRowCell>
<TableRowCell className={styles.rejected}>
{
!!rejections.length &&
<Popover
anchor={
<Icon
name={icons.DANGER}
kind={kinds.DANGER}
/>
}
title={translate('ReleaseRejected')}
body={
<ul>
{
rejections.map((rejection, index) => {
return (
<li key={index}>
{rejection}
</li>
);
})
}
</ul>
}
position={tooltipPositions.LEFT}
/>
}
</TableRowCell>
<TableRowCell className={styles.download}>
<SpinnerIconButton
name={getDownloadIcon(isGrabbing, isGrabbed, grabError)}
kind={grabError ? kinds.DANGER : kinds.DEFAULT}
title={getDownloadTooltip(isGrabbing, isGrabbed, grabError)}
isDisabled={isGrabbed}
isSpinning={isGrabbing}
onPress={downloadAllowed ? this.onGrabPress : this.onConfirmGrabPress}
/>
</TableRowCell>
<ConfirmModal <ConfirmModal
isOpen={this.state.isConfirmGrabModalOpen} isOpen={this.state.isConfirmGrabModalOpen}
kind={kinds.WARNING} kind={kinds.WARNING}

@ -1,16 +0,0 @@
import React from 'react';
import InteractiveSearchContentConnector from './InteractiveSearchContentConnector';
function InteractiveSearchTable(props) {
return (
<InteractiveSearchContentConnector
searchPayload={props}
/>
);
}
InteractiveSearchTable.propTypes = {
};
export default InteractiveSearchTable;

@ -69,7 +69,8 @@ class MovieCastPoster extends Component {
const elementStyle = { const elementStyle = {
width: `${posterWidth}px`, width: `${posterWidth}px`,
height: `${posterHeight}px` height: `${posterHeight}px`,
borderRadius: '5px'
}; };
const contentStyle = { const contentStyle = {

@ -69,7 +69,8 @@ class MovieCrewPoster extends Component {
const elementStyle = { const elementStyle = {
width: `${posterWidth}px`, width: `${posterWidth}px`,
height: `${posterHeight}px` height: `${posterHeight}px`,
borderRadius: '5px'
}; };
const contentStyle = { const contentStyle = {

@ -1,6 +1,7 @@
$hoverScale: 1.05; $hoverScale: 1.05;
.content { .content {
border-radius: 5px;
transition: all 200ms ease-in; transition: all 200ms ease-in;
&:hover { &:hover {

@ -2,6 +2,10 @@
flex: 1 0 auto; flex: 1 0 auto;
} }
.movie {
padding: 10px;
}
.container { .container {
padding: 10px; padding: 10px;
} }

@ -1,7 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Grid, WindowScroller } from 'react-virtualized'; import Carousel from 'Components/Carousel';
import Measure from 'Components/Measure';
import dimensions from 'Styles/Variables/dimensions'; import dimensions from 'Styles/Variables/dimensions';
import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder'; import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import MovieCreditPosterConnector from './MovieCreditPosterConnector'; import MovieCreditPosterConnector from './MovieCreditPosterConnector';
@ -169,56 +168,36 @@ class MovieCreditPosters extends Component {
render() { render() {
const { const {
items items,
itemComponent
} = this.props; } = this.props;
const { const {
width, posterWidth,
columnWidth, posterHeight
columnCount,
rowHeight
} = this.state; } = this.state;
const rowCount = Math.ceil(items.length / columnCount);
return ( return (
<Measure
whitelist={['width']} <div className={styles.sliderContainer}>
onMeasure={this.onMeasure} <Carousel setRef={this.setSliderRef}>
> {items.map((movie) => (
<WindowScroller <div className={styles.movie} key={movie.tmdbId}>
scrollElement={undefined} <MovieCreditPosterConnector
> key={movie.order}
{({ height, registerChild, onChildScroll, scrollTop }) => { component={itemComponent}
if (!height) { posterWidth={posterWidth}
return <div />; posterHeight={posterHeight}
} tmdbId={movie.personTmdbId}
personName={movie.personName}
return ( job={movie.job}
<div ref={registerChild}> character={movie.character}
<Grid images={movie.images}
ref={this.setGridRef} />
className={styles.grid} </div>
autoHeight={true} ))}
height={height} </Carousel>
columnCount={columnCount} </div>
columnWidth={columnWidth}
rowCount={rowCount}
rowHeight={rowHeight}
width={width}
onScroll={onChildScroll}
scrollTop={scrollTop}
overscanRowCount={2}
cellRenderer={this.cellRenderer}
scrollToAlignment={'start'}
isScrollingOptOut={true}
/>
</div>
);
}
}
</WindowScroller>
</Measure>
); );
} }
} }

@ -1,3 +0,0 @@
.alternateTitle {
white-space: nowrap;
}

@ -1,28 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import styles from './MovieAlternateTitles.css';
function MovieAlternateTitles({ alternateTitles }) {
return (
<ul>
{
alternateTitles.filter((x, i, a) => a.indexOf(x) === i).map((alternateTitle) => {
return (
<li
key={alternateTitle}
className={styles.alternateTitle}
>
{alternateTitle}
</li>
);
})
}
</ul>
);
}
MovieAlternateTitles.propTypes = {
alternateTitles: PropTypes.arrayOf(PropTypes.string).isRequired
};
export default MovieAlternateTitles;

@ -5,7 +5,7 @@
.header { .header {
position: relative; position: relative;
width: 100%; width: 100%;
height: 375px; height: 425px;
} }
.errorMessage { .errorMessage {
@ -39,10 +39,11 @@
} }
.poster { .poster {
z-index: 2;
flex-shrink: 0; flex-shrink: 0;
margin-right: 35px; margin-right: 35px;
width: 217px; width: 250px;
height: 319px; height: 368px;
} }
.info { .info {

@ -1,8 +1,8 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { Tab, TabList, TabPanel, Tabs } from 'react-tabs';
import TextTruncate from 'react-text-truncate'; import TextTruncate from 'react-text-truncate';
import FieldSet from 'Components/FieldSet';
import Icon from 'Components/Icon'; import Icon from 'Components/Icon';
import ImdbRating from 'Components/ImdbRating'; import ImdbRating from 'Components/ImdbRating';
import InfoLabel from 'Components/InfoLabel'; import InfoLabel from 'Components/InfoLabel';
@ -22,12 +22,11 @@ import Popover from 'Components/Tooltip/Popover';
import Tooltip from 'Components/Tooltip/Tooltip'; import Tooltip from 'Components/Tooltip/Tooltip';
import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props'; import { icons, kinds, sizes, tooltipPositions } from 'Helpers/Props';
import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal'; import InteractiveImportModal from 'InteractiveImport/InteractiveImportModal';
import InteractiveSearchFilterMenuConnector from 'InteractiveSearch/InteractiveSearchFilterMenuConnector';
import InteractiveSearchTable from 'InteractiveSearch/InteractiveSearchTable';
import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal'; import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector'; import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
import MovieHistoryTable from 'Movie/History/MovieHistoryTable'; import MovieHistoryTable from 'Movie/History/MovieHistoryTable';
import MoviePoster from 'Movie/MoviePoster'; import MoviePoster from 'Movie/MoviePoster';
import MovieInteractiveSearchModalConnector from 'Movie/Search/MovieInteractiveSearchModalConnector';
import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable'; import MovieFileEditorTable from 'MovieFile/Editor/MovieFileEditorTable';
import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable'; import ExtraFileTable from 'MovieFile/Extras/ExtraFileTable';
import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector'; import OrganizePreviewModalConnector from 'Organize/OrganizePreviewModalConnector';
@ -81,10 +80,10 @@ class MovieDetails extends Component {
isEditMovieModalOpen: false, isEditMovieModalOpen: false,
isDeleteMovieModalOpen: false, isDeleteMovieModalOpen: false,
isInteractiveImportModalOpen: false, isInteractiveImportModalOpen: false,
isInteractiveSearchModalOpen: false,
allExpanded: false, allExpanded: false,
allCollapsed: false, allCollapsed: false,
expandedState: {}, expandedState: {},
selectedTabIndex: 0,
overviewHeight: 0, overviewHeight: 0,
titleWidth: 0 titleWidth: 0
}; };
@ -137,6 +136,14 @@ class MovieDetails extends Component {
this.setState({ isEditMovieModalOpen: false }); this.setState({ isEditMovieModalOpen: false });
}; };
onInteractiveSearchPress = () => {
this.setState({ isInteractiveSearchModalOpen: true });
};
onInteractiveSearchModalClose = () => {
this.setState({ isInteractiveSearchModalOpen: false });
};
onDeleteMoviePress = () => { onDeleteMoviePress = () => {
this.setState({ this.setState({
isEditMovieModalOpen: false, isEditMovieModalOpen: false,
@ -298,9 +305,9 @@ class MovieDetails extends Component {
isEditMovieModalOpen, isEditMovieModalOpen,
isDeleteMovieModalOpen, isDeleteMovieModalOpen,
isInteractiveImportModalOpen, isInteractiveImportModalOpen,
isInteractiveSearchModalOpen,
overviewHeight, overviewHeight,
titleWidth, titleWidth
selectedTabIndex
} = this.state; } = this.state;
const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150); const marqueeWidth = isSmallScreen ? titleWidth : (titleWidth - 150);
@ -326,6 +333,14 @@ class MovieDetails extends Component {
onPress={onSearchPress} onPress={onSearchPress}
/> />
<PageToolbarButton
label={translate('InteractiveSearch')}
iconName={icons.INTERACTIVE}
isSpinning={isSearching}
title={undefined}
onPress={this.onInteractiveSearchPress}
/>
<PageToolbarSeparator /> <PageToolbarSeparator />
<PageToolbarButton <PageToolbarButton
@ -651,101 +666,39 @@ class MovieDetails extends Component {
</div> </div>
} }
<Tabs selectedIndex={this.state.tabIndex} onSelect={this.onTabSelect}> <FieldSet legend={translate('History')}>
<TabList <MovieHistoryTable
className={styles.tabList} movieId={id}
> />
<Tab </FieldSet>
className={styles.tab}
selectedClassName={styles.selectedTab}
>
{translate('History')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
{translate('Search')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
{translate('Files')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
{translate('Titles')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
{translate('Cast')}
</Tab>
<Tab
className={styles.tab}
selectedClassName={styles.selectedTab}
>
{translate('Crew')}
</Tab>
{
selectedTabIndex === 1 &&
<div className={styles.filterIcon}>
<InteractiveSearchFilterMenuConnector />
</div>
}
</TabList>
<TabPanel>
<MovieHistoryTable
movieId={id}
/>
</TabPanel>
<TabPanel>
<InteractiveSearchTable
movieId={id}
/>
</TabPanel>
<TabPanel>
<MovieFileEditorTable
movieId={id}
/>
<ExtraFileTable
movieId={id}
/>
</TabPanel>
<TabPanel>
<MovieTitlesTable
movieId={id}
/>
</TabPanel>
<TabPanel>
<MovieCastPostersConnector
isSmallScreen={isSmallScreen}
/>
</TabPanel>
<TabPanel>
<MovieCrewPostersConnector
isSmallScreen={isSmallScreen}
/>
</TabPanel>
</Tabs>
<FieldSet legend={translate('Files')}>
<MovieFileEditorTable
movieId={id}
/>
<ExtraFileTable
movieId={id}
/>
</FieldSet>
<FieldSet legend={translate('Cast')}>
<MovieCastPostersConnector
isSmallScreen={isSmallScreen}
/>
</FieldSet>
<FieldSet legend={translate('Crew')}>
<MovieCrewPostersConnector
isSmallScreen={isSmallScreen}
/>
</FieldSet>
<FieldSet legend={translate('Titles')}>
<MovieTitlesTable
movieId={id}
/>
</FieldSet>
</div> </div>
<OrganizePreviewModalConnector <OrganizePreviewModalConnector
@ -777,6 +730,12 @@ class MovieDetails extends Component {
showImportMode={false} showImportMode={false}
onModalClose={this.onInteractiveImportModalClose} onModalClose={this.onInteractiveImportModalClose}
/> />
<MovieInteractiveSearchModalConnector
isOpen={isInteractiveSearchModalOpen}
movieId={id}
onModalClose={this.onInteractiveSearchModalClose}
/>
</PageContentBody> </PageContentBody>
</PageContent> </PageContent>
); );

@ -0,0 +1,9 @@
.container {
border: 1px solid $borderColor;
border-radius: 4px;
background-color: $white;
&:last-of-type {
margin-bottom: 0;
}
}

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector'; import MovieTitlesTableContentConnector from './MovieTitlesTableContentConnector';
import styles from './MovieTitlesTable.css';
function MovieTitlesTable(props) { function MovieTitlesTable(props) {
const { const {
@ -7,9 +8,11 @@ function MovieTitlesTable(props) {
} = props; } = props;
return ( return (
<MovieTitlesTableContentConnector <div className={styles.container}>
{...otherProps} <MovieTitlesTableContentConnector
/> {...otherProps}
/>
</div>
); );
} }

@ -0,0 +1,9 @@
.container {
border: 1px solid $borderColor;
border-radius: 4px;
background-color: $white;
&:last-of-type {
margin-bottom: 0;
}
}

@ -1,5 +1,6 @@
import React from 'react'; import React from 'react';
import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector'; import MovieHistoryTableContentConnector from './MovieHistoryTableContentConnector';
import styles from './MovieHistoryTable.css';
function MovieHistoryTable(props) { function MovieHistoryTable(props) {
const { const {
@ -7,9 +8,11 @@ function MovieHistoryTable(props) {
} = props; } = props;
return ( return (
<MovieHistoryTableContentConnector <div className={styles.container}>
{...otherProps} <MovieHistoryTableContentConnector
/> {...otherProps}
/>
</div>
); );
} }

@ -0,0 +1,35 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import MovieInteractiveSearchModalContent from './MovieInteractiveSearchModalContent';
function MovieInteractiveSearchModal(props) {
const {
isOpen,
movieId,
onModalClose
} = props;
return (
<Modal
isOpen={isOpen}
closeOnBackgroundClick={false}
onModalClose={onModalClose}
size={sizes.EXTRA_LARGE}
>
<MovieInteractiveSearchModalContent
movieId={movieId}
onModalClose={onModalClose}
/>
</Modal>
);
}
MovieInteractiveSearchModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
movieId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default MovieInteractiveSearchModal;

@ -0,0 +1,15 @@
import { connect } from 'react-redux';
import { cancelFetchReleases, clearReleases } from 'Store/Actions/releaseActions';
import MovieInteractiveSearchModal from './MovieInteractiveSearchModal';
function createMapDispatchToProps(dispatch, props) {
return {
onModalClose() {
dispatch(cancelFetchReleases());
dispatch(clearReleases());
props.onModalClose();
}
};
}
export default connect(null, createMapDispatchToProps)(MovieInteractiveSearchModal);

@ -0,0 +1,45 @@
import PropTypes from 'prop-types';
import React from 'react';
import Button from 'Components/Link/Button';
import ModalBody from 'Components/Modal/ModalBody';
import ModalContent from 'Components/Modal/ModalContent';
import ModalFooter from 'Components/Modal/ModalFooter';
import ModalHeader from 'Components/Modal/ModalHeader';
import { scrollDirections } from 'Helpers/Props';
import InteractiveSearchConnector from 'InteractiveSearch/InteractiveSearchConnector';
function MovieInteractiveSearchModalContent(props) {
const {
movieId,
onModalClose
} = props;
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Interactive Search
</ModalHeader>
<ModalBody scrollDirection={scrollDirections.BOTH}>
<InteractiveSearchConnector
searchPayload={{
movieId
}}
/>
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
Close
</Button>
</ModalFooter>
</ModalContent>
);
}
MovieInteractiveSearchModalContent.propTypes = {
movieId: PropTypes.number.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default MovieInteractiveSearchModalContent;

@ -1,5 +1,4 @@
.container { .container {
margin-top: 20px;
border: 1px solid $borderColor; border: 1px solid $borderColor;
border-radius: 4px; border-radius: 4px;
background-color: $white; background-color: $white;

Loading…
Cancel
Save