3
0
mirror of https://github.com/Qortal/q-tube.git synced 2025-02-11 17:55:51 +00:00

Refactor to VideoContent.tsx, PlaylistContent.tsx, and VideoListComponentLevel.tsx to use customhooks. This reuses a lot of code between them.

ChannelActions.tsx and VideoActionsBar.tsx components created to reduce code reuse between VideoContent.tsx, PlaylistContent.tsx, and VideoListComponentLevel.tsx.

If a playlist is empty, comments and buttons no longer appear, the page just shows text explaining the situation.
This commit is contained in:
Qortal Dev 2024-10-10 17:02:08 -06:00
parent d42b512caa
commit d3e35ddbdc
16 changed files with 663 additions and 1029 deletions

View File

@ -20,7 +20,7 @@ import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../../../pages/ContentPages/VideoContent/VideoContent-State.tsx";
} from "../../../pages/ContentPages/VideoContent/VideoContent-State.ts";
import { RootState } from "../../../state/store";
import NotificationsIcon from "@mui/icons-material/Notifications";
import { formatDate } from "../../../utils/time";

View File

@ -35,24 +35,11 @@ export const useFetchVideos = () => {
(state: RootState) => state.video.hashMapVideos
);
const videos = useSelector((state: RootState) => state.video.videos);
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
);
const totalVideosPublished = useSelector(
(state: RootState) => state.global.totalVideosPublished
);
const totalNamesPublished = useSelector(
(state: RootState) => state.global.totalNamesPublished
);
const videosPerNamePublished = useSelector(
(state: RootState) => state.global.videosPerNamePublished
);
const filteredVideos = useSelector(
(state: RootState) => state.video.filteredVideos
);
const videoReducer = useSelector((state: RootState) => state.video);
const checkAndUpdateVideo = React.useCallback(
(video: Video) => {
const existingVideo = hashMapVideos[video.id + "-" + video.user];
@ -71,26 +58,6 @@ export const useFetchVideos = () => {
[hashMapVideos]
);
const getAvatar = React.useCallback(async (author: string) => {
try {
const url = await qortalRequest({
action: "GET_QDN_RESOURCE_URL",
name: author,
service: "THUMBNAIL",
identifier: "qortal_avatar",
});
dispatch(
setUserAvatarHash({
name: author,
url,
})
);
} catch (error) {
console.log(error);
}
}, []);
const getVideo = async (
user: string,
videoId: string,
@ -123,17 +90,6 @@ export const useFetchVideos = () => {
try {
dispatch(setIsLoadingGlobal(true));
// const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&exc
// ludeblocked=true&exactmatchnames=true`;
//
// const response = await fetch(url, {
// method: "GET",
// headers: {
// "Content-Type": "application/json",
// },
// });
// const responseData = await response.json();
const responseData = await qortalRequest({
action: "SEARCH_QDN_RESOURCES",
mode: "ALL",
@ -235,7 +191,7 @@ export const useFetchVideos = () => {
const videoLimit = limit || 20;
let defaultUrl = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`;
if (name) {
defaultUrl = defaultUrl + `&name=${name}`;
} else if (videoListType === "subscriptions") {

View File

@ -0,0 +1,207 @@
import { useDispatch, useSelector } from "react-redux";
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
import { RootState } from "../../../state/store.ts";
import { useVideoContentState } from "../VideoContent/VideoContent-State.ts";
import { useCallback, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
export const usePlaylistContentState = () => {
const {
channelName,
id,
videoData,
superLikeList,
getVideoData,
setVideoData,
videoReference,
focusVideo,
videoCover,
containerRef,
theme,
descriptionHeight,
setSuperLikeList,
isExpandedDescription,
setIsExpandedDescription,
contentRef,
descriptionThreshold,
loadingSuperLikes,
} = useVideoContentState();
const userName = useSelector((state: RootState) => state.auth.user?.name);
const [doAutoPlay, setDoAutoPlay] = useState(false);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superLikeList?.reduce((acc, curr) => {
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
return acc + parseFloat(curr.amount);
else return acc;
}, 0);
return totalQort?.toFixed(2);
}, [superLikeList]);
const numberOfSuperlikes = useMemo(() => {
return superLikeList?.length ?? 0;
}, [superLikeList]);
const navigate = useNavigate();
const [playlistData, setPlaylistData] = useState<any>(null);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
const dispatch = useDispatch();
const checkforPlaylist = useCallback(
async (name, id) => {
try {
dispatch(setIsLoadingGlobal(true));
if (!name || !id) return;
const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&identifier=${id}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataSearch = await response.json();
if (responseDataSearch?.length > 0) {
let resourceData = responseDataSearch[0];
resourceData = {
title: resourceData?.metadata?.title,
category: resourceData?.metadata?.category,
categoryName: resourceData?.metadata?.categoryName,
tags: resourceData?.metadata?.tags || [],
description: resourceData?.metadata?.description,
created: resourceData?.created,
updated: resourceData?.updated,
name: resourceData.name,
videoImage: "",
identifier: resourceData.identifier,
service: resourceData.service,
};
const responseData = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: resourceData.name,
service: resourceData.service,
identifier: resourceData.identifier,
});
if (responseData && !responseData.error) {
const combinedData = {
...resourceData,
...responseData,
};
const videos = [];
if (combinedData?.videos) {
for (const vid of combinedData.videos) {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataSearchVid = await response.json();
if (responseDataSearchVid?.length > 0) {
const resourceData2 = responseDataSearchVid[0];
videos.push(resourceData2);
}
}
}
combinedData.videos = videos;
setPlaylistData(combinedData);
if (combinedData?.videos?.length > 0) {
const vid = combinedData?.videos[0];
const fullId = vid ? `${vid.identifier}-${vid.name}` : undefined;
const existingVideo = hashMapVideos[fullId];
if (existingVideo) setVideoData(existingVideo);
else getVideoData(vid?.name, vid?.identifier);
}
}
}
} catch (error) {
console.log(error);
} finally {
dispatch(setIsLoadingGlobal(false));
}
},
[hashMapVideos]
);
useEffect(() => {
if (channelName && id) {
checkforPlaylist(channelName, id);
}
}, [id, channelName]);
const nextVideo = useMemo(() => {
const currentVideoIndex = playlistData?.videos?.findIndex(
item => item?.identifier === videoData?.id
);
if (currentVideoIndex !== -1) {
const nextVideoIndex = currentVideoIndex + 1;
const findVideo = playlistData?.videos[nextVideoIndex] || null;
if (findVideo) {
const id = findVideo?.identifier?.replace("_metadata", "");
return {
...findVideo,
service: "VIDEO",
identifier: id,
jsonId: findVideo?.identifier,
};
}
}
return null;
}, [playlistData, videoData]);
const onEndVideo = useCallback(() => {
const currentVideoIndex = playlistData?.videos?.findIndex(
item => item?.identifier === videoData?.id
);
if (currentVideoIndex !== -1) {
const nextVideoIndex = currentVideoIndex + 1;
const findVideo = playlistData?.videos[nextVideoIndex] || null;
if (findVideo) {
getVideoData(findVideo?.name, findVideo?.identifier);
setDoAutoPlay(true);
}
}
}, [videoData, playlistData]);
return {
channelName,
id,
videoData,
superLikeList,
getVideoData,
setVideoData,
videoReference,
focusVideo,
videoCover,
containerRef,
theme,
descriptionHeight,
nextVideo,
onEndVideo,
doAutoPlay,
playlistData,
navigate,
userName,
numberOfSuperlikes,
calculateAmountSuperlike,
setSuperLikeList,
isExpandedDescription,
setIsExpandedDescription,
contentRef,
descriptionThreshold,
loadingSuperLikes,
};
};

View File

@ -1,433 +1,55 @@
import React, {
useState,
useMemo,
useRef,
useEffect,
useCallback,
} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
import { Avatar, Box, Typography, useTheme } from "@mui/material";
import { Box, Typography } from "@mui/material";
import React from "react";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import { VideoPlayer } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
import { formatDate } from "../../../utils/time.ts";
import { VideoActionsBar } from "../VideoContent/VideoActionsBar.tsx";
import { usePlaylistContentState } from "./PlaylistContent-State.ts";
import {
refType,
VideoPlayer,
VideoStyles,
} from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { RootState } from "../../../state/store.ts";
import { addToHashMap } from "../../../state/features/videoSlice.ts";
import AttachFileIcon from "@mui/icons-material/AttachFile";
import DownloadIcon from "@mui/icons-material/Download";
import mockImg from "../../../test/mockimg.jpg";
import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../VideoContent/VideoContent-State.tsx";
import {
AuthorTextComment,
FileAttachmentContainer,
FileAttachmentFont,
Spacer,
StyledCardColComment,
StyledCardHeaderComment,
VideoDescription,
VideoPlayerContainer,
VideoTitle,
} from "./PlaylistContent-styles.tsx";
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
import {
formatDate,
formatDateSeconds,
formatTimestampSeconds,
} from "../../../utils/time.ts";
import { NavbarName } from "../../../components/layout/Navbar/Navbar-styles.tsx";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import {
CrowdfundSubTitle,
CrowdfundSubTitleRow,
} from "../../../components/Publish/PublishVideo/PublishVideo-styles.tsx";
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import FileElement from "../../../components/common/FileElement.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import {
QTUBE_VIDEO_BASE,
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import { minPriceSuperlike } from "../../../constants/Misc.ts";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
export const PlaylistContent = () => {
const { name: channelName, id } = useParams();
const userName = useSelector((state: RootState) => state.auth.user?.name);
const {
channelName,
id,
videoData,
superLikeList,
getVideoData,
videoReference,
focusVideo,
videoCover,
containerRef,
theme,
descriptionHeight,
nextVideo,
onEndVideo,
doAutoPlay,
playlistData,
setSuperLikeList,
isExpandedDescription,
setIsExpandedDescription,
contentRef,
descriptionThreshold,
loadingSuperLikes,
} = usePlaylistContentState();
const [doAutoPlay, setDoAutoPlay] = useState(false);
const [isExpandedDescription, setIsExpandedDescription] =
useState<boolean>(false);
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
null
);
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const containerRef = useRef<refType>(null);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superlikeList?.reduce((acc, curr) => {
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
return acc + parseFloat(curr.amount);
else return acc;
}, 0);
return totalQort?.toFixed(2);
}, [superlikeList]);
const numberOfSuperlikes = useMemo(() => {
return superlikeList?.length ?? 0;
}, [superlikeList]);
const [nameAddress, setNameAddress] = useState<string>("");
const getAddressName = async name => {
const response = await qortalRequest({
action: "GET_NAME_DATA",
name: name,
});
if (response?.owner) {
setNameAddress(response.owner);
}
};
useEffect(() => {
if (channelName) {
getAddressName(channelName);
}
}, [channelName]);
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
);
const contentRef = useRef(null);
const avatarUrl = useMemo(() => {
let url = "";
if (channelName && userAvatarHash[channelName]) {
url = userAvatarHash[channelName];
}
return url;
}, [userAvatarHash, channelName]);
const navigate = useNavigate();
const theme = useTheme();
const [videoData, setVideoData] = useState<any>(null);
const [playlistData, setPlaylistData] = useState<any>(null);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
const videoReference = useMemo(() => {
if (!videoData) return null;
const { videoReference } = videoData;
if (
videoReference?.identifier &&
videoReference?.name &&
videoReference?.service
) {
return videoReference;
} else {
return null;
}
}, [videoData]);
const videoCover = useMemo(() => {
if (!videoData) return null;
const { videoImage } = videoData;
return videoImage || null;
}, [videoData]);
const dispatch = useDispatch();
const getVideoData = React.useCallback(async (name: string, id: string) => {
try {
if (!name || !id) return;
dispatch(setIsLoadingGlobal(true));
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0&identifier=${id}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataSearch = await response.json();
if (responseDataSearch?.length > 0) {
let resourceData = responseDataSearch[0];
resourceData = {
title: resourceData?.metadata?.title,
category: resourceData?.metadata?.category,
categoryName: resourceData?.metadata?.categoryName,
tags: resourceData?.metadata?.tags || [],
description: resourceData?.metadata?.description,
created: resourceData?.created,
updated: resourceData?.updated,
user: resourceData.name,
videoImage: "",
id: resourceData.identifier,
};
const responseData = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: name,
service: "DOCUMENT",
identifier: id,
});
if (responseData && !responseData.error) {
const combinedData = {
...resourceData,
...responseData,
};
setVideoData(combinedData);
dispatch(addToHashMap(combinedData));
checkforPlaylist(name, id);
}
}
} catch (error) {
console.log(error);
} finally {
dispatch(setIsLoadingGlobal(false));
}
}, []);
const checkforPlaylist = React.useCallback(
async (name, id) => {
try {
dispatch(setIsLoadingGlobal(true));
if (!name || !id) return;
const url = `/arbitrary/resources/search?mode=ALL&service=PLAYLIST&identifier=${id}&limit=1&includemetadata=true&reverse=true&excludeblocked=true&name=${name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataSearch = await response.json();
if (responseDataSearch?.length > 0) {
let resourceData = responseDataSearch[0];
resourceData = {
title: resourceData?.metadata?.title,
category: resourceData?.metadata?.category,
categoryName: resourceData?.metadata?.categoryName,
tags: resourceData?.metadata?.tags || [],
description: resourceData?.metadata?.description,
created: resourceData?.created,
updated: resourceData?.updated,
name: resourceData.name,
videoImage: "",
identifier: resourceData.identifier,
service: resourceData.service,
};
const responseData = await qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: resourceData.name,
service: resourceData.service,
identifier: resourceData.identifier,
});
if (responseData && !responseData.error) {
const combinedData = {
...resourceData,
...responseData,
};
const videos = [];
if (combinedData?.videos) {
for (const vid of combinedData.videos) {
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${vid.identifier}&limit=1&includemetadata=true&reverse=true&name=${vid.name}&exactmatchnames=true&offset=0`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseDataSearchVid = await response.json();
if (responseDataSearchVid?.length > 0) {
const resourceData2 = responseDataSearchVid[0];
videos.push(resourceData2);
}
}
}
combinedData.videos = videos;
setPlaylistData(combinedData);
if (combinedData?.videos?.length > 0) {
const vid = combinedData?.videos[0];
const fullId = vid ? `${vid.identifier}-${vid.name}` : undefined;
const existingVideo = hashMapVideos[fullId];
if (existingVideo) setVideoData(existingVideo);
else getVideoData(vid?.name, vid?.identifier);
}
}
}
} catch (error) {
console.log(error);
} finally {
dispatch(setIsLoadingGlobal(false));
}
},
[hashMapVideos]
);
React.useEffect(() => {
if (channelName && id) {
checkforPlaylist(channelName, id);
}
}, [id, channelName]);
const descriptionThreshold = 200;
useEffect(() => {
if (contentRef.current) {
const height = contentRef.current.offsetHeight;
if (height > descriptionThreshold)
setDescriptionHeight(descriptionThreshold);
}
}, [videoData]);
const nextVideo = useMemo(() => {
const currentVideoIndex = playlistData?.videos?.findIndex(
item => item?.identifier === videoData?.id
);
if (currentVideoIndex !== -1) {
const nextVideoIndex = currentVideoIndex + 1;
const findVideo = playlistData?.videos[nextVideoIndex] || null;
if (findVideo) {
const id = findVideo?.identifier?.replace("_metadata", "");
return {
...findVideo,
service: "VIDEO",
identifier: id,
jsonId: findVideo?.identifier,
};
}
}
return null;
}, [playlistData, videoData]);
const onEndVideo = useCallback(() => {
const currentVideoIndex = playlistData?.videos?.findIndex(
item => item?.identifier === videoData?.id
);
if (currentVideoIndex !== -1) {
const nextVideoIndex = currentVideoIndex + 1;
const findVideo = playlistData?.videos[nextVideoIndex] || null;
if (findVideo) {
getVideoData(findVideo?.name, findVideo?.identifier);
setDoAutoPlay(true);
}
}
}, [videoData, playlistData]);
const getComments = useCallback(async (id, nameAddressParam) => {
if (!id) return;
try {
setLoadingSuperLikes(true);
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}${id.slice(
0,
39
)}&limit=100&includemetadata=true&reverse=true&excludeblocked=true`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
const responseData = await response.json();
let comments: any[] = [];
for (const comment of responseData) {
if (
comment.identifier &&
comment.name &&
comment?.metadata?.description
) {
try {
const result = extractSigValue(comment?.metadata?.description);
if (!result) continue;
const res = await getPaymentInfo(result);
if (
+res?.amount >= minPriceSuperlike &&
res.recipient === nameAddressParam &&
isTimestampWithinRange(res?.timestamp, comment.created)
) {
addSuperlikeRawDataGetToList({
name: comment.name,
identifier: comment.identifier,
content: comment,
});
comments = [
...comments,
{
...comment,
message: "",
amount: res.amount,
},
];
}
} catch (error) {
console.log(error);
}
}
}
setSuperlikelist(comments);
} catch (error) {
console.error(error);
} finally {
setLoadingSuperLikes(false);
}
}, []);
useEffect(() => {
if (!nameAddress || !videoData?.id) return;
getComments(videoData?.id, nameAddress);
}, [getComments, videoData?.id, nameAddress]);
const focusVideo = (e: React.MouseEvent<HTMLDivElement>) => {
console.log("in focusVideo");
const target = e.target as Element;
const textTagNames = ["TEXTAREA", "P", "H[1-6]", "STRONG", "svg", "A"];
const noText =
textTagNames.findIndex(s => {
return target?.tagName.match(s);
}) < 0;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const clickOnEmptySpace = !target?.onclick && noText;
console.log("tagName is: ", target?.tagName);
if (target == e.currentTarget || clickOnEmptySpace) {
console.log("in correctTarget");
const focusRef = containerRef.current?.getContainerRef()?.current;
focusRef.focus({ preventScroll: true });
}
};
return (
return videoData && videoData?.videos?.length === 0 ? (
<Box
sx={{
width: "100%",
display: "flex",
}}
>
<Typography>This playlist doesn't exist</Typography>
</Box>
) : (
<Box
sx={{
display: "flex",
@ -443,288 +65,176 @@ export const PlaylistContent = () => {
marginBottom: "30px",
}}
>
{videoData && videoData?.videos?.length === 0 ? (
<>
<>
<Box
sx={{
display: "grid",
gridTemplateColumns: "55vw 35vw",
width: "100vw",
gap: "3vw",
}}
>
{videoReference && (
<Box
sx={{
aspectRatio: "16/9",
}}
>
<VideoPlayer
name={videoReference?.name}
service={videoReference?.service}
identifier={videoReference?.identifier}
user={channelName}
jsonId={id}
poster={videoCover || ""}
nextVideo={nextVideo}
onEnd={onEndVideo}
autoPlay={doAutoPlay}
ref={containerRef}
videoStyles={{
videoContainer: { aspectRatio: "16 / 9" },
video: { aspectRatio: "16 / 9" },
}}
/>
</Box>
)}
{playlistData && (
<Playlists
playlistData={playlistData}
currentVideoIdentifier={videoData?.id}
onClick={getVideoData}
/>
)}
</Box>
<VideoActionsBar
channelName={channelName}
videoData={videoData}
videoReference={videoReference}
superLikeList={superLikeList}
setSuperLikeList={setSuperLikeList}
sx={{ width: "100%" }}
/>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "10px",
gap: "10px",
}}
>
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
textAlign: "center",
}}
>
{videoData?.title}
</VideoTitle>
</Box>
{videoData?.created && (
<Typography
variant="h6"
sx={{
fontSize: "16px",
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
<Spacer height="30px" />
{videoData?.fullDescription && (
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
display: "flex",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
}
>
<Typography>This playlist is empty</Typography>
</Box>
</>
) : (
<>
<Box
sx={{
display: "grid",
gridTemplateColumns: "55vw 35vw",
width: "100vw",
gap: "3vw",
}}
>
{videoReference && (
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
aspectRatio: "16/9",
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
>
<VideoPlayer
name={videoReference?.name}
service={videoReference?.service}
identifier={videoReference?.identifier}
user={channelName}
jsonId={id}
poster={videoCover || ""}
nextVideo={nextVideo}
onEnd={onEndVideo}
autoPlay={doAutoPlay}
ref={containerRef}
videoStyles={{
videoContainer: { aspectRatio: "16 / 9" },
video: { aspectRatio: "16 / 9" },
}}
/>
</Box>
)}
{playlistData && (
<Playlists
playlistData={playlistData}
currentVideoIdentifier={videoData?.id}
onClick={getVideoData}
/>
)}
</Box>
<Spacer height="15px" />
<Box
sx={{
width: "100%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
}}
>
<Box>
<StyledCardHeaderComment
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: `${descriptionHeight}px`,
overflow: "hidden",
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
sx={{
"& .MuiCardHeader-content": {
overflow: "hidden",
},
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
>
<Box
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
}}
>
{videoData && (
<>
<LikeAndDislike
name={videoData?.user}
identifier={videoData?.id}
/>
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
name={videoData?.user}
service={videoData?.service}
identifier={videoData?.id}
onSuccess={val => {
setSuperlikelist(prev => [val, ...prev]);
}}
/>
</>
)}
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename:
videoData?.filename ||
videoData?.title?.slice(0, 20) + ".mp4",
mimeType: videoData?.videoType || '"video/mp4',
}}
title={
videoData?.filename || videoData?.title?.slice(0, 20)
}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
</Box>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "10px",
gap: "10px",
}}
>
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
textAlign: "center",
}}
>
{videoData?.title}
</VideoTitle>
</Box>
{videoData?.created && (
<Typography
variant="h6"
sx={{
fontSize: "16px",
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
<Spacer height="30px" />
{videoData?.fullDescription && (
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: `${descriptionHeight}px`,
overflow: "hidden",
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
)}
</>
)}
)}
</>
{videoData?.id && videoData?.user && (
<SuperLikesSection
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
superlikes={superLikeList}
postId={videoData?.id || ""}
postName={videoData?.user || ""}
/>

View File

@ -0,0 +1,75 @@
import { Avatar, Box, useTheme } from "@mui/material";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { RootState } from "../../../state/store.ts";
import {
AuthorTextComment,
StyledCardColComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
export interface ChannelActionsParams {
channelName: string;
}
export const ChannelActions = ({ channelName }: ChannelActionsParams) => {
const navigate = useNavigate();
const theme = useTheme();
const userName = useSelector((state: RootState) => state.auth.user?.name);
return (
<Box>
<StyledCardHeaderComment
sx={{
"& .MuiCardHeader-content": {
overflow: "hidden",
},
}}
>
<Box
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
</Box>
);
};

View File

@ -0,0 +1,136 @@
import { Avatar, Box, SxProps, Theme, useTheme } from "@mui/material";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import FileElement from "../../../components/common/FileElement.tsx";
import { refType } from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { titleFormatterOnSave } from "../../../constants/Misc.ts";
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
import DownloadIcon from "@mui/icons-material/Download";
import { RootState } from "../../../state/store.ts";
import { ChannelActions } from "./ChannelActions.tsx";
import {
AuthorTextComment,
FileAttachmentContainer,
FileAttachmentFont,
StyledCardColComment,
StyledCardHeaderComment,
} from "./VideoContent-styles.tsx";
import { useNavigate } from "react-router-dom";
import { useSelector } from "react-redux";
import { useMemo, useState } from "react";
export interface VideoActionsBarProps {
channelName: string;
videoData: any;
videoReference: any;
superLikeList: any[];
setSuperLikeList: React.Dispatch<React.SetStateAction<any[]>>;
sx?: SxProps<Theme>;
}
export const VideoActionsBar = ({
channelName,
videoData,
videoReference,
superLikeList,
setSuperLikeList,
sx,
}: VideoActionsBarProps) => {
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superLikeList?.reduce((acc, curr) => {
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
return acc + parseFloat(curr.amount);
else return acc;
}, 0);
return totalQort?.toFixed(2);
}, [superLikeList]);
const numberOfSuperlikes = useMemo(() => {
return superLikeList?.length ?? 0;
}, [superLikeList]);
const saveAsFilename = useMemo(() => {
// nb. we prefer to construct the local filename to use for
// saving, from the video "title" when possible
if (videoData?.title) {
// figure out filename extension
let ext = ".mp4";
if (videoData?.filename) {
// nb. this regex copied from https://stackoverflow.com/a/680982
const re = /(?:\.([^.]+))?$/;
const match = re.exec(videoData.filename);
if (match[1]) {
ext = "." + match[1];
}
}
return (videoData.title + ext).replace(titleFormatterOnSave, "");
}
// otherwise use QDN filename if applicable
if (videoData?.filename) {
return videoData.filename.replace(titleFormatterOnSave, "");
}
// TODO: this was the previous value, leaving here as the
// fallback for now even though it probably is not needed..?
return videoData?.filename || videoData?.title?.slice(0, 20) + ".mp4";
}, [videoData]);
return (
<Box
sx={{
width: "80%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
marginTop: "15px",
...sx,
}}
>
<ChannelActions channelName={channelName} />
<Box
sx={{
display: "flex",
flexDirection: "row",
}}
>
{videoData && (
<>
<LikeAndDislike name={videoData?.user} identifier={videoData?.id} />
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
name={videoData?.user}
service={videoData?.service}
identifier={videoData?.id}
onSuccess={val => {
setSuperLikeList(prev => [val, ...prev]);
}}
/>
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename: saveAsFilename,
mimeType: videoData?.videoType || '"video/mp4',
}}
title={videoData?.filename || videoData?.title?.slice(0, 20)}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
</>
)}
</Box>
</Box>
);
};

View File

@ -25,28 +25,10 @@ import { useNavigate, useParams } from "react-router-dom";
export const useVideoContentState = () => {
const { name: channelName, id } = useParams();
const userName = useSelector((state: RootState) => state.auth.user?.name);
const [isExpandedDescription, setIsExpandedDescription] =
useState<boolean>(false);
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const containerRef = useRef<refType>(null);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superlikeList?.reduce((acc, curr) => {
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
return acc + parseFloat(curr.amount);
else return acc;
}, 0);
return totalQort?.toFixed(2);
}, [superlikeList]);
const numberOfSuperlikes = useMemo(() => {
return superlikeList?.length ?? 0;
}, [superlikeList]);
const [nameAddress, setNameAddress] = useState<string>("");
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
null
@ -57,6 +39,9 @@ export const useVideoContentState = () => {
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
);
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
const [superLikeList, setSuperLikeList] = useState<any[]>([]);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const contentRef = useRef(null);
@ -87,34 +72,6 @@ export const useVideoContentState = () => {
const navigate = useNavigate();
const theme = useTheme();
const saveAsFilename = useMemo(() => {
// nb. we prefer to construct the local filename to use for
// saving, from the video "title" when possible
if (videoData?.title) {
// figure out filename extension
let ext = ".mp4";
if (videoData?.filename) {
// nb. this regex copied from https://stackoverflow.com/a/680982
const re = /(?:\.([^.]+))?$/;
const match = re.exec(videoData.filename);
if (match[1]) {
ext = "." + match[1];
}
}
return (videoData.title + ext).replace(titleFormatterOnSave, "");
}
// otherwise use QDN filename if applicable
if (videoData?.filename) {
return videoData.filename.replace(titleFormatterOnSave, "");
}
// TODO: this was the previous value, leaving here as the
// fallback for now even though it probably is not needed..?
return videoData?.filename || videoData?.title?.slice(0, 20) + ".mp4";
}, [videoData]);
const hashMapVideos = useSelector(
(state: RootState) => state.video.hashMapVideos
);
@ -140,7 +97,7 @@ export const useVideoContentState = () => {
}, [videoData]);
const dispatch = useDispatch();
const getVideoData = React.useCallback(async (name: string, id: string) => {
const getVideoData = useCallback(async (name: string, id: string) => {
try {
if (!name || !id) return;
dispatch(setIsLoadingGlobal(true));
@ -193,7 +150,7 @@ export const useVideoContentState = () => {
}
}, []);
React.useEffect(() => {
useEffect(() => {
if (channelName && id) {
const existingVideo = hashMapVideos[id + "-" + channelName];
@ -267,7 +224,7 @@ export const useVideoContentState = () => {
}
}
setSuperlikelist(comments);
setSuperLikeList(comments);
} catch (error) {
console.error(error);
} finally {
@ -316,19 +273,18 @@ export const useVideoContentState = () => {
isVideoLoaded,
navigate,
theme,
userName,
videoData,
numberOfSuperlikes,
calculateAmountSuperlike,
setSuperlikelist,
saveAsFilename,
descriptionHeight,
isExpandedDescription,
setIsExpandedDescription,
contentRef,
descriptionThreshold,
loadingSuperLikes,
superlikeList,
superLikeList,
setSuperLikeList,
getComments,
getVideoData,
setVideoData,
};
};

View File

@ -1,14 +1,8 @@
import DownloadIcon from "@mui/icons-material/Download";
import { Avatar, Box, Typography, useTheme } from "@mui/material";
import React from "react";
import DeletedVideo from "../../../assets/img/DeletedVideo.jpg";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import FileElement from "../../../components/common/FileElement.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import {
@ -28,12 +22,13 @@ import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
import { addToHashMap } from "../../../state/features/videoSlice.ts";
import { RootState } from "../../../state/store.ts";
import { formatDate } from "../../../utils/time.ts";
import { VideoActionsBar } from "./VideoActionsBar.tsx";
import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
useVideoContentState,
} from "./VideoContent-State.tsx";
} from "./VideoContent-State.ts";
import {
AuthorTextComment,
FileAttachmentContainer,
@ -56,21 +51,16 @@ export const VideoContent = () => {
videoCover,
containerRef,
isVideoLoaded,
navigate,
theme,
userName,
videoData,
numberOfSuperlikes,
calculateAmountSuperlike,
setSuperlikelist,
saveAsFilename,
descriptionHeight,
isExpandedDescription,
setIsExpandedDescription,
contentRef,
descriptionThreshold,
loadingSuperLikes,
superlikeList,
superLikeList,
setSuperLikeList,
} = useVideoContentState();
return (
@ -111,114 +101,14 @@ export const VideoContent = () => {
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
)}
<VideoContentContainer>
<Box
sx={{
width: "80%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
marginTop: "15px",
}}
>
<Box>
<StyledCardHeaderComment
sx={{
"& .MuiCardHeader-content": {
overflow: "hidden",
},
}}
>
<Box
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
}}
>
{videoData && (
<>
<LikeAndDislike
name={videoData?.user}
identifier={videoData?.id}
/>
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
name={videoData?.user}
service={videoData?.service}
identifier={videoData?.id}
onSuccess={val => {
setSuperlikelist(prev => [val, ...prev]);
}}
/>
</>
)}
{videoData?.filename && (
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename: saveAsFilename,
mimeType: videoData?.videoType || '"video/mp4',
}}
title={
videoData?.filename || videoData?.title?.slice(0, 20)
}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
)}
</Box>
</Box>
<VideoActionsBar
channelName={channelName}
videoData={videoData}
setSuperLikeList={setSuperLikeList}
superLikeList={superLikeList}
videoReference={videoReference}
/>
<Box
sx={{
display: "flex",
@ -343,7 +233,7 @@ export const VideoContent = () => {
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
getMore={() => {}}
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
superlikes={superLikeList}
postId={id || ""}
postName={channelName || ""}
/>

View File

@ -10,7 +10,7 @@ import {
} from "@mui/material";
import { StatsData } from "../../../components/StatsData.tsx";
import { categories, subCategories } from "../../../constants/Categories.ts";
import { useSidebarState } from "./SearchSidebar-State.tsx";
import { useSidebarState } from "./SearchSidebar-State.ts";
import {
FiltersCol,
FiltersContainer,

View File

@ -111,7 +111,7 @@ export const NameContainer = styled(Box)(({ theme }) => ({
marginBottom: "2px",
}));
export const ProductManagerRow = styled(Box)(({ theme }) => ({
export const VideoManagerRow = styled(Box)(({ theme }) => ({
display: "grid",
gridTemplateColumns: "1fr auto",
alignItems: "center",

View File

@ -38,9 +38,6 @@ export const VideoList = ({ videos }: VideoListProps) => {
(state: RootState) => state.video.hashMapVideos
);
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
);
const username = useSelector((state: RootState) => state.auth?.user?.name);
const navigate = useNavigate();

View File

@ -1,36 +1,21 @@
import React, { useCallback, useEffect, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { Box, useTheme } from "@mui/material";
import React, { useEffect, useRef, useState } from "react";
import { useSelector } from "react-redux";
import { RootState } from "../../../state/store.ts";
import { Avatar, Box, Button, Typography, useTheme } from "@mui/material";
import { useFetchVideos } from "../../../hooks/useFetchVideos.tsx";
import LazyLoad from "../../../components/common/LazyLoad.tsx";
import {
BottomParent,
NameContainer,
ProductManagerRow,
VideoCard,
VideoCardCol,
VideoCardContainer,
VideoCardName,
VideoCardTitle,
VideoContainer,
VideoUploadDate,
} from "./VideoList-styles.tsx";
import ResponsiveImage from "../../../components/ResponsiveImage.tsx";
import { formatDate, formatTimestampSeconds } from "../../../utils/time.ts";
import { Video } from "../../../state/features/videoSlice.ts";
import { queue } from "../../../wrappers/GlobalWrapper.tsx";
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
import { useParams } from "react-router-dom";
import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts";
import { useFetchVideos } from "../../../hooks/useFetchVideos.tsx";
import { Video } from "../../../state/features/videoSlice.ts";
import { RootState } from "../../../state/store.ts";
import { queue } from "../../../wrappers/GlobalWrapper.tsx";
import { VideoManagerRow } from "./VideoList-styles.tsx";
import VideoList from "./VideoList.tsx";
interface VideoListProps {
mode?: string;
}
export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
const { name: paramName } = useParams();
const theme = useTheme();
const [isLoading, setIsLoading] = useState<boolean>(true);
const firstFetch = useRef(false);
const afterFetch = useRef(false);
@ -38,18 +23,9 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
(state: RootState) => state.video.hashMapVideos
);
const countNewVideos = useSelector(
(state: RootState) => state.video.countNewVideos
);
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
);
const [videos, setVideos] = React.useState<Video[]>([]);
const navigate = useNavigate();
const { getVideo, getNewVideos, checkNewVideos, checkAndUpdateVideo } =
useFetchVideos();
const { getVideo, checkAndUpdateVideo } = useFetchVideos();
const getVideos = React.useCallback(async () => {
try {
@ -98,21 +74,15 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
}
}
} catch (error) {
} finally {
console.log(error);
}
}, [videos, hashMapVideos]);
const getVideosHandler = React.useCallback(async () => {
if (!firstFetch.current || !afterFetch.current) return;
await getVideos();
}, [getVideos]);
const getVideosHandlerMount = React.useCallback(async () => {
if (firstFetch.current) return;
firstFetch.current = true;
await getVideos();
afterFetch.current = true;
setIsLoading(false);
}, [getVideos]);
useEffect(() => {
@ -122,7 +92,7 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
}, [getVideosHandlerMount]);
return (
<ProductManagerRow>
<VideoManagerRow>
<Box
sx={{
width: "100%",
@ -131,68 +101,8 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
alignItems: "center",
}}
>
<VideoCardContainer>
{videos.map((video: any, index: number) => {
const existingVideo = hashMapVideos[video.id + "-" + video.user];
let hasHash = false;
let videoObj = video;
if (existingVideo) {
videoObj = existingVideo;
hasHash = true;
}
let avatarUrl = "";
if (userAvatarHash[videoObj?.user]) {
avatarUrl = userAvatarHash[videoObj?.user];
}
if (
hasHash &&
(!videoObj?.videoImage || videoObj?.videoImage?.length < 50)
) {
return null;
}
return (
<VideoCardCol key={videoObj.id}>
<VideoCard
onClick={() => {
navigate(`/video/${videoObj.user}/${videoObj.id}`);
}}
>
<VideoCardImageContainer
width={266}
height={150}
videoImage={videoObj.videoImage}
frameImages={videoObj?.extracts || []}
/>
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
<BottomParent>
<NameContainer>
<Avatar
sx={{ height: 24, width: 24 }}
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
alt={`${videoObj.user}'s avatar`}
/>
<VideoCardName>{videoObj.user}</VideoCardName>
</NameContainer>
{videoObj?.created && (
<VideoUploadDate>
{formatDate(videoObj.created)}
</VideoUploadDate>
)}
</BottomParent>
</VideoCard>
</VideoCardCol>
);
})}
</VideoCardContainer>
<LazyLoad
onLoadMore={getVideosHandler}
isLoading={isLoading}
></LazyLoad>
<VideoList videos={videos} />
</Box>
</ProductManagerRow>
</VideoManagerRow>
);
};

View File

@ -5,12 +5,9 @@ import React from "react";
import LazyLoad from "../../components/common/LazyLoad";
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
import { SearchSidebar } from "./Components/SearchSidebar.tsx";
import {
FiltersCol,
ProductManagerRow,
} from "./Components/VideoList-styles.tsx";
import { FiltersCol, VideoManagerRow } from "./Components/VideoList-styles.tsx";
import VideoList from "./Components/VideoList.tsx";
import { useHomeState } from "./Home-State.tsx";
import { useHomeState } from "./Home-State.ts";
import { SubtitleContainer } from "./Home-styles";
interface HomeProps {
@ -32,7 +29,7 @@ export const Home = ({ mode }: HomeProps) => {
<Grid container sx={{ width: "100%" }}>
<SearchSidebar onSearch={getVideosHandler} />
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
<ProductManagerRow>
<VideoManagerRow>
<Box
sx={{
width: "100%",
@ -93,7 +90,7 @@ export const Home = ({ mode }: HomeProps) => {
</TabPanel>
</TabContext>
</Box>
</ProductManagerRow>
</VideoManagerRow>
</Grid>
<FiltersCol item xs={0} lg={3} xl={2}>
<ListSuperLikeContainer />

View File

@ -10,7 +10,7 @@ import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../pages/ContentPages/VideoContent/VideoContent-State.tsx";
} from "../pages/ContentPages/VideoContent/VideoContent-State.ts";
import { addUser } from "../state/features/authSlice";
import NavBar from "../components/layout/Navbar/Navbar";