first commit
Some checks failed
Build / run (push) Has been cancelled

This commit is contained in:
maher
2025-10-29 11:42:25 +01:00
commit 703f50a09d
4595 changed files with 385164 additions and 0 deletions

View File

@@ -0,0 +1,140 @@
import {useTrans} from '@common/i18n/use-trans';
import {message} from '@common/i18n/message';
import clsx from 'clsx';
import {Title} from '@app/titles/models/title';
import {
ImageSize,
useImageSrc,
useImageSrcSet,
} from '@app/images/use-image-src';
import {Episode} from '@app/titles/models/episode';
import {TitleLink} from '@app/titles/title-link';
import {EpisodeLink} from '@app/episodes/episode-link';
import {IconButton} from '@common/ui/buttons/icon-button';
import {Link} from 'react-router-dom';
import {getWatchLink} from '@app/videos/watch-page/get-watch-link';
import {MediaPlayIcon} from '@common/icons/media/media-play';
import {MovieIcon} from '@common/icons/material/Movie';
// can provide either url for backdrop directly or
// title/episode object if main backdrop for it should be used
interface Props {
src?: string;
title?: Title;
episode?: Episode;
className?: string;
size?: string;
lazy?: boolean;
srcSize?: ImageSize;
wrapWithLink?: boolean;
showPlayButton?: boolean;
wrapperClassName?: string;
}
export function TitleBackdrop({
src: initialSrc,
title,
episode,
className,
size,
srcSize,
lazy = true,
wrapWithLink = false,
showPlayButton,
wrapperClassName,
}: Props) {
const {trans} = useTrans();
const primaryVideo = episode?.primary_video || title?.primary_video;
if (!primaryVideo) {
showPlayButton = false;
}
if (!initialSrc && episode) {
initialSrc = episode?.poster;
}
if (!initialSrc && title) {
initialSrc = title.backdrop;
}
const src = useImageSrc(initialSrc, {size: srcSize});
const item = episode || title;
const srcset = useImageSrcSet(initialSrc);
const imageClassName = clsx(
className,
size,
'aspect-video bg-fg-base/4 object-cover',
!src ? 'flex items-center justify-center' : 'block',
);
let img = src ? (
<img
className={imageClassName}
draggable={false}
decoding="async"
sizes={!srcSize ? `100vw` : undefined}
loading={lazy ? 'lazy' : 'eager'}
src={src}
srcSet={!srcSize ? srcset : undefined}
alt={
item
? trans(
message('Backdrop for :name', {
values: {name: item.name},
}),
)
: ''
}
/>
) : (
<span className={imageClassName}>
<MovieIcon className="max-w-[60%] text-divider" size="text-6xl" />
</span>
);
const playButton = showPlayButton ? (
<div className="absolute bottom-14 left-14">
<IconButton
color="white"
variant="flat"
className="shadow-md"
radius="rounded-full"
elementType={Link}
to={getWatchLink(primaryVideo!)}
aria-label="Play"
>
<MediaPlayIcon />
</IconButton>
</div>
) : null;
if (wrapWithLink) {
if (episode) {
img = (
<EpisodeLink
episode={episode}
title={title!}
seasonNumber={episode.season_number}
displayContents
>
{img}
</EpisodeLink>
);
} else if (title) {
img = (
<TitleLink title={title} displayContents>
{img}
</TitleLink>
);
}
}
return (
<div className={clsx('group relative flex-shrink-0', wrapperClassName)}>
{img}
{playButton}
{wrapWithLink && (
<div className="pointer-events-none absolute inset-0 bg-black opacity-0 transition-opacity group-hover:opacity-10" />
)}
</div>
);
}

View File

@@ -0,0 +1,97 @@
import {useTrans} from '@common/i18n/use-trans';
import {message} from '@common/i18n/message';
import clsx from 'clsx';
import {Title} from '@app/titles/models/title';
import {TitleLink} from '@app/titles/title-link';
import {ImageSize, useImageSrc} from '@app/images/use-image-src';
import {Link} from 'react-router-dom';
import {getWatchLink} from '@app/videos/watch-page/get-watch-link';
import {MediaPlayIcon} from '@common/icons/media/media-play';
import {IconButton} from '@common/ui/buttons/icon-button';
import {Fragment} from 'react';
import {MovieIcon} from '@common/icons/material/Movie';
interface Props {
title: Title;
className?: string;
size?: string;
lazy?: boolean;
srcSize?: ImageSize;
aspect?: string;
showPlayButton?: boolean;
link?: string;
}
export function TitlePoster({
title,
className,
size = 'w-full',
srcSize,
lazy = true,
aspect = 'aspect-poster',
showPlayButton,
link,
}: Props) {
const {trans} = useTrans();
const src = useImageSrc(title?.poster, {size: srcSize});
if (!title.primary_video) {
showPlayButton = false;
}
const imageClassName = clsx(
'h-full w-full rounded bg-fg-base/4 object-cover',
!src ? 'flex items-center justify-center' : 'block',
);
const image = src ? (
<img
className={imageClassName}
decoding="async"
draggable={false}
loading={lazy ? 'lazy' : 'eager'}
src={src}
alt={trans(message('Poster for :name', {values: {name: title.name}}))}
/>
) : (
<span className={clsx(imageClassName, 'overflow-hidden')}>
<MovieIcon className="max-w-[60%] text-divider" size="text-6xl" />
</span>
);
const linkChildren = (
<Fragment>
{image}
<span className="pointer-events-none absolute inset-0 block bg-black opacity-0 transition-opacity group-hover:opacity-10" />
</Fragment>
);
return (
<div
className={clsx(size, aspect, className, 'group relative flex-shrink-0')}
>
{link ? (
<Link to={link} className="contents">
{linkChildren}
</Link>
) : (
<TitleLink title={title} displayContents>
{linkChildren}
</TitleLink>
)}
{showPlayButton ? (
<div className="absolute bottom-14 left-14">
<IconButton
color="white"
variant="flat"
className="shadow-md"
radius="rounded-full"
elementType={Link}
to={getWatchLink(title.primary_video)}
aria-label={`Play ${title.name}`}
>
<MediaPlayIcon />
</IconButton>
</div>
) : null}
</div>
);
}