1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Convert ListView to react

This commit is contained in:
grafixeyehero 2024-01-31 04:20:42 +03:00
parent cc87ba3859
commit 9efc71fa3b
14 changed files with 1009 additions and 0 deletions

View file

@ -0,0 +1,32 @@
import React, { FC } from 'react';
import useList from './useList';
import ListContent from './ListContent';
import ListWrapper from './ListWrapper';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
import '../../mediainfo/mediainfo.scss';
import '../../guide/programs.scss';
interface ListProps {
index: number;
item: ItemDto;
listOptions?: ListOptions;
}
const List: FC<ListProps> = ({ index, item, listOptions = {} }) => {
const { getListdWrapperProps, getListContentProps } = useList({ item, listOptions } );
const listWrapperProps = getListdWrapperProps();
const listContentProps = getListContentProps();
return (
<ListWrapper
key={index}
index={index}
{...listWrapperProps}
>
<ListContent {...listContentProps} />
</ListWrapper>
);
};
export default List;

View file

@ -0,0 +1,106 @@
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
import React, { FC } from 'react';
import DragHandleIcon from '@mui/icons-material/DragHandle';
import Box from '@mui/material/Box';
import useIndicator from 'components/indicators/useIndicator';
import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo';
import ListContentWrapper from './ListContentWrapper';
import ListItemBody from './ListItemBody';
import ListImageContainer from './ListImageContainer';
import ListViewUserDataButtons from './ListViewUserDataButtons';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
interface ListContentProps {
item: ItemDto;
listOptions: ListOptions;
enableContentWrapper?: boolean;
enableOverview?: boolean;
enableSideMediaInfo?: boolean;
clickEntireItem?: boolean;
action?: string;
isLargeStyle: boolean;
downloadWidth?: number;
}
const ListContent: FC<ListContentProps> = ({
item,
listOptions,
enableContentWrapper,
enableOverview,
enableSideMediaInfo,
clickEntireItem,
action,
isLargeStyle,
downloadWidth
}) => {
const indicator = useIndicator(item);
return (
<ListContentWrapper
itemOverview={item.Overview}
enableContentWrapper={enableContentWrapper}
enableOverview={enableOverview}
>
{!clickEntireItem && listOptions.dragHandle && (
<DragHandleIcon className='listViewDragHandle listItemIcon listItemIcon-transparent' />
)}
{listOptions.image !== false && (
<ListImageContainer
item={item}
listOptions={listOptions}
action={action}
isLargeStyle={isLargeStyle}
clickEntireItem={clickEntireItem}
downloadWidth={downloadWidth}
/>
)}
{listOptions.showIndexNumberLeft && (
<Box className='listItem-indexnumberleft'>
{item.IndexNumber ?? <span>&nbsp;</span>}
</Box>
)}
<ListItemBody
item={item}
listOptions={listOptions}
action={action}
enableContentWrapper={enableContentWrapper}
enableOverview={enableOverview}
enableSideMediaInfo={enableSideMediaInfo}
getMissingIndicator={indicator.getMissingIndicator}
/>
{listOptions.mediaInfo !== false && enableSideMediaInfo && (
<PrimaryMediaInfo
className='secondary listItemMediaInfo'
item={item}
isRuntimeEnabled={true}
isStarRatingEnabled={true}
isCaptionIndicatorEnabled={true}
isEpisodeTitleEnabled={true}
isOfficialRatingEnabled={true}
getMissingIndicator={indicator.getMissingIndicator}
/>
)}
{listOptions.recordButton
&& (item.Type === 'Timer' || item.Type === BaseItemKind.Program) && (
indicator.getTimerIndicator('listItemAside')
)}
{!clickEntireItem && (
<ListViewUserDataButtons
item={item}
listOptions={listOptions}
/>
)}
</ListContentWrapper>
);
};
export default ListContent;

View file

@ -0,0 +1,34 @@
import React, { FC } from 'react';
import Box from '@mui/material/Box';
interface ListContentWrapperProps {
itemOverview: string | null | undefined;
enableContentWrapper?: boolean;
enableOverview?: boolean;
}
const ListContentWrapper: FC<ListContentWrapperProps> = ({
itemOverview,
enableContentWrapper,
enableOverview,
children
}) => {
if (enableContentWrapper) {
return (
<>
<Box className='listItem-content'>{children}</Box>
{enableOverview && itemOverview && (
<Box className='listItem-bottomoverview secondary'>
<bdi>{itemOverview}</bdi>
</Box>
)}
</>
);
} else {
// eslint-disable-next-line react/jsx-no-useless-fragment
return <>{children}</>;
}
};
export default ListContentWrapper;

