mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Merge pull request #5850 from grafixeyehero/move-reusable-component
Move reusable Text Lines component to common file
This commit is contained in:
commit
3e8592e29e
9 changed files with 172 additions and 103 deletions
|
@ -92,7 +92,7 @@ const ItemsView: FC<ItemsViewProps> = ({
|
||||||
listOptions.showParentTitle = true;
|
listOptions.showParentTitle = true;
|
||||||
listOptions.action = 'playallfromhere';
|
listOptions.action = 'playallfromhere';
|
||||||
listOptions.smallIcon = true;
|
listOptions.smallIcon = true;
|
||||||
listOptions.artist = true;
|
listOptions.showArtist = true;
|
||||||
listOptions.addToListButton = true;
|
listOptions.addToListButton = true;
|
||||||
} else if (viewType === LibraryTab.Albums) {
|
} else if (viewType === LibraryTab.Albums) {
|
||||||
listOptions.sortBy = libraryViewSettings.SortBy;
|
listOptions.sortBy = libraryViewSettings.SortBy;
|
||||||
|
|
74
src/components/common/textLines/TextLines.tsx
Normal file
74
src/components/common/textLines/TextLines.tsx
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
import React, { type FC, type PropsWithChildren } from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import Typography from '@mui/material/Typography';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import useTextLines from './useTextLines';
|
||||||
|
|
||||||
|
import type { ItemDto } from 'types/base/models/item-dto';
|
||||||
|
import type { TextLine, TextLineOpts } from './types';
|
||||||
|
|
||||||
|
interface TextWrapperProps {
|
||||||
|
isHeading?: boolean;
|
||||||
|
isLargeStyle?: boolean;
|
||||||
|
className?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextWrapper: FC<PropsWithChildren<TextWrapperProps>> = ({
|
||||||
|
isHeading,
|
||||||
|
isLargeStyle,
|
||||||
|
className,
|
||||||
|
children
|
||||||
|
}) => {
|
||||||
|
if (isHeading) {
|
||||||
|
return (
|
||||||
|
<Typography className={classNames('primary', className)} variant={isLargeStyle ? 'h1' : 'h3'}>
|
||||||
|
{children}
|
||||||
|
</Typography>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Box className={classNames('secondary', className )}>
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface TextLinesProps {
|
||||||
|
item: ItemDto;
|
||||||
|
textLineOpts?: TextLineOpts;
|
||||||
|
isLargeStyle?: boolean;
|
||||||
|
className?: string;
|
||||||
|
textClassName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextLines: FC<TextLinesProps> = ({
|
||||||
|
item,
|
||||||
|
textLineOpts,
|
||||||
|
isLargeStyle,
|
||||||
|
className,
|
||||||
|
textClassName
|
||||||
|
}) => {
|
||||||
|
const { textLines } = useTextLines({ item, textLineOpts });
|
||||||
|
|
||||||
|
const renderTextlines = (text: TextLine, index: number) => {
|
||||||
|
return (
|
||||||
|
<TextWrapper
|
||||||
|
key={index}
|
||||||
|
isHeading={index === 0}
|
||||||
|
isLargeStyle={isLargeStyle}
|
||||||
|
className={textClassName}
|
||||||
|
>
|
||||||
|
<bdi>{text.title}</bdi>
|
||||||
|
</TextWrapper>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box className={className}>
|
||||||
|
{textLines?.map((text, index) => renderTextlines(text, index))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TextLines;
|
18
src/components/common/textLines/types.ts
Normal file
18
src/components/common/textLines/types.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
export interface TextLine {
|
||||||
|
title?: string;
|
||||||
|
cssClass?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TextLineOpts {
|
||||||
|
showProgramDateTime?: boolean;
|
||||||
|
showProgramTime?: boolean;
|
||||||
|
showChannel?: boolean;
|
||||||
|
showTitle?: boolean;
|
||||||
|
showParentTitle?: boolean;
|
||||||
|
showIndexNumber?: boolean;
|
||||||
|
parentTitleWithTitle?: boolean;
|
||||||
|
showArtist?: boolean;
|
||||||
|
showCurrentProgram?: boolean;
|
||||||
|
includeIndexNumber?: boolean;
|
||||||
|
includeParentInfoInTitle?: boolean;
|
||||||
|
}
|
|
@ -1,27 +1,25 @@
|
||||||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client/models/base-item-kind';
|
|
||||||
import React from 'react';
|
|
||||||
import itemHelper from '../../itemHelper';
|
import itemHelper from '../../itemHelper';
|
||||||
import datetime from 'scripts/datetime';
|
import datetime from 'scripts/datetime';
|
||||||
import ListTextWrapper from './ListTextWrapper';
|
|
||||||
import type { ItemDto } from 'types/base/models/item-dto';
|
import type { ItemDto } from 'types/base/models/item-dto';
|
||||||
import type { ListOptions } from 'types/listOptions';
|
import type { TextLine, TextLineOpts } from './types';
|
||||||
|
import { ItemKind } from 'types/base/models/item-kind';
|
||||||
|
|
||||||
function getParentTitle(
|
function getParentTitle(
|
||||||
showParentTitle: boolean | undefined,
|
|
||||||
item: ItemDto,
|
item: ItemDto,
|
||||||
|
showParentTitle: boolean | undefined,
|
||||||
parentTitleWithTitle: boolean | undefined,
|
parentTitleWithTitle: boolean | undefined,
|
||||||
displayName: string | null | undefined
|
displayName: string | null | undefined
|
||||||
) {
|
) {
|
||||||
let parentTitle = null;
|
let parentTitle;
|
||||||
if (showParentTitle) {
|
if (showParentTitle) {
|
||||||
if (item.Type === BaseItemKind.Episode) {
|
if (item.Type === ItemKind.Season || item.Type === ItemKind.Episode) {
|
||||||
parentTitle = item.SeriesName;
|
parentTitle = item.SeriesName;
|
||||||
} else if (item.IsSeries || (item.EpisodeTitle && item.Name)) {
|
} else if (item.IsSeries || (item.EpisodeTitle && item.Name)) {
|
||||||
parentTitle = item.Name;
|
parentTitle = item.Name;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (showParentTitle && parentTitleWithTitle) {
|
if (showParentTitle && parentTitleWithTitle) {
|
||||||
if (displayName) {
|
if (displayName && parentTitle) {
|
||||||
parentTitle += ' - ';
|
parentTitle += ' - ';
|
||||||
}
|
}
|
||||||
parentTitle = (parentTitle ?? '') + displayName;
|
parentTitle = (parentTitle ?? '') + displayName;
|
||||||
|
@ -31,11 +29,13 @@ function getParentTitle(
|
||||||
|
|
||||||
function getNameOrIndexWithName(
|
function getNameOrIndexWithName(
|
||||||
item: ItemDto,
|
item: ItemDto,
|
||||||
listOptions: ListOptions,
|
showIndexNumber?: boolean,
|
||||||
showIndexNumber: boolean | undefined
|
includeParentInfoInTitle?: boolean,
|
||||||
|
includeIndexNumber?: boolean
|
||||||
) {
|
) {
|
||||||
let displayName = itemHelper.getDisplayName(item, {
|
let displayName = itemHelper.getDisplayName(item, {
|
||||||
includeParentInfo: listOptions.includeParentInfoInTitle
|
includeParentInfo: includeParentInfoInTitle,
|
||||||
|
includeIndexNumber
|
||||||
});
|
});
|
||||||
|
|
||||||
if (showIndexNumber && item.IndexNumber != null) {
|
if (showIndexNumber && item.IndexNumber != null) {
|
||||||
|
@ -44,27 +44,31 @@ function getNameOrIndexWithName(
|
||||||
return displayName;
|
return displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UseListTextlinesProps {
|
interface UseTextLinesProps {
|
||||||
item: ItemDto;
|
item: ItemDto;
|
||||||
listOptions?: ListOptions;
|
textLineOpts?: TextLineOpts;
|
||||||
isLargeStyle?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function useListTextlines({ item = {}, listOptions = {}, isLargeStyle }: UseListTextlinesProps) {
|
function useTextLines({ item, textLineOpts = {} }: UseTextLinesProps) {
|
||||||
const {
|
const {
|
||||||
|
showTitle,
|
||||||
showProgramDateTime,
|
showProgramDateTime,
|
||||||
showProgramTime,
|
showProgramTime,
|
||||||
showChannel,
|
showChannel,
|
||||||
showParentTitle,
|
showParentTitle,
|
||||||
showIndexNumber,
|
showIndexNumber,
|
||||||
parentTitleWithTitle,
|
parentTitleWithTitle,
|
||||||
artist
|
showArtist,
|
||||||
} = listOptions;
|
showCurrentProgram,
|
||||||
const textLines: string[] = [];
|
includeParentInfoInTitle,
|
||||||
|
includeIndexNumber
|
||||||
|
} = textLineOpts;
|
||||||
|
|
||||||
const addTextLine = (text: string | null) => {
|
const textLines: TextLine[] = [];
|
||||||
if (text) {
|
|
||||||
textLines.push(text);
|
const addTextLine = (textLine: TextLine) => {
|
||||||
|
if (textLine) {
|
||||||
|
textLines.push(textLine);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -80,7 +84,7 @@ function useListTextlines({ item = {}, listOptions = {}, isLargeStyle }: UseList
|
||||||
minute: '2-digit'
|
minute: '2-digit'
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
addTextLine(programDateTime);
|
addTextLine({ title: programDateTime });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -89,50 +93,54 @@ function useListTextlines({ item = {}, listOptions = {}, isLargeStyle }: UseList
|
||||||
const programTime = datetime.getDisplayTime(
|
const programTime = datetime.getDisplayTime(
|
||||||
datetime.parseISO8601Date(item.StartDate)
|
datetime.parseISO8601Date(item.StartDate)
|
||||||
);
|
);
|
||||||
addTextLine(programTime);
|
addTextLine({ title: programTime });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addChannelName = () => {
|
const addChannelName = () => {
|
||||||
if (showChannel && item.ChannelName) {
|
if (showChannel && item.ChannelName) {
|
||||||
addTextLine(item.ChannelName);
|
addTextLine({ title: item.ChannelName });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const displayName = getNameOrIndexWithName(item, listOptions, showIndexNumber);
|
const displayName = getNameOrIndexWithName(item, showIndexNumber, includeParentInfoInTitle, includeIndexNumber);
|
||||||
|
|
||||||
const parentTitle = getParentTitle(showParentTitle, item, parentTitleWithTitle, displayName );
|
const parentTitle = getParentTitle(item, showParentTitle, parentTitleWithTitle, displayName );
|
||||||
|
|
||||||
const addParentTitle = () => {
|
const addParentTitle = () => {
|
||||||
addTextLine(parentTitle ?? '');
|
if (parentTitle) {
|
||||||
|
addTextLine({ title: parentTitle });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addDisplayName = () => {
|
const addDisplayName = () => {
|
||||||
if (displayName && !parentTitleWithTitle) {
|
if (displayName && !parentTitleWithTitle && showTitle !== false) {
|
||||||
addTextLine(displayName);
|
addTextLine({ title: displayName });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addAlbumArtistOrArtists = () => {
|
const addAlbumArtistOrArtists = () => {
|
||||||
if (item.IsFolder && artist !== false) {
|
if (item.IsFolder && showArtist !== false) {
|
||||||
if (item.AlbumArtist && item.Type === BaseItemKind.MusicAlbum) {
|
if (item.AlbumArtist && item.Type === ItemKind.MusicAlbum) {
|
||||||
addTextLine(item.AlbumArtist);
|
addTextLine({ title: item.AlbumArtist });
|
||||||
}
|
}
|
||||||
} else if (artist) {
|
} else if (showArtist) {
|
||||||
const artistItems = item.ArtistItems;
|
const artistItems = item.ArtistItems;
|
||||||
if (artistItems && item.Type !== BaseItemKind.MusicAlbum) {
|
if (artistItems && item.Type !== ItemKind.MusicAlbum) {
|
||||||
const artists = artistItems.map((a) => a.Name).join(', ');
|
const artists = artistItems.map((a) => a.Name).join(', ');
|
||||||
addTextLine(artists);
|
addTextLine({ title: artists });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const addCurrentProgram = () => {
|
const addCurrentProgram = () => {
|
||||||
if (item.Type === BaseItemKind.TvChannel && item.CurrentProgram) {
|
if (item.Type === ItemKind.TvChannel && item.CurrentProgram && showCurrentProgram !== false) {
|
||||||
const currentProgram = itemHelper.getDisplayName(
|
const currentProgram = itemHelper.getDisplayName(item.CurrentProgram, {
|
||||||
item.CurrentProgram
|
includeParentInfo: includeParentInfoInTitle,
|
||||||
);
|
includeIndexNumber
|
||||||
addTextLine(currentProgram);
|
});
|
||||||
|
|
||||||
|
addTextLine({ title: currentProgram });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -144,24 +152,9 @@ function useListTextlines({ item = {}, listOptions = {}, isLargeStyle }: UseList
|
||||||
addAlbumArtistOrArtists();
|
addAlbumArtistOrArtists();
|
||||||
addCurrentProgram();
|
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 {
|
return {
|
||||||
listTextLines
|
textLines
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default useListTextlines;
|
export default useTextLines;
|
|
@ -75,7 +75,7 @@ const ListContent: FC<ListContentProps> = ({
|
||||||
getMissingIndicator={indicator.getMissingIndicator}
|
getMissingIndicator={indicator.getMissingIndicator}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{listOptions.mediaInfo !== false && enableSideMediaInfo && (
|
{listOptions.showMediaInfo !== false && enableSideMediaInfo && (
|
||||||
<PrimaryMediaInfo
|
<PrimaryMediaInfo
|
||||||
className='secondary listItemMediaInfo'
|
className='secondary listItemMediaInfo'
|
||||||
item={item}
|
item={item}
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
import React, { type FC } from 'react';
|
import React, { type FC } from 'react';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import useListTextlines from './useListTextlines';
|
|
||||||
|
import TextLines from 'components/common/textLines/TextLines';
|
||||||
import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo';
|
import PrimaryMediaInfo from '../../mediainfo/PrimaryMediaInfo';
|
||||||
|
|
||||||
import type { ItemDto } from 'types/base/models/item-dto';
|
import type { ItemDto } from 'types/base/models/item-dto';
|
||||||
|
@ -30,7 +31,6 @@ const ListItemBody: FC<ListItemBodyProps> = ({
|
||||||
enableSideMediaInfo,
|
enableSideMediaInfo,
|
||||||
getMissingIndicator
|
getMissingIndicator
|
||||||
}) => {
|
}) => {
|
||||||
const { listTextLines } = useListTextlines({ item, listOptions, isLargeStyle });
|
|
||||||
const cssClass = classNames(
|
const cssClass = classNames(
|
||||||
'listItemBody',
|
'listItemBody',
|
||||||
{ 'itemAction': !clickEntireItem },
|
{ 'itemAction': !clickEntireItem },
|
||||||
|
@ -40,9 +40,25 @@ const ListItemBody: FC<ListItemBodyProps> = ({
|
||||||
return (
|
return (
|
||||||
<Box data-action={action} className={cssClass}>
|
<Box data-action={action} className={cssClass}>
|
||||||
|
|
||||||
{listTextLines}
|
<TextLines
|
||||||
|
item={item}
|
||||||
|
textClassName='listItemBodyText'
|
||||||
|
textLineOpts={{
|
||||||
|
showProgramDateTime: listOptions.showProgramDateTime,
|
||||||
|
showProgramTime: listOptions.showProgramTime,
|
||||||
|
showChannel: listOptions.showChannel,
|
||||||
|
showParentTitle: listOptions.showParentTitle,
|
||||||
|
showIndexNumber: listOptions.showIndexNumber,
|
||||||
|
parentTitleWithTitle: listOptions.parentTitleWithTitle,
|
||||||
|
showArtist: listOptions.showArtist,
|
||||||
|
includeParentInfoInTitle: listOptions.includeParentInfoInTitle,
|
||||||
|
includeIndexNumber: listOptions.includeIndexNumber,
|
||||||
|
showCurrentProgram: listOptions.showCurrentProgram
|
||||||
|
}}
|
||||||
|
isLargeStyle={isLargeStyle}
|
||||||
|
/>
|
||||||
|
|
||||||
{listOptions.mediaInfo !== false && !enableSideMediaInfo && (
|
{listOptions.showMediaInfo !== false && !enableSideMediaInfo && (
|
||||||
<PrimaryMediaInfo
|
<PrimaryMediaInfo
|
||||||
className='secondary listItemMediaInfo listItemBodyText'
|
className='secondary listItemMediaInfo listItemBodyText'
|
||||||
item={item}
|
item={item}
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
import React, { type FC, type PropsWithChildren } from 'react';
|
|
||||||
import Box from '@mui/material/Box';
|
|
||||||
import Typography from '@mui/material/Typography';
|
|
||||||
|
|
||||||
interface ListTextWrapperProps {
|
|
||||||
index?: number;
|
|
||||||
isLargeStyle?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const ListTextWrapper: FC<PropsWithChildren<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;
|
|
|
@ -5,6 +5,10 @@
|
||||||
contain: layout style;
|
contain: layout style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.listItemMediaInfo {
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.listItem {
|
.listItem {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: 0;
|
border: 0;
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
import { ItemSortBy } from '@jellyfin/sdk/lib/models/api/item-sort-by';
|
||||||
import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
import type { CollectionType } from '@jellyfin/sdk/lib/generated-client/models/collection-type';
|
||||||
import type { ItemDto } from './base/models/item-dto';
|
import type { ItemDto } from './base/models/item-dto';
|
||||||
export interface ListOptions {
|
import type { TextLineOpts } from 'components/common/textLines/types';
|
||||||
|
|
||||||
|
export interface ListOptions extends TextLineOpts {
|
||||||
items?: ItemDto[] | null;
|
items?: ItemDto[] | null;
|
||||||
index?: string;
|
index?: string;
|
||||||
showIndex?: boolean;
|
showIndex?: boolean;
|
||||||
|
@ -17,21 +19,13 @@ export interface ListOptions {
|
||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
dragHandle?: boolean;
|
dragHandle?: boolean;
|
||||||
showIndexNumberLeft?: boolean;
|
showIndexNumberLeft?: boolean;
|
||||||
mediaInfo?: boolean;
|
showMediaInfo?: boolean;
|
||||||
recordButton?: boolean;
|
recordButton?: boolean;
|
||||||
image?: boolean;
|
image?: boolean;
|
||||||
imageSource?: string;
|
imageSource?: string;
|
||||||
defaultCardImageIcon?: string;
|
defaultCardImageIcon?: string;
|
||||||
disableIndicators?: boolean;
|
disableIndicators?: boolean;
|
||||||
imagePlayButton?: boolean;
|
imagePlayButton?: boolean;
|
||||||
showProgramDateTime?: boolean;
|
|
||||||
showProgramTime?: boolean;
|
|
||||||
showChannel?: boolean;
|
|
||||||
showParentTitle?: boolean;
|
|
||||||
showIndexNumber?: boolean;
|
|
||||||
parentTitleWithTitle?: boolean;
|
|
||||||
artist?: boolean;
|
|
||||||
includeParentInfoInTitle?: boolean;
|
|
||||||
addToListButton?: boolean;
|
addToListButton?: boolean;
|
||||||
infoButton?: boolean;
|
infoButton?: boolean;
|
||||||
enableUserDataButtons?: boolean;
|
enableUserDataButtons?: boolean;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue