mirror of
https://github.com/Sonarr/Sonarr
synced 2024-12-21 23:33:00 +00:00
Convert MediaInfo to TypeScript
This commit is contained in:
parent
34ae65c087
commit
4e4bf3507f
11 changed files with 166 additions and 198 deletions
|
@ -9,26 +9,27 @@ import Popover from 'Components/Tooltip/Popover';
|
|||
import EpisodeFormats from 'Episode/EpisodeFormats';
|
||||
import EpisodeLanguages from 'Episode/EpisodeLanguages';
|
||||
import EpisodeQuality from 'Episode/EpisodeQuality';
|
||||
import { EpisodeFile } from 'EpisodeFile/EpisodeFile';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import Language from 'Language/Language';
|
||||
import { QualityModel } from 'Quality/Quality';
|
||||
import CustomFormat from 'typings/CustomFormat';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import MediaInfo from './MediaInfo';
|
||||
import styles from './EpisodeFileRow.css';
|
||||
|
||||
interface EpisodeFileRowProps {
|
||||
path: string;
|
||||
size: number;
|
||||
languages: Language[];
|
||||
quality: QualityModel;
|
||||
qualityCutoffNotMet: boolean;
|
||||
customFormats: CustomFormat[];
|
||||
customFormatScore: number;
|
||||
mediaInfo: object;
|
||||
interface EpisodeFileRowProps
|
||||
extends Pick<
|
||||
EpisodeFile,
|
||||
| 'path'
|
||||
| 'size'
|
||||
| 'languages'
|
||||
| 'quality'
|
||||
| 'customFormats'
|
||||
| 'customFormatScore'
|
||||
| 'qualityCutoffNotMet'
|
||||
| 'mediaInfo'
|
||||
> {
|
||||
columns: Column[];
|
||||
onDeleteEpisodeFile(): void;
|
||||
}
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
import React from 'react';
|
||||
import DescriptionList from 'Components/DescriptionList/DescriptionList';
|
||||
import DescriptionListItem from 'Components/DescriptionList/DescriptionListItem';
|
||||
|
||||
function MediaInfo(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 MediaInfo;
|
27
frontend/src/Episode/Summary/MediaInfo.tsx
Normal file
27
frontend/src/Episode/Summary/MediaInfo.tsx
Normal file
|
@ -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;
|
|
@ -1,17 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import EpisodeLanguages from 'Episode/EpisodeLanguages';
|
||||
import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createEpisodeFileSelector(),
|
||||
(episodeFile) => {
|
||||
return {
|
||||
languages: episodeFile ? episodeFile.languages : undefined
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(EpisodeLanguages);
|
15
frontend/src/EpisodeFile/EpisodeFileLanguages.tsx
Normal file
15
frontend/src/EpisodeFile/EpisodeFileLanguages.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import EpisodeLanguages from 'Episode/EpisodeLanguages';
|
||||
import useEpisodeFile from './useEpisodeFile';
|
||||
|
||||
interface EpisodeFileLanguagesProps {
|
||||
episodeFileId: number;
|
||||
}
|
||||
|
||||
function EpisodeFileLanguages({ episodeFileId }: EpisodeFileLanguagesProps) {
|
||||
const episodeFile = useEpisodeFile(episodeFileId);
|
||||
|
||||
return <EpisodeLanguages languages={episodeFile?.languages ?? []} />;
|
||||
}
|
||||
|
||||
export default EpisodeFileLanguages;
|
|
@ -1,105 +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;
|
92
frontend/src/EpisodeFile/MediaInfo.tsx
Normal file
92
frontend/src/EpisodeFile/MediaInfo.tsx
Normal file
|
@ -0,0 +1,92 @@
|
|||
import React from 'react';
|
||||
import getLanguageName from 'Utilities/String/getLanguageName';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import useEpisodeFile from './useEpisodeFile';
|
||||
|
||||
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 {
|
||||
episodeFileId?: number;
|
||||
type: MediaInfoType;
|
||||
}
|
||||
|
||||
function MediaInfo({ episodeFileId, type }: MediaInfoProps) {
|
||||
const episodeFile = useEpisodeFile(episodeFileId);
|
||||
|
||||
if (!episodeFile?.mediaInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {
|
||||
audioChannels,
|
||||
audioCodec,
|
||||
audioLanguages,
|
||||
subtitles,
|
||||
videoCodec,
|
||||
videoDynamicRangeType,
|
||||
} = episodeFile.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;
|
|
@ -1,21 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createEpisodeFileSelector from 'Store/Selectors/createEpisodeFileSelector';
|
||||
import MediaInfo from './MediaInfo';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
createEpisodeFileSelector(),
|
||||
(episodeFile) => {
|
||||
if (episodeFile) {
|
||||
return {
|
||||
...episodeFile.mediaInfo
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(createMapStateToProps)(MediaInfo);
|
|
@ -13,8 +13,8 @@ import EpisodeSearchCell from 'Episode/EpisodeSearchCell';
|
|||
import EpisodeStatus from 'Episode/EpisodeStatus';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
import IndexerFlags from 'Episode/IndexerFlags';
|
||||
import EpisodeFileLanguageConnector from 'EpisodeFile/EpisodeFileLanguageConnector';
|
||||
import MediaInfoConnector from 'EpisodeFile/MediaInfoConnector';
|
||||
import EpisodeFileLanguages from 'EpisodeFile/EpisodeFileLanguages';
|
||||
import MediaInfo from 'EpisodeFile/MediaInfo';
|
||||
import * as mediaInfoTypes from 'EpisodeFile/mediaInfoTypes';
|
||||
import { icons, kinds, tooltipPositions } from 'Helpers/Props';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
|
@ -229,7 +229,7 @@ class EpisodeRow extends Component {
|
|||
key={name}
|
||||
className={styles.languages}
|
||||
>
|
||||
<EpisodeFileLanguageConnector
|
||||
<EpisodeFileLanguages
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
@ -242,7 +242,7 @@ class EpisodeRow extends Component {
|
|||
key={name}
|
||||
className={styles.audio}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
<MediaInfo
|
||||
type={mediaInfoTypes.AUDIO}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
|
@ -256,7 +256,7 @@ class EpisodeRow extends Component {
|
|||
key={name}
|
||||
className={styles.audioLanguages}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
<MediaInfo
|
||||
type={mediaInfoTypes.AUDIO_LANGUAGES}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
|
@ -270,7 +270,7 @@ class EpisodeRow extends Component {
|
|||
key={name}
|
||||
className={styles.subtitles}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
<MediaInfo
|
||||
type={mediaInfoTypes.SUBTITLES}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
|
@ -284,7 +284,7 @@ class EpisodeRow extends Component {
|
|||
key={name}
|
||||
className={styles.video}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
<MediaInfo
|
||||
type={mediaInfoTypes.VIDEO}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
|
@ -298,7 +298,7 @@ class EpisodeRow extends Component {
|
|||
key={name}
|
||||
className={styles.videoDynamicRangeType}
|
||||
>
|
||||
<MediaInfoConnector
|
||||
<MediaInfo
|
||||
type={mediaInfoTypes.VIDEO_DYNAMIC_RANGE_TYPE}
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
|
|
9
frontend/src/Utilities/Object/getEntries.ts
Normal file
9
frontend/src/Utilities/Object/getEntries.ts
Normal file
|
@ -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;
|
|
@ -9,7 +9,7 @@ import EpisodeSearchCell from 'Episode/EpisodeSearchCell';
|
|||
import EpisodeStatus from 'Episode/EpisodeStatus';
|
||||
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
|
||||
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
|
||||
import EpisodeFileLanguageConnector from 'EpisodeFile/EpisodeFileLanguageConnector';
|
||||
import EpisodeFileLanguages from 'EpisodeFile/EpisodeFileLanguages';
|
||||
import SeriesTitleLink from 'Series/SeriesTitleLink';
|
||||
import styles from './CutoffUnmetRow.css';
|
||||
|
||||
|
@ -123,7 +123,7 @@ function CutoffUnmetRow(props) {
|
|||
key={name}
|
||||
className={styles.languages}
|
||||
>
|
||||
<EpisodeFileLanguageConnector
|
||||
<EpisodeFileLanguages
|
||||
episodeFileId={episodeFileId}
|
||||
/>
|
||||
</TableRowCell>
|
||||
|
|
Loading…
Reference in a new issue