View file

@ -0,0 +1,30 @@
import React, { FC } from 'react';
import Typography from '@mui/material/Typography';
interface ListGroupHeaderWrapperProps {
index?: number;
}
const ListGroupHeaderWrapper: FC<ListGroupHeaderWrapperProps> = ({
index,
children
}) => {
if (index === 0) {
return (
<Typography
className='listGroupHeader listGroupHeader-first'
variant='h2'
>
{children}
</Typography>
);
} else {
return (
<Typography className='listGroupHeader' variant='h2'>
{children}
</Typography>
);
}
};
export default ListGroupHeaderWrapper;

View file

@ -0,0 +1,103 @@
import React, { FC } from 'react';
import classNames from 'classnames';
import Box from '@mui/material/Box';
import { useApi } from 'hooks/useApi';
import useIndicator from '../../indicators/useIndicator';
import layoutManager from '../../layoutManager';
import { getDefaultBackgroundClass } from '../../cardbuilder/cardBuilderUtils';
import {
canResume,
getChannelImageUrl,
getImageUrl
} from './listHelper';
import Media from 'components/common/Media';
import PlayArrowIconButton from 'components/common/PlayArrowIconButton';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
interface ListImageContainerProps {
item: ItemDto;
listOptions: ListOptions;
action?: string | null;
isLargeStyle: boolean;
clickEntireItem?: boolean;
downloadWidth?: number;
}
const ListImageContainer: FC<ListImageContainerProps> = ({
item = {},
listOptions,
action,
isLargeStyle,
clickEntireItem,
downloadWidth
}) => {
const { api } = useApi();
const { getMediaSourceIndicator, getProgressBar, getPlayedIndicator } = useIndicator(item);
const imgInfo = listOptions.imageSource === 'channel' ?
getChannelImageUrl(item, api, downloadWidth) :
getImageUrl(item, api, downloadWidth);
const defaultCardImageIcon = listOptions.defaultCardImageIcon;
const disableIndicators = listOptions.disableIndicators;
const imgUrl = imgInfo?.imgUrl;
const blurhash = imgInfo.blurhash;
const imageClass = classNames(
'listItemImage',
{ 'listItemImage-large': isLargeStyle },
{ 'listItemImage-channel': listOptions.imageSource === 'channel' },
{ 'listItemImage-large-tv': isLargeStyle && layoutManager.tv },
{ itemAction: !clickEntireItem },
{ [getDefaultBackgroundClass(item.Name)]: !imgUrl }
);
const playOnImageClick = listOptions.imagePlayButton && !layoutManager.tv;
const imageAction = playOnImageClick ? 'link' : action;
const btnCssClass =
'paper-icon-button-light listItemImageButton itemAction';
const mediaSourceIndicator = getMediaSourceIndicator();
const playedIndicator = getPlayedIndicator();
const progressBar = getProgressBar();
const playbackPositionTicks = item?.UserData?.PlaybackPositionTicks;
return (
<Box
data-action={imageAction}
className={imageClass}
>
<Media item={item} imgUrl={imgUrl} blurhash={blurhash} defaultCardImageIcon={defaultCardImageIcon} />
{disableIndicators !== true && mediaSourceIndicator}
{playedIndicator && (
<Box className='indicators listItemIndicators'>
{playedIndicator}
</Box>
)}
{playOnImageClick && (
<PlayArrowIconButton
className={btnCssClass}
action={
canResume(playbackPositionTicks) ? 'resume' : 'play'
}
title={
canResume(playbackPositionTicks) ?
'ButtonResume' :
'Play'
}
/>
)}
{progressBar}
</Box>
);
};
export default ListImageContainer;

View file

