diff --git a/src/components/common/Notifications/Notifications.tsx b/src/components/common/Notifications/Notifications.tsx index e58bed7..4568f8e 100644 --- a/src/components/common/Notifications/Notifications.tsx +++ b/src/components/common/Notifications/Notifications.tsx @@ -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"; diff --git a/src/hooks/useFetchVideos.tsx b/src/hooks/useFetchVideos.tsx index 01860f1..5f3c226 100644 --- a/src/hooks/useFetchVideos.tsx +++ b/src/hooks/useFetchVideos.tsx @@ -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") { diff --git a/src/pages/ContentPages/PlaylistContent/PlaylistContent-State.ts b/src/pages/ContentPages/PlaylistContent/PlaylistContent-State.ts new file mode 100644 index 0000000..ff8c6dc --- /dev/null +++ b/src/pages/ContentPages/PlaylistContent/PlaylistContent-State.ts @@ -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(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, + }; +}; diff --git a/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx b/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx index 26251a0..a059cf2 100644 --- a/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx +++ b/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx @@ -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(false); - const [descriptionHeight, setDescriptionHeight] = useState( - null - ); - const [superlikeList, setSuperlikelist] = useState([]); - const [loadingSuperLikes, setLoadingSuperLikes] = useState(false); - const { addSuperlikeRawDataGetToList } = useFetchSuperLikes(); - const containerRef = useRef(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(""); - - 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(null); - const [playlistData, setPlaylistData] = useState(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) => { - 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 ? ( + + This playlist doesn't exist + + ) : ( { marginBottom: "30px", }} > - {videoData && videoData?.videos?.length === 0 ? ( - <> + <> + + {videoReference && ( + + + + )} + {playlistData && ( + + )} + + + + + + {videoData?.title} + + + + {videoData?.created && ( + + {formatDate(videoData.created)} + + )} + + + {videoData?.fullDescription && ( - This playlist is empty - - - ) : ( - <> - - {videoReference && ( + {descriptionHeight && !isExpandedDescription && ( { + if (isExpandedDescription) return; + setIsExpandedDescription(true); }} - > - - - )} - {playlistData && ( - )} - - - - - + {videoData?.htmlDescription ? ( + + ) : ( + + {videoData?.fullDescription} + + )} + + {descriptionHeight >= descriptionThreshold && ( + { + setIsExpandedDescription(prev => !prev); + }} sx={{ - "& .MuiCardHeader-content": { - overflow: "hidden", - }, + fontWeight: "bold", + fontSize: "16px", + cursor: "pointer", + paddingLeft: "15px", + paddingTop: "15px", }} > - { - navigate(`/channel/${channelName}`); - }} - > - - - - { - navigate(`/channel/${channelName}`); - }} - > - {channelName} - {channelName !== userName && ( - <> - - - - )} - - - - - - {videoData && ( - <> - - { - setSuperlikelist(prev => [val, ...prev]); - }} - /> - - )} - - Save to Disk - - - - - + {isExpandedDescription ? "Show less" : "...more"} + + )} - - - {videoData?.title} - - - - {videoData?.created && ( - - {formatDate(videoData.created)} - - )} - - - {videoData?.fullDescription && ( - - {descriptionHeight && !isExpandedDescription && ( - { - if (isExpandedDescription) return; - setIsExpandedDescription(true); - }} - /> - )} - - {videoData?.htmlDescription ? ( - - ) : ( - - {videoData?.fullDescription} - - )} - - {descriptionHeight >= descriptionThreshold && ( - { - setIsExpandedDescription(prev => !prev); - }} - sx={{ - fontWeight: "bold", - fontSize: "16px", - cursor: "pointer", - paddingLeft: "15px", - paddingTop: "15px", - }} - > - {isExpandedDescription ? "Show less" : "...more"} - - )} - - )} - - )} + )} + {videoData?.id && videoData?.user && ( diff --git a/src/pages/ContentPages/VideoContent/ChannelActions.tsx b/src/pages/ContentPages/VideoContent/ChannelActions.tsx new file mode 100644 index 0000000..3bd2afa --- /dev/null +++ b/src/pages/ContentPages/VideoContent/ChannelActions.tsx @@ -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 ( + + + { + navigate(`/channel/${channelName}`); + }} + > + + + + { + navigate(`/channel/${channelName}`); + }} + > + {channelName} + {channelName !== userName && ( + <> + + + + )} + + + + + ); +}; diff --git a/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx b/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx new file mode 100644 index 0000000..23eafda --- /dev/null +++ b/src/pages/ContentPages/VideoContent/VideoActionsBar.tsx @@ -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>; + sx?: SxProps; +} + +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 ( + + + + {videoData && ( + <> + + { + setSuperLikeList(prev => [val, ...prev]); + }} + /> + + + Save to Disk + + + + + + )} + + + ); +}; diff --git a/src/pages/ContentPages/VideoContent/VideoContent-State.tsx b/src/pages/ContentPages/VideoContent/VideoContent-State.ts similarity index 84% rename from src/pages/ContentPages/VideoContent/VideoContent-State.tsx rename to src/pages/ContentPages/VideoContent/VideoContent-State.ts index f2d4838..3c41fbf 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent-State.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent-State.ts @@ -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(false); - const [superlikeList, setSuperlikelist] = useState([]); - const [loadingSuperLikes, setLoadingSuperLikes] = useState(false); - - const { addSuperlikeRawDataGetToList } = useFetchSuperLikes(); const containerRef = useRef(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(""); const [descriptionHeight, setDescriptionHeight] = useState( null @@ -57,6 +39,9 @@ export const useVideoContentState = () => { const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash ); + const [loadingSuperLikes, setLoadingSuperLikes] = useState(false); + const [superLikeList, setSuperLikeList] = useState([]); + 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, }; }; diff --git a/src/pages/ContentPages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx index ec593c5..331fbe9 100644 --- a/src/pages/ContentPages/VideoContent/VideoContent.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx @@ -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 = () => { )} - - - - { - navigate(`/channel/${channelName}`); - }} - > - - - - { - navigate(`/channel/${channelName}`); - }} - > - {channelName} - {channelName !== userName && ( - <> - - - - )} - - - - - - {videoData && ( - <> - - { - setSuperlikelist(prev => [val, ...prev]); - }} - /> - - )} - {videoData?.filename && ( - - Save to Disk - - - - - )} - - + + { /* eslint-disable-next-line @typescript-eslint/no-empty-function */ getMore={() => {}} loadingSuperLikes={loadingSuperLikes} - superlikes={superlikeList} + superlikes={superLikeList} postId={id || ""} postName={channelName || ""} /> diff --git a/src/pages/Home/Components/SearchSidebar-State.tsx b/src/pages/Home/Components/SearchSidebar-State.ts similarity index 100% rename from src/pages/Home/Components/SearchSidebar-State.tsx rename to src/pages/Home/Components/SearchSidebar-State.ts diff --git a/src/pages/Home/Components/SearchSidebar.tsx b/src/pages/Home/Components/SearchSidebar.tsx index 85d1a06..af51a41 100644 --- a/src/pages/Home/Components/SearchSidebar.tsx +++ b/src/pages/Home/Components/SearchSidebar.tsx @@ -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, diff --git a/src/pages/Home/Components/VideoList-styles.tsx b/src/pages/Home/Components/VideoList-styles.tsx index 0a4f6ee..3a0e48d 100644 --- a/src/pages/Home/Components/VideoList-styles.tsx +++ b/src/pages/Home/Components/VideoList-styles.tsx @@ -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", diff --git a/src/pages/Home/Components/VideoList.tsx b/src/pages/Home/Components/VideoList.tsx index bba1f9a..e2e4922 100644 --- a/src/pages/Home/Components/VideoList.tsx +++ b/src/pages/Home/Components/VideoList.tsx @@ -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(); diff --git a/src/pages/Home/Components/VideoListComponentLevel.tsx b/src/pages/Home/Components/VideoListComponentLevel.tsx index 3e441ca..af1da91 100644 --- a/src/pages/Home/Components/VideoListComponentLevel.tsx +++ b/src/pages/Home/Components/VideoListComponentLevel.tsx @@ -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(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([]); - 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 ( - + { alignItems: "center", }} > - - {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 ( - - { - navigate(`/video/${videoObj.user}/${videoObj.id}`); - }} - > - - {videoObj.title} - - - - {videoObj.user} - - - {videoObj?.created && ( - - {formatDate(videoObj.created)} - - )} - - - - ); - })} - - + - + ); }; diff --git a/src/pages/Home/Home-State.tsx b/src/pages/Home/Home-State.ts similarity index 100% rename from src/pages/Home/Home-State.tsx rename to src/pages/Home/Home-State.ts diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index ee58557..a5791dc 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -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) => { - + { - + diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index 579a656..59dbd34 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -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";