163
resources/client/videos/watch-page/watch-page-title-details.tsx
Executable file
163
resources/client/videos/watch-page/watch-page-title-details.tsx
Executable file
@@ -0,0 +1,163 @@
|
||||
import {useWatchPageVideo} from '@app/videos/requests/use-watch-page-video';
|
||||
import {TitlePoster} from '@app/titles/title-poster/title-poster';
|
||||
import {TitleLink} from '@app/titles/title-link';
|
||||
import React, {Fragment, ReactNode, useState} from 'react';
|
||||
import {Skeleton} from '@common/ui/skeleton/skeleton';
|
||||
import {AnimatePresence, m} from 'framer-motion';
|
||||
import {opacityAnimation} from '@common/ui/animation/opacity-animation';
|
||||
import {ThumbButtons} from '@common/votes/thumb-buttons';
|
||||
import {IconButton} from '@common/ui/buttons/icon-button';
|
||||
import {ShareIcon} from '@common/icons/material/Share';
|
||||
import {FlagIcon} from '@common/icons/material/Flag';
|
||||
import {Tooltip} from '@common/ui/tooltip/tooltip';
|
||||
import {Trans} from '@common/i18n/trans';
|
||||
import {useSubmitReport} from '@common/reports/requests/use-submit-report';
|
||||
import {useDeleteReport} from '@common/reports/requests/use-delete-report';
|
||||
import {Video} from '@app/titles/models/video';
|
||||
import {useIsStreamingMode} from '@app/videos/use-is-streaming-mode';
|
||||
import {EpisodeLink} from '@app/episodes/episode-link';
|
||||
import {CompactSeasonEpisode} from '@app/episodes/compact-season-episode';
|
||||
import {ShareMenuTrigger} from '@app/sharing/share-menu-trigger';
|
||||
import {getWatchLink} from '@app/videos/watch-page/get-watch-link';
|
||||
|
||||
export function WatchPageTitleDetails() {
|
||||
const {data} = useWatchPageVideo();
|
||||
const isStreamingMode = useIsStreamingMode();
|
||||
|
||||
const content = !data ? (
|
||||
<Layout
|
||||
key="skeleton"
|
||||
poster={<Skeleton variant="rect" size="w-132 aspect-poster" />}
|
||||
titleLink={<Skeleton className="max-w-144" />}
|
||||
videoName={<Skeleton className="max-w-240" />}
|
||||
description={
|
||||
<Fragment>
|
||||
<Skeleton />
|
||||
<Skeleton />
|
||||
</Fragment>
|
||||
}
|
||||
rate={
|
||||
<div className="flex h-32 items-center gap-2">
|
||||
<Skeleton variant="rect" size="w-56 h-24" className="mr-10" />
|
||||
<Skeleton variant="rect" size="w-56 h-24" />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<Layout
|
||||
key="loaded"
|
||||
poster={
|
||||
<TitlePoster
|
||||
size="w-132"
|
||||
srcSize="md"
|
||||
title={data.title}
|
||||
showPlayButton
|
||||
className="max-md:hidden"
|
||||
/>
|
||||
}
|
||||
titleLink={<TitleLink title={data.title} />}
|
||||
videoName={!isStreamingMode ? data.video.name : undefined}
|
||||
episodeName={
|
||||
data.episode ? (
|
||||
<EpisodeLink title={data.title} episode={data.episode}>
|
||||
{data.episode.name} (<CompactSeasonEpisode episode={data.episode} />
|
||||
)
|
||||
</EpisodeLink>
|
||||
) : undefined
|
||||
}
|
||||
description={data.episode?.description || data.title.description}
|
||||
rate={
|
||||
<div className="flex items-center gap-2">
|
||||
<ThumbButtons model={data.video} className="mr-auto" />
|
||||
<ReportButton video={data.video} />
|
||||
<ShareButton video={data.video} />
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatePresence initial={false} mode="wait">
|
||||
{content}
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
||||
|
||||
interface ShareButtonProps {
|
||||
video: Video;
|
||||
}
|
||||
function ShareButton({video}: ShareButtonProps) {
|
||||
const link = getWatchLink(video, {absolute: true});
|
||||
return (
|
||||
<ShareMenuTrigger link={link}>
|
||||
<Tooltip label={<Trans message="Share" />}>
|
||||
<IconButton>
|
||||
<ShareIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</ShareMenuTrigger>
|
||||
);
|
||||
}
|
||||
|
||||
interface ReportButtonProps {
|
||||
video: Video;
|
||||
}
|
||||
function ReportButton({video}: ReportButtonProps) {
|
||||
const report = useSubmitReport(video);
|
||||
const deleteReport = useDeleteReport(video);
|
||||
const [isReported, setIsReported] = useState(video.current_user_reported);
|
||||
|
||||
return (
|
||||
<Tooltip label={<Trans message="Report" />}>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
if (isReported) {
|
||||
deleteReport.mutate();
|
||||
} else {
|
||||
report.mutate({});
|
||||
}
|
||||
setIsReported(!isReported);
|
||||
}}
|
||||
>
|
||||
<FlagIcon />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
|
||||
interface LayoutProps {
|
||||
poster?: ReactNode;
|
||||
titleLink: ReactNode;
|
||||
videoName: ReactNode;
|
||||
episodeName?: ReactNode;
|
||||
description: ReactNode;
|
||||
rate?: ReactNode;
|
||||
}
|
||||
function Layout({
|
||||
poster,
|
||||
titleLink,
|
||||
videoName,
|
||||
episodeName,
|
||||
description,
|
||||
rate,
|
||||
}: LayoutProps) {
|
||||
return (
|
||||
<m.div
|
||||
className="flex items-start gap-16 overflow-hidden rounded pr-6"
|
||||
{...opacityAnimation}
|
||||
>
|
||||
{poster}
|
||||
<div className="flex-auto py-6">
|
||||
<h1 className="mb-6 text-2xl font-medium">{titleLink}</h1>
|
||||
{episodeName && (
|
||||
<div className="text-base font-medium">{episodeName}</div>
|
||||
)}
|
||||
{videoName && <div className="text-base font-medium">{videoName}</div>}
|
||||
<div className="my-12">{rate}</div>
|
||||
{description && (
|
||||
<p className="max-w-780 text-sm text-muted">{description}</p>
|
||||
)}
|
||||
</div>
|
||||
</m.div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user