@ -0,0 +1,65 @@
import React, { FC } from 'react';
import classNames from 'classnames';
import Box from '@mui/material/Box';
import useListTextlines from './useListTextlines';
import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
interface ListItemBodyProps {
item: ItemDto;
listOptions: ListOptions;
action?: string | null;
isLargeStyle?: boolean;
clickEntireItem?: boolean;
enableContentWrapper?: boolean;
enableOverview?: boolean;
enableSideMediaInfo?: boolean;
getMissingIndicator: () => React.JSX.Element | null
}
const ListItemBody: FC<ListItemBodyProps> = ({
item = {},
listOptions = {},
action,
isLargeStyle,
clickEntireItem,
enableContentWrapper,
enableOverview,
enableSideMediaInfo,
getMissingIndicator
}) => {
const { listTextLines } = useListTextlines({ item, listOptions, isLargeStyle });
const cssClass = classNames(
'listItemBody',
{ 'itemAction': !clickEntireItem },
{ 'listItemBody-noleftpadding': listOptions.image === false }
);
return (
<Box data-action={action} className={cssClass}>
{listTextLines}
{listOptions.mediaInfo !== false && !enableSideMediaInfo && (
<PrimaryMediaInfo
className='secondary listItemMediaInfo listItemBodyText'
item={item}
isEpisodeTitleEnabled={true}
isOriginalAirDateEnabled={true}
isCaptionIndicatorEnabled={true}
getMissingIndicator={getMissingIndicator}
/>
)}
{!enableContentWrapper && enableOverview && item.Overview && (
<Box className='secondary listItem-overview listItemBodyText'>
<bdi>{item.Overview}</bdi>
</Box>
)}
</Box>
);
};
export default ListItemBody;

View file

@ -0,0 +1,30 @@
import React, { FC } from 'react';
import Box from '@mui/material/Box';
import Typography from '@mui/material/Typography';
interface ListTextWrapperProps {
index?: number;
isLargeStyle?: boolean;
}
const ListTextWrapper: FC<ListTextWrapperProps> = ({
index,
isLargeStyle,
children
}) => {
if (index === 0) {
if (isLargeStyle) {
return (
<Typography className='listItemBodyText' variant='h2'>
{children}
</Typography>
);
} else {
return <Box className='listItemBodyText'>{children}</Box>;
}
} else {
return <Box className='secondary listItemBodyText'>{children}</Box>;
}
};
export default ListTextWrapper;

View file

@ -0,0 +1,87 @@
import React, { FC } from 'react';
import { Box } from '@mui/material';
import itemHelper from '../../itemHelper';
import PlayedButton from 'elements/emby-playstatebutton/PlayedButton';
import FavoriteButton from 'elements/emby-ratingbutton/FavoriteButton';
import PlaylistAddIconButton from '../../common/PlaylistAddIconButton';
import InfoIconButton from '../../common/InfoIconButton';
import RightIconButtons from '../../common/RightIconButtons';
import MoreVertIconButton from '../../common/MoreVertIconButton';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
interface ListViewUserDataButtonsProps {
item: ItemDto;
listOptions: ListOptions;
}
const ListViewUserDataButtons: FC<ListViewUserDataButtonsProps> = ({
item = {},
listOptions
}) => {
const { IsFavorite, Played } = item.UserData ?? {};
const renderRightButtons = () => {
return listOptions.rightButtons?.map((button, index) => (
<RightIconButtons
// eslint-disable-next-line react/no-array-index-key
key={index}
className='listItemButton itemAction'
id={button.id}
title={button.title}
icon={button.icon}
/>
));
};
return (
<Box className='listViewUserDataButtons'>
{listOptions.addToListButton && (
<PlaylistAddIconButton
className='paper-icon-button-light listItemButton itemAction'
/>
)}
{listOptions.infoButton && (
<InfoIconButton
className='paper-icon-button-light listItemButton itemAction'
/>
) }
{listOptions.rightButtons && renderRightButtons()}
{listOptions.enableUserDataButtons !== false && (
<>
{itemHelper.canMarkPlayed(item)
&& listOptions.enablePlayedButton !== false && (
<PlayedButton
className='listItemButton'
isPlayed={Played}
itemId={item.Id}
itemType={item.Type}
/>
)}
{itemHelper.canRate(item)
&& listOptions.enableRatingButton !== false && (
<FavoriteButton
className='listItemButton'
isFavorite={IsFavorite}
itemId={item.Id}
/>
)}
</>
)}
{listOptions.moreButton !== false && (
<MoreVertIconButton
className='paper-icon-button-light listItemButton itemAction'
/>
)}
</Box>
);
};
export default ListViewUserDataButtons;

