Browse Source

Merge pull request #23 from QortalSeth/main

Follow, Like, and Dislike buttons Added to  Video, Playlist, and Channel pages
pull/24/head^2
Qortal Dev 6 months ago committed by GitHub
parent
commit
b8463daef0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      src/App.tsx
  2. 2
      src/components/Playlists/Playlists.tsx
  3. 24
      src/components/Publish/EditPlaylist/EditPlaylist.tsx
  4. 28
      src/components/Publish/EditPlaylist/Upload-styles.tsx
  5. 28
      src/components/Publish/EditVideo/EditVideo-styles.tsx
  6. 24
      src/components/Publish/EditVideo/EditVideo.tsx
  7. 4
      src/components/Publish/MultiplePublish/MultiplePublishAll.tsx
  8. 8
      src/components/Publish/PlaylistListEdit/PlaylistListEdit.tsx
  9. 2
      src/components/Publish/PublishVideo/PublishVideo-styles.tsx
  10. 32
      src/components/Publish/PublishVideo/PublishVideo.tsx
  11. 7
      src/components/StatsData.tsx
  12. 2
      src/components/common/Comments/CommentSection.tsx
  13. 159
      src/components/common/ContentButtons/FollowButton.tsx
  14. 66
      src/components/common/ContentButtons/LikeAndDislike-functions.ts
  15. 230
      src/components/common/ContentButtons/LikeAndDislike.tsx
  16. 30
      src/components/common/ContentButtons/SubscribeButton.tsx
  17. 75
      src/components/common/ContentButtons/SuperLike.tsx
  18. 2
      src/components/common/Notifications/Notifications.tsx
  19. 2
      src/components/common/SuperLikesList/SuperLikesSection.tsx
  20. 25
      src/components/common/VideoPlayer/VideoPlayer-styles.ts
  21. 49
      src/components/common/VideoPlayer/VideoPlayer.tsx
  22. 4
      src/components/layout/Navbar/Navbar.tsx
  23. 11
      src/constants/Categories.ts
  24. 4
      src/constants/Identifiers.ts
  25. 5
      src/constants/Misc.ts
  26. 24
      src/hooks/useFetchVideos.tsx
  27. 39
      src/pages/ContentPages/IndividualProfile/IndividualProfile.tsx
  28. 0
      src/pages/ContentPages/IndividualProfile/Profile-styles.tsx
  29. 0
      src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx
  30. 113
      src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx
  31. 0
      src/pages/ContentPages/VideoContent/VideoContent-styles.tsx
  32. 117
      src/pages/ContentPages/VideoContent/VideoContent.tsx
  33. 27
      src/pages/Home/Home.tsx
  34. 21
      src/state/features/persistSlice.ts
  35. 2
      src/state/features/videoSlice.ts
  36. 28
      src/utils/qortalRequestFunctions.ts
  37. 17
      src/utils/qortalRequestTypes.ts
  38. 10
      src/wrappers/GlobalWrapper.tsx

8
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({

2
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";

24
src/components/EditPlaylist/EditPlaylist.tsx → 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 });

28
src/components/EditPlaylist/Upload-styles.tsx → 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",

28
src/components/EditVideo/EditVideo-styles.tsx → 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",

24
src/components/EditVideo/EditVideo.tsx → 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 });

