2025-01-06 21:08:51 +07:00
|
|
|
import { Link, useNavigate, useParams } from 'react-router-dom';
|
|
|
|
import loadVideoById from '../api/loader/loadVideoById';
|
|
|
|
import { Fragment, useEffect, useState } from 'react';
|
|
|
|
import { ConfigType, VideoType } from './Home';
|
|
|
|
import VideoPlayer from '../components/VideoPlayer';
|
|
|
|
import iconEye from '/img/icon-eye.svg';
|
|
|
|
import iconThumb from '/img/icon-thumb.svg';
|
|
|
|
import iconStarFull from '/img/icon-star-full.svg';
|
|
|
|
import iconStarEmpty from '/img/icon-star-empty.svg';
|
|
|
|
import iconStarHalf from '/img/icon-star-half.svg';
|
|
|
|
import iconClose from '/img/icon-close.svg';
|
|
|
|
import iconUnseen from '/img/icon-unseen.svg';
|
|
|
|
import iconSeen from '/img/icon-seen.svg';
|
|
|
|
import Routes from '../configuration/routes/RouteList';
|
|
|
|
import Linkify from '../components/Linkify';
|
|
|
|
import loadSimmilarVideosById from '../api/loader/loadSimmilarVideosById';
|
|
|
|
import VideoList from '../components/VideoList';
|
|
|
|
import updateWatchedState from '../api/actions/updateWatchedState';
|
|
|
|
import humanFileSize from '../functions/humanFileSize';
|
|
|
|
import ScrollToTopOnNavigate from '../components/ScrollToTop';
|
|
|
|
import ChannelOverview from '../components/ChannelOverview';
|
|
|
|
import deleteVideo from '../api/actions/deleteVideo';
|
|
|
|
import capitalizeFirstLetter from '../functions/capitalizeFirstLetter';
|
|
|
|
import formatDate from '../functions/formatDates';
|
|
|
|
import formatNumbers from '../functions/formatNumbers';
|
|
|
|
import queueReindex from '../api/actions/queueReindex';
|
|
|
|
import GoogleCast from '../components/GoogleCast';
|
|
|
|
import WatchedCheckBox from '../components/WatchedCheckBox';
|
|
|
|
import convertStarRating from '../functions/convertStarRating';
|
|
|
|
import loadPlaylistList from '../api/loader/loadPlaylistList';
|
|
|
|
import { PlaylistsResponseType } from './Playlists';
|
|
|
|
import PaginationDummy from '../components/PaginationDummy';
|
|
|
|
import updateCustomPlaylist from '../api/actions/updateCustomPlaylist';
|
|
|
|
import { PlaylistType } from './Playlist';
|
|
|
|
import loadCommentsbyVideoId from '../api/loader/loadCommentsbyVideoId';
|
|
|
|
import CommentBox, { CommentsType } from '../components/CommentBox';
|
|
|
|
import Button from '../components/Button';
|
|
|
|
import getApiUrl from '../configuration/getApiUrl';
|
|
|
|
import loadVideoNav, { VideoNavResponseType } from '../api/loader/loadVideoNav';
|
|
|
|
import loadIsAdmin from '../functions/getIsAdmin';
|
|
|
|
|
|
|
|
const isInPlaylist = (videoId: string, playlist: PlaylistType) => {
|
|
|
|
return playlist.playlist_entries.some(entry => {
|
|
|
|
return entry.youtube_id === videoId;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
type VideoParams = {
|
|
|
|
videoId: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
type PlaylistNavPreviousItemType = {
|
|
|
|
youtube_id: string;
|
|
|
|
vid_thumb: string;
|
|
|
|
idx: number;
|
|
|
|
title: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
type PlaylistNavNextItemType = {
|
|
|
|
youtube_id: string;
|
|
|
|
vid_thumb: string;
|
|
|
|
idx: number;
|
|
|
|
title: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
type PlaylistNavItemType = {
|
|
|
|
playlist_meta: {
|
|
|
|
current_idx: string;
|
|
|
|
playlist_id: string;
|
|
|
|
playlist_name: string;
|
|
|
|
playlist_channel: string;
|
|
|
|
};
|
|
|
|
playlist_previous: PlaylistNavPreviousItemType;
|
|
|
|
playlist_next: PlaylistNavNextItemType;
|
|
|
|
};
|
|
|
|
|
|
|
|
type PlaylistNavType = PlaylistNavItemType[];
|
|
|
|
|
|
|
|
export type SponsorBlockSegmentType = {
|
|
|
|
category: string;
|
|
|
|
actionType: string;
|
|
|
|
segment: number[];
|
|
|
|
UUID: string;
|
|
|
|
videoDuration: number;
|
|
|
|
locked: number;
|
|
|
|
votes: number;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type SponsorBlockType = {
|
|
|
|
last_refresh: number;
|
|
|
|
has_unlocked: boolean;
|
|
|
|
is_enabled: boolean;
|
|
|
|
segments: SponsorBlockSegmentType[];
|
|
|
|
message?: string;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type SimilarVideosResponseType = {
|
|
|
|
data: VideoType[];
|
|
|
|
config: ConfigType;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type VideoResponseType = {
|
|
|
|
data: VideoType;
|
|
|
|
config: ConfigType;
|
|
|
|
};
|
|
|
|
|
|
|
|
type CommentsResponseType = {
|
|
|
|
data: CommentsType[];
|
|
|
|
config: ConfigType;
|
|
|
|
};
|
|
|
|
|
|
|
|
export type VideoCommentsResponseType = {
|
|
|
|
data: VideoType;
|
|
|
|
config: ConfigType;
|
|
|
|
playlist_nav: PlaylistNavType;
|
|
|
|
};
|
|
|
|
|
|
|
|
const Video = () => {
|
|
|
|
const { videoId } = useParams() as VideoParams;
|
|
|
|
const navigate = useNavigate();
|
|
|
|
const isAdmin = loadIsAdmin();
|
|
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const [videoEnded, setVideoEnded] = useState(false);
|
|
|
|
const [playlistAutoplay, setPlaylistAutoplay] = useState(
|
|
|
|
localStorage.getItem('playlistAutoplay') === 'true',
|
|
|
|
);
|
|
|
|
const [playlistIdForAutoplay, setPlaylistIDForAutoplay] = useState<string | undefined>(
|
|
|
|
localStorage.getItem('playlistIdForAutoplay') ?? '',
|
|
|
|
);
|
|
|
|
const [descriptionExpanded, setDescriptionExpanded] = useState(false);
|
|
|
|
const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
|
|
|
|
const [showAddToPlaylist, setShowAddToPlaylist] = useState(false);
|
|
|
|
const [refreshVideoList, setRefreshVideoList] = useState(false);
|
|
|
|
const [reindex, setReindex] = useState(false);
|
|
|
|
|
|
|
|
const [videoResponse, setVideoResponse] = useState<VideoResponseType>();
|
|
|
|
const [simmilarVideos, setSimmilarVideos] = useState<SimilarVideosResponseType>();
|
|
|
|
const [videoPlaylistNav, setVideoPlaylistNav] = useState<VideoNavResponseType[]>();
|
|
|
|
const [customPlaylistsResponse, setCustomPlaylistsResponse] = useState<PlaylistsResponseType>();
|
|
|
|
const [commentsResponse, setCommentsResponse] = useState<CommentsResponseType>();
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
(async () => {
|
|
|
|
setLoading(true);
|
|
|
|
|
|
|
|
const videoResponse = await loadVideoById(videoId);
|
|
|
|
const simmilarVideosResponse = await loadSimmilarVideosById(videoId);
|
|
|
|
const customPlaylistsResponse = await loadPlaylistList({ type: 'custom' });
|
|
|
|
const commentsResponse = await loadCommentsbyVideoId(videoId);
|
|
|
|
const videoNavResponse = await loadVideoNav(videoId);
|
|
|
|
|
|
|
|
setVideoResponse(videoResponse);
|
|
|
|
setSimmilarVideos(simmilarVideosResponse);
|
|
|
|
setVideoPlaylistNav(videoNavResponse);
|
|
|
|
setCustomPlaylistsResponse(customPlaylistsResponse);
|
|
|
|
setCommentsResponse(commentsResponse);
|
|
|
|
setRefreshVideoList(false);
|
|
|
|
|
|
|
|
setLoading(false);
|
|
|
|
})();
|
|
|
|
}, [videoId, refreshVideoList]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
localStorage.setItem('playlistAutoplay', playlistAutoplay.toString());
|
|
|
|
|
|
|
|
if (!playlistAutoplay) {
|
|
|
|
localStorage.setItem('playlistIdForAutoplay', '');
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
localStorage.setItem('playlistIdForAutoplay', playlistIdForAutoplay || '');
|
|
|
|
}, [playlistAutoplay, playlistIdForAutoplay]);
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (videoEnded && playlistAutoplay) {
|
|
|
|
const playlist = videoPlaylistNav?.find(playlist => {
|
|
|
|
return playlist.playlist_meta.playlist_id === playlistIdForAutoplay;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (playlist) {
|
|
|
|
const nextYoutubeId = playlist.playlist_next?.youtube_id;
|
|
|
|
|
|
|
|
if (nextYoutubeId) {
|
|
|
|
setVideoEnded(false);
|
|
|
|
navigate(Routes.Video(nextYoutubeId));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [videoEnded, playlistAutoplay]);
|
|
|
|
|
|
|
|
if (videoResponse === undefined) {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const video = videoResponse.data;
|
|
|
|
const watched = videoResponse.data.player.watched;
|
|
|
|
const config = videoResponse.config;
|
|
|
|
const playlistNav = videoPlaylistNav;
|
|
|
|
const sponsorBlock = videoResponse.data.sponsorblock;
|
|
|
|
const customPlaylists = customPlaylistsResponse?.data;
|
|
|
|
const starRating = convertStarRating(video?.stats?.average_rating);
|
|
|
|
const comments = commentsResponse?.data;
|
|
|
|
|
|
|
|
console.log('playlistNav', playlistNav);
|
|
|
|
|
|
|
|
const cast = config.enable_cast;
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
|
|
|
<title>{`TA | ${video.title}`}</title>
|
|
|
|
<ScrollToTopOnNavigate />
|
|
|
|
|
|
|
|
{!loading && (
|
|
|
|
<VideoPlayer
|
|
|
|
video={videoResponse}
|
|
|
|
sponsorBlock={sponsorBlock}
|
|
|
|
autoplay={playlistAutoplay}
|
|
|
|
onVideoEnd={() => {
|
|
|
|
setVideoEnded(true);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<div className="boxed-content">
|
|
|
|
<div className="title-bar">
|
|
|
|
{cast && (
|
|
|
|
<GoogleCast
|
|
|
|
video={video}
|
|
|
|
setRefresh={() => {
|
|
|
|
setRefreshVideoList(true);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
<h1 id="video-title">{video.title}</h1>
|
|
|
|
</div>
|
|
|
|
<div className="info-box info-box-3">
|
|
|
|
<ChannelOverview
|
|
|
|
channelId={video.channel.channel_id}
|
|
|
|
channelname={video.channel.channel_name}
|
|
|
|
channelSubs={video.channel.channel_subs}
|
|
|
|
channelSubscribed={video.channel.channel_subscribed}
|
|
|
|
channelThumbUrl={video.channel.channel_thumb_url}
|
|
|
|
setRefresh={setRefreshVideoList}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<div className="info-box-item">
|
|
|
|
<div>
|
|
|
|
<p>Published: {formatDate(video.published)}</p>
|
|
|
|
<p>Last refreshed: {formatDate(video.vid_last_refresh)}</p>
|
|
|
|
<p className="video-info-watched">
|
|
|
|
Watched:
|
|
|
|
<WatchedCheckBox
|
|
|
|
watched={watched}
|
|
|
|
onClick={async status => {
|
|
|
|
await updateWatchedState({
|
|
|
|
id: videoId,
|
|
|
|
is_watched: status,
|
|
|
|
});
|
|
|
|
}}
|
|
|
|
onDone={() => {
|
|
|
|
setRefreshVideoList(true);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</p>
|
|
|
|
{video.active && (
|
|
|
|
<p>
|
|
|
|
Youtube:{' '}
|
|
|
|
<a href={`https://www.youtube.com/watch?v=${video.youtube_id}`} target="_blank">
|
|
|
|
Active
|
|
|
|
</a>
|
|
|
|
</p>
|
|
|
|
)}
|
|
|
|
{!video.active && <p>Youtube: Deactivated</p>}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="info-box-item">
|
|
|
|
<div>
|
|
|
|
<p className="thumb-icon">
|
|
|
|
<img src={iconEye} alt="views" />: {formatNumbers(video.stats.view_count)}
|
|
|
|
</p>
|
|
|
|
<p className="thumb-icon like">
|
|
|
|
<img src={iconThumb} alt="thumbs-up" />: {formatNumbers(video.stats.like_count)}
|
|
|
|
</p>
|
|
|
|
{video.stats.dislike_count > 0 && (
|
|
|
|
<p className="thumb-icon">
|
|
|
|
<img className="dislike" src={iconThumb} alt="thumbs-down" />:{' '}
|
|
|
|
{video.stats.dislike_count}
|
|
|
|
</p>
|
|
|
|
)}
|
|
|
|
{video?.stats && starRating && (
|
|
|
|
<div className="rating-stars">
|
|
|
|
{starRating?.map?.((star, index) => {
|
|
|
|
if (star === 'full') {
|
|
|
|
return <img key={index} src={iconStarFull} alt={star} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (star === 'half') {
|
|
|
|
return <img key={index} src={iconStarHalf} alt={star} />;
|
|
|
|
}
|
|
|
|
|
|
|
|
return <img key={index} src={iconStarEmpty} alt={star} />;
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="info-box info-box-2">
|
|
|
|
<div className="info-box-item">
|
|
|
|
<div className="button-box">
|
|
|
|
{reindex && <p>Reindex scheduled</p>}
|
|
|
|
{!reindex && (
|
|
|
|
<>
|
|
|
|
{isAdmin && (
|
|
|
|
<div id="reindex-button" className="button-box">
|
|
|
|
<Button
|
|
|
|
label="Reindex"
|
|
|
|
title={`Reindex ${video.title}`}
|
|
|
|
onClick={async () => {
|
|
|
|
await queueReindex(video.youtube_id, 'video');
|
|
|
|
setReindex(true);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
<a download="" href={`${getApiUrl()}${video.media_url}`}>
|
|
|
|
<Button label="Download File" id="download-item" />
|
|
|
|
</a>{' '}
|
|
|
|
{isAdmin && (
|
|
|
|
<>
|
|
|
|
{!showDeleteConfirm && (
|
|
|
|
<Button
|
|
|
|
label="Delete Video"
|
|
|
|
id="delete-item"
|
|
|
|
onClick={() => setShowDeleteConfirm(!showDeleteConfirm)}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{showDeleteConfirm && (
|
|
|
|
<div className="delete-confirm" id="delete-button">
|
|
|
|
<span>Are you sure? </span>
|
|
|
|
<Button
|
|
|
|
label="Delete"
|
|
|
|
className="danger-button"
|
|
|
|
onClick={async () => {
|
|
|
|
await deleteVideo(videoId);
|
|
|
|
navigate(Routes.Channel(video.channel.channel_id));
|
|
|
|
}}
|
|
|
|
/>{' '}
|
|
|
|
<Button
|
|
|
|
label="Cancel"
|
|
|
|
onClick={() => setShowDeleteConfirm(!showDeleteConfirm)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</>
|
|
|
|
)}{' '}
|
|
|
|
{!showAddToPlaylist && (
|
|
|
|
<Button
|
|
|
|
label="Add To Playlist"
|
|
|
|
id={`${video.youtube_id}-button`}
|
|
|
|
data-id={video.youtube_id}
|
|
|
|
data-context="video"
|
|
|
|
onClick={() => {
|
|
|
|
setShowAddToPlaylist(true);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
)}
|
|
|
|
{showAddToPlaylist && (
|
|
|
|
<>
|
|
|
|
<div className="video-popup-menu">
|
|
|
|
<img
|
|
|
|
src={iconClose}
|
|
|
|
className="video-popup-menu-close-button"
|
|
|
|
title="Close menu"
|
|
|
|
onClick={() => {
|
|
|
|
setShowAddToPlaylist(false);
|
|
|
|
}}
|
|
|
|
/>
|
|
|
|
<h3>Add video to...</h3>
|
|
|
|
|
|
|
|
{customPlaylists?.map(playlist => {
|
|
|
|
return (
|
|
|
|
<p
|
|
|
|
onClick={async () => {
|
|
|
|
if (isInPlaylist(videoId, playlist)) {
|
|
|
|
await updateCustomPlaylist('remove', playlist.playlist_id, videoId);
|
|
|
|
} else {
|
|
|
|
await updateCustomPlaylist('create', playlist.playlist_id, videoId);
|
|
|
|
}
|
|
|
|
|
|
|
|
setRefreshVideoList(true);
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
{isInPlaylist(videoId, playlist) && (
|
|
|
|
<img className="p-button" src={iconSeen} />
|
|
|
|
)}
|
|
|
|
|
|
|
|
{!isInPlaylist(videoId, playlist) && (
|
|
|
|
<img className="p-button" src={iconUnseen} />
|
|
|
|
)}
|
|
|
|
|
|
|
|
{playlist.playlist_name}
|
|
|
|
</p>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
|
|
|
|
<p>
|
|
|
|
<Link to={Routes.Playlists}>Create playlist</Link>
|
|
|
|
</p>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="info-box-item">
|
|
|
|
{video.media_size && <p>File size: {humanFileSize(video.media_size)}</p>}
|
|
|
|
|
|
|
|
{video.streams &&
|
|
|
|
video.streams.map(stream => {
|
|
|
|
return (
|
|
|
|
<p key={stream.index}>
|
|
|
|
{capitalizeFirstLetter(stream.type)}: {stream.codec}{' '}
|
|
|
|
{humanFileSize(stream.bitrate)}/s
|
|
|
|
{stream.width && (
|
|
|
|
<>
|
|
|
|
<span className="space-carrot">|</span> {stream.width}x{stream.height}
|
|
|
|
</>
|
|
|
|
)}{' '}
|
|
|
|
</p>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{video.tags && video.tags.length > 0 && (
|
|
|
|
<div className="description-box">
|
|
|
|
<div className="video-tag-box">
|
|
|
|
{video.tags.map(tag => {
|
|
|
|
return (
|
|
|
|
<span key={tag} className="video-tag">
|
|
|
|
{tag}
|
|
|
|
</span>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{video.description && (
|
|
|
|
<div className="description-box">
|
|
|
|
<p
|
|
|
|
id={descriptionExpanded ? 'text-expand-expanded' : 'text-expand'}
|
|
|
|
className="description-text"
|
|
|
|
>
|
|
|
|
<Linkify>{video.description}</Linkify>
|
|
|
|
</p>
|
|
|
|
|
|
|
|
<Button
|
|
|
|
label={descriptionExpanded ? 'Show less' : 'Show more'}
|
|
|
|
id="text-expand-button"
|
|
|
|
onClick={() => setDescriptionExpanded(!descriptionExpanded)}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{playlistNav && (
|
|
|
|
<>
|
|
|
|
{playlistNav.map(playlistItem => {
|
|
|
|
return (
|
|
|
|
<div key={playlistItem.playlist_meta.playlist_id} className="playlist-wrap">
|
|
|
|
<Link to={Routes.Playlist(playlistItem.playlist_meta.playlist_id)}>
|
|
|
|
<h3>
|
|
|
|
Playlist [{playlistItem.playlist_meta.current_idx + 1}
|
|
|
|
]: {playlistItem.playlist_meta.playlist_name}
|
|
|
|
</h3>
|
|
|
|
</Link>
|
|
|
|
|
|
|
|
<div className="toggle">
|
|
|
|
<p>Autoplay:</p>
|
|
|
|
<div className="toggleBox">
|
|
|
|
<input
|
|
|
|
checked={playlistAutoplay}
|
|
|
|
onChange={() => {
|
|
|
|
if (!playlistAutoplay) {
|
|
|
|
setPlaylistIDForAutoplay(playlistItem.playlist_meta.playlist_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
setPlaylistAutoplay(!playlistAutoplay);
|
|
|
|
}}
|
|
|
|
type="checkbox"
|
|
|
|
/>
|
|
|
|
{!playlistAutoplay && (
|
|
|
|
<label htmlFor="" className="ofbtn">
|
|
|
|
Off
|
|
|
|
</label>
|
|
|
|
)}
|
|
|
|
{playlistAutoplay && (
|
|
|
|
<label htmlFor="" className="onbtn">
|
|
|
|
On
|
|
|
|
</label>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div className="playlist-nav">
|
|
|
|
<div className="playlist-nav-item">
|
|
|
|
{playlistItem.playlist_previous && (
|
|
|
|
<>
|
|
|
|
<Link to={Routes.Video(playlistItem.playlist_previous.youtube_id)}>
|
|
|
|
<img
|
|
|
|
src={`${getApiUrl()}/${playlistItem.playlist_previous.vid_thumb}`}
|
|
|
|
alt="previous thumbnail"
|
|
|
|
/>
|
|
|
|
</Link>
|
|
|
|
<div className="playlist-desc">
|
|
|
|
<p>Previous:</p>
|
|
|
|
<Link to={Routes.Video(playlistItem.playlist_previous.youtube_id)}>
|
|
|
|
<h3>
|
|
|
|
[{playlistItem.playlist_previous.idx + 1}]{' '}
|
|
|
|
{playlistItem.playlist_previous.title}
|
|
|
|
</h3>
|
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
<div className="playlist-nav-item">
|
|
|
|
{playlistItem.playlist_next && (
|
|
|
|
<>
|
|
|
|
<div className="playlist-desc">
|
|
|
|
<p>Next:</p>
|
|
|
|
<Link to={Routes.Video(playlistItem.playlist_next.youtube_id)}>
|
|
|
|
<h3>
|
|
|
|
[{playlistItem.playlist_next.idx + 1}]{' '}
|
|
|
|
{playlistItem.playlist_next.title}
|
|
|
|
</h3>
|
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
<Link to={Routes.Video(playlistItem.playlist_next.youtube_id)}>
|
|
|
|
<img
|
|
|
|
src={`${getApiUrl()}/${playlistItem.playlist_next.vid_thumb}`}
|
|
|
|
alt="previous thumbnail"
|
|
|
|
/>
|
|
|
|
</Link>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<div className="description-box">
|
|
|
|
<h3>Similar Videos</h3>
|
|
|
|
<div className="video-list grid grid-3" id="similar-videos">
|
|
|
|
<VideoList
|
|
|
|
videoList={simmilarVideos?.data}
|
|
|
|
viewLayout="grid"
|
|
|
|
refreshVideoList={setRefreshVideoList}
|
|
|
|
/>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{video.comment_count == 0 && (
|
|
|
|
<div className="comments-section">
|
|
|
|
<span>Video has no comments</span>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{video.comment_count && (
|
|
|
|
<div className="comments-section">
|
|
|
|
<h3>Comments: {video.comment_count}</h3>
|
|
|
|
<div id="comments-list" className="comments-list">
|
|
|
|
{comments?.map(comment => {
|
|
|
|
return (
|
|
|
|
<Fragment key={comment.comment_id}>
|
|
|
|
<CommentBox comment={comment} />
|
|
|
|
</Fragment>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
<div className="boxed-content-empty" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<PaginationDummy />
|
|
|
|
</>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
export default Video;
|