Convert movie credits to TypeScript Switching to metadata based order for crewpull/10418/head^2
parent
6095819005
commit
5975be3690
@ -0,0 +1,6 @@
|
||||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import MovieCredit from 'typings/MovieCredit';
|
||||
|
||||
interface MovieCreditAppState extends AppSectionState<MovieCredit> {}
|
||||
|
||||
export default MovieCreditAppState;
|
@ -1,175 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../MovieCreditPoster.css';
|
||||
|
||||
class MovieCastPoster extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasPosterError: false,
|
||||
isEditImportListModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onEditImportListPress = () => {
|
||||
this.setState({ isEditImportListModalOpen: true });
|
||||
};
|
||||
|
||||
onAddImportListPress = () => {
|
||||
this.props.onImportListSelect();
|
||||
this.setState({ isEditImportListModalOpen: true });
|
||||
};
|
||||
|
||||
onEditImportListModalClose = () => {
|
||||
this.setState({ isEditImportListModalOpen: false });
|
||||
};
|
||||
|
||||
onPosterLoad = () => {
|
||||
if (this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: false });
|
||||
}
|
||||
};
|
||||
|
||||
onPosterLoadError = () => {
|
||||
if (!this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: true });
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
tmdbId,
|
||||
personName,
|
||||
character,
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
importList
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
hasPosterError
|
||||
} = this.state;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`,
|
||||
borderRadius: '5px'
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
width: `${posterWidth}px`
|
||||
};
|
||||
|
||||
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
||||
const importListId = importList ? importList.id : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.content}
|
||||
style={contentStyle}
|
||||
>
|
||||
<div className={styles.posterContainer}>
|
||||
<div className={styles.toggleMonitoredContainer}>
|
||||
<MonitorToggleButton
|
||||
className={styles.monitorToggleButton}
|
||||
monitored={monitored}
|
||||
size={20}
|
||||
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label className={styles.controls}>
|
||||
<span className={styles.externalLinks}>
|
||||
<Popover
|
||||
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
|
||||
title={translate('Links')}
|
||||
body={
|
||||
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
|
||||
<Label
|
||||
className={styles.externalLinkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{translate('TMDb')}
|
||||
</Label>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
<div
|
||||
style={elementStyle}
|
||||
>
|
||||
<MovieHeadshot
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
onError={this.onPosterLoadError}
|
||||
onLoad={this.onPosterLoad}
|
||||
/>
|
||||
|
||||
{
|
||||
hasPosterError &&
|
||||
<div className={styles.overlayTitle}>
|
||||
{personName}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{personName}
|
||||
</div>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{character}
|
||||
</div>
|
||||
|
||||
<EditImportListModalConnector
|
||||
id={importListId}
|
||||
isOpen={this.state.isEditImportListModalOpen}
|
||||
onModalClose={this.onEditImportListModalClose}
|
||||
onDeleteImportListPress={this.onDeleteImportListPress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCastPoster.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
personName: PropTypes.string.isRequired,
|
||||
character: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
importList: PropTypes.object,
|
||||
onImportListSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieCastPoster;
|
@ -0,0 +1,179 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||
import { deleteImportList } from 'Store/Actions/Settings/importLists';
|
||||
import ImportList from 'typings/ImportList';
|
||||
import MovieCredit from 'typings/MovieCredit';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../MovieCreditPoster.css';
|
||||
|
||||
export interface MovieCastPosterProps
|
||||
extends Pick<MovieCredit, 'personName' | 'images' | 'character'> {
|
||||
tmdbId: number;
|
||||
posterWidth: number;
|
||||
posterHeight: number;
|
||||
importList?: ImportList;
|
||||
onImportListSelect(): void;
|
||||
}
|
||||
|
||||
function MovieCastPoster(props: MovieCastPosterProps) {
|
||||
const {
|
||||
tmdbId,
|
||||
personName,
|
||||
character,
|
||||
images = [],
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
importList,
|
||||
onImportListSelect,
|
||||
} = props;
|
||||
|
||||
const importListId = importList?.id ?? 0;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [hasPosterError, setHasPosterError] = useState(false);
|
||||
|
||||
const [
|
||||
isEditImportListModalOpen,
|
||||
setEditImportListModalOpen,
|
||||
setEditImportListModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const [
|
||||
isDeleteImportListModalOpen,
|
||||
setDeleteImportListModalOpen,
|
||||
setDeleteImportListModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const handlePosterLoadError = useCallback(() => {
|
||||
setHasPosterError(true);
|
||||
}, [setHasPosterError]);
|
||||
|
||||
const handlePosterLoad = useCallback(() => {
|
||||
setHasPosterError(false);
|
||||
}, [setHasPosterError]);
|
||||
|
||||
const handleManageImportListPress = useCallback(() => {
|
||||
if (importListId === 0) {
|
||||
onImportListSelect();
|
||||
}
|
||||
|
||||
setEditImportListModalOpen();
|
||||
}, [importListId, onImportListSelect, setEditImportListModalOpen]);
|
||||
|
||||
const handleDeleteImportListConfirmed = useCallback(() => {
|
||||
dispatch(deleteImportList({ id: importListId }));
|
||||
|
||||
setEditImportListModalClosed();
|
||||
setDeleteImportListModalClosed();
|
||||
}, [
|
||||
importListId,
|
||||
setEditImportListModalClosed,
|
||||
setDeleteImportListModalClosed,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`,
|
||||
borderRadius: '5px',
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
};
|
||||
|
||||
const monitored =
|
||||
importList?.enabled === true && importList?.enableAuto === true;
|
||||
|
||||
return (
|
||||
<div className={styles.content} style={contentStyle}>
|
||||
<div className={styles.posterContainer}>
|
||||
<div className={styles.toggleMonitoredContainer}>
|
||||
<MonitorToggleButton
|
||||
className={styles.monitorToggleButton}
|
||||
monitored={monitored}
|
||||
size={20}
|
||||
onPress={handleManageImportListPress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label className={styles.controls}>
|
||||
<span className={styles.externalLinks}>
|
||||
<Popover
|
||||
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
|
||||
title={translate('Links')}
|
||||
body={
|
||||
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
|
||||
<Label
|
||||
className={styles.externalLinkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{translate('TMDb')}
|
||||
</Label>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
<div style={elementStyle}>
|
||||
<MovieHeadshot
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
onError={handlePosterLoadError}
|
||||
onLoad={handlePosterLoad}
|
||||
/>
|
||||
|
||||
{hasPosterError && (
|
||||
<div className={styles.overlayTitle}>{personName}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{personName}
|
||||
</div>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{character}
|
||||
</div>
|
||||
|
||||
<EditImportListModalConnector
|
||||
id={importListId}
|
||||
isOpen={isEditImportListModalOpen}
|
||||
onModalClose={setEditImportListModalClosed}
|
||||
onDeleteImportListPress={setDeleteImportListModalOpen}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isDeleteImportListModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteImportList')}
|
||||
message={translate('DeleteImportListMessageText', {
|
||||
name: importList?.name ?? personName,
|
||||
})}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={handleDeleteImportListConfirmed}
|
||||
onCancel={setDeleteImportListModalClosed}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCastPoster;
|
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import createMovieCreditsSelector from 'Store/Selectors/createMovieCreditsSelector';
|
||||
import MovieCreditPosters from '../MovieCreditPosters';
|
||||
import MovieCastPoster from './MovieCastPoster';
|
||||
|
||||
interface MovieCastPostersProps {
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
function MovieCastPosters({ isSmallScreen }: MovieCastPostersProps) {
|
||||
const { items: castCredits } = useSelector(
|
||||
createMovieCreditsSelector('cast')
|
||||
);
|
||||
|
||||
return (
|
||||
<MovieCreditPosters
|
||||
items={castCredits}
|
||||
itemComponent={MovieCastPoster}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCastPosters;
|
@ -1,43 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import MovieCreditPosters from '../MovieCreditPosters';
|
||||
import MovieCastPoster from './MovieCastPoster';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.movieCredits.items,
|
||||
(credits) => {
|
||||
const cast = _.reduce(credits, (acc, credit) => {
|
||||
if (credit.type === 'cast') {
|
||||
acc.push(credit);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
items: cast
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class MovieCastPostersConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<MovieCreditPosters
|
||||
{...this.props}
|
||||
itemComponent={MovieCastPoster}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(MovieCastPostersConnector);
|
@ -1,175 +0,0 @@
|
||||
import classNames from 'classnames';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../MovieCreditPoster.css';
|
||||
|
||||
class MovieCrewPoster extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasPosterError: false,
|
||||
isEditImportListModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onEditImportListPress = () => {
|
||||
this.setState({ isEditImportListModalOpen: true });
|
||||
};
|
||||
|
||||
onAddImportListPress = () => {
|
||||
this.props.onImportListSelect();
|
||||
this.setState({ isEditImportListModalOpen: true });
|
||||
};
|
||||
|
||||
onEditImportListModalClose = () => {
|
||||
this.setState({ isEditImportListModalOpen: false });
|
||||
};
|
||||
|
||||
onPosterLoad = () => {
|
||||
if (this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: false });
|
||||
}
|
||||
};
|
||||
|
||||
onPosterLoadError = () => {
|
||||
if (!this.state.hasPosterError) {
|
||||
this.setState({ hasPosterError: true });
|
||||
}
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
tmdbId,
|
||||
personName,
|
||||
job,
|
||||
images,
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
importList
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
hasPosterError
|
||||
} = this.state;
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`,
|
||||
borderRadius: '5px'
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
width: `${posterWidth}px`
|
||||
};
|
||||
|
||||
const monitored = importList !== undefined && importList.enabled && importList.enableAuto;
|
||||
const importListId = importList ? importList.id : 0;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={styles.content}
|
||||
style={contentStyle}
|
||||
>
|
||||
<div className={styles.posterContainer}>
|
||||
<div className={styles.toggleMonitoredContainer}>
|
||||
<MonitorToggleButton
|
||||
className={styles.monitorToggleButton}
|
||||
monitored={monitored}
|
||||
size={20}
|
||||
onPress={importListId > 0 ? this.onEditImportListPress : this.onAddImportListPress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label className={styles.controls}>
|
||||
<span className={styles.externalLinks}>
|
||||
<Popover
|
||||
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
|
||||
title={translate('Links')}
|
||||
body={
|
||||
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
|
||||
<Label
|
||||
className={styles.externalLinkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{translate('TMDb')}
|
||||
</Label>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
<div
|
||||
style={elementStyle}
|
||||
>
|
||||
<MovieHeadshot
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
onError={this.onPosterLoadError}
|
||||
onLoad={this.onPosterLoad}
|
||||
/>
|
||||
|
||||
{
|
||||
hasPosterError &&
|
||||
<div className={styles.overlayTitle}>
|
||||
{personName}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{personName}
|
||||
</div>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{job}
|
||||
</div>
|
||||
|
||||
<EditImportListModalConnector
|
||||
id={importListId}
|
||||
isOpen={this.state.isEditImportListModalOpen}
|
||||
onModalClose={this.onEditImportListModalClose}
|
||||
onDeleteImportListPress={this.onDeleteImportListPress}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCrewPoster.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
personName: PropTypes.string.isRequired,
|
||||
job: PropTypes.string.isRequired,
|
||||
images: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
posterWidth: PropTypes.number.isRequired,
|
||||
posterHeight: PropTypes.number.isRequired,
|
||||
importList: PropTypes.object,
|
||||
onImportListSelect: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieCrewPoster;
|
@ -0,0 +1,177 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import Icon from 'Components/Icon';
|
||||
import Label from 'Components/Label';
|
||||
import Link from 'Components/Link/Link';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import Popover from 'Components/Tooltip/Popover';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import MovieHeadshot from 'Movie/MovieHeadshot';
|
||||
import EditImportListModalConnector from 'Settings/ImportLists/ImportLists/EditImportListModalConnector';
|
||||
import { deleteImportList } from 'Store/Actions/Settings/importLists';
|
||||
import ImportList from 'typings/ImportList';
|
||||
import MovieCredit from 'typings/MovieCredit';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from '../MovieCreditPoster.css';
|
||||
|
||||
export interface MovieCrewPosterProps
|
||||
extends Pick<MovieCredit, 'personName' | 'images' | 'job'> {
|
||||
tmdbId: number;
|
||||
posterWidth: number;
|
||||
posterHeight: number;
|
||||
importList?: ImportList;
|
||||
onImportListSelect(): void;
|
||||
}
|
||||
|
||||
function MovieCrewPoster(props: MovieCrewPosterProps) {
|
||||
const {
|
||||
tmdbId,
|
||||
personName,
|
||||
job,
|
||||
images = [],
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
importList,
|
||||
onImportListSelect,
|
||||
} = props;
|
||||
|
||||
const importListId = importList?.id ?? 0;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [hasPosterError, setHasPosterError] = useState(false);
|
||||
|
||||
const [
|
||||
isEditImportListModalOpen,
|
||||
setEditImportListModalOpen,
|
||||
setEditImportListModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const [
|
||||
isDeleteImportListModalOpen,
|
||||
setDeleteImportListModalOpen,
|
||||
setDeleteImportListModalClosed,
|
||||
] = useModalOpenState(false);
|
||||
|
||||
const handlePosterLoadError = useCallback(() => {
|
||||
setHasPosterError(true);
|
||||
}, [setHasPosterError]);
|
||||
|
||||
const handlePosterLoad = useCallback(() => {
|
||||
setHasPosterError(false);
|
||||
}, [setHasPosterError]);
|
||||
|
||||
const handleManageImportListPress = useCallback(() => {
|
||||
if (importListId === 0) {
|
||||
onImportListSelect();
|
||||
}
|
||||
|
||||
setEditImportListModalOpen();
|
||||
}, [importListId, onImportListSelect, setEditImportListModalOpen]);
|
||||
|
||||
const handleDeleteImportListConfirmed = useCallback(() => {
|
||||
dispatch(deleteImportList({ id: importListId }));
|
||||
|
||||
setEditImportListModalClosed();
|
||||
setDeleteImportListModalClosed();
|
||||
}, [
|
||||
importListId,
|
||||
setEditImportListModalClosed,
|
||||
setDeleteImportListModalClosed,
|
||||
dispatch,
|
||||
]);
|
||||
|
||||
const elementStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
height: `${posterHeight}px`,
|
||||
borderRadius: '5px',
|
||||
};
|
||||
|
||||
const contentStyle = {
|
||||
width: `${posterWidth}px`,
|
||||
};
|
||||
|
||||
const monitored =
|
||||
importList?.enabled === true && importList?.enableAuto === true;
|
||||
|
||||
return (
|
||||
<div className={styles.content} style={contentStyle}>
|
||||
<div className={styles.posterContainer}>
|
||||
<div className={styles.toggleMonitoredContainer}>
|
||||
<MonitorToggleButton
|
||||
className={styles.monitorToggleButton}
|
||||
monitored={monitored}
|
||||
size={20}
|
||||
onPress={handleManageImportListPress}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Label className={styles.controls}>
|
||||
<span className={styles.externalLinks}>
|
||||
<Popover
|
||||
anchor={<Icon name={icons.EXTERNAL_LINK} size={12} />}
|
||||
title={translate('Links')}
|
||||
body={
|
||||
<Link to={`https://www.themoviedb.org/person/${tmdbId}`}>
|
||||
<Label
|
||||
className={styles.externalLinkLabel}
|
||||
kind={kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{translate('TMDb')}
|
||||
</Label>
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
</Label>
|
||||
|
||||
<div style={elementStyle}>
|
||||
<MovieHeadshot
|
||||
className={styles.poster}
|
||||
style={elementStyle}
|
||||
images={images}
|
||||
size={250}
|
||||
lazy={false}
|
||||
overflow={true}
|
||||
onError={handlePosterLoadError}
|
||||
onLoad={handlePosterLoad}
|
||||
/>
|
||||
|
||||
{hasPosterError && (
|
||||
<div className={styles.overlayTitle}>{personName}</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>
|
||||
{personName}
|
||||
</div>
|
||||
<div className={classNames(styles.title, 'swiper-no-swiping')}>{job}</div>
|
||||
|
||||
<EditImportListModalConnector
|
||||
id={importListId}
|
||||
isOpen={isEditImportListModalOpen}
|
||||
onModalClose={setEditImportListModalClosed}
|
||||
onDeleteImportListPress={setDeleteImportListModalOpen}
|
||||
/>
|
||||
|
||||
<ConfirmModal
|
||||
isOpen={isDeleteImportListModalOpen}
|
||||
kind={kinds.DANGER}
|
||||
title={translate('DeleteImportList')}
|
||||
message={translate('DeleteImportListMessageText', {
|
||||
name: importList?.name ?? personName,
|
||||
})}
|
||||
confirmLabel={translate('Delete')}
|
||||
onConfirm={handleDeleteImportListConfirmed}
|
||||
onCancel={setDeleteImportListModalClosed}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCrewPoster;
|
@ -0,0 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import createMovieCreditsSelector from 'Store/Selectors/createMovieCreditsSelector';
|
||||
import MovieCreditPosters from '../MovieCreditPosters';
|
||||
import MovieCrewPoster from './MovieCrewPoster';
|
||||
|
||||
interface MovieCrewPostersProps {
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
function MovieCrewPosters({ isSmallScreen }: MovieCrewPostersProps) {
|
||||
const { items: crewCredits } = useSelector(
|
||||
createMovieCreditsSelector('crew')
|
||||
);
|
||||
|
||||
return (
|
||||
<MovieCreditPosters
|
||||
items={crewCredits}
|
||||
itemComponent={MovieCrewPoster}
|
||||
isSmallScreen={isSmallScreen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCrewPosters;
|
@ -1,68 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import MovieCreditPosters from '../MovieCreditPosters';
|
||||
import MovieCrewPoster from './MovieCrewPoster';
|
||||
|
||||
function crewSort(a, b) {
|
||||
const jobOrder = ['Director', 'Writer', 'Producer', 'Executive Producer', 'Director of Photography'];
|
||||
|
||||
const indexA = jobOrder.indexOf(a.job);
|
||||
const indexB = jobOrder.indexOf(b.job);
|
||||
|
||||
if (indexA === -1 && indexB === -1) {
|
||||
return 0;
|
||||
} else if (indexA === -1) {
|
||||
return 1;
|
||||
} else if (indexB === -1) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (indexA < indexB) {
|
||||
return -1;
|
||||
} else if (indexA > indexB) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.movieCredits.items,
|
||||
(credits) => {
|
||||
const crew = _.reduce(credits, (acc, credit) => {
|
||||
if (credit.type === 'crew') {
|
||||
acc.push(credit);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const sortedCrew = crew.sort(crewSort);
|
||||
|
||||
return {
|
||||
items: _.uniqBy(sortedCrew, 'personName')
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class MovieCrewPostersConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
|
||||
return (
|
||||
<MovieCreditPosters
|
||||
{...this.props}
|
||||
itemComponent={MovieCrewPoster}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(MovieCrewPostersConnector);
|
@ -0,0 +1,60 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import {
|
||||
selectImportListSchema,
|
||||
setImportListFieldValue,
|
||||
setImportListValue,
|
||||
} from 'Store/Actions/settingsActions';
|
||||
import createMovieCreditImportListSelector from 'Store/Selectors/createMovieCreditImportListSelector';
|
||||
import { MovieCastPosterProps } from './Cast/MovieCastPoster';
|
||||
import { MovieCrewPosterProps } from './Crew/MovieCrewPoster';
|
||||
|
||||
type MovieCreditPosterProps = {
|
||||
component: React.ElementType;
|
||||
} & (
|
||||
| Omit<MovieCrewPosterProps, 'onImportListSelect'>
|
||||
| Omit<MovieCastPosterProps, 'onImportListSelect'>
|
||||
);
|
||||
|
||||
function MovieCreditPoster({
|
||||
component: ItemComponent,
|
||||
tmdbId,
|
||||
personName,
|
||||
...otherProps
|
||||
}: MovieCreditPosterProps) {
|
||||
const importList = useSelector(createMovieCreditImportListSelector(tmdbId));
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleImportListSelect = useCallback(() => {
|
||||
dispatch(
|
||||
selectImportListSchema({
|
||||
implementation: 'TMDbPersonImport',
|
||||
implementationName: 'TMDb Person',
|
||||
presetName: undefined,
|
||||
})
|
||||
);
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error 'setImportListFieldValue' isn't typed yet
|
||||
setImportListFieldValue({ name: 'personId', value: tmdbId.toString() })
|
||||
);
|
||||
|
||||
dispatch(
|
||||
// @ts-expect-error 'setImportListValue' isn't typed yet
|
||||
setImportListValue({ name: 'name', value: `${personName} - ${tmdbId}` })
|
||||
);
|
||||
}, [dispatch, tmdbId, personName]);
|
||||
|
||||
return (
|
||||
<ItemComponent
|
||||
{...otherProps}
|
||||
tmdbId={tmdbId}
|
||||
personName={personName}
|
||||
importList={importList}
|
||||
onImportListSelect={handleImportListSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCreditPoster;
|
@ -1,66 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { selectImportListSchema, setImportListFieldValue, setImportListValue } from 'Store/Actions/settingsActions';
|
||||
import createMovieCreditListSelector from 'Store/Selectors/createMovieCreditListSelector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMovieCreditListSelector(),
|
||||
(importList) => {
|
||||
return {
|
||||
importList
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
selectImportListSchema,
|
||||
setImportListFieldValue,
|
||||
setImportListValue
|
||||
};
|
||||
|
||||
class MovieCreditPosterConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onImportListSelect = () => {
|
||||
this.props.selectImportListSchema({ implementation: 'TMDbPersonImport', implementationName: 'TMDb Person', presetName: undefined });
|
||||
this.props.setImportListFieldValue({ name: 'personId', value: this.props.tmdbId.toString() });
|
||||
this.props.setImportListValue({ name: 'name', value: `${this.props.personName} - ${this.props.tmdbId}` });
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
tmdbId,
|
||||
component: ItemComponent,
|
||||
personName
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<ItemComponent
|
||||
{...this.props}
|
||||
tmdbId={tmdbId}
|
||||
personName={personName}
|
||||
onImportListSelect={this.onImportListSelect}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCreditPosterConnector.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
personName: PropTypes.string.isRequired,
|
||||
component: PropTypes.elementType.isRequired,
|
||||
selectImportListSchema: PropTypes.func.isRequired,
|
||||
setImportListFieldValue: PropTypes.func.isRequired,
|
||||
setImportListValue: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieCreditPosterConnector);
|
@ -1,109 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { Navigation } from 'swiper';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import MovieCreditPosterConnector from './MovieCreditPosterConnector';
|
||||
import styles from './MovieCreditPosters.css';
|
||||
|
||||
// Import Swiper styles
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(dimensions.movieIndexColumnPaddingSmallScreen);
|
||||
|
||||
function calculateRowHeight(posterHeight, isSmallScreen) {
|
||||
const titleHeight = 19;
|
||||
const characterHeight = 19;
|
||||
|
||||
const heights = [
|
||||
posterHeight,
|
||||
titleHeight,
|
||||
characterHeight,
|
||||
isSmallScreen ? columnPaddingSmallScreen : columnPadding
|
||||
];
|
||||
|
||||
return heights.reduce((acc, height) => acc + height, 0);
|
||||
}
|
||||
|
||||
class MovieCreditPosters extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
width: 0,
|
||||
columnWidth: 182,
|
||||
columnCount: 1,
|
||||
posterWidth: 162,
|
||||
posterHeight: 238,
|
||||
rowHeight: calculateRowHeight(238, props.isSmallScreen)
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
items,
|
||||
itemComponent,
|
||||
isSmallScreen
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
posterWidth,
|
||||
posterHeight,
|
||||
rowHeight
|
||||
} = this.state;
|
||||
|
||||
return (
|
||||
|
||||
<div className={styles.sliderContainer}>
|
||||
<Swiper
|
||||
slidesPerView='auto'
|
||||
spaceBetween={10}
|
||||
slidesPerGroup={isSmallScreen ? 1 : 3}
|
||||
navigation={true}
|
||||
loop={false}
|
||||
loopFillGroupWithBlank={true}
|
||||
className="mySwiper"
|
||||
modules={[Navigation]}
|
||||
onInit={(swiper) => {
|
||||
swiper.navigation.init();
|
||||
swiper.navigation.update();
|
||||
}}
|
||||
>
|
||||
{items.map((credit) => (
|
||||
<SwiperSlide key={credit.id} style={{ width: posterWidth, height: rowHeight }}>
|
||||
<MovieCreditPosterConnector
|
||||
key={credit.id}
|
||||
component={itemComponent}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
tmdbId={credit.personTmdbId}
|
||||
personName={credit.personName}
|
||||
job={credit.job}
|
||||
character={credit.character}
|
||||
images={credit.images}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCreditPosters.propTypes = {
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
itemComponent: PropTypes.elementType.isRequired,
|
||||
isSmallScreen: PropTypes.bool.isRequired
|
||||
};
|
||||
|
||||
export default MovieCreditPosters;
|
@ -0,0 +1,87 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Navigation } from 'swiper';
|
||||
import { Swiper, SwiperSlide } from 'swiper/react';
|
||||
import { Swiper as SwiperClass } from 'swiper/types';
|
||||
import dimensions from 'Styles/Variables/dimensions';
|
||||
import MovieCredit from 'typings/MovieCredit';
|
||||
import MovieCreditPoster from './MovieCreditPoster';
|
||||
import styles from './MovieCreditPosters.css';
|
||||
|
||||
// Import Swiper styles
|
||||
import 'swiper/css';
|
||||
import 'swiper/css/navigation';
|
||||
|
||||
// Poster container dimensions
|
||||
const columnPadding = parseInt(dimensions.movieIndexColumnPadding);
|
||||
const columnPaddingSmallScreen = parseInt(
|
||||
dimensions.movieIndexColumnPaddingSmallScreen
|
||||
);
|
||||
|
||||
interface MovieCreditPostersProps {
|
||||
items: MovieCredit[];
|
||||
itemComponent: React.ElementType;
|
||||
isSmallScreen: boolean;
|
||||
}
|
||||
|
||||
function MovieCreditPosters(props: MovieCreditPostersProps) {
|
||||
const { items, itemComponent, isSmallScreen } = props;
|
||||
|
||||
const posterWidth = 162;
|
||||
const posterHeight = 238;
|
||||
|
||||
const rowHeight = useMemo(() => {
|
||||
const titleHeight = 19;
|
||||
const characterHeight = 19;
|
||||
|
||||
const heights = [
|
||||
posterHeight,
|
||||
titleHeight,
|
||||
characterHeight,
|
||||
isSmallScreen ? columnPaddingSmallScreen : columnPadding,
|
||||
];
|
||||
|
||||
return heights.reduce((acc, height) => acc + height, 0);
|
||||
}, [posterHeight, isSmallScreen]);
|
||||
|
||||
const handleSwiperInit = useCallback((swiper: SwiperClass) => {
|
||||
swiper.navigation.init();
|
||||
swiper.navigation.update();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={styles.sliderContainer}>
|
||||
<Swiper
|
||||
slidesPerView="auto"
|
||||
spaceBetween={10}
|
||||
slidesPerGroup={isSmallScreen ? 1 : 3}
|
||||
navigation={true}
|
||||
loop={false}
|
||||
loopFillGroupWithBlank={true}
|
||||
className="mySwiper"
|
||||
modules={[Navigation]}
|
||||
onInit={handleSwiperInit}
|
||||
>
|
||||
{items.map((credit) => (
|
||||
<SwiperSlide
|
||||
key={credit.id}
|
||||
style={{ width: posterWidth, height: rowHeight }}
|
||||
>
|
||||
<MovieCreditPoster
|
||||
key={credit.id}
|
||||
component={itemComponent}
|
||||
posterWidth={posterWidth}
|
||||
posterHeight={posterHeight}
|
||||
tmdbId={credit.personTmdbId}
|
||||
personName={credit.personName}
|
||||
images={credit.images}
|
||||
job={credit.job}
|
||||
character={credit.character}
|
||||
/>
|
||||
</SwiperSlide>
|
||||
))}
|
||||
</Swiper>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCreditPosters;
|
@ -1,25 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import MovieImage from './MovieImage';
|
||||
|
||||
const posterPlaceholder = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKgAAAD3CAMAAAC+Te+kAAAAZlBMVEUvLi8vLy8vLzAvMDAwLy8wLzAwMDAwMDEwMTExMDAxMDExMTExMTIxMjIyMjIyMjMyMzMzMjMzMzMzMzQzNDQ0NDQ0NDU0NTU1NTU1NTY1NjY2NTY2NjY2Njc2Nzc3Njc3Nzc3NziHChLWAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+MKCgEdHeShUbsAAALZSURBVHja7dxNcuwgDEZR1qAVmP1vMrNUJe91GfTzCSpXo575lAymjYWGXRIDKFCgQIECBQoUKFCgQIECBQoUKFCgQIECBQoUKFCgQIECBQoUKNA/AZ3fcTR0/owjofNDnAadnwPoPnS+xTXQeQZ0rkQ/dC4H0Gzo7ITO3bgGOnug/2PcAF3Mczt0fUj0QncG7znQBupw3PkWqh8qpkagpnyqjuArkkxaC02kRqGypCZANVYFdJZCdy9WTRVB5znQ6qTmjFFBWnOhdg20Lqnp0CpqAbRmAJRAK5JaA32zngTNvv910OSkVkJTs1oLtWugeTkNQZ/nkT2rotBHldUwNE6VQTVWGTQ6AHKggqGaBS23JkKf0hUgE1qa01Ro5fzPhoapR0HtCGg4q0poSCqFRgaAFhqxqqEr1EOgmdJaqHdaHQq1I6CunPZAHdY2aIJUBN2V9kE3H1Wd0BXrNVA7BLpgdUCtALo8pZqhdgd0Z6OyE7q1pdoH3dv7tS7o7iZ1E3R/N70Huuz795cQao65vvkqooT+vEgDdPcbj2s3zxTv9Qt/7cuhdgfUo2yAOplyqNuphfqZSqhFmEJo0HkcdPZCo0rRymRxpwSawHR+YtyBZihfvi+nQO0OqCmcYahGqYPGS4qCUJkzBpUpJdCkordyaFZxXi1UUpaZAJ2XQFOLh8ug2XXjVdD0+vYiqLIO3w1VH8EogtoxUPnpGxe04zyTA1p57i4T2nTmbnnnUuLMg1afYE2C1h+1zYEKjlknQLtPg9tb3YzU+dL054qOBb8cvcz3DlqBZhUmhdrnKo9j+pR0rkN5UHkznZHPtJIYN2TTCe1poTUyk9nWPO0bt8Ys7Ug34mlUMONtPUXMaEdXnXN1MnUzN2Z9q3Lr8XQN1DaLQJpXpiamZwltYdIUHShQoECBAgUKFChQoECBAgUKFChQoECBAgUKFChQoECBAgUKFCjQ+vgCff/mEp/vtiIAAAAASUVORK5CYII=';
|
||||
|
||||
function MovieHeadshot(props) {
|
||||
return (
|
||||
<MovieImage
|
||||
{...props}
|
||||
coverType="headshot"
|
||||
placeholder={posterPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
MovieHeadshot.propTypes = {
|
||||
size: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
MovieHeadshot.defaultProps = {
|
||||
size: 250
|
||||
};
|
||||
|
||||
export default MovieHeadshot;
|
@ -0,0 +1,23 @@
|
||||
import React from 'react';
|
||||
import MovieImage, { MovieImageProps } from './MovieImage';
|
||||
|
||||
const posterPlaceholder =
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKgAAAD3CAMAAAC+Te+kAAAAZlBMVEUvLi8vLy8vLzAvMDAwLy8wLzAwMDAwMDEwMTExMDAxMDExMTExMTIxMjIyMjIyMjMyMzMzMjMzMzMzMzQzNDQ0NDQ0NDU0NTU1NTU1NTY1NjY2NTY2NjY2Njc2Nzc3Njc3Nzc3NziHChLWAAAAAWJLR0QAiAUdSAAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+MKCgEdHeShUbsAAALZSURBVHja7dxNcuwgDEZR1qAVmP1vMrNUJe91GfTzCSpXo575lAymjYWGXRIDKFCgQIECBQoUKFCgQIECBQoUKFCgQIECBQoUKFCgQIECBQoUKNA/AZ3fcTR0/owjofNDnAadnwPoPnS+xTXQeQZ0rkQ/dC4H0Gzo7ITO3bgGOnug/2PcAF3Mczt0fUj0QncG7znQBupw3PkWqh8qpkagpnyqjuArkkxaC02kRqGypCZANVYFdJZCdy9WTRVB5znQ6qTmjFFBWnOhdg20Lqnp0CpqAbRmAJRAK5JaA32zngTNvv910OSkVkJTs1oLtWugeTkNQZ/nkT2rotBHldUwNE6VQTVWGTQ6AHKggqGaBS23JkKf0hUgE1qa01Ro5fzPhoapR0HtCGg4q0poSCqFRgaAFhqxqqEr1EOgmdJaqHdaHQq1I6CunPZAHdY2aIJUBN2V9kE3H1Wd0BXrNVA7BLpgdUCtALo8pZqhdgd0Z6OyE7q1pdoH3dv7tS7o7iZ1E3R/N70Huuz795cQao65vvkqooT+vEgDdPcbj2s3zxTv9Qt/7cuhdgfUo2yAOplyqNuphfqZSqhFmEJo0HkcdPZCo0rRymRxpwSawHR+YtyBZihfvi+nQO0OqCmcYahGqYPGS4qCUJkzBpUpJdCkordyaFZxXi1UUpaZAJ2XQFOLh8ug2XXjVdD0+vYiqLIO3w1VH8EogtoxUPnpGxe04zyTA1p57i4T2nTmbnnnUuLMg1afYE2C1h+1zYEKjlknQLtPg9tb3YzU+dL054qOBb8cvcz3DlqBZhUmhdrnKo9j+pR0rkN5UHkznZHPtJIYN2TTCe1poTUyk9nWPO0bt8Ys7Ug34mlUMONtPUXMaEdXnXN1MnUzN2Z9q3Lr8XQN1DaLQJpXpiamZwltYdIUHShQoECBAgUKFChQoECBAgUKFChQoECBAgUKFChQoECBAgUKFCjQ+vgCff/mEp/vtiIAAAAASUVORK5CYII=';
|
||||
|
||||
interface MovieHeadshotProps
|
||||
extends Omit<MovieImageProps, 'coverType' | 'placeholder'> {
|
||||
size?: 250 | 500;
|
||||
}
|
||||
|
||||
function MovieHeadshot({ size = 250, ...otherProps }: MovieHeadshotProps) {
|
||||
return (
|
||||
<MovieImage
|
||||
{...otherProps}
|
||||
size={size}
|
||||
coverType="headshot"
|
||||
placeholder={posterPlaceholder}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieHeadshot;
|
@ -0,0 +1,37 @@
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import ImportList from 'typings/ImportList';
|
||||
|
||||
function createMovieCreditImportListSelector(tmdbId: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.settings.importLists.items,
|
||||
(importLists) => {
|
||||
const importListIds = importLists.reduce(
|
||||
(acc: ImportList[], importList) => {
|
||||
if (importList.implementation === 'TMDbPersonImport') {
|
||||
const personIdValue = importList.fields.find(
|
||||
(field) => field.name === 'personId'
|
||||
)?.value as string | null;
|
||||
|
||||
if (personIdValue && parseInt(personIdValue) === tmdbId) {
|
||||
acc.push(importList);
|
||||
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
if (importListIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return importListIds[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createMovieCreditImportListSelector;
|
@ -1,33 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
|
||||
function createMovieCreditListSelector() {
|
||||
return createSelector(
|
||||
(state, { tmdbId }) => tmdbId,
|
||||
(state) => state.settings.importLists.items,
|
||||
(tmdbId, importLists) => {
|
||||
const importListIds = _.reduce(importLists, (acc, list) => {
|
||||
if (list.implementation === 'TMDbPersonImport') {
|
||||
const personIdField = list.fields.find((field) => {
|
||||
return field.name === 'personId';
|
||||
});
|
||||
|
||||
if (personIdField && parseInt(personIdField.value) === tmdbId) {
|
||||
acc.push(list);
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
if (importListIds.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return importListIds[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createMovieCreditListSelector;
|
@ -0,0 +1,23 @@
|
||||
import _ from 'lodash';
|
||||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
import { MovieCreditType } from 'typings/MovieCredit';
|
||||
|
||||
function createMovieCreditsSelector(movieCreditType: MovieCreditType) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.movieCredits.items,
|
||||
(movieCredits) => {
|
||||
const credits = movieCredits.filter(
|
||||
({ type }) => type === movieCreditType
|
||||
);
|
||||
|
||||
const sortedCredits = credits.sort((a, b) => a.order - b.order);
|
||||
|
||||
return {
|
||||
items: _.uniqBy(sortedCredits, 'personName'),
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default createMovieCreditsSelector;
|
@ -0,0 +1,17 @@
|
||||
import ModelBase from 'App/ModelBase';
|
||||
import { Image } from 'Movie/Movie';
|
||||
|
||||
export type MovieCreditType = 'cast' | 'crew';
|
||||
|
||||
interface MovieCredit extends ModelBase {
|
||||
personTmdbId: number;
|
||||
personName: string;
|
||||
images: Image[];
|
||||
type: MovieCreditType;
|
||||
department: string;
|
||||
job: string;
|
||||
character: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export default MovieCredit;
|
Loading…
Reference in new issue