From c81b2e80ee22bbe0acc3d48b04d151612f5b187b Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Sat, 7 Dec 2024 19:04:18 -0800 Subject: [PATCH] Convert MediaInfo to TypeScript (cherry picked from commit 4e4bf3507f20c0f8581c66804f8ef406c41952d8) Closes #10753 --- frontend/src/MovieFile/Editor/MediaInfo.tsx | 27 +++++ .../src/MovieFile/Editor/MediaInfoPopover.js | 33 ------ .../MovieFile/Editor/MovieFileEditorRow.js | 12 +- frontend/src/MovieFile/FileDetailsModal.js | 4 +- frontend/src/MovieFile/MediaInfo.js | 104 ------------------ frontend/src/MovieFile/MediaInfo.tsx | 92 ++++++++++++++++ frontend/src/MovieFile/MediaInfoConnector.js | 21 ---- .../MovieFile/MovieFileLanguageConnector.js | 17 --- frontend/src/MovieFile/MovieFileLanguages.tsx | 15 +++ frontend/src/MovieFile/useMovieFile.ts | 18 +++ frontend/src/Utilities/Object/getEntries.ts | 9 ++ .../src/Wanted/CutoffUnmet/CutoffUnmetRow.js | 4 +- 12 files changed, 171 insertions(+), 185 deletions(-) create mode 100644 frontend/src/MovieFile/Editor/MediaInfo.tsx delete mode 100644 frontend/src/MovieFile/Editor/MediaInfoPopover.js delete mode 100644 frontend/src/MovieFile/MediaInfo.js create mode 100644 frontend/src/MovieFile/MediaInfo.tsx delete mode 100644 frontend/src/MovieFile/MediaInfoConnector.js delete mode 100644 frontend/src/MovieFile/MovieFileLanguageConnector.js create mode 100644 frontend/src/MovieFile/MovieFileLanguages.tsx create mode 100644 frontend/src/MovieFile/useMovieFile.ts create mode 100644 frontend/src/Utilities/Object/getEntries.ts diff --git a/frontend/src/MovieFile/Editor/MediaInfo.tsx b/frontend/src/MovieFile/Editor/MediaInfo.tsx new file mode 100644 index 000000000..d0a895175 --- /dev/null +++ b/frontend/src/MovieFile/Editor/MediaInfo.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import DescriptionList from 'Components/DescriptionList/DescriptionList'; +import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; +import MediaInfoProps from 'typings/MediaInfo'; +import getEntries from 'Utilities/Object/getEntries'; + +function MediaInfo(props: MediaInfoProps) { + return ( + + {getEntries(props).map(([key, value]) => { + const title = key + .replace(/([A-Z])/g, ' $1') + .replace(/^./, (str) => str.toUpperCase()); + + if (!value) { + return null; + } + + return ( + + ); + })} + + ); +} + +export default MediaInfo; diff --git a/frontend/src/MovieFile/Editor/MediaInfoPopover.js b/frontend/src/MovieFile/Editor/MediaInfoPopover.js deleted file mode 100644 index a3d0d2403..000000000 --- a/frontend/src/MovieFile/Editor/MediaInfoPopover.js +++ /dev/null @@ -1,33 +0,0 @@ -import React from 'react'; -import DescriptionList from 'Components/DescriptionList/DescriptionList'; -import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem'; - -function MediaInfoPopover(props) { - return ( - - { - Object.keys(props).map((key) => { - const title = key - .replace(/([A-Z])/g, ' $1') - .replace(/^./, (str) => str.toUpperCase()); - - const value = props[key]; - - if (!value) { - return null; - } - - return ( - - ); - }) - } - - ); -} - -export default MediaInfoPopover; diff --git a/frontend/src/MovieFile/Editor/MovieFileEditorRow.js b/frontend/src/MovieFile/Editor/MovieFileEditorRow.js index f93e31838..4659b8d5e 100644 --- a/frontend/src/MovieFile/Editor/MovieFileEditorRow.js +++ b/frontend/src/MovieFile/Editor/MovieFileEditorRow.js @@ -14,7 +14,7 @@ import MovieFormats from 'Movie/MovieFormats'; import MovieLanguages from 'Movie/MovieLanguages'; import MovieQuality from 'Movie/MovieQuality'; import FileEditModal from 'MovieFile/Edit/FileEditModal'; -import MediaInfoConnector from 'MovieFile/MediaInfoConnector'; +import MediaInfo from 'MovieFile/MediaInfo'; import * as mediaInfoTypes from 'MovieFile/mediaInfoTypes'; import formatBytes from 'Utilities/Number/formatBytes'; import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore'; @@ -224,7 +224,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.audio} > - @@ -238,7 +238,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.audioLanguages} > - @@ -252,7 +252,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.subtitles} > - @@ -266,7 +266,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.video} > - @@ -280,7 +280,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.videoDynamicRangeType} > - diff --git a/frontend/src/MovieFile/FileDetailsModal.js b/frontend/src/MovieFile/FileDetailsModal.js index dd19b3137..2917d1bc7 100644 --- a/frontend/src/MovieFile/FileDetailsModal.js +++ b/frontend/src/MovieFile/FileDetailsModal.js @@ -8,7 +8,7 @@ import ModalFooter from 'Components/Modal/ModalFooter'; import ModalHeader from 'Components/Modal/ModalHeader'; import { sizes } from 'Helpers/Props'; import translate from 'Utilities/String/translate'; -import MediaInfoPopover from './Editor/MediaInfoPopover'; +import MediaInfo from './Editor/MediaInfo'; function FileDetailsModal(props) { const { @@ -31,7 +31,7 @@ function FileDetailsModal(props) { - + diff --git a/frontend/src/MovieFile/MediaInfo.js b/frontend/src/MovieFile/MediaInfo.js deleted file mode 100644 index e45e4b472..000000000 --- a/frontend/src/MovieFile/MediaInfo.js +++ /dev/null @@ -1,104 +0,0 @@ -import _ from 'lodash'; -import PropTypes from 'prop-types'; -import React from 'react'; -import getLanguageName from 'Utilities/String/getLanguageName'; -import translate from 'Utilities/String/translate'; -import * as mediaInfoTypes from './mediaInfoTypes'; - -function formatLanguages(languages) { - if (!languages) { - return null; - } - - const splitLanguages = _.uniq(languages.split('/')).map((l) => { - const simpleLanguage = l.split('_')[0]; - - if (simpleLanguage === 'und') { - return translate('Unknown'); - } - - return getLanguageName(simpleLanguage); - }); - - if (splitLanguages.length > 3) { - return ( - - {splitLanguages.slice(0, 2).join(', ')}, {splitLanguages.length - 2} more - - ); - } - - return ( - - {splitLanguages.join(', ')} - - ); -} - -function MediaInfo(props) { - const { - type, - audioChannels, - audioCodec, - audioLanguages, - subtitles, - videoCodec, - videoDynamicRangeType - } = props; - - if (type === mediaInfoTypes.AUDIO) { - return ( - - { - audioCodec ? audioCodec : '' - } - - { - audioCodec && audioChannels ? ' - ' : '' - } - - { - audioChannels ? audioChannels.toFixed(1) : '' - } - - ); - } - - if (type === mediaInfoTypes.AUDIO_LANGUAGES) { - return formatLanguages(audioLanguages); - } - - if (type === mediaInfoTypes.SUBTITLES) { - return formatLanguages(subtitles); - } - - if (type === mediaInfoTypes.VIDEO) { - return ( - - {videoCodec} - - ); - } - - if (type === mediaInfoTypes.VIDEO_DYNAMIC_RANGE_TYPE) { - return ( - - {videoDynamicRangeType} - - ); - } - - return null; -} - -MediaInfo.propTypes = { - type: PropTypes.string.isRequired, - audioChannels: PropTypes.number, - audioCodec: PropTypes.string, - audioLanguages: PropTypes.string, - subtitles: PropTypes.string, - videoCodec: PropTypes.string, - videoDynamicRangeType: PropTypes.string -}; - -export default MediaInfo; diff --git a/frontend/src/MovieFile/MediaInfo.tsx b/frontend/src/MovieFile/MediaInfo.tsx new file mode 100644 index 000000000..0e94b15a3 --- /dev/null +++ b/frontend/src/MovieFile/MediaInfo.tsx @@ -0,0 +1,92 @@ +import React from 'react'; +import getLanguageName from 'Utilities/String/getLanguageName'; +import translate from 'Utilities/String/translate'; +import useMovieFile from './useMovieFile'; + +function formatLanguages(languages: string | undefined) { + if (!languages) { + return null; + } + + const splitLanguages = [...new Set(languages.split('/'))].map((l) => { + const simpleLanguage = l.split('_')[0]; + + if (simpleLanguage === 'und') { + return translate('Unknown'); + } + + return getLanguageName(simpleLanguage); + }); + + if (splitLanguages.length > 3) { + return ( + + {splitLanguages.slice(0, 2).join(', ')}, {splitLanguages.length - 2}{' '} + more + + ); + } + + return {splitLanguages.join(', ')}; +} + +export type MediaInfoType = + | 'audio' + | 'audioLanguages' + | 'subtitles' + | 'video' + | 'videoDynamicRangeType'; + +interface MediaInfoProps { + movieFileId?: number; + type: MediaInfoType; +} + +function MediaInfo({ movieFileId, type }: MediaInfoProps) { + const movieFile = useMovieFile(movieFileId); + + if (!movieFile?.mediaInfo) { + return null; + } + + const { + audioChannels, + audioCodec, + audioLanguages, + subtitles, + videoCodec, + videoDynamicRangeType, + } = movieFile.mediaInfo; + + if (type === 'audio') { + return ( + + {audioCodec ? audioCodec : ''} + + {audioCodec && audioChannels ? ' - ' : ''} + + {audioChannels ? audioChannels.toFixed(1) : ''} + + ); + } + + if (type === 'audioLanguages') { + return formatLanguages(audioLanguages); + } + + if (type === 'subtitles') { + return formatLanguages(subtitles); + } + + if (type === 'video') { + return {videoCodec}; + } + + if (type === 'videoDynamicRangeType') { + return {videoDynamicRangeType}; + } + + return null; +} + +export default MediaInfo; diff --git a/frontend/src/MovieFile/MediaInfoConnector.js b/frontend/src/MovieFile/MediaInfoConnector.js deleted file mode 100644 index ce955c8aa..000000000 --- a/frontend/src/MovieFile/MediaInfoConnector.js +++ /dev/null @@ -1,21 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import createMovieFileSelector from 'Store/Selectors/createMovieFileSelector'; -import MediaInfo from './MediaInfo'; - -function createMapStateToProps() { - return createSelector( - createMovieFileSelector(), - (movieFile) => { - if (movieFile) { - return { - ...movieFile.mediaInfo - }; - } - - return {}; - } - ); -} - -export default connect(createMapStateToProps)(MediaInfo); diff --git a/frontend/src/MovieFile/MovieFileLanguageConnector.js b/frontend/src/MovieFile/MovieFileLanguageConnector.js deleted file mode 100644 index 4bbd41236..000000000 --- a/frontend/src/MovieFile/MovieFileLanguageConnector.js +++ /dev/null @@ -1,17 +0,0 @@ -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; -import MovieLanguages from 'Movie/MovieLanguages'; -import createMovieFileSelector from 'Store/Selectors/createMovieFileSelector'; - -function createMapStateToProps() { - return createSelector( - createMovieFileSelector(), - (movieFile) => { - return { - languages: movieFile ? movieFile.languages : undefined - }; - } - ); -} - -export default connect(createMapStateToProps)(MovieLanguages); diff --git a/frontend/src/MovieFile/MovieFileLanguages.tsx b/frontend/src/MovieFile/MovieFileLanguages.tsx new file mode 100644 index 000000000..288041787 --- /dev/null +++ b/frontend/src/MovieFile/MovieFileLanguages.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import MovieLanguages from 'Movie/MovieLanguages'; +import useMovieFile from './useMovieFile'; + +interface MovieFileLanguagesProps { + movieFileId: number; +} + +function MovieFileLanguages({ movieFileId }: MovieFileLanguagesProps) { + const movieFile = useMovieFile(movieFileId); + + return ; +} + +export default MovieFileLanguages; diff --git a/frontend/src/MovieFile/useMovieFile.ts b/frontend/src/MovieFile/useMovieFile.ts new file mode 100644 index 000000000..143415db8 --- /dev/null +++ b/frontend/src/MovieFile/useMovieFile.ts @@ -0,0 +1,18 @@ +import { useSelector } from 'react-redux'; +import { createSelector } from 'reselect'; +import AppState from 'App/State/AppState'; + +function createMovieFileSelector(movieFileId?: number) { + return createSelector( + (state: AppState) => state.movieFiles.items, + (movieFiles) => { + return movieFiles.find(({ id }) => id === movieFileId); + } + ); +} + +function useMovieFile(movieFileId: number | undefined) { + return useSelector(createMovieFileSelector(movieFileId)); +} + +export default useMovieFile; diff --git a/frontend/src/Utilities/Object/getEntries.ts b/frontend/src/Utilities/Object/getEntries.ts new file mode 100644 index 000000000..ca540c5da --- /dev/null +++ b/frontend/src/Utilities/Object/getEntries.ts @@ -0,0 +1,9 @@ +export type Entries = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; + +function getEntries(obj: T): Entries { + return Object.entries(obj) as Entries; +} + +export default getEntries; diff --git a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js index d0b8ff8ea..eb83e34dd 100644 --- a/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js +++ b/frontend/src/Wanted/CutoffUnmet/CutoffUnmetRow.js @@ -8,7 +8,7 @@ import movieEntities from 'Movie/movieEntities'; import MovieSearchCell from 'Movie/MovieSearchCell'; import MovieStatusConnector from 'Movie/MovieStatusConnector'; import MovieTitleLink from 'Movie/MovieTitleLink'; -import MovieFileLanguageConnector from 'MovieFile/MovieFileLanguageConnector'; +import MovieFileLanguages from 'MovieFile/MovieFileLanguages'; import styles from './CutoffUnmetRow.css'; function CutoffUnmetRow(props) { @@ -104,7 +104,7 @@ function CutoffUnmetRow(props) { key={name} className={styles.languages} > -