diff --git a/src/App.tsx b/src/App.tsx index 455ad31..84231f3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,14 +8,14 @@ import { Provider } from "react-redux"; import GlobalWrapper from "./wrappers/GlobalWrapper"; import Notification from "./components/common/Notification/Notification"; import { Home } from "./pages/Home/Home"; -import { VideoContent } from "./pages/VideoContent/VideoContent"; +import { VideoContent } from "./pages/ContentPages/VideoContent/VideoContent"; import DownloadWrapper from "./wrappers/DownloadWrapper"; -import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile"; -import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent"; +import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile"; +import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent"; import { PersistGate } from "redux-persist/integration/react"; import { persistStore } from "redux-persist"; import { setFilteredSubscriptions } from "./state/features/videoSlice.ts"; -import { SubscriptionData } from "./components/common/SubscribeButton.tsx"; +import { SubscriptionData } from "./components/common/ContentButtons/SubscribeButton.tsx"; export const getUserName = async () => { const account = await qortalRequest({ diff --git a/src/components/Playlists/Playlists.tsx b/src/components/Playlists/Playlists.tsx index 046863b..7834c55 100644 --- a/src/components/Playlists/Playlists.tsx +++ b/src/components/Playlists/Playlists.tsx @@ -3,7 +3,7 @@ import { CardContentContainerComment } from "../common/Comments/Comments-styles" import { CrowdfundSubTitle, CrowdfundSubTitleRow, -} from "../PublishVideo/PublishVideo-styles.tsx"; +} from "../Publish/PublishVideo/PublishVideo-styles.tsx"; import { Box, Typography, useTheme } from "@mui/material"; import { useNavigate } from "react-router-dom"; diff --git a/src/components/EditPlaylist/EditPlaylist.tsx b/src/components/Publish/EditPlaylist/EditPlaylist.tsx similarity index 96% rename from src/components/EditPlaylist/EditPlaylist.tsx rename to src/components/Publish/EditPlaylist/EditPlaylist.tsx index d224dc2..52a4c5e 100644 --- a/src/components/EditPlaylist/EditPlaylist.tsx +++ b/src/components/Publish/EditPlaylist/EditPlaylist.tsx @@ -12,7 +12,7 @@ import { NewCrowdfundTitle, StyledButton, TimesIcon, -} from "./Upload-styles"; +} from "./Upload-styles.tsx"; import { Box, FormControl, @@ -30,9 +30,9 @@ import { useDispatch, useSelector } from "react-redux"; import AddBoxIcon from "@mui/icons-material/AddBox"; import { useDropzone } from "react-dropzone"; -import { setNotification } from "../../state/features/notificationsSlice"; -import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64"; -import { RootState } from "../../state/store"; +import { setNotification } from "../../../state/features/notificationsSlice.ts"; +import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts"; +import { RootState } from "../../../state/store.ts"; import { upsertVideosBeginning, addToHashMap, @@ -41,17 +41,17 @@ import { updateVideo, updateInHashMap, setEditPlaylist, -} from "../../state/features/videoSlice"; -import ImageUploader from "../common/ImageUploader"; -import { categories, subCategories } from "../../constants/Categories.ts"; -import { Playlists } from "../Playlists/Playlists"; -import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit"; -import { TextEditor } from "../common/TextEditor/TextEditor"; -import { extractTextFromHTML } from "../common/TextEditor/utils"; +} from "../../../state/features/videoSlice.ts"; +import ImageUploader from "../../common/ImageUploader.tsx"; +import { categories, subCategories } from "../../../constants/Categories.ts"; +import { Playlists } from "../../Playlists/Playlists.tsx"; +import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit.tsx"; +import { TextEditor } from "../../common/TextEditor/TextEditor.tsx"; +import { extractTextFromHTML } from "../../common/TextEditor/utils.ts"; import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE, -} from "../../constants/Identifiers.ts"; +} from "../../../constants/Identifiers.ts"; const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); diff --git a/src/components/EditPlaylist/Upload-styles.tsx b/src/components/Publish/EditPlaylist/Upload-styles.tsx similarity index 97% rename from src/components/EditPlaylist/Upload-styles.tsx rename to src/components/Publish/EditPlaylist/Upload-styles.tsx index b1b8813..53cb8e0 100644 --- a/src/components/EditPlaylist/Upload-styles.tsx +++ b/src/components/Publish/EditPlaylist/Upload-styles.tsx @@ -9,10 +9,10 @@ import { Rating, TextField, Typography, - Select + Select, } from "@mui/material"; import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; -import { TimesSVG } from "../../assets/svgs/TimesSVG"; +import { TimesSVG } from "../../../assets/svgs/TimesSVG.tsx"; export const DoubleLine = styled(Typography)` display: -webkit-box; @@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({ }, })); - - export const CrowdfundTitle = styled(Typography)(({ theme }) => ({ fontFamily: "Copse", letterSpacing: "1px", @@ -539,8 +537,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({ export const StyledButton = styled(Button)(({ theme }) => ({ fontWeight: 600, - color: theme.palette.text.primary -})) + color: theme.palette.text.primary, +})); export const CustomSelect = styled(Select)(({ theme }) => ({ fontFamily: "Mulish", @@ -549,34 +547,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({ fontWeight: 400, color: theme.palette.text.primary, backgroundColor: theme.palette.background.default, - '& .MuiSelect-select': { - padding: '12px', + "& .MuiSelect-select": { + padding: "12px", fontFamily: "Mulish", fontSize: "19px", letterSpacing: "0px", fontWeight: 400, borderRadius: theme.shape.borderRadius, // Match border radius }, - '&:before': { + "&:before": { // Underline style borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf", }, - '&:after': { + "&:after": { // Underline style when focused borderBottomColor: theme.palette.secondary.main, }, - '& .MuiOutlinedInput-root': { - '& fieldset': { + "& .MuiOutlinedInput-root": { + "& fieldset": { borderColor: "#E0E3E7", }, - '&:hover fieldset': { + "&:hover fieldset": { borderColor: "#B2BAC2", }, - '&.Mui-focused fieldset': { + "&.Mui-focused fieldset": { borderColor: "#6F7E8C", }, }, - '& .MuiInputBase-root': { + "& .MuiInputBase-root": { fontFamily: "Mulish", fontSize: "19px", letterSpacing: "0px", diff --git a/src/components/EditVideo/EditVideo-styles.tsx b/src/components/Publish/EditVideo/EditVideo-styles.tsx similarity index 97% rename from src/components/EditVideo/EditVideo-styles.tsx rename to src/components/Publish/EditVideo/EditVideo-styles.tsx index b1b8813..53cb8e0 100644 --- a/src/components/EditVideo/EditVideo-styles.tsx +++ b/src/components/Publish/EditVideo/EditVideo-styles.tsx @@ -9,10 +9,10 @@ import { Rating, TextField, Typography, - Select + Select, } from "@mui/material"; import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; -import { TimesSVG } from "../../assets/svgs/TimesSVG"; +import { TimesSVG } from "../../../assets/svgs/TimesSVG.tsx"; export const DoubleLine = styled(Typography)` display: -webkit-box; @@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({ }, })); - - export const CrowdfundTitle = styled(Typography)(({ theme }) => ({ fontFamily: "Copse", letterSpacing: "1px", @@ -539,8 +537,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({ export const StyledButton = styled(Button)(({ theme }) => ({ fontWeight: 600, - color: theme.palette.text.primary -})) + color: theme.palette.text.primary, +})); export const CustomSelect = styled(Select)(({ theme }) => ({ fontFamily: "Mulish", @@ -549,34 +547,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({ fontWeight: 400, color: theme.palette.text.primary, backgroundColor: theme.palette.background.default, - '& .MuiSelect-select': { - padding: '12px', + "& .MuiSelect-select": { + padding: "12px", fontFamily: "Mulish", fontSize: "19px", letterSpacing: "0px", fontWeight: 400, borderRadius: theme.shape.borderRadius, // Match border radius }, - '&:before': { + "&:before": { // Underline style borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf", }, - '&:after': { + "&:after": { // Underline style when focused borderBottomColor: theme.palette.secondary.main, }, - '& .MuiOutlinedInput-root': { - '& fieldset': { + "& .MuiOutlinedInput-root": { + "& fieldset": { borderColor: "#E0E3E7", }, - '&:hover fieldset': { + "&:hover fieldset": { borderColor: "#B2BAC2", }, - '&.Mui-focused fieldset': { + "&.Mui-focused fieldset": { borderColor: "#6F7E8C", }, }, - '& .MuiInputBase-root': { + "& .MuiInputBase-root": { fontFamily: "Mulish", fontSize: "19px", letterSpacing: "0px", diff --git a/src/components/EditVideo/EditVideo.tsx b/src/components/Publish/EditVideo/EditVideo.tsx similarity index 95% rename from src/components/EditVideo/EditVideo.tsx rename to src/components/Publish/EditVideo/EditVideo.tsx index 56cd622..a5fa0cc 100644 --- a/src/components/EditVideo/EditVideo.tsx +++ b/src/components/Publish/EditVideo/EditVideo.tsx @@ -34,9 +34,9 @@ import { useDispatch, useSelector } from "react-redux"; import AddBoxIcon from "@mui/icons-material/AddBox"; import { useDropzone } from "react-dropzone"; -import { setNotification } from "../../state/features/notificationsSlice"; -import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64"; -import { RootState } from "../../state/store"; +import { setNotification } from "../../../state/features/notificationsSlice.ts"; +import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts"; +import { RootState } from "../../../state/store.ts"; import { upsertVideosBeginning, addToHashMap, @@ -44,16 +44,16 @@ import { setEditVideo, updateVideo, updateInHashMap, -} from "../../state/features/videoSlice"; -import ImageUploader from "../common/ImageUploader"; -import { categories, subCategories } from "../../constants/Categories.ts"; -import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll"; -import { TextEditor } from "../common/TextEditor/TextEditor"; -import { extractTextFromHTML } from "../common/TextEditor/utils"; +} from "../../../state/features/videoSlice.ts"; +import ImageUploader from "../../common/ImageUploader.tsx"; +import { categories, subCategories } from "../../../constants/Categories.ts"; +import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll.tsx"; +import { TextEditor } from "../../common/TextEditor/TextEditor.tsx"; +import { extractTextFromHTML } from "../../common/TextEditor/utils.ts"; import { toBase64 } from "../PublishVideo/PublishVideo.tsx"; -import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor"; -import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts"; -import { titleFormatter } from "../../constants/Misc.ts"; +import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx"; +import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts"; +import { titleFormatter } from "../../../constants/Misc.ts"; const uid = new ShortUniqueId(); const shortuid = new ShortUniqueId({ length: 5 }); diff --git a/src/components/common/MultiplePublish/MultiplePublishAll.tsx b/src/components/Publish/MultiplePublish/MultiplePublishAll.tsx similarity index 98% rename from src/components/common/MultiplePublish/MultiplePublishAll.tsx rename to src/components/Publish/MultiplePublish/MultiplePublishAll.tsx index 6993710..ac9024a 100644 --- a/src/components/common/MultiplePublish/MultiplePublishAll.tsx +++ b/src/components/Publish/MultiplePublish/MultiplePublishAll.tsx @@ -8,8 +8,8 @@ import { useTheme, } from "@mui/material"; import React, { useCallback, useEffect, useState, useRef } from "react"; -import { CircleSVG } from "../../../assets/svgs/CircleSVG"; -import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG"; +import { CircleSVG } from "../../../assets/svgs/CircleSVG.tsx"; +import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG.tsx"; import { styled } from "@mui/system"; interface Publish { diff --git a/src/components/PlaylistListEdit/PlaylistListEdit.tsx b/src/components/Publish/PlaylistListEdit/PlaylistListEdit.tsx similarity index 95% rename from src/components/PlaylistListEdit/PlaylistListEdit.tsx rename to src/components/Publish/PlaylistListEdit/PlaylistListEdit.tsx index 90fba1c..49187f3 100644 --- a/src/components/PlaylistListEdit/PlaylistListEdit.tsx +++ b/src/components/Publish/PlaylistListEdit/PlaylistListEdit.tsx @@ -1,5 +1,5 @@ import React, { useState } from "react"; -import { CardContentContainerComment } from "../common/Comments/Comments-styles"; +import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx"; import { CrowdfundSubTitle, CrowdfundSubTitleRow, @@ -7,11 +7,11 @@ import { import { Box, Button, Input, Typography, useTheme } from "@mui/material"; import { useNavigate } from "react-router-dom"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; -import { removeVideo } from "../../state/features/videoSlice"; +import { removeVideo } from "../../../state/features/videoSlice.ts"; import AddIcon from "@mui/icons-material/Add"; import { useSelector } from "react-redux"; -import { RootState } from "../../state/store"; -import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts"; +import { RootState } from "../../../state/store.ts"; +import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts"; export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => { const theme = useTheme(); const navigate = useNavigate(); diff --git a/src/components/PublishVideo/PublishVideo-styles.tsx b/src/components/Publish/PublishVideo/PublishVideo-styles.tsx similarity index 99% rename from src/components/PublishVideo/PublishVideo-styles.tsx rename to src/components/Publish/PublishVideo/PublishVideo-styles.tsx index 6495a8a..bf3d011 100644 --- a/src/components/PublishVideo/PublishVideo-styles.tsx +++ b/src/components/Publish/PublishVideo/PublishVideo-styles.tsx @@ -12,7 +12,7 @@ import { Select, } from "@mui/material"; import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate"; -import { TimesSVG } from "../../assets/svgs/TimesSVG"; +import { TimesSVG } from "../../../assets/svgs/TimesSVG.tsx"; export const DoubleLine = styled(Typography)` display: -webkit-box; diff --git a/src/components/PublishVideo/PublishVideo.tsx b/src/components/Publish/PublishVideo/PublishVideo.tsx similarity index 97% rename from src/components/PublishVideo/PublishVideo.tsx rename to src/components/Publish/PublishVideo/PublishVideo.tsx index ab59967..a5952df 100644 --- a/src/components/PublishVideo/PublishVideo.tsx +++ b/src/components/Publish/PublishVideo/PublishVideo.tsx @@ -37,36 +37,36 @@ import AddBoxIcon from "@mui/icons-material/AddBox"; import { useDropzone } from "react-dropzone"; import AddIcon from "@mui/icons-material/Add"; -import { setNotification } from "../../state/features/notificationsSlice"; -import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64"; -import { RootState } from "../../state/store"; +import { setNotification } from "../../../state/features/notificationsSlice.ts"; +import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts"; +import { RootState } from "../../../state/store.ts"; import { upsertVideosBeginning, addToHashMap, upsertVideos, -} from "../../state/features/videoSlice"; -import ImageUploader from "../common/ImageUploader"; -import { categories, subCategories } from "../../constants/Categories.ts"; -import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll"; +} from "../../../state/features/videoSlice.ts"; +import ImageUploader from "../../common/ImageUploader.tsx"; +import { categories, subCategories } from "../../../constants/Categories.ts"; +import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll.tsx"; import { CrowdfundSubTitle, CrowdfundSubTitleRow, -} from "../EditPlaylist/Upload-styles"; -import { CardContentContainerComment } from "../common/Comments/Comments-styles"; -import { TextEditor } from "../common/TextEditor/TextEditor"; -import { extractTextFromHTML } from "../common/TextEditor/utils"; +} from "../EditPlaylist/Upload-styles.tsx"; +import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx"; +import { TextEditor } from "../../common/TextEditor/TextEditor.tsx"; +import { extractTextFromHTML } from "../../common/TextEditor/utils.ts"; import { FiltersCheckbox, FiltersRow, FiltersSubContainer, -} from "../../pages/Home/VideoList-styles"; -import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor"; +} from "../../../pages/Home/VideoList-styles.tsx"; +import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx"; import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE, -} from "../../constants/Identifiers.ts"; -import { titleFormatter } from "../../constants/Misc.ts"; -import { getFileName } from "../../utils/stringFunctions.ts"; +} from "../../../constants/Identifiers.ts"; +import { titleFormatter } from "../../../constants/Misc.ts"; +import { getFileName } from "../../../utils/stringFunctions.ts"; export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { diff --git a/src/components/StatsData.tsx b/src/components/StatsData.tsx index 115dea8..a474ca3 100644 --- a/src/components/StatsData.tsx +++ b/src/components/StatsData.tsx @@ -12,8 +12,6 @@ export const StatsData = () => { width: "100%", padding: "20px 0px", backgroundColor: theme.palette.background.default, - borderTop: `1px solid ${theme.palette.background.paper}`, - borderRight: `1px solid ${theme.palette.background.paper}`, })); const { @@ -51,7 +49,10 @@ export const StatsData = () => {
Average:{" "} - {videosPerNamePublished} + + {videosPerNamePublished > 0 && + Number(videosPerNamePublished).toFixed(0)} +
); diff --git a/src/components/common/Comments/CommentSection.tsx b/src/components/common/Comments/CommentSection.tsx index df325b1..99f3fb0 100644 --- a/src/components/common/Comments/CommentSection.tsx +++ b/src/components/common/Comments/CommentSection.tsx @@ -17,7 +17,7 @@ import { import { CrowdfundSubTitle, CrowdfundSubTitleRow, -} from "../../PublishVideo/PublishVideo-styles.tsx"; +} from "../../Publish/PublishVideo/PublishVideo-styles.tsx"; import { COMMENT_BASE } from "../../../constants/Identifiers.ts"; interface CommentSectionProps { diff --git a/src/components/common/ContentButtons/FollowButton.tsx b/src/components/common/ContentButtons/FollowButton.tsx new file mode 100644 index 0000000..5739d3d --- /dev/null +++ b/src/components/common/ContentButtons/FollowButton.tsx @@ -0,0 +1,159 @@ +import { Box, Button, ButtonProps } from "@mui/material"; +import Tooltip, { TooltipProps, tooltipClasses } from "@mui/material/Tooltip"; +import { MouseEvent, useEffect, useState } from "react"; +import { styled } from "@mui/material/styles"; + +interface FollowButtonProps extends ButtonProps { + followerName: string; +} + +export type FollowData = { + userName: string; + followerName: string; +}; + +export const FollowButton = ({ followerName, ...props }: FollowButtonProps) => { + const [followingList, setFollowingList] = useState([]); + const [followingSize, setFollowingSize] = useState(""); + const [followingItemCount, setFollowingItemCount] = useState(""); + const isFollowingName = () => { + return followingList.includes(followerName); + }; + + useEffect(() => { + qortalRequest({ + action: "GET_LIST_ITEMS", + list_name: "followedNames", + }).then(followList => { + setFollowingList(followList); + }); + getFollowSize(); + }, []); + + const followName = () => { + if (followingList.includes(followerName) === false) { + qortalRequest({ + action: "ADD_LIST_ITEM", + list_name: "followedNames", + item: followerName, + }).then(response => { + if (response === false) console.log("followName failed"); + else { + setFollowingList([...followingList, followerName]); + console.log("following Name: ", followerName); + } + }); + } + }; + const unfollowName = () => { + if (followingList.includes(followerName)) { + qortalRequest({ + action: "DELETE_LIST_ITEM", + list_name: "followedNames", + item: followerName, + }).then(response => { + if (response === false) console.log("unfollowName failed"); + else { + const listWithoutName = followingList.filter( + item => followerName !== item + ); + setFollowingList(listWithoutName); + console.log("unfollowing Name: ", followerName); + } + }); + } + }; + + const manageFollow = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + isFollowingName() ? unfollowName() : followName(); + }; + + const verticalPadding = "3px"; + const horizontalPadding = "8px"; + const buttonStyle = { + fontSize: "15px", + fontWeight: "700", + paddingTop: verticalPadding, + paddingBottom: verticalPadding, + paddingLeft: horizontalPadding, + paddingRight: horizontalPadding, + borderRadius: 28, + color: "white", + width: "96px", + height: "45px", + ...props.sx, + }; + + const getFollowSize = () => { + qortalRequest({ + action: "LIST_QDN_RESOURCES", + name: followerName, + limit: 0, + includeMetadata: false, + }).then(publishesList => { + let totalSize = 0; + let itemsCount = 0; + publishesList.map(publish => { + totalSize += +publish.size; + itemsCount++; + }); + setFollowingSize(formatBytes(totalSize)); + setFollowingItemCount(itemsCount.toString()); + }); + }; + + function formatBytes(bytes: number, decimals = 2) { + if (!+bytes) return "0 Bytes"; + + const k = 1024; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + + const i = Math.floor(Math.log(bytes) / Math.log(k)); + + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`; + } + + const TooltipLine = styled("div")(({ theme }) => ({ + fontSize: "18px", + })); + + const tooltipTitle = followingSize && ( + <> + + Following a name automatically downloads all of its content to your + node. The more followers a name has, the faster its content will + download for everyone. + +
+ {`${followerName}'s Current Download Size: ${followingSize}`} + {`Number of Files: ${followingItemCount}`} + + ); + + const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => ( + + ))({ + [`& .${tooltipClasses.tooltip}`]: { + maxWidth: 600, + }, + }); + + return ( + <> + + + + + ); +}; diff --git a/src/components/common/ContentButtons/LikeAndDislike-functions.ts b/src/components/common/ContentButtons/LikeAndDislike-functions.ts new file mode 100644 index 0000000..bb17191 --- /dev/null +++ b/src/components/common/ContentButtons/LikeAndDislike-functions.ts @@ -0,0 +1,66 @@ +import { fetchResourcesByIdentifier } from "../../../utils/qortalRequestFunctions.ts"; +import { DISLIKE, LIKE, LikeType, NEUTRAL } from "./LikeAndDislike.tsx"; + +export const getCurrentLikeType = async ( + username: string, + likeIdentifier: string +): Promise => { + try { + const response = await qortalRequest({ + action: "FETCH_QDN_RESOURCE", + name: username, + service: "CHAIN_COMMENT", + identifier: likeIdentifier, + }); + return response?.likeType; + } catch (e) { + console.log("liketype error: ", e); + return NEUTRAL; + } +}; + +type ResourceType = { likeType: LikeType }; +export type LikesAndDislikes = { likes: number; dislikes: number }; +const countLikesAndDislikes = (likesAndDislikes: ResourceType[]) => { + let totalLikeCount = 0; + let totalDislikeCount = 0; + likesAndDislikes.map(likeOrDislike => { + const likeType = likeOrDislike.likeType; + if (likeType === LIKE) totalLikeCount += 1; + if (likeType === DISLIKE) totalDislikeCount += 1; + }); + return { + likes: totalLikeCount, + dislikes: totalDislikeCount, + } as LikesAndDislikes; +}; +export const getCurrentLikesAndDislikesCount = async ( + likeIdentifier: string +) => { + try { + const likesAndDislikes = await fetchResourcesByIdentifier( + "CHAIN_COMMENT", + likeIdentifier + ); + return countLikesAndDislikes(likesAndDislikes); + } catch (e) { + console.log(e); + return undefined; + } +}; + +export function formatLikeCount(likeCount: number, decimals = 2) { + if (!+likeCount) return ""; + + const sigDigits = Math.floor(Math.log10(likeCount) / 3); + if (sigDigits < 1) return likeCount.toString(); + + const sigDigitSize = 1000; + const dm = decimals < 0 ? 0 : decimals; + const sizes = ["K", "M", "B"]; + + const sigDigitsToTheThousands = Math.pow(sigDigitSize, sigDigits); + const sigDigitLikeCount = (likeCount / sigDigitsToTheThousands).toFixed(dm); + + return `${sigDigitLikeCount}${sizes[sigDigits - 1] || ""}`; +} diff --git a/src/components/common/ContentButtons/LikeAndDislike.tsx b/src/components/common/ContentButtons/LikeAndDislike.tsx new file mode 100644 index 0000000..3e9c169 --- /dev/null +++ b/src/components/common/ContentButtons/LikeAndDislike.tsx @@ -0,0 +1,230 @@ +import React, { useEffect, useState } from "react"; +import ThumbUpIcon from "@mui/icons-material/ThumbUp"; +import ThumbDownIcon from "@mui/icons-material/ThumbDown"; +import ThumbUpOffAltOutlinedIcon from "@mui/icons-material/ThumbUpOffAltOutlined"; +import ThumbDownOffAltOutlinedIcon from "@mui/icons-material/ThumbDownOffAltOutlined"; +import { Box, Tooltip } from "@mui/material"; +import { useDispatch, useSelector } from "react-redux"; +import { setNotification } from "../../../state/features/notificationsSlice.ts"; +import ShortUniqueId from "short-unique-id"; +import { objectToBase64 } from "../../../utils/toBase64.ts"; +import { RootState } from "../../../state/store.ts"; +import { FOR, FOR_LIKE, LIKE_BASE } from "../../../constants/Identifiers.ts"; +import { + formatLikeCount, + getCurrentLikesAndDislikesCount, + getCurrentLikeType, + LikesAndDislikes, +} from "./LikeAndDislike-functions.ts"; + +interface LikeAndDislikeProps { + name: string; + identifier: string; +} +export enum LikeType { + Like = 1, + Neutral = 0, + Dislike = -1, +} +export const LIKE = LikeType.Like; +export const DISLIKE = LikeType.Dislike; +export const NEUTRAL = LikeType.Neutral; +export const LikeAndDislike = ({ name, identifier }: LikeAndDislikeProps) => { + const username = useSelector((state: RootState) => state.auth?.user?.name); + const dispatch = useDispatch(); + const [likeCount, setLikeCount] = useState(0); + const [dislikeCount, setDislikeCount] = useState(0); + const [currentLikeType, setCurrentLikeType] = useState(NEUTRAL); + const likeIdentifier = `${LIKE_BASE}${identifier.slice(0, 39)}`; + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + type PromiseReturn = [LikeType, LikesAndDislikes]; + + Promise.all([ + getCurrentLikeType(username, likeIdentifier), + getCurrentLikesAndDislikesCount(likeIdentifier), + ]).then(([likeType, likesAndDislikes]: PromiseReturn) => { + setCurrentLikeType(likeType); + + setLikeCount(likesAndDislikes?.likes || 0); + setDislikeCount(likesAndDislikes?.dislikes || 0); + setIsLoading(false); + }); + }, []); + + const updateLikeDataState = (newLikeType: LikeType) => { + const setSuccessNotification = (msg: string) => + dispatch( + setNotification({ + msg, + alertType: "success", + }) + ); + setCurrentLikeType(newLikeType); + switch (newLikeType) { + case NEUTRAL: + if (currentLikeType === LIKE) { + setLikeCount(count => count - 1); + setSuccessNotification("Like Removed"); + } else { + setDislikeCount(count => count - 1); + setSuccessNotification("Dislike Removed"); + } + + break; + case LIKE: + if (currentLikeType === DISLIKE) setDislikeCount(count => count - 1); + setLikeCount(count => count + 1); + setSuccessNotification("Like Successful"); + break; + case DISLIKE: + if (currentLikeType === LIKE) setLikeCount(count => count - 1); + setDislikeCount(count => count + 1); + setSuccessNotification("Dislike Successful"); + break; + } + }; + function publishLike(chosenLikeType: LikeType) { + if (isLoading) { + dispatch( + setNotification({ + msg: "Wait for Like Data to load first", + alertType: "error", + }) + ); + return; + } + try { + if (!username) throw new Error("You need a name to publish"); + if (!name) throw new Error("Could not retrieve content creator's name"); + if (!identifier) throw new Error("Could not retrieve id of video post"); + + if (username === name) { + dispatch( + setNotification({ + msg: "You cannot send yourself a like", + alertType: "error", + }) + ); + return; + } + qortalRequest({ + action: "GET_NAME_DATA", + name: name, + }).then(resName => { + const address = resName.owner; + if (!address) + throw new Error("Could not retrieve content creator's address"); + }); + + objectToBase64({ + likeType: chosenLikeType, + }).then(likeToBase64 => { + qortalRequest({ + action: "PUBLISH_QDN_RESOURCE", + name: username, + service: "CHAIN_COMMENT", + data64: likeToBase64, + title: "", + identifier: likeIdentifier, + filename: `like_metadata.json`, + }).then(() => { + updateLikeDataState(chosenLikeType); + }); + }); + } catch (error: any) { + dispatch( + setNotification({ + msg: + error || + error?.error || + error?.message || + "Failed to publish Like or Dislike", + alertType: "error", + }) + ); + throw new Error("Failed to publish Super Like"); + } + } + + return ( + <> + + + + {currentLikeType === LIKE ? ( + publishLike(NEUTRAL)} /> + ) : ( + publishLike(LIKE)} /> + )} + {likeCount > 0 && ( +
+ + {formatLikeCount(likeCount)} + +
+ )} + + {currentLikeType === DISLIKE ? ( + publishLike(NEUTRAL)} + color={"error"} + /> + ) : ( + publishLike(DISLIKE)} + color={"error"} + /> + )} + {dislikeCount > 0 && ( +
+ + {formatLikeCount(dislikeCount)} + +
+ )} +
+
+
+ + ); +}; diff --git a/src/components/common/SubscribeButton.tsx b/src/components/common/ContentButtons/SubscribeButton.tsx similarity index 70% rename from src/components/common/SubscribeButton.tsx rename to src/components/common/ContentButtons/SubscribeButton.tsx index cdb7cb9..2214289 100644 --- a/src/components/common/SubscribeButton.tsx +++ b/src/components/common/ContentButtons/SubscribeButton.tsx @@ -1,10 +1,14 @@ -import { Button, ButtonProps } from "@mui/material"; +import { Button, ButtonProps, Tooltip } from "@mui/material"; import { MouseEvent, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { RootState } from "../../state/store.ts"; -import { subscribe, unSubscribe } from "../../state/features/persistSlice.ts"; -import { setFilteredSubscriptions } from "../../state/features/videoSlice.ts"; -import { subscriptionListFilter } from "../../App.tsx"; +import { RootState } from "../../../state/store.ts"; +import { + subscribe, + unSubscribe, +} from "../../../state/features/persistSlice.ts"; +import { setFilteredSubscriptions } from "../../../state/features/videoSlice.ts"; +import { subscriptionListFilter } from "../../../App.tsx"; +import { styled } from "@mui/material/styles"; interface SubscribeButtonProps extends ButtonProps { subscriberName: string; @@ -89,15 +93,31 @@ export const SubscribeButton = ({ height: "45px", ...props.sx, }; + + const TooltipLine = styled("div")(({ theme }) => ({ + fontSize: "18px", + })); + + const tooltipTitle = ( + <> + + Subscribing to a name lets you see their content on the Subscriptions + tab of the Home Page. This does NOT download any data to your node. + + + ); + return ( - + + + ); }; diff --git a/src/components/common/SuperLike/SuperLike.tsx b/src/components/common/ContentButtons/SuperLike.tsx similarity index 86% rename from src/components/common/SuperLike/SuperLike.tsx rename to src/components/common/ContentButtons/SuperLike.tsx index 5e1be7b..ac33288 100644 --- a/src/components/common/SuperLike/SuperLike.tsx +++ b/src/components/common/ContentButtons/SuperLike.tsx @@ -17,26 +17,25 @@ import { Tooltip, } from "@mui/material"; import qortImg from "../../../assets/img/qort.png"; -import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll"; +import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAll.tsx"; import { useDispatch, useSelector } from "react-redux"; -import { setNotification } from "../../../state/features/notificationsSlice"; +import { setNotification } from "../../../state/features/notificationsSlice.ts"; import ShortUniqueId from "short-unique-id"; -import { objectToBase64 } from "../../../utils/toBase64"; +import { objectToBase64 } from "../../../utils/toBase64.ts"; import { minPriceSuperlike } from "../../../constants/Misc.ts"; -import { CommentInput } from "../Comments/Comments-styles"; +import { CommentInput } from "../Comments/Comments-styles.tsx"; import { CrowdfundActionButton, CrowdfundActionButtonRow, ModalBody, NewCrowdfundTitle, Spacer, -} from "../../PublishVideo/PublishVideo-styles.tsx"; -import { utf8ToBase64 } from "../SuperLikesList/CommentEditor"; -import { RootState } from "../../../state/store"; +} from "../../Publish/PublishVideo/PublishVideo-styles.tsx"; +import { utf8ToBase64 } from "../SuperLikesList/CommentEditor.tsx"; +import { RootState } from "../../../state/store.ts"; import { FOR, FOR_SUPER_LIKE, - QTUBE_VIDEO_BASE, SUPER_LIKE_BASE, } from "../../../constants/Identifiers.ts"; import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx"; @@ -158,7 +157,7 @@ export const SuperLike = ({ for: `${name}_${FOR_SUPER_LIKE}`, }, about: - "Super likes are a way to suppert your favorite content creators. Attach a message to the Super like and have your message seen before normal comments. There is a minimum superLikeAmount for a Super like. Each Super like is verified before displaying to make there aren't any non-paid Super likes", + "Super likes are a way to support your favorite content creators. Attach a message to the Super like and have your message seen before normal comments. There is a minimum superLikeAmount for a Super like. Each Super like is verified before displaying to make there aren't any non-paid Super likes", }); // Description is obtained from raw data // const base64 = utf8ToBase64(comment); @@ -185,26 +184,16 @@ export const SuperLike = ({ setIsOpenMultiplePublish(true); } catch (error: any) { - let notificationObj: any = null; - if (typeof error === "string") { - notificationObj = { - msg: error || "Failed to publish Super Like", + dispatch( + setNotification({ + msg: + error || + error?.error || + error?.message || + "Failed to publish Super Like", alertType: "error", - }; - } else if (typeof error?.error === "string") { - notificationObj = { - msg: error?.error || "Failed to publish Super Like", - alertType: "error", - }; - } else { - notificationObj = { - msg: error?.message || "Failed to publish Super Like", - alertType: "error", - }; - } - if (!notificationObj) return; - dispatch(setNotification(notificationObj)); - + }) + ); throw new Error("Failed to publish Super Like"); } } @@ -239,8 +228,6 @@ export const SuperLike = ({ flexShrink: 0, }} > - - - {numberOfSuperlikes === 0 ? null : ( -
- {numberOfSuperlikes} - {"Qort - {truncateNumber(totalAmount,0)} -
- )} + {numberOfSuperlikes === 0 ? null : ( +
+ + {numberOfSuperlikes} + + {"Qort + {truncateNumber(totalAmount, 0)} +
+ )}
@@ -301,10 +294,10 @@ export const SuperLike = ({ - Amount in QORT (min 10 QORT) + Amount in QORT (min 1 QORT) ({ + position: "relative", + display: "flex", + flexDirection: "column", + alignItems: "center", + justifyContent: "center", + width: "100%", + height: "100%", + margin: 0, + padding: 0, + maxHeight: "70vh", + "&:focus": { outline: "none" }, +})); export const VideoElement = styled("video")` width: 100%; diff --git a/src/components/common/VideoPlayer/VideoPlayer.tsx b/src/components/common/VideoPlayer/VideoPlayer.tsx index 92b4498..3cbfc5e 100644 --- a/src/components/common/VideoPlayer/VideoPlayer.tsx +++ b/src/components/common/VideoPlayer/VideoPlayer.tsx @@ -1,4 +1,12 @@ -import React, { useContext, useEffect, useMemo, useRef, useState } from "react"; +import React, { + MutableRefObject, + useContext, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from "react"; import ReactDOM from "react-dom"; import { Box, IconButton, Slider } from "@mui/material"; import { CircularProgress, Typography } from "@mui/material"; @@ -45,922 +53,947 @@ interface VideoPlayerProps { style?: CSS.Properties; } -export const VideoPlayer: React.FC = ({ - poster, - name, - identifier, - service, - autoplay = true, - from = null, - customStyle = {}, - user = "", - jsonId = "", - nextVideo, - onEnd, - autoPlay, - style = {}, -}) => { - const videoSelector = useSelector((state: RootState) => state.video); - const persistSelector = useSelector((state: RootState) => state.persist); - const dispatch = useDispatch(); - const videoRef = useRef(null); - const [playing, setPlaying] = useState(false); - const [volume, setVolume] = useState(1); - const [mutedVolume, setMutedVolume] = useState(1); - const [isMuted, setIsMuted] = useState(false); - const [progress, setProgress] = useState(0); - const [isLoading, setIsLoading] = useState(false); - const [canPlay, setCanPlay] = useState(false); - const [startPlay, setStartPlay] = useState(false); - const [isMobileView, setIsMobileView] = useState(false); - const [playbackRate, setPlaybackRate] = useState( - persistSelector.playbackRate - ); - const [anchorEl, setAnchorEl] = useState(null); - const [showControlsFullScreen, setShowControlsFullScreen] = - useState(true); - const videoPlaying = useSelector( - (state: RootState) => state.global.videoPlaying - ); - const { downloads } = useSelector((state: RootState) => state.global); - - const reDownload = useRef(false); - const reDownloadNextVid = useRef(false); - - const isFetchingProperties = useRef(false); - - const status = useRef(null); - - const download = useMemo(() => { - if (!downloads || !identifier) return {}; - const findDownload = downloads[identifier]; - - if (!findDownload) return {}; - return findDownload; - }, [downloads, identifier]); - - const src = useMemo(() => { - return download?.url || ""; - }, [download?.url]); - const resourceStatus = useMemo(() => { - return download?.status || {}; - }, [download]); - - const minSpeed = 0.25; - const maxSpeed = 4.0; - const speedChange = 0.25; - - const updatePlaybackRate = (newSpeed: number) => { - if (videoRef.current) { - if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed; - - videoRef.current.playbackRate = playbackRate; - setPlaybackRate(newSpeed); - dispatch(setReduxPlaybackRate(newSpeed)); - } - }; +export type refType = { + getContainerRef: () => React.MutableRefObject; + getVideoRef: () => React.MutableRefObject; +}; +export const VideoPlayer = React.forwardRef( + ( + { + poster, + name, + identifier, + service, + autoplay = true, + from = null, + customStyle = {}, + user = "", + jsonId = "", + nextVideo, + onEnd, + autoPlay, + style = {}, + }: VideoPlayerProps, + ref + ) => { + const videoSelector = useSelector((state: RootState) => state.video); + const persistSelector = useSelector((state: RootState) => state.persist); + const dispatch = useDispatch(); + const videoRef = useRef(null); + const containerRef = useRef(null); + const [playing, setPlaying] = useState(false); + const [volume, setVolume] = useState(1); + const [mutedVolume, setMutedVolume] = useState(1); + const [isMuted, setIsMuted] = useState(false); + const [progress, setProgress] = useState(0); + const [isLoading, setIsLoading] = useState(false); + const [canPlay, setCanPlay] = useState(false); + const [startPlay, setStartPlay] = useState(false); + const [isMobileView, setIsMobileView] = useState(false); + const [playbackRate, setPlaybackRate] = useState( + persistSelector.playbackRate + ); + const [anchorEl, setAnchorEl] = useState(null); + const [showControlsFullScreen, setShowControlsFullScreen] = + useState(true); + const videoPlaying = useSelector( + (state: RootState) => state.global.videoPlaying + ); + const { downloads } = useSelector((state: RootState) => state.global); + + const reDownload = useRef(false); + const reDownloadNextVid = useRef(false); + + const isFetchingProperties = useRef(false); + + const status = useRef(null); + + const download = useMemo(() => { + if (!downloads || !identifier) return {}; + const findDownload = downloads[identifier]; + + if (!findDownload) return {}; + return findDownload; + }, [downloads, identifier]); + + const src = useMemo(() => { + return download?.url || ""; + }, [download?.url]); + const resourceStatus = useMemo(() => { + return download?.status || {}; + }, [download]); + + const minSpeed = 0.25; + const maxSpeed = 4.0; + const speedChange = 0.25; + + useImperativeHandle(ref, () => ({ + getVideoRef: () => { + return videoRef; + }, + getContainerRef: () => { + return containerRef; + }, + })); + const updatePlaybackRate = (newSpeed: number) => { + if (videoRef.current) { + if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed; + + videoRef.current.playbackRate = playbackRate; + setPlaybackRate(newSpeed); + dispatch(setReduxPlaybackRate(newSpeed)); + } + }; - const increaseSpeed = (wrapOverflow = true) => { - const changedSpeed = playbackRate + speedChange; - let newSpeed = wrapOverflow - ? changedSpeed - : Math.min(changedSpeed, maxSpeed); + const increaseSpeed = (wrapOverflow = true) => { + const changedSpeed = playbackRate + speedChange; + let newSpeed = wrapOverflow + ? changedSpeed + : Math.min(changedSpeed, maxSpeed); - if (videoRef.current) { - updatePlaybackRate(newSpeed); - } - }; + if (videoRef.current) { + updatePlaybackRate(newSpeed); + } + }; - const decreaseSpeed = () => { - if (videoRef.current) { - updatePlaybackRate(playbackRate - speedChange); - } - }; - - useEffect(() => { - reDownload.current = false; - reDownloadNextVid.current = false; - setIsLoading(false); - setCanPlay(false); - setProgress(0); - setPlaying(false); - setStartPlay(false); - isFetchingProperties.current = false; - status.current = null; - }, [identifier]); - - useEffect(() => { - if (autoPlay && identifier) { - setStartPlay(true); - setPlaying(true); - togglePlay(undefined, true); - } - }, [autoPlay, startPlay, identifier]); - - const refetch = React.useCallback(async () => { - if (!name || !identifier || !service || isFetchingProperties.current) - return; - try { - isFetchingProperties.current = true; - await qortalRequest({ - action: "GET_QDN_RESOURCE_PROPERTIES", - name, - service, - identifier, - }); - } catch (error) { - } finally { + const decreaseSpeed = () => { + if (videoRef.current) { + updatePlaybackRate(playbackRate - speedChange); + } + }; + + useEffect(() => { + reDownload.current = false; + reDownloadNextVid.current = false; + setIsLoading(false); + setCanPlay(false); + setProgress(0); + setPlaying(false); + setStartPlay(false); isFetchingProperties.current = false; - } - }, [identifier, name, service]); - - const toggleRef = useRef(null); - const { downloadVideo } = useContext(MyContext); - const togglePlay = async (event?: any, isPlay?: boolean) => { - if (!videoRef.current) return; - setStartPlay(true); - if (!src || resourceStatus?.status !== "READY") { - const el = document.getElementById("videoWrapper"); - if (el) { - el?.parentElement?.removeChild(el); + status.current = null; + }, [identifier]); + + useEffect(() => { + if (autoPlay && identifier) { + setStartPlay(true); + setPlaying(true); + togglePlay(undefined, true); } - ReactDOM.flushSync(() => { - setIsLoading(true); - }); - getSrc(); - } - if (playing && !isPlay) { - videoRef.current.pause(); - } else { - videoRef.current.play(); - } - if (isPlay) { - setPlaying(true); - } else { - setPlaying(!playing); - } - }; - - const onVolumeChange = (_: any, value: number | number[]) => { - if (!videoRef.current) return; - videoRef.current.volume = value as number; - setVolume(value as number); - setIsMuted(false); - }; - - const onProgressChange = (_: any, value: number | number[]) => { - if (!videoRef.current) return; - videoRef.current.currentTime = value as number; - setProgress(value as number); - if (!playing) { - videoRef.current.play(); - setPlaying(true); - } - }; + }, [autoPlay, startPlay, identifier]); + + const refetch = React.useCallback(async () => { + if (!name || !identifier || !service || isFetchingProperties.current) + return; + try { + isFetchingProperties.current = true; + await qortalRequest({ + action: "GET_QDN_RESOURCE_PROPERTIES", + name, + service, + identifier, + }); + } catch (error) { + } finally { + isFetchingProperties.current = false; + } + }, [identifier, name, service]); - const handleEnded = () => { - setPlaying(false); - if (onEnd) { - onEnd(); - } - }; + const toggleRef = useRef(null); + const { downloadVideo } = useContext(MyContext); + const togglePlay = async (event?: any, isPlay?: boolean) => { + if (!videoRef.current) return; + setStartPlay(true); + if (!src || resourceStatus?.status !== "READY") { + const el = document.getElementById("videoWrapper"); + if (el) { + el?.parentElement?.removeChild(el); + } + ReactDOM.flushSync(() => { + setIsLoading(true); + }); + getSrc(); + } + if (playing && !isPlay) { + videoRef.current.pause(); + } else { + videoRef.current.play(); + } + if (isPlay) { + setPlaying(true); + } else { + setPlaying(!playing); + } + }; - const updateProgress = () => { - if (!videoRef.current) return; - setProgress(videoRef.current.currentTime); - }; + const onVolumeChange = (_: any, value: number | number[]) => { + if (!videoRef.current) return; + videoRef.current.volume = value as number; + setVolume(value as number); + setIsMuted(false); + }; - const [isFullscreen, setIsFullscreen] = useState(false); + const onProgressChange = (_: any, value: number | number[]) => { + if (!videoRef.current) return; + videoRef.current.currentTime = value as number; + setProgress(value as number); + if (!playing) { + videoRef.current.play(); + setPlaying(true); + } + }; - const enterFullscreen = () => { - if (!videoRef.current) return; - if (videoRef.current.requestFullscreen) { - videoRef.current.requestFullscreen(); - } - }; + const handleEnded = () => { + setPlaying(false); + if (onEnd) { + onEnd(); + } + }; - const exitFullscreen = () => { - if (document.exitFullscreen) { - document.exitFullscreen(); - } - }; - - const toggleFullscreen = () => { - isFullscreen ? exitFullscreen() : enterFullscreen(); - }; - const togglePictureInPicture = async () => { - if (!videoRef.current) return; - if (document.pictureInPictureElement === videoRef.current) { - await document.exitPictureInPicture(); - } else { - await videoRef.current.requestPictureInPicture(); - } - }; + const updateProgress = () => { + if (!videoRef.current) return; + setProgress(videoRef.current.currentTime); + }; + + const [isFullscreen, setIsFullscreen] = useState(false); - useEffect(() => { - const handleFullscreenChange = () => { - setIsFullscreen(!!document.fullscreenElement); + const enterFullscreen = () => { + if (!videoRef.current) return; + if (videoRef.current.requestFullscreen) { + videoRef.current.requestFullscreen(); + } }; - document.addEventListener("fullscreenchange", handleFullscreenChange); - return () => { - document.removeEventListener("fullscreenchange", handleFullscreenChange); + const exitFullscreen = () => { + if (document.exitFullscreen) { + document.exitFullscreen(); + } }; - }, []); - - useEffect(() => { - if ( - videoPlaying && - videoPlaying.id === identifier && - src && - videoRef?.current - ) { - handleCanPlay(); - videoRef.current.volume = videoPlaying.volume; - videoRef.current.currentTime = videoPlaying.currentTime; - videoRef.current.play(); - setPlaying(true); - setStartPlay(true); - dispatch(setVideoPlaying(null)); - } - }, [videoPlaying, identifier, src]); - - const handleCanPlay = () => { - setIsLoading(false); - setCanPlay(true); - updatePlaybackRate(playbackRate); - }; - - const getSrc = React.useCallback(async () => { - if (!name || !identifier || !service || !jsonId || !user) return; - try { - downloadVideo({ - name, - service, - identifier, - properties: { - jsonId, - user, - }, - }); - } catch (error) { - console.error(error); - } - }, [identifier, name, service, jsonId, user]); - useEffect(() => { - const videoElement = videoRef.current; + const toggleFullscreen = () => { + isFullscreen ? exitFullscreen() : enterFullscreen(); + }; + const togglePictureInPicture = async () => { + if (!videoRef.current) return; + if (document.pictureInPictureElement === videoRef.current) { + await document.exitPictureInPicture(); + } else { + await videoRef.current.requestPictureInPicture(); + } + }; - const handleLeavePictureInPicture = async (event: any) => { - const target = event?.target; - if (target) { - target.pause(); - if (setPlaying) { - setPlaying(false); - } + useEffect(() => { + const handleFullscreenChange = () => { + setIsFullscreen(!!document.fullscreenElement); + }; + + document.addEventListener("fullscreenchange", handleFullscreenChange); + return () => { + document.removeEventListener( + "fullscreenchange", + handleFullscreenChange + ); + }; + }, []); + + useEffect(() => { + if ( + videoPlaying && + videoPlaying.id === identifier && + src && + videoRef?.current + ) { + handleCanPlay(); + videoRef.current.volume = videoPlaying.volume; + videoRef.current.currentTime = videoPlaying.currentTime; + videoRef.current.play(); + setPlaying(true); + setStartPlay(true); + dispatch(setVideoPlaying(null)); } + }, [videoPlaying, identifier, src]); + + const handleCanPlay = () => { + setIsLoading(false); + setCanPlay(true); + updatePlaybackRate(playbackRate); }; - if (videoElement) { - videoElement.addEventListener( - "leavepictureinpicture", - handleLeavePictureInPicture - ); - } + const getSrc = React.useCallback(async () => { + if (!name || !identifier || !service || !jsonId || !user) return; + try { + downloadVideo({ + name, + service, + identifier, + properties: { + jsonId, + user, + }, + }); + } catch (error) { + console.error(error); + } + }, [identifier, name, service, jsonId, user]); + + useEffect(() => { + const videoElement = videoRef.current; + + const handleLeavePictureInPicture = async (event: any) => { + const target = event?.target; + if (target) { + target.pause(); + if (setPlaying) { + setPlaying(false); + } + } + }; - return () => { if (videoElement) { - videoElement.removeEventListener( + videoElement.addEventListener( "leavepictureinpicture", handleLeavePictureInPicture ); } - }; - }, []); - - useEffect(() => { - const videoElement = videoRef.current; - - const minimizeVideo = async () => { - if (!videoElement) return; - - dispatch(setVideoPlaying(videoElement)); - // const handleClose = () => { - // if (videoElement && videoElement.parentElement) { - // const el = document.getElementById('videoWrapper') - // if (el) { - // el?.parentElement?.removeChild(el) - // } - // } - // } - // const createCloseButton = (): HTMLButtonElement => { - // const closeButton = document.createElement('button') - // closeButton.textContent = 'X' - // closeButton.style.position = 'absolute' - // closeButton.style.top = '0' - // closeButton.style.right = '0' - // closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.7)' - // closeButton.style.border = 'none' - // closeButton.style.fontWeight = 'bold' - // closeButton.style.fontSize = '1.2rem' - // closeButton.style.cursor = 'pointer' - // closeButton.style.padding = '2px 8px' - // closeButton.style.borderRadius = '0 0 0 4px' - - // closeButton.addEventListener('click', handleClose) - - // return closeButton - // } - // const buttonClose = createCloseButton() - // const videoWrapper = document.createElement('div') - // videoWrapper.id = 'videoWrapper' - // videoWrapper.style.position = 'fixed' - // videoWrapper.style.zIndex = '900000009' - // videoWrapper.style.bottom = '0px' - // videoWrapper.style.right = '0px' - - // videoElement.parentElement?.insertBefore(videoWrapper, videoElement) - // videoWrapper.appendChild(videoElement) - - // videoWrapper.appendChild(buttonClose) - // videoElement.controls = true - // videoElement.style.height = 'auto' - // videoElement.style.width = '300px' - - // document.body.appendChild(videoWrapper) - }; - return () => { - if (videoElement) { - if (videoElement && !videoElement.paused && !videoElement.ended) { - minimizeVideo(); + return () => { + if (videoElement) { + videoElement.removeEventListener( + "leavepictureinpicture", + handleLeavePictureInPicture + ); } - } - }; - }, []); + }; + }, []); + + useEffect(() => { + const videoElement = videoRef.current; + + const minimizeVideo = async () => { + if (!videoElement) return; + + dispatch(setVideoPlaying(videoElement)); + // const handleClose = () => { + // if (videoElement && videoElement.parentElement) { + // const el = document.getElementById('videoWrapper') + // if (el) { + // el?.parentElement?.removeChild(el) + // } + // } + // } + // const createCloseButton = (): HTMLButtonElement => { + // const closeButton = document.createElement('button') + // closeButton.textContent = 'X' + // closeButton.style.position = 'absolute' + // closeButton.style.top = '0' + // closeButton.style.right = '0' + // closeButton.style.backgroundColor = 'rgba(255, 255, 255, 0.7)' + // closeButton.style.border = 'none' + // closeButton.style.fontWeight = 'bold' + // closeButton.style.fontSize = '1.2rem' + // closeButton.style.cursor = 'pointer' + // closeButton.style.padding = '2px 8px' + // closeButton.style.borderRadius = '0 0 0 4px' + + // closeButton.addEventListener('click', handleClose) + + // return closeButton + // } + // const buttonClose = createCloseButton() + // const videoWrapper = document.createElement('div') + // videoWrapper.id = 'videoWrapper' + // videoWrapper.style.position = 'fixed' + // videoWrapper.style.zIndex = '900000009' + // videoWrapper.style.bottom = '0px' + // videoWrapper.style.right = '0px' + + // videoElement.parentElement?.insertBefore(videoWrapper, videoElement) + // videoWrapper.appendChild(videoElement) + + // videoWrapper.appendChild(buttonClose) + // videoElement.controls = true + // videoElement.style.height = 'auto' + // videoElement.style.width = '300px' + + // document.body.appendChild(videoWrapper) + }; + + return () => { + if (videoElement) { + if (videoElement && !videoElement.paused && !videoElement.ended) { + minimizeVideo(); + } + } + }; + }, []); - function formatTime(seconds: number): string { - seconds = Math.floor(seconds); - let minutes: number | string = Math.floor(seconds / 60); - let hours: number | string = Math.floor(minutes / 60); + function formatTime(seconds: number): string { + seconds = Math.floor(seconds); + let minutes: number | string = Math.floor(seconds / 60); + let hours: number | string = Math.floor(minutes / 60); - let remainingSeconds: number | string = seconds % 60; - let remainingMinutes: number | string = minutes % 60; + let remainingSeconds: number | string = seconds % 60; + let remainingMinutes: number | string = minutes % 60; - if (remainingSeconds < 10) { - remainingSeconds = "0" + remainingSeconds; - } + if (remainingSeconds < 10) { + remainingSeconds = "0" + remainingSeconds; + } - if (remainingMinutes < 10) { - remainingMinutes = "0" + remainingMinutes; - } + if (remainingMinutes < 10) { + remainingMinutes = "0" + remainingMinutes; + } - if (hours === 0) { - hours = ""; - } else { - hours = hours + ":"; + if (hours === 0) { + hours = ""; + } else { + hours = hours + ":"; + } + + return hours + remainingMinutes + ":" + remainingSeconds; } - return hours + remainingMinutes + ":" + remainingSeconds; - } + const reloadVideo = () => { + if (!videoRef.current) return; + const currentTime = videoRef.current.currentTime; + videoRef.current.src = src; + videoRef.current.load(); + videoRef.current.currentTime = currentTime; + if (playing) { + videoRef.current.play(); + } + }; - const reloadVideo = () => { - if (!videoRef.current) return; - const currentTime = videoRef.current.currentTime; - videoRef.current.src = src; - videoRef.current.load(); - videoRef.current.currentTime = currentTime; - if (playing) { - videoRef.current.play(); - } - }; + const refetchInInterval = () => { + try { + const interval = setInterval(() => { + if (status?.current === "DOWNLOADED") { + refetch(); + } + if (status?.current === "READY") { + clearInterval(interval); + } + }, 7500); + } catch (error) {} + }; - const refetchInInterval = () => { - try { - const interval = setInterval(() => { - if (status?.current === "DOWNLOADED") { - refetch(); - } - if (status?.current === "READY") { - clearInterval(interval); + useEffect(() => { + if (resourceStatus?.status) { + status.current = resourceStatus?.status; + } + if ( + resourceStatus?.status === "DOWNLOADED" && + reDownload?.current === false + ) { + refetchInInterval(); + reDownload.current = true; + } + }, [getSrc, resourceStatus]); + + useEffect(() => { + if (resourceStatus?.status) { + status.current = resourceStatus?.status; + } + if ( + resourceStatus?.status === "READY" && + reDownloadNextVid?.current === false + ) { + if (nextVideo) { + downloadVideo({ + name: nextVideo?.name, + service: nextVideo?.service, + identifier: nextVideo?.identifier, + properties: { + jsonId: nextVideo?.jsonId, + user, + }, + }); } - }, 7500); - } catch (error) {} - }; + reDownloadNextVid.current = true; + } + }, [getSrc, resourceStatus]); - useEffect(() => { - if (resourceStatus?.status) { - status.current = resourceStatus?.status; - } - if ( - resourceStatus?.status === "DOWNLOADED" && - reDownload?.current === false - ) { - refetchInInterval(); - reDownload.current = true; - } - }, [getSrc, resourceStatus]); + const handleMenuOpen = (event: any) => { + setAnchorEl(event.currentTarget); + }; - useEffect(() => { - if (resourceStatus?.status) { - status.current = resourceStatus?.status; - } - if ( - resourceStatus?.status === "READY" && - reDownloadNextVid?.current === false - ) { - if (nextVideo) { - downloadVideo({ - name: nextVideo?.name, - service: nextVideo?.service, - identifier: nextVideo?.identifier, - properties: { - jsonId: nextVideo?.jsonId, - user, - }, - }); + const handleMenuClose = () => { + setAnchorEl(null); + }; + + useEffect(() => { + const videoWidth = videoRef?.current?.offsetWidth; + if (videoWidth && videoWidth <= 600) { + setIsMobileView(true); } - reDownloadNextVid.current = true; - } - }, [getSrc, resourceStatus]); + }, [canPlay]); - const handleMenuOpen = (event: any) => { - setAnchorEl(event.currentTarget); - }; + const getDownloadProgress = (current: number, total: number) => { + const progress = (current / total) * 100; + return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%"; + }; + const mute = () => { + setIsMuted(true); + setMutedVolume(volume); + setVolume(0); + if (videoRef.current) videoRef.current.volume = 0; + }; + const unMute = () => { + setIsMuted(false); + setVolume(mutedVolume); + if (videoRef.current) videoRef.current.volume = mutedVolume; + }; - const handleMenuClose = () => { - setAnchorEl(null); - }; + const toggleMute = () => { + isMuted ? unMute() : mute(); + }; - useEffect(() => { - const videoWidth = videoRef?.current?.offsetWidth; - if (videoWidth && videoWidth <= 600) { - setIsMobileView(true); - } - }, [canPlay]); - - const getDownloadProgress = (current: number, total: number) => { - const progress = (current / total) * 100; - return Number.isNaN(progress) ? "" : progress.toFixed(0) + "%"; - }; - const mute = () => { - setIsMuted(true); - setMutedVolume(volume); - setVolume(0); - if (videoRef.current) videoRef.current.volume = 0; - }; - const unMute = () => { - setIsMuted(false); - setVolume(mutedVolume); - if (videoRef.current) videoRef.current.volume = mutedVolume; - }; - - const toggleMute = () => { - isMuted ? unMute() : mute(); - }; - - const changeVolume = (volumeChange: number) => { - if (videoRef.current) { - const minVolume = 0; - const maxVolume = 1; - - let newVolume = volumeChange + volume; - - newVolume = Math.max(newVolume, minVolume); - newVolume = Math.min(newVolume, maxVolume); + const changeVolume = (volumeChange: number) => { + if (videoRef.current) { + const minVolume = 0; + const maxVolume = 1; - setIsMuted(false); - setMutedVolume(newVolume); - videoRef.current.volume = newVolume; - setVolume(newVolume); - } - }; - const setProgressRelative = (secondsChange: number) => { - if (videoRef.current) { - const currentTime = videoRef.current?.currentTime; - const minTime = 0; - const maxTime = videoRef.current?.duration || 100; - - let newTime = currentTime + secondsChange; - newTime = Math.max(newTime, minTime); - newTime = Math.min(newTime, maxTime); - videoRef.current.currentTime = newTime; - setProgress(newTime); - } - }; - - const setProgressAbsolute = (videoPercent: number) => { - if (videoRef.current) { - videoPercent = Math.min(videoPercent, 100); - videoPercent = Math.max(videoPercent, 0); - const finalTime = (videoRef.current?.duration * videoPercent) / 100; - videoRef.current.currentTime = finalTime; - setProgress(finalTime); - } - }; - - const keyboardShortcutsDown = (e: React.KeyboardEvent) => { - e.preventDefault(); - - switch (e.key) { - case Key.Add: - increaseSpeed(false); - break; - case "+": - increaseSpeed(false); - break; - case ">": - increaseSpeed(false); - break; - - case Key.Subtract: - decreaseSpeed(); - break; - case "-": - decreaseSpeed(); - break; - case "<": - decreaseSpeed(); - break; - - case Key.ArrowLeft: - { - if (e.shiftKey) setProgressRelative(-300); - else if (e.ctrlKey) setProgressRelative(-60); - else if (e.altKey) setProgressRelative(-10); - else setProgressRelative(-5); - } - break; - - case Key.ArrowRight: - { - if (e.shiftKey) setProgressRelative(300); - else if (e.ctrlKey) setProgressRelative(60); - else if (e.altKey) setProgressRelative(10); - else setProgressRelative(5); - } - break; - - case Key.ArrowDown: - changeVolume(-0.05); - break; - case Key.ArrowUp: - changeVolume(0.05); - break; - } - }; - - const keyboardShortcutsUp = (e: React.KeyboardEvent) => { - e.preventDefault(); - - switch (e.key) { - case " ": - togglePlay(); - break; - case "m": - toggleMute(); - break; - - case "f": - enterFullscreen(); - break; - case Key.Escape: - exitFullscreen(); - break; - - case "0": - setProgressAbsolute(0); - break; - case "1": - setProgressAbsolute(10); - break; - case "2": - setProgressAbsolute(20); - break; - case "3": - setProgressAbsolute(30); - break; - case "4": - setProgressAbsolute(40); - break; - case "5": - setProgressAbsolute(50); - break; - case "6": - setProgressAbsolute(60); - break; - case "7": - setProgressAbsolute(70); - break; - case "8": - setProgressAbsolute(80); - break; - case "9": - setProgressAbsolute(90); - break; - } - }; - - return ( - - {isLoading && ( - - - {resourceStatus && ( - - {resourceStatus?.status === "NOT_PUBLISHED" && ( - <>Video file was not published. Please inform the publisher! - )} - {resourceStatus?.status === "REFETCHING" ? ( - <> + let newVolume = volumeChange + volume; + + newVolume = Math.max(newVolume, minVolume); + newVolume = Math.min(newVolume, maxVolume); + + setIsMuted(false); + setMutedVolume(newVolume); + videoRef.current.volume = newVolume; + setVolume(newVolume); + } + }; + const setProgressRelative = (secondsChange: number) => { + if (videoRef.current) { + const currentTime = videoRef.current?.currentTime; + const minTime = 0; + const maxTime = videoRef.current?.duration || 100; + + let newTime = currentTime + secondsChange; + newTime = Math.max(newTime, minTime); + newTime = Math.min(newTime, maxTime); + videoRef.current.currentTime = newTime; + setProgress(newTime); + } + }; + + const setProgressAbsolute = (videoPercent: number) => { + if (videoRef.current) { + videoPercent = Math.min(videoPercent, 100); + videoPercent = Math.max(videoPercent, 0); + const finalTime = (videoRef.current?.duration * videoPercent) / 100; + videoRef.current.currentTime = finalTime; + setProgress(finalTime); + } + }; + + const keyboardShortcutsDown = (e: React.KeyboardEvent) => { + e.preventDefault(); + + switch (e.key) { + case Key.Add: + increaseSpeed(false); + break; + case "+": + increaseSpeed(false); + break; + case ">": + increaseSpeed(false); + break; + + case Key.Subtract: + decreaseSpeed(); + break; + case "-": + decreaseSpeed(); + break; + case "<": + decreaseSpeed(); + break; + + case Key.ArrowLeft: + { + if (e.shiftKey) setProgressRelative(-300); + else if (e.ctrlKey) setProgressRelative(-60); + else if (e.altKey) setProgressRelative(-10); + else setProgressRelative(-5); + } + break; + + case Key.ArrowRight: + { + if (e.shiftKey) setProgressRelative(300); + else if (e.ctrlKey) setProgressRelative(60); + else if (e.altKey) setProgressRelative(10); + else setProgressRelative(5); + } + break; + + case Key.ArrowDown: + changeVolume(-0.05); + break; + case Key.ArrowUp: + changeVolume(0.05); + break; + } + }; + + const keyboardShortcutsUp = (e: React.KeyboardEvent) => { + e.preventDefault(); + + switch (e.key) { + case " ": + togglePlay(); + break; + case "m": + toggleMute(); + break; + + case "f": + enterFullscreen(); + break; + case Key.Escape: + exitFullscreen(); + break; + + case "0": + setProgressAbsolute(0); + break; + case "1": + setProgressAbsolute(10); + break; + case "2": + setProgressAbsolute(20); + break; + case "3": + setProgressAbsolute(30); + break; + case "4": + setProgressAbsolute(40); + break; + case "5": + setProgressAbsolute(50); + break; + case "6": + setProgressAbsolute(60); + break; + case "7": + setProgressAbsolute(70); + break; + case "8": + setProgressAbsolute(80); + break; + case "9": + setProgressAbsolute(90); + break; + } + }; + + return ( + + {isLoading && ( + + + {resourceStatus && ( + + {resourceStatus?.status === "NOT_PUBLISHED" && ( + <> + Video file was not published. Please inform the publisher! + + )} + {resourceStatus?.status === "REFETCHING" ? ( + <> + <> + {getDownloadProgress( + resourceStatus?.localChunkCount, + resourceStatus?.totalChunkCount + )} + + + <> Refetching in 25 seconds + + ) : resourceStatus?.status === "DOWNLOADED" ? ( + <>Download Completed: building video... + ) : resourceStatus?.status !== "READY" ? ( <> {getDownloadProgress( resourceStatus?.localChunkCount, resourceStatus?.totalChunkCount )} - - <> Refetching in 25 seconds - - ) : resourceStatus?.status === "DOWNLOADED" ? ( - <>Download Completed: building video... - ) : resourceStatus?.status !== "READY" ? ( - <> - {getDownloadProgress( - resourceStatus?.localChunkCount, - resourceStatus?.totalChunkCount - )} - - ) : ( - <>Fetching video... - )} - - )} - - )} - {((!src && !isLoading) || !startPlay) && ( - { - if (from === "create") return; - dispatch(setVideoPlaying(null)); - togglePlay(); - }} - sx={{ - cursor: "pointer", - }} - > - Fetching video... + )} + + )} + + )} + {((!src && !isLoading) || !startPlay) && ( + { + if (from === "create") return; + dispatch(setVideoPlaying(null)); + togglePlay(); + }} sx={{ - width: "50px", - height: "50px", - color: "white", + cursor: "pointer", }} - /> - - )} - - { - setShowControlsFullScreen(true); - }} - onMouseLeave={e => { - setShowControlsFullScreen(false); - }} - preload="metadata" - style={ - startPlay - ? { - ...customStyle, - objectFit: persistSelector.stretchVideoSetting, - height: "calc(100% - 80px)", - } - : { ...customStyle, height: "100%" } - } - /> - - - {isMobileView && canPlay && showControlsFullScreen ? ( - <> - - {playing ? : } - - + - - - - - - - - - - - - increaseSpeed()}> - - Speed: {playbackRate}x - - - + + )} + + { + setShowControlsFullScreen(true); + }} + onMouseLeave={e => { + setShowControlsFullScreen(false); + }} + preload="metadata" + style={ + startPlay + ? { + ...customStyle, + objectFit: persistSelector.stretchVideoSetting, + height: "calc(100% - 80px)", + } + : { ...customStyle, height: "100%" } + } + /> + + + {isMobileView && canPlay && showControlsFullScreen ? ( + <> + + {playing ? : } + + + + + + + + + + + + + + increaseSpeed()}> + + Speed: {playbackRate}x + + + + + + + + + + + ) : canPlay ? ( + <> + + {playing ? : } + + + + + + + {progress && videoRef.current?.duration && formatTime(progress)} + / + {progress && + videoRef.current?.duration && + formatTime(videoRef.current?.duration)} + + + {isMuted ? : } + + + increaseSpeed()} + > + Speed: {playbackRate}x + + + - - + + - - - - ) : canPlay ? ( - <> - - {playing ? : } - - - - - - - {progress && videoRef.current?.duration && formatTime(progress)}/ - {progress && - videoRef.current?.duration && - formatTime(videoRef.current?.duration)} - - - {isMuted ? : } - - - increaseSpeed()} - > - Speed: {playbackRate}x - - - - - - - - - - ) : null} - - - ); -}; + + + ) : null} + + + ); + } +); diff --git a/src/components/layout/Navbar/Navbar.tsx b/src/components/layout/Navbar/Navbar.tsx index df28207..051a3cd 100644 --- a/src/components/layout/Navbar/Navbar.tsx +++ b/src/components/layout/Navbar/Navbar.tsx @@ -35,8 +35,8 @@ import { } from "../../../state/features/videoSlice"; import { RootState } from "../../../state/store"; import { useWindowSize } from "../../../hooks/useWindowSize"; -import { PublishVideo } from "../../PublishVideo/PublishVideo.tsx"; -import { StyledButton } from "../../PublishVideo/PublishVideo-styles.tsx"; +import { PublishVideo } from "../../Publish/PublishVideo/PublishVideo.tsx"; +import { StyledButton } from "../../Publish/PublishVideo/PublishVideo-styles.tsx"; import { Notifications } from "../../common/Notifications/Notifications"; interface Props { isAuthenticated: boolean; diff --git a/src/constants/Categories.ts b/src/constants/Categories.ts index 85b4ec1..63ac90e 100644 --- a/src/constants/Categories.ts +++ b/src/constants/Categories.ts @@ -39,6 +39,7 @@ export const categories = [ { id: 24, name: "Anime" }, { id: 25, name: "Cartoons" }, { id: 26, name: "Qortal" }, + { id: 99, name: "Other" }, ].sort(sortCategory); export const subCategories: CategoryMap = { @@ -59,7 +60,7 @@ export const subCategories: CategoryMap = { { id: 113, name: "Indie Films" }, { id: 114, name: "International Films" }, { id: 115, name: "Biographies & True Stories" }, - { id: 116, name: "Other" }, + { id: 199, name: "Other" }, ].sort(sortCategory), 2: [ // Series @@ -78,14 +79,14 @@ export const subCategories: CategoryMap = { { id: 213, name: "Anthologies" }, { id: 214, name: "International Series" }, { id: 215, name: "Miniseries" }, - { id: 216, name: "Other" }, + { id: 299, name: "Other" }, ].sort(sortCategory), 4: [ // Education { id: 400, name: "Tutorial" }, - { id: 401, name: "Documentary" }, { id: 401, name: "Qortal" }, - { id: 402, name: "Other" }, + { id: 402, name: "Documentary" }, + { id: 499, name: "Other" }, ].sort(sortCategory), 24: [ @@ -102,6 +103,6 @@ export const subCategories: CategoryMap = { { id: 2411, name: "Harem" }, { id: 2412, name: "Ecchi" }, { id: 2413, name: "Idol" }, - { id: 2414, name: "Other" }, + { id: 2499, name: "Other" }, ].sort(sortCategory), }; diff --git a/src/constants/Identifiers.ts b/src/constants/Identifiers.ts index d568662..b31b257 100644 --- a/src/constants/Identifiers.ts +++ b/src/constants/Identifiers.ts @@ -8,8 +8,12 @@ export const QTUBE_PLAYLIST_BASE = useTestIdentifiers export const SUPER_LIKE_BASE = useTestIdentifiers ? "MYTEST_superlike_" : "qtube_superlike_"; + +export const LIKE_BASE = useTestIdentifiers ? "MYTEST_like_" : "qtube_like_"; + export const COMMENT_BASE = useTestIdentifiers ? "qcomment_v1_MYTEST_" : "qcomment_v1_qtube_"; export const FOR = useTestIdentifiers ? "FORTEST5" : "FOR0962"; export const FOR_SUPER_LIKE = useTestIdentifiers ? "MYTEST_sl" : `qtube_sl`; +export const FOR_LIKE = useTestIdentifiers ? "MYTEST_like" : `qtube_like`; diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts index bed6f07..2edb2a9 100644 --- a/src/constants/Misc.ts +++ b/src/constants/Misc.ts @@ -1,6 +1,3 @@ -export const minPriceSuperlike = 10; +export const minPriceSuperlike = 1; export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g; export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g; - -export const allTabValue = "all"; -export const subscriptionTabValue = "subscriptions"; diff --git a/src/hooks/useFetchVideos.tsx b/src/hooks/useFetchVideos.tsx index f181bff..d9de1db 100644 --- a/src/hooks/useFetchVideos.tsx +++ b/src/hooks/useFetchVideos.tsx @@ -24,9 +24,9 @@ import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE, } from "../constants/Identifiers.ts"; -import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts"; import { persistReducer } from "redux-persist"; import { subscriptionListFilter } from "../App.tsx"; +import { ContentType, VideoListType } from "../state/features/persistSlice.ts"; export const useFetchVideos = () => { const dispatch = useDispatch(); @@ -194,15 +194,15 @@ export const useFetchVideos = () => { category?: string; subcategory?: string; keywords?: string; - type?: string; + contentType?: ContentType; }; - const emptyFilters = { + const emptyFilters: FilterType = { name: "", category: "", subcategory: "", keywords: "", - type: "", + contentType: "videos", }; const getVideos = React.useCallback( async ( @@ -210,16 +210,16 @@ export const useFetchVideos = () => { reset?: boolean, resetFilters?: boolean, limit?: number, - listType = allTabValue + videoListType: VideoListType = "all" ) => { - emptyFilters.type = filters.type; + emptyFilters.contentType = filters.contentType; try { const { name = "", category = "", subcategory = "", keywords = "", - type = filters.type, + contentType = filters.contentType, }: FilterType = resetFilters ? emptyFilters : filters; let offset = videos.length; if (reset) { @@ -231,9 +231,7 @@ export const useFetchVideos = () => { if (name) { defaultUrl = defaultUrl + `&name=${name}`; - } - - if (listType === subscriptionTabValue) { + } else if (videoListType === "subscriptions") { const filteredSubscribeList = await subscriptionListFilter(false); filteredSubscribeList.map(sub => { defaultUrl += `&name=${sub.subscriberName}`; @@ -252,7 +250,7 @@ export const useFetchVideos = () => { if (keywords) { defaultUrl = defaultUrl + `&query=${keywords}`; } - if (type === "playlists") { + if (contentType === "playlists") { defaultUrl = defaultUrl + `&service=PLAYLIST`; defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`; } else { @@ -431,9 +429,7 @@ export const useFetchVideos = () => { const totalVideosPublished = responseData.length; const uniqueNames = new Set(responseData.map(video => video.name)); const totalNamesPublished = uniqueNames.size; - const videosPerNamePublished = ( - totalVideosPublished / totalNamesPublished - ).toFixed(0); + const videosPerNamePublished = totalVideosPublished / totalNamesPublished; dispatch(setTotalVideosPublished(totalVideosPublished)); dispatch(setTotalNamesPublished(totalNamesPublished)); diff --git a/src/pages/IndividualProfile/IndividualProfile.tsx b/src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx similarity index 53% rename from src/pages/IndividualProfile/IndividualProfile.tsx rename to src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx index acf1d42..5f941d9 100644 --- a/src/pages/IndividualProfile/IndividualProfile.tsx +++ b/src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx @@ -1,34 +1,37 @@ import React, { useMemo } from "react"; -import { VideoListComponentLevel } from "../Home/VideoListComponentLevel"; -import { HeaderContainer, ProfileContainer } from "./Profile-styles"; +import { VideoListComponentLevel } from "../../Home/VideoListComponentLevel.tsx"; +import { HeaderContainer, ProfileContainer } from "./Profile-styles.tsx"; import { AuthorTextComment, StyledCardColComment, StyledCardHeaderComment, -} from "../VideoContent/VideoContent-styles"; +} from "../VideoContent/VideoContent-styles.tsx"; import { Avatar, Box, useTheme } from "@mui/material"; import { useParams } from "react-router-dom"; import { useSelector } from "react-redux"; -import { setUserAvatarHash } from "../../state/features/globalSlice"; -import { RootState } from "../../state/store"; -import { SubscribeButton } from "../../components/common/SubscribeButton.tsx"; +import { setUserAvatarHash } from "../../../state/features/globalSlice.ts"; +import { RootState } from "../../../state/store.ts"; +import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx"; +import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx"; export const IndividualProfile = () => { - const { name: paramName } = useParams(); + const { name: channelName } = useParams(); + const userName = useSelector((state: RootState) => state.auth.user?.name); const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash ); + const theme = useTheme(); const avatarUrl = useMemo(() => { let url = ""; - if (paramName && userAvatarHash[paramName]) { - url = userAvatarHash[paramName]; + if (channelName && userAvatarHash[channelName]) { + url = userAvatarHash[channelName]; } return url; - }, [userAvatarHash, paramName]); + }, [userAvatarHash, channelName]); return ( @@ -46,8 +49,8 @@ export const IndividualProfile = () => { > @@ -58,13 +61,21 @@ export const IndividualProfile = () => { : "#d6e8ff" } > - {paramName} + {channelName} - + {channelName !== userName && ( + <> + + + + )} diff --git a/src/pages/IndividualProfile/Profile-styles.tsx b/src/pages/ContentPages/IndividualProfile/Profile-styles.tsx similarity index 100% rename from src/pages/IndividualProfile/Profile-styles.tsx rename to src/pages/ContentPages/IndividualProfile/Profile-styles.tsx diff --git a/src/pages/PlaylistContent/PlaylistContent-styles.tsx b/src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx similarity index 100% rename from src/pages/PlaylistContent/PlaylistContent-styles.tsx rename to src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx diff --git a/src/pages/PlaylistContent/PlaylistContent.tsx b/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx similarity index 82% rename from src/pages/PlaylistContent/PlaylistContent.tsx rename to src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx index ced8b00..2dce472 100644 --- a/src/pages/PlaylistContent/PlaylistContent.tsx +++ b/src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx @@ -7,15 +7,18 @@ import React, { } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; -import { setIsLoadingGlobal } from "../../state/features/globalSlice"; +import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts"; import { Avatar, Box, Typography, useTheme } from "@mui/material"; -import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx"; -import { RootState } from "../../state/store"; -import { addToHashMap } from "../../state/features/videoSlice"; +import { + refType, + VideoPlayer, +} 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 mockImg from "../../../test/mockimg.jpg"; import { AuthorTextComment, FileAttachmentContainer, @@ -26,39 +29,43 @@ import { VideoDescription, VideoPlayerContainer, VideoTitle, -} from "./PlaylistContent-styles"; -import { setUserAvatarHash } from "../../state/features/globalSlice"; +} from "./PlaylistContent-styles.tsx"; +import { setUserAvatarHash } from "../../../state/features/globalSlice.ts"; import { formatDate, formatDateSeconds, formatTimestampSeconds, -} from "../../utils/time"; -import { NavbarName } from "../../components/layout/Navbar/Navbar-styles"; -import { CommentSection } from "../../components/common/Comments/CommentSection"; +} 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/PublishVideo/PublishVideo-styles.tsx"; -import { Playlists } from "../../components/Playlists/Playlists"; -import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml"; -import FileElement from "../../components/common/FileElement"; -import { SuperLike } from "../../components/common/SuperLike/SuperLike"; -import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes"; +} 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 { extractSigValue, getPaymentInfo, isTimestampWithinRange, -} from "../VideoContent/VideoContent"; -import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection"; +} from "../VideoContent/VideoContent.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/SubscribeButton.tsx"; +} 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, id } = useParams(); + const { name: channelName, id } = useParams(); + const userName = useSelector((state: RootState) => state.auth.user?.name); + const [doAutoPlay, setDoAutoPlay] = useState(false); const [isExpandedDescription, setIsExpandedDescription] = useState(false); @@ -68,6 +75,7 @@ export const PlaylistContent = () => { 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) => { @@ -95,10 +103,10 @@ export const PlaylistContent = () => { }; useEffect(() => { - if (name) { - getAddressName(name); + if (channelName) { + getAddressName(channelName); } - }, [name]); + }, [channelName]); const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash @@ -107,12 +115,12 @@ export const PlaylistContent = () => { const avatarUrl = useMemo(() => { let url = ""; - if (name && userAvatarHash[name]) { - url = userAvatarHash[name]; + if (channelName && userAvatarHash[channelName]) { + url = userAvatarHash[channelName]; } return url; - }, [userAvatarHash, name]); + }, [userAvatarHash, channelName]); const navigate = useNavigate(); const theme = useTheme(); @@ -282,10 +290,10 @@ export const PlaylistContent = () => { ); React.useEffect(() => { - if (name && id) { - checkforPlaylist(name, id); + if (channelName && id) { + checkforPlaylist(channelName, id); } - }, [id, name]); + }, [id, channelName]); useEffect(() => { if (contentRef.current) { @@ -395,6 +403,14 @@ export const PlaylistContent = () => { getComments(videoData?.id, nameAddress); }, [getComments, videoData?.id, nameAddress]); + const focusVideo = (e: React.MouseEvent) => { + const focusRef = containerRef.current?.getContainerRef()?.current; + const isCorrectTarget = e.currentTarget == e.target; + if (focusRef && isCorrectTarget) { + focusRef.focus({ preventScroll: true }); + } + }; + return ( { flexDirection: "column", padding: "20px 10px", }} + onClick={focusVideo} > { name={videoReference?.name} service={videoReference?.service} identifier={videoReference?.identifier} - user={name} + user={channelName} jsonId={id} poster={videoCover || ""} nextVideo={nextVideo} onEnd={onEndVideo} autoPlay={doAutoPlay} customStyle={{ aspectRatio: "16/9" }} + ref={containerRef} /> )} {playlistData && ( @@ -473,12 +491,12 @@ export const PlaylistContent = () => { cursor: "pointer", }} onClick={() => { - navigate(`/channel/${name}`); + navigate(`/channel/${channelName}`); }} > @@ -492,14 +510,22 @@ export const PlaylistContent = () => { cursor: "pointer", }} onClick={() => { - navigate(`/channel/${name}`); + navigate(`/channel/${channelName}`); }} > - {name} - + {channelName} + {channelName !== userName && ( + <> + + + + )} @@ -511,16 +537,22 @@ export const PlaylistContent = () => { }} > {videoData && ( - { - setSuperlikelist(prev => [val, ...prev]); - }} - /> + <> + + { + setSuperlikelist(prev => [val, ...prev]); + }} + /> + )} Save to Disk @@ -677,7 +709,10 @@ export const PlaylistContent = () => { maxWidth: "1200px", }} > - + ); diff --git a/src/pages/VideoContent/VideoContent-styles.tsx b/src/pages/ContentPages/VideoContent/VideoContent-styles.tsx similarity index 100% rename from src/pages/VideoContent/VideoContent-styles.tsx rename to src/pages/ContentPages/VideoContent/VideoContent-styles.tsx diff --git a/src/pages/VideoContent/VideoContent.tsx b/src/pages/ContentPages/VideoContent/VideoContent.tsx similarity index 79% rename from src/pages/VideoContent/VideoContent.tsx rename to src/pages/ContentPages/VideoContent/VideoContent.tsx index 247c565..451fbb9 100644 --- a/src/pages/VideoContent/VideoContent.tsx +++ b/src/pages/ContentPages/VideoContent/VideoContent.tsx @@ -7,15 +7,18 @@ import React, { } from "react"; import { useDispatch, useSelector } from "react-redux"; import { useNavigate, useParams } from "react-router-dom"; -import { setIsLoadingGlobal } from "../../state/features/globalSlice"; +import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts"; import { Avatar, Box, Typography, useTheme } from "@mui/material"; -import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx"; -import { RootState } from "../../state/store"; -import { addToHashMap } from "../../state/features/videoSlice"; +import { + refType, + VideoPlayer, +} 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 mockImg from "../../../test/mockimg.jpg"; import { AuthorTextComment, FileAttachmentContainer, @@ -26,37 +29,39 @@ import { VideoDescription, VideoPlayerContainer, VideoTitle, -} from "./VideoContent-styles"; -import { setUserAvatarHash } from "../../state/features/globalSlice"; +} from "./VideoContent-styles.tsx"; +import { setUserAvatarHash } from "../../../state/features/globalSlice.ts"; import { formatDate, formatDateSeconds, formatTimestampSeconds, -} from "../../utils/time"; -import { NavbarName } from "../../components/layout/Navbar/Navbar-styles"; -import { CommentSection } from "../../components/common/Comments/CommentSection"; +} 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/PublishVideo/PublishVideo-styles.tsx"; -import { Playlists } from "../../components/Playlists/Playlists"; -import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml"; -import FileElement from "../../components/common/FileElement"; -import { SuperLike } from "../../components/common/SuperLike/SuperLike"; -import { CommentContainer } from "../../components/common/Comments/Comments-styles"; -import { Comment } from "../../components/common/Comments/Comment"; -import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection"; -import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes"; +} 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 { CommentContainer } from "../../../components/common/Comments/Comments-styles.tsx"; +import { Comment } from "../../../components/common/Comments/Comment.tsx"; +import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx"; +import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx"; import { FOR_SUPER_LIKE, QTUBE_VIDEO_BASE, SUPER_LIKE_BASE, -} from "../../constants/Identifiers.ts"; +} from "../../../constants/Identifiers.ts"; import { minPriceSuperlike, titleFormatterOnSave, -} from "../../constants/Misc.ts"; -import { SubscribeButton } from "../../components/common/SubscribeButton.tsx"; +} 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 function isTimestampWithinRange(resTimestamp, resCreated) { // Calculate the absolute difference in milliseconds @@ -116,12 +121,15 @@ export const getPaymentInfo = async (signature: string) => { }; export const VideoContent = () => { - const { name, id } = useParams(); + 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) => { @@ -143,6 +151,7 @@ export const VideoContent = () => { const userAvatarHash = useSelector( (state: RootState) => state.global.userAvatarHash ); + const contentRef = useRef(null); const getAddressName = async name => { @@ -157,18 +166,18 @@ export const VideoContent = () => { }; useEffect(() => { - if (name) { - getAddressName(name); + if (channelName) { + getAddressName(channelName); } - }, [name]); + }, [channelName]); const avatarUrl = useMemo(() => { let url = ""; - if (name && userAvatarHash[name]) { - url = userAvatarHash[name]; + if (channelName && userAvatarHash[channelName]) { + url = userAvatarHash[channelName]; } return url; - }, [userAvatarHash, name]); + }, [userAvatarHash, channelName]); const navigate = useNavigate(); const theme = useTheme(); @@ -279,16 +288,16 @@ export const VideoContent = () => { }, []); React.useEffect(() => { - if (name && id) { - const existingVideo = hashMapVideos[id + "-" + name]; + if (channelName && id) { + const existingVideo = hashMapVideos[id + "-" + channelName]; if (existingVideo) { setVideoData(existingVideo); } else { - getVideoData(name, id); + getVideoData(channelName, id); } } - }, [id, name]); + }, [id, channelName]); useEffect(() => { if (contentRef.current) { @@ -367,6 +376,14 @@ export const VideoContent = () => { (state: RootState) => state.persist.subscriptionList ); + const focusVideo = (e: React.MouseEvent) => { + const focusRef = containerRef.current?.getContainerRef()?.current; + const isCorrectTarget = e.currentTarget == e.target; + if (focusRef && isCorrectTarget) { + focusRef.focus({ preventScroll: true }); + } + }; + return ( { flexDirection: "column", padding: "20px 10px", }} + onClick={focusVideo} > { name={videoReference?.name} service={videoReference?.service} identifier={videoReference?.identifier} - user={name} + user={channelName} jsonId={id} poster={videoCover || ""} customStyle={{ aspectRatio: "16/9" }} + ref={containerRef} /> )} { cursor: "pointer", }} onClick={() => { - navigate(`/channel/${name}`); + navigate(`/channel/${channelName}`); }} > @@ -433,14 +452,22 @@ export const VideoContent = () => { cursor: "pointer", }} onClick={() => { - navigate(`/channel/${name}`); + navigate(`/channel/${channelName}`); }} > - {name} - + {channelName} + {channelName !== userName && ( + <> + + + + )} @@ -452,16 +479,22 @@ export const VideoContent = () => { }} > {videoData && ( - { - setSuperlikelist(prev => [val, ...prev]); - }} - /> + <> + + { + setSuperlikelist(prev => [val, ...prev]); + }} + /> + )} Save to Disk @@ -598,7 +631,7 @@ export const VideoContent = () => { loadingSuperLikes={loadingSuperLikes} superlikes={superlikeList} postId={id || ""} - postName={name || ""} + postName={channelName || ""} /> { maxWidth: "1200px", }} > - + ); diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index ce33036..558669b 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -37,12 +37,12 @@ import { import { changeFilterType, resetSubscriptions, + VideoListType, } from "../../state/features/persistSlice.ts"; import { categories, subCategories } from "../../constants/Categories.ts"; import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx"; import { TabContext, TabList, TabPanel } from "@mui/lab"; import VideoList from "./VideoList.tsx"; -import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts"; import { setHomePageSelectedTab } from "../../state/features/persistSlice.ts"; import { StatsData } from "../../components/StatsData.tsx"; @@ -74,7 +74,9 @@ export const Home = ({ mode }: HomeProps) => { ); const [isLoading, setIsLoading] = useState(false); - const [tabValue, setTabValue] = useState(persistReducer.selectedTab); + const [tabValue, setTabValue] = useState( + persistReducer.selectedTab + ); const tabFontSize = "20px"; @@ -123,14 +125,14 @@ export const Home = ({ mode }: HomeProps) => { if (!firstFetch.current || !afterFetch.current) return; if (isFetching.current) return; isFetching.current = true; - console.log("in getvideoshandler"); + await getVideos( { name: filterName, category: selectedCategoryVideos?.id, subcategory: selectedSubCategoryVideos?.id, keywords: filterSearch, - type: filterType, + contentType: filterType, }, reset, resetFilters, @@ -171,7 +173,7 @@ export const Home = ({ mode }: HomeProps) => { category: "", subcategory: "", keywords: "", - type: filterType, + contentType: filterType, }, null, null, @@ -269,11 +271,10 @@ export const Home = ({ mode }: HomeProps) => { }; useEffect(() => { - console.log("useeffect 5"); getVideosHandler(true); }, [tabValue]); - const changeTab = (e: React.SyntheticEvent, newValue: string) => { + const changeTab = (e: React.SyntheticEvent, newValue: VideoListType) => { setTabValue(newValue); dispatch(setHomePageSelectedTab(newValue)); }; @@ -516,23 +517,23 @@ export const Home = ({ mode }: HomeProps) => { > - + - + {filteredSubscriptionList.length > 0 ? ( <> @@ -541,10 +542,12 @@ export const Home = ({ mode }: HomeProps) => { isLoading={isLoading} > - ) : ( + ) : !isLoading ? (
You have no subscriptions
+ ) : ( + <> )}
diff --git a/src/state/features/persistSlice.ts b/src/state/features/persistSlice.ts index 8a3747d..1b02954 100644 --- a/src/state/features/persistSlice.ts +++ b/src/state/features/persistSlice.ts @@ -1,14 +1,19 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts"; -import { SubscriptionData } from "../../components/common/SubscribeButton.tsx"; - -type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down"; -type SubscriptionListFilterType = "ALL" | "currentNameOnly"; +import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx"; +export type StretchVideoType = + | "contain" + | "fill" + | "cover" + | "none" + | "scale-down"; +export type SubscriptionListFilterType = "ALL" | "currentNameOnly"; +export type ContentType = "videos" | "playlists"; +export type VideoListType = "all" | "subscriptions"; interface settingsState { - selectedTab: string; + selectedTab: VideoListType; stretchVideoSetting: StretchVideoType; - filterType: string; + filterType: ContentType; subscriptionList: SubscriptionData[]; playbackRate: number; subscriptionListFilter: SubscriptionListFilterType; @@ -16,7 +21,7 @@ interface settingsState { } const initialState: settingsState = { - selectedTab: allTabValue, + selectedTab: "all", stretchVideoSetting: "contain", filterType: "videos", subscriptionList: [], diff --git a/src/state/features/videoSlice.ts b/src/state/features/videoSlice.ts index 15c38a1..19e685c 100644 --- a/src/state/features/videoSlice.ts +++ b/src/state/features/videoSlice.ts @@ -1,5 +1,5 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; -import { SubscriptionData } from "../../components/common/SubscribeButton"; +import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx"; interface GlobalState { videos: Video[]; diff --git a/src/utils/qortalRequestFunctions.ts b/src/utils/qortalRequestFunctions.ts index bd1111d..c64c0bc 100644 --- a/src/utils/qortalRequestFunctions.ts +++ b/src/utils/qortalRequestFunctions.ts @@ -2,6 +2,7 @@ import { AccountInfo, AccountName, GetRequestData, + SearchResourcesResponse, SearchTransactionResponse, TransactionSearchParams, } from "./qortalRequestTypes.ts"; @@ -46,3 +47,30 @@ export const searchTransactions = async (params: TransactionSearchParams) => { ...params, })) as SearchTransactionResponse[]; }; + +export const fetchResourcesByIdentifier = async ( + service: string, + identifier: string +) => { + const names: SearchResourcesResponse[] = await qortalRequest({ + action: "SEARCH_QDN_RESOURCES", + service, + identifier, + includeMetadata: false, + }); + const distinctNames = names.filter( + (searchResponse, index) => names.indexOf(searchResponse) === index + ); + + const promises: Promise[] = []; + distinctNames.map(response => { + const resource: Promise = qortalRequest({ + action: "FETCH_QDN_RESOURCE", + name: response.name, + service, + identifier, + }); + promises.push(resource); + }); + return (await Promise.all(promises)) as T[]; +}; diff --git a/src/utils/qortalRequestTypes.ts b/src/utils/qortalRequestTypes.ts index 5a6cf54..085a28e 100644 --- a/src/utils/qortalRequestTypes.ts +++ b/src/utils/qortalRequestTypes.ts @@ -23,6 +23,23 @@ export interface SearchTransactionResponse { amount: string; } +export interface MetaData { + title: string; + description: string; + tags: string[]; + mimeType: string; +} + +export interface SearchResourcesResponse { + name: string; + service: string; + identifier: string; + metadata?: MetaData; + size: number; + created: number; + updated: number; +} + export type TransactionType = | "GENESIS" | "PAYMENT" diff --git a/src/wrappers/GlobalWrapper.tsx b/src/wrappers/GlobalWrapper.tsx index fa7a136..c80f223 100644 --- a/src/wrappers/GlobalWrapper.tsx +++ b/src/wrappers/GlobalWrapper.tsx @@ -18,14 +18,14 @@ import { import { VideoPlayerGlobal } from "../components/common/VideoPlayer/VideoPlayerGlobal.tsx"; import { Rnd } from "react-rnd"; import { RequestQueue } from "../utils/queue"; -import { EditVideo } from "../components/EditVideo/EditVideo"; -import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist"; +import { EditVideo } from "../components/Publish/EditVideo/EditVideo"; +import { EditPlaylist } from "../components/Publish/EditPlaylist/EditPlaylist"; import ConsentModal from "../components/common/ConsentModal"; import { extractSigValue, getPaymentInfo, isTimestampWithinRange, -} from "../pages/VideoContent/VideoContent"; +} from "../pages/ContentPages/VideoContent/VideoContent"; import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes"; import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts"; import { minPriceSuperlike } from "../constants/Misc.ts"; @@ -143,8 +143,8 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { const getSuperlikes = useCallback(async () => { try { - let totalCount = 0 - let validCount = 0 + let totalCount = 0; + let validCount = 0; let comments: any[] = []; while (validCount < 20 && totalCount < 100) { const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}&limit=1&offset=${totalCount}&includemetadata=true&reverse=true&excludeblocked=true`; @@ -177,12 +177,12 @@ const GlobalWrapper: React.FC = ({ children, setTheme }) => { }); comments = [ ...comments, - { - ...comment, - message: "", - amount: res.amount, - }, - ]; + { + ...comment, + message: "", + amount: res.amount, + }, + ]; validCount++; } } catch (error) {}