4
src/components/common/MultiplePublish/MultiplePublishAll.tsx → 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 {

8
src/components/PlaylistListEdit/PlaylistListEdit.tsx → 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();

2
src/components/PublishVideo/PublishVideo-styles.tsx → 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;

32
src/components/PublishVideo/PublishVideo.tsx → 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<string | ArrayBuffer | null> =>
new Promise((resolve, reject) => {

7
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 = () => {
</div>
<div>
Average:{" "}
<span style={{ fontWeight: "bold" }}>{videosPerNamePublished}</span>
<span style={{ fontWeight: "bold" }}>
{videosPerNamePublished > 0 &&
Number(videosPerNamePublished).toFixed(0)}
</span>
</div>
</StatsCol>
);

2
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 {

159
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<string[]>([]);
const [followingSize, setFollowingSize] = useState<string>("");
const [followingItemCount, setFollowingItemCount] = useState<string>("");
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<HTMLButtonElement>) => {
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 && (
<>
<TooltipLine>
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.
</TooltipLine>
<br />
<TooltipLine>{`${followerName}'s Current Download Size: ${followingSize}`}</TooltipLine>
<TooltipLine>{`Number of Files: ${followingItemCount}`}</TooltipLine>
</>
);
const CustomWidthTooltip = styled(({ className, ...props }: TooltipProps) => (
<Tooltip {...props} classes={{ popper: className }} />
))({
[`& .${tooltipClasses.tooltip}`]: {
maxWidth: 600,
},
});
return (
<>
<CustomWidthTooltip title={tooltipTitle} placement={"top"} arrow>
<Button
{...props}
variant={"contained"}
color="success"
sx={buttonStyle}
onClick={e => manageFollow(e)}
>
{isFollowingName() ? "Unfollow" : "Follow"}
</Button>
</CustomWidthTooltip>
</>
);
};

66
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<LikeType> => {
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<ResourceType>(
"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] || ""}`;
}

230
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<number>(0);
const [dislikeCount, setDislikeCount] = useState<number>(0);
const [currentLikeType, setCurrentLikeType] = useState<LikeType>(NEUTRAL);
const likeIdentifier = `${LIKE_BASE}${identifier.slice(0, 39)}`;
const [isLoading, setIsLoading] = useState<boolean>(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 (
<>
<Box
sx={{
display: "flex",
alignItems: "center",
gap: "15px",
cursor: "pointer",
flexShrink: 0,
}}
>
<Tooltip title="Like or Dislike Video" placement="top">
<Box
sx={{
padding: "5px",
borderRadius: "7px",
gap: "5px",
display: "flex",
alignItems: "center",
marginRight: "10px",
height: "53px",
}}
>
{currentLikeType === LIKE ? (
<ThumbUpIcon onClick={() => publishLike(NEUTRAL)} />
) : (
<ThumbUpOffAltOutlinedIcon onClick={() => publishLike(LIKE)} />
)}
{likeCount > 0 && (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
userSelect: "none",
}}
>
<span style={{ marginRight: "10px", paddingBottom: "4px" }}>
{formatLikeCount(likeCount)}
</span>
</div>
)}
{currentLikeType === DISLIKE ? (
<ThumbDownIcon
onClick={() => publishLike(NEUTRAL)}
color={"error"}
/>
) : (
<ThumbDownOffAltOutlinedIcon
onClick={() => publishLike(DISLIKE)}
color={"error"}
/>
)}
{dislikeCount > 0 && (
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
userSelect: "none",
}}
>
<span
style={{
marginRight: "10px",
paddingBottom: "4px",
color: "red",
}}
>
{formatLikeCount(dislikeCount)}
</span>
</div>
)}
</Box>
</Tooltip>
</Box>
</>
);
};