View file

@ -0,0 +1,49 @@
import classNames from 'classnames';
import escapeHTML from 'escape-html';
import React, { FC } from 'react';
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import layoutManager from '../../layoutManager';
import type { DataAttributes } from 'types/dataAttributes';
interface ListWrapperProps {
index: number | undefined;
title?: string | null;
action?: string | null;
dataAttributes?: DataAttributes;
className?: string;
}
const ListWrapper: FC<ListWrapperProps> = ({
index,
action,
title,
className,
dataAttributes,
children
}) => {
if (layoutManager.tv) {
return (
<Button
data-index={index}
className={classNames(
className,
'itemAction listItem-button listItem-focusscale'
)}
data-action={action}
aria-label={escapeHTML(title)}
{...dataAttributes}
>
{children}
</Button>
);
} else {
return (
<Box data-index={index} className={className} {...dataAttributes}>
{children}
</Box>
);
}
};
export default ListWrapper;

View file

@ -0,0 +1,57 @@
import React, { FC } from 'react';
import escapeHTML from 'escape-html';
import { groupBy } from 'lodash-es';
import Box from '@mui/material/Box';
import { getIndex } from './listHelper';
import ListGroupHeaderWrapper from './ListGroupHeaderWrapper';
import List from './List';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
import '../listview.scss';
interface ListsProps {
items: ItemDto[];
listOptions?: ListOptions;
}
const Lists: FC<ListsProps> = ({ items = [], listOptions = {} }) => {
const groupedData = groupBy(items, (item) => {
if (listOptions.showIndex) {
return getIndex(item, listOptions);
}
return '';
});
const renderListItem = (item: ItemDto, index: number) => {
return (
<List
// eslint-disable-next-line react/no-array-index-key
key={`${item.Id}-${index}`}
index={index}
item={item}
listOptions={listOptions}
/>
);
};
return (
<>
{Object.entries(groupedData).map(
([itemGroupTitle, getItems], index) => (
// eslint-disable-next-line react/no-array-index-key
<Box key={index}>
{itemGroupTitle && (
<ListGroupHeaderWrapper index={index}>
{escapeHTML(itemGroupTitle)}
</ListGroupHeaderWrapper>
)}
{getItems.map((item) => renderListItem(item, index))}
</Box>
)
)}
</>
);
};
export default Lists;

View file

