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 ( + <DescriptionList> + {getEntries(props).map(([key, value]) => { + const title = key + .replace(/([A-Z])/g, ' $1') + .replace(/^./, (str) => str.toUpperCase()); + + if (!value) { + return null; + } + + return ( + <DescriptionListItem key={key} title={title} data={props[key]} /> + ); + })} + </DescriptionList> + ); +} + +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 ( - <DescriptionList> - { - 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 ( - <DescriptionListItem - key={key} - title={title} - data={props[key]} - /> - ); - }) - } - </DescriptionList> - ); -} - -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} > - <MediaInfoConnector + <MediaInfo type={mediaInfoTypes.AUDIO} movieFileId={id} /> @@ -238,7 +238,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.audioLanguages} > - <MediaInfoConnector + <MediaInfo type={mediaInfoTypes.AUDIO_LANGUAGES} movieFileId={id} /> @@ -252,7 +252,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.subtitles} > - <MediaInfoConnector + <MediaInfo type={mediaInfoTypes.SUBTITLES} movieFileId={id} /> @@ -266,7 +266,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.video} > - <MediaInfoConnector + <MediaInfo type={mediaInfoTypes.VIDEO} movieFileId={id} /> @@ -280,7 +280,7 @@ class MovieFileEditorRow extends Component { key={name} className={styles.videoDynamicRangeType} > - <MediaInfoConnector + <MediaInfo type={mediaInfoTypes.VIDEO_DYNAMIC_RANGE_TYPE} movieFileId={id} /> 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) { </ModalHeader> <ModalBody> - <MediaInfoPopover {...mediaInfo} /> + <MediaInfo {...mediaInfo} /> </ModalBody> <ModalFooter> 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 ( - <span title={splitLanguages.join(', ')}> - {splitLanguages.slice(0, 2).join(', ')}, {splitLanguages.length - 2} more - </span> - ); - } - - return ( - <span> - {splitLanguages.join(', ')} - </span> - ); -} - -function MediaInfo(props) { - const { - type, - audioChannels, - audioCodec, - audioLanguages, - subtitles, - videoCodec, - videoDynamicRangeType - } = props; - - if (type === mediaInfoTypes.AUDIO) { - return ( - <span> - { - audioCodec ? audioCodec : '' - } - - { - audioCodec && audioChannels ? ' - ' : '' - } - - { - audioChannels ? audioChannels.toFixed(1) : '' - } - </span> - ); - } - - if (type === mediaInfoTypes.AUDIO_LANGUAGES) { - return formatLanguages(audioLanguages); - } - - if (type === mediaInfoTypes.SUBTITLES) { - return formatLanguages(subtitles); - } - - if (type === mediaInfoTypes.VIDEO) { - return ( - <span> - {videoCodec} - </span> - ); - } - - if (type === mediaInfoTypes.VIDEO_DYNAMIC_RANGE_TYPE) { - return ( - <span> - {videoDynamicRangeType} - </span> - ); - } - - 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 ( + <span title={splitLanguages.join(', ')}> + {splitLanguages.slice(0, 2).join(', ')}, {splitLanguages.length - 2}{' '} + more + </span> + ); + } + + return <span>{splitLanguages.join(', ')}</span>; +} + +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 ( + <span> + {audioCodec ? audioCodec : ''} + + {audioCodec && audioChannels ? ' - ' : ''} + + {audioChannels ? audioChannels.toFixed(1) : ''} + </span> + ); + } + + if (type === 'audioLanguages') { + return formatLanguages(audioLanguages); + } + + if (type === 'subtitles') { + return formatLanguages(subtitles); + } + + if (type === 'video') { + return <span>{videoCodec}</span>; + } + + if (type === 'videoDynamicRangeType') { + return <span>{videoDynamicRangeType}</span>; + } + + 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 <MovieLanguages languages={movieFile?.languages ?? []} />; +} + +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<T> = { + [K in keyof T]: [K, T[K]]; +}[keyof T][]; + +function getEntries<T extends object>(obj: T): Entries<T> { + return Object.entries(obj) as Entries<T>; +} + +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} > - <MovieFileLanguageConnector + <MovieFileLanguages movieFileId={movieFileId} /> </TableRowCell>