168 lines
5.5 KiB
TypeScript
Executable File
168 lines
5.5 KiB
TypeScript
Executable File
import React, {Fragment, useState} from 'react';
|
|
import {MainNavbar} from '@app/main-navbar';
|
|
import {useDarkThemeVariables} from '@common/ui/themes/use-dark-theme-variables';
|
|
import {
|
|
useWatchPageVideo,
|
|
UseWatchPageVideoResponse,
|
|
} from '@app/videos/requests/use-watch-page-video';
|
|
import {Footer} from '@common/ui/footer/footer';
|
|
import {PageErrorMessage} from '@common/errors/page-error-message';
|
|
import {CommentList} from '@common/comments/comment-list/comment-list';
|
|
import {NewCommentForm} from '@common/comments/new-comment-form';
|
|
import {WatchPageTitleDetails} from '@app/videos/watch-page/watch-page-title-details';
|
|
import {WatchPageAside} from '@app/videos/watch-page/watch-page-aside';
|
|
import {AnimatePresence, m} from 'framer-motion';
|
|
import {opacityAnimation} from '@common/ui/animation/opacity-animation';
|
|
import {useScrollToTop} from '@common/ui/navigation/use-scroll-to-top';
|
|
import {VideoPlayerSkeleton} from '@app/videos/video-player-skeleton';
|
|
import {SiteVideoPlayer} from '@app/videos/site-video-player';
|
|
import {useSettings} from '@common/core/settings/use-settings';
|
|
import {useAuth} from '@common/auth/use-auth';
|
|
import {useIsStreamingMode} from '@app/videos/use-is-streaming-mode';
|
|
import {WatchPageAlternativeVideos} from '@app/videos/watch-page/watch-page-alternative-videos';
|
|
import {AdHost} from '@common/admin/ads/ad-host';
|
|
import {Episode} from '@app/titles/models/episode';
|
|
import {Title} from '@app/titles/models/title';
|
|
import {Video} from '@app/titles/models/video';
|
|
import {PageMetaTags} from '@common/http/page-meta-tags';
|
|
import {useLayoutEffect} from '@react-aria/utils';
|
|
import {VideoThumbnail} from '@app/videos/video-thumbnail';
|
|
import {Trans} from '@common/i18n/trans';
|
|
import {Button} from '@common/ui/buttons/button';
|
|
import {Link} from 'react-router-dom';
|
|
|
|
export function WatchPage() {
|
|
const darkThemeVars = useDarkThemeVariables();
|
|
useScrollToTop();
|
|
|
|
return (
|
|
<Fragment>
|
|
<MainNavbar />
|
|
<div style={darkThemeVars} className="dark min-h-screen bg text">
|
|
<div className="container mx-auto p-14 md:p-24">
|
|
<Content />
|
|
<Footer className="mt-48" />
|
|
</div>
|
|
</div>
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
function Content() {
|
|
const {titles, comments} = useSettings();
|
|
const {isLoggedIn, hasPermission} = useAuth();
|
|
const query = useWatchPageVideo();
|
|
const {data, isLoading} = query;
|
|
const title = data?.title;
|
|
const episode = data?.episode;
|
|
const video = data?.video;
|
|
let commentable: Episode | Title | Video | undefined = video;
|
|
|
|
if (!comments?.per_video) {
|
|
commentable = episode || title;
|
|
}
|
|
|
|
const shouldShowComments =
|
|
title && video && titles.enable_comments && hasPermission('comments.view');
|
|
|
|
if (data || isLoading) {
|
|
return (
|
|
<Fragment key={video?.id || 'loading'}>
|
|
<PageMetaTags query={query} />
|
|
<VideoWrapper data={data} />
|
|
<WatchPageAlternativeVideos data={data} />
|
|
<AdHost slot="watch_top" className="pt-48" />
|
|
<section className="mt-42 items-start gap-56 lg:flex">
|
|
<div className="flex-auto">
|
|
<WatchPageTitleDetails />
|
|
{shouldShowComments && (
|
|
<CommentList
|
|
commentable={commentable!}
|
|
className="mt-44"
|
|
perPage={20}
|
|
>
|
|
{isLoggedIn && hasPermission('comments.create') && (
|
|
<NewCommentForm
|
|
commentable={commentable!}
|
|
className="mb-14 mt-24"
|
|
/>
|
|
)}
|
|
</CommentList>
|
|
)}
|
|
</div>
|
|
<WatchPageAside />
|
|
</section>
|
|
</Fragment>
|
|
);
|
|
}
|
|
|
|
return <PageErrorMessage />;
|
|
}
|
|
|
|
interface VideoWrapperProps {
|
|
data?: UseWatchPageVideoResponse;
|
|
}
|
|
function VideoWrapper({data}: VideoWrapperProps) {
|
|
const isStreamingMode = useIsStreamingMode();
|
|
const {hasPermission} = useAuth();
|
|
|
|
const [isVisible, setIsVisible] = useState(false);
|
|
useLayoutEffect(() => {
|
|
setIsVisible(true);
|
|
}, []);
|
|
|
|
return (
|
|
<AnimatePresence initial={false} mode="wait">
|
|
{data?.video && isVisible ? (
|
|
<m.div key="player" {...opacityAnimation}>
|
|
{hasPermission('videos.play') ? (
|
|
<SiteVideoPlayer
|
|
title={data.title}
|
|
episode={data.episode}
|
|
video={data.video}
|
|
relatedVideos={data.related_videos}
|
|
autoPlay
|
|
logPlays
|
|
showEpisodeSelector={isStreamingMode}
|
|
/>
|
|
) : (
|
|
<UpgradeMessage video={data.video} />
|
|
)}
|
|
</m.div>
|
|
) : (
|
|
<m.div className="relative" key="skeleton" {...opacityAnimation}>
|
|
<VideoPlayerSkeleton animate />
|
|
</m.div>
|
|
)}
|
|
</AnimatePresence>
|
|
);
|
|
}
|
|
|
|
interface UpgradeMessageProps {
|
|
video: Video;
|
|
}
|
|
function UpgradeMessage({video}: UpgradeMessageProps) {
|
|
return (
|
|
<div className="relative flex aspect-video items-center justify-center bg-alt">
|
|
<div className="blur">
|
|
<VideoThumbnail video={video} />
|
|
</div>
|
|
<div className="absolute h-max w-max max-w-full rounded-lg bg-black/60 p-24 text-lg font-medium">
|
|
<div>
|
|
<Trans message="Your current plan does not allow watching videos. Upgrade to unlock this feature." />
|
|
</div>
|
|
<div className="mt-14 text-center">
|
|
<Button
|
|
variant="flat"
|
|
color="primary"
|
|
elementType={Link}
|
|
to="/pricing"
|
|
>
|
|
<Trans message="Upgrade" />
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|