@ -0,0 +1,171 @@
import { Api } from '@jellyfin/sdk';
import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client';
import globalize from 'scripts/globalize';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
const sortBySortName = (item: ItemDto): string => {
if (item.Type === BaseItemKind.Episode) {
return '';
}
// SortName
const name = (item.SortName ?? item.Name ?? '?')[0].toUpperCase();
const code = name.charCodeAt(0);
if (code < 65 || code > 90) {
return '#';
}
return name.toUpperCase();
};
const sortByOfficialrating = (item: ItemDto): string => {
return item.OfficialRating ?? globalize.translate('Unrated');
};
const sortByCommunityRating = (item: ItemDto): string => {
if (item.CommunityRating == null) {
return globalize.translate('Unrated');
}
return String(Math.floor(item.CommunityRating));
};
const sortByCriticRating = (item: ItemDto): string => {
if (item.CriticRating == null) {
return globalize.translate('Unrated');
}
return String(Math.floor(item.CriticRating));
};
const sortByAlbumArtist = (item: ItemDto): string => {
// SortName
if (!item.AlbumArtist) {
return '';
}
const name = item.AlbumArtist[0].toUpperCase();
const code = name.charCodeAt(0);
if (code < 65 || code > 90) {
return '#';
}
return name.toUpperCase();
};
export function getIndex(item: ItemDto, listOptions: ListOptions): string {
if (listOptions.index === 'disc') {
return item.ParentIndexNumber == null ?
'' :
globalize.translate('ValueDiscNumber', item.ParentIndexNumber);
}
const sortBy = (listOptions.sortBy ?? '').toLowerCase();
if (sortBy.startsWith('sortname')) {
return sortBySortName(item);
}
if (sortBy.startsWith('officialrating')) {
return sortByOfficialrating(item);
}
if (sortBy.startsWith('communityrating')) {
return sortByCommunityRating(item);
}
if (sortBy.startsWith('criticrating')) {
return sortByCriticRating(item);
}
if (sortBy.startsWith('albumartist')) {
return sortByAlbumArtist(item);
}
return '';
}
export function getImageUrl(
item: ItemDto,
api: Api | undefined,
size: number | undefined
) {
let imgTag;
let itemId;
const fillWidth = size;
const fillHeight = size;
const imgType = ImageType.Primary;
if (item.ImageTags?.Primary) {
imgTag = item.ImageTags.Primary;
itemId = item.Id;
} else if (item.AlbumId && item.AlbumPrimaryImageTag) {
imgTag = item.AlbumPrimaryImageTag;
itemId = item.AlbumId;
} else if (item.SeriesId && item.SeriesPrimaryImageTag) {
imgTag = item.SeriesPrimaryImageTag;
itemId = item.SeriesId;
} else if (item.ParentPrimaryImageTag) {
imgTag = item.ParentPrimaryImageTag;
itemId = item.ParentPrimaryImageItemId;
}
if (api && imgTag && imgType && itemId) {
const response = api.getItemImageUrl(itemId, imgType, {
fillWidth: fillWidth,
fillHeight: fillHeight,
tag: imgTag
});
return {
imgUrl: response,
blurhash: item.ImageBlurHashes?.[imgType]?.[imgTag]
};
}
return {
imgUrl: undefined,
blurhash: undefined
};
}
export function getChannelImageUrl(
item: ItemDto,
api: Api | undefined,
size: number | undefined
) {
let imgTag;
let itemId;
const fillWidth = size;
const fillHeight = size;
const imgType = ImageType.Primary;
if (item.ChannelId && item.ChannelPrimaryImageTag) {
imgTag = item.ChannelPrimaryImageTag;
itemId = item.ChannelId;
}
if (api && imgTag && imgType && itemId) {
const response = api.getItemImageUrl(itemId, imgType, {
fillWidth: fillWidth,
fillHeight: fillHeight,
tag: imgTag
});
return {
imgUrl: response,
blurhash: item.ImageBlurHashes?.[imgType]?.[imgTag]
};
}
return {
imgUrl: undefined,
blurhash: undefined
};
}
export function canResume(PlaybackPositionTicks: number | undefined): boolean {
return Boolean(
PlaybackPositionTicks
&& PlaybackPositionTicks > 0
);
}

View file

@ -0,0 +1,77 @@
import classNames from 'classnames';
import { getDataAttributes } from 'utils/items';
import layoutManager from 'components/layoutManager';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
interface UseListProps {
item: ItemDto;
listOptions: ListOptions;
}
function useList({ item, listOptions }: UseListProps) {
const action = listOptions.action ?? 'link';
const isLargeStyle = listOptions.imageSize === 'large';
const enableOverview = listOptions.enableOverview;
const clickEntireItem = !!layoutManager.tv;
const enableSideMediaInfo = listOptions.enableSideMediaInfo ?? true;
const enableContentWrapper =
listOptions.enableOverview && !layoutManager.tv;
const downloadWidth = isLargeStyle ? 500 : 80;
const dataAttributes = getDataAttributes(
{
action,
itemServerId: item.ServerId,
itemId: item.Id,
collectionId: listOptions.collectionId,
playlistId: listOptions.playlistId,
itemChannelId: item.ChannelId,
itemType: item.Type,
itemMediaType: item.MediaType,
itemCollectionType: item.CollectionType,
itemIsFolder: item.IsFolder,
itemPlaylistItemId: item.PlaylistItemId
}
);
const listWrapperClass = classNames(
'listItem',
{
'listItem-border':
listOptions.border
?? (listOptions.highlight !== false && !layoutManager.tv)
},
{ 'itemAction listItem-button': clickEntireItem },
{ 'listItem-focusscale': layoutManager.tv },
{ 'listItem-largeImage': isLargeStyle },
{ 'listItem-withContentWrapper': enableContentWrapper }
);
const getListdWrapperProps = () => ({
className: listWrapperClass,
title: item.Name,
action,
dataAttributes
});
const getListContentProps = () => ({
item,
listOptions,
enableContentWrapper,
enableOverview,
enableSideMediaInfo,
clickEntireItem,
action,
isLargeStyle,
downloadWidth
});
return {
getListdWrapperProps,
getListContentProps
};
}
export default useList;

View file

