mirror of
https://github.com/Radarr/Radarr.git
synced 2025-04-23 22:17:15 -04:00
Convert Movie Formats/Status/CollectionLabel to TypeScript
This commit is contained in:
parent
102849a697
commit
8ec60eb0a6
18 changed files with 137 additions and 224 deletions
|
@ -1,7 +1,11 @@
|
|||
import AppSectionState from 'App/State/AppSectionState';
|
||||
import AppSectionState, {
|
||||
AppSectionSaveState,
|
||||
} from 'App/State/AppSectionState';
|
||||
import MovieCollection from 'typings/MovieCollection';
|
||||
|
||||
interface MovieCollectionAppState extends AppSectionState<MovieCollection> {
|
||||
interface MovieCollectionAppState
|
||||
extends AppSectionState<MovieCollection>,
|
||||
AppSectionSaveState {
|
||||
itemMap: Record<number, number>;
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
reprocessInteractiveImportItems,
|
||||
updateInteractiveImportItem,
|
||||
} from 'Store/Actions/interactiveImportActions';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import { SelectStateInputProps } from 'typings/props';
|
||||
import Rejection from 'typings/Rejection';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
|
@ -52,7 +53,7 @@ interface InteractiveImportRowProps {
|
|||
quality?: QualityModel;
|
||||
languages?: Language[];
|
||||
size: number;
|
||||
customFormats?: object[];
|
||||
customFormats?: CustomFormat[];
|
||||
customFormatScore?: number;
|
||||
indexerFlags: number;
|
||||
rejections: Rejection[];
|
||||
|
|
|
@ -2,6 +2,7 @@ import ModelBase from 'App/ModelBase';
|
|||
import Language from 'Language/Language';
|
||||
import Movie from 'Movie/Movie';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import Rejection from 'typings/Rejection';
|
||||
|
||||
export interface InteractiveImportCommandOptions {
|
||||
|
@ -27,7 +28,7 @@ interface InteractiveImport extends ModelBase {
|
|||
languages: Language[];
|
||||
movie?: Movie;
|
||||
qualityWeight: number;
|
||||
customFormats: object[];
|
||||
customFormats: CustomFormat[];
|
||||
indexerFlags: number;
|
||||
rejections: Rejection[];
|
||||
movieFileId?: number;
|
||||
|
|
|
@ -27,7 +27,7 @@ import DeleteMovieModal from 'Movie/Delete/DeleteMovieModal';
|
|||
import EditMovieModalConnector from 'Movie/Edit/EditMovieModalConnector';
|
||||
import getMovieStatusDetails from 'Movie/getMovieStatusDetails';
|
||||
import MovieHistoryModal from 'Movie/History/MovieHistoryModal';
|
||||
import MovieCollectionLabelConnector from 'Movie/MovieCollectionLabelConnector';
|
||||
import MovieCollectionLabel from 'Movie/MovieCollectionLabel';
|
||||
import MovieGenres from 'Movie/MovieGenres';
|
||||
import MoviePoster from 'Movie/MoviePoster';
|
||||
import MovieInteractiveSearchModal from 'Movie/Search/MovieInteractiveSearchModal';
|
||||
|
@ -609,7 +609,7 @@ class MovieDetails extends Component {
|
|||
size={sizes.LARGE}
|
||||
>
|
||||
<div className={styles.collection}>
|
||||
<MovieCollectionLabelConnector
|
||||
<MovieCollectionLabel
|
||||
tmdbId={collection.tmdbId}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -79,6 +79,7 @@ interface Movie extends ModelBase {
|
|||
images: Image[];
|
||||
movieFile: MovieFile;
|
||||
hasFile: boolean;
|
||||
grabbed?: boolean;
|
||||
lastSearchTime?: string;
|
||||
isAvailable: boolean;
|
||||
isSaving?: boolean;
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import styles from './MovieCollectionLabel.css';
|
||||
|
||||
class MovieCollectionLabel extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
hasPosterError: false
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const {
|
||||
title,
|
||||
monitored,
|
||||
onMonitorTogglePress
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MonitorToggleButton
|
||||
className={styles.monitorToggleButton}
|
||||
monitored={monitored}
|
||||
size={15}
|
||||
onPress={onMonitorTogglePress}
|
||||
/>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCollectionLabel.propTypes = {
|
||||
title: PropTypes.string.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
onMonitorTogglePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MovieCollectionLabel;
|
46
frontend/src/Movie/MovieCollectionLabel.tsx
Normal file
46
frontend/src/Movie/MovieCollectionLabel.tsx
Normal file
|
@ -0,0 +1,46 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import MonitorToggleButton from 'Components/MonitorToggleButton';
|
||||
import { toggleCollectionMonitored } from 'Store/Actions/movieCollectionActions';
|
||||
import { createCollectionSelectorForHook } from 'Store/Selectors/createCollectionSelector';
|
||||
import MovieCollection from 'typings/MovieCollection';
|
||||
import styles from './MovieCollectionLabel.css';
|
||||
|
||||
interface MovieCollectionLabelProps {
|
||||
tmdbId: number;
|
||||
}
|
||||
|
||||
function MovieCollectionLabel({ tmdbId }: MovieCollectionLabelProps) {
|
||||
const {
|
||||
id,
|
||||
monitored,
|
||||
title,
|
||||
isSaving = false,
|
||||
} = useSelector(createCollectionSelectorForHook(tmdbId)) as MovieCollection;
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const handleMonitorTogglePress = useCallback(
|
||||
(value: boolean) => {
|
||||
dispatch(
|
||||
toggleCollectionMonitored({ collectionId: id, monitored: value })
|
||||
);
|
||||
},
|
||||
[id, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<MonitorToggleButton
|
||||
className={styles.monitorToggleButton}
|
||||
monitored={monitored}
|
||||
isSaving={isSaving}
|
||||
size={15}
|
||||
onPress={handleMonitorTogglePress}
|
||||
/>
|
||||
{title}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieCollectionLabel;
|
|
@ -1,57 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { toggleCollectionMonitored } from 'Store/Actions/movieCollectionActions';
|
||||
import MovieCollectionLabel from './MovieCollectionLabel';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state, { tmdbId }) => tmdbId,
|
||||
(state) => state.movieCollections.items,
|
||||
(tmdbId, collections) => {
|
||||
const collection = collections.find((movie) => movie.tmdbId === tmdbId);
|
||||
return {
|
||||
...collection
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
toggleCollectionMonitored
|
||||
};
|
||||
|
||||
class MovieCollectionLabelConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onMonitorTogglePress = (monitored) => {
|
||||
this.props.toggleCollectionMonitored({
|
||||
collectionId: this.props.id,
|
||||
monitored
|
||||
});
|
||||
};
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MovieCollectionLabel
|
||||
{...this.props}
|
||||
onMonitorTogglePress={this.onMonitorTogglePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieCollectionLabelConnector.propTypes = {
|
||||
tmdbId: PropTypes.number.isRequired,
|
||||
id: PropTypes.number.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
toggleCollectionMonitored: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MovieCollectionLabelConnector);
|
|
@ -1,33 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
|
||||
function MovieFormats({ formats }) {
|
||||
return (
|
||||
<div>
|
||||
{
|
||||
formats.map((format) => {
|
||||
return (
|
||||
<Label
|
||||
key={format.id}
|
||||
kind={kinds.INFO}
|
||||
>
|
||||
{format.name}
|
||||
</Label>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MovieFormats.propTypes = {
|
||||
formats: PropTypes.arrayOf(PropTypes.object).isRequired
|
||||
};
|
||||
|
||||
MovieFormats.defaultProps = {
|
||||
formats: []
|
||||
};
|
||||
|
||||
export default MovieFormats;
|
22
frontend/src/Movie/MovieFormats.tsx
Normal file
22
frontend/src/Movie/MovieFormats.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React from 'react';
|
||||
import Label from 'Components/Label';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
|
||||
interface MovieFormatsProps {
|
||||
formats: CustomFormat[];
|
||||
}
|
||||
|
||||
function MovieFormats({ formats }: MovieFormatsProps) {
|
||||
return (
|
||||
<div>
|
||||
{formats.map(({ id, name }) => (
|
||||
<Label key={id} kind={kinds.INFO}>
|
||||
{name}
|
||||
</Label>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieFormats;
|
|
@ -1,32 +1,40 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import QueueDetails from 'Activity/Queue/QueueDetails';
|
||||
import Icon from 'Components/Icon';
|
||||
import ProgressBar from 'Components/ProgressBar';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Movie from 'Movie/Movie';
|
||||
import useMovie, { MovieEntity } from 'Movie/useMovie';
|
||||
import useMovieFile from 'MovieFile/useMovieFile';
|
||||
import { createQueueItemSelectorForHook } from 'Store/Selectors/createQueueItemSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MovieQuality from './MovieQuality';
|
||||
import styles from './MovieStatus.css';
|
||||
|
||||
function MovieStatus(props) {
|
||||
interface MovieStatusProps {
|
||||
movieId: number;
|
||||
movieEntity?: MovieEntity;
|
||||
movieFileId: number | undefined;
|
||||
}
|
||||
|
||||
function MovieStatus({ movieId, movieFileId }: MovieStatusProps) {
|
||||
const {
|
||||
isAvailable,
|
||||
monitored,
|
||||
grabbed,
|
||||
queueItem,
|
||||
movieFile
|
||||
} = props;
|
||||
grabbed = false,
|
||||
} = useMovie(movieId) as Movie;
|
||||
|
||||
const queueItem = useSelector(createQueueItemSelectorForHook(movieId));
|
||||
const movieFile = useMovieFile(movieFileId);
|
||||
|
||||
const hasMovieFile = !!movieFile;
|
||||
const isQueued = !!queueItem;
|
||||
|
||||
if (isQueued) {
|
||||
const {
|
||||
sizeleft,
|
||||
size
|
||||
} = queueItem;
|
||||
const { sizeleft, size } = queueItem;
|
||||
|
||||
const progress = size ? (100 - sizeleft / size * 100) : 0;
|
||||
const progress = size ? 100 - (sizeleft / size) * 100 : 0;
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
|
@ -86,30 +94,16 @@ function MovieStatus(props) {
|
|||
if (isAvailable) {
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Icon
|
||||
name={icons.MISSING}
|
||||
title={translate('MovieMissingFromDisk')}
|
||||
/>
|
||||
<Icon name={icons.MISSING} title={translate('MovieMissingFromDisk')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.center}>
|
||||
<Icon
|
||||
name={icons.NOT_AIRED}
|
||||
title={translate('MovieIsNotAvailable')}
|
||||
/>
|
||||
<Icon name={icons.NOT_AIRED} title={translate('MovieIsNotAvailable')} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
MovieStatus.propTypes = {
|
||||
isAvailable: PropTypes.bool.isRequired,
|
||||
monitored: PropTypes.bool.isRequired,
|
||||
grabbed: PropTypes.bool,
|
||||
queueItem: PropTypes.object,
|
||||
movieFile: PropTypes.object
|
||||
};
|
||||
|
||||
export default MovieStatus;
|
|
@ -1,50 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import MovieStatus from 'Movie/MovieStatus';
|
||||
import createMovieFileSelector from 'Store/Selectors/createMovieFileSelector';
|
||||
import { createMovieByEntitySelector } from 'Store/Selectors/createMovieSelector';
|
||||
import createQueueItemSelector from 'Store/Selectors/createQueueItemSelector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createMovieByEntitySelector(),
|
||||
createQueueItemSelector(),
|
||||
createMovieFileSelector(),
|
||||
(movie, queueItem, movieFile) => {
|
||||
const result = _.pick(movie, [
|
||||
'isAvailable',
|
||||
'monitored',
|
||||
'grabbed'
|
||||
]);
|
||||
|
||||
result.queueItem = queueItem;
|
||||
result.movieFile = movieFile;
|
||||
|
||||
return result;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class MovieStatusConnector extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MovieStatus
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MovieStatusConnector.propTypes = {
|
||||
movieId: PropTypes.number.isRequired,
|
||||
movieFileId: PropTypes.number.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, null)(MovieStatusConnector);
|
|
@ -2,6 +2,13 @@ import { useSelector } from 'react-redux';
|
|||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
||||
export type MovieEntity =
|
||||
| 'calendar'
|
||||
| 'movies'
|
||||
| 'interactiveImport.movies'
|
||||
| 'wanted.cutoffUnmet'
|
||||
| 'wanted.missing';
|
||||
|
||||
export function createMovieSelector(movieId?: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.movies.itemMap,
|
||||
|
@ -12,7 +19,7 @@ export function createMovieSelector(movieId?: number) {
|
|||
);
|
||||
}
|
||||
|
||||
function useMovie(movieId?: number) {
|
||||
function useMovie(movieId: number | undefined) {
|
||||
return useSelector(createMovieSelector(movieId));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,15 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
||||
export function createCollectionSelectorForHook(tmdbId: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.movieCollections.items,
|
||||
(collections) => {
|
||||
return collections.find((item) => item.tmdbId === tmdbId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createCollectionSelector() {
|
||||
return createSelector(
|
||||
(_: AppState, { collectionId }: { collectionId: number }) => collectionId,
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import AppState from 'App/State/AppState';
|
||||
|
||||
export function createQueueItemSelectorForHook(movieId: number) {
|
||||
return createSelector(
|
||||
(state: AppState) => state.queue.details.items,
|
||||
(details) => {
|
||||
if (!movieId || !details) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return details.find((item) => item.movieId === movieId);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function createQueueItemSelector() {
|
||||
return createSelector(
|
||||
(_: AppState, { movieId }: { movieId: number }) => movieId,
|
||||
|
|
|
@ -6,7 +6,7 @@ import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
|||
import TableRow from 'Components/Table/TableRow';
|
||||
import movieEntities from 'Movie/movieEntities';
|
||||
import MovieSearchCell from 'Movie/MovieSearchCell';
|
||||
import MovieStatusConnector from 'Movie/MovieStatusConnector';
|
||||
import MovieStatus from 'Movie/MovieStatus';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import MovieFileLanguages from 'MovieFile/MovieFileLanguages';
|
||||
import styles from './CutoffUnmetRow.css';
|
||||
|
@ -127,7 +127,7 @@ function CutoffUnmetRow(props) {
|
|||
key={name}
|
||||
className={styles.status}
|
||||
>
|
||||
<MovieStatusConnector
|
||||
<MovieStatus
|
||||
movieId={id}
|
||||
movieFileId={movieFileId}
|
||||
movieEntity={movieEntities.WANTED_CUTOFF_UNMET}
|
||||
|
|
|
@ -6,7 +6,7 @@ import TableSelectCell from 'Components/Table/Cells/TableSelectCell';
|
|||
import TableRow from 'Components/Table/TableRow';
|
||||
import movieEntities from 'Movie/movieEntities';
|
||||
import MovieSearchCell from 'Movie/MovieSearchCell';
|
||||
import MovieStatusConnector from 'Movie/MovieStatusConnector';
|
||||
import MovieStatus from 'Movie/MovieStatus';
|
||||
import MovieTitleLink from 'Movie/MovieTitleLink';
|
||||
import styles from './MissingRow.css';
|
||||
|
||||
|
@ -117,7 +117,7 @@ function MissingRow(props) {
|
|||
key={name}
|
||||
className={styles.status}
|
||||
>
|
||||
<MovieStatusConnector
|
||||
<MovieStatus
|
||||
movieId={id}
|
||||
movieFileId={movieFileId}
|
||||
movieEntity={movieEntities.WANTED_MISSING}
|
||||
|
|
|
@ -12,6 +12,7 @@ interface MovieCollection extends ModelBase {
|
|||
movies: Movie[];
|
||||
missingMovies: number;
|
||||
tags: number[];
|
||||
isSaving?: boolean;
|
||||
}
|
||||
|
||||
export default MovieCollection;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue