mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-16 04:05:52 +00:00
Merge pull request #23 from QortalSeth/main
Follow, Like, and Dislike buttons Added to Video, Playlist, and Channel pages
This commit is contained in:
commit
b8463daef0
@ -8,14 +8,14 @@ import { Provider } from "react-redux";
|
|||||||
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
import GlobalWrapper from "./wrappers/GlobalWrapper";
|
||||||
import Notification from "./components/common/Notification/Notification";
|
import Notification from "./components/common/Notification/Notification";
|
||||||
import { Home } from "./pages/Home/Home";
|
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 DownloadWrapper from "./wrappers/DownloadWrapper";
|
||||||
import { IndividualProfile } from "./pages/IndividualProfile/IndividualProfile";
|
import { IndividualProfile } from "./pages/ContentPages/IndividualProfile/IndividualProfile";
|
||||||
import { PlaylistContent } from "./pages/PlaylistContent/PlaylistContent";
|
import { PlaylistContent } from "./pages/ContentPages/PlaylistContent/PlaylistContent";
|
||||||
import { PersistGate } from "redux-persist/integration/react";
|
import { PersistGate } from "redux-persist/integration/react";
|
||||||
import { persistStore } from "redux-persist";
|
import { persistStore } from "redux-persist";
|
||||||
import { setFilteredSubscriptions } from "./state/features/videoSlice.ts";
|
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 () => {
|
export const getUserName = async () => {
|
||||||
const account = await qortalRequest({
|
const account = await qortalRequest({
|
||||||
|
@ -3,7 +3,7 @@ import { CardContentContainerComment } from "../common/Comments/Comments-styles"
|
|||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../PublishVideo/PublishVideo-styles.tsx";
|
} from "../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { Box, Typography, useTheme } from "@mui/material";
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ import {
|
|||||||
NewCrowdfundTitle,
|
NewCrowdfundTitle,
|
||||||
StyledButton,
|
StyledButton,
|
||||||
TimesIcon,
|
TimesIcon,
|
||||||
} from "./Upload-styles";
|
} from "./Upload-styles.tsx";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
FormControl,
|
FormControl,
|
||||||
@ -30,9 +30,9 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
||||||
import { setNotification } from "../../state/features/notificationsSlice";
|
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import {
|
import {
|
||||||
upsertVideosBeginning,
|
upsertVideosBeginning,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
@ -41,17 +41,17 @@ import {
|
|||||||
updateVideo,
|
updateVideo,
|
||||||
updateInHashMap,
|
updateInHashMap,
|
||||||
setEditPlaylist,
|
setEditPlaylist,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../../state/features/videoSlice.ts";
|
||||||
import ImageUploader from "../common/ImageUploader";
|
import ImageUploader from "../../common/ImageUploader.tsx";
|
||||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
import { categories, subCategories } from "../../../constants/Categories.ts";
|
||||||
import { Playlists } from "../Playlists/Playlists";
|
import { Playlists } from "../../Playlists/Playlists.tsx";
|
||||||
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
|
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit.tsx";
|
||||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
import { TextEditor } from "../../common/TextEditor/TextEditor.tsx";
|
||||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
|
||||||
import {
|
import {
|
||||||
QTUBE_PLAYLIST_BASE,
|
QTUBE_PLAYLIST_BASE,
|
||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
} from "../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
@ -9,10 +9,10 @@ import {
|
|||||||
Rating,
|
Rating,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
Select
|
Select,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
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)`
|
export const DoubleLine = styled(Typography)`
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Copse",
|
fontFamily: "Copse",
|
||||||
letterSpacing: "1px",
|
letterSpacing: "1px",
|
||||||
@ -539,8 +537,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
|
|||||||
|
|
||||||
export const StyledButton = styled(Button)(({ theme }) => ({
|
export const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: theme.palette.text.primary
|
color: theme.palette.text.primary,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
export const CustomSelect = styled(Select)(({ theme }) => ({
|
export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
@ -549,34 +547,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
|
|||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
'& .MuiSelect-select': {
|
"& .MuiSelect-select": {
|
||||||
padding: '12px',
|
padding: "12px",
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
letterSpacing: "0px",
|
letterSpacing: "0px",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
borderRadius: theme.shape.borderRadius, // Match border radius
|
borderRadius: theme.shape.borderRadius, // Match border radius
|
||||||
},
|
},
|
||||||
'&:before': {
|
"&:before": {
|
||||||
// Underline style
|
// Underline style
|
||||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||||
},
|
},
|
||||||
'&:after': {
|
"&:after": {
|
||||||
// Underline style when focused
|
// Underline style when focused
|
||||||
borderBottomColor: theme.palette.secondary.main,
|
borderBottomColor: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
'& .MuiOutlinedInput-root': {
|
"& .MuiOutlinedInput-root": {
|
||||||
'& fieldset': {
|
"& fieldset": {
|
||||||
borderColor: "#E0E3E7",
|
borderColor: "#E0E3E7",
|
||||||
},
|
},
|
||||||
'&:hover fieldset': {
|
"&:hover fieldset": {
|
||||||
borderColor: "#B2BAC2",
|
borderColor: "#B2BAC2",
|
||||||
},
|
},
|
||||||
'&.Mui-focused fieldset': {
|
"&.Mui-focused fieldset": {
|
||||||
borderColor: "#6F7E8C",
|
borderColor: "#6F7E8C",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'& .MuiInputBase-root': {
|
"& .MuiInputBase-root": {
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
letterSpacing: "0px",
|
letterSpacing: "0px",
|
@ -9,10 +9,10 @@ import {
|
|||||||
Rating,
|
Rating,
|
||||||
TextField,
|
TextField,
|
||||||
Typography,
|
Typography,
|
||||||
Select
|
Select,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
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)`
|
export const DoubleLine = styled(Typography)`
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
@ -159,8 +159,6 @@ export const CustomInputField = styled(TextField)(({ theme }) => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
export const CrowdfundTitle = styled(Typography)(({ theme }) => ({
|
||||||
fontFamily: "Copse",
|
fontFamily: "Copse",
|
||||||
letterSpacing: "1px",
|
letterSpacing: "1px",
|
||||||
@ -539,8 +537,8 @@ export const NoReviewsFont = styled(Typography)(({ theme }) => ({
|
|||||||
|
|
||||||
export const StyledButton = styled(Button)(({ theme }) => ({
|
export const StyledButton = styled(Button)(({ theme }) => ({
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
color: theme.palette.text.primary
|
color: theme.palette.text.primary,
|
||||||
}))
|
}));
|
||||||
|
|
||||||
export const CustomSelect = styled(Select)(({ theme }) => ({
|
export const CustomSelect = styled(Select)(({ theme }) => ({
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
@ -549,34 +547,34 @@ export const CustomSelect = styled(Select)(({ theme }) => ({
|
|||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
color: theme.palette.text.primary,
|
color: theme.palette.text.primary,
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
'& .MuiSelect-select': {
|
"& .MuiSelect-select": {
|
||||||
padding: '12px',
|
padding: "12px",
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
letterSpacing: "0px",
|
letterSpacing: "0px",
|
||||||
fontWeight: 400,
|
fontWeight: 400,
|
||||||
borderRadius: theme.shape.borderRadius, // Match border radius
|
borderRadius: theme.shape.borderRadius, // Match border radius
|
||||||
},
|
},
|
||||||
'&:before': {
|
"&:before": {
|
||||||
// Underline style
|
// Underline style
|
||||||
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
borderBottomColor: theme.palette.mode === "light" ? "#B2BAC2" : "#c9cccf",
|
||||||
},
|
},
|
||||||
'&:after': {
|
"&:after": {
|
||||||
// Underline style when focused
|
// Underline style when focused
|
||||||
borderBottomColor: theme.palette.secondary.main,
|
borderBottomColor: theme.palette.secondary.main,
|
||||||
},
|
},
|
||||||
'& .MuiOutlinedInput-root': {
|
"& .MuiOutlinedInput-root": {
|
||||||
'& fieldset': {
|
"& fieldset": {
|
||||||
borderColor: "#E0E3E7",
|
borderColor: "#E0E3E7",
|
||||||
},
|
},
|
||||||
'&:hover fieldset': {
|
"&:hover fieldset": {
|
||||||
borderColor: "#B2BAC2",
|
borderColor: "#B2BAC2",
|
||||||
},
|
},
|
||||||
'&.Mui-focused fieldset': {
|
"&.Mui-focused fieldset": {
|
||||||
borderColor: "#6F7E8C",
|
borderColor: "#6F7E8C",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'& .MuiInputBase-root': {
|
"& .MuiInputBase-root": {
|
||||||
fontFamily: "Mulish",
|
fontFamily: "Mulish",
|
||||||
fontSize: "19px",
|
fontSize: "19px",
|
||||||
letterSpacing: "0px",
|
letterSpacing: "0px",
|
@ -34,9 +34,9 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import AddBoxIcon from "@mui/icons-material/AddBox";
|
import AddBoxIcon from "@mui/icons-material/AddBox";
|
||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
|
|
||||||
import { setNotification } from "../../state/features/notificationsSlice";
|
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import {
|
import {
|
||||||
upsertVideosBeginning,
|
upsertVideosBeginning,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
@ -44,16 +44,16 @@ import {
|
|||||||
setEditVideo,
|
setEditVideo,
|
||||||
updateVideo,
|
updateVideo,
|
||||||
updateInHashMap,
|
updateInHashMap,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../../state/features/videoSlice.ts";
|
||||||
import ImageUploader from "../common/ImageUploader";
|
import ImageUploader from "../../common/ImageUploader.tsx";
|
||||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
import { categories, subCategories } from "../../../constants/Categories.ts";
|
||||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll.tsx";
|
||||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
import { TextEditor } from "../../common/TextEditor/TextEditor.tsx";
|
||||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
|
||||||
import { toBase64 } from "../PublishVideo/PublishVideo.tsx";
|
import { toBase64 } from "../PublishVideo/PublishVideo.tsx";
|
||||||
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx";
|
||||||
import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts";
|
import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts";
|
||||||
import { titleFormatter } from "../../constants/Misc.ts";
|
import { titleFormatter } from "../../../constants/Misc.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
@ -8,8 +8,8 @@ import {
|
|||||||
useTheme,
|
useTheme,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import React, { useCallback, useEffect, useState, useRef } from "react";
|
import React, { useCallback, useEffect, useState, useRef } from "react";
|
||||||
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
|
import { CircleSVG } from "../../../assets/svgs/CircleSVG.tsx";
|
||||||
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
|
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG.tsx";
|
||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
|
|
||||||
interface Publish {
|
interface Publish {
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
@ -7,11 +7,11 @@ import {
|
|||||||
import { Box, Button, Input, Typography, useTheme } from "@mui/material";
|
import { Box, Button, Input, Typography, useTheme } from "@mui/material";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline";
|
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 AddIcon from "@mui/icons-material/Add";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts";
|
import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts";
|
||||||
export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
|
export const PlaylistListEdit = ({ playlistData, removeVideo, addVideo }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
@ -12,7 +12,7 @@ import {
|
|||||||
Select,
|
Select,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import AddPhotoAlternateIcon from "@mui/icons-material/AddPhotoAlternate";
|
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)`
|
export const DoubleLine = styled(Typography)`
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
@ -37,36 +37,36 @@ import AddBoxIcon from "@mui/icons-material/AddBox";
|
|||||||
import { useDropzone } from "react-dropzone";
|
import { useDropzone } from "react-dropzone";
|
||||||
import AddIcon from "@mui/icons-material/Add";
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
|
||||||
import { setNotification } from "../../state/features/notificationsSlice";
|
import { setNotification } from "../../../state/features/notificationsSlice.ts";
|
||||||
import { objectToBase64, uint8ArrayToBase64 } from "../../utils/toBase64";
|
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import {
|
import {
|
||||||
upsertVideosBeginning,
|
upsertVideosBeginning,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
upsertVideos,
|
upsertVideos,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../../state/features/videoSlice.ts";
|
||||||
import ImageUploader from "../common/ImageUploader";
|
import ImageUploader from "../../common/ImageUploader.tsx";
|
||||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
import { categories, subCategories } from "../../../constants/Categories.ts";
|
||||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublishAll";
|
import { MultiplePublish } from "../MultiplePublish/MultiplePublishAll.tsx";
|
||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../EditPlaylist/Upload-styles";
|
} from "../EditPlaylist/Upload-styles.tsx";
|
||||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
import { CardContentContainerComment } from "../../common/Comments/Comments-styles.tsx";
|
||||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
import { TextEditor } from "../../common/TextEditor/TextEditor.tsx";
|
||||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
|
||||||
import {
|
import {
|
||||||
FiltersCheckbox,
|
FiltersCheckbox,
|
||||||
FiltersRow,
|
FiltersRow,
|
||||||
FiltersSubContainer,
|
FiltersSubContainer,
|
||||||
} from "../../pages/Home/VideoList-styles";
|
} from "../../../pages/Home/VideoList-styles.tsx";
|
||||||
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx";
|
||||||
import {
|
import {
|
||||||
QTUBE_PLAYLIST_BASE,
|
QTUBE_PLAYLIST_BASE,
|
||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
} from "../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
import { titleFormatter } from "../../constants/Misc.ts";
|
import { titleFormatter } from "../../../constants/Misc.ts";
|
||||||
import { getFileName } from "../../utils/stringFunctions.ts";
|
import { getFileName } from "../../../utils/stringFunctions.ts";
|
||||||
|
|
||||||
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
@ -12,8 +12,6 @@ export const StatsData = () => {
|
|||||||
width: "100%",
|
width: "100%",
|
||||||
padding: "20px 0px",
|
padding: "20px 0px",
|
||||||
backgroundColor: theme.palette.background.default,
|
backgroundColor: theme.palette.background.default,
|
||||||
borderTop: `1px solid ${theme.palette.background.paper}`,
|
|
||||||
borderRight: `1px solid ${theme.palette.background.paper}`,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -51,7 +49,10 @@ export const StatsData = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
Average:{" "}
|
Average:{" "}
|
||||||
<span style={{ fontWeight: "bold" }}>{videosPerNamePublished}</span>
|
<span style={{ fontWeight: "bold" }}>
|
||||||
|
{videosPerNamePublished > 0 &&
|
||||||
|
Number(videosPerNamePublished).toFixed(0)}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</StatsCol>
|
</StatsCol>
|
||||||
);
|
);
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
|
|
||||||
interface CommentSectionProps {
|
interface CommentSectionProps {
|
||||||
|
159
src/components/common/ContentButtons/FollowButton.tsx
Normal file
159
src/components/common/ContentButtons/FollowButton.tsx
Normal file
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -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
Normal file
230
src/components/common/ContentButtons/LikeAndDislike.tsx
Normal file
@ -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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
@ -1,10 +1,14 @@
|
|||||||
import { Button, ButtonProps } from "@mui/material";
|
import { Button, ButtonProps, Tooltip } from "@mui/material";
|
||||||
import { MouseEvent, useEffect, useState } from "react";
|
import { MouseEvent, useEffect, useState } from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { RootState } from "../../state/store.ts";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import { subscribe, unSubscribe } from "../../state/features/persistSlice.ts";
|
import {
|
||||||
import { setFilteredSubscriptions } from "../../state/features/videoSlice.ts";
|
subscribe,
|
||||||
import { subscriptionListFilter } from "../../App.tsx";
|
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 {
|
interface SubscribeButtonProps extends ButtonProps {
|
||||||
subscriberName: string;
|
subscriberName: string;
|
||||||
@ -89,7 +93,22 @@ export const SubscribeButton = ({
|
|||||||
height: "45px",
|
height: "45px",
|
||||||
...props.sx,
|
...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 (
|
return (
|
||||||
|
<Tooltip title={tooltipTitle} placement={"top"} arrow>
|
||||||
<Button
|
<Button
|
||||||
{...props}
|
{...props}
|
||||||
variant={"contained"}
|
variant={"contained"}
|
||||||
@ -99,5 +118,6 @@ export const SubscribeButton = ({
|
|||||||
>
|
>
|
||||||
{isSubscribed ? "Unsubscribe" : "Subscribe"}
|
{isSubscribed ? "Unsubscribe" : "Subscribe"}
|
||||||
</Button>
|
</Button>
|
||||||
|
</Tooltip>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -17,26 +17,25 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import qortImg from "../../../assets/img/qort.png";
|
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 { 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 ShortUniqueId from "short-unique-id";
|
||||||
import { objectToBase64 } from "../../../utils/toBase64";
|
import { objectToBase64 } from "../../../utils/toBase64.ts";
|
||||||
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
||||||
import { CommentInput } from "../Comments/Comments-styles";
|
import { CommentInput } from "../Comments/Comments-styles.tsx";
|
||||||
import {
|
import {
|
||||||
CrowdfundActionButton,
|
CrowdfundActionButton,
|
||||||
CrowdfundActionButtonRow,
|
CrowdfundActionButtonRow,
|
||||||
ModalBody,
|
ModalBody,
|
||||||
NewCrowdfundTitle,
|
NewCrowdfundTitle,
|
||||||
Spacer,
|
Spacer,
|
||||||
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor";
|
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor.tsx";
|
||||||
import { RootState } from "../../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import {
|
import {
|
||||||
FOR,
|
FOR,
|
||||||
FOR_SUPER_LIKE,
|
FOR_SUPER_LIKE,
|
||||||
QTUBE_VIDEO_BASE,
|
|
||||||
SUPER_LIKE_BASE,
|
SUPER_LIKE_BASE,
|
||||||
} from "../../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
|
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
|
||||||
@ -158,7 +157,7 @@ export const SuperLike = ({
|
|||||||
for: `${name}_${FOR_SUPER_LIKE}`,
|
for: `${name}_${FOR_SUPER_LIKE}`,
|
||||||
},
|
},
|
||||||
about:
|
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
|
// Description is obtained from raw data
|
||||||
// const base64 = utf8ToBase64(comment);
|
// const base64 = utf8ToBase64(comment);
|
||||||
@ -185,26 +184,16 @@ export const SuperLike = ({
|
|||||||
|
|
||||||
setIsOpenMultiplePublish(true);
|
setIsOpenMultiplePublish(true);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
let notificationObj: any = null;
|
dispatch(
|
||||||
if (typeof error === "string") {
|
setNotification({
|
||||||
notificationObj = {
|
msg:
|
||||||
msg: error || "Failed to publish Super Like",
|
error ||
|
||||||
|
error?.error ||
|
||||||
|
error?.message ||
|
||||||
|
"Failed to publish Super Like",
|
||||||
alertType: "error",
|
alertType: "error",
|
||||||
};
|
})
|
||||||
} else if (typeof error?.error === "string") {
|
);
|
||||||
notificationObj = {
|
|
||||||
msg: error?.error || "Failed to publish Super Like",
|
|
||||||
alertType: "error",
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
notificationObj = {
|
|
||||||
msg: error?.message || "Failed to publish Super Like",
|
|
||||||
alertType: "error",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (!notificationObj) return;
|
|
||||||
dispatch(setNotification(notificationObj));
|
|
||||||
|
|
||||||
throw new Error("Failed to publish Super Like");
|
throw new Error("Failed to publish Super Like");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -239,8 +228,6 @@ export const SuperLike = ({
|
|||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
||||||
|
|
||||||
<Tooltip title="Super Like" placement="top">
|
<Tooltip title="Super Like" placement="top">
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -250,8 +237,8 @@ export const SuperLike = ({
|
|||||||
display: "flex",
|
display: "flex",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
outline: "1px gold solid",
|
outline: "1px gold solid",
|
||||||
marginRight:'10px',
|
marginRight: "10px",
|
||||||
height: '53px',
|
height: "53px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ThumbUpIcon
|
<ThumbUpIcon
|
||||||
@ -261,21 +248,27 @@ export const SuperLike = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{numberOfSuperlikes === 0 ? null : (
|
{numberOfSuperlikes === 0 ? null : (
|
||||||
<div style={{
|
<div
|
||||||
display: 'flex',
|
style={{
|
||||||
alignItems: 'center',
|
display: "flex",
|
||||||
justifyContent: 'center', userSelect: "none"}}>
|
alignItems: "center",
|
||||||
<span style={{marginRight:'10px', paddingBottom:'4px'}}>{numberOfSuperlikes}</span>
|
justifyContent: "center",
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span style={{ marginRight: "10px", paddingBottom: "4px" }}>
|
||||||
|
{numberOfSuperlikes}
|
||||||
|
</span>
|
||||||
<img
|
<img
|
||||||
style={{
|
style={{
|
||||||
height: "25px",
|
height: "25px",
|
||||||
width: "25px",
|
width: "25px",
|
||||||
marginRight:'5px',
|
marginRight: "5px",
|
||||||
}}
|
}}
|
||||||
src={qortImg}
|
src={qortImg}
|
||||||
alt={"Qort Icon"}
|
alt={"Qort Icon"}
|
||||||
/>
|
/>
|
||||||
{truncateNumber(totalAmount,0)}
|
{truncateNumber(totalAmount, 0)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
@ -301,10 +294,10 @@ export const SuperLike = ({
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box>
|
<Box>
|
||||||
<InputLabel htmlFor="standard-adornment-amount">
|
<InputLabel htmlFor="standard-adornment-amount">
|
||||||
Amount in QORT (min 10 QORT)
|
Amount in QORT (min 1 QORT)
|
||||||
</InputLabel>
|
</InputLabel>
|
||||||
<BoundedNumericTextField
|
<BoundedNumericTextField
|
||||||
minValue={10}
|
minValue={+minPriceSuperlike}
|
||||||
initialValue={minPriceSuperlike.toString()}
|
initialValue={minPriceSuperlike.toString()}
|
||||||
maxValue={numberToInt(+currentBalance)}
|
maxValue={numberToInt(+currentBalance)}
|
||||||
allowDecimals={false}
|
allowDecimals={false}
|
@ -24,7 +24,7 @@ import {
|
|||||||
extractSigValue,
|
extractSigValue,
|
||||||
getPaymentInfo,
|
getPaymentInfo,
|
||||||
isTimestampWithinRange,
|
isTimestampWithinRange,
|
||||||
} from "../../../pages/VideoContent/VideoContent";
|
} from "../../../pages/ContentPages/VideoContent/VideoContent";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import localForage from "localforage";
|
import localForage from "localforage";
|
||||||
import moment from "moment";
|
import moment from "moment";
|
||||||
|
@ -17,7 +17,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
} from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
|
|
||||||
interface CommentSectionProps {
|
interface CommentSectionProps {
|
||||||
|
@ -1,18 +1,19 @@
|
|||||||
import { styled } from "@mui/system";
|
import { styled } from "@mui/system";
|
||||||
import { Box } from "@mui/material";
|
import { Box } from "@mui/material";
|
||||||
|
|
||||||
export const VideoContainer = styled(Box)`
|
export const VideoContainer = styled(Box)(({ theme }) => ({
|
||||||
position: relative;
|
position: "relative",
|
||||||
display: flex;
|
display: "flex",
|
||||||
flex-direction: column;
|
flexDirection: "column",
|
||||||
align-items: center;
|
alignItems: "center",
|
||||||
justify-content: center;
|
justifyContent: "center",
|
||||||
width: 100%;
|
width: "100%",
|
||||||
height: 100%;
|
height: "100%",
|
||||||
margin: 0;
|
margin: 0,
|
||||||
padding: 0;
|
padding: 0,
|
||||||
max-height: 70vh;
|
maxHeight: "70vh",
|
||||||
`;
|
"&:focus": { outline: "none" },
|
||||||
|
}));
|
||||||
|
|
||||||
export const VideoElement = styled("video")`
|
export const VideoElement = styled("video")`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -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 ReactDOM from "react-dom";
|
||||||
import { Box, IconButton, Slider } from "@mui/material";
|
import { Box, IconButton, Slider } from "@mui/material";
|
||||||
import { CircularProgress, Typography } from "@mui/material";
|
import { CircularProgress, Typography } from "@mui/material";
|
||||||
@ -45,7 +53,13 @@ interface VideoPlayerProps {
|
|||||||
style?: CSS.Properties;
|
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,
|
poster,
|
||||||
name,
|
name,
|
||||||
identifier,
|
identifier,
|
||||||
@ -59,11 +73,14 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
onEnd,
|
onEnd,
|
||||||
autoPlay,
|
autoPlay,
|
||||||
style = {},
|
style = {},
|
||||||
}) => {
|
}: VideoPlayerProps,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
const videoSelector = useSelector((state: RootState) => state.video);
|
const videoSelector = useSelector((state: RootState) => state.video);
|
||||||
const persistSelector = useSelector((state: RootState) => state.persist);
|
const persistSelector = useSelector((state: RootState) => state.persist);
|
||||||
const dispatch = useDispatch();
|
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 [playing, setPlaying] = useState(false);
|
||||||
const [volume, setVolume] = useState(1);
|
const [volume, setVolume] = useState(1);
|
||||||
const [mutedVolume, setMutedVolume] = useState(1);
|
const [mutedVolume, setMutedVolume] = useState(1);
|
||||||
@ -110,6 +127,14 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const maxSpeed = 4.0;
|
const maxSpeed = 4.0;
|
||||||
const speedChange = 0.25;
|
const speedChange = 0.25;
|
||||||
|
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
getVideoRef: () => {
|
||||||
|
return videoRef;
|
||||||
|
},
|
||||||
|
getContainerRef: () => {
|
||||||
|
return containerRef;
|
||||||
|
},
|
||||||
|
}));
|
||||||
const updatePlaybackRate = (newSpeed: number) => {
|
const updatePlaybackRate = (newSpeed: number) => {
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
|
if (newSpeed > maxSpeed || newSpeed < minSpeed) newSpeed = minSpeed;
|
||||||
@ -264,7 +289,10 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
|
|
||||||
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
document.addEventListener("fullscreenchange", handleFullscreenChange);
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("fullscreenchange", handleFullscreenChange);
|
document.removeEventListener(
|
||||||
|
"fullscreenchange",
|
||||||
|
handleFullscreenChange
|
||||||
|
);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -670,6 +698,7 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
padding: from === "create" ? "8px" : 0,
|
padding: from === "create" ? "8px" : 0,
|
||||||
...customStyle,
|
...customStyle,
|
||||||
}}
|
}}
|
||||||
|
ref={containerRef}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
{isLoading && (
|
||||||
<Box
|
<Box
|
||||||
@ -701,7 +730,9 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{resourceStatus?.status === "NOT_PUBLISHED" && (
|
{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" ? (
|
{resourceStatus?.status === "REFETCHING" ? (
|
||||||
<>
|
<>
|
||||||
@ -905,7 +936,8 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
: "visible",
|
: "visible",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{progress && videoRef.current?.duration && formatTime(progress)}/
|
{progress && videoRef.current?.duration && formatTime(progress)}
|
||||||
|
/
|
||||||
{progress &&
|
{progress &&
|
||||||
videoRef.current?.duration &&
|
videoRef.current?.duration &&
|
||||||
formatTime(videoRef.current?.duration)}
|
formatTime(videoRef.current?.duration)}
|
||||||
@ -963,4 +995,5 @@ export const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
</ControlsContainer>
|
</ControlsContainer>
|
||||||
</VideoContainer>
|
</VideoContainer>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
);
|
||||||
|
@ -35,8 +35,8 @@ import {
|
|||||||
} from "../../../state/features/videoSlice";
|
} from "../../../state/features/videoSlice";
|
||||||
import { RootState } from "../../../state/store";
|
import { RootState } from "../../../state/store";
|
||||||
import { useWindowSize } from "../../../hooks/useWindowSize";
|
import { useWindowSize } from "../../../hooks/useWindowSize";
|
||||||
import { PublishVideo } from "../../PublishVideo/PublishVideo.tsx";
|
import { PublishVideo } from "../../Publish/PublishVideo/PublishVideo.tsx";
|
||||||
import { StyledButton } from "../../PublishVideo/PublishVideo-styles.tsx";
|
import { StyledButton } from "../../Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { Notifications } from "../../common/Notifications/Notifications";
|
import { Notifications } from "../../common/Notifications/Notifications";
|
||||||
interface Props {
|
interface Props {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
|
@ -39,6 +39,7 @@ export const categories = [
|
|||||||
{ id: 24, name: "Anime" },
|
{ id: 24, name: "Anime" },
|
||||||
{ id: 25, name: "Cartoons" },
|
{ id: 25, name: "Cartoons" },
|
||||||
{ id: 26, name: "Qortal" },
|
{ id: 26, name: "Qortal" },
|
||||||
|
{ id: 99, name: "Other" },
|
||||||
].sort(sortCategory);
|
].sort(sortCategory);
|
||||||
|
|
||||||
export const subCategories: CategoryMap = {
|
export const subCategories: CategoryMap = {
|
||||||
@ -59,7 +60,7 @@ export const subCategories: CategoryMap = {
|
|||||||
{ id: 113, name: "Indie Films" },
|
{ id: 113, name: "Indie Films" },
|
||||||
{ id: 114, name: "International Films" },
|
{ id: 114, name: "International Films" },
|
||||||
{ id: 115, name: "Biographies & True Stories" },
|
{ id: 115, name: "Biographies & True Stories" },
|
||||||
{ id: 116, name: "Other" },
|
{ id: 199, name: "Other" },
|
||||||
].sort(sortCategory),
|
].sort(sortCategory),
|
||||||
2: [
|
2: [
|
||||||
// Series
|
// Series
|
||||||
@ -78,14 +79,14 @@ export const subCategories: CategoryMap = {
|
|||||||
{ id: 213, name: "Anthologies" },
|
{ id: 213, name: "Anthologies" },
|
||||||
{ id: 214, name: "International Series" },
|
{ id: 214, name: "International Series" },
|
||||||
{ id: 215, name: "Miniseries" },
|
{ id: 215, name: "Miniseries" },
|
||||||
{ id: 216, name: "Other" },
|
{ id: 299, name: "Other" },
|
||||||
].sort(sortCategory),
|
].sort(sortCategory),
|
||||||
4: [
|
4: [
|
||||||
// Education
|
// Education
|
||||||
{ id: 400, name: "Tutorial" },
|
{ id: 400, name: "Tutorial" },
|
||||||
{ id: 401, name: "Documentary" },
|
|
||||||
{ id: 401, name: "Qortal" },
|
{ id: 401, name: "Qortal" },
|
||||||
{ id: 402, name: "Other" },
|
{ id: 402, name: "Documentary" },
|
||||||
|
{ id: 499, name: "Other" },
|
||||||
].sort(sortCategory),
|
].sort(sortCategory),
|
||||||
|
|
||||||
24: [
|
24: [
|
||||||
@ -102,6 +103,6 @@ export const subCategories: CategoryMap = {
|
|||||||
{ id: 2411, name: "Harem" },
|
{ id: 2411, name: "Harem" },
|
||||||
{ id: 2412, name: "Ecchi" },
|
{ id: 2412, name: "Ecchi" },
|
||||||
{ id: 2413, name: "Idol" },
|
{ id: 2413, name: "Idol" },
|
||||||
{ id: 2414, name: "Other" },
|
{ id: 2499, name: "Other" },
|
||||||
].sort(sortCategory),
|
].sort(sortCategory),
|
||||||
};
|
};
|
||||||
|
@ -8,8 +8,12 @@ export const QTUBE_PLAYLIST_BASE = useTestIdentifiers
|
|||||||
export const SUPER_LIKE_BASE = useTestIdentifiers
|
export const SUPER_LIKE_BASE = useTestIdentifiers
|
||||||
? "MYTEST_superlike_"
|
? "MYTEST_superlike_"
|
||||||
: "qtube_superlike_";
|
: "qtube_superlike_";
|
||||||
|
|
||||||
|
export const LIKE_BASE = useTestIdentifiers ? "MYTEST_like_" : "qtube_like_";
|
||||||
|
|
||||||
export const COMMENT_BASE = useTestIdentifiers
|
export const COMMENT_BASE = useTestIdentifiers
|
||||||
? "qcomment_v1_MYTEST_"
|
? "qcomment_v1_MYTEST_"
|
||||||
: "qcomment_v1_qtube_";
|
: "qcomment_v1_qtube_";
|
||||||
export const FOR = useTestIdentifiers ? "FORTEST5" : "FOR0962";
|
export const FOR = useTestIdentifiers ? "FORTEST5" : "FOR0962";
|
||||||
export const FOR_SUPER_LIKE = useTestIdentifiers ? "MYTEST_sl" : `qtube_sl`;
|
export const FOR_SUPER_LIKE = useTestIdentifiers ? "MYTEST_sl" : `qtube_sl`;
|
||||||
|
export const FOR_LIKE = useTestIdentifiers ? "MYTEST_like" : `qtube_like`;
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
export const minPriceSuperlike = 10;
|
export const minPriceSuperlike = 1;
|
||||||
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
|
||||||
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
|
||||||
|
|
||||||
export const allTabValue = "all";
|
|
||||||
export const subscriptionTabValue = "subscriptions";
|
|
||||||
|
@ -24,9 +24,9 @@ import {
|
|||||||
QTUBE_PLAYLIST_BASE,
|
QTUBE_PLAYLIST_BASE,
|
||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
} from "../constants/Identifiers.ts";
|
} from "../constants/Identifiers.ts";
|
||||||
import { allTabValue, subscriptionTabValue } from "../constants/Misc.ts";
|
|
||||||
import { persistReducer } from "redux-persist";
|
import { persistReducer } from "redux-persist";
|
||||||
import { subscriptionListFilter } from "../App.tsx";
|
import { subscriptionListFilter } from "../App.tsx";
|
||||||
|
import { ContentType, VideoListType } from "../state/features/persistSlice.ts";
|
||||||
|
|
||||||
export const useFetchVideos = () => {
|
export const useFetchVideos = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -194,15 +194,15 @@ export const useFetchVideos = () => {
|
|||||||
category?: string;
|
category?: string;
|
||||||
subcategory?: string;
|
subcategory?: string;
|
||||||
keywords?: string;
|
keywords?: string;
|
||||||
type?: string;
|
contentType?: ContentType;
|
||||||
};
|
};
|
||||||
|
|
||||||
const emptyFilters = {
|
const emptyFilters: FilterType = {
|
||||||
name: "",
|
name: "",
|
||||||
category: "",
|
category: "",
|
||||||
subcategory: "",
|
subcategory: "",
|
||||||
keywords: "",
|
keywords: "",
|
||||||
type: "",
|
contentType: "videos",
|
||||||
};
|
};
|
||||||
const getVideos = React.useCallback(
|
const getVideos = React.useCallback(
|
||||||
async (
|
async (
|
||||||
@ -210,16 +210,16 @@ export const useFetchVideos = () => {
|
|||||||
reset?: boolean,
|
reset?: boolean,
|
||||||
resetFilters?: boolean,
|
resetFilters?: boolean,
|
||||||
limit?: number,
|
limit?: number,
|
||||||
listType = allTabValue
|
videoListType: VideoListType = "all"
|
||||||
) => {
|
) => {
|
||||||
emptyFilters.type = filters.type;
|
emptyFilters.contentType = filters.contentType;
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
name = "",
|
name = "",
|
||||||
category = "",
|
category = "",
|
||||||
subcategory = "",
|
subcategory = "",
|
||||||
keywords = "",
|
keywords = "",
|
||||||
type = filters.type,
|
contentType = filters.contentType,
|
||||||
}: FilterType = resetFilters ? emptyFilters : filters;
|
}: FilterType = resetFilters ? emptyFilters : filters;
|
||||||
let offset = videos.length;
|
let offset = videos.length;
|
||||||
if (reset) {
|
if (reset) {
|
||||||
@ -231,9 +231,7 @@ export const useFetchVideos = () => {
|
|||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
defaultUrl = defaultUrl + `&name=${name}`;
|
defaultUrl = defaultUrl + `&name=${name}`;
|
||||||
}
|
} else if (videoListType === "subscriptions") {
|
||||||
|
|
||||||
if (listType === subscriptionTabValue) {
|
|
||||||
const filteredSubscribeList = await subscriptionListFilter(false);
|
const filteredSubscribeList = await subscriptionListFilter(false);
|
||||||
filteredSubscribeList.map(sub => {
|
filteredSubscribeList.map(sub => {
|
||||||
defaultUrl += `&name=${sub.subscriberName}`;
|
defaultUrl += `&name=${sub.subscriberName}`;
|
||||||
@ -252,7 +250,7 @@ export const useFetchVideos = () => {
|
|||||||
if (keywords) {
|
if (keywords) {
|
||||||
defaultUrl = defaultUrl + `&query=${keywords}`;
|
defaultUrl = defaultUrl + `&query=${keywords}`;
|
||||||
}
|
}
|
||||||
if (type === "playlists") {
|
if (contentType === "playlists") {
|
||||||
defaultUrl = defaultUrl + `&service=PLAYLIST`;
|
defaultUrl = defaultUrl + `&service=PLAYLIST`;
|
||||||
defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`;
|
defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`;
|
||||||
} else {
|
} else {
|
||||||
@ -431,9 +429,7 @@ export const useFetchVideos = () => {
|
|||||||
const totalVideosPublished = responseData.length;
|
const totalVideosPublished = responseData.length;
|
||||||
const uniqueNames = new Set(responseData.map(video => video.name));
|
const uniqueNames = new Set(responseData.map(video => video.name));
|
||||||
const totalNamesPublished = uniqueNames.size;
|
const totalNamesPublished = uniqueNames.size;
|
||||||
const videosPerNamePublished = (
|
const videosPerNamePublished = totalVideosPublished / totalNamesPublished;
|
||||||
totalVideosPublished / totalNamesPublished
|
|
||||||
).toFixed(0);
|
|
||||||
|
|
||||||
dispatch(setTotalVideosPublished(totalVideosPublished));
|
dispatch(setTotalVideosPublished(totalVideosPublished));
|
||||||
dispatch(setTotalNamesPublished(totalNamesPublished));
|
dispatch(setTotalNamesPublished(totalNamesPublished));
|
||||||
|
@ -1,34 +1,37 @@
|
|||||||
import React, { useMemo } from "react";
|
import React, { useMemo } from "react";
|
||||||
import { VideoListComponentLevel } from "../Home/VideoListComponentLevel";
|
import { VideoListComponentLevel } from "../../Home/VideoListComponentLevel.tsx";
|
||||||
import { HeaderContainer, ProfileContainer } from "./Profile-styles";
|
import { HeaderContainer, ProfileContainer } from "./Profile-styles.tsx";
|
||||||
import {
|
import {
|
||||||
AuthorTextComment,
|
AuthorTextComment,
|
||||||
StyledCardColComment,
|
StyledCardColComment,
|
||||||
StyledCardHeaderComment,
|
StyledCardHeaderComment,
|
||||||
} from "../VideoContent/VideoContent-styles";
|
} from "../VideoContent/VideoContent-styles.tsx";
|
||||||
import { Avatar, Box, useTheme } from "@mui/material";
|
import { Avatar, Box, useTheme } from "@mui/material";
|
||||||
import { useParams } from "react-router-dom";
|
import { useParams } from "react-router-dom";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../../state/store.ts";
|
||||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
import { SubscribeButton } from "../../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||||
|
import { FollowButton } from "../../../components/common/ContentButtons/FollowButton.tsx";
|
||||||
|
|
||||||
export const IndividualProfile = () => {
|
export const IndividualProfile = () => {
|
||||||
const { name: paramName } = useParams();
|
const { name: channelName } = useParams();
|
||||||
|
const userName = useSelector((state: RootState) => state.auth.user?.name);
|
||||||
|
|
||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
);
|
);
|
||||||
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const avatarUrl = useMemo(() => {
|
const avatarUrl = useMemo(() => {
|
||||||
let url = "";
|
let url = "";
|
||||||
if (paramName && userAvatarHash[paramName]) {
|
if (channelName && userAvatarHash[channelName]) {
|
||||||
url = userAvatarHash[paramName];
|
url = userAvatarHash[channelName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}, [userAvatarHash, paramName]);
|
}, [userAvatarHash, channelName]);
|
||||||
return (
|
return (
|
||||||
<ProfileContainer>
|
<ProfileContainer>
|
||||||
<HeaderContainer>
|
<HeaderContainer>
|
||||||
@ -46,8 +49,8 @@ export const IndividualProfile = () => {
|
|||||||
>
|
>
|
||||||
<Box>
|
<Box>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={`/arbitrary/THUMBNAIL/${paramName}/qortal_avatar`}
|
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
|
||||||
alt={`${paramName}'s avatar`}
|
alt={`${channelName}'s avatar`}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<StyledCardColComment>
|
<StyledCardColComment>
|
||||||
@ -58,13 +61,21 @@ export const IndividualProfile = () => {
|
|||||||
: "#d6e8ff"
|
: "#d6e8ff"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{paramName}
|
{channelName}
|
||||||
</AuthorTextComment>
|
</AuthorTextComment>
|
||||||
</StyledCardColComment>
|
</StyledCardColComment>
|
||||||
|
{channelName !== userName && (
|
||||||
|
<>
|
||||||
<SubscribeButton
|
<SubscribeButton
|
||||||
subscriberName={paramName}
|
subscriberName={channelName}
|
||||||
sx={{ marginLeft: "10px" }}
|
sx={{ marginLeft: "10px" }}
|
||||||
/>
|
/>
|
||||||
|
<FollowButton
|
||||||
|
followerName={channelName}
|
||||||
|
sx={{ marginLeft: "20px" }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</StyledCardHeaderComment>
|
</StyledCardHeaderComment>
|
||||||
</Box>
|
</Box>
|
||||||
</HeaderContainer>
|
</HeaderContainer>
|
@ -7,15 +7,18 @@ import React, {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
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 { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||||
import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx";
|
import {
|
||||||
import { RootState } from "../../state/store";
|
refType,
|
||||||
import { addToHashMap } from "../../state/features/videoSlice";
|
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 AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
|
||||||
import mockImg from "../../test/mockimg.jpg";
|
import mockImg from "../../../test/mockimg.jpg";
|
||||||
import {
|
import {
|
||||||
AuthorTextComment,
|
AuthorTextComment,
|
||||||
FileAttachmentContainer,
|
FileAttachmentContainer,
|
||||||
@ -26,39 +29,43 @@ import {
|
|||||||
VideoDescription,
|
VideoDescription,
|
||||||
VideoPlayerContainer,
|
VideoPlayerContainer,
|
||||||
VideoTitle,
|
VideoTitle,
|
||||||
} from "./PlaylistContent-styles";
|
} from "./PlaylistContent-styles.tsx";
|
||||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
|
||||||
import {
|
import {
|
||||||
formatDate,
|
formatDate,
|
||||||
formatDateSeconds,
|
formatDateSeconds,
|
||||||
formatTimestampSeconds,
|
formatTimestampSeconds,
|
||||||
} from "../../utils/time";
|
} from "../../../utils/time.ts";
|
||||||
import { NavbarName } from "../../components/layout/Navbar/Navbar-styles";
|
import { NavbarName } from "../../../components/layout/Navbar/Navbar-styles.tsx";
|
||||||
import { CommentSection } from "../../components/common/Comments/CommentSection";
|
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
|
||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../../components/PublishVideo/PublishVideo-styles.tsx";
|
} from "../../../components/Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { Playlists } from "../../components/Playlists/Playlists";
|
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
|
||||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
|
||||||
import FileElement from "../../components/common/FileElement";
|
import FileElement from "../../../components/common/FileElement.tsx";
|
||||||
import { SuperLike } from "../../components/common/SuperLike/SuperLike";
|
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
|
||||||
import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes";
|
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
||||||
import {
|
import {
|
||||||
extractSigValue,
|
extractSigValue,
|
||||||
getPaymentInfo,
|
getPaymentInfo,
|
||||||
isTimestampWithinRange,
|
isTimestampWithinRange,
|
||||||
} from "../VideoContent/VideoContent";
|
} from "../VideoContent/VideoContent.tsx";
|
||||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
|
||||||
import {
|
import {
|
||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
SUPER_LIKE_BASE,
|
SUPER_LIKE_BASE,
|
||||||
} from "../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
import { minPriceSuperlike } from "../../constants/Misc.ts";
|
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
||||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
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 = () => {
|
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 [doAutoPlay, setDoAutoPlay] = useState(false);
|
||||||
const [isExpandedDescription, setIsExpandedDescription] =
|
const [isExpandedDescription, setIsExpandedDescription] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
@ -68,6 +75,7 @@ export const PlaylistContent = () => {
|
|||||||
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
||||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
||||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||||
|
const containerRef = useRef<refType>(null);
|
||||||
|
|
||||||
const calculateAmountSuperlike = useMemo(() => {
|
const calculateAmountSuperlike = useMemo(() => {
|
||||||
const totalQort = superlikeList?.reduce((acc, curr) => {
|
const totalQort = superlikeList?.reduce((acc, curr) => {
|
||||||
@ -95,10 +103,10 @@ export const PlaylistContent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (name) {
|
if (channelName) {
|
||||||
getAddressName(name);
|
getAddressName(channelName);
|
||||||
}
|
}
|
||||||
}, [name]);
|
}, [channelName]);
|
||||||
|
|
||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
@ -107,12 +115,12 @@ export const PlaylistContent = () => {
|
|||||||
|
|
||||||
const avatarUrl = useMemo(() => {
|
const avatarUrl = useMemo(() => {
|
||||||
let url = "";
|
let url = "";
|
||||||
if (name && userAvatarHash[name]) {
|
if (channelName && userAvatarHash[channelName]) {
|
||||||
url = userAvatarHash[name];
|
url = userAvatarHash[channelName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}, [userAvatarHash, name]);
|
}, [userAvatarHash, channelName]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -282,10 +290,10 @@ export const PlaylistContent = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (name && id) {
|
if (channelName && id) {
|
||||||
checkforPlaylist(name, id);
|
checkforPlaylist(channelName, id);
|
||||||
}
|
}
|
||||||
}, [id, name]);
|
}, [id, channelName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
@ -395,6 +403,14 @@ export const PlaylistContent = () => {
|
|||||||
getComments(videoData?.id, nameAddress);
|
getComments(videoData?.id, nameAddress);
|
||||||
}, [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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -403,6 +419,7 @@ export const PlaylistContent = () => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: "20px 10px",
|
padding: "20px 10px",
|
||||||
}}
|
}}
|
||||||
|
onClick={focusVideo}
|
||||||
>
|
>
|
||||||
<VideoPlayerContainer
|
<VideoPlayerContainer
|
||||||
sx={{
|
sx={{
|
||||||
@ -434,13 +451,14 @@ export const PlaylistContent = () => {
|
|||||||
name={videoReference?.name}
|
name={videoReference?.name}
|
||||||
service={videoReference?.service}
|
service={videoReference?.service}
|
||||||
identifier={videoReference?.identifier}
|
identifier={videoReference?.identifier}
|
||||||
user={name}
|
user={channelName}
|
||||||
jsonId={id}
|
jsonId={id}
|
||||||
poster={videoCover || ""}
|
poster={videoCover || ""}
|
||||||
nextVideo={nextVideo}
|
nextVideo={nextVideo}
|
||||||
onEnd={onEndVideo}
|
onEnd={onEndVideo}
|
||||||
autoPlay={doAutoPlay}
|
autoPlay={doAutoPlay}
|
||||||
customStyle={{ aspectRatio: "16/9" }}
|
customStyle={{ aspectRatio: "16/9" }}
|
||||||
|
ref={containerRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{playlistData && (
|
{playlistData && (
|
||||||
@ -473,12 +491,12 @@ export const PlaylistContent = () => {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/channel/${name}`);
|
navigate(`/channel/${channelName}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
|
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
|
||||||
alt={`${name}'s avatar`}
|
alt={`${channelName}'s avatar`}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<StyledCardColComment>
|
<StyledCardColComment>
|
||||||
@ -492,14 +510,22 @@ export const PlaylistContent = () => {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/channel/${name}`);
|
navigate(`/channel/${channelName}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
{channelName}
|
||||||
|
{channelName !== userName && (
|
||||||
|
<>
|
||||||
<SubscribeButton
|
<SubscribeButton
|
||||||
subscriberName={name}
|
subscriberName={channelName}
|
||||||
sx={{ marginLeft: "20px" }}
|
sx={{ marginLeft: "20px" }}
|
||||||
/>
|
/>
|
||||||
|
<FollowButton
|
||||||
|
followerName={channelName}
|
||||||
|
sx={{ marginLeft: "20px" }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</AuthorTextComment>
|
</AuthorTextComment>
|
||||||
</StyledCardColComment>
|
</StyledCardColComment>
|
||||||
</StyledCardHeaderComment>
|
</StyledCardHeaderComment>
|
||||||
@ -511,6 +537,11 @@ export const PlaylistContent = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoData && (
|
{videoData && (
|
||||||
|
<>
|
||||||
|
<LikeAndDislike
|
||||||
|
name={videoData?.user}
|
||||||
|
identifier={videoData?.id}
|
||||||
|
/>
|
||||||
<SuperLike
|
<SuperLike
|
||||||
numberOfSuperlikes={numberOfSuperlikes}
|
numberOfSuperlikes={numberOfSuperlikes}
|
||||||
totalAmount={calculateAmountSuperlike}
|
totalAmount={calculateAmountSuperlike}
|
||||||
@ -521,6 +552,7 @@ export const PlaylistContent = () => {
|
|||||||
setSuperlikelist(prev => [val, ...prev]);
|
setSuperlikelist(prev => [val, ...prev]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<FileAttachmentContainer>
|
<FileAttachmentContainer>
|
||||||
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
|
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
|
||||||
@ -677,7 +709,10 @@ export const PlaylistContent = () => {
|
|||||||
maxWidth: "1200px",
|
maxWidth: "1200px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CommentSection postId={videoData?.id || ""} postName={name || ""} />
|
<CommentSection
|
||||||
|
postId={videoData?.id || ""}
|
||||||
|
postName={channelName || ""}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
@ -7,15 +7,18 @@ import React, {
|
|||||||
} from "react";
|
} from "react";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
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 { Avatar, Box, Typography, useTheme } from "@mui/material";
|
||||||
import { VideoPlayer } from "../../components/common/VideoPlayer/VideoPlayer.tsx";
|
import {
|
||||||
import { RootState } from "../../state/store";
|
refType,
|
||||||
import { addToHashMap } from "../../state/features/videoSlice";
|
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 AttachFileIcon from "@mui/icons-material/AttachFile";
|
||||||
import DownloadIcon from "@mui/icons-material/Download";
|
import DownloadIcon from "@mui/icons-material/Download";
|
||||||
|
|
||||||
import mockImg from "../../test/mockimg.jpg";
|
import mockImg from "../../../test/mockimg.jpg";
|
||||||
import {
|
import {
|
||||||
AuthorTextComment,
|
AuthorTextComment,
|
||||||
FileAttachmentContainer,
|
FileAttachmentContainer,
|
||||||
@ -26,37 +29,39 @@ import {
|
|||||||
VideoDescription,
|
VideoDescription,
|
||||||
VideoPlayerContainer,
|
VideoPlayerContainer,
|
||||||
VideoTitle,
|
VideoTitle,
|
||||||
} from "./VideoContent-styles";
|
} from "./VideoContent-styles.tsx";
|
||||||
import { setUserAvatarHash } from "../../state/features/globalSlice";
|
import { setUserAvatarHash } from "../../../state/features/globalSlice.ts";
|
||||||
import {
|
import {
|
||||||
formatDate,
|
formatDate,
|
||||||
formatDateSeconds,
|
formatDateSeconds,
|
||||||
formatTimestampSeconds,
|
formatTimestampSeconds,
|
||||||
} from "../../utils/time";
|
} from "../../../utils/time.ts";
|
||||||
import { NavbarName } from "../../components/layout/Navbar/Navbar-styles";
|
import { NavbarName } from "../../../components/layout/Navbar/Navbar-styles.tsx";
|
||||||
import { CommentSection } from "../../components/common/Comments/CommentSection";
|
import { CommentSection } from "../../../components/common/Comments/CommentSection.tsx";
|
||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../../components/PublishVideo/PublishVideo-styles.tsx";
|
} from "../../../components/Publish/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { Playlists } from "../../components/Playlists/Playlists";
|
import { Playlists } from "../../../components/Playlists/Playlists.tsx";
|
||||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
import { DisplayHtml } from "../../../components/common/TextEditor/DisplayHtml.tsx";
|
||||||
import FileElement from "../../components/common/FileElement";
|
import FileElement from "../../../components/common/FileElement.tsx";
|
||||||
import { SuperLike } from "../../components/common/SuperLike/SuperLike";
|
import { SuperLike } from "../../../components/common/ContentButtons/SuperLike.tsx";
|
||||||
import { CommentContainer } from "../../components/common/Comments/Comments-styles";
|
import { CommentContainer } from "../../../components/common/Comments/Comments-styles.tsx";
|
||||||
import { Comment } from "../../components/common/Comments/Comment";
|
import { Comment } from "../../../components/common/Comments/Comment.tsx";
|
||||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
import { SuperLikesSection } from "../../../components/common/SuperLikesList/SuperLikesSection.tsx";
|
||||||
import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes";
|
import { useFetchSuperLikes } from "../../../hooks/useFetchSuperLikes.tsx";
|
||||||
import {
|
import {
|
||||||
FOR_SUPER_LIKE,
|
FOR_SUPER_LIKE,
|
||||||
QTUBE_VIDEO_BASE,
|
QTUBE_VIDEO_BASE,
|
||||||
SUPER_LIKE_BASE,
|
SUPER_LIKE_BASE,
|
||||||
} from "../../constants/Identifiers.ts";
|
} from "../../../constants/Identifiers.ts";
|
||||||
import {
|
import {
|
||||||
minPriceSuperlike,
|
minPriceSuperlike,
|
||||||
titleFormatterOnSave,
|
titleFormatterOnSave,
|
||||||
} from "../../constants/Misc.ts";
|
} from "../../../constants/Misc.ts";
|
||||||
import { SubscribeButton } from "../../components/common/SubscribeButton.tsx";
|
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) {
|
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
||||||
// Calculate the absolute difference in milliseconds
|
// Calculate the absolute difference in milliseconds
|
||||||
@ -116,12 +121,15 @@ export const getPaymentInfo = async (signature: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const VideoContent = () => {
|
export const VideoContent = () => {
|
||||||
const { name, id } = useParams();
|
const { name: channelName, id } = useParams();
|
||||||
|
const userName = useSelector((state: RootState) => state.auth.user?.name);
|
||||||
|
|
||||||
const [isExpandedDescription, setIsExpandedDescription] =
|
const [isExpandedDescription, setIsExpandedDescription] =
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
const [superlikeList, setSuperlikelist] = useState<any[]>([]);
|
||||||
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
const [loadingSuperLikes, setLoadingSuperLikes] = useState<boolean>(false);
|
||||||
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||||
|
const containerRef = useRef<refType>(null);
|
||||||
|
|
||||||
const calculateAmountSuperlike = useMemo(() => {
|
const calculateAmountSuperlike = useMemo(() => {
|
||||||
const totalQort = superlikeList?.reduce((acc, curr) => {
|
const totalQort = superlikeList?.reduce((acc, curr) => {
|
||||||
@ -143,6 +151,7 @@ export const VideoContent = () => {
|
|||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
);
|
);
|
||||||
|
|
||||||
const contentRef = useRef(null);
|
const contentRef = useRef(null);
|
||||||
|
|
||||||
const getAddressName = async name => {
|
const getAddressName = async name => {
|
||||||
@ -157,18 +166,18 @@ export const VideoContent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (name) {
|
if (channelName) {
|
||||||
getAddressName(name);
|
getAddressName(channelName);
|
||||||
}
|
}
|
||||||
}, [name]);
|
}, [channelName]);
|
||||||
const avatarUrl = useMemo(() => {
|
const avatarUrl = useMemo(() => {
|
||||||
let url = "";
|
let url = "";
|
||||||
if (name && userAvatarHash[name]) {
|
if (channelName && userAvatarHash[channelName]) {
|
||||||
url = userAvatarHash[name];
|
url = userAvatarHash[channelName];
|
||||||
}
|
}
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}, [userAvatarHash, name]);
|
}, [userAvatarHash, channelName]);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
@ -279,16 +288,16 @@ export const VideoContent = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (name && id) {
|
if (channelName && id) {
|
||||||
const existingVideo = hashMapVideos[id + "-" + name];
|
const existingVideo = hashMapVideos[id + "-" + channelName];
|
||||||
|
|
||||||
if (existingVideo) {
|
if (existingVideo) {
|
||||||
setVideoData(existingVideo);
|
setVideoData(existingVideo);
|
||||||
} else {
|
} else {
|
||||||
getVideoData(name, id);
|
getVideoData(channelName, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [id, name]);
|
}, [id, channelName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
@ -367,6 +376,14 @@ export const VideoContent = () => {
|
|||||||
(state: RootState) => state.persist.subscriptionList
|
(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 (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -375,6 +392,7 @@ export const VideoContent = () => {
|
|||||||
flexDirection: "column",
|
flexDirection: "column",
|
||||||
padding: "20px 10px",
|
padding: "20px 10px",
|
||||||
}}
|
}}
|
||||||
|
onClick={focusVideo}
|
||||||
>
|
>
|
||||||
<VideoPlayerContainer
|
<VideoPlayerContainer
|
||||||
sx={{
|
sx={{
|
||||||
@ -387,10 +405,11 @@ export const VideoContent = () => {
|
|||||||
name={videoReference?.name}
|
name={videoReference?.name}
|
||||||
service={videoReference?.service}
|
service={videoReference?.service}
|
||||||
identifier={videoReference?.identifier}
|
identifier={videoReference?.identifier}
|
||||||
user={name}
|
user={channelName}
|
||||||
jsonId={id}
|
jsonId={id}
|
||||||
poster={videoCover || ""}
|
poster={videoCover || ""}
|
||||||
customStyle={{ aspectRatio: "16/9" }}
|
customStyle={{ aspectRatio: "16/9" }}
|
||||||
|
ref={containerRef}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
||||||
@ -414,12 +433,12 @@ export const VideoContent = () => {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/channel/${name}`);
|
navigate(`/channel/${channelName}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Avatar
|
<Avatar
|
||||||
src={`/arbitrary/THUMBNAIL/${name}/qortal_avatar`}
|
src={`/arbitrary/THUMBNAIL/${channelName}/qortal_avatar`}
|
||||||
alt={`${name}'s avatar`}
|
alt={`${channelName}'s avatar`}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<StyledCardColComment>
|
<StyledCardColComment>
|
||||||
@ -433,14 +452,22 @@ export const VideoContent = () => {
|
|||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
}}
|
}}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate(`/channel/${name}`);
|
navigate(`/channel/${channelName}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{name}
|
{channelName}
|
||||||
|
{channelName !== userName && (
|
||||||
|
<>
|
||||||
<SubscribeButton
|
<SubscribeButton
|
||||||
subscriberName={name}
|
subscriberName={channelName}
|
||||||
sx={{ marginLeft: "20px" }}
|
sx={{ marginLeft: "20px" }}
|
||||||
/>
|
/>
|
||||||
|
<FollowButton
|
||||||
|
followerName={channelName}
|
||||||
|
sx={{ marginLeft: "20px" }}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</AuthorTextComment>
|
</AuthorTextComment>
|
||||||
</StyledCardColComment>
|
</StyledCardColComment>
|
||||||
</StyledCardHeaderComment>
|
</StyledCardHeaderComment>
|
||||||
@ -452,6 +479,11 @@ export const VideoContent = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoData && (
|
{videoData && (
|
||||||
|
<>
|
||||||
|
<LikeAndDislike
|
||||||
|
name={videoData?.user}
|
||||||
|
identifier={videoData?.id}
|
||||||
|
/>
|
||||||
<SuperLike
|
<SuperLike
|
||||||
numberOfSuperlikes={numberOfSuperlikes}
|
numberOfSuperlikes={numberOfSuperlikes}
|
||||||
totalAmount={calculateAmountSuperlike}
|
totalAmount={calculateAmountSuperlike}
|
||||||
@ -462,6 +494,7 @@ export const VideoContent = () => {
|
|||||||
setSuperlikelist(prev => [val, ...prev]);
|
setSuperlikelist(prev => [val, ...prev]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<FileAttachmentContainer>
|
<FileAttachmentContainer>
|
||||||
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
|
<FileAttachmentFont>Save to Disk</FileAttachmentFont>
|
||||||
@ -598,7 +631,7 @@ export const VideoContent = () => {
|
|||||||
loadingSuperLikes={loadingSuperLikes}
|
loadingSuperLikes={loadingSuperLikes}
|
||||||
superlikes={superlikeList}
|
superlikes={superlikeList}
|
||||||
postId={id || ""}
|
postId={id || ""}
|
||||||
postName={name || ""}
|
postName={channelName || ""}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
@ -609,7 +642,7 @@ export const VideoContent = () => {
|
|||||||
maxWidth: "1200px",
|
maxWidth: "1200px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CommentSection postId={id || ""} postName={name || ""} />
|
<CommentSection postId={id || ""} postName={channelName || ""} />
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
@ -37,12 +37,12 @@ import {
|
|||||||
import {
|
import {
|
||||||
changeFilterType,
|
changeFilterType,
|
||||||
resetSubscriptions,
|
resetSubscriptions,
|
||||||
|
VideoListType,
|
||||||
} from "../../state/features/persistSlice.ts";
|
} from "../../state/features/persistSlice.ts";
|
||||||
import { categories, subCategories } from "../../constants/Categories.ts";
|
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||||
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
import { ListSuperLikeContainer } from "../../components/common/ListSuperLikes/ListSuperLikeContainer.tsx";
|
||||||
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
import { TabContext, TabList, TabPanel } from "@mui/lab";
|
||||||
import VideoList from "./VideoList.tsx";
|
import VideoList from "./VideoList.tsx";
|
||||||
import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts";
|
|
||||||
import { setHomePageSelectedTab } from "../../state/features/persistSlice.ts";
|
import { setHomePageSelectedTab } from "../../state/features/persistSlice.ts";
|
||||||
import { StatsData } from "../../components/StatsData.tsx";
|
import { StatsData } from "../../components/StatsData.tsx";
|
||||||
|
|
||||||
@ -74,7 +74,9 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const [tabValue, setTabValue] = useState<string>(persistReducer.selectedTab);
|
const [tabValue, setTabValue] = useState<VideoListType>(
|
||||||
|
persistReducer.selectedTab
|
||||||
|
);
|
||||||
|
|
||||||
const tabFontSize = "20px";
|
const tabFontSize = "20px";
|
||||||
|
|
||||||
@ -123,14 +125,14 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
if (!firstFetch.current || !afterFetch.current) return;
|
if (!firstFetch.current || !afterFetch.current) return;
|
||||||
if (isFetching.current) return;
|
if (isFetching.current) return;
|
||||||
isFetching.current = true;
|
isFetching.current = true;
|
||||||
console.log("in getvideoshandler");
|
|
||||||
await getVideos(
|
await getVideos(
|
||||||
{
|
{
|
||||||
name: filterName,
|
name: filterName,
|
||||||
category: selectedCategoryVideos?.id,
|
category: selectedCategoryVideos?.id,
|
||||||
subcategory: selectedSubCategoryVideos?.id,
|
subcategory: selectedSubCategoryVideos?.id,
|
||||||
keywords: filterSearch,
|
keywords: filterSearch,
|
||||||
type: filterType,
|
contentType: filterType,
|
||||||
},
|
},
|
||||||
reset,
|
reset,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
@ -171,7 +173,7 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
category: "",
|
category: "",
|
||||||
subcategory: "",
|
subcategory: "",
|
||||||
keywords: "",
|
keywords: "",
|
||||||
type: filterType,
|
contentType: filterType,
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
@ -269,11 +271,10 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log("useeffect 5");
|
|
||||||
getVideosHandler(true);
|
getVideosHandler(true);
|
||||||
}, [tabValue]);
|
}, [tabValue]);
|
||||||
|
|
||||||
const changeTab = (e: React.SyntheticEvent, newValue: string) => {
|
const changeTab = (e: React.SyntheticEvent, newValue: VideoListType) => {
|
||||||
setTabValue(newValue);
|
setTabValue(newValue);
|
||||||
dispatch(setHomePageSelectedTab(newValue));
|
dispatch(setHomePageSelectedTab(newValue));
|
||||||
};
|
};
|
||||||
@ -516,23 +517,23 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
>
|
>
|
||||||
<Tab
|
<Tab
|
||||||
label="All Videos"
|
label="All Videos"
|
||||||
value={allTabValue}
|
value={"all"}
|
||||||
sx={{ fontSize: tabFontSize }}
|
sx={{ fontSize: tabFontSize }}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
label="Subscriptions"
|
label="Subscriptions"
|
||||||
value={subscriptionTabValue}
|
value={"subscriptions"}
|
||||||
sx={{ fontSize: tabFontSize }}
|
sx={{ fontSize: tabFontSize }}
|
||||||
/>
|
/>
|
||||||
</TabList>
|
</TabList>
|
||||||
<TabPanel value={allTabValue} sx={{ width: "100%" }}>
|
<TabPanel value={"all"} sx={{ width: "100%" }}>
|
||||||
<VideoList videos={videos} />
|
<VideoList videos={videos} />
|
||||||
<LazyLoad
|
<LazyLoad
|
||||||
onLoadMore={getVideosHandler}
|
onLoadMore={getVideosHandler}
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
></LazyLoad>
|
></LazyLoad>
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
<TabPanel value={subscriptionTabValue} sx={{ width: "100%" }}>
|
<TabPanel value={"subscriptions"} sx={{ width: "100%" }}>
|
||||||
{filteredSubscriptionList.length > 0 ? (
|
{filteredSubscriptionList.length > 0 ? (
|
||||||
<>
|
<>
|
||||||
<VideoList videos={videos} />
|
<VideoList videos={videos} />
|
||||||
@ -541,10 +542,12 @@ export const Home = ({ mode }: HomeProps) => {
|
|||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
></LazyLoad>
|
></LazyLoad>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : !isLoading ? (
|
||||||
<div style={{ textAlign: "center" }}>
|
<div style={{ textAlign: "center" }}>
|
||||||
You have no subscriptions
|
You have no subscriptions
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
)}
|
)}
|
||||||
</TabPanel>
|
</TabPanel>
|
||||||
</TabContext>
|
</TabContext>
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { allTabValue, subscriptionTabValue } from "../../constants/Misc.ts";
|
import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||||
import { SubscriptionData } from "../../components/common/SubscribeButton.tsx";
|
|
||||||
|
|
||||||
type StretchVideoType = "contain" | "fill" | "cover" | "none" | "scale-down";
|
|
||||||
type SubscriptionListFilterType = "ALL" | "currentNameOnly";
|
|
||||||
|
|
||||||
|
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 {
|
interface settingsState {
|
||||||
selectedTab: string;
|
selectedTab: VideoListType;
|
||||||
stretchVideoSetting: StretchVideoType;
|
stretchVideoSetting: StretchVideoType;
|
||||||
filterType: string;
|
filterType: ContentType;
|
||||||
subscriptionList: SubscriptionData[];
|
subscriptionList: SubscriptionData[];
|
||||||
playbackRate: number;
|
playbackRate: number;
|
||||||
subscriptionListFilter: SubscriptionListFilterType;
|
subscriptionListFilter: SubscriptionListFilterType;
|
||||||
@ -16,7 +21,7 @@ interface settingsState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const initialState: settingsState = {
|
const initialState: settingsState = {
|
||||||
selectedTab: allTabValue,
|
selectedTab: "all",
|
||||||
stretchVideoSetting: "contain",
|
stretchVideoSetting: "contain",
|
||||||
filterType: "videos",
|
filterType: "videos",
|
||||||
subscriptionList: [],
|
subscriptionList: [],
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
|
||||||
import { SubscriptionData } from "../../components/common/SubscribeButton";
|
import { SubscriptionData } from "../../components/common/ContentButtons/SubscribeButton.tsx";
|
||||||
|
|
||||||
interface GlobalState {
|
interface GlobalState {
|
||||||
videos: Video[];
|
videos: Video[];
|
||||||
|
@ -2,6 +2,7 @@ import {
|
|||||||
AccountInfo,
|
AccountInfo,
|
||||||
AccountName,
|
AccountName,
|
||||||
GetRequestData,
|
GetRequestData,
|
||||||
|
SearchResourcesResponse,
|
||||||
SearchTransactionResponse,
|
SearchTransactionResponse,
|
||||||
TransactionSearchParams,
|
TransactionSearchParams,
|
||||||
} from "./qortalRequestTypes.ts";
|
} from "./qortalRequestTypes.ts";
|
||||||
@ -46,3 +47,30 @@ export const searchTransactions = async (params: TransactionSearchParams) => {
|
|||||||
...params,
|
...params,
|
||||||
})) as SearchTransactionResponse[];
|
})) 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[];
|
||||||
|
};
|
||||||
|
@ -23,6 +23,23 @@ export interface SearchTransactionResponse {
|
|||||||
amount: string;
|
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 =
|
export type TransactionType =
|
||||||
| "GENESIS"
|
| "GENESIS"
|
||||||
| "PAYMENT"
|
| "PAYMENT"
|
||||||
|
@ -18,14 +18,14 @@ import {
|
|||||||
import { VideoPlayerGlobal } from "../components/common/VideoPlayer/VideoPlayerGlobal.tsx";
|
import { VideoPlayerGlobal } from "../components/common/VideoPlayer/VideoPlayerGlobal.tsx";
|
||||||
import { Rnd } from "react-rnd";
|
import { Rnd } from "react-rnd";
|
||||||
import { RequestQueue } from "../utils/queue";
|
import { RequestQueue } from "../utils/queue";
|
||||||
import { EditVideo } from "../components/EditVideo/EditVideo";
|
import { EditVideo } from "../components/Publish/EditVideo/EditVideo";
|
||||||
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
|
import { EditPlaylist } from "../components/Publish/EditPlaylist/EditPlaylist";
|
||||||
import ConsentModal from "../components/common/ConsentModal";
|
import ConsentModal from "../components/common/ConsentModal";
|
||||||
import {
|
import {
|
||||||
extractSigValue,
|
extractSigValue,
|
||||||
getPaymentInfo,
|
getPaymentInfo,
|
||||||
isTimestampWithinRange,
|
isTimestampWithinRange,
|
||||||
} from "../pages/VideoContent/VideoContent";
|
} from "../pages/ContentPages/VideoContent/VideoContent";
|
||||||
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
|
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
|
||||||
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
|
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
|
||||||
import { minPriceSuperlike } from "../constants/Misc.ts";
|
import { minPriceSuperlike } from "../constants/Misc.ts";
|
||||||
@ -143,8 +143,8 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
|||||||
|
|
||||||
const getSuperlikes = useCallback(async () => {
|
const getSuperlikes = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
let totalCount = 0
|
let totalCount = 0;
|
||||||
let validCount = 0
|
let validCount = 0;
|
||||||
let comments: any[] = [];
|
let comments: any[] = [];
|
||||||
while (validCount < 20 && totalCount < 100) {
|
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`;
|
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…
x
Reference in New Issue
Block a user