@ -0,0 +1,167 @@
import React from 'react';
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
import itemHelper from '../../itemHelper';
import datetime from 'scripts/datetime';
import ListTextWrapper from './ListTextWrapper';
import type { ItemDto } from 'types/itemDto';
import type { ListOptions } from 'types/listOptions';
function getParentTitle(
showParentTitle: boolean | undefined,
item: ItemDto,
parentTitleWithTitle: boolean | undefined,
displayName: string | null | undefined
) {
let parentTitle = null;
if (showParentTitle) {
if (item.Type === BaseItemKind.Episode) {
parentTitle = item.SeriesName;
} else if (item.IsSeries || (item.EpisodeTitle && item.Name)) {
parentTitle = item.Name;
}
}
if (showParentTitle && parentTitleWithTitle) {
if (displayName) {
parentTitle += ' - ';
}
parentTitle = (parentTitle ?? '') + displayName;
}
return parentTitle;
}
function getNameOrIndexWithName(
item: ItemDto,
listOptions: ListOptions,
showIndexNumber: boolean | undefined
) {
let displayName = itemHelper.getDisplayName(item, {
includeParentInfo: listOptions.includeParentInfoInTitle
});
if (showIndexNumber && item.IndexNumber != null) {
displayName = `${item.IndexNumber}. ${displayName}`;
}
return displayName;
}
interface UseListTextlinesProps {
item: ItemDto;
listOptions?: ListOptions;
isLargeStyle?: boolean;
}
function useListTextlines({ item = {}, listOptions = {}, isLargeStyle }: UseListTextlinesProps) {
const {
showProgramDateTime,
showProgramTime,
showChannel,
showParentTitle,
showIndexNumber,
parentTitleWithTitle,
artist
} = listOptions;
const textLines: string[] = [];
const addTextLine = (text: string | null) => {
if (text) {
textLines.push(text);
}
};
const addProgramDateTime = () => {
if (showProgramDateTime) {
const programDateTime = datetime.toLocaleString(
datetime.parseISO8601Date(item.StartDate),
{
weekday: 'long',
month: 'short',
day: 'numeric',
hour: 'numeric',
minute: '2-digit'
}
);
addTextLine(programDateTime);
}
};
const addProgramTime = () => {
if (showProgramTime) {
const programTime = datetime.getDisplayTime(
datetime.parseISO8601Date(item.StartDate)
);
addTextLine(programTime);
}
};
const addChannelName = () => {
if (showChannel && item.ChannelName) {
addTextLine(item.ChannelName);
}
};
const displayName = getNameOrIndexWithName(item, listOptions, showIndexNumber);
const parentTitle = getParentTitle(showParentTitle, item, parentTitleWithTitle, displayName );
const addParentTitle = () => {
addTextLine(parentTitle ?? '');
};
const addDisplayName = () => {
if (displayName && !parentTitleWithTitle) {
addTextLine(displayName);
}
};
const addAlbumArtistOrArtists = () => {
if (item.IsFolder && artist !== false) {
if (item.AlbumArtist && item.Type === BaseItemKind.MusicAlbum) {
addTextLine(item.AlbumArtist);
}
} else if (artist) {
const artistItems = item.ArtistItems;
if (artistItems && item.Type !== BaseItemKind.MusicAlbum) {
const artists = artistItems.map((a) => a.Name).join(', ');
addTextLine(artists);
}
}
};
const addCurrentProgram = () => {
if (item.Type === BaseItemKind.TvChannel && item.CurrentProgram) {
const currentProgram = itemHelper.getDisplayName(
item.CurrentProgram
);
addTextLine(currentProgram);
}
};
addProgramDateTime();
addProgramTime();
addChannelName();
addParentTitle();
addDisplayName();
addAlbumArtistOrArtists();
addCurrentProgram();
const renderTextlines = (text: string, index: number) => {
return (
<ListTextWrapper
// eslint-disable-next-line react/no-array-index-key
key={index}
index={index}
isLargeStyle={isLargeStyle}
>
<bdi>{text}</bdi>
</ListTextWrapper>
);
};
const listTextLines = textLines?.map((text, index) => renderTextlines(text, index));
return {
listTextLines
};
}
export default useListTextlines;

View file

@ -183,6 +183,7 @@
}
.listItemImage .cardImageIcon {
margin: auto;
font-size: 3em;
}