mirror of
https://github.com/jellyfin/jellyfin-web
synced 2025-03-30 19:56:21 +00:00
Add reusable component
This commit is contained in:
parent
c3b5d50313
commit
cc87ba3859
16 changed files with 512 additions and 5 deletions
56
src/components/common/DefaultIconText.tsx
Normal file
56
src/components/common/DefaultIconText.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { BaseItemKind } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC } from 'react';
|
||||
import Icon from '@mui/material/Icon';
|
||||
import imageHelper from 'utils/image';
|
||||
import DefaultName from './DefaultName';
|
||||
import type { ItemDto } from 'types/itemDto';
|
||||
|
||||
interface DefaultIconTextProps {
|
||||
item: ItemDto;
|
||||
defaultCardImageIcon?: string;
|
||||
}
|
||||
|
||||
const DefaultIconText: FC<DefaultIconTextProps> = ({
|
||||
item,
|
||||
defaultCardImageIcon
|
||||
}) => {
|
||||
if (item.CollectionType) {
|
||||
return (
|
||||
<Icon
|
||||
className='cardImageIcon'
|
||||
sx={{ color: 'inherit', fontSize: '5em' }}
|
||||
aria-hidden='true'
|
||||
>
|
||||
{imageHelper.getLibraryIcon(item.CollectionType)}
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
if (item.Type && !(item.Type === BaseItemKind.TvChannel || item.Type === BaseItemKind.Studio )) {
|
||||
return (
|
||||
<Icon
|
||||
className='cardImageIcon'
|
||||
sx={{ color: 'inherit', fontSize: '5em' }}
|
||||
aria-hidden='true'
|
||||
>
|
||||
{imageHelper.getItemTypeIcon(item.Type)}
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
if (defaultCardImageIcon) {
|
||||
return (
|
||||
<Icon
|
||||
className='cardImageIcon'
|
||||
sx={{ color: 'inherit', fontSize: '5em' }}
|
||||
aria-hidden='true'
|
||||
>
|
||||
{defaultCardImageIcon}
|
||||
</Icon>
|
||||
);
|
||||
}
|
||||
|
||||
return <DefaultName item={item} />;
|
||||
};
|
||||
|
||||
export default DefaultIconText;
|
23
src/components/common/DefaultName.tsx
Normal file
23
src/components/common/DefaultName.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React, { FC } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import escapeHTML from 'escape-html';
|
||||
import itemHelper from 'components/itemHelper';
|
||||
import { isUsingLiveTvNaming } from '../cardbuilder/cardBuilderUtils';
|
||||
import type { ItemDto } from 'types/itemDto';
|
||||
|
||||
interface DefaultNameProps {
|
||||
item: ItemDto;
|
||||
}
|
||||
|
||||
const DefaultName: FC<DefaultNameProps> = ({ item }) => {
|
||||
const defaultName = isUsingLiveTvNaming(item.Type) ?
|
||||
item.Name :
|
||||
itemHelper.getDisplayName(item);
|
||||
return (
|
||||
<Box className='cardText cardDefaultText'>
|
||||
{escapeHTML(defaultName)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DefaultName;
|
67
src/components/common/Image.tsx
Normal file
67
src/components/common/Image.tsx
Normal file
|
@ -0,0 +1,67 @@
|
|||
import React, { FC, useCallback, useState } from 'react';
|
||||
import { BlurhashCanvas } from 'react-blurhash';
|
||||
import { LazyLoadImage } from 'react-lazy-load-image-component';
|
||||
|
||||
const imageStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
zIndex: 0
|
||||
};
|
||||
|
||||
interface ImageProps {
|
||||
imgUrl: string;
|
||||
blurhash?: string;
|
||||
containImage: boolean;
|
||||
}
|
||||
|
||||
const Image: FC<ImageProps> = ({
|
||||
imgUrl,
|
||||
blurhash,
|
||||
containImage
|
||||
}) => {
|
||||
const [isLoaded, setIsLoaded] = useState(false);
|
||||
const [isLoadStarted, setIsLoadStarted] = useState(false);
|
||||
const handleLoad = useCallback(() => {
|
||||
setIsLoaded(true);
|
||||
}, []);
|
||||
|
||||
const handleLoadStarted = useCallback(() => {
|
||||
setIsLoadStarted(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{!isLoaded && isLoadStarted && blurhash && (
|
||||
<BlurhashCanvas
|
||||
hash={blurhash}
|
||||
width= {20}
|
||||
height={20}
|
||||
punch={1}
|
||||
style={{
|
||||
...imageStyle,
|
||||
borderRadius: '0.2em',
|
||||
pointerEvents: 'none'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<LazyLoadImage
|
||||
key={imgUrl}
|
||||
src={imgUrl}
|
||||
style={{
|
||||
...imageStyle,
|
||||
objectFit: containImage ? 'contain' : 'cover'
|
||||
}}
|
||||
onLoad={handleLoad}
|
||||
beforeLoad={handleLoadStarted}
|
||||
/>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Image;
|
22
src/components/common/InfoIconButton.tsx
Normal file
22
src/components/common/InfoIconButton.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React, { FC } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import InfoIcon from '@mui/icons-material/Info';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
interface InfoIconButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const InfoIconButton: FC<InfoIconButtonProps> = ({ className }) => {
|
||||
return (
|
||||
<IconButton
|
||||
className={className}
|
||||
data-action='link'
|
||||
title={globalize.translate('ButtonInfo')}
|
||||
>
|
||||
<InfoIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default InfoIconButton;
|
36
src/components/common/Media.tsx
Normal file
36
src/components/common/Media.tsx
Normal file
|
@ -0,0 +1,36 @@
|
|||
import { BaseItemKind, ImageType } from '@jellyfin/sdk/lib/generated-client';
|
||||
import React, { FC } from 'react';
|
||||
import Image from './Image';
|
||||
import DefaultIconText from './DefaultIconText';
|
||||
import type { ItemDto } from 'types/itemDto';
|
||||
|
||||
interface MediaProps {
|
||||
item: ItemDto;
|
||||
imgUrl: string | undefined;
|
||||
blurhash: string | undefined;
|
||||
imageType?: ImageType
|
||||
defaultCardImageIcon?: string
|
||||
}
|
||||
|
||||
const Media: FC<MediaProps> = ({
|
||||
item,
|
||||
imgUrl,
|
||||
blurhash,
|
||||
imageType,
|
||||
defaultCardImageIcon
|
||||
}) => {
|
||||
return imgUrl ? (
|
||||
<Image
|
||||
imgUrl={imgUrl}
|
||||
blurhash={blurhash}
|
||||
containImage={item.Type === BaseItemKind.TvChannel || imageType === ImageType.Logo}
|
||||
/>
|
||||
) : (
|
||||
<DefaultIconText
|
||||
item={item}
|
||||
defaultCardImageIcon={defaultCardImageIcon}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Media;
|
23
src/components/common/MoreVertIconButton.tsx
Normal file
23
src/components/common/MoreVertIconButton.tsx
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React, { FC } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import MoreVertIcon from '@mui/icons-material/MoreVert';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
interface MoreVertIconButtonProps {
|
||||
className?: string;
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
const MoreVertIconButton: FC<MoreVertIconButtonProps> = ({ className, iconClassName }) => {
|
||||
return (
|
||||
<IconButton
|
||||
className={className}
|
||||
data-action='menu'
|
||||
title={globalize.translate('ButtonMore')}
|
||||
>
|
||||
<MoreVertIcon className={iconClassName} />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default MoreVertIconButton;
|
25
src/components/common/NoItemsMessage.tsx
Normal file
25
src/components/common/NoItemsMessage.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { FC } from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
interface NoItemsMessageProps {
|
||||
noItemsMessage?: string;
|
||||
}
|
||||
|
||||
const NoItemsMessage: FC<NoItemsMessageProps> = ({
|
||||
noItemsMessage = 'MessageNoItemsAvailable'
|
||||
}) => {
|
||||
return (
|
||||
<Box className='noItemsMessage centerMessage'>
|
||||
<Typography variant='h2'>
|
||||
{globalize.translate('MessageNothingHere')}
|
||||
</Typography>
|
||||
<Typography paragraph variant='h2'>
|
||||
{globalize.translate(noItemsMessage)}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NoItemsMessage;
|
25
src/components/common/PlayArrowIconButton.tsx
Normal file
25
src/components/common/PlayArrowIconButton.tsx
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React, { FC } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import PlayArrowIcon from '@mui/icons-material/PlayArrow';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
interface PlayArrowIconButtonProps {
|
||||
className: string;
|
||||
action: string;
|
||||
title: string;
|
||||
iconClassName?: string;
|
||||
}
|
||||
|
||||
const PlayArrowIconButton: FC<PlayArrowIconButtonProps> = ({ className, action, title, iconClassName }) => {
|
||||
return (
|
||||
<IconButton
|
||||
className={className}
|
||||
data-action={action}
|
||||
title={globalize.translate(title)}
|
||||
>
|
||||
<PlayArrowIcon className={iconClassName} />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlayArrowIconButton;
|
22
src/components/common/PlaylistAddIconButton.tsx
Normal file
22
src/components/common/PlaylistAddIconButton.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
import React, { FC } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
import PlaylistAddIcon from '@mui/icons-material/PlaylistAdd';
|
||||
import globalize from 'scripts/globalize';
|
||||
|
||||
interface PlaylistAddIconButtonProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const PlaylistAddIconButton: FC<PlaylistAddIconButtonProps> = ({ className }) => {
|
||||
return (
|
||||
<IconButton
|
||||
className={className}
|
||||
data-action='addtoplaylist'
|
||||
title={globalize.translate('AddToPlaylist')}
|
||||
>
|
||||
<PlaylistAddIcon />
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlaylistAddIconButton;
|
24
src/components/common/RightIconButtons.tsx
Normal file
24
src/components/common/RightIconButtons.tsx
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React, { FC } from 'react';
|
||||
import IconButton from '@mui/material/IconButton';
|
||||
|
||||
interface RightIconButtonsProps {
|
||||
className?: string;
|
||||
id: string;
|
||||
icon: string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
const RightIconButtons: FC<RightIconButtonsProps> = ({ className, id, title, icon }) => {
|
||||
return (
|
||||
<IconButton
|
||||
className={className}
|
||||
data-action='custom'
|
||||
data-customaction={id}
|
||||
title={title}
|
||||
>
|
||||
{icon}
|
||||
</IconButton>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightIconButtons;
|
Loading…
Add table
Add a link
Reference in a new issue