30
src/components/common/SubscribeButton.tsx → 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,7 +93,22 @@ export const SubscribeButton = ({
height: "45px",
...props.sx,
};
const TooltipLine = styled("div")(({ theme }) => ({
fontSize: "18px",
}));
const tooltipTitle = (
<>
<TooltipLine>
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.
</TooltipLine>
</>
);
return (
<Tooltip title={tooltipTitle} placement={"top"} arrow>
<Button
{...props}
variant={"contained"}
@ -99,5 +118,6 @@ export const SubscribeButton = ({
>
{isSubscribed ? "Unsubscribe" : "Subscribe"}
</Button>
</Tooltip>
);
};

75
src/components/common/SuperLike/SuperLike.tsx → 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",
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",
dispatch(
setNotification({
msg:
error ||
error?.error ||
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,
}}
>
<Tooltip title="Super Like" placement="top">
<Box
sx={{
@ -250,8 +237,8 @@ export const SuperLike = ({
display: "flex",
alignItems: "center",
outline: "1px gold solid",
marginRight:'10px',
height: '53px',
marginRight: "10px",
height: "53px",
}}
>
<ThumbUpIcon
@ -261,21 +248,27 @@ export const SuperLike = ({
/>
{numberOfSuperlikes === 0 ? null : (
<div style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center', userSelect: "none"}}>
<span style={{marginRight:'10px', paddingBottom:'4px'}}>{numberOfSuperlikes}</span>
<div
style={{
display: "flex",
alignItems: "center",
justifyContent: "center",
userSelect: "none",
}}
>
<span style={{ marginRight: "10px", paddingBottom: "4px" }}>
{numberOfSuperlikes}
</span>
<img
style={{
height: "25px",
width: "25px",
marginRight:'5px',
marginRight: "5px",
}}
src={qortImg}
alt={"Qort Icon"}
/>
{truncateNumber(totalAmount,0)}
{truncateNumber(totalAmount, 0)}
</div>
)}
</Box>
@ -301,10 +294,10 @@ export const SuperLike = ({
<DialogContent>
<Box>
<InputLabel htmlFor="standard-adornment-amount">
Amount in QORT (min 10 QORT)
Amount in QORT (min 1 QORT)
</InputLabel>
<BoundedNumericTextField
minValue={10}
minValue={+minPriceSuperlike}
initialValue={minPriceSuperlike.toString()}
maxValue={numberToInt(+currentBalance)}
allowDecimals={false}

2
src/components/common/Notifications/Notifications.tsx

@ -24,7 +24,7 @@ import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../../../pages/VideoContent/VideoContent";
} from "../../../pages/ContentPages/VideoContent/VideoContent";
import { useNavigate } from "react-router-dom";
import localForage from "localforage";
import moment from "moment";

2
src/components/common/SuperLikesList/SuperLikesSection.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 {

25
src/components/common/VideoPlayer/VideoPlayer-styles.ts

@ -1,18 +1,19 @@
import { styled } from "@mui/system";
import { Box } from "@mui/material";
export const VideoContainer = styled(Box)`
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
max-height: 70vh;
`;
export const VideoContainer = styled(Box)(({ theme }) => ({
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%;

49
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,7 +53,13 @@ interface VideoPlayerProps {
style?: CSS.Properties;
}
export const VideoPlayer: React.FC<VideoPlayerProps> = ({
export type refType = {
getContainerRef: () => React.MutableRefObject<HTMLDivElement>;
getVideoRef: () => React.MutableRefObject<HTMLVideoElement>;
};
export const VideoPlayer = React.forwardRef<refType, VideoPlayerProps>(
(
{
poster,
name,
identifier,
@ -59,11 +73,14 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
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<HTMLVideoElement | null>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [playing, setPlaying] = useState(false);
const [volume, setVolume] = useState(1);
const [mutedVolume, setMutedVolume] = useState(1);
@ -110,6 +127,14 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
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;
@ -264,7 +289,10 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
document.addEventListener("fullscreenchange", handleFullscreenChange);
return () => {
document.removeEventListener("fullscreenchange", handleFullscreenChange);
document.removeEventListener(
"fullscreenchange",
handleFullscreenChange
);
};
}, []);
@ -670,6 +698,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
padding: from === "create" ? "8px" : 0,
...customStyle,
}}
ref={containerRef}
>
{isLoading && (
<Box
@ -701,7 +730,9 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
}}
>
{resourceStatus?.status === "NOT_PUBLISHED" && (
<>Video file was not published. Please inform the publisher!</>
<>
Video file was not published. Please inform the publisher!
</>
)}
{resourceStatus?.status === "REFETCHING" ? (
<>
@ -905,7 +936,8 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
: "visible",
}}
>
{progress && videoRef.current?.duration && formatTime(progress)}/
{progress && videoRef.current?.duration && formatTime(progress)}
/
{progress &&
videoRef.current?.duration &&
formatTime(videoRef.current?.duration)}
@ -963,4 +995,5 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
</ControlsContainer>
</VideoContainer>
);
};
}
);

4
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;

11
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),
};

4
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`;

5
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";

24
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));

39
src/pages/IndividualProfile/IndividualProfile.tsx → 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 (
<ProfileContainer>
<HeaderContainer>
@ -46,8 +49,8 @@ export const IndividualProfile = () => {
>
<Box>
<Avatar
src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`}
alt={`${paramName}'s avatar`}
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
@ -58,13 +61,21 @@ export const IndividualProfile = () => {
: "#d6e8ff"
}
>
{paramName}
{channelName}
</AuthorTextComment>
</StyledCardColComment>
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={paramName}
subscriberName={channelName}
sx={{ marginLeft: "10px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</StyledCardHeaderComment>
</Box>
</HeaderContainer>

0
src/pages/IndividualProfile/Profile-styles.tsx → src/pages/ContentPages/IndividualProfile/Profile-styles.tsx

0
src/pages/PlaylistContent/PlaylistContent-styles.tsx → src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx

113
src/pages/PlaylistContent/PlaylistContent.tsx → 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<boolean>(false);
@ -68,6 +75,7 @@ export const PlaylistContent = () => {
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const containerRef = useRef<refType>(null);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superlikeList?.reduce((acc, curr) => {
@ -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<HTMLDivElement>) => {
const focusRef = containerRef.current?.getContainerRef()?.current;
const isCorrectTarget = e.currentTarget == e.target;
if (focusRef && isCorrectTarget) {
focusRef.focus({ preventScroll: true });
}
};
return (
<Box
sx={{
@ -403,6 +419,7 @@ export const PlaylistContent = () => {
flexDirection: "column",
padding: "20px 10px",
}}
onClick={focusVideo}
>
<VideoPlayerContainer
sx={{
@ -434,13 +451,14 @@ export const PlaylistContent = () => {
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}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
alt={`${name}'s avatar`}
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
@ -492,14 +510,22 @@ export const PlaylistContent = () => {
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${name}`);
navigate(`/channel/${channelName}`);
}}
>
{name}
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={name}
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
@ -511,6 +537,11 @@ export const PlaylistContent = () => {
}}
>
{videoData && (
<>
<LikeAndDislike
name={videoData?.user}
identifier={videoData?.id}
/>
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
@ -521,6 +552,7 @@ export const PlaylistContent = () => {
setSuperlikelist(prev => [val, ...prev]);
}}
/>
</>
)}
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
@ -677,7 +709,10 @@ export const PlaylistContent = () => {
maxWidth: "1200px",
}}
>
<CommentSection postId={videoData?.id || ""} postName={name || ""} />
<CommentSection
postId={videoData?.id || ""}
postName={channelName || ""}
/>
</Box>
</Box>
);

0
src/pages/VideoContent/VideoContent-styles.tsx → src/pages/ContentPages/VideoContent/VideoContent-styles.tsx

117
src/pages/VideoContent/VideoContent.tsx → 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<boolean>(false);
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const containerRef = useRef<refType>(null);
const calculateAmountSuperlike = useMemo(() => {
const totalQort = superlikeList?.reduce((acc, curr) => {
@ -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<HTMLDivElement>) => {
const focusRef = containerRef.current?.getContainerRef()?.current;
const isCorrectTarget = e.currentTarget == e.target;
if (focusRef && isCorrectTarget) {
focusRef.focus({ preventScroll: true });
}
};
return (
<Box
sx={{
@ -375,6 +392,7 @@ export const VideoContent = () => {
flexDirection: "column",
padding: "20px 10px",
}}
onClick={focusVideo}
>
<VideoPlayerContainer
sx={{
@ -387,10 +405,11 @@ export const VideoContent = () => {
name={videoReference?.name}
service={videoReference?.service}
identifier={videoReference?.identifier}
user={name}
user={channelName}
jsonId={id}
poster={videoCover || ""}
customStyle={{ aspectRatio: "16/9" }}
ref={containerRef}
/>
)}
<Box
@ -414,12 +433,12 @@ export const VideoContent = () => {
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${name}`);
navigate(`/channel/${channelName}`);
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
alt={`${name}'s avatar`}
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
@ -433,14 +452,22 @@ export const VideoContent = () => {
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${name}`);
navigate(`/channel/${channelName}`);
}}
>
{name}
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={name}
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
@ -452,6 +479,11 @@ export const VideoContent = () => {
}}
>
{videoData && (
<>
<LikeAndDislike
name={videoData?.user}
identifier={videoData?.id}
/>
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
@ -462,6 +494,7 @@ export const VideoContent = () => {
setSuperlikelist(prev => [val, ...prev]);
}}
/>
</>
)}
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
@ -598,7 +631,7 @@ export const VideoContent = () => {
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
postId={id || ""}
postName={name || ""}
postName={channelName || ""}
/>
<Box
@ -609,7 +642,7 @@ export const VideoContent = () => {
maxWidth: "1200px",
}}
>
<CommentSection postId={id || ""} postName={name || ""} />
<CommentSection postId={id || ""} postName={channelName || ""} />
</Box>
</Box>
);

27
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<boolean>(false);
const [tabValue, setTabValue] = useState<string>(persistReducer.selectedTab);
const [tabValue, setTabValue] = useState<VideoListType>(
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) => {
>
<Tab
label="All Videos"
value={allTabValue}
value={"all"}
sx={{ fontSize: tabFontSize }}
/>
<Tab
label="Subscriptions"
value={subscriptionTabValue}
value={"subscriptions"}
sx={{ fontSize: tabFontSize }}
/>
</TabList>
<TabPanel value={allTabValue} sx={{ width: "100%" }}>
<TabPanel value={"all"} sx={{ width: "100%" }}>
<VideoList videos={videos} />
<LazyLoad
onLoadMore={getVideosHandler}
isLoading={isLoading}
></LazyLoad>
</TabPanel>
<TabPanel value={subscriptionTabValue} sx={{ width: "100%" }}>
<TabPanel value={"subscriptions"} sx={{ width: "100%" }}>
{filteredSubscriptionList.length > 0 ? (
<>
<VideoList videos={videos} />
@ -541,10 +542,12 @@ export const Home = ({ mode }: HomeProps) => {
isLoading={isLoading}
></LazyLoad>
</>
) : (
) : !isLoading ? (
<div style={{ textAlign: "center" }}>
You have no subscriptions
</div>
) : (
<></>
)}
</TabPanel>
</TabContext>

21
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: [],

2
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[];

28
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 <T>(
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<T>[] = [];
distinctNames.map(response => {
const resource: Promise<T> = qortalRequest({
action: "FETCH_QDN_RESOURCE",
name: response.name,
service,
identifier,
});
promises.push(resource);
});
return (await Promise.all(promises)) as T[];
};

17
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"

10
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<Props> = ({ 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`;

Loading…
Cancel
Save