diff --git a/src/components/listview/List/ListContent.tsx b/src/components/listview/List/ListContent.tsx index 6d4d49b83a..0b9bc03d01 100644 --- a/src/components/listview/List/ListContent.tsx +++ b/src/components/listview/List/ListContent.tsx @@ -78,12 +78,13 @@ const ListContent: FC = ({ {listOptions.showMediaInfo !== false && enableSideMediaInfo && ( )} diff --git a/src/components/listview/List/ListItemBody.tsx b/src/components/listview/List/ListItemBody.tsx index ef44c5a76a..90e9cb502e 100644 --- a/src/components/listview/List/ListItemBody.tsx +++ b/src/components/listview/List/ListItemBody.tsx @@ -61,10 +61,11 @@ const ListItemBody: FC = ({ {listOptions.showMediaInfo !== false && !enableSideMediaInfo && ( )} diff --git a/src/components/listview/listview.scss b/src/components/listview/listview.scss index f3fae54f19..ce96f93b24 100644 --- a/src/components/listview/listview.scss +++ b/src/components/listview/listview.scss @@ -7,6 +7,7 @@ .listItemMediaInfo { align-items: center; + margin-right: 1em; } .listItem { diff --git a/src/components/mediainfo/CaptionMediaInfo.tsx b/src/components/mediainfo/CaptionMediaInfo.tsx index 497f9fae59..8e30b2a12b 100644 --- a/src/components/mediainfo/CaptionMediaInfo.tsx +++ b/src/components/mediainfo/CaptionMediaInfo.tsx @@ -10,14 +10,13 @@ interface CaptionMediaInfoProps { const CaptionMediaInfo: FC = ({ className }) => { const cssClass = classNames( 'mediaInfoItem', - 'mediaInfoText', 'closedCaptionMediaInfoText', className ); return ( - + ); }; diff --git a/src/components/mediainfo/EndsAt.tsx b/src/components/mediainfo/EndsAt.tsx index fa26d0801f..2f2f9f48a3 100644 --- a/src/components/mediainfo/EndsAt.tsx +++ b/src/components/mediainfo/EndsAt.tsx @@ -12,7 +12,6 @@ interface EndsAtProps { const EndsAt: FC = ({ runTimeTicks, className }) => { const cssClass = classNames( 'mediaInfoItem', - 'mediaInfoText', 'endsAt', className ); diff --git a/src/components/mediainfo/MediaInfoItem.tsx b/src/components/mediainfo/MediaInfoItem.tsx index d38635ac2d..35159ddfc6 100644 --- a/src/components/mediainfo/MediaInfoItem.tsx +++ b/src/components/mediainfo/MediaInfoItem.tsx @@ -1,25 +1,38 @@ import React, { type FC } from 'react'; import Box from '@mui/material/Box'; +import Link from '@mui/material/Link'; import classNames from 'classnames'; import type { MiscInfo } from 'types/mediaInfoItem'; interface MediaInfoItemProps { className?: string; - miscInfo?: MiscInfo ; + miscInfo: MiscInfo ; } const MediaInfoItem: FC = ({ className, miscInfo }) => { - const cssClass = classNames( - 'mediaInfoItem', - 'mediaInfoText', - className, - miscInfo?.cssClass - ); + const { text, textAction, cssClass, type } = miscInfo; + + const renderText = () => { + if (textAction) { + return ( + + {textAction.title} + + ); + } else { + return text; + } + }; return ( - - {miscInfo?.text} + + {renderText()} ); }; diff --git a/src/components/mediainfo/MediaInfoStats.tsx b/src/components/mediainfo/MediaInfoStats.tsx new file mode 100644 index 0000000000..08984770b1 --- /dev/null +++ b/src/components/mediainfo/MediaInfoStats.tsx @@ -0,0 +1,49 @@ +import React, { type FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import useMediaInfoStats from './useMediaInfoStats'; + +import MediaInfoItem from './MediaInfoItem'; +import type { ItemDto } from 'types/base/models/item-dto'; +import type { MiscInfo } from 'types/mediaInfoItem'; +import type { MediaInfoStatsOpts } from './type'; + +interface MediaInfoStatsProps extends MediaInfoStatsOpts { + className?: string; + infoclass?: string; + item: ItemDto; +} + +const MediaInfoStats: FC = ({ + className, + infoclass, + item, + showResolutionInfo, + showVideoStreamCodecInfo, + showAudoChannelInfo, + showAudioStreamCodecInfo, + showDateAddedInfo +}) => { + const mediaInfoStats = useMediaInfoStats({ + item, + showResolutionInfo, + showVideoStreamCodecInfo, + showAudoChannelInfo, + showAudioStreamCodecInfo, + showDateAddedInfo + }); + + const cssClass = classNames(className); + + const renderMediaInfo = (info: MiscInfo, index: number) => ( + + ); + + return ( + + {mediaInfoStats.map((info, index) => renderMediaInfo(info, index))} + + ); +}; + +export default MediaInfoStats; diff --git a/src/components/mediainfo/PrimaryMediaInfo.tsx b/src/components/mediainfo/PrimaryMediaInfo.tsx index 74a1af2fbb..00bdaa7390 100644 --- a/src/components/mediainfo/PrimaryMediaInfo.tsx +++ b/src/components/mediainfo/PrimaryMediaInfo.tsx @@ -12,54 +12,59 @@ import EndsAt from './EndsAt'; import { ItemMediaKind } from 'types/base/models/item-media-kind'; import type { ItemDto } from 'types/base/models/item-dto'; import type { MiscInfo } from 'types/mediaInfoItem'; +import type { PrimaryInfoOpts } from './type'; -interface PrimaryMediaInfoProps { +interface PrimaryMediaInfoProps extends PrimaryInfoOpts { className?: string; + infoclass?: string; item: ItemDto; - isYearEnabled?: boolean; - isContainerEnabled?: boolean; - isEpisodeTitleEnabled?: boolean; - isCriticRatingEnabled?: boolean; - isEndsAtEnabled?: boolean; - isOriginalAirDateEnabled?: boolean; - isRuntimeEnabled?: boolean; - isProgramIndicatorEnabled?: boolean; - isEpisodeTitleIndexNumberEnabled?: boolean; - isOfficialRatingEnabled?: boolean; - isStarRatingEnabled?: boolean; - isCaptionIndicatorEnabled?: boolean; - isMissingIndicatorEnabled?: boolean; - getMissingIndicator: () => React.JSX.Element | null + showStarRatingInfo?: boolean; + showCaptionIndicatorInfo?: boolean; + showCriticRatingInfo?: boolean; + showEndsAtInfo?: boolean; + getMissingIndicator?: () => React.JSX.Element | null; } const PrimaryMediaInfo: FC = ({ className, + infoclass, item, - isYearEnabled = false, - isContainerEnabled = false, - isEpisodeTitleEnabled = false, - isCriticRatingEnabled = false, - isEndsAtEnabled = false, - isOriginalAirDateEnabled = false, - isRuntimeEnabled = false, - isProgramIndicatorEnabled = false, - isEpisodeTitleIndexNumberEnabled = false, - isOfficialRatingEnabled = false, - isStarRatingEnabled = false, - isCaptionIndicatorEnabled = false, - isMissingIndicatorEnabled = false, + showYearInfo, + showAudioContainerInfo, + showEpisodeTitleInfo, + showOriginalAirDateInfo, + showFolderRuntimeInfo, + showRuntimeInfo, + showItemCountInfo, + showSeriesTimerInfo, + showStartDateInfo, + showProgramIndicatorInfo, + includeEpisodeTitleIndexNumber, + showOfficialRatingInfo, + showVideo3DFormatInfo, + showPhotoSizeInfo, + showStarRatingInfo = false, + showCaptionIndicatorInfo = false, + showCriticRatingInfo = false, + showEndsAtInfo = false, getMissingIndicator }) => { const miscInfo = usePrimaryMediaInfo({ item, - isYearEnabled, - isContainerEnabled, - isEpisodeTitleEnabled, - isOriginalAirDateEnabled, - isRuntimeEnabled, - isProgramIndicatorEnabled, - isEpisodeTitleIndexNumberEnabled, - isOfficialRatingEnabled + showYearInfo, + showAudioContainerInfo, + showEpisodeTitleInfo, + showOriginalAirDateInfo, + showFolderRuntimeInfo, + showRuntimeInfo, + showItemCountInfo, + showSeriesTimerInfo, + showStartDateInfo, + showProgramIndicatorInfo, + includeEpisodeTitleIndexNumber, + showOfficialRatingInfo, + showVideo3DFormatInfo, + showPhotoSizeInfo }); const { StartDate, @@ -72,32 +77,40 @@ const PrimaryMediaInfo: FC = ({ const cssClass = classNames(className); - const renderMediaInfo = (info: MiscInfo | undefined, index: number) => ( - + const renderMediaInfo = (info: MiscInfo, index: number) => ( + ); return ( {miscInfo.map((info, index) => renderMediaInfo(info, index))} - {isStarRatingEnabled && CommunityRating && ( - + {showStarRatingInfo && CommunityRating && ( + )} - {HasSubtitles && isCaptionIndicatorEnabled && } - - {CriticRating && isCriticRatingEnabled && ( - + {showCaptionIndicatorInfo && HasSubtitles && ( + )} - {isEndsAtEnabled + {showCriticRatingInfo && CriticRating && ( + + )} + + {showEndsAtInfo && MediaType === ItemMediaKind.Video && RunTimeTicks - && !StartDate && } - - {isMissingIndicatorEnabled && ( - getMissingIndicator() + && !StartDate && ( + )} + + {getMissingIndicator?.()} ); }; diff --git a/src/components/mediainfo/SecondaryMediaInfo.tsx b/src/components/mediainfo/SecondaryMediaInfo.tsx new file mode 100644 index 0000000000..dae138cef4 --- /dev/null +++ b/src/components/mediainfo/SecondaryMediaInfo.tsx @@ -0,0 +1,55 @@ +import React, { type FC } from 'react'; +import classNames from 'classnames'; +import Box from '@mui/material/Box'; +import useSecondaryMediaInfo from './useSecondaryMediaInfo'; +import useIndicator from 'components/indicators/useIndicator'; +import MediaInfoItem from './MediaInfoItem'; +import type { ItemDto } from 'types/base/models/item-dto'; +import { MiscInfo } from 'types/mediaInfoItem'; +import type { SecondaryInfoOpts } from './type'; + +interface SecondaryMediaInfoProps extends SecondaryInfoOpts { + className?: string; + infoclass?: string; + item: ItemDto; + showTimerIndicatorInfo?: boolean; +} + +const SecondaryMediaInfo: FC = ({ + className, + infoclass, + item, + showProgramTimeInfo, + showStartDateInfo, + showChannelNumberInfo, + showChannelInfo, + channelInteractive, + showTimerIndicatorInfo = false +}) => { + const miscInfo = useSecondaryMediaInfo({ + item, + showProgramTimeInfo, + showStartDateInfo, + showChannelNumberInfo, + showChannelInfo, + channelInteractive + }); + + const indicator = useIndicator(item); + + const cssClass = classNames(className); + + const renderMediaInfo = (info: MiscInfo, index: number) => ( + + ); + + return ( + + {miscInfo.map((info, index) => renderMediaInfo(info, index))} + + {showTimerIndicatorInfo !== false && indicator.getTimerIndicator()} + + ); +}; + +export default SecondaryMediaInfo; diff --git a/src/components/mediainfo/StarIcons.tsx b/src/components/mediainfo/StarIcons.tsx index 0d38453d7e..ca2a1859d4 100644 --- a/src/components/mediainfo/StarIcons.tsx +++ b/src/components/mediainfo/StarIcons.tsx @@ -13,16 +13,18 @@ const StarIcons: FC = ({ className, communityRating }) => { const theme = useTheme(); const cssClass = classNames( 'mediaInfoItem', - 'mediaInfoText', 'starRatingContainer', className ); return ( - + {communityRating.toFixed(1)} ); diff --git a/src/components/mediainfo/mediainfo.js b/src/components/mediainfo/mediainfo.js index 2fdc5d09bf..4ce337f586 100644 --- a/src/components/mediainfo/mediainfo.js +++ b/src/components/mediainfo/mediainfo.js @@ -271,7 +271,7 @@ export function getMediaInfoHtml(item, options = {}) { if (options.officialRating !== false && item.OfficialRating && item.Type !== 'Season' && item.Type !== 'Episode') { miscInfo.push({ text: item.OfficialRating, - cssClass: 'mediaInfoOfficialRating' + cssClass: 'mediaInfoText mediaInfoOfficialRating' }); } diff --git a/src/components/mediainfo/mediainfo.scss b/src/components/mediainfo/mediainfo.scss index cb623b685d..668d1f4ed1 100644 --- a/src/components/mediainfo/mediainfo.scss +++ b/src/components/mediainfo/mediainfo.scss @@ -46,8 +46,6 @@ align-items: center; justify-content: center; vertical-align: middle; - padding-top: 0; - padding-bottom: 0; } .starIcon { @@ -94,15 +92,3 @@ .closedCaptionMediaInfoText { font-weight: bold; } - -.mediaInfoOfficialRating { - border: 0.09em solid currentColor; - padding: 0 0.6em; - height: 1.3em; - line-height: 1.8em; - display: flex; - align-items: center; - justify-content: center; - border-radius: 0.1em; - font-size: 96%; -} diff --git a/src/components/mediainfo/type.ts b/src/components/mediainfo/type.ts new file mode 100644 index 0000000000..2a5a51edec --- /dev/null +++ b/src/components/mediainfo/type.ts @@ -0,0 +1,34 @@ +export interface PrimaryInfoOpts { + showYearInfo?: boolean; + showAudioContainerInfo?: boolean; + showEpisodeTitleInfo?: boolean; + includeEpisodeTitleIndexNumber?: boolean; + showOriginalAirDateInfo?: boolean; + showFolderRuntimeInfo?: boolean; + showRuntimeInfo?: boolean; + showItemCountInfo?: boolean; + showSeriesTimerInfo?: boolean; + showStartDateInfo?: boolean; + showProgramIndicatorInfo?: boolean; + showOfficialRatingInfo?: boolean; + showVideo3DFormatInfo?: boolean; + showPhotoSizeInfo?: boolean; +} + +export interface SecondaryInfoOpts { + showProgramTimeInfo?: boolean; + showStartDateInfo?: boolean; + showEndDateInfo?: boolean; + showChannelNumberInfo?: boolean; + showChannelInfo?: boolean; + channelInteractive?: boolean; +} + +export interface MediaInfoStatsOpts { + showVideoTypeInfo?: boolean; + showResolutionInfo?: boolean; + showVideoStreamCodecInfo?: boolean; + showAudoChannelInfo?: boolean; + showAudioStreamCodecInfo?: boolean; + showDateAddedInfo?: boolean; +} diff --git a/src/components/mediainfo/useMediaInfoStats.tsx b/src/components/mediainfo/useMediaInfoStats.tsx new file mode 100644 index 0000000000..593d17788f --- /dev/null +++ b/src/components/mediainfo/useMediaInfoStats.tsx @@ -0,0 +1,229 @@ +import { MediaStreamType } from '@jellyfin/sdk/lib/generated-client/models/media-stream-type'; +import { VideoType } from '@jellyfin/sdk/lib/generated-client/models/video-type'; +import type { MediaStream } from '@jellyfin/sdk/lib/generated-client/models/media-stream'; +import itemHelper from 'components/itemHelper'; +import datetime from 'scripts/datetime'; +import globalize from 'lib/globalize'; + +import type { ItemDto } from 'types/base/models/item-dto'; +import type { MiscInfo } from 'types/mediaInfoItem'; +import type { NullableString } from 'types/base/common/shared/types'; +import type { MediaInfoStatsOpts } from './type'; + +const getResolution = (label: string, isInterlaced?: boolean) => + isInterlaced ? `${label}i` : label; + +const getResolutionText = ( + showResolutionInfo: boolean, + stream: MediaStream +) => { + const { Width, Height, IsInterlaced } = stream; + + if (showResolutionInfo && Width && Height) { + switch (true) { + case Width >= 3800 || Height >= 2000: + return '4K'; + case Width >= 2500 || Height >= 1400: + return getResolution('1440p', IsInterlaced); + case Width >= 1800 || Height >= 1000: + return getResolution('1080p', IsInterlaced); + case Width >= 1200 || Height >= 700: + return getResolution('720p', IsInterlaced); + case Width >= 700 || Height >= 400: + return getResolution('480p', IsInterlaced); + default: + return null; + } + } + + return null; +}; + +const getAudoChannelText = ( + showAudoChannelInfo: boolean, + stream: MediaStream +) => { + const { Channels } = stream; + + if (showAudoChannelInfo && Channels) { + switch (true) { + case Channels === 8: + return '7.1'; + case Channels === 7: + return '6.1'; + case Channels === 6: + return '5.1'; + case Channels === 2: + return '2.0'; + default: + return null; + } + } + + return null; +}; + +function getAudioStreamForDisplay(item: ItemDto) { + const mediaSource = (item.MediaSources || [])[0] || {}; + + return ( + (mediaSource.MediaStreams || []).filter((i) => { + return ( + i.Type === MediaStreamType.Audio + && (i.Index === mediaSource.DefaultAudioStreamIndex + || mediaSource.DefaultAudioStreamIndex == null) + ); + })[0] || {} + ); +} + +function getVideoStreamForDisplay(item: ItemDto) { + const mediaSource = (item.MediaSources || [])[0] || {}; + + return ( + (mediaSource.MediaStreams || []).filter((i) => { + return i.Type === MediaStreamType.Video; + })[0] || {} + ); +} + +function addVideoType( + showVideoTypeInfo: boolean, + itemVideoType: VideoType | undefined, + addMiscInfo: (val: MiscInfo) => void +): void { + if (showVideoTypeInfo) { + if (itemVideoType === VideoType.Dvd) { + addMiscInfo({ type: 'mediainfo', text: 'Dvd' }); + } + + if (itemVideoType === VideoType.BluRay) { + addMiscInfo({ type: 'mediainfo', text: 'BluRay' }); + } + } +} + +function addResolution( + showResolutionInfo: boolean, + videoStream: MediaStream, + addMiscInfo: (val: MiscInfo) => void +): void { + const resolutionText = getResolutionText(showResolutionInfo, videoStream); + + if (resolutionText) { + addMiscInfo({ type: 'mediainfo', text: resolutionText }); + } +} + +function addVideoStreamCodec( + showVideoCodecInfo: boolean, + videoStreamCodec: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (showVideoCodecInfo && videoStreamCodec) { + addMiscInfo({ type: 'mediainfo', text: videoStreamCodec }); + } +} + +function addAudoChannel( + showAudoChannelInfo: boolean, + audioStream: MediaStream, + addMiscInfo: (val: MiscInfo) => void +): void { + const audioChannelText = getAudoChannelText( + showAudoChannelInfo, + audioStream + ); + + if (audioChannelText) { + addMiscInfo({ type: 'mediainfo', text: audioChannelText }); + } +} + +function addAudioStreamCodec( + showAudioStreamCodecInfo: boolean, + audioStream: MediaStream, + addMiscInfo: (val: MiscInfo) => void +): void { + const audioCodec = (audioStream.Codec || '').toLowerCase(); + + if (showAudioStreamCodecInfo) { + if ( + (audioCodec === 'dca' || audioCodec === 'dts') + && audioStream?.Profile + ) { + addMiscInfo({ type: 'mediainfo', text: audioStream.Profile }); + } else if (audioStream?.Codec) { + addMiscInfo({ type: 'mediainfo', text: audioStream.Codec }); + } + } +} + +function addDateAdded( + showDateAddedInfo: boolean, + item: ItemDto, + addMiscInfo: (val: MiscInfo) => void +): void { + if ( + showDateAddedInfo + && item.DateCreated + && itemHelper.enableDateAddedDisplay(item) + ) { + const dateCreated = datetime.parseISO8601Date(item.DateCreated); + addMiscInfo({ + type: 'added', + text: globalize.translate( + 'AddedOnValue', + `${datetime.toLocaleDateString( + dateCreated + )} ${datetime.getDisplayTime(dateCreated)}` + ) + }); + } +} + +interface UseMediaInfoStatsProps extends MediaInfoStatsOpts { + item: ItemDto; +} + +function useMediaInfoStats({ + item, + showVideoTypeInfo = false, + showResolutionInfo = false, + showVideoStreamCodecInfo = false, + showAudoChannelInfo = false, + showAudioStreamCodecInfo = false, + showDateAddedInfo = false +}: UseMediaInfoStatsProps) { + const miscInfo: MiscInfo[] = []; + + const addMiscInfo = (val: MiscInfo) => { + if (val) { + miscInfo.push(val); + } + }; + + const videoStream = getVideoStreamForDisplay(item); + + const audioStream = getAudioStreamForDisplay(item); + + addVideoType(showVideoTypeInfo, item.VideoType, addMiscInfo); + + addResolution(showResolutionInfo, videoStream, addMiscInfo); + + addVideoStreamCodec( + showVideoStreamCodecInfo, + videoStream.Codec, + addMiscInfo + ); + + addAudoChannel(showAudoChannelInfo, audioStream, addMiscInfo); + + addAudioStreamCodec(showAudioStreamCodecInfo, audioStream, addMiscInfo); + + addDateAdded(showDateAddedInfo, item, addMiscInfo); + + return miscInfo; +} + +export default useMediaInfoStats; diff --git a/src/components/mediainfo/usePrimaryMediaInfo.tsx b/src/components/mediainfo/usePrimaryMediaInfo.tsx index b79ecf503b..49452ec6a9 100644 --- a/src/components/mediainfo/usePrimaryMediaInfo.tsx +++ b/src/components/mediainfo/usePrimaryMediaInfo.tsx @@ -6,41 +6,53 @@ import itemHelper from '../itemHelper'; import { ItemKind } from 'types/base/models/item-kind'; import { ItemMediaKind } from 'types/base/models/item-media-kind'; import { ItemStatus } from 'types/base/models/item-status'; -import type { NullableNumber, NullableString } from 'types/base/common/shared/types'; +import type { + NullableNumber, + NullableString +} from 'types/base/common/shared/types'; import type { ItemDto } from 'types/base/models/item-dto'; import type { MiscInfo } from 'types/mediaInfoItem'; +import { PrimaryInfoOpts } from './type'; function shouldShowFolderRuntime( + showFolderRuntimeInfo: boolean, itemType: ItemKind, itemMediaType: ItemMediaKind ): boolean { return ( - itemType === ItemKind.MusicAlbum - || itemMediaType === ItemMediaKind.MusicArtist - || itemType === ItemKind.Playlist - || itemMediaType === ItemMediaKind.Playlist - || itemMediaType === ItemMediaKind.MusicGenre + showFolderRuntimeInfo + && (itemType === ItemKind.MusicAlbum + || itemMediaType === ItemMediaKind.MusicArtist + || itemType === ItemKind.Playlist + || itemMediaType === ItemMediaKind.Playlist + || itemMediaType === ItemMediaKind.MusicGenre) ); } function addTrackCountOrItemCount( - showFolderRuntime: boolean, + isFolderRuntimeEnabled: boolean, + showItemCountInfo: boolean, itemSongCount: NullableNumber, itemChildCount: NullableNumber, itemRunTimeTicks: NullableNumber, - itemType: NullableString, + itemType: ItemKind, addMiscInfo: (val: MiscInfo) => void ): void { - if (showFolderRuntime) { - const count = itemSongCount ?? itemChildCount; + if (isFolderRuntimeEnabled) { + const count = itemSongCount || itemChildCount; if (count) { addMiscInfo({ text: globalize.translate('TrackCount', count) }); } if (itemRunTimeTicks) { - addMiscInfo({ text: datetime.getDisplayDuration(itemRunTimeTicks) }); + addMiscInfo({ + text: datetime.getDisplayDuration(itemRunTimeTicks) + }); } - } else if (itemType === ItemKind.PhotoAlbum || itemType === ItemKind.BoxSet) { + } else if ( + showItemCountInfo + && (itemType === ItemKind.PhotoAlbum || itemType === ItemKind.BoxSet) + ) { const count = itemChildCount; if (count) { addMiscInfo({ text: globalize.translate('ItemCount', count) }); @@ -49,16 +61,17 @@ function addTrackCountOrItemCount( } function addOriginalAirDateInfo( + showOriginalAirDateInfo: boolean, itemType: ItemKind, itemMediaType: ItemMediaKind, - isOriginalAirDateEnabled: boolean, itemPremiereDate: NullableString, addMiscInfo: (val: MiscInfo) => void ): void { if ( - itemPremiereDate - && (itemType === ItemKind.Episode || itemMediaType === ItemMediaKind.Photo) - && isOriginalAirDateEnabled + showOriginalAirDateInfo + && (itemType === ItemKind.Episode + || itemMediaType === ItemMediaKind.Photo) + && itemPremiereDate ) { try { //don't modify date to locale if episode. Only Dates (not times) are stored, or editable in the edit metadata dialog @@ -74,6 +87,7 @@ function addOriginalAirDateInfo( } function addSeriesTimerInfo( + showSeriesTimerInfo: boolean, itemType: ItemKind, itemRecordAnyTime: boolean | undefined, itemStartDate: NullableString, @@ -81,7 +95,7 @@ function addSeriesTimerInfo( itemChannelName: NullableString, addMiscInfo: (val: MiscInfo) => void ): void { - if (itemType === ItemKind.SeriesTimer) { + if (showSeriesTimerInfo && itemType === ItemKind.SeriesTimer) { if (itemRecordAnyTime) { addMiscInfo({ text: globalize.translate('Anytime') }); } else { @@ -92,7 +106,7 @@ function addSeriesTimerInfo( addMiscInfo({ text: globalize.translate('AllChannels') }); } else { addMiscInfo({ - text: itemChannelName ?? globalize.translate('OneChannel') + text: itemChannelName || globalize.translate('OneChannel') }); } } @@ -104,7 +118,7 @@ function addProgramIndicatorInfo( ): void { if ( program?.IsLive - && userSettings.get('guide-indicator-live', false) === 'true' + && userSettings.get('guide-indicator-live') === 'true' ) { addMiscInfo({ text: globalize.translate('Live'), @@ -112,7 +126,7 @@ function addProgramIndicatorInfo( }); } else if ( program?.IsPremiere - && userSettings.get('guide-indicator-premiere', false) === 'true' + && userSettings.get('guide-indicator-premiere') === 'true' ) { addMiscInfo({ text: globalize.translate('Premiere'), @@ -121,7 +135,7 @@ function addProgramIndicatorInfo( } else if ( program?.IsSeries && !program?.IsRepeat - && userSettings.get('guide-indicator-new', false) === 'true' + && userSettings.get('guide-indicator-new') === 'true' ) { addMiscInfo({ text: globalize.translate('New'), @@ -130,7 +144,7 @@ function addProgramIndicatorInfo( } else if ( program?.IsSeries && program?.IsRepeat - && userSettings.get('guide-indicator-repeat', false) === 'true' + && userSettings.get('guide-indicator-repeat') === 'true' ) { addMiscInfo({ text: globalize.translate('Repeat'), @@ -140,12 +154,12 @@ function addProgramIndicatorInfo( } function addProgramIndicators( + showYearInfo: boolean, + showEpisodeTitleInfo: boolean, + showOriginalAirDateInfo: boolean, + showProgramIndicatorInfo: boolean, + includeEpisodeTitleIndexNumber: boolean, item: ItemDto, - isYearEnabled: boolean, - isEpisodeTitleEnabled: boolean, - isOriginalAirDateEnabled: boolean, - isProgramIndicatorEnabled: boolean, - isEpisodeTitleIndexNumberEnabled: boolean, addMiscInfo: (val: MiscInfo) => void ): void { if (item.Type === ItemKind.Program || item.Type === ItemKind.Timer) { @@ -154,45 +168,43 @@ function addProgramIndicators( program = item.ProgramInfo; } - if (isProgramIndicatorEnabled !== false) { + if (showProgramIndicatorInfo) { addProgramIndicatorInfo(program, addMiscInfo); } addProgramTextInfo( + showEpisodeTitleInfo, + includeEpisodeTitleIndexNumber, + showOriginalAirDateInfo, + showYearInfo, program, - isEpisodeTitleEnabled, - isEpisodeTitleIndexNumberEnabled, - isOriginalAirDateEnabled, - isYearEnabled, addMiscInfo ); } } function addProgramTextInfo( + showEpisodeTitleInfo: boolean, + includeEpisodeTitleIndexNumber: boolean, + showOriginalAirDateInfo: boolean, + showYearInfo: boolean, program: ItemDto, - isEpisodeTitleEnabled: boolean, - isEpisodeTitleIndexNumberEnabled: boolean, - isOriginalAirDateEnabled: boolean, - isYearEnabled: boolean, addMiscInfo: (val: MiscInfo) => void ): void { - if ((program?.IsSeries || program?.EpisodeTitle) - && isEpisodeTitleEnabled !== false) { + if (showEpisodeTitleInfo && (program.IsSeries || program.EpisodeTitle)) { const text = itemHelper.getDisplayName(program, { - includeIndexNumber: isEpisodeTitleIndexNumberEnabled + includeIndexNumber: includeEpisodeTitleIndexNumber }); if (text) { addMiscInfo({ text: text }); } } else if ( - program?.ProductionYear - && ((program?.IsMovie && isOriginalAirDateEnabled !== false) - || isYearEnabled !== false) + ((showOriginalAirDateInfo && program.IsMovie) || showYearInfo) + && program.ProductionYear ) { addMiscInfo({ text: program.ProductionYear }); - } else if (program?.PremiereDate && isOriginalAirDateEnabled !== false) { + } else if (showOriginalAirDateInfo && program.PremiereDate) { try { const date = datetime.parseISO8601Date(program.PremiereDate); const text = globalize.translate( @@ -207,12 +219,14 @@ function addProgramTextInfo( } function addStartDateInfo( + showStartDateInfo: boolean, itemStartDate: NullableString, itemType: ItemKind, addMiscInfo: (val: MiscInfo) => void ): void { if ( - itemStartDate + showStartDateInfo + && itemStartDate && itemType !== ItemKind.Program && itemType !== ItemKind.SeriesTimer && itemType !== ItemKind.Timer @@ -231,14 +245,14 @@ function addStartDateInfo( } function addSeriesProductionYearInfo( + showYearInfo: boolean, itemProductionYear: NullableNumber, itemType: ItemKind, - isYearEnabled: boolean, itemStatus: ItemStatus, itemEndDate: NullableString, addMiscInfo: (val: MiscInfo) => void ): void { - if (itemProductionYear && isYearEnabled && itemType === ItemKind.Series) { + if (showYearInfo && itemProductionYear && itemType === ItemKind.Series) { if (itemStatus === ItemStatus.Continuing) { addMiscInfo({ text: globalize.translate( @@ -249,7 +263,11 @@ function addSeriesProductionYearInfo( ) }); } else { - addproductionYearWithEndDate(itemProductionYear, itemEndDate, addMiscInfo); + addproductionYearWithEndDate( + itemProductionYear, + itemEndDate, + addMiscInfo + ); } } } @@ -281,7 +299,7 @@ function addproductionYearWithEndDate( } function addYearInfo( - isYearEnabled: boolean, + showYearInfo: boolean, itemType: ItemKind, itemMediaType: ItemMediaKind, itemProductionYear: NullableNumber, @@ -289,7 +307,7 @@ function addYearInfo( addMiscInfo: (val: MiscInfo) => void ): void { if ( - isYearEnabled + showYearInfo && itemType !== ItemKind.Series && itemType !== ItemKind.Episode && itemType !== ItemKind.Person @@ -314,103 +332,116 @@ function addYearInfo( } function addVideo3DFormat( + showVideo3DFormatInfo: boolean, itemVideo3DFormat: NullableString, addMiscInfo: (val: MiscInfo) => void ): void { - if (itemVideo3DFormat) { + if (showVideo3DFormatInfo && itemVideo3DFormat) { addMiscInfo({ text: '3D' }); } } function addRunTimeInfo( + isFolderRuntimeEnabled: boolean, + showRuntimeInfo: boolean, itemRunTimeTicks: NullableNumber, itemType: ItemKind, - showFolderRuntime: boolean, - isRuntimeEnabled: boolean, addMiscInfo: (val: MiscInfo) => void ): void { if ( - itemRunTimeTicks + !isFolderRuntimeEnabled + && showRuntimeInfo + && itemRunTimeTicks && itemType !== ItemKind.Series && itemType !== ItemKind.Program && itemType !== ItemKind.Timer && itemType !== ItemKind.Book - && !showFolderRuntime - && isRuntimeEnabled ) { if (itemType === ItemKind.Audio) { - addMiscInfo({ text: datetime.getDisplayRunningTime(itemRunTimeTicks) }); + addMiscInfo({ + text: datetime.getDisplayRunningTime(itemRunTimeTicks) + }); } else { - addMiscInfo({ text: datetime.getDisplayDuration(itemRunTimeTicks) }); + addMiscInfo({ + text: datetime.getDisplayDuration(itemRunTimeTicks) + }); } } } function addOfficialRatingInfo( + showOfficialRatingInfo: boolean, itemOfficialRating: NullableString, itemType: ItemKind, - isOfficialRatingEnabled: boolean, addMiscInfo: (val: MiscInfo) => void ): void { if ( - itemOfficialRating - && isOfficialRatingEnabled + showOfficialRatingInfo + && itemOfficialRating && itemType !== ItemKind.Season && itemType !== ItemKind.Episode ) { addMiscInfo({ text: itemOfficialRating, - cssClass: 'mediaInfoOfficialRating' + cssClass: 'mediaInfoText mediaInfoOfficialRating' }); } } function addAudioContainer( + showAudioContainerInfo: boolean, itemContainer: NullableString, - isContainerEnabled: boolean, itemType: ItemKind, addMiscInfo: (val: MiscInfo) => void ): void { - if (itemContainer && isContainerEnabled && itemType === ItemKind.Audio) { + if ( + showAudioContainerInfo + && itemContainer + && itemType === ItemKind.Audio + ) { addMiscInfo({ text: itemContainer }); } } function addPhotoSize( + showPhotoSizeInfo: boolean, itemMediaType: ItemMediaKind, itemWidth: NullableNumber, itemHeight: NullableNumber, addMiscInfo: (val: MiscInfo) => void ): void { - if (itemMediaType === ItemMediaKind.Photo && itemWidth && itemHeight) { + if ( + showPhotoSizeInfo + && itemMediaType === ItemMediaKind.Photo + && itemWidth + && itemHeight + ) { const size = `${itemWidth}x${itemHeight}`; addMiscInfo({ text: size }); } } -interface UsePrimaryMediaInfoProps { +interface UsePrimaryMediaInfoProps extends PrimaryInfoOpts { item: ItemDto; - isYearEnabled: boolean; - isContainerEnabled: boolean; - isEpisodeTitleEnabled: boolean; - isOriginalAirDateEnabled: boolean; - isRuntimeEnabled: boolean; - isProgramIndicatorEnabled: boolean; - isEpisodeTitleIndexNumberEnabled: boolean; - isOfficialRatingEnabled: boolean; } function usePrimaryMediaInfo({ item, - isYearEnabled = false, - isContainerEnabled = false, - isEpisodeTitleEnabled = false, - isOriginalAirDateEnabled = false, - isRuntimeEnabled = false, - isProgramIndicatorEnabled = false, - isEpisodeTitleIndexNumberEnabled = false, - isOfficialRatingEnabled = false + showYearInfo = false, + showAudioContainerInfo = false, + showEpisodeTitleInfo = false, + showOriginalAirDateInfo = false, + showFolderRuntimeInfo = false, + showRuntimeInfo = false, + showItemCountInfo = false, + showSeriesTimerInfo = false, + showStartDateInfo = false, + showProgramIndicatorInfo = false, + includeEpisodeTitleIndexNumber = false, + showOfficialRatingInfo = false, + showVideo3DFormatInfo = false, + showPhotoSizeInfo = false }: UsePrimaryMediaInfoProps) { const { EndDate, @@ -441,10 +472,15 @@ function usePrimaryMediaInfo({ } }; - const showFolderRuntime = shouldShowFolderRuntime(Type, MediaType); + const isFolderRuntimeEnabled = shouldShowFolderRuntime( + showFolderRuntimeInfo, + Type, + MediaType + ); addTrackCountOrItemCount( - showFolderRuntime, + isFolderRuntimeEnabled, + showItemCountInfo, SongCount, ChildCount, RunTimeTicks, @@ -453,14 +489,15 @@ function usePrimaryMediaInfo({ ); addOriginalAirDateInfo( + showOriginalAirDateInfo, Type, MediaType, - isOriginalAirDateEnabled, PremiereDate, addMiscInfo ); addSeriesTimerInfo( + showSeriesTimerInfo, Type, RecordAnyTime, StartDate, @@ -469,29 +506,29 @@ function usePrimaryMediaInfo({ addMiscInfo ); - addStartDateInfo(StartDate, Type, addMiscInfo); + addStartDateInfo(showStartDateInfo, StartDate, Type, addMiscInfo); addSeriesProductionYearInfo( + showYearInfo, ProductionYear, Type, - isYearEnabled, Status, EndDate, addMiscInfo ); addProgramIndicators( + showProgramIndicatorInfo, + showEpisodeTitleInfo, + includeEpisodeTitleIndexNumber, + showOriginalAirDateInfo, + showYearInfo, item, - isProgramIndicatorEnabled, - isEpisodeTitleEnabled, - isEpisodeTitleIndexNumberEnabled, - isOriginalAirDateEnabled, - isYearEnabled, addMiscInfo ); addYearInfo( - isYearEnabled, + showYearInfo, Type, MediaType, ProductionYear, @@ -500,25 +537,25 @@ function usePrimaryMediaInfo({ ); addRunTimeInfo( + isFolderRuntimeEnabled, + showRuntimeInfo, RunTimeTicks, Type, - showFolderRuntime, - isRuntimeEnabled, addMiscInfo ); addOfficialRatingInfo( + showOfficialRatingInfo, OfficialRating, Type, - isOfficialRatingEnabled, addMiscInfo ); - addVideo3DFormat(Video3DFormat, addMiscInfo); + addVideo3DFormat(showVideo3DFormatInfo, Video3DFormat, addMiscInfo); - addPhotoSize(MediaType, Width, Height, addMiscInfo); + addPhotoSize(showPhotoSizeInfo, MediaType, Width, Height, addMiscInfo); - addAudioContainer(Container, isContainerEnabled, Type, addMiscInfo); + addAudioContainer(showAudioContainerInfo, Container, Type, addMiscInfo); return miscInfo; } diff --git a/src/components/mediainfo/useSecondaryMediaInfo.tsx b/src/components/mediainfo/useSecondaryMediaInfo.tsx new file mode 100644 index 0000000000..c7bbb0d708 --- /dev/null +++ b/src/components/mediainfo/useSecondaryMediaInfo.tsx @@ -0,0 +1,124 @@ +import datetime from 'scripts/datetime'; +import { appRouter } from 'components/router/appRouter'; +import type { NullableString } from 'types/base/common/shared/types'; +import type { ItemDto } from 'types/base/models/item-dto'; +import type { MiscInfo } from 'types/mediaInfoItem'; +import { ItemKind } from 'types/base/models/item-kind'; +import type { SecondaryInfoOpts } from './type'; + +function addProgramTime( + showProgramTimeInfo: boolean, + showStartDateInfo: boolean, + showEndDateInfo: boolean, + itemStartDate: NullableString, + itemEndDate: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + let programTimeText = ''; + let date; + + if (showProgramTimeInfo && itemStartDate) { + try { + date = datetime.parseISO8601Date(itemStartDate); + + if (showStartDateInfo) { + programTimeText += datetime.toLocaleDateString(date, { + weekday: 'short', + month: 'short', + day: 'numeric' + }); + } + + programTimeText += ` ${datetime.getDisplayTime(date)}`; + + if (showEndDateInfo && itemEndDate) { + date = datetime.parseISO8601Date(itemEndDate); + programTimeText += ` - ${datetime.getDisplayTime(date)}`; + } + addMiscInfo({ text: programTimeText }); + } catch (e) { + console.error('error parsing date:', itemStartDate); + } + } +} + +function addChannelNumber( + showChannelNumberInfo: boolean, + itemChannelNumber: NullableString, + addMiscInfo: (val: MiscInfo) => void +): void { + if (showChannelNumberInfo && itemChannelNumber) { + addMiscInfo({ + text: `CH ${itemChannelNumber}` + }); + } +} + +const addChannelName = ( + showChannelInfo: boolean, + channelInteractive: boolean, + item: ItemDto, + addMiscInfo: (val: MiscInfo) => void +) => { + if (showChannelInfo && item.ChannelName) { + if (channelInteractive && item.ChannelId) { + const url = appRouter.getRouteUrl({ + ServerId: item.ServerId, + Type: ItemKind.TvChannel, + Name: item.ChannelName, + Id: item.ChannelId + }); + + addMiscInfo({ + textAction: { + url, + title: item.ChannelName + } + }); + } else { + addMiscInfo({ text: item.ChannelName }); + } + } +}; + +interface UseSecondaryMediaInfoProps extends SecondaryInfoOpts { + item: ItemDto; +} + +function useSecondaryMediaInfo({ + item, + showProgramTimeInfo = false, + showStartDateInfo = false, + showEndDateInfo = false, + showChannelNumberInfo = false, + showChannelInfo = false, + channelInteractive = false +}: UseSecondaryMediaInfoProps) { + const { EndDate, StartDate, ChannelNumber } = item; + + const miscInfo: MiscInfo[] = []; + + if (item.Type === ItemKind.Program) { + const addMiscInfo = (val: MiscInfo) => { + if (val) { + miscInfo.push(val); + } + }; + + addProgramTime( + showProgramTimeInfo, + showStartDateInfo, + showEndDateInfo, + StartDate, + EndDate, + addMiscInfo + ); + + addChannelNumber(showChannelNumberInfo, ChannelNumber, addMiscInfo); + + addChannelName(showChannelInfo, channelInteractive, item, addMiscInfo); + } + return miscInfo; +} + +export default useSecondaryMediaInfo; diff --git a/src/types/mediaInfoItem.ts b/src/types/mediaInfoItem.ts index 3620fad620..faa6ebc374 100644 --- a/src/types/mediaInfoItem.ts +++ b/src/types/mediaInfoItem.ts @@ -1,4 +1,11 @@ -export interface MiscInfo { - text?: string | number; +interface TextAction { + url: string; + title: string; + cssClass?: string; +} +export interface MiscInfo { + text?: string | number; + type?: string; + textAction?: TextAction; cssClass?: string; }