Convert Delete Movie Modal to TypeScript

Co-authored-by: Mark McDowall <mark@mcdowall.ca>
This commit is contained in:
Bogdan 2025-03-08 16:36:12 +02:00
parent 6a7ed22b44
commit 653b358fd3
7 changed files with 175 additions and 247 deletions

View file

@ -1,37 +0,0 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import DeleteMovieModalContentConnector from './DeleteMovieModalContentConnector';
function DeleteMovieModal(props) {
const {
isOpen,
onModalClose,
previousMovie,
...otherProps
} = props;
return (
<Modal
isOpen={isOpen}
size={sizes.MEDIUM}
onModalClose={onModalClose}
>
<DeleteMovieModalContentConnector
{...otherProps}
onModalClose={onModalClose}
previousMovie={previousMovie}
/>
</Modal>
);
}
DeleteMovieModal.propTypes = {
...DeleteMovieModalContentConnector.propTypes,
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired,
previousMovie: PropTypes.string
};
export default DeleteMovieModal;

View file

@ -0,0 +1,24 @@
import React from 'react';
import Modal from 'Components/Modal/Modal';
import { sizes } from 'Helpers/Props';
import DeleteMovieModalContent, {
DeleteMovieModalContentProps,
} from './DeleteMovieModalContent';
interface DeleteMovieModalProps extends DeleteMovieModalContentProps {
isOpen: boolean;
}
function DeleteMovieModal({
isOpen,
onModalClose,
...otherProps
}: DeleteMovieModalProps) {
return (
<Modal isOpen={isOpen} size={sizes.MEDIUM} onModalClose={onModalClose}>
<DeleteMovieModalContent {...otherProps} onModalClose={onModalClose} />
</Modal>
);
}
export default DeleteMovieModal;

View file

