Browse Source

Merge pull request #35 from QortalSeth/main

Updates to User Interface
pull/36/head^2
Qortal Dev 1 month ago committed by GitHub
parent
commit
15d8146c7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      src/components/Publish/PublishVideo/PublishVideo-styles.tsx
  2. 18
      src/components/common/Comments/CommentSection.tsx
  3. 8
      src/components/common/Comments/Comments-styles.tsx
  4. 20
      src/components/common/Notifications/Notifications.tsx
  5. 6
      src/components/common/SuperLikesList/Comments-styles.tsx
  6. 24
      src/components/common/SuperLikesList/SuperLikesSection.tsx
  7. 23
      src/components/common/VideoPlayer/VideoPlayer.tsx
  8. 1
      src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx
  9. 221
      src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx
  10. 56
      src/pages/ContentPages/VideoContent/VideoContent-functions.ts
  11. 11
      src/pages/ContentPages/VideoContent/VideoContent-styles.tsx
  12. 627
      src/pages/ContentPages/VideoContent/VideoContent.tsx
  13. 1
      src/state/features/persistSlice.ts
  14. 15
      src/wrappers/GlobalWrapper.tsx

3
src/components/Publish/PublishVideo/PublishVideo-styles.tsx

@ -175,8 +175,7 @@ export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
export const CrowdfundSubTitleRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
width: "100%",
justifyContent: "start",
flexDirection: "row",
});

18
src/components/common/Comments/CommentSection.tsx

