diff --git a/frontend/src/MovieFile/Editor/MediaInfo.tsx b/frontend/src/MovieFile/Editor/MediaInfo.tsx
new file mode 100644
index 0000000000..d0a8951750
--- /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 a3d0d24031..0000000000
--- 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 f93e318380..4659b8d5e8 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 dd19b31375..2917d1bc70 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 e45e4b472a..0000000000
--- 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 0000000000..0e94b15a3c
--- /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 ce955c8aa5..0000000000
--- 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 4bbd412366..0000000000
--- 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 0000000000..2880417878
--- /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 0000000000..143415db80
--- /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 0000000000..ca540c5dae
--- /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 d0b8ff8eae..eb83e34dd2 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}
>
-