@ -1,163 +0,0 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
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 { icons, inputTypes, kinds } from 'Helpers/Props';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './DeleteMovieModalContent.css';
class DeleteMovieModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
deleteFiles: false
};
}
//
// Listeners
onDeleteFilesChange = ({ value }) => {
this.setState({ deleteFiles: value });
};
onDeleteMovieConfirmed = () => {
const deleteFiles = this.state.deleteFiles;
const addImportExclusion = this.props.deleteOptions.addImportExclusion;
this.setState({ deleteFiles: false });
this.props.onDeletePress(deleteFiles, addImportExclusion);
};
//
// Render
render() {
const {
title,
path,
statistics = {},
deleteOptions,
onModalClose,
onDeleteOptionChange
} = this.props;
const {
movieFileCount = 0,
sizeOnDisk = 0
} = statistics;
const deleteFiles = this.state.deleteFiles;
const addImportExclusion = deleteOptions.addImportExclusion;
return (
<ModalContent
onModalClose={onModalClose}
>
<ModalHeader>
{translate('DeleteHeader', { title })}
</ModalHeader>
<ModalBody>
<div className={styles.pathContainer}>
<Icon
className={styles.pathIcon}
name={icons.FOLDER}
/>
{path}
</div>
<FormGroup>
<FormLabel>
{translate('AddListExclusion')}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportExclusion"
value={addImportExclusion}
helpText={translate('AddListExclusionMovieHelpText')}
kind={kinds.DANGER}
onChange={onDeleteOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>{movieFileCount === 0 ? translate('DeleteMovieFolder') : translate('DeleteMovieFiles', { movieFileCount })}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="deleteFiles"
value={deleteFiles}
helpText={movieFileCount === 0 ? translate('DeleteMovieFolderHelpText') : translate('DeleteMovieFilesHelpText')}
kind={kinds.DANGER}
onChange={this.onDeleteFilesChange}
/>
</FormGroup>
{
deleteFiles ?
<div className={styles.deleteFilesMessage}>
<div><InlineMarkdown data={translate('DeleteMovieFolderConfirmation', { path })} blockClassName={styles.folderPath} /></div>
{
movieFileCount ?
<div className={styles.deleteCount}>
{translate('DeleteMovieFolderMovieCount', { movieFileCount, size: formatBytes(sizeOnDisk) })}
</div> :
null
}
</div> :
null
}
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>
{translate('Close')}
</Button>
<Button
kind={kinds.DANGER}
onPress={this.onDeleteMovieConfirmed}
>
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>
);
}
}
DeleteMovieModalContent.propTypes = {
title: PropTypes.string.isRequired,
path: PropTypes.string.isRequired,
statistics: PropTypes.object.isRequired,
hasFile: PropTypes.bool.isRequired,
deleteOptions: PropTypes.object.isRequired,
onDeleteOptionChange: PropTypes.func.isRequired,
onDeletePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
DeleteMovieModalContent.defaultProps = {
statistics: {}
};
export default DeleteMovieModalContent;

View file

@ -0,0 +1,149 @@
import React, { useCallback, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import FormGroup from 'Components/Form/FormGroup';
import FormInputGroup from 'Components/Form/FormInputGroup';
import FormLabel from 'Components/Form/FormLabel';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button';
import InlineMarkdown from 'Components/Markdown/InlineMarkdown';
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 { icons, inputTypes, kinds } from 'Helpers/Props';
import { Statistics } from 'Movie/Movie';
import useMovie from 'Movie/useMovie';
import { deleteMovie, setDeleteOption } from 'Store/Actions/movieActions';
import { CheckInputChanged } from 'typings/inputs';
import formatBytes from 'Utilities/Number/formatBytes';
import translate from 'Utilities/String/translate';
import styles from './DeleteMovieModalContent.css';
export interface DeleteMovieModalContentProps {
movieId: number;
onModalClose: () => void;
}
function DeleteMovieModalContent({
movieId,
onModalClose,
}: DeleteMovieModalContentProps) {
const dispatch = useDispatch();
const {
title,
path,
collection,
statistics = {} as Statistics,
} = useMovie(movieId)!;
const { addImportExclusion } = useSelector(
(state: AppState) => state.movies.deleteOptions
);
const { movieFileCount = 0, sizeOnDisk = 0 } = statistics;
const [deleteFiles, setDeleteFiles] = useState(false);
const handleDeleteFilesChange = useCallback(
({ value }: CheckInputChanged) => {
setDeleteFiles(value);
},
[]
);
const handleDeleteMovieConfirmed = useCallback(() => {
dispatch(
deleteMovie({
id: movieId,
collectionTmdbId: collection?.tmdbId,
deleteFiles,
addImportExclusion,
})
);
}, [movieId, collection, addImportExclusion, deleteFiles, dispatch]);
const handleDeleteOptionChange = useCallback(
({ name, value }: CheckInputChanged) => {
dispatch(setDeleteOption({ [name]: value }));
},
[dispatch]
);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>{translate('DeleteHeader', { title })}</ModalHeader>
<ModalBody>
<div className={styles.pathContainer}>
<Icon className={styles.pathIcon} name={icons.FOLDER} />
{path}
</div>
<FormGroup>
<FormLabel>{translate('AddListExclusion')}</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="addImportExclusion"
value={addImportExclusion}
helpText={translate('AddListExclusionMovieHelpText')}
kind={kinds.DANGER}
onChange={handleDeleteOptionChange}
/>
</FormGroup>
<FormGroup>
<FormLabel>
{movieFileCount === 0
? translate('DeleteMovieFolder')
: translate('DeleteMovieFiles', { movieFileCount })}
</FormLabel>
<FormInputGroup
type={inputTypes.CHECK}
name="deleteFiles"
value={deleteFiles}
helpText={
movieFileCount === 0
? translate('DeleteMovieFolderHelpText')
: translate('DeleteMovieFilesHelpText')
}
kind={kinds.DANGER}
onChange={handleDeleteFilesChange}
/>
</FormGroup>
{deleteFiles ? (
<div className={styles.deleteFilesMessage}>
<div>
<InlineMarkdown
data={translate('DeleteMovieFolderConfirmation', { path })}
blockClassName={styles.folderPath}
/>
</div>
{movieFileCount ? (
<div className={styles.deleteCount}>
{translate('DeleteMovieFolderMovieCount', {
movieFileCount,
size: formatBytes(sizeOnDisk),
})}
</div>
) : null}
</div>
) : null}
</ModalBody>
<ModalFooter>
<Button onPress={onModalClose}>{translate('Close')}</Button>
<Button kind={kinds.DANGER} onPress={handleDeleteMovieConfirmed}>
{translate('Delete')}
</Button>
</ModalFooter>
</ModalContent>
);
}
export default DeleteMovieModalContent;

View file

@ -1,45 +0,0 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { deleteMovie, setDeleteOption } from 'Store/Actions/movieActions';
import createMovieSelector from 'Store/Selectors/createMovieSelector';
import DeleteMovieModalContent from './DeleteMovieModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.movies.deleteOptions,
createMovieSelector(),
(deleteOptions, movie) => {
return {
...movie,
deleteOptions
};
}
);
}
function createMapDispatchToProps(dispatch, props) {
return {
onDeleteOptionChange(option) {
dispatch(
setDeleteOption({
[option.name]: option.value
})
);
},
onDeletePress(deleteFiles, addImportExclusion) {
dispatch(
deleteMovie({
id: props.movieId,
collectionTmdbId: this.collection?.tmdbId,
deleteFiles,
addImportExclusion
})
);
props.onModalClose(true);
}
};
}
export default connect(createMapStateToProps, createMapDispatchToProps)(DeleteMovieModalContent);

View file

@ -747,7 +747,6 @@ class MovieDetails extends Component {
isOpen={isDeleteMovieModalOpen}
movieId={id}
onModalClose={this.onDeleteMovieModalClose}
nextMovieRelativePath={`/movie/${nextMovie.titleSlug}`}
/>
<InteractiveImportModal

View file

@ -18,6 +18,7 @@ export interface Image {
}
export interface Collection {
tmdbId: number;
title: string;
}
@ -74,7 +75,7 @@ interface Movie extends ModelBase {
ratings: Ratings;
popularity: number;
certification: string;
statistics: Statistics;
statistics?: Statistics;
tags: number[];
images: Image[];
movieFile: MovieFile;