@ -28,8 +28,8 @@ interface CommentSectionProps {
const Panel = styled("div")`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
justify-content: start;
align-items: start;
width: 100%;
padding-bottom: 10px;
height: 100%;
@ -57,7 +57,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
const { user } = useSelector((state: RootState) => state.auth);
const [newMessages, setNewMessages] = useState(0);
const [loadingComments, setLoadingComments] = useState<boolean>(false);
// console.log("postId is: ", postId, " postName is: ", postName);
const onSubmit = (obj?: any, isEdit?: boolean) => {
if (isEdit) {
setListComments((prev: any[]) => {
@ -113,6 +113,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
)}_reply_${removeBaseCommentId.slice(
-6
)}&limit=0&includemetadata=false&offset=${offset}&reverse=false&excludeblocked=true`;
const response = await fetch(url, {
method: "GET",
headers: {
@ -120,6 +121,7 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
},
});
const responseData = await response.json();
const comments: any[] = [];
for (const comment of responseData) {
if (comment.identifier && comment.name) {
@ -163,6 +165,9 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
},
});
const responseData = await response.json();
// console.log("url is: ", url);
// console.log("response is: ", responseData);
let comments: any[] = [];
for (const comment of responseData) {
if (comment.identifier && comment.name) {
@ -222,18 +227,13 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
return (
<>
<Panel>
<CrowdfundSubTitleRow>
<CrowdfundSubTitle>Comments</CrowdfundSubTitle>
</CrowdfundSubTitleRow>
<CommentsContainer>
{loadingComments ? (
<NoCommentsRow>
<CircularProgress />
</NoCommentsRow>
) : listComments.length === 0 ? (
<NoCommentsRow>
There are no comments yet. Be the first to comment!
</NoCommentsRow>
<></>
) : (
<CommentContainer>
{structuredCommentList.map((comment: any) => {

8
src/components/common/Comments/Comments-styles.tsx

@ -93,7 +93,7 @@ export const StyledCardComment = styled(Typography)(({ theme }) => ({
fontWeight: 400,
color: theme.palette.text.primary,
fontSize: "19px",
wordBreak: "break-word"
wordBreak: "break-word",
}));
export const TitleText = styled(Typography)({
@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({
});
export const CommentsContainer = styled(Box)({
width: "90%",
width: "70%",
maxWidth: "1000px",
display: "flex",
flexDirection: "column",
@ -180,7 +180,7 @@ export const CommentContainer = styled(Box)({
export const NoCommentsRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
justifyContent: "start",
flex: "1",
padding: "10px 0px",
fontFamily: "Mulish",
@ -218,7 +218,7 @@ export const CommentActionButtonRow = styled(Box)({
export const CommentEditorContainer = styled(Box)({
width: "100%",
display: "flex",
justifyContent: "center",
justifyContent: "start",
});
export const CommentDateText = styled(Typography)(({ theme }) => ({

20
src/components/common/Notifications/Notifications.tsx

@ -16,15 +16,15 @@ import React, {
useState,
} from "react";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../../state/store";
import NotificationsIcon from "@mui/icons-material/Notifications";
import { formatDate } from "../../../utils/time";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../../../pages/ContentPages/VideoContent/VideoContent";
} from "../../../pages/ContentPages/VideoContent/VideoContent-functions.ts";
import { RootState } from "../../../state/store";
import NotificationsIcon from "@mui/icons-material/Notifications";
import { formatDate } from "../../../utils/time";
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
import { useNavigate } from "react-router-dom";
import localForage from "localforage";
import moment from "moment";
@ -148,7 +148,7 @@ export const Notifications = () => {
) {
let urlReference = null;
try {
let idForUrl = extractIdValue(comment?.metadata?.description);
const idForUrl = extractIdValue(comment?.metadata?.description);
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${idForUrl}&limit=1&includemetadata=false&reverse=false&excludeblocked=true&offset=0&name=${username}`;
const response2 = await fetch(url, {
method: "GET",
@ -160,7 +160,9 @@ export const Notifications = () => {
if (responseSearch.length > 0) {
urlReference = responseSearch[0];
}
} catch (error) {}
} catch (error) {
console.log(error);
}
// const url = `/arbitrary/BLOG_COMMENT/${comment.name}/${comment.identifier}`;
// const response = await fetch(url, {
// method: "GET",
@ -180,7 +182,9 @@ export const Notifications = () => {
},
];
}
} catch (error) {}
} catch (error) {
console.log(error);
}
}
}
setNotifications(prev => {

6
src/components/common/SuperLikesList/Comments-styles.tsx

@ -93,7 +93,7 @@ export const StyledCardComment = styled(Typography)(({ theme }) => ({
fontWeight: 400,
color: theme.palette.text.primary,
fontSize: "19px",
wordBreak: "break-word"
wordBreak: "break-word",
}));
export const TitleText = styled(Typography)({
@ -159,7 +159,7 @@ export const BlockIconContainer = styled(Box)({
});
export const CommentsContainer = styled(Box)({
width: "90%",
width: "70%",
maxWidth: "1000px",
display: "flex",
flexDirection: "column",
@ -180,7 +180,7 @@ export const CommentContainer = styled(Box)({
export const NoCommentsRow = styled(Box)({
display: "flex",
alignItems: "center",
justifyContent: "center",
justifyContent: "start",
flex: "1",
padding: "10px 0px",
fontFamily: "Mulish",

24
src/components/common/SuperLikesList/SuperLikesSection.tsx

@ -24,15 +24,15 @@ interface CommentSectionProps {
postId: string;
postName: string;
superlikes: any[];
getMore: () => void;
getMore?: () => void;
loadingSuperLikes: boolean;
}
const Panel = styled("div")`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
justify-content: start;
align-items: start;
width: 100%;
padding-bottom: 10px;
height: 100%;
@ -199,30 +199,18 @@ export const SuperLikesSection = ({
return (
<>
<Panel>
<CrowdfundSubTitleRow>
<CrowdfundSubTitle
sx={{
fontSize: "18px",
color: "gold",
}}
>
Super Likes
</CrowdfundSubTitle>
</CrowdfundSubTitleRow>
<CommentsContainer>
{loadingComments || loadingSuperLikes ? (
<NoCommentsRow>
<CircularProgress />
</NoCommentsRow>
) : listComments.length === 0 ? (
<NoCommentsRow>
There are no super likes yet. Be the first!
</NoCommentsRow>
<></>
) : (
<CommentContainer>
{structuredCommentList.map((comment: any) => {
let hasHash = false;
let message = { ...comment };
const message = { ...comment };
let hash = {};
if (hashMapSuperlikes[comment?.identifier]) {
message.message =
@ -249,7 +237,7 @@ export const SuperLikesSection = ({
<LoadMoreCommentsButtonRow>
<LoadMoreCommentsButton
onClick={() => {
getMore();
if (getMore) getMore();
}}
variant="contained"
size="small"

23
src/components/common/VideoPlayer/VideoPlayer.tsx

@ -34,7 +34,11 @@ import {
VideoElement,
} from "./VideoPlayer-styles.ts";
import CSS from "csstype";
import { setReduxPlaybackRate } from "../../../state/features/persistSlice.ts";
import {
setReduxPlaybackRate,
setStretchVideoSetting,
StretchVideoType,
} from "../../../state/features/persistSlice.ts";
export interface VideoStyles {
videoContainer?: CSS.Properties;
@ -101,6 +105,10 @@ export const VideoPlayer = React.forwardRef<refType, VideoPlayerProps>(
const [anchorEl, setAnchorEl] = useState(null);
const [showControlsFullScreen, setShowControlsFullScreen] =
useState<boolean>(true);
const [videoObjectFit, setVideoObjectFit] = useState<StretchVideoType>(
persistSelector.stretchVideoSetting
);
const videoPlaying = useSelector(
(state: RootState) => state.global.videoPlaying
);
@ -597,10 +605,21 @@ export const VideoPlayer = React.forwardRef<refType, VideoPlayerProps>(
}
};
const toggleStretchVideoSetting = () => {
const newStretchVideoSetting =
persistSelector.stretchVideoSetting === "contain" ? "fill" : "contain";
setVideoObjectFit(newStretchVideoSetting);
dispatch(setStretchVideoSetting(newStretchVideoSetting));
};
const keyboardShortcutsDown = (e: React.KeyboardEvent<HTMLDivElement>) => {
e.preventDefault();
switch (e.key) {
case "o":
toggleStretchVideoSetting();
break;
case Key.Add:
increaseSpeed(false);
break;
@ -825,7 +844,7 @@ export const VideoPlayer = React.forwardRef<refType, VideoPlayerProps>(
startPlay
? {
...videoStyles?.video,
objectFit: persistSelector.stretchVideoSetting,
objectFit: videoObjectFit,
}
: { height: "100%", ...videoStyles }
}

1
src/pages/ContentPages/PlaylistContent/PlaylistContent-styles.tsx

@ -2,7 +2,6 @@ import { styled } from "@mui/system";
import { Box, Grid, Typography, Checkbox } from "@mui/material";
export const VideoPlayerContainer = styled(Box)(({ theme }) => ({
maxWidth: "95%",
display: "flex",
flexDirection: "column",
alignItems: "flex-start",

221
src/pages/ContentPages/PlaylistContent/PlaylistContent.tsx

@ -20,6 +20,11 @@ import AttachFileIcon from "@mui/icons-material/AttachFile";
import DownloadIcon from "@mui/icons-material/Download";
import mockImg from "../../../test/mockimg.jpg";
import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../VideoContent/VideoContent-functions.ts";
import {
AuthorTextComment,
FileAttachmentContainer,
@ -48,11 +53,6 @@ import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.t
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.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import {
QTUBE_VIDEO_BASE,
@ -196,6 +196,7 @@ export const PlaylistContent = () => {
setVideoData(combinedData);
dispatch(addToHashMap(combinedData));
checkforPlaylist(name, id);
}
}
@ -275,11 +276,8 @@ export const PlaylistContent = () => {
const fullId = vid ? `${vid.identifier}-${vid.name}` : undefined;
const existingVideo = hashMapVideos[fullId];
if (existingVideo) {
setVideoData(existingVideo);
} else {
getVideoData(vid?.name, vid?.identifier);
}
if (existingVideo) setVideoData(existingVideo);
else getVideoData(vid?.name, vid?.identifier);
}
}
}
@ -298,13 +296,12 @@ export const PlaylistContent = () => {
}
}, [id, channelName]);
const descriptionThreshold = 200;
useEffect(() => {
if (contentRef.current) {
const height = contentRef.current.offsetHeight;
if (height > 100) {
// Assuming 100px is your threshold
setDescriptionHeight(100);
}
if (height > descriptionThreshold)
setDescriptionHeight(descriptionThreshold);
}
}, [videoData]);
@ -409,13 +406,27 @@ export const PlaylistContent = () => {
}, [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) {
console.log("in focusVideo");
const target = e.target as Element;
const textTagNames = ["TEXTAREA", "P", "H[1-6]", "STRONG", "svg", "A"];
const noText =
textTagNames.findIndex(s => {
return target?.tagName.match(s);
}) < 0;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const clickOnEmptySpace = !target?.onclick && noText;
console.log("tagName is: ", target?.tagName);
if (target == e.currentTarget || clickOnEmptySpace) {
console.log("in correctTarget");
const focusRef = containerRef.current?.getContainerRef()?.current;
focusRef.focus({ preventScroll: true });
}
};
return (
<Box
sx={{
@ -423,6 +434,7 @@ export const PlaylistContent = () => {
alignItems: "center",
flexDirection: "column",
padding: "0px 10px",
marginLeft: "5%",
}}
onClick={focusVideo}
>
@ -492,7 +504,6 @@ export const PlaylistContent = () => {
gridTemplateColumns: "1fr 1fr",
}}
>
{" "}
<Box>
<StyledCardHeaderComment
sx={{
@ -627,108 +638,104 @@ export const PlaylistContent = () => {
)}
<Spacer height="30px" />
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
{videoData?.fullDescription && (
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "auto"
: "100px",
overflow: "hidden",
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
cursor: "default",
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
>
{videoData?.fullDescription}
</VideoDescription>
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
</Box>
{descriptionHeight && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
<Box
ref={contentRef}
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: `${descriptionHeight}px`,
overflow: "hidden",
}}
>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
)}
</>
)}
{videoData?.id && videoData?.user && (
<SuperLikesSection
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
postId={videoData?.id || ""}
postName={videoData?.user || ""}
/>
)}
{videoData?.id && channelName && (
<CommentSection
postId={videoData?.id || ""}
postName={channelName || ""}
/>
)}
</VideoPlayerContainer>
<SuperLikesSection
getMore={() => {}}
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
postId={videoData?.id || ""}
postName={videoData?.user || ""}
/>
<Box
sx={{
display: "flex",
gap: "20px",
width: "100%",
maxWidth: "1200px",
}}
>
<CommentSection
postId={videoData?.id || ""}
postName={channelName || ""}
/>
</Box>
</Box>
);
};

56
src/pages/ContentPages/VideoContent/VideoContent-functions.ts

@ -0,0 +1,56 @@
export function isTimestampWithinRange(resTimestamp, resCreated) {
// Calculate the absolute difference in milliseconds
const difference = Math.abs(resTimestamp - resCreated);
// 2 minutes in milliseconds
const twoMinutesInMilliseconds = 3 * 60 * 1000;
// Check if the difference is within 2 minutes
return difference <= twoMinutesInMilliseconds;
}
export function extractSigValue(metadescription) {
// Function to extract the substring within double asterisks
function extractSubstring(str) {
const match = str.match(/\*\*(.*?)\*\*/);
return match ? match[1] : null;
}
// Function to extract the 'sig' value
function extractSig(str) {
const regex = /sig:(.*?)(;|$)/;
const match = str.match(regex);
return match ? match[1] : null;
}
// Extracting the relevant substring
const relevantSubstring = extractSubstring(metadescription);
if (relevantSubstring) {
// Extracting the 'sig' value
return extractSig(relevantSubstring);
} else {
return null;
}
}
export const getPaymentInfo = async (signature: string) => {
try {
const url = `/transactions/signature/${signature}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
// Coin payment info must be added to responseData so we can display it to the user
const responseData = await response.json();
if (responseData && !responseData.error) {
return responseData;
} else {
throw new Error("unable to get payment");
}
} catch (error) {
throw new Error("unable to get payment");
}
};

11
src/pages/ContentPages/VideoContent/VideoContent-styles.tsx

@ -1,12 +1,15 @@
import { styled } from "@mui/system";
import { Box, Grid, Typography, Checkbox } from "@mui/material";
export const VideoPlayerContainer = styled(Box)(({ theme }) => ({
maxWidth: "95%",
width: "1000px",
export const VideoContentContainer = styled(Box)(({ theme }) => ({
display: "flex",
flexDirection: "column",
alignItems: "flex-start",
alignItems: "start",
}));
export const VideoPlayerContainer = styled(Box)(({ theme }) => ({
width: "55vw",
marginLeft: "5%",
}));
export const VideoTitle = styled(Typography)(({ theme }) => ({

627
src/pages/ContentPages/VideoContent/VideoContent.tsx

@ -1,26 +1,45 @@
import DownloadIcon from "@mui/icons-material/Download";
import { Avatar, Box, Typography, useTheme } from "@mui/material";
import React, {
useState,
useCallback,
useEffect,
useMemo,
useRef,
useEffect,
useCallback,
useState,
} from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate, useParams } from "react-router-dom";
import ResponsiveImage from "../../../components/ResponsiveImage.tsx";
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
import { Avatar, Box, Typography, useTheme } from "@mui/material";
import DeletedVideo from "../../../assets/img/DeletedVideo.jpg";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAndDislike.tsx";
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import FileElement from "../../../components/common/FileElement.tsx";
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import {
refType,
VideoPlayer,
} from "../../../components/common/VideoPlayer/VideoPlayer.tsx";
import { RootState } from "../../../state/store.ts";
import {
QTUBE_VIDEO_BASE,
SUPER_LIKE_BASE,
} from "../../../constants/Identifiers.ts";
import {
minPriceSuperlike,
titleFormatterOnSave,
} from "../../../constants/Misc.ts";
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
import { setIsLoadingGlobal } from "../../../state/features/globalSlice.ts";
import { addToHashMap } from "../../../state/features/videoSlice.ts";
import AttachFileIcon from "@mui/icons-material/AttachFile";
import DownloadIcon from "@mui/icons-material/Download";
import DeletedVideo from "../../../assets/img/DeletedVideo.jpg";
import mockImg from "../../../test/mockimg.jpg";
import { RootState } from "../../../state/store.ts";
import { formatDate } from "../../../utils/time.ts";
import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "./VideoContent-functions.ts";
import {
AuthorTextComment,
FileAttachmentContainer,
@ -29,98 +48,10 @@ import {
StyledCardColComment,
StyledCardHeaderComment,
VideoDescription,
VideoPlayerContainer,
VideoContentContainer,
VideoTitle,
VideoPlayerContainer,
} from "./VideoContent-styles.tsx";
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
import {
formatDate,
formatDateSeconds,
formatTimestampSeconds,
} from "../../../utils/time.ts";
import { NavbarName } from "../../../components/layout/Navbar/Navbar-styles.tsx";
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
import {
CrowdfundSubTitle,
CrowdfundSubTitleRow,
} from "../../../components/Publish/PublishVideo/PublishVideo-styles.tsx";
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
import FileElement from "../../../components/common/FileElement.tsx";
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
import { 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";
import {
minPriceSuperlike,
titleFormatterOnSave,
} 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
const difference = Math.abs(resTimestamp - resCreated);
// 2 minutes in milliseconds
const twoMinutesInMilliseconds = 3 * 60 * 1000;
// Check if the difference is within 2 minutes
return difference <= twoMinutesInMilliseconds;
}
export function extractSigValue(metadescription) {
// Function to extract the substring within double asterisks
function extractSubstring(str) {
const match = str.match(/\*\*(.*?)\*\*/);
return match ? match[1] : null;
}
// Function to extract the 'sig' value
function extractSig(str) {
const regex = /sig:(.*?)(;|$)/;
const match = str.match(regex);
return match ? match[1] : null;
}
// Extracting the relevant substring
const relevantSubstring = extractSubstring(metadescription);
if (relevantSubstring) {
// Extracting the 'sig' value
return extractSig(relevantSubstring);
} else {
return null;
}
}
export const getPaymentInfo = async (signature: string) => {
try {
const url = `/transactions/signature/${signature}`;
const response = await fetch(url, {
method: "GET",
headers: {
"Content-Type": "application/json",
},
});
// Coin payment info must be added to responseData so we can display it to the user
const responseData = await response.json();
if (responseData && !responseData.error) {
return responseData;
} else {
throw new Error("unable to get payment");
}
} catch (error) {
throw new Error("unable to get payment");
}
};
export const VideoContent = () => {
const { name: channelName, id } = useParams();
@ -130,6 +61,7 @@ export const VideoContent = () => {
useState<boolean>(false);
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
const containerRef = useRef<refType>(null);
@ -149,6 +81,8 @@ export const VideoContent = () => {
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
null
);
const [videoData, setVideoData] = useState<any>(null);
const [isVideoLoaded, setIsVideoLoaded] = useState<boolean>(false);
const userAvatarHash = useSelector(
(state: RootState) => state.global.userAvatarHash
@ -183,8 +117,6 @@ export const VideoContent = () => {
const navigate = useNavigate();
const theme = useTheme();
const [videoData, setVideoData] = useState<any>(null);
const saveAsFilename = useMemo(() => {
// nb. we prefer to construct the local filename to use for
// saving, from the video "title" when possible
@ -224,6 +156,7 @@ export const VideoContent = () => {
videoReference?.name &&
videoReference?.service
) {
setIsVideoLoaded(true);
return videoReference;
} else {
return null;
@ -302,13 +235,12 @@ export const VideoContent = () => {
}
}, [id, channelName]);
const descriptionThreshold = 200;
useEffect(() => {
if (contentRef.current) {
const height = contentRef.current.offsetHeight;
if (height > 100) {
// Assuming 100px is your threshold
setDescriptionHeight(100);
}
if (height > descriptionThreshold)
setDescriptionHeight(descriptionThreshold);
}
}, [videoData]);
@ -382,35 +314,42 @@ export const VideoContent = () => {
);
const focusVideo = (e: React.MouseEvent<HTMLDivElement>) => {
const focusRef = containerRef.current?.getContainerRef()?.current;
const isCorrectTarget = e.currentTarget == e.target;
if (focusRef && isCorrectTarget) {
console.log("in focusVideo");
const target = e.target as Element;
const textTagNames = ["TEXTAREA", "P", "H[1-6]", "STRONG", "svg", "A"];
const noText =
textTagNames.findIndex(s => {
return target?.tagName.match(s);
}) < 0;
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const clickOnEmptySpace = !target?.onclick && noText;
console.log("tagName is: ", target?.tagName);
// clicking on link in superlikes bar shows deleted video when loading
if (target == e.currentTarget || clickOnEmptySpace) {
console.log("in correctTarget");
const focusRef = containerRef.current?.getContainerRef()?.current;
focusRef.focus({ preventScroll: true });
}
};
return (
<Box
sx={{
display: "flex",
alignItems: "center",
flexDirection: "column",
padding: "0px 10px",
}}
onClick={focusVideo}
>
<VideoPlayerContainer
<>
<Box
sx={{
width: "55vw",
aspectRatio: "16/9",
display: "flex",
marginLeft: "5%",
flexDirection: "column",
padding: "0px 10px",
}}
onClick={focusVideo}
>
{videoReference ? (
<Box
sx={{
aspectRatio: "16/9",
}}
>
<VideoPlayerContainer>
<VideoPlayer
name={videoReference?.name}
service={videoReference?.service}
@ -424,46 +363,35 @@ export const VideoContent = () => {
video: { aspectRatio: "16 / 9" },
}}
/>
</Box>
</VideoPlayerContainer>
) : isVideoLoaded ? (
<img
src={DeletedVideo}
width={"70%"}
height={"37%"}
style={{ marginLeft: "5%" }}
/>
) : (
<img src={DeletedVideo} width={"100%"} height={"100%"} />
<Box sx={{ width: "55vw", aspectRatio: "16/9" }}></Box>
)}
<Box
sx={{
width: "100%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
marginTop: "15px",
}}
>
<Box>
<StyledCardHeaderComment
sx={{
"& .MuiCardHeader-content": {
overflow: "hidden",
},
}}
>
<Box
<VideoContentContainer>
<Box
sx={{
width: "80%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
marginTop: "15px",
}}
>
<Box>
<StyledCardHeaderComment
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
"& .MuiCardHeader-content": {
overflow: "hidden",
},
}}
>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
<Box
sx={{
cursor: "pointer",
}}
@ -471,196 +399,223 @@ export const VideoContent = () => {
navigate(`/channel/${channelName}`);
}}
>
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
<Avatar
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
alt={`${channelName}'s avatar`}
/>
</Box>
<StyledCardColComment>
<AuthorTextComment
color={
theme.palette.mode === "light"
? theme.palette.text.secondary
: "#d6e8ff"
}
sx={{
cursor: "pointer",
}}
onClick={() => {
navigate(`/channel/${channelName}`);
}}
>
{channelName}
{channelName !== userName && (
<>
<SubscribeButton
subscriberName={channelName}
sx={{ marginLeft: "20px" }}
/>
<FollowButton
followerName={channelName}
sx={{ marginLeft: "20px" }}
/>
</>
)}
</AuthorTextComment>
</StyledCardColComment>
</StyledCardHeaderComment>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
}}
>
{videoData && (
<>
<LikeAndDislike
name={videoData?.user}
identifier={videoData?.id}
/>
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
name={videoData?.user}
service={videoData?.service}
identifier={videoData?.id}
onSuccess={val => {
setSuperlikelist(prev => [val, ...prev]);
}}
/>
</>
)}
{videoData?.filename && (
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename: saveAsFilename,
mimeType: videoData?.videoType || '"video/mp4',
}}
title={
videoData?.filename || videoData?.title?.slice(0, 20)
}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
)}
</Box>
</Box>
<Box
sx={{
display: "flex",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "20px",
gap: "10px",
}}
>
{videoData && (
<>
<LikeAndDislike
name={videoData?.user}
identifier={videoData?.id}
/>
<SuperLike
numberOfSuperlikes={numberOfSuperlikes}
totalAmount={calculateAmountSuperlike}
name={videoData?.user}
service={videoData?.service}
identifier={videoData?.id}
onSuccess={val => {
setSuperlikelist(prev => [val, ...prev]);
}}
/>
</>
)}
<FileAttachmentContainer>
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
<FileElement
fileInfo={{
...videoReference,
filename: saveAsFilename,
mimeType: videoData?.videoType || '"video/mp4',
}}
title={videoData?.filename || videoData?.title?.slice(0, 20)}
customStyles={{
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
}}
>
<DownloadIcon />
</FileElement>
</FileAttachmentContainer>
</Box>
</Box>
<Box
sx={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
width: "100%",
marginTop: "20px",
gap: "10px",
}}
>
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
textAlign: "start",
}}
>
{videoData?.title}
</VideoTitle>
</Box>
{videoData?.created && (
<Typography
variant="h6"
sx={{
fontSize: "16px",
}}
color={theme.palette.text.primary}
>
{formatDate(videoData.created)}
</Typography>
)}
<Spacer height="30px" />
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "100%",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
}}
className={
!descriptionHeight ? "" : isExpandedDescription ? "" : "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
<VideoTitle
variant="h1"
color="textPrimary"
sx={{
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
textAlign: "start",
}}
/>
)}
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: "100px",
overflow: "hidden",
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
>
{videoData?.title}
</VideoTitle>
</Box>
{descriptionHeight && (
{videoData?.created && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
variant="h6"
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
color={theme.palette.text.primary}
>
{isExpandedDescription ? "Show less" : "...more"}
{formatDate(videoData.created)}
</Typography>
)}
</Box>
</VideoPlayerContainer>
<SuperLikesSection
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
getMore={() => {}}
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
postId={id || ""}
postName={channelName || ""}
/>
<Box
sx={{
display: "flex",
gap: "20px",
width: "100%",
maxWidth: "1200px",
}}
>
<CommentSection postId={id || ""} postName={channelName || ""} />
<Spacer height="30px" />
{videoData?.fullDescription && (
<Box
sx={{
background: "#333333",
borderRadius: "5px",
padding: "5px",
width: "70%",
cursor: !descriptionHeight
? "default"
: isExpandedDescription
? "default"
: "pointer",
position: "relative",
marginBottom: "30px",
}}
className={
!descriptionHeight
? ""
: isExpandedDescription
? ""
: "hover-click"
}
>
{descriptionHeight && !isExpandedDescription && (
<Box
sx={{
position: "absolute",
top: "0px",
right: "0px",
left: "0px",
bottom: "0px",
cursor: "pointer",
}}
onClick={() => {
if (isExpandedDescription) return;
setIsExpandedDescription(true);
}}
/>
)}
<Box
ref={contentRef}
sx={{
height: !descriptionHeight
? "auto"
: isExpandedDescription
? "auto"
: "200px",
overflow: "hidden",
}}
>
{videoData?.htmlDescription ? (
<DisplayHtml html={videoData?.htmlDescription} />
) : (
<VideoDescription
variant="body1"
color="textPrimary"
sx={{
cursor: "default",
}}
>
{videoData?.fullDescription}
</VideoDescription>
)}
</Box>
{descriptionHeight >= descriptionThreshold && (
<Typography
onClick={() => {
setIsExpandedDescription(prev => !prev);
}}
sx={{
fontWeight: "bold",
fontSize: "16px",
cursor: "pointer",
paddingLeft: "15px",
paddingTop: "15px",
}}
>
{isExpandedDescription ? "Show less" : "...more"}
</Typography>
)}
</Box>
)}
{id && channelName && (
<>
<SuperLikesSection
/* eslint-disable-next-line @typescript-eslint/no-empty-function */
getMore={() => {}}
loadingSuperLikes={loadingSuperLikes}
superlikes={superlikeList}
postId={id || ""}
postName={channelName || ""}
/>
<CommentSection postId={id || ""} postName={channelName || ""} />
</>
)}
</VideoContentContainer>
</Box>
</Box>
</>
);
};

1
src/state/features/persistSlice.ts

@ -77,6 +77,7 @@ export const persistSlice = createSlice({
export const {
setHomePageSelectedTab,
setStretchVideoSetting,
subscribe,
unSubscribe,
setReduxPlaybackRate,

15
src/wrappers/GlobalWrapper.tsx

@ -6,6 +6,11 @@ import React, {
useMemo,
} from "react";
import { useDispatch, useSelector } from "react-redux";
import {
extractSigValue,
getPaymentInfo,
isTimestampWithinRange,
} from "../pages/ContentPages/VideoContent/VideoContent-functions.ts";
import { addUser } from "../state/features/authSlice";
import NavBar from "../components/layout/Navbar/Navbar";
@ -21,11 +26,6 @@ import { RequestQueue } from "../utils/queue";
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/ContentPages/VideoContent/VideoContent";
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
import { minPriceSuperlike } from "../constants/Misc.ts";
@ -185,7 +185,9 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
];
validCount++;
}
} catch (error) {}
} catch (error) {
console.log(error);
}
}
}
}
@ -194,7 +196,6 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
dispatch(setSuperlikesAll(comments));
} catch (error) {
console.error(error);
} finally {
}
}, []);

Loading…
Cancel
Save