mirror of
https://github.com/Qortal/q-tube.git
synced 2025-02-11 17:55:51 +00:00
Superlike Dialog allows optional donation to DevFund.
Refactored constants/index.ts into Identifiers.ts, Categories.ts, and Misc.ts. Regular expressions that titles allow all use new variable in Misc.ts for consistency and ease of editing it. New Characters are allowed in titles. Categories sorted by name, "Other" is always at end of list. New Categories such as Qortal under Education have been added Title prefix TextField added that starts all video titles with the entered value.
This commit is contained in:
parent
1806ec3aa0
commit
8d3549739c
10
.prettierrc
Normal file
10
.prettierrc
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 80,
|
||||||
|
"singleQuote": false,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"jsxBracketSameLine": false,
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true
|
||||||
|
}
|
@ -43,11 +43,15 @@ import {
|
|||||||
setEditPlaylist,
|
setEditPlaylist,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../state/features/videoSlice";
|
||||||
import ImageUploader from "../common/ImageUploader";
|
import ImageUploader from "../common/ImageUploader";
|
||||||
import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE, categories, subCategories } from "../../constants";
|
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||||
import { Playlists } from "../Playlists/Playlists";
|
import { Playlists } from "../Playlists/Playlists";
|
||||||
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
|
import { PlaylistListEdit } from "../PlaylistListEdit/PlaylistListEdit";
|
||||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||||
|
import {
|
||||||
|
QTUBE_PLAYLIST_BASE,
|
||||||
|
QTUBE_VIDEO_BASE,
|
||||||
|
} from "../../constants/Identifiers.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
||||||
@ -87,17 +91,17 @@ export const EditPlaylist = () => {
|
|||||||
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
||||||
useState<any>(null);
|
useState<any>(null);
|
||||||
|
|
||||||
const isNew = useMemo(()=> {
|
const isNew = useMemo(() => {
|
||||||
return editVideoProperties?.mode === 'new'
|
return editVideoProperties?.mode === "new";
|
||||||
}, [editVideoProperties])
|
}, [editVideoProperties]);
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
if(isNew){
|
if (isNew) {
|
||||||
setPlaylistData({
|
setPlaylistData({
|
||||||
videos: []
|
videos: [],
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}, [isNew])
|
}, [isNew]);
|
||||||
|
|
||||||
// useEffect(() => {
|
// useEffect(() => {
|
||||||
// if (editVideoProperties) {
|
// if (editVideoProperties) {
|
||||||
@ -145,7 +149,7 @@ export const EditPlaylist = () => {
|
|||||||
// }
|
// }
|
||||||
// }, [editVideoProperties]);
|
// }, [editVideoProperties]);
|
||||||
|
|
||||||
const checkforPlaylist = React.useCallback(async (videoList) => {
|
const checkforPlaylist = React.useCallback(async videoList => {
|
||||||
try {
|
try {
|
||||||
const combinedData: any = {};
|
const combinedData: any = {};
|
||||||
const videos = [];
|
const videos = [];
|
||||||
@ -174,21 +178,19 @@ export const EditPlaylist = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editVideoProperties) {
|
if (editVideoProperties) {
|
||||||
setTitle(editVideoProperties?.title || "");
|
setTitle(editVideoProperties?.title || "");
|
||||||
|
|
||||||
if(editVideoProperties?.htmlDescription){
|
if (editVideoProperties?.htmlDescription) {
|
||||||
setDescription(editVideoProperties?.htmlDescription);
|
setDescription(editVideoProperties?.htmlDescription);
|
||||||
|
} else if (editVideoProperties?.description) {
|
||||||
} else if(editVideoProperties?.description) {
|
const paragraph = `<p>${editVideoProperties?.description}</p>`;
|
||||||
const paragraph = `<p>${editVideoProperties?.description}</p>`
|
|
||||||
setDescription(paragraph);
|
setDescription(paragraph);
|
||||||
|
|
||||||
}
|
}
|
||||||
setCoverImage(editVideoProperties?.image || "");
|
setCoverImage(editVideoProperties?.image || "");
|
||||||
setVideos(editVideoProperties?.videos || []);
|
setVideos(editVideoProperties?.videos || []);
|
||||||
|
|
||||||
if (editVideoProperties?.category) {
|
if (editVideoProperties?.category) {
|
||||||
const selectedOption = categories.find(
|
const selectedOption = categories.find(
|
||||||
(option) => option.id === +editVideoProperties.category
|
option => option.id === +editVideoProperties.category
|
||||||
);
|
);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
}
|
}
|
||||||
@ -200,7 +202,7 @@ export const EditPlaylist = () => {
|
|||||||
) {
|
) {
|
||||||
const selectedOption = subCategories[
|
const selectedOption = subCategories[
|
||||||
+editVideoProperties?.category
|
+editVideoProperties?.category
|
||||||
]?.find((option) => option.id === +editVideoProperties.subcategory);
|
]?.find(option => option.id === +editVideoProperties.subcategory);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,24 +213,22 @@ export const EditPlaylist = () => {
|
|||||||
}, [editVideoProperties]);
|
}, [editVideoProperties]);
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = () => {
|
||||||
setTitle("")
|
setTitle("");
|
||||||
setDescription("")
|
setDescription("");
|
||||||
setVideos([])
|
setVideos([]);
|
||||||
setPlaylistData(null)
|
setPlaylistData(null);
|
||||||
setSelectedCategoryVideos(null)
|
setSelectedCategoryVideos(null);
|
||||||
setSelectedSubCategoryVideos(null)
|
setSelectedSubCategoryVideos(null);
|
||||||
setCoverImage("")
|
setCoverImage("");
|
||||||
dispatch(setEditPlaylist(null));
|
dispatch(setEditPlaylist(null));
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function publishQDNResource() {
|
async function publishQDNResource() {
|
||||||
try {
|
try {
|
||||||
|
if (!title) throw new Error("Please enter a title");
|
||||||
if(!title) throw new Error('Please enter a title')
|
if (!description) throw new Error("Please enter a description");
|
||||||
if(!description) throw new Error('Please enter a description')
|
if (!coverImage) throw new Error("Please select cover image");
|
||||||
if(!coverImage) throw new Error('Please select cover image')
|
if (!selectedCategoryVideos) throw new Error("Please select a category");
|
||||||
if(!selectedCategoryVideos) throw new Error('Please select a category')
|
|
||||||
|
|
||||||
if (!editVideoProperties) return;
|
if (!editVideoProperties) return;
|
||||||
if (!userAddress) throw new Error("Unable to locate user address");
|
if (!userAddress) throw new Error("Unable to locate user address");
|
||||||
@ -258,7 +258,7 @@ export const EditPlaylist = () => {
|
|||||||
const category = selectedCategoryVideos.id;
|
const category = selectedCategoryVideos.id;
|
||||||
const subcategory = selectedSubCategoryVideos?.id || "";
|
const subcategory = selectedSubCategoryVideos?.id || "";
|
||||||
|
|
||||||
const videoStructured = playlistData.videos.map((item) => {
|
const videoStructured = playlistData.videos.map(item => {
|
||||||
const descriptionVid = item?.metadata?.description;
|
const descriptionVid = item?.metadata?.description;
|
||||||
if (!descriptionVid) throw new Error("cannot find video code");
|
if (!descriptionVid) throw new Error("cannot find video code");
|
||||||
|
|
||||||
@ -286,13 +286,12 @@ export const EditPlaylist = () => {
|
|||||||
});
|
});
|
||||||
const id = uid();
|
const id = uid();
|
||||||
|
|
||||||
let commentsId = editVideoProperties?.id
|
let commentsId = editVideoProperties?.id;
|
||||||
|
|
||||||
if(isNew){
|
|
||||||
commentsId = `${QTUBE_PLAYLIST_BASE}_cm_${id}`
|
|
||||||
}
|
|
||||||
const stringDescription = extractTextFromHTML(description)
|
|
||||||
|
|
||||||
|
if (isNew) {
|
||||||
|
commentsId = `${QTUBE_PLAYLIST_BASE}_cm_${id}`;
|
||||||
|
}
|
||||||
|
const stringDescription = extractTextFromHTML(description);
|
||||||
|
|
||||||
const playlistObject: any = {
|
const playlistObject: any = {
|
||||||
title,
|
title,
|
||||||
@ -303,10 +302,13 @@ export const EditPlaylist = () => {
|
|||||||
videos: videoStructured,
|
videos: videoStructured,
|
||||||
commentsId: commentsId,
|
commentsId: commentsId,
|
||||||
category,
|
category,
|
||||||
subcategory
|
subcategory,
|
||||||
};
|
};
|
||||||
|
|
||||||
const codes = videoStructured.map((item) => `c:${item.code};`).slice(0,10).join("");
|
const codes = videoStructured
|
||||||
|
.map(item => `c:${item.code};`)
|
||||||
|
.slice(0, 10)
|
||||||
|
.join("");
|
||||||
let metadescription =
|
let metadescription =
|
||||||
`**category:${category};subcategory:${subcategory};${codes}**` +
|
`**category:${category};subcategory:${subcategory};${codes}**` +
|
||||||
stringDescription.slice(0, 120);
|
stringDescription.slice(0, 120);
|
||||||
@ -314,15 +316,18 @@ export const EditPlaylist = () => {
|
|||||||
const crowdfundObjectToBase64 = await objectToBase64(playlistObject);
|
const crowdfundObjectToBase64 = await objectToBase64(playlistObject);
|
||||||
// Description is obtained from raw data
|
// Description is obtained from raw data
|
||||||
|
|
||||||
let identifier = editVideoProperties?.id
|
let identifier = editVideoProperties?.id;
|
||||||
const sanitizeTitle = title
|
const sanitizeTitle = title
|
||||||
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
||||||
.replace(/\s+/g, "-")
|
.replace(/\s+/g, "-")
|
||||||
.replace(/-+/g, "-")
|
.replace(/-+/g, "-")
|
||||||
.trim()
|
.trim()
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
if(isNew){
|
if (isNew) {
|
||||||
identifier = `${QTUBE_PLAYLIST_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
|
identifier = `${QTUBE_PLAYLIST_BASE}${sanitizeTitle.slice(
|
||||||
|
0,
|
||||||
|
30
|
||||||
|
)}_${id}`;
|
||||||
}
|
}
|
||||||
const requestBodyJson: any = {
|
const requestBodyJson: any = {
|
||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
@ -336,21 +341,17 @@ export const EditPlaylist = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
await qortalRequest(requestBodyJson);
|
await qortalRequest(requestBodyJson);
|
||||||
if(isNew){
|
if (isNew) {
|
||||||
const objectToStore = {
|
const objectToStore = {
|
||||||
title: title.slice(0, 50),
|
title: title.slice(0, 50),
|
||||||
description: metadescription,
|
description: metadescription,
|
||||||
id: identifier,
|
id: identifier,
|
||||||
service: "PLAYLIST",
|
service: "PLAYLIST",
|
||||||
user: username,
|
user: username,
|
||||||
...playlistObject
|
...playlistObject,
|
||||||
}
|
};
|
||||||
dispatch(
|
dispatch(updateVideo(objectToStore));
|
||||||
updateVideo(objectToStore)
|
dispatch(updateInHashMap(objectToStore));
|
||||||
);
|
|
||||||
dispatch(
|
|
||||||
updateInHashMap(objectToStore)
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
dispatch(
|
dispatch(
|
||||||
updateVideo({
|
updateVideo({
|
||||||
@ -365,7 +366,7 @@ export const EditPlaylist = () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
dispatch(
|
dispatch(
|
||||||
setNotification({
|
setNotification({
|
||||||
msg: "Playlist published",
|
msg: "Playlist published",
|
||||||
@ -399,13 +400,11 @@ export const EditPlaylist = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleOptionCategoryChangeVideos = (
|
const handleOptionCategoryChangeVideos = (
|
||||||
event: SelectChangeEvent<string>
|
event: SelectChangeEvent<string>
|
||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
const selectedOption = categories.find(option => option.id === +optionId);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
const handleOptionSubCategoryChangeVideos = (
|
const handleOptionSubCategoryChangeVideos = (
|
||||||
@ -414,19 +413,18 @@ export const EditPlaylist = () => {
|
|||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = subcategories.find(
|
const selectedOption = subcategories.find(
|
||||||
(option) => option.id === +optionId
|
option => option.id === +optionId
|
||||||
);
|
);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const removeVideo = index => {
|
||||||
const removeVideo = (index) => {
|
|
||||||
const copyData = structuredClone(playlistData);
|
const copyData = structuredClone(playlistData);
|
||||||
copyData.videos.splice(index, 1);
|
copyData.videos.splice(index, 1);
|
||||||
setPlaylistData(copyData);
|
setPlaylistData(copyData);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addVideo = (data) => {
|
const addVideo = data => {
|
||||||
const copyData = structuredClone(playlistData);
|
const copyData = structuredClone(playlistData);
|
||||||
copyData.videos = [...copyData.videos, { ...data }];
|
copyData.videos = [...copyData.videos, { ...data }];
|
||||||
setPlaylistData(copyData);
|
setPlaylistData(copyData);
|
||||||
@ -449,10 +447,8 @@ export const EditPlaylist = () => {
|
|||||||
>
|
>
|
||||||
{isNew ? (
|
{isNew ? (
|
||||||
<NewCrowdfundTitle>Create new playlist</NewCrowdfundTitle>
|
<NewCrowdfundTitle>Create new playlist</NewCrowdfundTitle>
|
||||||
|
|
||||||
) : (
|
) : (
|
||||||
<NewCrowdfundTitle>Update Playlist properties</NewCrowdfundTitle>
|
<NewCrowdfundTitle>Update Playlist properties</NewCrowdfundTitle>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
<>
|
<>
|
||||||
@ -471,7 +467,7 @@ export const EditPlaylist = () => {
|
|||||||
value={selectedCategoryVideos?.id || ""}
|
value={selectedCategoryVideos?.id || ""}
|
||||||
onChange={handleOptionCategoryChangeVideos}
|
onChange={handleOptionCategoryChangeVideos}
|
||||||
>
|
>
|
||||||
{categories.map((option) => (
|
{categories.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -486,20 +482,18 @@ export const EditPlaylist = () => {
|
|||||||
labelId="Sub-Category"
|
labelId="Sub-Category"
|
||||||
input={<OutlinedInput label="Select a Sub-Category" />}
|
input={<OutlinedInput label="Select a Sub-Category" />}
|
||||||
value={selectedSubCategoryVideos?.id || ""}
|
value={selectedSubCategoryVideos?.id || ""}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
handleOptionSubCategoryChangeVideos(
|
handleOptionSubCategoryChangeVideos(
|
||||||
e,
|
e,
|
||||||
subCategories[selectedCategoryVideos?.id]
|
subCategories[selectedCategoryVideos?.id]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{subCategories[selectedCategoryVideos.id].map(
|
{subCategories[selectedCategoryVideos.id].map(option => (
|
||||||
(option) => (
|
<MenuItem key={option.id} value={option.id}>
|
||||||
<MenuItem key={option.id} value={option.id}>
|
{option.name}
|
||||||
{option.name}
|
</MenuItem>
|
||||||
</MenuItem>
|
))}
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
@ -533,9 +527,12 @@ export const EditPlaylist = () => {
|
|||||||
label="Title of playlist"
|
label="Title of playlist"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
const formattedValue = value.replace(/[^a-zA-Z0-9\s-_!?]/g, "");
|
const formattedValue = value.replace(
|
||||||
|
/[^a-zA-Z0-9\s-_!?]/g,
|
||||||
|
""
|
||||||
|
);
|
||||||
setTitle(formattedValue);
|
setTitle(formattedValue);
|
||||||
}}
|
}}
|
||||||
inputProps={{ maxLength: 180 }}
|
inputProps={{ maxLength: 180 }}
|
||||||
@ -552,12 +549,19 @@ export const EditPlaylist = () => {
|
|||||||
maxRows={3}
|
maxRows={3}
|
||||||
required
|
required
|
||||||
/> */}
|
/> */}
|
||||||
<Typography sx={{
|
<Typography
|
||||||
fontSize: '18px'
|
sx={{
|
||||||
}}>Description of playlist</Typography>
|
fontSize: "18px",
|
||||||
<TextEditor inlineContent={description} setInlineContent={(value)=> {
|
}}
|
||||||
setDescription(value)
|
>
|
||||||
}} />
|
Description of playlist
|
||||||
|
</Typography>
|
||||||
|
<TextEditor
|
||||||
|
inlineContent={description}
|
||||||
|
setInlineContent={value => {
|
||||||
|
setDescription(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
|
|
||||||
<PlaylistListEdit
|
<PlaylistListEdit
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Compressor from 'compressorjs'
|
import Compressor from "compressorjs";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AddCoverImageButton,
|
AddCoverImageButton,
|
||||||
@ -14,7 +14,7 @@ import {
|
|||||||
NewCrowdfundTitle,
|
NewCrowdfundTitle,
|
||||||
StyledButton,
|
StyledButton,
|
||||||
TimesIcon,
|
TimesIcon,
|
||||||
} from "./Upload-styles";
|
} from "./EditVideo-styles.tsx";
|
||||||
import { CircularProgress } from "@mui/material";
|
import { CircularProgress } from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -46,12 +46,14 @@ import {
|
|||||||
updateInHashMap,
|
updateInHashMap,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../state/features/videoSlice";
|
||||||
import ImageUploader from "../common/ImageUploader";
|
import ImageUploader from "../common/ImageUploader";
|
||||||
import { QTUBE_VIDEO_BASE, categories, subCategories } from "../../constants";
|
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublish";
|
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublish";
|
||||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||||
import { toBase64 } from "../UploadVideo/UploadVideo";
|
import { toBase64 } from "../PublishVideo/PublishVideo.tsx";
|
||||||
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
||||||
|
import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.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 });
|
||||||
@ -94,8 +96,7 @@ export const EditVideo = () => {
|
|||||||
useState<any>(null);
|
useState<any>(null);
|
||||||
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
const [selectedSubCategoryVideos, setSelectedSubCategoryVideos] =
|
||||||
useState<any>(null);
|
useState<any>(null);
|
||||||
const [imageExtracts, setImageExtracts] = useState<any>([])
|
const [imageExtracts, setImageExtracts] = useState<any>([]);
|
||||||
|
|
||||||
|
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
@ -111,7 +112,7 @@ export const EditVideo = () => {
|
|||||||
let errorString = null;
|
let errorString = null;
|
||||||
|
|
||||||
rejectedFiles.forEach(({ file, errors }) => {
|
rejectedFiles.forEach(({ file, errors }) => {
|
||||||
errors.forEach((error) => {
|
errors.forEach(error => {
|
||||||
if (error.code === "file-too-large") {
|
if (error.code === "file-too-large") {
|
||||||
errorString = "File must be under 400mb";
|
errorString = "File must be under 400mb";
|
||||||
}
|
}
|
||||||
@ -178,19 +179,17 @@ export const EditVideo = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editVideoProperties) {
|
if (editVideoProperties) {
|
||||||
setTitle(editVideoProperties?.title || "");
|
setTitle(editVideoProperties?.title || "");
|
||||||
if(editVideoProperties?.htmlDescription){
|
if (editVideoProperties?.htmlDescription) {
|
||||||
setDescription(editVideoProperties?.htmlDescription);
|
setDescription(editVideoProperties?.htmlDescription);
|
||||||
|
} else if (editVideoProperties?.fullDescription) {
|
||||||
} else if(editVideoProperties?.fullDescription) {
|
const paragraph = `<p>${editVideoProperties?.fullDescription}</p>`;
|
||||||
const paragraph = `<p>${editVideoProperties?.fullDescription}</p>`
|
|
||||||
setDescription(paragraph);
|
setDescription(paragraph);
|
||||||
|
|
||||||
}
|
}
|
||||||
setCoverImage(editVideoProperties?.videoImage || "");
|
setCoverImage(editVideoProperties?.videoImage || "");
|
||||||
|
|
||||||
if (editVideoProperties?.category) {
|
if (editVideoProperties?.category) {
|
||||||
const selectedOption = categories.find(
|
const selectedOption = categories.find(
|
||||||
(option) => option.id === +editVideoProperties.category
|
option => option.id === +editVideoProperties.category
|
||||||
);
|
);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
}
|
}
|
||||||
@ -202,7 +201,7 @@ export const EditVideo = () => {
|
|||||||
) {
|
) {
|
||||||
const selectedOption = subCategories[
|
const selectedOption = subCategories[
|
||||||
+editVideoProperties?.category
|
+editVideoProperties?.category
|
||||||
]?.find((option) => option.id === +editVideoProperties.subcategory);
|
]?.find(option => option.id === +editVideoProperties.subcategory);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,7 +212,7 @@ export const EditVideo = () => {
|
|||||||
setVideoPropertiesToSetToRedux(null);
|
setVideoPropertiesToSetToRedux(null);
|
||||||
setFile(null);
|
setFile(null);
|
||||||
setTitle("");
|
setTitle("");
|
||||||
setImageExtracts([])
|
setImageExtracts([]);
|
||||||
setDescription("");
|
setDescription("");
|
||||||
setCoverImage("");
|
setCoverImage("");
|
||||||
};
|
};
|
||||||
@ -253,7 +252,7 @@ export const EditVideo = () => {
|
|||||||
const category = selectedCategoryVideos.id;
|
const category = selectedCategoryVideos.id;
|
||||||
const subcategory = selectedSubCategoryVideos?.id || "";
|
const subcategory = selectedSubCategoryVideos?.id || "";
|
||||||
|
|
||||||
const fullDescription = extractTextFromHTML(description)
|
const fullDescription = extractTextFromHTML(description);
|
||||||
let fileExtension = "mp4";
|
let fileExtension = "mp4";
|
||||||
const fileExtensionSplit = file?.name?.split(".");
|
const fileExtensionSplit = file?.name?.split(".");
|
||||||
if (fileExtensionSplit?.length > 1) {
|
if (fileExtensionSplit?.length > 1) {
|
||||||
@ -285,15 +284,13 @@ export const EditVideo = () => {
|
|||||||
subcategory,
|
subcategory,
|
||||||
code: editVideoProperties.code,
|
code: editVideoProperties.code,
|
||||||
videoType: file?.type || "video/mp4",
|
videoType: file?.type || "video/mp4",
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
let metadescription =
|
let metadescription =
|
||||||
`**category:${category};subcategory:${subcategory};code:${editVideoProperties.code}**` +
|
`**category:${category};subcategory:${subcategory};code:${editVideoProperties.code}**` +
|
||||||
description.slice(0, 150);
|
description.slice(0, 150);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const crowdfundObjectToBase64 = await objectToBase64(videoObject);
|
const crowdfundObjectToBase64 = await objectToBase64(videoObject);
|
||||||
// Description is obtained from raw data
|
// Description is obtained from raw data
|
||||||
const requestBodyJson: any = {
|
const requestBodyJson: any = {
|
||||||
@ -319,7 +316,7 @@ export const EditVideo = () => {
|
|||||||
description: metadescription,
|
description: metadescription,
|
||||||
identifier: editVideoProperties.videoReference?.identifier,
|
identifier: editVideoProperties.videoReference?.identifier,
|
||||||
tag1: QTUBE_VIDEO_BASE,
|
tag1: QTUBE_VIDEO_BASE,
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
listOfPublishes.push(requestBodyVideo);
|
listOfPublishes.push(requestBodyVideo);
|
||||||
@ -356,13 +353,11 @@ export const EditVideo = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const handleOptionCategoryChangeVideos = (
|
const handleOptionCategoryChangeVideos = (
|
||||||
event: SelectChangeEvent<string>
|
event: SelectChangeEvent<string>
|
||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
const selectedOption = categories.find(option => option.id === +optionId);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
const handleOptionSubCategoryChangeVideos = (
|
const handleOptionSubCategoryChangeVideos = (
|
||||||
@ -371,48 +366,45 @@ export const EditVideo = () => {
|
|||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = subcategories.find(
|
const selectedOption = subcategories.find(
|
||||||
(option) => option.id === +optionId
|
option => option.id === +optionId
|
||||||
);
|
);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFramesExtracted = async (imgs)=> {
|
const onFramesExtracted = async imgs => {
|
||||||
try {
|
try {
|
||||||
let imagesExtracts = []
|
let imagesExtracts = [];
|
||||||
|
|
||||||
for (const img of imgs){
|
for (const img of imgs) {
|
||||||
try {
|
try {
|
||||||
let compressedFile
|
let compressedFile;
|
||||||
const image = img
|
const image = img;
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>(resolve => {
|
||||||
new Compressor(image, {
|
new Compressor(image, {
|
||||||
quality: .8,
|
quality: 0.8,
|
||||||
maxWidth: 750,
|
maxWidth: 750,
|
||||||
mimeType: 'image/webp',
|
mimeType: "image/webp",
|
||||||
success(result) {
|
success(result) {
|
||||||
const file = new File([result], 'name', {
|
const file = new File([result], "name", {
|
||||||
type: 'image/webp'
|
type: "image/webp",
|
||||||
})
|
});
|
||||||
compressedFile = file
|
compressedFile = file;
|
||||||
resolve()
|
resolve();
|
||||||
},
|
},
|
||||||
error(err) {}
|
error(err) {},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
if (!compressedFile) continue
|
if (!compressedFile) continue;
|
||||||
const base64Img = await toBase64(compressedFile)
|
const base64Img = await toBase64(compressedFile);
|
||||||
imagesExtracts.push(base64Img)
|
imagesExtracts.push(base64Img);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setImageExtracts(imagesExtracts)
|
setImageExtracts(imagesExtracts);
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -467,7 +459,7 @@ export const EditVideo = () => {
|
|||||||
value={selectedCategoryVideos?.id || ""}
|
value={selectedCategoryVideos?.id || ""}
|
||||||
onChange={handleOptionCategoryChangeVideos}
|
onChange={handleOptionCategoryChangeVideos}
|
||||||
>
|
>
|
||||||
{categories.map((option) => (
|
{categories.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -482,26 +474,27 @@ export const EditVideo = () => {
|
|||||||
labelId="Sub-Category"
|
labelId="Sub-Category"
|
||||||
input={<OutlinedInput label="Select a Sub-Category" />}
|
input={<OutlinedInput label="Select a Sub-Category" />}
|
||||||
value={selectedSubCategoryVideos?.id || ""}
|
value={selectedSubCategoryVideos?.id || ""}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
handleOptionSubCategoryChangeVideos(
|
handleOptionSubCategoryChangeVideos(
|
||||||
e,
|
e,
|
||||||
subCategories[selectedCategoryVideos?.id]
|
subCategories[selectedCategoryVideos?.id]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{subCategories[selectedCategoryVideos.id].map(
|
{subCategories[selectedCategoryVideos.id].map(option => (
|
||||||
(option) => (
|
<MenuItem key={option.id} value={option.id}>
|
||||||
<MenuItem key={option.id} value={option.id}>
|
{option.name}
|
||||||
{option.name}
|
</MenuItem>
|
||||||
</MenuItem>
|
))}
|
||||||
)
|
|
||||||
)}
|
|
||||||
</Select>
|
</Select>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{file && (
|
{file && (
|
||||||
<FrameExtractor videoFile={file} onFramesExtracted={(imgs)=> onFramesExtracted(imgs)}/>
|
<FrameExtractor
|
||||||
|
videoFile={file}
|
||||||
|
onFramesExtracted={imgs => onFramesExtracted(imgs)}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{!coverImage ? (
|
{!coverImage ? (
|
||||||
@ -532,23 +525,27 @@ export const EditVideo = () => {
|
|||||||
label="Title of video"
|
label="Title of video"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={title}
|
value={title}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
const formattedValue = value.replace(
|
const formattedValue = value.replace(titleFormatter, "");
|
||||||
/[^a-zA-Z0-9\s-_!?]/g,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
setTitle(formattedValue);
|
setTitle(formattedValue);
|
||||||
}}
|
}}
|
||||||
inputProps={{ maxLength: 180 }}
|
inputProps={{ maxLength: 180 }}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
fontSize: '18px'
|
sx={{
|
||||||
}}>Description of video</Typography>
|
fontSize: "18px",
|
||||||
<TextEditor inlineContent={description} setInlineContent={(value)=> {
|
}}
|
||||||
setDescription(value)
|
>
|
||||||
}} />
|
Description of video
|
||||||
|
</Typography>
|
||||||
|
<TextEditor
|
||||||
|
inlineContent={description}
|
||||||
|
setInlineContent={value => {
|
||||||
|
setDescription(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
{/* <CustomInputField
|
{/* <CustomInputField
|
||||||
name="description"
|
name="description"
|
||||||
label="Describe your video in a few words"
|
label="Describe your video in a few words"
|
||||||
@ -588,8 +585,8 @@ export const EditVideo = () => {
|
|||||||
disabled={file && imageExtracts.length === 0}
|
disabled={file && imageExtracts.length === 0}
|
||||||
>
|
>
|
||||||
{file && imageExtracts.length === 0 && (
|
{file && imageExtracts.length === 0 && (
|
||||||
<CircularProgress color="secondary" size={14} />
|
<CircularProgress color="secondary" size={14} />
|
||||||
)}
|
)}
|
||||||
Publish
|
Publish
|
||||||
</CrowdfundActionButton>
|
</CrowdfundActionButton>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -3,208 +3,208 @@ import { CardContentContainerComment } from "../common/Comments/Comments-styles"
|
|||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../UploadVideo/Upload-styles";
|
} from "../PublishVideo/PublishVideo-styles.tsx";
|
||||||
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";
|
||||||
import AddIcon from '@mui/icons-material/Add';
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
import { QTUBE_VIDEO_BASE } from "../../constants";
|
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { RootState } from "../../state/store";
|
import { RootState } from "../../state/store";
|
||||||
|
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();
|
||||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||||
|
|
||||||
const [searchResults, setSearchResults] = useState([])
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
const [filterSearch, setFilterSearch] = useState("")
|
const [filterSearch, setFilterSearch] = useState("");
|
||||||
const search = async()=> {
|
const search = async () => {
|
||||||
|
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&mode=ALL&identifier=${QTUBE_VIDEO_BASE}&title=${filterSearch}&limit=20&includemetadata=true&reverse=true&name=${username}&exactmatchnames=true&offset=0`;
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&mode=ALL&identifier=${QTUBE_VIDEO_BASE}&title=${filterSearch}&limit=20&includemetadata=true&reverse=true&name=${username}&exactmatchnames=true&offset=0`
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
const responseDataSearchVid = await response.json()
|
const responseDataSearchVid = await response.json();
|
||||||
setSearchResults(responseDataSearchVid)
|
setSearchResults(responseDataSearchVid);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
gap: '10px',
|
|
||||||
width: '100%',
|
|
||||||
justifyContent: 'center'
|
|
||||||
}}>
|
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
|
|
||||||
maxWidth: "300px",
|
|
||||||
width: "100%",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<CrowdfundSubTitleRow>
|
|
||||||
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
|
||||||
</CrowdfundSubTitleRow>
|
|
||||||
<CardContentContainerComment
|
|
||||||
sx={{
|
|
||||||
marginTop: "25px",
|
|
||||||
height: "450px",
|
|
||||||
overflow: 'auto'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{playlistData?.videos?.map((vid, index) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={vid?.identifier}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "10px",
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "10px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
userSelect: "none",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "14px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{index + 1}
|
|
||||||
</Typography>
|
|
||||||
<Typography
|
|
||||||
sx={{
|
|
||||||
fontSize: "18px",
|
|
||||||
wordBreak: 'break-word'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{vid?.metadata?.title}
|
|
||||||
</Typography>
|
|
||||||
<DeleteOutlineIcon
|
|
||||||
onClick={() => {
|
|
||||||
removeVideo(index);
|
|
||||||
}}
|
|
||||||
sx={{
|
|
||||||
cursor: "pointer",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CardContentContainerComment>
|
|
||||||
</Box>
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "column",
|
gap: "10px",
|
||||||
|
|
||||||
maxWidth: "300px",
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
justifyContent: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box
|
||||||
<CrowdfundSubTitleRow>
|
|
||||||
<CrowdfundSubTitle>Add videos to playlist</CrowdfundSubTitle>
|
|
||||||
</CrowdfundSubTitleRow>
|
|
||||||
<CardContentContainerComment
|
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: "25px",
|
display: "flex",
|
||||||
height: "450px",
|
flexDirection: "column",
|
||||||
overflow: 'auto'
|
|
||||||
|
maxWidth: "300px",
|
||||||
|
width: "100%",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Box sx={{
|
<CrowdfundSubTitleRow>
|
||||||
display: 'flex',
|
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
||||||
gap: '10px'
|
</CrowdfundSubTitleRow>
|
||||||
}}>
|
<CardContentContainerComment
|
||||||
<Input
|
sx={{
|
||||||
id="standard-adornment-name"
|
marginTop: "25px",
|
||||||
onChange={(e) => {
|
height: "450px",
|
||||||
setFilterSearch(e.target.value);
|
overflow: "auto",
|
||||||
}}
|
}}
|
||||||
value={filterSearch}
|
>
|
||||||
placeholder="Search by title"
|
{playlistData?.videos?.map((vid, index) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={vid?.identifier}
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "14px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{index + 1}
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
wordBreak: "break-word",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{vid?.metadata?.title}
|
||||||
|
</Typography>
|
||||||
|
<DeleteOutlineIcon
|
||||||
|
onClick={() => {
|
||||||
|
removeVideo(index);
|
||||||
|
}}
|
||||||
|
sx={{
|
||||||
|
cursor: "pointer",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContentContainerComment>
|
||||||
|
</Box>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
|
||||||
|
maxWidth: "300px",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<CrowdfundSubTitleRow>
|
||||||
|
<CrowdfundSubTitle>Add videos to playlist</CrowdfundSubTitle>
|
||||||
|
</CrowdfundSubTitleRow>
|
||||||
|
<CardContentContainerComment
|
||||||
|
sx={{
|
||||||
|
marginTop: "25px",
|
||||||
|
height: "450px",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
borderBottom: "1px solid white",
|
display: "flex",
|
||||||
"&&:before": {
|
gap: "10px",
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&:after": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&:hover:before": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&.Mui-focused:before": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
"&&.Mui-focused": {
|
|
||||||
outline: "none",
|
|
||||||
},
|
|
||||||
fontSize: "18px",
|
|
||||||
}}
|
}}
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
onClick={() => {
|
|
||||||
search();
|
|
||||||
}}
|
|
||||||
|
|
||||||
variant="contained"
|
|
||||||
>
|
>
|
||||||
Search
|
<Input
|
||||||
</Button>
|
id="standard-adornment-name"
|
||||||
</Box>
|
onChange={e => {
|
||||||
|
setFilterSearch(e.target.value);
|
||||||
{searchResults?.map((vid, index) => {
|
|
||||||
return (
|
|
||||||
<Box
|
|
||||||
key={vid?.identifier}
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
gap: "10px",
|
|
||||||
width: "100%",
|
|
||||||
alignItems: "center",
|
|
||||||
padding: "10px",
|
|
||||||
borderRadius: "5px",
|
|
||||||
userSelect: "none",
|
|
||||||
}}
|
}}
|
||||||
|
value={filterSearch}
|
||||||
|
placeholder="Search by title"
|
||||||
|
sx={{
|
||||||
|
borderBottom: "1px solid white",
|
||||||
|
"&&:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&:after": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&:hover:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&.Mui-focused:before": {
|
||||||
|
borderBottom: "none",
|
||||||
|
},
|
||||||
|
"&&.Mui-focused": {
|
||||||
|
outline: "none",
|
||||||
|
},
|
||||||
|
fontSize: "18px",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
search();
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
>
|
>
|
||||||
<Typography
|
Search
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{searchResults?.map((vid, index) => {
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
key={vid?.identifier}
|
||||||
sx={{
|
sx={{
|
||||||
fontSize: "14px",
|
display: "flex",
|
||||||
|
gap: "10px",
|
||||||
|
width: "100%",
|
||||||
|
alignItems: "center",
|
||||||
|
padding: "10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
userSelect: "none",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{index + 1}
|
<Typography
|
||||||
</Typography>
|
sx={{
|
||||||
<Typography
|
fontSize: "14px",
|
||||||
sx={{
|
}}
|
||||||
fontSize: "18px",
|
>
|
||||||
wordBreak: 'break-word'
|
{index + 1}
|
||||||
}}
|
</Typography>
|
||||||
>
|
<Typography
|
||||||
{vid?.metadata?.title}
|
sx={{
|
||||||
</Typography>
|
fontSize: "18px",
|
||||||
<AddIcon
|
wordBreak: "break-word",
|
||||||
onClick={() => {
|
}}
|
||||||
addVideo(vid);
|
>
|
||||||
}}
|
{vid?.metadata?.title}
|
||||||
sx={{
|
</Typography>
|
||||||
cursor: "pointer",
|
<AddIcon
|
||||||
}}
|
onClick={() => {
|
||||||
/>
|
addVideo(vid);
|
||||||
</Box>
|
}}
|
||||||
);
|
sx={{
|
||||||
})}
|
cursor: "pointer",
|
||||||
</CardContentContainerComment>
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContentContainerComment>
|
||||||
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
|
||||||
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -1,66 +1,83 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { CardContentContainerComment } from '../common/Comments/Comments-styles'
|
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
||||||
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from '../UploadVideo/Upload-styles'
|
import {
|
||||||
import { Box, Typography, useTheme } from '@mui/material'
|
CrowdfundSubTitle,
|
||||||
import { useNavigate } from 'react-router-dom'
|
CrowdfundSubTitleRow,
|
||||||
|
} from "../PublishVideo/PublishVideo-styles.tsx";
|
||||||
export const Playlists = ({playlistData, currentVideoIdentifier, onClick}) => {
|
import { Box, Typography, useTheme } from "@mui/material";
|
||||||
const theme = useTheme();
|
import { useNavigate } from "react-router-dom";
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
|
export const Playlists = ({
|
||||||
|
playlistData,
|
||||||
|
currentVideoIdentifier,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{
|
<Box
|
||||||
display: 'flex',
|
sx={{
|
||||||
flexDirection: 'column',
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
maxWidth: '400px',
|
|
||||||
width: '100%'
|
maxWidth: "400px",
|
||||||
}}>
|
width: "100%",
|
||||||
<CrowdfundSubTitleRow >
|
}}
|
||||||
|
>
|
||||||
|
<CrowdfundSubTitleRow>
|
||||||
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
<CrowdfundSubTitle>Playlist</CrowdfundSubTitle>
|
||||||
</CrowdfundSubTitleRow>
|
</CrowdfundSubTitleRow>
|
||||||
<CardContentContainerComment sx={{
|
<CardContentContainerComment
|
||||||
marginTop: '25px',
|
sx={{
|
||||||
height: '450px',
|
marginTop: "25px",
|
||||||
overflow: 'auto'
|
height: "450px",
|
||||||
}}>
|
overflow: "auto",
|
||||||
{playlistData?.videos?.map((vid, index)=> {
|
}}
|
||||||
const isCurrentVidPlayling = vid?.identifier === currentVideoIdentifier;
|
>
|
||||||
|
{playlistData?.videos?.map((vid, index) => {
|
||||||
|
const isCurrentVidPlayling =
|
||||||
|
vid?.identifier === currentVideoIdentifier;
|
||||||
return (
|
|
||||||
<Box key={vid?.identifier} sx={{
|
return (
|
||||||
display: 'flex',
|
<Box
|
||||||
gap: '10px',
|
key={vid?.identifier}
|
||||||
width: '100%',
|
sx={{
|
||||||
background: isCurrentVidPlayling && theme.palette.primary.main,
|
display: "flex",
|
||||||
alignItems: 'center',
|
gap: "10px",
|
||||||
padding: '10px',
|
width: "100%",
|
||||||
borderRadius: '5px',
|
background: isCurrentVidPlayling && theme.palette.primary.main,
|
||||||
cursor: isCurrentVidPlayling ? 'default' : 'pointer',
|
alignItems: "center",
|
||||||
userSelect: 'none'
|
padding: "10px",
|
||||||
|
borderRadius: "5px",
|
||||||
|
cursor: isCurrentVidPlayling ? "default" : "pointer",
|
||||||
|
userSelect: "none",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (isCurrentVidPlayling) return;
|
||||||
|
onClick(vid.name, vid.identifier);
|
||||||
|
// navigate(`/video/${vid.name}/${vid.identifier}`)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "14px",
|
||||||
}}
|
}}
|
||||||
onClick={()=> {
|
>
|
||||||
if(isCurrentVidPlayling) return
|
{index + 1}
|
||||||
onClick(vid.name, vid.identifier)
|
</Typography>
|
||||||
// navigate(`/video/${vid.name}/${vid.identifier}`)
|
<Typography
|
||||||
|
sx={{
|
||||||
|
fontSize: "18px",
|
||||||
|
wordBreak: "break-word",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Typography sx={{
|
{vid?.metadata?.title}
|
||||||
fontSize: '14px'
|
</Typography>
|
||||||
}}>{index + 1}</Typography>
|
</Box>
|
||||||
<Typography sx={{
|
);
|
||||||
fontSize: '18px',
|
|
||||||
wordBreak: 'break-word'
|
|
||||||
}}>{vid?.metadata?.title}</Typography>
|
|
||||||
|
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
})}
|
})}
|
||||||
</CardContentContainerComment>
|
</CardContentContainerComment>
|
||||||
</Box>
|
</Box>
|
||||||
|
);
|
||||||
)
|
};
|
||||||
}
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import Compressor from 'compressorjs'
|
import Compressor from "compressorjs";
|
||||||
import {
|
import {
|
||||||
AddCoverImageButton,
|
AddCoverImageButton,
|
||||||
AddLogoIcon,
|
AddLogoIcon,
|
||||||
@ -13,7 +13,7 @@ import {
|
|||||||
NewCrowdfundTitle,
|
NewCrowdfundTitle,
|
||||||
StyledButton,
|
StyledButton,
|
||||||
TimesIcon,
|
TimesIcon,
|
||||||
} from "./Upload-styles";
|
} from "./PublishVideo-styles.tsx";
|
||||||
import { CircularProgress } from "@mui/material";
|
import { CircularProgress } from "@mui/material";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -45,12 +45,7 @@ import {
|
|||||||
upsertVideos,
|
upsertVideos,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../state/features/videoSlice";
|
||||||
import ImageUploader from "../common/ImageUploader";
|
import ImageUploader from "../common/ImageUploader";
|
||||||
import {
|
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||||
QTUBE_PLAYLIST_BASE,
|
|
||||||
QTUBE_VIDEO_BASE,
|
|
||||||
categories,
|
|
||||||
subCategories,
|
|
||||||
} from "../../constants";
|
|
||||||
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublish";
|
import { MultiplePublish } from "../common/MultiplePublish/MultiplePublish";
|
||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
@ -59,18 +54,28 @@ import {
|
|||||||
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
import { CardContentContainerComment } from "../common/Comments/Comments-styles";
|
||||||
import { TextEditor } from "../common/TextEditor/TextEditor";
|
import { TextEditor } from "../common/TextEditor/TextEditor";
|
||||||
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
import { extractTextFromHTML } from "../common/TextEditor/utils";
|
||||||
import { FiltersCheckbox, FiltersRow, FiltersSubContainer } from "../../pages/Home/VideoList-styles";
|
import {
|
||||||
|
FiltersCheckbox,
|
||||||
|
FiltersRow,
|
||||||
|
FiltersSubContainer,
|
||||||
|
} from "../../pages/Home/VideoList-styles";
|
||||||
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
import { FrameExtractor } from "../common/FrameExtractor/FrameExtractor";
|
||||||
|
import {
|
||||||
|
QTUBE_PLAYLIST_BASE,
|
||||||
|
QTUBE_VIDEO_BASE,
|
||||||
|
} from "../../constants/Identifiers.ts";
|
||||||
|
import { titleFormatter } from "../../constants/Misc.ts";
|
||||||
|
import { getFileName } from "../../utils/stringFunctions.ts";
|
||||||
|
|
||||||
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
|
||||||
new Promise((resolve, reject) => {
|
new Promise((resolve, reject) => {
|
||||||
const reader = new FileReader()
|
const reader = new FileReader();
|
||||||
reader.readAsDataURL(file)
|
reader.readAsDataURL(file);
|
||||||
reader.onload = () => resolve(reader.result)
|
reader.onload = () => resolve(reader.result);
|
||||||
reader.onerror = (error) => {
|
reader.onerror = error => {
|
||||||
reject(error)
|
reject(error);
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
|
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
const shortuid = new ShortUniqueId({ length: 5 });
|
const shortuid = new ShortUniqueId({ length: 5 });
|
||||||
@ -90,7 +95,7 @@ interface VideoFile {
|
|||||||
description: string;
|
description: string;
|
||||||
coverImage?: string;
|
coverImage?: string;
|
||||||
}
|
}
|
||||||
export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
||||||
@ -113,6 +118,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
useState<any>(null);
|
useState<any>(null);
|
||||||
const [searchResults, setSearchResults] = useState([]);
|
const [searchResults, setSearchResults] = useState([]);
|
||||||
const [filterSearch, setFilterSearch] = useState("");
|
const [filterSearch, setFilterSearch] = useState("");
|
||||||
|
const [titlesPrefix, setTitlesPrefix] = useState("");
|
||||||
const [playlistTitle, setPlaylistTitle] = useState<string>("");
|
const [playlistTitle, setPlaylistTitle] = useState<string>("");
|
||||||
const [playlistDescription, setPlaylistDescription] = useState<string>("");
|
const [playlistDescription, setPlaylistDescription] = useState<string>("");
|
||||||
const [selectedCategory, setSelectedCategory] = useState<any>(null);
|
const [selectedCategory, setSelectedCategory] = useState<any>(null);
|
||||||
@ -125,39 +131,37 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
|
|
||||||
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
|
const [playlistSetting, setPlaylistSetting] = useState<null | string>(null);
|
||||||
const [publishes, setPublishes] = useState<any[]>([]);
|
const [publishes, setPublishes] = useState<any[]>([]);
|
||||||
const [isCheckTitleByFile, setIsCheckTitleByFile] = useState(false)
|
const [isCheckTitleByFile, setIsCheckTitleByFile] = useState(true);
|
||||||
const [isCheckSameCoverImage, setIsCheckSameCoverImage] = useState(false)
|
const [isCheckSameCoverImage, setIsCheckSameCoverImage] = useState(true);
|
||||||
const [isCheckDescriptionIsTitle, setIsCheckDescriptionIsTitle] = useState(false)
|
const [isCheckDescriptionIsTitle, setIsCheckDescriptionIsTitle] =
|
||||||
const [imageExtracts, setImageExtracts] = useState<any>({})
|
useState(false);
|
||||||
|
const [imageExtracts, setImageExtracts] = useState<any>({});
|
||||||
const { getRootProps, getInputProps } = useDropzone({
|
const { getRootProps, getInputProps } = useDropzone({
|
||||||
accept: {
|
accept: {
|
||||||
"video/*": [],
|
"video/*": [],
|
||||||
},
|
},
|
||||||
maxSize: 419430400, // 400 MB in bytes
|
maxSize: 419430400, // 400 MB in bytes
|
||||||
onDrop: (acceptedFiles, rejectedFiles) => {
|
onDrop: (acceptedFiles, rejectedFiles) => {
|
||||||
const formatArray = acceptedFiles.map((item) => {
|
const formatArray = acceptedFiles.map(item => {
|
||||||
|
let filteredTitle = "";
|
||||||
|
|
||||||
let formatTitle = ''
|
if (isCheckTitleByFile) {
|
||||||
if(isCheckTitleByFile && item?.name){
|
const fileName = getFileName(item?.name || "");
|
||||||
const fileExtensionSplit = item?.name?.split(".");
|
filteredTitle = (titlesPrefix + fileName).replace(titleFormatter, "");
|
||||||
if (fileExtensionSplit?.length > 1) {
|
|
||||||
formatTitle = fileExtensionSplit[0]
|
|
||||||
}
|
|
||||||
formatTitle = (formatTitle || "").replace(/[^a-zA-Z0-9\s-_!?]/g, "");
|
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
file: item,
|
file: item,
|
||||||
title: formatTitle,
|
title: filteredTitle || "",
|
||||||
description: "",
|
description: "",
|
||||||
coverImage: "",
|
coverImage: "",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
setFiles((prev) => [...prev, ...formatArray]);
|
setFiles(prev => [...prev, ...formatArray]);
|
||||||
|
|
||||||
let errorString = null;
|
let errorString = null;
|
||||||
rejectedFiles.forEach(({ file, errors }) => {
|
rejectedFiles.forEach(({ file, errors }) => {
|
||||||
errors.forEach((error) => {
|
errors.forEach(error => {
|
||||||
if (error.code === "file-too-large") {
|
if (error.code === "file-too-large") {
|
||||||
errorString = "File must be under 400mb";
|
errorString = "File must be under 400mb";
|
||||||
}
|
}
|
||||||
@ -204,8 +208,10 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
if (!playlistCoverImage) throw new Error("Please select cover image");
|
if (!playlistCoverImage) throw new Error("Please select cover image");
|
||||||
if (!selectedCategory) throw new Error("Please select a category");
|
if (!selectedCategory) throw new Error("Please select a category");
|
||||||
}
|
}
|
||||||
if(files?.length === 0) throw new Error("Please select at least one file");
|
if (files?.length === 0)
|
||||||
if(isCheckSameCoverImage && !coverImageForAll) throw new Error("Please select cover image");
|
throw new Error("Please select at least one file");
|
||||||
|
if (isCheckSameCoverImage && !coverImageForAll)
|
||||||
|
throw new Error("Please select cover image");
|
||||||
if (!userAddress) throw new Error("Unable to locate user address");
|
if (!userAddress) throw new Error("Unable to locate user address");
|
||||||
let errorMsg = "";
|
let errorMsg = "";
|
||||||
let name = "";
|
let name = "";
|
||||||
@ -234,12 +240,16 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
let listOfPublishes = [];
|
let listOfPublishes = [];
|
||||||
|
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
const publish = files[i]
|
const publish = files[i];
|
||||||
const title = publish.title;
|
const title = publish.title;
|
||||||
const description = isCheckDescriptionIsTitle ? publish.title : publish.description;
|
const description = isCheckDescriptionIsTitle
|
||||||
|
? publish.title
|
||||||
|
: publish.description;
|
||||||
const category = selectedCategoryVideos.id;
|
const category = selectedCategoryVideos.id;
|
||||||
const subcategory = selectedSubCategoryVideos?.id || "";
|
const subcategory = selectedSubCategoryVideos?.id || "";
|
||||||
const coverImage = isCheckSameCoverImage ? coverImageForAll : publish.coverImage;
|
const coverImage = isCheckSameCoverImage
|
||||||
|
? coverImageForAll
|
||||||
|
: publish.coverImage;
|
||||||
const file = publish.file;
|
const file = publish.file;
|
||||||
const sanitizeTitle = title
|
const sanitizeTitle = title
|
||||||
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
.replace(/[^a-zA-Z0-9\s-]/g, "")
|
||||||
@ -255,7 +265,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
: `${QTUBE_VIDEO_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
|
: `${QTUBE_VIDEO_BASE}${sanitizeTitle.slice(0, 30)}_${id}`;
|
||||||
|
|
||||||
const code = shortuid();
|
const code = shortuid();
|
||||||
const fullDescription = extractTextFromHTML(description)
|
const fullDescription = extractTextFromHTML(description);
|
||||||
|
|
||||||
let fileExtension = "mp4";
|
let fileExtension = "mp4";
|
||||||
const fileExtensionSplit = file?.name?.split(".");
|
const fileExtensionSplit = file?.name?.split(".");
|
||||||
@ -292,15 +302,13 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
subcategory,
|
subcategory,
|
||||||
code,
|
code,
|
||||||
videoType: file?.type || "video/mp4",
|
videoType: file?.type || "video/mp4",
|
||||||
filename: `${alphanumericString.trim()}.${fileExtension}`
|
filename: `${alphanumericString.trim()}.${fileExtension}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
let metadescription =
|
let metadescription =
|
||||||
`**category:${category};subcategory:${subcategory};code:${code}**` +
|
`**category:${category};subcategory:${subcategory};code:${code}**` +
|
||||||
fullDescription.slice(0, 150);
|
fullDescription.slice(0, 150);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const crowdfundObjectToBase64 = await objectToBase64(videoObject);
|
const crowdfundObjectToBase64 = await objectToBase64(videoObject);
|
||||||
// Description is obtained from raw data
|
// Description is obtained from raw data
|
||||||
const requestBodyJson: any = {
|
const requestBodyJson: any = {
|
||||||
@ -335,7 +343,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
if (isNewPlaylist) {
|
if (isNewPlaylist) {
|
||||||
const title = playlistTitle;
|
const title = playlistTitle;
|
||||||
const description = playlistDescription;
|
const description = playlistDescription;
|
||||||
const stringDescription = extractTextFromHTML(description)
|
const stringDescription = extractTextFromHTML(description);
|
||||||
const category = selectedCategory.id;
|
const category = selectedCategory.id;
|
||||||
const subcategory = selectedSubCategory?.id || "";
|
const subcategory = selectedSubCategory?.id || "";
|
||||||
const coverImage = playlistCoverImage;
|
const coverImage = playlistCoverImage;
|
||||||
@ -354,10 +362,10 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
|
|
||||||
const videos = listOfPublishes
|
const videos = listOfPublishes
|
||||||
.filter(
|
.filter(
|
||||||
(item) =>
|
item =>
|
||||||
item.service === "DOCUMENT" && item.tag1 === QTUBE_VIDEO_BASE
|
item.service === "DOCUMENT" && item.tag1 === QTUBE_VIDEO_BASE
|
||||||
)
|
)
|
||||||
.map((vid) => {
|
.map(vid => {
|
||||||
return {
|
return {
|
||||||
identifier: vid.identifier,
|
identifier: vid.identifier,
|
||||||
service: vid.service,
|
service: vid.service,
|
||||||
@ -378,7 +386,10 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
subcategory,
|
subcategory,
|
||||||
};
|
};
|
||||||
|
|
||||||
const codes = videos.map((item) => `c:${item.code};`).slice(0,10).join("");
|
const codes = videos
|
||||||
|
.map(item => `c:${item.code};`)
|
||||||
|
.slice(0, 10)
|
||||||
|
.join("");
|
||||||
|
|
||||||
let metadescription =
|
let metadescription =
|
||||||
`**category:${category};subcategory:${subcategory};${codes}**` +
|
`**category:${category};subcategory:${subcategory};${codes}**` +
|
||||||
@ -413,10 +424,10 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
if (responseData && !responseData.error) {
|
if (responseData && !responseData.error) {
|
||||||
const videos = listOfPublishes
|
const videos = listOfPublishes
|
||||||
.filter(
|
.filter(
|
||||||
(item) =>
|
item =>
|
||||||
item.service === "DOCUMENT" && item.tag1 === QTUBE_VIDEO_BASE
|
item.service === "DOCUMENT" && item.tag1 === QTUBE_VIDEO_BASE
|
||||||
)
|
)
|
||||||
.map((vid) => {
|
.map(vid => {
|
||||||
return {
|
return {
|
||||||
identifier: vid.identifier,
|
identifier: vid.identifier,
|
||||||
service: vid.service,
|
service: vid.service,
|
||||||
@ -431,7 +442,8 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
videos: videosInPlaylist,
|
videos: videosInPlaylist,
|
||||||
};
|
};
|
||||||
const codes = videosInPlaylist
|
const codes = videosInPlaylist
|
||||||
.map((item) => `c:${item.code};`).slice(0,10)
|
.map(item => `c:${item.code};`)
|
||||||
|
.slice(0, 10)
|
||||||
.join("");
|
.join("");
|
||||||
|
|
||||||
let metadescription =
|
let metadescription =
|
||||||
@ -485,10 +497,10 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleOnchange = (index: number, type: string, value: string) => {
|
const handleOnchange = (index: number, type: string, value: string) => {
|
||||||
setFiles((prev) => {
|
setFiles(prev => {
|
||||||
let formattedValue = value;
|
let formattedValue = value;
|
||||||
if (type === "title") {
|
if (type === "title") {
|
||||||
formattedValue = value.replace(/[^a-zA-Z0-9\s-_!?]/g, "");
|
formattedValue = value.replace(titleFormatter, "");
|
||||||
}
|
}
|
||||||
const copyFiles = [...prev];
|
const copyFiles = [...prev];
|
||||||
copyFiles[index] = {
|
copyFiles[index] = {
|
||||||
@ -501,7 +513,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
|
|
||||||
const handleOptionCategoryChange = (event: SelectChangeEvent<string>) => {
|
const handleOptionCategoryChange = (event: SelectChangeEvent<string>) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
const selectedOption = categories.find(option => option.id === +optionId);
|
||||||
setSelectedCategory(selectedOption || null);
|
setSelectedCategory(selectedOption || null);
|
||||||
};
|
};
|
||||||
const handleOptionSubCategoryChange = (
|
const handleOptionSubCategoryChange = (
|
||||||
@ -510,7 +522,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = subcategories.find(
|
const selectedOption = subcategories.find(
|
||||||
(option) => option.id === +optionId
|
option => option.id === +optionId
|
||||||
);
|
);
|
||||||
setSelectedSubCategory(selectedOption || null);
|
setSelectedSubCategory(selectedOption || null);
|
||||||
};
|
};
|
||||||
@ -519,7 +531,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
event: SelectChangeEvent<string>
|
event: SelectChangeEvent<string>
|
||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
const selectedOption = categories.find(option => option.id === +optionId);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
const handleOptionSubCategoryChangeVideos = (
|
const handleOptionSubCategoryChangeVideos = (
|
||||||
@ -528,20 +540,24 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = subcategories.find(
|
const selectedOption = subcategories.find(
|
||||||
(option) => option.id === +optionId
|
option => option.id === +optionId
|
||||||
);
|
);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
|
|
||||||
const next = () => {
|
const next = () => {
|
||||||
try {
|
try {
|
||||||
if(isCheckSameCoverImage && !coverImageForAll) throw new Error("Please select cover image");
|
if (isCheckSameCoverImage && !coverImageForAll)
|
||||||
if(files?.length === 0) throw new Error("Please select at least one file");
|
throw new Error("Please select cover image");
|
||||||
|
if (files?.length === 0)
|
||||||
|
throw new Error("Please select at least one file");
|
||||||
if (!selectedCategoryVideos) throw new Error("Please select a category");
|
if (!selectedCategoryVideos) throw new Error("Please select a category");
|
||||||
files.forEach((file) => {
|
files.forEach(file => {
|
||||||
if (!file.title) throw new Error("Please enter a title");
|
if (!file.title) throw new Error("Please enter a title");
|
||||||
if (!isCheckTitleByFile && !file.description) throw new Error("Please enter a description");
|
if (!isCheckTitleByFile && !file.description)
|
||||||
if (!isCheckSameCoverImage && !file.coverImage) throw new Error("Please select cover image");
|
throw new Error("Please enter a description");
|
||||||
|
if (!isCheckSameCoverImage && !file.coverImage)
|
||||||
|
throw new Error("Please select cover image");
|
||||||
});
|
});
|
||||||
|
|
||||||
setStep("playlist");
|
setStep("playlist");
|
||||||
@ -555,48 +571,45 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFramesExtracted = async (imgs, index)=> {
|
const onFramesExtracted = async (imgs, index) => {
|
||||||
try {
|
try {
|
||||||
let imagesExtracts = []
|
let imagesExtracts = [];
|
||||||
|
|
||||||
for (const img of imgs){
|
for (const img of imgs) {
|
||||||
try {
|
try {
|
||||||
let compressedFile
|
let compressedFile;
|
||||||
const image = img
|
const image = img;
|
||||||
await new Promise<void>((resolve) => {
|
await new Promise<void>(resolve => {
|
||||||
new Compressor(image, {
|
new Compressor(image, {
|
||||||
quality: .8,
|
quality: 0.8,
|
||||||
maxWidth: 750,
|
maxWidth: 750,
|
||||||
mimeType: 'image/webp',
|
mimeType: "image/webp",
|
||||||
success(result) {
|
success(result) {
|
||||||
const file = new File([result], 'name', {
|
const file = new File([result], "name", {
|
||||||
type: 'image/webp'
|
type: "image/webp",
|
||||||
})
|
});
|
||||||
compressedFile = file
|
compressedFile = file;
|
||||||
resolve()
|
resolve();
|
||||||
},
|
},
|
||||||
error(err) {}
|
error(err) {},
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
if (!compressedFile) continue
|
if (!compressedFile) continue;
|
||||||
const base64Img = await toBase64(compressedFile)
|
const base64Img = await toBase64(compressedFile);
|
||||||
imagesExtracts.push(base64Img)
|
imagesExtracts.push(base64Img);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error)
|
console.error(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setImageExtracts((prev)=> {
|
setImageExtracts(prev => {
|
||||||
return {
|
return {
|
||||||
...prev,
|
...prev,
|
||||||
[index]: imagesExtracts
|
[index]: imagesExtracts,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
} catch (error) {
|
} catch (error) {}
|
||||||
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -638,38 +651,49 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
|
|
||||||
{step === "videos" && (
|
{step === "videos" && (
|
||||||
<>
|
<>
|
||||||
<FiltersSubContainer>
|
<FiltersSubContainer>
|
||||||
<FiltersRow>
|
<FiltersRow>
|
||||||
Populate Titles by filename (when the files are picked)
|
Populate Titles by filename (when the files are picked)
|
||||||
<FiltersCheckbox
|
<FiltersCheckbox
|
||||||
checked={isCheckTitleByFile}
|
checked={isCheckTitleByFile}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setIsCheckTitleByFile(e.target.checked);
|
setIsCheckTitleByFile(e.target.checked);
|
||||||
}}
|
}}
|
||||||
inputProps={{ "aria-label": "controlled" }}
|
inputProps={{ "aria-label": "controlled" }}
|
||||||
|
/>
|
||||||
|
</FiltersRow>
|
||||||
|
<FiltersRow>
|
||||||
|
All videos use the same Cover Image
|
||||||
|
<FiltersCheckbox
|
||||||
|
checked={isCheckSameCoverImage}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setIsCheckSameCoverImage(e.target.checked);
|
||||||
|
}}
|
||||||
|
inputProps={{ "aria-label": "controlled" }}
|
||||||
|
/>
|
||||||
|
</FiltersRow>
|
||||||
|
<FiltersRow>
|
||||||
|
Populate all descriptions by Title
|
||||||
|
<FiltersCheckbox
|
||||||
|
checked={isCheckDescriptionIsTitle}
|
||||||
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
setIsCheckDescriptionIsTitle(e.target.checked);
|
||||||
|
}}
|
||||||
|
inputProps={{ "aria-label": "controlled" }}
|
||||||
|
/>
|
||||||
|
</FiltersRow>
|
||||||
|
</FiltersSubContainer>
|
||||||
|
<CustomInputField
|
||||||
|
name="prefix"
|
||||||
|
label="Titles Prefix"
|
||||||
|
variant="filled"
|
||||||
|
value={titlesPrefix}
|
||||||
|
onChange={e =>
|
||||||
|
setTitlesPrefix(e.target.value.replace(titleFormatter, ""))
|
||||||
|
}
|
||||||
|
inputProps={{ maxLength: 180 }}
|
||||||
|
required
|
||||||
/>
|
/>
|
||||||
</FiltersRow>
|
|
||||||
<FiltersRow>
|
|
||||||
All videos use the same Cover Image
|
|
||||||
<FiltersCheckbox
|
|
||||||
checked={isCheckSameCoverImage}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setIsCheckSameCoverImage(e.target.checked);
|
|
||||||
}}
|
|
||||||
inputProps={{ "aria-label": "controlled" }}
|
|
||||||
/>
|
|
||||||
</FiltersRow>
|
|
||||||
<FiltersRow>
|
|
||||||
Populate all descriptions by Title
|
|
||||||
<FiltersCheckbox
|
|
||||||
checked={isCheckDescriptionIsTitle}
|
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
setIsCheckDescriptionIsTitle(e.target.checked);
|
|
||||||
}}
|
|
||||||
inputProps={{ "aria-label": "controlled" }}
|
|
||||||
/>
|
|
||||||
</FiltersRow>
|
|
||||||
</FiltersSubContainer>
|
|
||||||
<Box
|
<Box
|
||||||
{...getRootProps()}
|
{...getRootProps()}
|
||||||
sx={{
|
sx={{
|
||||||
@ -685,7 +709,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
Drag and drop a video files here or click to select files
|
Drag and drop a video files here or click to select files
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@ -703,7 +727,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
value={selectedCategoryVideos?.id || ""}
|
value={selectedCategoryVideos?.id || ""}
|
||||||
onChange={handleOptionCategoryChangeVideos}
|
onChange={handleOptionCategoryChangeVideos}
|
||||||
>
|
>
|
||||||
{categories.map((option) => (
|
{categories.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -722,7 +746,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
<OutlinedInput label="Select a Sub-Category" />
|
<OutlinedInput label="Select a Sub-Category" />
|
||||||
}
|
}
|
||||||
value={selectedSubCategoryVideos?.id || ""}
|
value={selectedSubCategoryVideos?.id || ""}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
handleOptionSubCategoryChangeVideos(
|
handleOptionSubCategoryChangeVideos(
|
||||||
e,
|
e,
|
||||||
subCategories[selectedCategoryVideos?.id]
|
subCategories[selectedCategoryVideos?.id]
|
||||||
@ -730,7 +754,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{subCategories[selectedCategoryVideos.id].map(
|
{subCategories[selectedCategoryVideos.id].map(
|
||||||
(option) => (
|
option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -743,83 +767,85 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{files?.length > 0 && isCheckSameCoverImage && (
|
{files?.length > 0 && isCheckSameCoverImage && (
|
||||||
<>
|
<>
|
||||||
{!coverImageForAll ? (
|
{!coverImageForAll ? (
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
onPick={(img: string) =>
|
onPick={(img: string) => setCoverImageForAll(img)}
|
||||||
setCoverImageForAll(img)
|
>
|
||||||
}
|
<AddCoverImageButton variant="contained">
|
||||||
>
|
Add Cover Image
|
||||||
<AddCoverImageButton variant="contained">
|
<AddLogoIcon
|
||||||
Add Cover Image
|
sx={{
|
||||||
<AddLogoIcon
|
height: "25px",
|
||||||
sx={{
|
width: "auto",
|
||||||
height: "25px",
|
}}
|
||||||
width: "auto",
|
></AddLogoIcon>
|
||||||
}}
|
</AddCoverImageButton>
|
||||||
></AddLogoIcon>
|
</ImageUploader>
|
||||||
</AddCoverImageButton>
|
) : (
|
||||||
</ImageUploader>
|
<LogoPreviewRow>
|
||||||
) : (
|
<CoverImagePreview src={coverImageForAll} alt="logo" />
|
||||||
<LogoPreviewRow>
|
<TimesIcon
|
||||||
<CoverImagePreview src={coverImageForAll} alt="logo" />
|
color={theme.palette.text.primary}
|
||||||
<TimesIcon
|
onClickFunc={() => setCoverImageForAll(null)}
|
||||||
color={theme.palette.text.primary}
|
height={"32"}
|
||||||
onClickFunc={() =>
|
width={"32"}
|
||||||
setCoverImageForAll(null)
|
></TimesIcon>
|
||||||
}
|
</LogoPreviewRow>
|
||||||
height={"32"}
|
)}
|
||||||
width={"32"}
|
</>
|
||||||
></TimesIcon>
|
)}
|
||||||
</LogoPreviewRow>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{files.map((file, index) => {
|
{files.map((file, index) => {
|
||||||
return (
|
return (
|
||||||
<React.Fragment key={index}>
|
<React.Fragment key={index}>
|
||||||
<FrameExtractor videoFile={file.file} onFramesExtracted={(imgs)=> onFramesExtracted(imgs, index)}/>
|
<FrameExtractor
|
||||||
|
videoFile={file.file}
|
||||||
|
onFramesExtracted={imgs => onFramesExtracted(imgs, index)}
|
||||||
|
/>
|
||||||
<Typography>{file?.file?.name}</Typography>
|
<Typography>{file?.file?.name}</Typography>
|
||||||
{!isCheckSameCoverImage && (
|
{!isCheckSameCoverImage && (
|
||||||
<>
|
<>
|
||||||
{!file?.coverImage ? (
|
{!file?.coverImage ? (
|
||||||
<ImageUploader
|
<ImageUploader
|
||||||
onPick={(img: string) =>
|
onPick={(img: string) =>
|
||||||
handleOnchange(index, "coverImage", img)
|
handleOnchange(index, "coverImage", img)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AddCoverImageButton variant="contained">
|
<AddCoverImageButton variant="contained">
|
||||||
Add Cover Image
|
Add Cover Image
|
||||||
<AddLogoIcon
|
<AddLogoIcon
|
||||||
sx={{
|
sx={{
|
||||||
height: "25px",
|
height: "25px",
|
||||||
width: "auto",
|
width: "auto",
|
||||||
}}
|
}}
|
||||||
></AddLogoIcon>
|
></AddLogoIcon>
|
||||||
</AddCoverImageButton>
|
</AddCoverImageButton>
|
||||||
</ImageUploader>
|
</ImageUploader>
|
||||||
) : (
|
) : (
|
||||||
<LogoPreviewRow>
|
<LogoPreviewRow>
|
||||||
<CoverImagePreview src={file?.coverImage} alt="logo" />
|
<CoverImagePreview
|
||||||
<TimesIcon
|
src={file?.coverImage}
|
||||||
color={theme.palette.text.primary}
|
alt="logo"
|
||||||
onClickFunc={() =>
|
/>
|
||||||
handleOnchange(index, "coverImage", "")
|
<TimesIcon
|
||||||
}
|
color={theme.palette.text.primary}
|
||||||
height={"32"}
|
onClickFunc={() =>
|
||||||
width={"32"}
|
handleOnchange(index, "coverImage", "")
|
||||||
></TimesIcon>
|
}
|
||||||
</LogoPreviewRow>
|
height={"32"}
|
||||||
)}
|
width={"32"}
|
||||||
|
></TimesIcon>
|
||||||
|
</LogoPreviewRow>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<CustomInputField
|
<CustomInputField
|
||||||
name="title"
|
name="title"
|
||||||
label="Title of video"
|
label="Title of video"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={file.title}
|
value={file.title}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
handleOnchange(index, "title", e.target.value)
|
handleOnchange(index, "title", e.target.value)
|
||||||
}
|
}
|
||||||
inputProps={{ maxLength: 180 }}
|
inputProps={{ maxLength: 180 }}
|
||||||
@ -827,15 +853,22 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
/>
|
/>
|
||||||
{!isCheckDescriptionIsTitle && (
|
{!isCheckDescriptionIsTitle && (
|
||||||
<>
|
<>
|
||||||
<Typography sx={{
|
<Typography
|
||||||
fontSize: '18px'
|
sx={{
|
||||||
}}>Description of video</Typography>
|
fontSize: "18px",
|
||||||
<TextEditor inlineContent={file?.description} setInlineContent={(value)=> {
|
}}
|
||||||
handleOnchange(index, "description", value)
|
>
|
||||||
}} />
|
Description of video
|
||||||
|
</Typography>
|
||||||
|
<TextEditor
|
||||||
|
inlineContent={file?.description}
|
||||||
|
setInlineContent={value => {
|
||||||
|
handleOnchange(index, "description", value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* <CustomInputField
|
{/* <CustomInputField
|
||||||
name="description"
|
name="description"
|
||||||
label="Describe your video in a few words"
|
label="Describe your video in a few words"
|
||||||
@ -962,7 +995,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
id="standard-adornment-name"
|
id="standard-adornment-name"
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
setFilterSearch(e.target.value);
|
setFilterSearch(e.target.value);
|
||||||
}}
|
}}
|
||||||
value={filterSearch}
|
value={filterSearch}
|
||||||
@ -1072,11 +1105,11 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
label="Title of playlist"
|
label="Title of playlist"
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={playlistTitle}
|
value={playlistTitle}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
let formattedValue: string = value;
|
let formattedValue: string = value;
|
||||||
|
|
||||||
formattedValue = value.replace(/[^a-zA-Z0-9\s-_!?]/g, "");
|
formattedValue = value.replace(titleFormatter, "");
|
||||||
|
|
||||||
setPlaylistTitle(formattedValue);
|
setPlaylistTitle(formattedValue);
|
||||||
}}
|
}}
|
||||||
@ -1095,12 +1128,19 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
required
|
required
|
||||||
/> */}
|
/> */}
|
||||||
|
|
||||||
<Typography sx={{
|
<Typography
|
||||||
fontSize: '18px'
|
sx={{
|
||||||
}}>Description of playlist</Typography>
|
fontSize: "18px",
|
||||||
<TextEditor inlineContent={playlistDescription} setInlineContent={(value)=> {
|
}}
|
||||||
setPlaylistDescription(value)
|
>
|
||||||
}} />
|
Description of playlist
|
||||||
|
</Typography>
|
||||||
|
<TextEditor
|
||||||
|
inlineContent={playlistDescription}
|
||||||
|
setInlineContent={value => {
|
||||||
|
setPlaylistDescription(value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
<FormControl fullWidth sx={{ marginBottom: 2, marginTop: 2 }}>
|
<FormControl fullWidth sx={{ marginBottom: 2, marginTop: 2 }}>
|
||||||
<InputLabel id="Category">Select a Category</InputLabel>
|
<InputLabel id="Category">Select a Category</InputLabel>
|
||||||
<Select
|
<Select
|
||||||
@ -1109,7 +1149,7 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
value={selectedCategory?.id || ""}
|
value={selectedCategory?.id || ""}
|
||||||
onChange={handleOptionCategoryChange}
|
onChange={handleOptionCategoryChange}
|
||||||
>
|
>
|
||||||
{categories.map((option) => (
|
{categories.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -1125,14 +1165,14 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
labelId="Sub-Category"
|
labelId="Sub-Category"
|
||||||
input={<OutlinedInput label="Select a Sub-Category" />}
|
input={<OutlinedInput label="Select a Sub-Category" />}
|
||||||
value={selectedSubCategory?.id || ""}
|
value={selectedSubCategory?.id || ""}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
handleOptionSubCategoryChange(
|
handleOptionSubCategoryChange(
|
||||||
e,
|
e,
|
||||||
subCategories[selectedCategory?.id]
|
subCategories[selectedCategory?.id]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{subCategories[selectedCategory.id].map((option) => (
|
{subCategories[selectedCategory.id].map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -1186,37 +1226,41 @@ export const UploadVideo = ({ editId, editContent }: NewCrowdfundProps) => {
|
|||||||
) : (
|
) : (
|
||||||
<CrowdfundActionButton
|
<CrowdfundActionButton
|
||||||
variant="contained"
|
variant="contained"
|
||||||
disabled={files?.length !== Object.keys(imageExtracts)?.length}
|
disabled={
|
||||||
|
files?.length !== Object.keys(imageExtracts)?.length
|
||||||
|
}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
next();
|
next();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{files?.length !== Object.keys(imageExtracts)?.length ? 'Generating image extracts' : ''}
|
{files?.length !== Object.keys(imageExtracts)?.length
|
||||||
|
? "Generating image extracts"
|
||||||
|
: ""}
|
||||||
{files?.length !== Object.keys(imageExtracts)?.length && (
|
{files?.length !== Object.keys(imageExtracts)?.length && (
|
||||||
<CircularProgress color="secondary" size={14} />
|
<CircularProgress color="secondary" size={14} />
|
||||||
)}
|
)}
|
||||||
Next
|
Next
|
||||||
</CrowdfundActionButton>
|
</CrowdfundActionButton>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</CrowdfundActionButtonRow>
|
</CrowdfundActionButtonRow>
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
||||||
{isOpenMultiplePublish && (
|
{isOpenMultiplePublish && (
|
||||||
<MultiplePublish
|
<MultiplePublish
|
||||||
isOpen={isOpenMultiplePublish}
|
isOpen={isOpenMultiplePublish}
|
||||||
onSubmit={() => {
|
onSubmit={() => {
|
||||||
setIsOpenMultiplePublish(false);
|
setIsOpenMultiplePublish(false);
|
||||||
setIsOpen(false);
|
setIsOpen(false);
|
||||||
setImageExtracts({})
|
setImageExtracts({});
|
||||||
setFiles([]);
|
setFiles([]);
|
||||||
setStep("videos");
|
setStep("videos");
|
||||||
setPlaylistCoverImage(null);
|
setPlaylistCoverImage(null);
|
||||||
setPlaylistTitle("");
|
setPlaylistTitle("");
|
||||||
setPlaylistDescription("");
|
setPlaylistDescription("");
|
||||||
setSelectedCategory(null);
|
setSelectedCategory(null);
|
||||||
setCoverImageForAll(null)
|
setCoverImageForAll(null);
|
||||||
setSelectedSubCategory(null);
|
setSelectedSubCategory(null);
|
||||||
setSelectedCategoryVideos(null);
|
setSelectedCategoryVideos(null);
|
||||||
setSelectedSubCategoryVideos(null);
|
setSelectedSubCategoryVideos(null);
|
@ -11,7 +11,8 @@ import {
|
|||||||
CommentInputContainer,
|
CommentInputContainer,
|
||||||
SubmitCommentButton,
|
SubmitCommentButton,
|
||||||
} from "./Comments-styles";
|
} from "./Comments-styles";
|
||||||
import { COMMENT_BASE } from "../../../constants";
|
|
||||||
|
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
|
|
||||||
const notification = localforage.createInstance({
|
const notification = localforage.createInstance({
|
||||||
|
@ -14,8 +14,11 @@ import {
|
|||||||
LoadMoreCommentsButtonRow,
|
LoadMoreCommentsButtonRow,
|
||||||
NoCommentsRow,
|
NoCommentsRow,
|
||||||
} from "./Comments-styles";
|
} from "./Comments-styles";
|
||||||
import { COMMENT_BASE } from "../../../constants";
|
import {
|
||||||
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from "../../UploadVideo/Upload-styles";
|
CrowdfundSubTitle,
|
||||||
|
CrowdfundSubTitleRow,
|
||||||
|
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||||
|
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
|
|
||||||
interface CommentSectionProps {
|
interface CommentSectionProps {
|
||||||
postId: string;
|
postId: string;
|
||||||
@ -218,11 +221,10 @@ export const CommentSection = ({ postId, postName }: CommentSectionProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<Panel>
|
<Panel>
|
||||||
<CrowdfundSubTitleRow >
|
<CrowdfundSubTitleRow>
|
||||||
<CrowdfundSubTitle>Comments</CrowdfundSubTitle>
|
<CrowdfundSubTitle>Comments</CrowdfundSubTitle>
|
||||||
</CrowdfundSubTitleRow>
|
</CrowdfundSubTitleRow>
|
||||||
<CommentsContainer>
|
<CommentsContainer>
|
||||||
{loadingComments ? (
|
{loadingComments ? (
|
||||||
<NoCommentsRow>
|
<NoCommentsRow>
|
||||||
|
@ -8,13 +8,13 @@ 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 { ModalBody } from "../../UploadVideo/Upload-styles";
|
import { ModalBody } from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
|
import { CircleSVG } from "../../../assets/svgs/CircleSVG";
|
||||||
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
|
import { EmptyCircleSVG } from "../../../assets/svgs/EmptyCircleSVG";
|
||||||
|
|
||||||
export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const listOfSuccessfulPublishesRef = useRef([])
|
const listOfSuccessfulPublishesRef = useRef([]);
|
||||||
const [listOfSuccessfulPublishes, setListOfSuccessfulPublishes] = useState<
|
const [listOfSuccessfulPublishes, setListOfSuccessfulPublishes] = useState<
|
||||||
any[]
|
any[]
|
||||||
>([]);
|
>([]);
|
||||||
@ -23,7 +23,7 @@ export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
|||||||
const publish = useCallback(async (pub: any) => {
|
const publish = useCallback(async (pub: any) => {
|
||||||
await qortalRequest(pub);
|
await qortalRequest(pub);
|
||||||
}, []);
|
}, []);
|
||||||
const [isPublishing, setIsPublishing] = useState(true)
|
const [isPublishing, setIsPublishing] = useState(true);
|
||||||
|
|
||||||
const handlePublish = useCallback(
|
const handlePublish = useCallback(
|
||||||
async (pub: any) => {
|
async (pub: any) => {
|
||||||
@ -33,10 +33,13 @@ export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
|||||||
await publish(pub);
|
await publish(pub);
|
||||||
|
|
||||||
setListOfSuccessfulPublishes((prev: any) => [...prev, pub?.identifier]);
|
setListOfSuccessfulPublishes((prev: any) => [...prev, pub?.identifier]);
|
||||||
listOfSuccessfulPublishesRef.current = [...listOfSuccessfulPublishesRef.current, pub?.identifier]
|
listOfSuccessfulPublishesRef.current = [
|
||||||
|
...listOfSuccessfulPublishesRef.current,
|
||||||
|
pub?.identifier,
|
||||||
|
];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log({ error });
|
console.log({ error });
|
||||||
await new Promise<void>((res) => {
|
await new Promise<void>(res => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
res();
|
res();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
@ -49,17 +52,18 @@ export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
|||||||
|
|
||||||
const startPublish = useCallback(
|
const startPublish = useCallback(
|
||||||
async (pubs: any) => {
|
async (pubs: any) => {
|
||||||
setIsPublishing(true)
|
setIsPublishing(true);
|
||||||
const filterPubs = pubs.filter((pub)=> !listOfSuccessfulPublishesRef.current.includes(pub.identifier))
|
const filterPubs = pubs.filter(
|
||||||
|
pub => !listOfSuccessfulPublishesRef.current.includes(pub.identifier)
|
||||||
|
);
|
||||||
for (const pub of filterPubs) {
|
for (const pub of filterPubs) {
|
||||||
await handlePublish(pub);
|
await handlePublish(pub);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(listOfSuccessfulPublishesRef.current.length === pubs.length){
|
if (listOfSuccessfulPublishesRef.current.length === pubs.length) {
|
||||||
onSubmit()
|
onSubmit();
|
||||||
}
|
}
|
||||||
setIsPublishing(false)
|
setIsPublishing(false);
|
||||||
},
|
},
|
||||||
[handlePublish, onSubmit, listOfSuccessfulPublishes, publishes]
|
[handlePublish, onSubmit, listOfSuccessfulPublishes, publishes]
|
||||||
);
|
);
|
||||||
@ -71,7 +75,6 @@ export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
|||||||
}
|
}
|
||||||
}, [startPublish, publishes, listOfSuccessfulPublishes]);
|
}, [startPublish, publishes, listOfSuccessfulPublishes]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={isOpen}
|
open={isOpen}
|
||||||
@ -118,18 +121,28 @@ export const MultiplePublish = ({ publishes, isOpen, onSubmit }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{!isPublishing && listOfSuccessfulPublishes.length !== publishes.length && (
|
{!isPublishing &&
|
||||||
<>
|
listOfSuccessfulPublishes.length !== publishes.length && (
|
||||||
<Typography sx={{
|
<>
|
||||||
marginTop: '20px',
|
<Typography
|
||||||
fontSize: '16px'
|
sx={{
|
||||||
}}>Some files were not published. Please try again. It's important that all the files get published. Maybe wait a couple minutes if the error keeps occurring</Typography>
|
marginTop: "20px",
|
||||||
<Button onClick={()=> {
|
fontSize: "16px",
|
||||||
startPublish(publishes)
|
}}
|
||||||
}}>Try again</Button>
|
>
|
||||||
</>
|
Some files were not published. Please try again. It's important
|
||||||
)}
|
that all the files get published. Maybe wait a couple minutes if
|
||||||
|
the error keeps occurring
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
startPublish(publishes);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Try again
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ModalBody>
|
</ModalBody>
|
||||||
</Modal>
|
</Modal>
|
||||||
);
|
);
|
||||||
|
@ -1,324 +1,347 @@
|
|||||||
import { Badge, Box, Button, List, ListItem, ListItemText, Popover, Typography } from '@mui/material'
|
import {
|
||||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
Badge,
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
Box,
|
||||||
import { RootState } from '../../../state/store'
|
Button,
|
||||||
import { FOR, FOR_SUPER_LIKE, SUPER_LIKE_BASE, minPriceSuperlike } from '../../../constants'
|
List,
|
||||||
import NotificationsIcon from '@mui/icons-material/Notifications'
|
ListItem,
|
||||||
import { formatDate } from '../../../utils/time'
|
ListItemText,
|
||||||
|
Popover,
|
||||||
|
Typography,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { RootState } from "../../../state/store";
|
||||||
|
import NotificationsIcon from "@mui/icons-material/Notifications";
|
||||||
|
import { formatDate } from "../../../utils/time";
|
||||||
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
|
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
|
||||||
import { extractSigValue, getPaymentInfo, isTimestampWithinRange } from '../../../pages/VideoContent/VideoContent'
|
import {
|
||||||
import { useNavigate } from 'react-router-dom'
|
extractSigValue,
|
||||||
|
getPaymentInfo,
|
||||||
|
isTimestampWithinRange,
|
||||||
|
} from "../../../pages/VideoContent/VideoContent";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
import localForage from "localforage";
|
import localForage from "localforage";
|
||||||
import moment from 'moment'
|
import moment from "moment";
|
||||||
|
import {
|
||||||
|
FOR,
|
||||||
|
FOR_SUPER_LIKE,
|
||||||
|
SUPER_LIKE_BASE,
|
||||||
|
} from "../../../constants/Identifiers.ts";
|
||||||
|
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
||||||
|
|
||||||
const generalLocal = localForage.createInstance({
|
const generalLocal = localForage.createInstance({
|
||||||
name: "q-tube-general",
|
name: "q-tube-general",
|
||||||
});
|
});
|
||||||
export function extractIdValue(metadescription) {
|
export function extractIdValue(metadescription) {
|
||||||
// Function to extract the substring within double asterisks
|
// Function to extract the substring within double asterisks
|
||||||
function extractSubstring(str) {
|
function extractSubstring(str) {
|
||||||
const match = str.match(/\*\*(.*?)\*\*/);
|
const match = str.match(/\*\*(.*?)\*\*/);
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
|
||||||
|
|
||||||
// Function to extract the 'sig' value
|
|
||||||
function extractSig(str) {
|
|
||||||
const regex = /id:(.*?)(;|$)/;
|
|
||||||
const match = str.match(regex);
|
|
||||||
return match ? match[1] : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extracting the relevant substring
|
|
||||||
const relevantSubstring = extractSubstring(metadescription);
|
|
||||||
|
|
||||||
if (relevantSubstring) {
|
|
||||||
// Extracting the 'sig' value
|
|
||||||
return extractSig(relevantSubstring);
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Function to extract the 'sig' value
|
||||||
|
function extractSig(str) {
|
||||||
|
const regex = /id:(.*?)(;|$)/;
|
||||||
|
const match = str.match(regex);
|
||||||
|
return match ? match[1] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extracting the relevant substring
|
||||||
|
const relevantSubstring = extractSubstring(metadescription);
|
||||||
|
|
||||||
|
if (relevantSubstring) {
|
||||||
|
// Extracting the 'sig' value
|
||||||
|
return extractSig(relevantSubstring);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const Notifications = () => {
|
export const Notifications = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const [anchorElNotification, setAnchorElNotification] = useState<HTMLButtonElement | null>(null)
|
const [anchorElNotification, setAnchorElNotification] =
|
||||||
const [notifications, setNotifications] = useState<any[]>([])
|
useState<HTMLButtonElement | null>(null);
|
||||||
const [notificationTimestamp, setNotificationTimestamp] = useState<null | number>(null)
|
const [notifications, setNotifications] = useState<any[]>([]);
|
||||||
|
const [notificationTimestamp, setNotificationTimestamp] = useState<
|
||||||
|
null | number
|
||||||
|
>(null);
|
||||||
|
|
||||||
|
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
const usernameAddress = useSelector(
|
||||||
const usernameAddress = useSelector((state: RootState) => state.auth?.user?.address);
|
(state: RootState) => state.auth?.user?.address
|
||||||
const navigate = useNavigate();
|
);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const interval = useRef<any>(null)
|
const interval = useRef<any>(null);
|
||||||
|
|
||||||
const getInitialTimestamp = async ()=> {
|
const getInitialTimestamp = async () => {
|
||||||
const timestamp: undefined | number = await generalLocal.getItem("notification-timestamp");
|
const timestamp: undefined | number = await generalLocal.getItem(
|
||||||
if(timestamp){
|
"notification-timestamp"
|
||||||
setNotificationTimestamp(timestamp)
|
);
|
||||||
}
|
if (timestamp) {
|
||||||
|
setNotificationTimestamp(timestamp);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
getInitialTimestamp()
|
getInitialTimestamp();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
const openNotificationPopover = (event: any) => {
|
const openNotificationPopover = (event: any) => {
|
||||||
const target = event.currentTarget as unknown as HTMLButtonElement | null
|
const target = event.currentTarget as unknown as HTMLButtonElement | null;
|
||||||
setAnchorElNotification(target)
|
setAnchorElNotification(target);
|
||||||
}
|
};
|
||||||
const closeNotificationPopover = () => {
|
const closeNotificationPopover = () => {
|
||||||
setAnchorElNotification(null)
|
setAnchorElNotification(null);
|
||||||
}
|
};
|
||||||
const fullNotifications = useMemo(() => {
|
const fullNotifications = useMemo(() => {
|
||||||
return [...notifications].sort(
|
return [...notifications].sort((a, b) => b.created - a.created);
|
||||||
(a, b) => b.created - a.created
|
}, [notifications]);
|
||||||
)
|
const notificationBadgeLength = useMemo(() => {
|
||||||
}, [notifications])
|
if (!notificationTimestamp) return fullNotifications.length;
|
||||||
const notificationBadgeLength = useMemo(()=> {
|
return fullNotifications?.filter(
|
||||||
if(!notificationTimestamp) return fullNotifications.length
|
item => item.created > notificationTimestamp
|
||||||
return fullNotifications?.filter((item)=> item.created > notificationTimestamp).length
|
).length;
|
||||||
}, [fullNotifications, notificationTimestamp])
|
}, [fullNotifications, notificationTimestamp]);
|
||||||
|
|
||||||
const checkNotifications = useCallback(async (username: string) => {
|
const checkNotifications = useCallback(async (username: string) => {
|
||||||
try {
|
try {
|
||||||
// let notificationComments: Item[] =
|
// let notificationComments: Item[] =
|
||||||
// (await notification.getItem('comments')) || []
|
// (await notification.getItem('comments')) || []
|
||||||
// notificationComments = notificationComments
|
// notificationComments = notificationComments
|
||||||
// .filter((nc) => nc.postId && nc.postName && nc.lastSeen)
|
// .filter((nc) => nc.postId && nc.postName && nc.lastSeen)
|
||||||
// .sort((a, b) => b.lastSeen - a.lastSeen)
|
// .sort((a, b) => b.lastSeen - a.lastSeen)
|
||||||
|
|
||||||
const timestamp = await generalLocal.getItem("notification-timestamp");
|
const timestamp = await generalLocal.getItem("notification-timestamp");
|
||||||
|
|
||||||
const after = timestamp || moment().subtract(5, 'days').valueOf();
|
|
||||||
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&identifier=${SUPER_LIKE_BASE}&limit=20&includemetadata=true&reverse=true&excludeblocked=true&offset=0&description=${FOR}:${username}_${FOR_SUPER_LIKE}&after=${after}`;
|
const after = timestamp || moment().subtract(5, "days").valueOf();
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const responseDataSearch = await response.json();
|
|
||||||
let notifys = []
|
|
||||||
for (const comment of responseDataSearch) {
|
|
||||||
if (comment.identifier && comment.name && comment?.metadata?.description) {
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = extractSigValue(comment?.metadata?.description)
|
|
||||||
if(!result) continue
|
|
||||||
const res = await getPaymentInfo(result);
|
|
||||||
if(+res?.amount >= minPriceSuperlike && res.recipient === usernameAddress && isTimestampWithinRange(res?.timestamp, comment.created)){
|
|
||||||
|
|
||||||
let urlReference = null
|
|
||||||
try {
|
|
||||||
let idForUrl = extractIdValue(comment?.metadata?.description)
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${idForUrl}&limit=1&includemetadata=false&reverse=false&excludeblocked=true&offset=0&name=${username}`;
|
|
||||||
const response2 = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const responseSearch = await response2.json();
|
|
||||||
if(responseSearch.length > 0){
|
|
||||||
urlReference = responseSearch[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&identifier=${SUPER_LIKE_BASE}&limit=20&includemetadata=true&reverse=true&excludeblocked=true&offset=0&description=${FOR}:${username}_${FOR_SUPER_LIKE}&after=${after}`;
|
||||||
|
const response = await fetch(url, {
|
||||||
}
|
method: "GET",
|
||||||
// const url = `/arbitrary/BLOG_COMMENT/${comment.name}/${comment.identifier}`;
|
headers: {
|
||||||
// const response = await fetch(url, {
|
"Content-Type": "application/json",
|
||||||
// method: "GET",
|
|
||||||
// headers: {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// },
|
|
||||||
// });
|
|
||||||
// if(!response.ok) continue
|
|
||||||
// const responseData2 = await response.text();
|
|
||||||
|
|
||||||
notifys = [...notifys, {
|
|
||||||
...comment,
|
|
||||||
amount: res.amount,
|
|
||||||
urlReference: urlReference || null
|
|
||||||
}];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setNotifications((prev) => {
|
|
||||||
const allNotifications = [...notifys, ...prev];
|
|
||||||
const uniqueNotifications = Array.from(new Map(allNotifications.map(notif => [notif.identifier, notif])).values());
|
|
||||||
return uniqueNotifications.slice(0, 20);
|
|
||||||
});
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.log({ error })
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
const checkNotificationsFunc = useCallback(
|
|
||||||
(username: string) => {
|
|
||||||
let isCalling = false
|
|
||||||
interval.current = setInterval(async () => {
|
|
||||||
if (isCalling) return
|
|
||||||
isCalling = true
|
|
||||||
const res = await checkNotifications(username)
|
|
||||||
isCalling = false
|
|
||||||
}, 60000)
|
|
||||||
checkNotifications(username)
|
|
||||||
},
|
},
|
||||||
[checkNotifications])
|
});
|
||||||
|
const responseDataSearch = await response.json();
|
||||||
|
let notifys = [];
|
||||||
|
for (const comment of responseDataSearch) {
|
||||||
|
if (
|
||||||
|
comment.identifier &&
|
||||||
|
comment.name &&
|
||||||
|
comment?.metadata?.description
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const result = extractSigValue(comment?.metadata?.description);
|
||||||
|
if (!result) continue;
|
||||||
|
const res = await getPaymentInfo(result);
|
||||||
|
if (
|
||||||
|
+res?.amount >= minPriceSuperlike &&
|
||||||
|
res.recipient === usernameAddress &&
|
||||||
|
isTimestampWithinRange(res?.timestamp, comment.created)
|
||||||
|
) {
|
||||||
|
let urlReference = null;
|
||||||
|
try {
|
||||||
|
let idForUrl = extractIdValue(comment?.metadata?.description);
|
||||||
|
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&identifier=${idForUrl}&limit=1&includemetadata=false&reverse=false&excludeblocked=true&offset=0&name=${username}`;
|
||||||
|
const response2 = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const responseSearch = await response2.json();
|
||||||
|
if (responseSearch.length > 0) {
|
||||||
|
urlReference = responseSearch[0];
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
// const url = `/arbitrary/BLOG_COMMENT/${comment.name}/${comment.identifier}`;
|
||||||
|
// const response = await fetch(url, {
|
||||||
|
// method: "GET",
|
||||||
|
// headers: {
|
||||||
|
// "Content-Type": "application/json",
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// if(!response.ok) continue
|
||||||
|
// const responseData2 = await response.text();
|
||||||
|
|
||||||
useEffect(() => {
|
notifys = [
|
||||||
if (!username) return
|
...notifys,
|
||||||
checkNotificationsFunc(username)
|
{
|
||||||
|
...comment,
|
||||||
|
amount: res.amount,
|
||||||
|
urlReference: urlReference || null,
|
||||||
return () => {
|
},
|
||||||
if (interval?.current) {
|
];
|
||||||
clearInterval(interval.current)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, [checkNotificationsFunc, username])
|
} catch (error) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNotifications(prev => {
|
||||||
|
const allNotifications = [...notifys, ...prev];
|
||||||
|
const uniqueNotifications = Array.from(
|
||||||
|
new Map(
|
||||||
|
allNotifications.map(notif => [notif.identifier, notif])
|
||||||
|
).values()
|
||||||
|
);
|
||||||
|
return uniqueNotifications.slice(0, 20);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error });
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const openPopover = Boolean(anchorElNotification)
|
const checkNotificationsFunc = useCallback(
|
||||||
|
(username: string) => {
|
||||||
|
let isCalling = false;
|
||||||
|
interval.current = setInterval(async () => {
|
||||||
|
if (isCalling) return;
|
||||||
|
isCalling = true;
|
||||||
|
const res = await checkNotifications(username);
|
||||||
|
isCalling = false;
|
||||||
|
}, 60000);
|
||||||
|
checkNotifications(username);
|
||||||
|
},
|
||||||
|
[checkNotifications]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!username) return;
|
||||||
|
checkNotificationsFunc(username);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (interval?.current) {
|
||||||
|
clearInterval(interval.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [checkNotificationsFunc, username]);
|
||||||
|
|
||||||
|
const openPopover = Boolean(anchorElNotification);
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
|
|
||||||
<Badge
|
|
||||||
badgeContent={notificationBadgeLength}
|
|
||||||
color="primary"
|
|
||||||
sx={{
|
sx={{
|
||||||
margin: '0px 12px'
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Button
|
<Badge
|
||||||
onClick={(e) => {
|
badgeContent={notificationBadgeLength}
|
||||||
openNotificationPopover(e)
|
color="primary"
|
||||||
generalLocal.setItem("notification-timestamp", Date.now());
|
|
||||||
setNotificationTimestamp(Date.now)
|
|
||||||
}}
|
|
||||||
sx={{
|
sx={{
|
||||||
margin: '0px',
|
margin: "0px 12px",
|
||||||
padding: '0px',
|
|
||||||
height: 'auto',
|
|
||||||
width: 'auto',
|
|
||||||
minWidth: 'unset'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<NotificationsIcon color="action" />
|
<Button
|
||||||
</Button>
|
onClick={e => {
|
||||||
</Badge>
|
openNotificationPopover(e);
|
||||||
<Popover
|
generalLocal.setItem("notification-timestamp", Date.now());
|
||||||
id={'simple-popover-notification'}
|
setNotificationTimestamp(Date.now);
|
||||||
open={openPopover}
|
}}
|
||||||
anchorEl={anchorElNotification}
|
|
||||||
onClose={closeNotificationPopover}
|
|
||||||
anchorOrigin={{
|
|
||||||
vertical: 'bottom',
|
|
||||||
horizontal: 'left'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box>
|
|
||||||
<List
|
|
||||||
sx={{
|
sx={{
|
||||||
maxHeight: '300px',
|
margin: "0px",
|
||||||
overflow: 'auto'
|
padding: "0px",
|
||||||
|
height: "auto",
|
||||||
|
width: "auto",
|
||||||
|
minWidth: "unset",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<NotificationsIcon color="action" />
|
||||||
|
</Button>
|
||||||
|
</Badge>
|
||||||
|
<Popover
|
||||||
|
id={"simple-popover-notification"}
|
||||||
|
open={openPopover}
|
||||||
|
anchorEl={anchorElNotification}
|
||||||
|
onClose={closeNotificationPopover}
|
||||||
|
anchorOrigin={{
|
||||||
|
vertical: "bottom",
|
||||||
|
horizontal: "left",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<List
|
||||||
|
sx={{
|
||||||
|
maxHeight: "300px",
|
||||||
|
overflow: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{fullNotifications.length === 0 && (
|
{fullNotifications.length === 0 && (
|
||||||
<ListItem
|
<ListItem>
|
||||||
|
<ListItemText primary="No new notifications"></ListItemText>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
|
{fullNotifications.map((notification: any, index: number) => (
|
||||||
|
<ListItem
|
||||||
|
key={index}
|
||||||
|
divider
|
||||||
|
sx={{
|
||||||
|
cursor: notification?.urlReference ? "pointer" : "default",
|
||||||
|
}}
|
||||||
|
onClick={async () => {
|
||||||
|
if (notification?.urlReference) {
|
||||||
|
navigate(
|
||||||
|
`/video/${notification?.urlReference?.name}/${notification?.urlReference?.identifier}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<ListItemText
|
<ListItemText
|
||||||
primary="No new notifications">
|
primary={
|
||||||
|
<Box
|
||||||
</ListItemText>
|
sx={{
|
||||||
</ListItem>
|
display: "flex",
|
||||||
)}
|
alignItems: "center",
|
||||||
{fullNotifications.map((notification: any, index: number) => (
|
gap: "5px",
|
||||||
<ListItem
|
}}
|
||||||
key={index}
|
|
||||||
divider
|
|
||||||
sx={{
|
|
||||||
cursor: notification?.urlReference ? 'pointer' : 'default'
|
|
||||||
}}
|
|
||||||
onClick={async () => {
|
|
||||||
if(notification?.urlReference){
|
|
||||||
navigate(`/video/${notification?.urlReference?.name}/${notification?.urlReference?.identifier}`);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ListItemText
|
|
||||||
primary={
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
gap: '5px'
|
|
||||||
}}>
|
|
||||||
<Typography
|
|
||||||
component="span"
|
|
||||||
variant="body1"
|
|
||||||
color="textPrimary"
|
|
||||||
>
|
>
|
||||||
|
<Typography
|
||||||
|
component="span"
|
||||||
|
variant="body1"
|
||||||
|
color="textPrimary"
|
||||||
|
>
|
||||||
Super Like
|
Super Like
|
||||||
|
</Typography>
|
||||||
</Typography>
|
<ThumbUpIcon
|
||||||
<ThumbUpIcon
|
style={{
|
||||||
style={{
|
color: "gold",
|
||||||
color: "gold",
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
}}
|
}
|
||||||
/>
|
secondary={
|
||||||
</Box>
|
<React.Fragment>
|
||||||
}
|
<Typography
|
||||||
secondary={
|
component="span"
|
||||||
<React.Fragment>
|
sx={{
|
||||||
<Typography
|
fontSize: "16px",
|
||||||
component="span"
|
}}
|
||||||
sx={{
|
color="textSecondary"
|
||||||
fontSize: '16px'
|
>
|
||||||
}}
|
{formatDate(notification.created)}
|
||||||
color="textSecondary"
|
</Typography>
|
||||||
>
|
<Typography
|
||||||
{formatDate(notification.created)}
|
component="span"
|
||||||
</Typography>
|
sx={{
|
||||||
<Typography
|
fontSize: "16px",
|
||||||
component="span"
|
}}
|
||||||
sx={{
|
color="textSecondary"
|
||||||
fontSize: '16px'
|
>
|
||||||
}}
|
{` from ${notification.name}`}
|
||||||
color="textSecondary"
|
</Typography>
|
||||||
>
|
</React.Fragment>
|
||||||
{` from ${notification.name}`}
|
}
|
||||||
</Typography>
|
/>
|
||||||
</React.Fragment>
|
</ListItem>
|
||||||
}
|
))}
|
||||||
/>
|
</List>
|
||||||
</ListItem>
|
</Box>
|
||||||
))}
|
</Popover>
|
||||||
</List>
|
</Box>
|
||||||
</Box>
|
);
|
||||||
</Popover>
|
};
|
||||||
</Box>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
|
import ThumbUpIcon from "@mui/icons-material/ThumbUp";
|
||||||
import {
|
import {
|
||||||
Box,
|
Box,
|
||||||
@ -7,10 +7,13 @@ import {
|
|||||||
DialogActions,
|
DialogActions,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
|
FormControl,
|
||||||
Input,
|
Input,
|
||||||
InputAdornment,
|
InputAdornment,
|
||||||
InputLabel,
|
InputLabel,
|
||||||
|
MenuItem,
|
||||||
Modal,
|
Modal,
|
||||||
|
Select,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@mui/material";
|
} from "@mui/material";
|
||||||
import qortImg from "../../../assets/img/qort.png";
|
import qortImg from "../../../assets/img/qort.png";
|
||||||
@ -19,13 +22,7 @@ import { useDispatch, useSelector } from "react-redux";
|
|||||||
import { setNotification } from "../../../state/features/notificationsSlice";
|
import { setNotification } from "../../../state/features/notificationsSlice";
|
||||||
import ShortUniqueId from "short-unique-id";
|
import ShortUniqueId from "short-unique-id";
|
||||||
import { objectToBase64 } from "../../../utils/toBase64";
|
import { objectToBase64 } from "../../../utils/toBase64";
|
||||||
import {
|
import { minPriceSuperlike } from "../../../constants/Misc.ts";
|
||||||
FOR,
|
|
||||||
FOR_SUPER_LIKE,
|
|
||||||
QTUBE_VIDEO_BASE,
|
|
||||||
SUPER_LIKE_BASE,
|
|
||||||
minPriceSuperlike,
|
|
||||||
} from "../../../constants";
|
|
||||||
import { CommentInput } from "../Comments/Comments-styles";
|
import { CommentInput } from "../Comments/Comments-styles";
|
||||||
import {
|
import {
|
||||||
CrowdfundActionButton,
|
CrowdfundActionButton,
|
||||||
@ -33,9 +30,18 @@ import {
|
|||||||
ModalBody,
|
ModalBody,
|
||||||
NewCrowdfundTitle,
|
NewCrowdfundTitle,
|
||||||
Spacer,
|
Spacer,
|
||||||
} from "../../UploadVideo/Upload-styles";
|
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor";
|
import { utf8ToBase64 } from "../SuperLikesList/CommentEditor";
|
||||||
import { RootState } from "../../../state/store";
|
import { RootState } from "../../../state/store";
|
||||||
|
import {
|
||||||
|
FOR,
|
||||||
|
FOR_SUPER_LIKE,
|
||||||
|
QTUBE_VIDEO_BASE,
|
||||||
|
SUPER_LIKE_BASE,
|
||||||
|
} from "../../../constants/Identifiers.ts";
|
||||||
|
import BoundedNumericTextField from "../../../utils/BoundedNumericTextField.tsx";
|
||||||
|
import { numberToInt, truncateNumber } from "../../../utils/numberFunctions.ts";
|
||||||
|
import { getUserBalance } from "../../../utils/qortalRequestFunctions.ts";
|
||||||
|
|
||||||
const uid = new ShortUniqueId({ length: 4 });
|
const uid = new ShortUniqueId({ length: 4 });
|
||||||
|
|
||||||
@ -48,17 +54,21 @@ export const SuperLike = ({
|
|||||||
numberOfSuperlikes,
|
numberOfSuperlikes,
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||||
const [amount, setAmount] = useState<number>(10);
|
|
||||||
|
const [superlikeDonationAmount, setSuperlikeDonationAmount] =
|
||||||
|
useState<number>(10);
|
||||||
|
const [qortalDevDonationAmount, setQortalDevDonationAmount] =
|
||||||
|
useState<number>(0);
|
||||||
|
const [currentBalance, setCurrentBalance] = useState<string>("");
|
||||||
|
|
||||||
const [comment, setComment] = useState<string>("");
|
const [comment, setComment] = useState<string>("");
|
||||||
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
const username = useSelector((state: RootState) => state.auth?.user?.name);
|
||||||
|
|
||||||
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
const [isOpenMultiplePublish, setIsOpenMultiplePublish] = useState(false);
|
||||||
const [publishes, setPublishes] = useState<any[]>([]);
|
const [publishes, setPublishes] = useState<any[]>([]);
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
const resetValues = () => {
|
const resetValues = () => {
|
||||||
setAmount(0);
|
setSuperlikeDonationAmount(0);
|
||||||
setComment("");
|
setComment("");
|
||||||
setPublishes([]);
|
setPublishes([]);
|
||||||
};
|
};
|
||||||
@ -71,6 +81,15 @@ export const SuperLike = ({
|
|||||||
try {
|
try {
|
||||||
if (!username) throw new Error("You need a name to publish");
|
if (!username) throw new Error("You need a name to publish");
|
||||||
if (!name) throw new Error("Could not retrieve content creator's name");
|
if (!name) throw new Error("Could not retrieve content creator's name");
|
||||||
|
const estimatedTransactionFees = 0.1;
|
||||||
|
const donationExceedsBalance =
|
||||||
|
superlikeDonationAmount +
|
||||||
|
qortalDevDonationAmount +
|
||||||
|
estimatedTransactionFees >=
|
||||||
|
+currentBalance;
|
||||||
|
if (donationExceedsBalance) {
|
||||||
|
throw new Error("Total donations exceeds current balance");
|
||||||
|
}
|
||||||
|
|
||||||
let resName = await qortalRequest({
|
let resName = await qortalRequest({
|
||||||
action: "GET_NAME_DATA",
|
action: "GET_NAME_DATA",
|
||||||
@ -83,7 +102,10 @@ export const SuperLike = ({
|
|||||||
|
|
||||||
if (!address)
|
if (!address)
|
||||||
throw new Error("Could not retrieve content creator's address");
|
throw new Error("Could not retrieve content creator's address");
|
||||||
if (!amount || amount < minPriceSuperlike)
|
if (
|
||||||
|
!superlikeDonationAmount ||
|
||||||
|
superlikeDonationAmount < minPriceSuperlike
|
||||||
|
)
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The amount needs to be at least ${minPriceSuperlike} QORT`
|
`The amount needs to be at least ${minPriceSuperlike} QORT`
|
||||||
);
|
);
|
||||||
@ -94,9 +116,26 @@ export const SuperLike = ({
|
|||||||
action: "SEND_COIN",
|
action: "SEND_COIN",
|
||||||
coin: "QORT",
|
coin: "QORT",
|
||||||
destinationAddress: address,
|
destinationAddress: address,
|
||||||
amount: amount,
|
amount: superlikeDonationAmount,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const devDonation = qortalDevDonationAmount > 0;
|
||||||
|
if (devDonation) {
|
||||||
|
const devFundName = "DevFund";
|
||||||
|
|
||||||
|
let devFundNameData = await qortalRequest({
|
||||||
|
action: "GET_NAME_DATA",
|
||||||
|
name: devFundName,
|
||||||
|
});
|
||||||
|
|
||||||
|
const devFundAddress = devFundNameData.owner;
|
||||||
|
const resDevFund = await qortalRequest({
|
||||||
|
action: "SEND_COIN",
|
||||||
|
coin: "QORT",
|
||||||
|
destinationAddress: devFundAddress,
|
||||||
|
amount: qortalDevDonationAmount,
|
||||||
|
});
|
||||||
|
}
|
||||||
let metadescription = `**sig:${
|
let metadescription = `**sig:${
|
||||||
res.signature
|
res.signature
|
||||||
};${FOR}:${name}_${FOR_SUPER_LIKE};nm:${name.slice(
|
};${FOR}:${name}_${FOR_SUPER_LIKE};nm:${name.slice(
|
||||||
@ -119,7 +158,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 amount 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 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",
|
||||||
});
|
});
|
||||||
// Description is obtained from raw data
|
// Description is obtained from raw data
|
||||||
// const base64 = utf8ToBase64(comment);
|
// const base64 = utf8ToBase64(comment);
|
||||||
@ -164,6 +203,14 @@ export const SuperLike = ({
|
|||||||
throw new Error("Failed to publish Super Like");
|
throw new Error("Failed to publish Super Like");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
getUserBalance().then(foundBalance => {
|
||||||
|
setCurrentBalance(truncateNumber(foundBalance, 2));
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const textFieldWidth = "350px";
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Box
|
<Box
|
||||||
@ -245,42 +292,44 @@ export const SuperLike = ({
|
|||||||
<NewCrowdfundTitle>Super Like</NewCrowdfundTitle>
|
<NewCrowdfundTitle>Super Like</NewCrowdfundTitle>
|
||||||
</Box>
|
</Box>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<Box
|
<Box>
|
||||||
sx={{
|
<InputLabel htmlFor="standard-adornment-amount">
|
||||||
width: "300px",
|
Amount in QORT (min 10 QORT)
|
||||||
display: "flex",
|
</InputLabel>
|
||||||
justifyContent: "center",
|
<BoundedNumericTextField
|
||||||
}}
|
minValue={10}
|
||||||
>
|
initialValue={minPriceSuperlike.toString()}
|
||||||
<Box>
|
maxValue={numberToInt(+currentBalance)}
|
||||||
<InputLabel htmlFor="standard-adornment-amount">
|
allowDecimals={false}
|
||||||
Amount in QORT (min 10 QORT)
|
allowNegatives={false}
|
||||||
</InputLabel>
|
id="standard-adornment-amount"
|
||||||
<Input
|
value={superlikeDonationAmount}
|
||||||
id="standard-adornment-amount"
|
afterChange={(e: string) => setSuperlikeDonationAmount(+e)}
|
||||||
type="number"
|
InputProps={{
|
||||||
value={amount}
|
style: { fontSize: 30, width: textFieldWidth },
|
||||||
onChange={(e) => setAmount(+e.target.value)}
|
startAdornment: (
|
||||||
startAdornment={
|
|
||||||
<InputAdornment position="start">
|
<InputAdornment position="start">
|
||||||
<img
|
<img
|
||||||
style={{
|
style={{
|
||||||
height: "15px",
|
height: "40px",
|
||||||
width: "15px",
|
width: "40px",
|
||||||
}}
|
}}
|
||||||
src={qortImg}
|
src={qortImg}
|
||||||
|
alt={"Qort Icon"}
|
||||||
/>
|
/>
|
||||||
</InputAdornment>
|
</InputAdornment>
|
||||||
}
|
),
|
||||||
/>
|
}}
|
||||||
</Box>
|
/>
|
||||||
</Box>
|
|
||||||
<Spacer height="25px" />
|
<div>Current QORT Balance is: {currentBalance}</div>
|
||||||
<Box>
|
<Spacer height="25px" />
|
||||||
|
|
||||||
<CommentInput
|
<CommentInput
|
||||||
id="standard-multiline-flexible"
|
id="standard-multiline-flexible"
|
||||||
label="Your comment"
|
label="Your comment"
|
||||||
multiline
|
multiline
|
||||||
|
minRows={8}
|
||||||
maxRows={8}
|
maxRows={8}
|
||||||
variant="filled"
|
variant="filled"
|
||||||
value={comment}
|
value={comment}
|
||||||
@ -288,7 +337,37 @@ export const SuperLike = ({
|
|||||||
maxLength: 500,
|
maxLength: 500,
|
||||||
}}
|
}}
|
||||||
InputLabelProps={{ style: { fontSize: "18px" } }}
|
InputLabelProps={{ style: { fontSize: "18px" } }}
|
||||||
onChange={(e) => setComment(e.target.value)}
|
onChange={e => setComment(e.target.value)}
|
||||||
|
/>
|
||||||
|
<Spacer height="50px" />
|
||||||
|
<InputLabel
|
||||||
|
htmlFor="standard-adornment-amount"
|
||||||
|
style={{ paddingBottom: "10px" }}
|
||||||
|
>
|
||||||
|
Would you like to donate to Qortal Development?
|
||||||
|
</InputLabel>
|
||||||
|
<BoundedNumericTextField
|
||||||
|
minValue={0}
|
||||||
|
initialValue={""}
|
||||||
|
maxValue={numberToInt(+currentBalance)}
|
||||||
|
allowDecimals={false}
|
||||||
|
value={superlikeDonationAmount}
|
||||||
|
afterChange={(e: string) => setQortalDevDonationAmount(+e)}
|
||||||
|
InputProps={{
|
||||||
|
style: { fontSize: 30, width: textFieldWidth },
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
height: "40px",
|
||||||
|
width: "40px",
|
||||||
|
}}
|
||||||
|
src={qortImg}
|
||||||
|
alt={"Qort Icon"}
|
||||||
|
/>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
@ -332,7 +411,7 @@ export const SuperLike = ({
|
|||||||
message: comment,
|
message: comment,
|
||||||
service,
|
service,
|
||||||
identifier,
|
identifier,
|
||||||
amount: +amount,
|
amount: +superlikeDonationAmount,
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
});
|
});
|
||||||
setIsOpenMultiplePublish(false);
|
setIsOpenMultiplePublish(false);
|
||||||
|
@ -11,8 +11,8 @@ import {
|
|||||||
CommentInputContainer,
|
CommentInputContainer,
|
||||||
SubmitCommentButton,
|
SubmitCommentButton,
|
||||||
} from "./Comments-styles";
|
} from "./Comments-styles";
|
||||||
import { COMMENT_BASE } from "../../../constants";
|
|
||||||
import { addtoHashMapSuperlikes } from "../../../state/features/videoSlice";
|
import { addtoHashMapSuperlikes } from "../../../state/features/videoSlice";
|
||||||
|
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
const uid = new ShortUniqueId();
|
const uid = new ShortUniqueId();
|
||||||
|
|
||||||
const notification = localforage.createInstance({
|
const notification = localforage.createInstance({
|
||||||
@ -84,9 +84,9 @@ interface CommentEditorProps {
|
|||||||
commentId?: string;
|
commentId?: string;
|
||||||
isEdit?: boolean;
|
isEdit?: boolean;
|
||||||
commentMessage?: string;
|
commentMessage?: string;
|
||||||
isSuperLike?: boolean
|
isSuperLike?: boolean;
|
||||||
comment?: any;
|
comment?: any;
|
||||||
hasHash?: boolean
|
hasHash?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function utf8ToBase64(inputString: string): string {
|
export function utf8ToBase64(inputString: string): string {
|
||||||
@ -111,7 +111,7 @@ export const CommentEditor = ({
|
|||||||
commentMessage,
|
commentMessage,
|
||||||
isSuperLike,
|
isSuperLike,
|
||||||
comment,
|
comment,
|
||||||
hasHash
|
hasHash,
|
||||||
}: CommentEditorProps) => {
|
}: CommentEditorProps) => {
|
||||||
const [value, setValue] = useState<string>("");
|
const [value, setValue] = useState<string>("");
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -156,34 +156,41 @@ export const CommentEditor = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let data64 = null
|
let data64 = null;
|
||||||
let description = ""
|
let description = "";
|
||||||
let tag1 = ""
|
let tag1 = "";
|
||||||
let superObj = {}
|
let superObj = {};
|
||||||
if(isSuperLike){
|
if (isSuperLike) {
|
||||||
if(!comment?.metadata?.description || !comment?.metadata?.tags[0] || !comment?.transactionReference || !comment?.notificationInformation || !comment?.about) throw new Error('unable to edit Super like')
|
if (
|
||||||
description = comment?.metadata?.description
|
!comment?.metadata?.description ||
|
||||||
tag1 = comment?.metadata?.tags[0]
|
!comment?.metadata?.tags[0] ||
|
||||||
superObj = {
|
!comment?.transactionReference ||
|
||||||
|
!comment?.notificationInformation ||
|
||||||
|
!comment?.about
|
||||||
|
)
|
||||||
|
throw new Error("unable to edit Super like");
|
||||||
|
description = comment?.metadata?.description;
|
||||||
|
tag1 = comment?.metadata?.tags[0];
|
||||||
|
superObj = {
|
||||||
comment: value,
|
comment: value,
|
||||||
transactionReference: comment.transactionReference,
|
transactionReference: comment.transactionReference,
|
||||||
notificationInformation: comment.notificationInformation,
|
notificationInformation: comment.notificationInformation,
|
||||||
about: comment.about
|
about: comment.about,
|
||||||
}
|
};
|
||||||
const superLikeToBase64 = await objectToBase64(superObj);
|
const superLikeToBase64 = await objectToBase64(superObj);
|
||||||
data64 = superLikeToBase64
|
data64 = superLikeToBase64;
|
||||||
}
|
}
|
||||||
if(isSuperLike && !data64) throw new Error('unable to edit Super like')
|
if (isSuperLike && !data64) throw new Error("unable to edit Super like");
|
||||||
|
|
||||||
const base64 = utf8ToBase64(value);
|
const base64 = utf8ToBase64(value);
|
||||||
const resourceResponse = await qortalRequest({
|
const resourceResponse = await qortalRequest({
|
||||||
action: "PUBLISH_QDN_RESOURCE",
|
action: "PUBLISH_QDN_RESOURCE",
|
||||||
name: name,
|
name: name,
|
||||||
service: "BLOG_COMMENT",
|
service: "BLOG_COMMENT",
|
||||||
data64: isSuperLike ? data64 : base64,
|
data64: isSuperLike ? data64 : base64,
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
description,
|
description,
|
||||||
tag1
|
tag1,
|
||||||
});
|
});
|
||||||
dispatch(
|
dispatch(
|
||||||
setNotification({
|
setNotification({
|
||||||
@ -192,13 +199,14 @@ export const CommentEditor = ({
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
if(isSuperLike){
|
if (isSuperLike) {
|
||||||
dispatch(addtoHashMapSuperlikes({
|
dispatch(
|
||||||
...superObj,
|
addtoHashMapSuperlikes({
|
||||||
...comment,
|
...superObj,
|
||||||
message: value
|
...comment,
|
||||||
}))
|
message: value,
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (idForNotification) {
|
if (idForNotification) {
|
||||||
addItem({
|
addItem({
|
||||||
@ -240,7 +248,7 @@ export const CommentEditor = ({
|
|||||||
|
|
||||||
let identifier = `${COMMENT_BASE}${postId.slice(-12)}_base_${id}`;
|
let identifier = `${COMMENT_BASE}${postId.slice(-12)}_base_${id}`;
|
||||||
let idForNotification = identifier;
|
let idForNotification = identifier;
|
||||||
let service = 'BLOG_COMMENT'
|
let service = "BLOG_COMMENT";
|
||||||
if (isReply && commentId) {
|
if (isReply && commentId) {
|
||||||
const removeBaseCommentId = commentId;
|
const removeBaseCommentId = commentId;
|
||||||
removeBaseCommentId.replace("_base_", "");
|
removeBaseCommentId.replace("_base_", "");
|
||||||
@ -252,10 +260,10 @@ export const CommentEditor = ({
|
|||||||
if (isEdit && commentId) {
|
if (isEdit && commentId) {
|
||||||
identifier = commentId;
|
identifier = commentId;
|
||||||
}
|
}
|
||||||
|
|
||||||
await publishComment(identifier, idForNotification);
|
await publishComment(identifier, idForNotification);
|
||||||
if(isSuperLike){
|
if (isSuperLike) {
|
||||||
onSubmit({})
|
onSubmit({});
|
||||||
} else {
|
} else {
|
||||||
onSubmit({
|
onSubmit({
|
||||||
created: Date.now(),
|
created: Date.now(),
|
||||||
@ -265,7 +273,7 @@ export const CommentEditor = ({
|
|||||||
name: user?.name,
|
name: user?.name,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
setValue("");
|
setValue("");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
|
@ -14,14 +14,17 @@ import {
|
|||||||
LoadMoreCommentsButtonRow,
|
LoadMoreCommentsButtonRow,
|
||||||
NoCommentsRow,
|
NoCommentsRow,
|
||||||
} from "./Comments-styles";
|
} from "./Comments-styles";
|
||||||
import { COMMENT_BASE } from "../../../constants";
|
import {
|
||||||
import { CrowdfundSubTitle, CrowdfundSubTitleRow } from "../../UploadVideo/Upload-styles";
|
CrowdfundSubTitle,
|
||||||
|
CrowdfundSubTitleRow,
|
||||||
|
} from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||||
|
import { COMMENT_BASE } from "../../../constants/Identifiers.ts";
|
||||||
|
|
||||||
interface CommentSectionProps {
|
interface CommentSectionProps {
|
||||||
postId: string;
|
postId: string;
|
||||||
postName: string;
|
postName: string;
|
||||||
superlikes: any[];
|
superlikes: any[];
|
||||||
getMore: ()=> void;
|
getMore: () => void;
|
||||||
loadingSuperLikes: boolean;
|
loadingSuperLikes: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +52,13 @@ const Panel = styled("div")`
|
|||||||
background-color: #555;
|
background-color: #555;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postName, getMore }: CommentSectionProps) => {
|
export const SuperLikesSection = ({
|
||||||
|
loadingSuperLikes,
|
||||||
|
superlikes,
|
||||||
|
postId,
|
||||||
|
postName,
|
||||||
|
getMore,
|
||||||
|
}: CommentSectionProps) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [listComments, setListComments] = useState<any[]>([]);
|
const [listComments, setListComments] = useState<any[]>([]);
|
||||||
@ -59,7 +68,7 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
|||||||
const [loadingComments, setLoadingComments] = useState<boolean>(null);
|
const [loadingComments, setLoadingComments] = useState<boolean>(null);
|
||||||
const hashMapSuperlikes = useSelector(
|
const hashMapSuperlikes = useSelector(
|
||||||
(state: RootState) => state.video.hashMapSuperlikes
|
(state: RootState) => state.video.hashMapSuperlikes
|
||||||
)
|
);
|
||||||
const onSubmit = (obj?: any, isEdit?: boolean) => {
|
const onSubmit = (obj?: any, isEdit?: boolean) => {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
setListComments((prev: any[]) => {
|
setListComments((prev: any[]) => {
|
||||||
@ -147,32 +156,28 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
|||||||
[postId]
|
[postId]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getComments = useCallback(
|
const getComments = useCallback(async (superlikes, postId) => {
|
||||||
async (superlikes, postId) => {
|
try {
|
||||||
try {
|
setLoadingComments(true);
|
||||||
setLoadingComments(true);
|
|
||||||
|
let comments: any[] = [];
|
||||||
let comments: any[] = [];
|
for (const comment of superlikes) {
|
||||||
for (const comment of superlikes) {
|
comments.push(comment);
|
||||||
comments.push(comment);
|
const res = await getReplies(comment.identifier, postId);
|
||||||
const res = await getReplies(comment.identifier, postId);
|
comments = [...comments, ...res];
|
||||||
comments = [...comments, ...res];
|
|
||||||
}
|
|
||||||
|
|
||||||
setListComments(comments);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
setLoadingComments(false);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
[]
|
setListComments(comments);
|
||||||
);
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoadingComments(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(postId){
|
if (postId) {
|
||||||
getComments(superlikes, postId)
|
getComments(superlikes, postId);
|
||||||
}
|
}
|
||||||
}, [getComments, superlikes, postId]);
|
}, [getComments, superlikes, postId]);
|
||||||
|
|
||||||
@ -191,41 +196,44 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
|||||||
}, []);
|
}, []);
|
||||||
}, [listComments]);
|
}, [listComments]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
||||||
<Panel>
|
<Panel>
|
||||||
<CrowdfundSubTitleRow >
|
<CrowdfundSubTitleRow>
|
||||||
<CrowdfundSubTitle sx={{
|
<CrowdfundSubTitle
|
||||||
fontSize: '18px',
|
sx={{
|
||||||
color: 'gold'
|
fontSize: "18px",
|
||||||
}}>Super Likes</CrowdfundSubTitle>
|
color: "gold",
|
||||||
</CrowdfundSubTitleRow>
|
}}
|
||||||
|
>
|
||||||
|
Super Likes
|
||||||
|
</CrowdfundSubTitle>
|
||||||
|
</CrowdfundSubTitleRow>
|
||||||
<CommentsContainer>
|
<CommentsContainer>
|
||||||
{(loadingComments || loadingSuperLikes) ? (
|
{loadingComments || loadingSuperLikes ? (
|
||||||
<NoCommentsRow>
|
<NoCommentsRow>
|
||||||
<CircularProgress />
|
<CircularProgress />
|
||||||
</NoCommentsRow>
|
</NoCommentsRow>
|
||||||
) : listComments.length === 0 ? (
|
) : listComments.length === 0 ? (
|
||||||
<NoCommentsRow>
|
<NoCommentsRow>
|
||||||
There are no super likes yet. Be the first!
|
There are no super likes yet. Be the first!
|
||||||
</NoCommentsRow>
|
</NoCommentsRow>
|
||||||
) : (
|
) : (
|
||||||
<CommentContainer>
|
<CommentContainer>
|
||||||
{structuredCommentList.map((comment: any) => {
|
{structuredCommentList.map((comment: any) => {
|
||||||
let hasHash = false
|
let hasHash = false;
|
||||||
let message = {...comment}
|
let message = { ...comment };
|
||||||
let hash = {}
|
let hash = {};
|
||||||
if(hashMapSuperlikes[comment?.identifier]){
|
if (hashMapSuperlikes[comment?.identifier]) {
|
||||||
message.message = hashMapSuperlikes[comment?.identifier]?.comment || ""
|
message.message =
|
||||||
hasHash = true
|
hashMapSuperlikes[comment?.identifier]?.comment || "";
|
||||||
hash = hashMapSuperlikes[comment?.identifier]
|
hasHash = true;
|
||||||
|
hash = hashMapSuperlikes[comment?.identifier];
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Comment
|
<Comment
|
||||||
key={comment?.identifier}
|
key={comment?.identifier}
|
||||||
comment={{...message, ...hash}}
|
comment={{ ...message, ...hash }}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
postId={postId}
|
postId={postId}
|
||||||
postName={postName}
|
postName={postName}
|
||||||
@ -241,7 +249,7 @@ export const SuperLikesSection = ({ loadingSuperLikes, superlikes, postId, postN
|
|||||||
<LoadMoreCommentsButtonRow>
|
<LoadMoreCommentsButtonRow>
|
||||||
<LoadMoreCommentsButton
|
<LoadMoreCommentsButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
getMore()
|
getMore();
|
||||||
}}
|
}}
|
||||||
variant="contained"
|
variant="contained"
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -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 { UploadVideo } from "../../UploadVideo/UploadVideo";
|
import { PublishVideo } from "../../PublishVideo/PublishVideo.tsx";
|
||||||
import { StyledButton } from "../../UploadVideo/Upload-styles";
|
import { StyledButton } from "../../PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { Notifications } from "../../common/Notifications/Notifications";
|
import { Notifications } from "../../common/Notifications/Notifications";
|
||||||
interface Props {
|
interface Props {
|
||||||
isAuthenticated: boolean;
|
isAuthenticated: boolean;
|
||||||
@ -279,10 +279,10 @@ const NavBar: React.FC<Props> = ({
|
|||||||
<Input
|
<Input
|
||||||
id="standard-adornment-name"
|
id="standard-adornment-name"
|
||||||
inputRef={inputRef}
|
inputRef={inputRef}
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
searchValRef.current = e.target.value;
|
searchValRef.current = e.target.value;
|
||||||
}}
|
}}
|
||||||
onKeyDown={(event) => {
|
onKeyDown={event => {
|
||||||
if (event.key === "Enter" || event.keyCode === 13) {
|
if (event.key === "Enter" || event.keyCode === 13) {
|
||||||
if (!searchValRef.current) {
|
if (!searchValRef.current) {
|
||||||
dispatch(setIsFiltering(false));
|
dispatch(setIsFiltering(false));
|
||||||
@ -355,9 +355,7 @@ const NavBar: React.FC<Props> = ({
|
|||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
</Popover>
|
</Popover>
|
||||||
{isAuthenticated && userName && (
|
{isAuthenticated && userName && <Notifications />}
|
||||||
<Notifications />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<DownloadTaskManager />
|
<DownloadTaskManager />
|
||||||
{isAuthenticated && userName && (
|
{isAuthenticated && userName && (
|
||||||
@ -393,20 +391,18 @@ const NavBar: React.FC<Props> = ({
|
|||||||
<AvatarContainer>
|
<AvatarContainer>
|
||||||
{isAuthenticated && userName && (
|
{isAuthenticated && userName && (
|
||||||
<>
|
<>
|
||||||
<UploadVideo />
|
<PublishVideo />
|
||||||
<StyledButton
|
<StyledButton
|
||||||
color="primary"
|
color="primary"
|
||||||
startIcon={<AddBoxIcon />}
|
startIcon={<AddBoxIcon />}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(setEditPlaylist({mode: 'new'}))
|
dispatch(setEditPlaylist({ mode: "new" }));
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
create playlist
|
create playlist
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
||||||
</AvatarContainer>
|
</AvatarContainer>
|
||||||
|
|
||||||
<Popover
|
<Popover
|
||||||
|
107
src/constants/Categories.ts
Normal file
107
src/constants/Categories.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
interface SubCategory {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CategoryMap {
|
||||||
|
[key: number]: SubCategory[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const sortCategory = (a: SubCategory, b: SubCategory) => {
|
||||||
|
if (a.name === "Other") return 1;
|
||||||
|
else if (b.name === "Other") return -1;
|
||||||
|
else return a.name.localeCompare(b.name);
|
||||||
|
};
|
||||||
|
export const categories = [
|
||||||
|
{ id: 1, name: "Movies" },
|
||||||
|
{ id: 2, name: "Series" },
|
||||||
|
{ id: 3, name: "Music" },
|
||||||
|
{ id: 4, name: "Education" },
|
||||||
|
{ id: 5, name: "Lifestyle" },
|
||||||
|
{ id: 6, name: "Gaming" },
|
||||||
|
{ id: 7, name: "Technology" },
|
||||||
|
{ id: 8, name: "Sports" },
|
||||||
|
{ id: 9, name: "News & Politics" },
|
||||||
|
{ id: 10, name: "Cooking & Food" },
|
||||||
|
{ id: 11, name: "Animation" },
|
||||||
|
{ id: 12, name: "Science" },
|
||||||
|
{ id: 13, name: "Health & Wellness" },
|
||||||
|
{ id: 14, name: "DIY & Crafts" },
|
||||||
|
{ id: 15, name: "Kids & Family" },
|
||||||
|
{ id: 16, name: "Comedy" },
|
||||||
|
{ id: 17, name: "Travel & Adventure" },
|
||||||
|
{ id: 18, name: "Art & Design" },
|
||||||
|
{ id: 19, name: "Nature & Environment" },
|
||||||
|
{ id: 20, name: "Business & Finance" },
|
||||||
|
{ id: 21, name: "Personal Development" },
|
||||||
|
{ id: 22, name: "Other" },
|
||||||
|
{ id: 23, name: "History" },
|
||||||
|
{ id: 24, name: "Anime" },
|
||||||
|
{ id: 25, name: "Cartoons" },
|
||||||
|
{ id: 26, name: "Qortal" },
|
||||||
|
].sort(sortCategory);
|
||||||
|
|
||||||
|
export const subCategories: CategoryMap = {
|
||||||
|
1: [
|
||||||
|
// Movies
|
||||||
|
{ id: 101, name: "Action & Adventure" },
|
||||||
|
{ id: 102, name: "Comedy" },
|
||||||
|
{ id: 103, name: "Drama" },
|
||||||
|
{ id: 104, name: "Fantasy & Science Fiction" },
|
||||||
|
{ id: 105, name: "Horror & Thriller" },
|
||||||
|
{ id: 106, name: "Documentaries" },
|
||||||
|
{ id: 107, name: "Animated" },
|
||||||
|
{ id: 108, name: "Family & Kids" },
|
||||||
|
{ id: 109, name: "Romance" },
|
||||||
|
{ id: 110, name: "Mystery & Crime" },
|
||||||
|
{ id: 111, name: "Historical & War" },
|
||||||
|
{ id: 112, name: "Musicals & Music Films" },
|
||||||
|
{ id: 113, name: "Indie Films" },
|
||||||
|
{ id: 114, name: "International Films" },
|
||||||
|
{ id: 115, name: "Biographies & True Stories" },
|
||||||
|
{ id: 116, name: "Other" },
|
||||||
|
].sort(sortCategory),
|
||||||
|
2: [
|
||||||
|
// Series
|
||||||
|
{ id: 201, name: "Dramas" },
|
||||||
|
{ id: 202, name: "Comedies" },
|
||||||
|
{ id: 203, name: "Reality & Competition" },
|
||||||
|
{ id: 204, name: "Documentaries & Docuseries" },
|
||||||
|
{ id: 205, name: "Sci-Fi & Fantasy" },
|
||||||
|
{ id: 206, name: "Crime & Mystery" },
|
||||||
|
{ id: 207, name: "Animated Series" },
|
||||||
|
{ id: 208, name: "Kids & Family" },
|
||||||
|
{ id: 209, name: "Historical & Period Pieces" },
|
||||||
|
{ id: 210, name: "Action & Adventure" },
|
||||||
|
{ id: 211, name: "Horror & Thriller" },
|
||||||
|
{ id: 212, name: "Romance" },
|
||||||
|
{ id: 213, name: "Anthologies" },
|
||||||
|
{ id: 214, name: "International Series" },
|
||||||
|
{ id: 215, name: "Miniseries" },
|
||||||
|
{ id: 216, name: "Other" },
|
||||||
|
].sort(sortCategory),
|
||||||
|
4: [
|
||||||
|
// Education
|
||||||
|
{ id: 400, name: "Tutorial" },
|
||||||
|
{ id: 401, name: "Documentary" },
|
||||||
|
{ id: 401, name: "Qortal" },
|
||||||
|
{ id: 402, name: "Other" },
|
||||||
|
].sort(sortCategory),
|
||||||
|
|
||||||
|
24: [
|
||||||
|
{ id: 2401, name: "Kodomomuke" },
|
||||||
|
{ id: 2402, name: "Shonen" },
|
||||||
|
{ id: 2403, name: "Shoujo" },
|
||||||
|
{ id: 2404, name: "Seinen" },
|
||||||
|
{ id: 2405, name: "Josei" },
|
||||||
|
{ id: 2406, name: "Mecha" },
|
||||||
|
{ id: 2407, name: "Mahou Shoujo" },
|
||||||
|
{ id: 2408, name: "Isekai" },
|
||||||
|
{ id: 2409, name: "Yaoi" },
|
||||||
|
{ id: 2410, name: "Yuri" },
|
||||||
|
{ id: 2411, name: "Harem" },
|
||||||
|
{ id: 2412, name: "Ecchi" },
|
||||||
|
{ id: 2413, name: "Idol" },
|
||||||
|
{ id: 2414, name: "Other" },
|
||||||
|
].sort(sortCategory),
|
||||||
|
};
|
15
src/constants/Identifiers.ts
Normal file
15
src/constants/Identifiers.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
const useTestIdentifiers = false;
|
||||||
|
export const QTUBE_VIDEO_BASE = useTestIdentifiers
|
||||||
|
? "MYTEST_vid_"
|
||||||
|
: "qtube_vid_";
|
||||||
|
export const QTUBE_PLAYLIST_BASE = useTestIdentifiers
|
||||||
|
? "MYTEST_playlist_"
|
||||||
|
: "qtube_playlist_";
|
||||||
|
export const SUPER_LIKE_BASE = useTestIdentifiers
|
||||||
|
? "MYTEST_superlike_"
|
||||||
|
: "qtube_superlike_";
|
||||||
|
export const COMMENT_BASE = useTestIdentifiers
|
||||||
|
? "qcomment_v1_MYTEST_"
|
||||||
|
: "qcomment_v1_qtube_";
|
||||||
|
export const FOR = useTestIdentifiers ? "FORTEST5" : "FOR0962";
|
||||||
|
export const FOR_SUPER_LIKE = useTestIdentifiers ? "MYTEST_sl" : `qtube_sl`;
|
2
src/constants/Misc.ts
Normal file
2
src/constants/Misc.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const minPriceSuperlike = 10;
|
||||||
|
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^&*+=]/g;
|
@ -1,121 +0,0 @@
|
|||||||
const useTestIdentifiers = true;
|
|
||||||
|
|
||||||
export const QTUBE_VIDEO_BASE = useTestIdentifiers
|
|
||||||
? "MYTEST_vid_"
|
|
||||||
: "qtube_vid_";
|
|
||||||
|
|
||||||
export const QTUBE_PLAYLIST_BASE = useTestIdentifiers
|
|
||||||
? "MYTEST_playlist_"
|
|
||||||
: "qtube_playlist_";
|
|
||||||
|
|
||||||
export const SUPER_LIKE_BASE = useTestIdentifiers
|
|
||||||
? "MYTEST_superlike_"
|
|
||||||
: "qtube_superlike_";
|
|
||||||
|
|
||||||
export const COMMENT_BASE = useTestIdentifiers
|
|
||||||
? "qcomment_v1_MYTEST_"
|
|
||||||
: "qcomment_v1_qtube_";
|
|
||||||
|
|
||||||
export const FOR = useTestIdentifiers
|
|
||||||
? "FORTEST5"
|
|
||||||
: "FOR0962";
|
|
||||||
|
|
||||||
export const FOR_SUPER_LIKE = useTestIdentifiers
|
|
||||||
? "MYTEST_sl"
|
|
||||||
: `qtube_sl`;
|
|
||||||
|
|
||||||
export const minPriceSuperlike = 10
|
|
||||||
|
|
||||||
interface SubCategory {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CategoryMap {
|
|
||||||
[key: number]: SubCategory[];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export const categories = [
|
|
||||||
{"id": 1, "name": "Movies"},
|
|
||||||
{"id": 2, "name": "Series"},
|
|
||||||
{"id": 3, "name": "Music"},
|
|
||||||
{"id": 4, "name": "Education"},
|
|
||||||
{"id": 5, "name": "Lifestyle"},
|
|
||||||
{"id": 6, "name": "Gaming"},
|
|
||||||
{"id": 7, "name": "Technology"},
|
|
||||||
{"id": 8, "name": "Sports"},
|
|
||||||
{"id": 9, "name": "News & Politics"},
|
|
||||||
{"id": 10, "name": "Cooking & Food"},
|
|
||||||
{"id": 11, "name": "Animation"},
|
|
||||||
{"id": 12, "name": "Science"},
|
|
||||||
{"id": 13, "name": "Health & Wellness"},
|
|
||||||
{"id": 14, "name": "DIY & Crafts"},
|
|
||||||
{"id": 15, "name": "Kids & Family"},
|
|
||||||
{"id": 16, "name": "Comedy"},
|
|
||||||
{"id": 17, "name": "Travel & Adventure"},
|
|
||||||
{"id": 18, "name": "Art & Design"},
|
|
||||||
{"id": 19, "name": "Nature & Environment"},
|
|
||||||
{"id": 20, "name": "Business & Finance"},
|
|
||||||
{"id": 21, "name": "Personal Development"},
|
|
||||||
{"id": 22, "name": "Other"},
|
|
||||||
{"id": 23, "name": "History"},
|
|
||||||
{"id": 24, "name": "Anime"},
|
|
||||||
{"id": 25, "name": "Cartoons"}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
export const subCategories: CategoryMap = {
|
|
||||||
1: [ // Movies
|
|
||||||
{"id": 101, "name": "Action & Adventure"},
|
|
||||||
{"id": 102, "name": "Comedy"},
|
|
||||||
{"id": 103, "name": "Drama"},
|
|
||||||
{"id": 104, "name": "Fantasy & Science Fiction"},
|
|
||||||
{"id": 105, "name": "Horror & Thriller"},
|
|
||||||
{"id": 106, "name": "Documentaries"},
|
|
||||||
{"id": 107, "name": "Animated"},
|
|
||||||
{"id": 108, "name": "Family & Kids"},
|
|
||||||
{"id": 109, "name": "Romance"},
|
|
||||||
{"id": 110, "name": "Mystery & Crime"},
|
|
||||||
{"id": 111, "name": "Historical & War"},
|
|
||||||
{"id": 112, "name": "Musicals & Music Films"},
|
|
||||||
{"id": 113, "name": "Indie Films"},
|
|
||||||
{"id": 114, "name": "International Films"},
|
|
||||||
{"id": 115, "name": "Biographies & True Stories"},
|
|
||||||
{"id": 116, "name": "Other"}
|
|
||||||
],
|
|
||||||
2: [ // Series
|
|
||||||
{"id": 201, "name": "Dramas"},
|
|
||||||
{"id": 202, "name": "Comedies"},
|
|
||||||
{"id": 203, "name": "Reality & Competition"},
|
|
||||||
{"id": 204, "name": "Documentaries & Docuseries"},
|
|
||||||
{"id": 205, "name": "Sci-Fi & Fantasy"},
|
|
||||||
{"id": 206, "name": "Crime & Mystery"},
|
|
||||||
{"id": 207, "name": "Animated Series"},
|
|
||||||
{"id": 208, "name": "Kids & Family"},
|
|
||||||
{"id": 209, "name": "Historical & Period Pieces"},
|
|
||||||
{"id": 210, "name": "Action & Adventure"},
|
|
||||||
{"id": 211, "name": "Horror & Thriller"},
|
|
||||||
{"id": 212, "name": "Romance"},
|
|
||||||
{"id": 213, "name": "Anthologies"},
|
|
||||||
{"id": 214, "name": "International Series"},
|
|
||||||
{"id": 215, "name": "Miniseries"},
|
|
||||||
{"id": 216, "name": "Other"}
|
|
||||||
],
|
|
||||||
24: [
|
|
||||||
{"id": 2401, "name": "Kodomomuke"},
|
|
||||||
{"id": 2402, "name": "Shonen"},
|
|
||||||
{"id": 2403, "name": "Shoujo"},
|
|
||||||
{"id": 2404, "name": "Seinen"},
|
|
||||||
{"id": 2405, "name": "Josei"},
|
|
||||||
{"id": 2406, "name": "Mecha"},
|
|
||||||
{"id": 2407, "name": "Mahou Shoujo"},
|
|
||||||
{"id": 2408, "name": "Isekai"},
|
|
||||||
{"id": 2409, "name": "Yaoi"},
|
|
||||||
{"id": 2410, "name": "Yuri"},
|
|
||||||
{"id": 2411, "name": "Harem"},
|
|
||||||
{"id": 2412, "name": "Ecchi"},
|
|
||||||
{"id": 2413, "name": "Idol"},
|
|
||||||
{"id": 2414, "name": "Other"}
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import React from 'react'
|
import React from "react";
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {
|
import {
|
||||||
addVideos,
|
addVideos,
|
||||||
addToHashMap,
|
addToHashMap,
|
||||||
@ -7,103 +7,108 @@ import {
|
|||||||
upsertVideos,
|
upsertVideos,
|
||||||
upsertVideosBeginning,
|
upsertVideosBeginning,
|
||||||
Video,
|
Video,
|
||||||
upsertFilteredVideos
|
upsertFilteredVideos,
|
||||||
} from '../state/features/videoSlice'
|
} from "../state/features/videoSlice";
|
||||||
import {
|
import {
|
||||||
setIsLoadingGlobal, setUserAvatarHash
|
setIsLoadingGlobal,
|
||||||
} from '../state/features/globalSlice'
|
setUserAvatarHash,
|
||||||
import { RootState } from '../state/store'
|
} from "../state/features/globalSlice";
|
||||||
import { fetchAndEvaluateVideos } from '../utils/fetchVideos'
|
import { RootState } from "../state/store";
|
||||||
import { QTUBE_PLAYLIST_BASE, QTUBE_VIDEO_BASE } from '../constants'
|
import { fetchAndEvaluateVideos } from "../utils/fetchVideos";
|
||||||
import { RequestQueue } from '../utils/queue'
|
import { RequestQueue } from "../utils/queue";
|
||||||
import { queue } from '../wrappers/GlobalWrapper'
|
import { queue } from "../wrappers/GlobalWrapper";
|
||||||
|
import {
|
||||||
|
QTUBE_PLAYLIST_BASE,
|
||||||
|
QTUBE_VIDEO_BASE,
|
||||||
|
} from "../constants/Identifiers.ts";
|
||||||
|
|
||||||
export const useFetchVideos = () => {
|
export const useFetchVideos = () => {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const hashMapVideos = useSelector(
|
const hashMapVideos = useSelector(
|
||||||
(state: RootState) => state.video.hashMapVideos
|
(state: RootState) => state.video.hashMapVideos
|
||||||
)
|
);
|
||||||
const videos = useSelector((state: RootState) => state.video.videos)
|
const videos = useSelector((state: RootState) => state.video.videos);
|
||||||
const userAvatarHash = useSelector(
|
const userAvatarHash = useSelector(
|
||||||
(state: RootState) => state.global.userAvatarHash
|
(state: RootState) => state.global.userAvatarHash
|
||||||
)
|
);
|
||||||
const filteredVideos = useSelector(
|
const filteredVideos = useSelector(
|
||||||
(state: RootState) => state.video.filteredVideos
|
(state: RootState) => state.video.filteredVideos
|
||||||
)
|
);
|
||||||
|
|
||||||
const checkAndUpdateVideo = React.useCallback(
|
const checkAndUpdateVideo = React.useCallback(
|
||||||
(video: Video) => {
|
(video: Video) => {
|
||||||
const existingVideo = hashMapVideos[video.id]
|
const existingVideo = hashMapVideos[video.id];
|
||||||
if (!existingVideo) {
|
if (!existingVideo) {
|
||||||
return true
|
return true;
|
||||||
} else if (
|
} else if (
|
||||||
video?.updated &&
|
video?.updated &&
|
||||||
existingVideo?.updated &&
|
existingVideo?.updated &&
|
||||||
(!existingVideo?.updated || video?.updated) > existingVideo?.updated
|
(!existingVideo?.updated || video?.updated) > existingVideo?.updated
|
||||||
) {
|
) {
|
||||||
return true
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[hashMapVideos]
|
[hashMapVideos]
|
||||||
)
|
);
|
||||||
|
|
||||||
const getAvatar = React.useCallback(async (author: string) => {
|
const getAvatar = React.useCallback(async (author: string) => {
|
||||||
try {
|
try {
|
||||||
let url = await qortalRequest({
|
let url = await qortalRequest({
|
||||||
action: 'GET_QDN_RESOURCE_URL',
|
action: "GET_QDN_RESOURCE_URL",
|
||||||
name: author,
|
name: author,
|
||||||
service: 'THUMBNAIL',
|
service: "THUMBNAIL",
|
||||||
identifier: 'qortal_avatar'
|
identifier: "qortal_avatar",
|
||||||
})
|
});
|
||||||
|
|
||||||
dispatch(setUserAvatarHash({
|
dispatch(
|
||||||
name: author,
|
setUserAvatarHash({
|
||||||
url
|
name: author,
|
||||||
}))
|
url,
|
||||||
} catch (error) { }
|
})
|
||||||
}, [])
|
);
|
||||||
|
} catch (error) {}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const getVideo = async (user: string, videoId: string, content: any, retries: number = 0) => {
|
const getVideo = async (
|
||||||
|
user: string,
|
||||||
|
videoId: string,
|
||||||
|
content: any,
|
||||||
|
retries: number = 0
|
||||||
|
) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetchAndEvaluateVideos({
|
const res = await fetchAndEvaluateVideos({
|
||||||
user,
|
user,
|
||||||
videoId,
|
videoId,
|
||||||
content
|
content,
|
||||||
})
|
});
|
||||||
|
|
||||||
dispatch(addToHashMap(res))
|
dispatch(addToHashMap(res));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
retries= retries + 1
|
retries = retries + 1;
|
||||||
if (retries < 2) { // 3 is the maximum number of retries here, you can adjust it to your needs
|
if (retries < 2) {
|
||||||
|
// 3 is the maximum number of retries here, you can adjust it to your needs
|
||||||
queue.push(() => getVideo(user, videoId, content, retries + 1));
|
queue.push(() => getVideo(user, videoId, content, retries + 1));
|
||||||
} else {
|
} else {
|
||||||
console.error('Failed to get video after 3 attempts', error);
|
console.error("Failed to get video after 3 attempts", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const getNewVideos = React.useCallback(async () => {
|
const getNewVideos = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
dispatch(setIsLoadingGlobal(true))
|
dispatch(setIsLoadingGlobal(true));
|
||||||
|
|
||||||
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`
|
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
const responseData = await response.json()
|
const responseData = await response.json();
|
||||||
|
|
||||||
// const responseData = await qortalRequest({
|
// const responseData = await qortalRequest({
|
||||||
// action: "SEARCH_QDN_RESOURCES",
|
// action: "SEARCH_QDN_RESOURCES",
|
||||||
// mode: "ALL",
|
// mode: "ALL",
|
||||||
@ -116,16 +121,16 @@ export const useFetchVideos = () => {
|
|||||||
// exactMatchNames: true,
|
// exactMatchNames: true,
|
||||||
// name: names
|
// name: names
|
||||||
// })
|
// })
|
||||||
const latestVideo = videos[0]
|
const latestVideo = videos[0];
|
||||||
if (!latestVideo) return
|
if (!latestVideo) return;
|
||||||
const findVideo = responseData?.findIndex(
|
const findVideo = responseData?.findIndex(
|
||||||
(item: any) => item?.identifier === latestVideo?.id
|
(item: any) => item?.identifier === latestVideo?.id
|
||||||
)
|
);
|
||||||
let fetchAll = responseData
|
let fetchAll = responseData;
|
||||||
let willFetchAll = true
|
let willFetchAll = true;
|
||||||
if (findVideo !== -1) {
|
if (findVideo !== -1) {
|
||||||
willFetchAll = false
|
willFetchAll = false;
|
||||||
fetchAll = responseData.slice(0, findVideo)
|
fetchAll = responseData.slice(0, findVideo);
|
||||||
}
|
}
|
||||||
|
|
||||||
const structureData = fetchAll.map((video: any): Video => {
|
const structureData = fetchAll.map((video: any): Video => {
|
||||||
@ -138,22 +143,22 @@ export const useFetchVideos = () => {
|
|||||||
created: video?.created,
|
created: video?.created,
|
||||||
updated: video?.updated,
|
updated: video?.updated,
|
||||||
user: video.name,
|
user: video.name,
|
||||||
videoImage: '',
|
videoImage: "",
|
||||||
id: video.identifier
|
id: video.identifier,
|
||||||
}
|
};
|
||||||
})
|
});
|
||||||
if (!willFetchAll) {
|
if (!willFetchAll) {
|
||||||
dispatch(upsertVideosBeginning(structureData))
|
dispatch(upsertVideosBeginning(structureData));
|
||||||
}
|
}
|
||||||
if (willFetchAll) {
|
if (willFetchAll) {
|
||||||
dispatch(addVideos(structureData))
|
dispatch(addVideos(structureData));
|
||||||
}
|
}
|
||||||
setTimeout(()=> {
|
setTimeout(() => {
|
||||||
dispatch(setCountNewVideos(0))
|
dispatch(setCountNewVideos(0));
|
||||||
}, 1000)
|
}, 1000);
|
||||||
for (const content of structureData) {
|
for (const content of structureData) {
|
||||||
if (content.user && content.id) {
|
if (content.user && content.id) {
|
||||||
const res = checkAndUpdateVideo(content)
|
const res = checkAndUpdateVideo(content);
|
||||||
if (res) {
|
if (res) {
|
||||||
queue.push(() => getVideo(content.user, content.id, content));
|
queue.push(() => getVideo(content.user, content.id, content));
|
||||||
}
|
}
|
||||||
@ -161,182 +166,185 @@ export const useFetchVideos = () => {
|
|||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
} finally {
|
} finally {
|
||||||
dispatch(setIsLoadingGlobal(false))
|
dispatch(setIsLoadingGlobal(false));
|
||||||
}
|
}
|
||||||
}, [videos, hashMapVideos])
|
}, [videos, hashMapVideos]);
|
||||||
|
|
||||||
const getVideos = React.useCallback(async (filters = {}, reset?:boolean, resetFilers?: boolean,limit?: number) => {
|
const getVideos = React.useCallback(
|
||||||
try {
|
async (
|
||||||
const {name = '',
|
filters = {},
|
||||||
category = '',
|
reset?: boolean,
|
||||||
subcategory = '',
|
resetFilers?: boolean,
|
||||||
keywords = '',
|
limit?: number
|
||||||
type = '' }: any = resetFilers ? {} : filters
|
) => {
|
||||||
let offset = videos.length
|
try {
|
||||||
if(reset){
|
const {
|
||||||
offset = 0
|
name = "",
|
||||||
}
|
category = "",
|
||||||
const videoLimit = limit || 20
|
subcategory = "",
|
||||||
|
keywords = "",
|
||||||
|
type = "",
|
||||||
|
}: any = resetFilers ? {} : filters;
|
||||||
|
let offset = videos.length;
|
||||||
|
if (reset) {
|
||||||
|
offset = 0;
|
||||||
|
}
|
||||||
|
const videoLimit = limit || 20;
|
||||||
|
|
||||||
let defaultUrl = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`
|
let defaultUrl = `/arbitrary/resources/search?mode=ALL&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}&limit=${videoLimit}`;
|
||||||
|
|
||||||
|
|
||||||
if(name){
|
|
||||||
defaultUrl = defaultUrl + `&name=${name}`
|
|
||||||
}
|
|
||||||
if(category){
|
|
||||||
if(!subcategory){
|
|
||||||
defaultUrl = defaultUrl + `&description=category:${category}`
|
|
||||||
|
|
||||||
|
if (name) {
|
||||||
|
defaultUrl = defaultUrl + `&name=${name}`;
|
||||||
|
}
|
||||||
|
if (category) {
|
||||||
|
if (!subcategory) {
|
||||||
|
defaultUrl = defaultUrl + `&description=category:${category}`;
|
||||||
|
} else {
|
||||||
|
defaultUrl =
|
||||||
|
defaultUrl +
|
||||||
|
`&description=category:${category};subcategory:${subcategory}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (keywords) {
|
||||||
|
defaultUrl = defaultUrl + `&query=${keywords}`;
|
||||||
|
}
|
||||||
|
if (type === "playlists") {
|
||||||
|
defaultUrl = defaultUrl + `&service=PLAYLIST`;
|
||||||
|
defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`;
|
||||||
} else {
|
} else {
|
||||||
defaultUrl = defaultUrl + `&description=category:${category};subcategory:${subcategory}`
|
defaultUrl = defaultUrl + `&service=DOCUMENT`;
|
||||||
|
defaultUrl = defaultUrl + `&identifier=${QTUBE_VIDEO_BASE}`;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if(keywords){
|
|
||||||
defaultUrl = defaultUrl + `&query=${keywords}`
|
|
||||||
}
|
|
||||||
if(type === 'playlists'){
|
|
||||||
defaultUrl = defaultUrl + `&service=PLAYLIST`
|
|
||||||
defaultUrl = defaultUrl + `&identifier=${QTUBE_PLAYLIST_BASE}`
|
|
||||||
|
|
||||||
} else {
|
|
||||||
defaultUrl = defaultUrl + `&service=DOCUMENT`
|
|
||||||
defaultUrl = defaultUrl + `&identifier=${QTUBE_VIDEO_BASE}`
|
|
||||||
}
|
|
||||||
|
|
||||||
// const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`
|
// const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=${videoLimit}&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`
|
||||||
const url = defaultUrl
|
const url = defaultUrl;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const responseData = await response.json();
|
||||||
|
|
||||||
|
// const responseData = await qortalRequest({
|
||||||
|
// action: "SEARCH_QDN_RESOURCES",
|
||||||
|
// mode: "ALL",
|
||||||
|
// service: "DOCUMENT",
|
||||||
|
// query: "${QTUBE_VIDEO_BASE}",
|
||||||
|
// limit: 20,
|
||||||
|
// includeMetadata: true,
|
||||||
|
// offset: offset,
|
||||||
|
// reverse: true,
|
||||||
|
// excludeBlocked: true,
|
||||||
|
// exactMatchNames: true,
|
||||||
|
// name: names
|
||||||
|
// })
|
||||||
|
const structureData = responseData.map((video: any): Video => {
|
||||||
|
return {
|
||||||
|
title: video?.metadata?.title,
|
||||||
|
service: video?.service,
|
||||||
|
category: video?.metadata?.category,
|
||||||
|
categoryName: video?.metadata?.categoryName,
|
||||||
|
tags: video?.metadata?.tags || [],
|
||||||
|
description: video?.metadata?.description,
|
||||||
|
created: video?.created,
|
||||||
|
updated: video?.updated,
|
||||||
|
user: video.name,
|
||||||
|
videoImage: "",
|
||||||
|
id: video.identifier,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
if (reset) {
|
||||||
|
dispatch(addVideos(structureData));
|
||||||
|
} else {
|
||||||
|
dispatch(upsertVideos(structureData));
|
||||||
}
|
}
|
||||||
})
|
for (const content of structureData) {
|
||||||
const responseData = await response.json()
|
if (content.user && content.id) {
|
||||||
|
const res = checkAndUpdateVideo(content);
|
||||||
|
if (res) {
|
||||||
// const responseData = await qortalRequest({
|
|
||||||
// action: "SEARCH_QDN_RESOURCES",
|
|
||||||
// mode: "ALL",
|
|
||||||
// service: "DOCUMENT",
|
|
||||||
// query: "${QTUBE_VIDEO_BASE}",
|
|
||||||
// limit: 20,
|
|
||||||
// includeMetadata: true,
|
|
||||||
// offset: offset,
|
|
||||||
// reverse: true,
|
|
||||||
// excludeBlocked: true,
|
|
||||||
// exactMatchNames: true,
|
|
||||||
// name: names
|
|
||||||
// })
|
|
||||||
const structureData = responseData.map((video: any): Video => {
|
|
||||||
return {
|
|
||||||
title: video?.metadata?.title,
|
|
||||||
service: video?.service,
|
|
||||||
category: video?.metadata?.category,
|
|
||||||
categoryName: video?.metadata?.categoryName,
|
|
||||||
tags: video?.metadata?.tags || [],
|
|
||||||
description: video?.metadata?.description,
|
|
||||||
created: video?.created,
|
|
||||||
updated: video?.updated,
|
|
||||||
user: video.name,
|
|
||||||
videoImage: '',
|
|
||||||
id: video.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if(reset){
|
|
||||||
dispatch(addVideos(structureData))
|
|
||||||
|
|
||||||
} else {
|
|
||||||
dispatch(upsertVideos(structureData))
|
|
||||||
|
|
||||||
}
|
|
||||||
for (const content of structureData) {
|
|
||||||
if (content.user && content.id) {
|
|
||||||
const res = checkAndUpdateVideo(content)
|
|
||||||
if (res) {
|
|
||||||
queue.push(() => getVideo(content.user, content.id, content));
|
queue.push(() => getVideo(content.user, content.id, content));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log({ error });
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
console.log({error})
|
[videos, hashMapVideos]
|
||||||
} finally {
|
);
|
||||||
|
|
||||||
}
|
|
||||||
}, [videos, hashMapVideos])
|
|
||||||
|
|
||||||
const getVideosFiltered = React.useCallback(async (filterValue: string) => {
|
const getVideosFiltered = React.useCallback(
|
||||||
try {
|
async (filterValue: string) => {
|
||||||
const offset = filteredVideos.length
|
try {
|
||||||
const replaceSpacesWithUnderscore = filterValue.replace(/ /g, '_');
|
const offset = filteredVideos.length;
|
||||||
|
const replaceSpacesWithUnderscore = filterValue.replace(/ /g, "_");
|
||||||
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QTUBE_VIDEO_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`
|
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${replaceSpacesWithUnderscore}&identifier=${QTUBE_VIDEO_BASE}&limit=10&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true&offset=${offset}`;
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
const responseData = await response.json()
|
const responseData = await response.json();
|
||||||
|
|
||||||
// const responseData = await qortalRequest({
|
|
||||||
// action: "SEARCH_QDN_RESOURCES",
|
|
||||||
// mode: "ALL",
|
|
||||||
// service: "DOCUMENT",
|
|
||||||
// query: replaceSpacesWithUnderscore,
|
|
||||||
// identifier: "${QTUBE_VIDEO_BASE}",
|
|
||||||
// limit: 20,
|
|
||||||
// includeMetadata: true,
|
|
||||||
// offset: offset,
|
|
||||||
// reverse: true,
|
|
||||||
// excludeBlocked: true,
|
|
||||||
// exactMatchNames: true,
|
|
||||||
// name: names
|
|
||||||
// })
|
|
||||||
const structureData = responseData.map((video: any): Video => {
|
|
||||||
return {
|
|
||||||
title: video?.metadata?.title,
|
|
||||||
category: video?.metadata?.category,
|
|
||||||
categoryName: video?.metadata?.categoryName,
|
|
||||||
tags: video?.metadata?.tags || [],
|
|
||||||
description: video?.metadata?.description,
|
|
||||||
created: video?.created,
|
|
||||||
updated: video?.updated,
|
|
||||||
user: video.name,
|
|
||||||
videoImage: '',
|
|
||||||
id: video.identifier
|
|
||||||
}
|
|
||||||
})
|
|
||||||
dispatch(upsertFilteredVideos(structureData))
|
|
||||||
|
|
||||||
for (const content of structureData) {
|
// const responseData = await qortalRequest({
|
||||||
if (content.user && content.id) {
|
// action: "SEARCH_QDN_RESOURCES",
|
||||||
const res = checkAndUpdateVideo(content)
|
// mode: "ALL",
|
||||||
if (res) {
|
// service: "DOCUMENT",
|
||||||
queue.push(() => getVideo(content.user, content.id, content));
|
// query: replaceSpacesWithUnderscore,
|
||||||
|
// identifier: "${QTUBE_VIDEO_BASE}",
|
||||||
|
// limit: 20,
|
||||||
|
// includeMetadata: true,
|
||||||
|
// offset: offset,
|
||||||
|
// reverse: true,
|
||||||
|
// excludeBlocked: true,
|
||||||
|
// exactMatchNames: true,
|
||||||
|
// name: names
|
||||||
|
// })
|
||||||
|
const structureData = responseData.map((video: any): Video => {
|
||||||
|
return {
|
||||||
|
title: video?.metadata?.title,
|
||||||
|
category: video?.metadata?.category,
|
||||||
|
categoryName: video?.metadata?.categoryName,
|
||||||
|
tags: video?.metadata?.tags || [],
|
||||||
|
description: video?.metadata?.description,
|
||||||
|
created: video?.created,
|
||||||
|
updated: video?.updated,
|
||||||
|
user: video.name,
|
||||||
|
videoImage: "",
|
||||||
|
id: video.identifier,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
dispatch(upsertFilteredVideos(structureData));
|
||||||
|
|
||||||
|
for (const content of structureData) {
|
||||||
|
if (content.user && content.id) {
|
||||||
|
const res = checkAndUpdateVideo(content);
|
||||||
|
if (res) {
|
||||||
|
queue.push(() => getVideo(content.user, content.id, content));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
} finally {
|
||||||
}
|
}
|
||||||
} catch (error) {
|
},
|
||||||
} finally {
|
[filteredVideos, hashMapVideos]
|
||||||
|
);
|
||||||
}
|
|
||||||
}, [filteredVideos, hashMapVideos])
|
|
||||||
|
|
||||||
const checkNewVideos = React.useCallback(async () => {
|
const checkNewVideos = React.useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
|
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`;
|
||||||
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=DOCUMENT&query=${QTUBE_VIDEO_BASE}&limit=20&includemetadata=false&reverse=true&excludeblocked=true&exactmatchnames=true`
|
|
||||||
const response = await fetch(url, {
|
const response = await fetch(url, {
|
||||||
method: 'GET',
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
"Content-Type": "application/json",
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
const responseData = await response.json()
|
const responseData = await response.json();
|
||||||
// const responseData = await qortalRequest({
|
// const responseData = await qortalRequest({
|
||||||
// action: "SEARCH_QDN_RESOURCES",
|
// action: "SEARCH_QDN_RESOURCES",
|
||||||
// mode: "ALL",
|
// mode: "ALL",
|
||||||
@ -349,21 +357,20 @@ export const useFetchVideos = () => {
|
|||||||
// exactMatchNames: true,
|
// exactMatchNames: true,
|
||||||
// name: names
|
// name: names
|
||||||
// })
|
// })
|
||||||
const latestVideo = videos[0]
|
const latestVideo = videos[0];
|
||||||
if (!latestVideo) return
|
if (!latestVideo) return;
|
||||||
const findVideo = responseData?.findIndex(
|
const findVideo = responseData?.findIndex(
|
||||||
(item: any) => item?.identifier === latestVideo?.id
|
(item: any) => item?.identifier === latestVideo?.id
|
||||||
)
|
);
|
||||||
if (findVideo === -1) {
|
if (findVideo === -1) {
|
||||||
dispatch(setCountNewVideos(responseData.length))
|
dispatch(setCountNewVideos(responseData.length));
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const newArray = responseData.slice(0, findVideo)
|
const newArray = responseData.slice(0, findVideo);
|
||||||
dispatch(setCountNewVideos(newArray.length))
|
dispatch(setCountNewVideos(newArray.length));
|
||||||
return
|
return;
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
}, [videos])
|
}, [videos]);
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getVideos,
|
getVideos,
|
||||||
@ -372,6 +379,6 @@ export const useFetchVideos = () => {
|
|||||||
hashMapVideos,
|
hashMapVideos,
|
||||||
getNewVideos,
|
getNewVideos,
|
||||||
checkNewVideos,
|
checkNewVideos,
|
||||||
getVideosFiltered
|
getVideosFiltered,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
@ -58,11 +58,11 @@ import {
|
|||||||
setEditPlaylist,
|
setEditPlaylist,
|
||||||
setEditVideo,
|
setEditVideo,
|
||||||
} from "../../state/features/videoSlice";
|
} from "../../state/features/videoSlice";
|
||||||
import { categories, subCategories } from "../../constants";
|
import { categories, subCategories } from "../../constants/Categories.ts";
|
||||||
import { Playlists } from "../../components/Playlists/Playlists";
|
import { Playlists } from "../../components/Playlists/Playlists";
|
||||||
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG";
|
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG";
|
||||||
import BlockIcon from "@mui/icons-material/Block";
|
import BlockIcon from "@mui/icons-material/Block";
|
||||||
import EditIcon from '@mui/icons-material/Edit';
|
import EditIcon from "@mui/icons-material/Edit";
|
||||||
import { LiskSuperLikeContainer } from "../../components/common/ListSuperLikes/LiskSuperLikeContainer";
|
import { LiskSuperLikeContainer } from "../../components/common/ListSuperLikes/LiskSuperLikeContainer";
|
||||||
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
||||||
|
|
||||||
@ -83,19 +83,19 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
|
|
||||||
const filterType = useSelector((state: RootState) => state.video.filterType);
|
const filterType = useSelector((state: RootState) => state.video.filterType);
|
||||||
|
|
||||||
const setFilterType = (payload) => {
|
const setFilterType = payload => {
|
||||||
dispatch(changeFilterType(payload));
|
dispatch(changeFilterType(payload));
|
||||||
};
|
};
|
||||||
const filterSearch = useSelector(
|
const filterSearch = useSelector(
|
||||||
(state: RootState) => state.video.filterSearch
|
(state: RootState) => state.video.filterSearch
|
||||||
);
|
);
|
||||||
|
|
||||||
const setFilterSearch = (payload) => {
|
const setFilterSearch = payload => {
|
||||||
dispatch(changefilterSearch(payload));
|
dispatch(changefilterSearch(payload));
|
||||||
};
|
};
|
||||||
const filterName = useSelector((state: RootState) => state.video.filterName);
|
const filterName = useSelector((state: RootState) => state.video.filterName);
|
||||||
|
|
||||||
const setFilterName = (payload) => {
|
const setFilterName = payload => {
|
||||||
dispatch(changefilterName(payload));
|
dispatch(changefilterName(payload));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -103,14 +103,14 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
(state: RootState) => state.video.selectedCategoryVideos
|
(state: RootState) => state.video.selectedCategoryVideos
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSelectedCategoryVideos = (payload) => {
|
const setSelectedCategoryVideos = payload => {
|
||||||
dispatch(changeSelectedCategoryVideos(payload));
|
dispatch(changeSelectedCategoryVideos(payload));
|
||||||
};
|
};
|
||||||
const selectedSubCategoryVideos = useSelector(
|
const selectedSubCategoryVideos = useSelector(
|
||||||
(state: RootState) => state.video.selectedSubCategoryVideos
|
(state: RootState) => state.video.selectedSubCategoryVideos
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSelectedSubCategoryVideos = (payload) => {
|
const setSelectedSubCategoryVideos = payload => {
|
||||||
dispatch(changeSelectedSubCategoryVideos(payload));
|
dispatch(changeSelectedSubCategoryVideos(payload));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -144,8 +144,6 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
|
|
||||||
const getVideosHandler = React.useCallback(
|
const getVideosHandler = React.useCallback(
|
||||||
async (reset?: boolean, resetFilers?: boolean) => {
|
async (reset?: boolean, resetFilers?: boolean) => {
|
||||||
|
|
||||||
|
|
||||||
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;
|
||||||
@ -259,7 +257,7 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
event: SelectChangeEvent<string>
|
event: SelectChangeEvent<string>
|
||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = categories.find((option) => option.id === +optionId);
|
const selectedOption = categories.find(option => option.id === +optionId);
|
||||||
setSelectedCategoryVideos(selectedOption || null);
|
setSelectedCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
const handleOptionSubCategoryChangeVideos = (
|
const handleOptionSubCategoryChangeVideos = (
|
||||||
@ -268,7 +266,7 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
) => {
|
) => {
|
||||||
const optionId = event.target.value;
|
const optionId = event.target.value;
|
||||||
const selectedOption = subcategories.find(
|
const selectedOption = subcategories.find(
|
||||||
(option) => option.id === +optionId
|
option => option.id === +optionId
|
||||||
);
|
);
|
||||||
setSelectedSubCategoryVideos(selectedOption || null);
|
setSelectedSubCategoryVideos(selectedOption || null);
|
||||||
};
|
};
|
||||||
@ -284,24 +282,24 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (response === true) {
|
if (response === true) {
|
||||||
dispatch(blockUser(user))
|
dispatch(blockUser(user));
|
||||||
}
|
}
|
||||||
} catch (error) {}
|
} catch (error) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleInputKeyDown = (event: any) => {
|
const handleInputKeyDown = (event: any) => {
|
||||||
if (event.key === 'Enter') {
|
if (event.key === "Enter") {
|
||||||
getVideosHandler(true);
|
getVideosHandler(true);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container sx={{ width: "100%" }}>
|
<Grid container sx={{ width: "100%" }}>
|
||||||
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3} >
|
<FiltersCol item xs={12} md={2} lg={2} xl={2} sm={3}>
|
||||||
<FiltersContainer>
|
<FiltersContainer>
|
||||||
<Input
|
<Input
|
||||||
id="standard-adornment-name"
|
id="standard-adornment-name"
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
setFilterSearch(e.target.value);
|
setFilterSearch(e.target.value);
|
||||||
}}
|
}}
|
||||||
value={filterSearch}
|
value={filterSearch}
|
||||||
@ -329,11 +327,11 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
/>
|
/>
|
||||||
<Input
|
<Input
|
||||||
id="standard-adornment-name"
|
id="standard-adornment-name"
|
||||||
onChange={(e) => {
|
onChange={e => {
|
||||||
setFilterName(e.target.value);
|
setFilterName(e.target.value);
|
||||||
}}
|
}}
|
||||||
value={filterName}
|
value={filterName}
|
||||||
placeholder="User's name"
|
placeholder="User's Name (Exact)"
|
||||||
onKeyDown={handleInputKeyDown}
|
onKeyDown={handleInputKeyDown}
|
||||||
sx={{
|
sx={{
|
||||||
marginTop: "20px",
|
marginTop: "20px",
|
||||||
@ -406,7 +404,7 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{categories.map((option) => (
|
{categories.map(option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -428,7 +426,7 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
labelId="Sub-Category"
|
labelId="Sub-Category"
|
||||||
input={<OutlinedInput label="Sub-Category" />}
|
input={<OutlinedInput label="Sub-Category" />}
|
||||||
value={selectedSubCategoryVideos?.id || ""}
|
value={selectedSubCategoryVideos?.id || ""}
|
||||||
onChange={(e) =>
|
onChange={e =>
|
||||||
handleOptionSubCategoryChangeVideos(
|
handleOptionSubCategoryChangeVideos(
|
||||||
e,
|
e,
|
||||||
subCategories[selectedCategoryVideos?.id]
|
subCategories[selectedCategoryVideos?.id]
|
||||||
@ -453,7 +451,7 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{subCategories[selectedCategoryVideos.id].map(
|
{subCategories[selectedCategoryVideos.id].map(
|
||||||
(option) => (
|
option => (
|
||||||
<MenuItem key={option.id} value={option.id}>
|
<MenuItem key={option.id} value={option.id}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@ -521,73 +519,171 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
</FiltersCol>
|
</FiltersCol>
|
||||||
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
|
<Grid item xs={12} md={10} lg={7} xl={8} sm={9}>
|
||||||
<ProductManagerRow>
|
<ProductManagerRow>
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
|
||||||
width: "100%",
|
|
||||||
display: "flex",
|
|
||||||
flexDirection: "column",
|
|
||||||
alignItems: "center",
|
|
||||||
marginTop: "20px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<SubtitleContainer
|
|
||||||
sx={{
|
sx={{
|
||||||
justifyContent: "flex-start",
|
|
||||||
paddingLeft: "15px",
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
maxWidth: "1400px",
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
alignItems: "center",
|
||||||
|
marginTop: "20px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<SubtitleContainer
|
||||||
</SubtitleContainer>
|
sx={{
|
||||||
|
justifyContent: "flex-start",
|
||||||
<VideoCardContainer >
|
paddingLeft: "15px",
|
||||||
{videos.map((video: any, index: number) => {
|
width: "100%",
|
||||||
const existingVideo = hashMapVideos[video?.id];
|
maxWidth: "1400px",
|
||||||
let hasHash = false;
|
}}
|
||||||
let videoObj = video;
|
></SubtitleContainer>
|
||||||
if (existingVideo) {
|
|
||||||
videoObj = existingVideo;
|
|
||||||
hasHash = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let avatarUrl = "";
|
<VideoCardContainer>
|
||||||
if (userAvatarHash[videoObj?.user]) {
|
{videos.map((video: any, index: number) => {
|
||||||
avatarUrl = userAvatarHash[videoObj?.user];
|
const existingVideo = hashMapVideos[video?.id];
|
||||||
}
|
let hasHash = false;
|
||||||
|
let videoObj = video;
|
||||||
|
if (existingVideo) {
|
||||||
|
videoObj = existingVideo;
|
||||||
|
hasHash = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (hasHash && !videoObj?.videoImage && !videoObj?.image) {
|
let avatarUrl = "";
|
||||||
return null;
|
if (userAvatarHash[videoObj?.user]) {
|
||||||
}
|
avatarUrl = userAvatarHash[videoObj?.user];
|
||||||
const isPlaylist = videoObj?.service === "PLAYLIST";
|
}
|
||||||
|
|
||||||
|
if (hasHash && !videoObj?.videoImage && !videoObj?.image) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const isPlaylist = videoObj?.service === "PLAYLIST";
|
||||||
|
|
||||||
|
if (isPlaylist) {
|
||||||
|
return (
|
||||||
|
<VideoCardCol
|
||||||
|
onMouseEnter={() => setShowIcons(videoObj.id)}
|
||||||
|
onMouseLeave={() => setShowIcons(null)}
|
||||||
|
key={videoObj.id}
|
||||||
|
>
|
||||||
|
<IconsBox
|
||||||
|
sx={{
|
||||||
|
opacity: showIcons === videoObj.id ? 1 : 0,
|
||||||
|
zIndex: 2,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{videoObj?.user === username && (
|
||||||
|
<Tooltip title="Edit playlist" placement="top">
|
||||||
|
<BlockIconContainer>
|
||||||
|
<EditIcon
|
||||||
|
onClick={() => {
|
||||||
|
dispatch(setEditPlaylist(videoObj));
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Tooltip title="Block user content" placement="top">
|
||||||
|
<BlockIconContainer>
|
||||||
|
<BlockIcon
|
||||||
|
onClick={() => {
|
||||||
|
blockUserFunc(videoObj?.user);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</BlockIconContainer>
|
||||||
|
</Tooltip>
|
||||||
|
</IconsBox>
|
||||||
|
<VideoCard
|
||||||
|
sx={{
|
||||||
|
cursor: !hasHash && "default",
|
||||||
|
}}
|
||||||
|
onClick={() => {
|
||||||
|
if (!hasHash) return;
|
||||||
|
navigate(
|
||||||
|
`/playlist/${videoObj?.user}/${videoObj?.id}`
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ResponsiveImage
|
||||||
|
src={videoObj?.image}
|
||||||
|
width={266}
|
||||||
|
height={150}
|
||||||
|
style={{
|
||||||
|
maxHeight: "50%",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
||||||
|
<BottomParent>
|
||||||
|
<NameContainer
|
||||||
|
onClick={e => {
|
||||||
|
e.stopPropagation();
|
||||||
|
navigate(`/channel/${videoObj?.user}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar
|
||||||
|
sx={{ height: 24, width: 24 }}
|
||||||
|
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
||||||
|
alt={`${videoObj?.user}'s avatar`}
|
||||||
|
/>
|
||||||
|
<VideoCardName
|
||||||
|
sx={{
|
||||||
|
":hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{videoObj?.user}
|
||||||
|
</VideoCardName>
|
||||||
|
</NameContainer>
|
||||||
|
|
||||||
|
{videoObj?.created && (
|
||||||
|
<VideoUploadDate>
|
||||||
|
{formatDate(videoObj.created)}
|
||||||
|
</VideoUploadDate>
|
||||||
|
)}
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
position: "absolute",
|
||||||
|
bottom: "5px",
|
||||||
|
right: "5px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlaylistSVG
|
||||||
|
color={theme.palette.text.primary}
|
||||||
|
height="36px"
|
||||||
|
width="36px"
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</BottomParent>
|
||||||
|
</VideoCard>
|
||||||
|
</VideoCardCol>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (isPlaylist) {
|
|
||||||
return (
|
return (
|
||||||
<VideoCardCol
|
<VideoCardCol
|
||||||
|
key={videoObj.id}
|
||||||
onMouseEnter={() => setShowIcons(videoObj.id)}
|
onMouseEnter={() => setShowIcons(videoObj.id)}
|
||||||
onMouseLeave={() => setShowIcons(null)}
|
onMouseLeave={() => setShowIcons(null)}
|
||||||
key={videoObj.id}
|
|
||||||
>
|
>
|
||||||
|
|
||||||
<IconsBox
|
<IconsBox
|
||||||
sx={{
|
sx={{
|
||||||
opacity: showIcons === videoObj.id ? 1 : 0,
|
opacity: showIcons === videoObj.id ? 1 : 0,
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoObj?.user === username && (
|
{videoObj?.user === username && (
|
||||||
<Tooltip title="Edit playlist" placement="top">
|
<Tooltip title="Edit video properties" placement="top">
|
||||||
<BlockIconContainer>
|
<BlockIconContainer>
|
||||||
<EditIcon
|
<EditIcon
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
dispatch(setEditPlaylist(videoObj));
|
dispatch(setEditVideo(videoObj));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</BlockIconContainer>
|
</BlockIconContainer>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip title="Block user content" placement="top">
|
<Tooltip title="Block user content" placement="top">
|
||||||
<BlockIconContainer>
|
<BlockIconContainer>
|
||||||
<BlockIcon
|
<BlockIcon
|
||||||
@ -599,28 +695,25 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</IconsBox>
|
</IconsBox>
|
||||||
<VideoCard
|
<VideoCard
|
||||||
sx={{
|
|
||||||
cursor: !hasHash && 'default'
|
|
||||||
}}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if(!hasHash) return
|
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
||||||
navigate(
|
|
||||||
`/playlist/${videoObj?.user}/${videoObj?.id}`
|
|
||||||
);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ResponsiveImage
|
<VideoCardImageContainer
|
||||||
src={videoObj?.image}
|
|
||||||
width={266}
|
width={266}
|
||||||
height={150}
|
height={150}
|
||||||
style={{
|
videoImage={videoObj.videoImage}
|
||||||
maxHeight: '50%'
|
frameImages={videoObj?.extracts || []}
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
<VideoCardTitle>{videoObj?.title}</VideoCardTitle>
|
{/* <ResponsiveImage
|
||||||
|
src={videoObj.videoImage}
|
||||||
|
width={266}
|
||||||
|
height={150}
|
||||||
|
/> */}
|
||||||
|
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
|
||||||
<BottomParent>
|
<BottomParent>
|
||||||
<NameContainer
|
<NameContainer
|
||||||
onClick={(e) => {
|
onClick={e => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
navigate(`/channel/${videoObj?.user}`);
|
navigate(`/channel/${videoObj?.user}`);
|
||||||
}}
|
}}
|
||||||
@ -646,115 +739,18 @@ export const VideoList = ({ mode }: VideoListProps) => {
|
|||||||
{formatDate(videoObj.created)}
|
{formatDate(videoObj.created)}
|
||||||
</VideoUploadDate>
|
</VideoUploadDate>
|
||||||
)}
|
)}
|
||||||
<Box
|
|
||||||
sx={{
|
|
||||||
display: "flex",
|
|
||||||
position: "absolute",
|
|
||||||
bottom: "5px",
|
|
||||||
right: "5px",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<PlaylistSVG
|
|
||||||
color={theme.palette.text.primary}
|
|
||||||
height="36px"
|
|
||||||
width="36px"
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</BottomParent>
|
</BottomParent>
|
||||||
</VideoCard>
|
</VideoCard>
|
||||||
</VideoCardCol>
|
</VideoCardCol>
|
||||||
);
|
);
|
||||||
}
|
})}
|
||||||
|
</VideoCardContainer>
|
||||||
|
|
||||||
return (
|
<LazyLoad
|
||||||
<VideoCardCol
|
onLoadMore={getVideosHandler}
|
||||||
|
isLoading={isLoading}
|
||||||
key={videoObj.id}
|
></LazyLoad>
|
||||||
onMouseEnter={() => setShowIcons(videoObj.id)}
|
</Box>
|
||||||
onMouseLeave={() => setShowIcons(null)}
|
|
||||||
>
|
|
||||||
<IconsBox
|
|
||||||
sx={{
|
|
||||||
opacity: showIcons === videoObj.id ? 1 : 0,
|
|
||||||
zIndex: 2,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{videoObj?.user === username && (
|
|
||||||
<Tooltip title="Edit video properties" placement="top">
|
|
||||||
<BlockIconContainer>
|
|
||||||
<EditIcon
|
|
||||||
onClick={() => {
|
|
||||||
dispatch(setEditVideo(videoObj));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</BlockIconContainer>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Tooltip title="Block user content" placement="top">
|
|
||||||
<BlockIconContainer>
|
|
||||||
<BlockIcon
|
|
||||||
onClick={() => {
|
|
||||||
blockUserFunc(videoObj?.user);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</BlockIconContainer>
|
|
||||||
</Tooltip>
|
|
||||||
</IconsBox>
|
|
||||||
<VideoCard
|
|
||||||
onClick={() => {
|
|
||||||
navigate(`/video/${videoObj?.user}/${videoObj?.id}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<VideoCardImageContainer width={266}
|
|
||||||
height={150} videoImage={videoObj.videoImage} frameImages={videoObj?.extracts || []} />
|
|
||||||
{/* <ResponsiveImage
|
|
||||||
src={videoObj.videoImage}
|
|
||||||
width={266}
|
|
||||||
height={150}
|
|
||||||
/> */}
|
|
||||||
<VideoCardTitle>{videoObj.title}</VideoCardTitle>
|
|
||||||
<BottomParent>
|
|
||||||
<NameContainer
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
navigate(`/channel/${videoObj?.user}`);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Avatar
|
|
||||||
sx={{ height: 24, width: 24 }}
|
|
||||||
src={`/arbitrary/THUMBNAIL/${videoObj?.user}/qortal_avatar`}
|
|
||||||
alt={`${videoObj?.user}'s avatar`}
|
|
||||||
/>
|
|
||||||
<VideoCardName
|
|
||||||
sx={{
|
|
||||||
":hover": {
|
|
||||||
textDecoration: "underline",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{videoObj?.user}
|
|
||||||
</VideoCardName>
|
|
||||||
</NameContainer>
|
|
||||||
|
|
||||||
{videoObj?.created && (
|
|
||||||
<VideoUploadDate>
|
|
||||||
{formatDate(videoObj.created)}
|
|
||||||
</VideoUploadDate>
|
|
||||||
)}
|
|
||||||
</BottomParent>
|
|
||||||
</VideoCard>
|
|
||||||
</VideoCardCol>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</VideoCardContainer>
|
|
||||||
|
|
||||||
<LazyLoad
|
|
||||||
onLoadMore={getVideosHandler}
|
|
||||||
isLoading={isLoading}
|
|
||||||
></LazyLoad>
|
|
||||||
</Box>
|
|
||||||
</ProductManagerRow>
|
</ProductManagerRow>
|
||||||
</Grid>
|
</Grid>
|
||||||
<FiltersCol item xs={0} lg={3} xl={2}>
|
<FiltersCol item xs={0} lg={3} xl={2}>
|
||||||
|
@ -21,8 +21,8 @@ import ResponsiveImage from "../../components/ResponsiveImage";
|
|||||||
import { formatDate, formatTimestampSeconds } from "../../utils/time";
|
import { formatDate, formatTimestampSeconds } from "../../utils/time";
|
||||||
import { Video } from "../../state/features/videoSlice";
|
import { Video } from "../../state/features/videoSlice";
|
||||||
import { queue } from "../../wrappers/GlobalWrapper";
|
import { queue } from "../../wrappers/GlobalWrapper";
|
||||||
import { QTUBE_VIDEO_BASE } from "../../constants";
|
|
||||||
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
import { VideoCardImageContainer } from "./VideoCardImageContainer";
|
||||||
|
import { QTUBE_VIDEO_BASE } from "../../constants/Identifiers.ts";
|
||||||
|
|
||||||
interface VideoListProps {
|
interface VideoListProps {
|
||||||
mode?: string;
|
mode?: string;
|
||||||
@ -80,7 +80,7 @@ export const VideoListComponentLevel = ({ mode }: VideoListProps) => {
|
|||||||
|
|
||||||
const copiedVideos: Video[] = [...videos];
|
const copiedVideos: Video[] = [...videos];
|
||||||
structureData.forEach((video: Video) => {
|
structureData.forEach((video: Video) => {
|
||||||
const index = videos.findIndex((p) => p.id === video.id);
|
const index = videos.findIndex(p => p.id === video.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
copiedVideos[index] = video;
|
copiedVideos[index] = video;
|
||||||
} else {
|
} else {
|
||||||
|
@ -38,12 +38,7 @@ import { CommentSection } from "../../components/common/Comments/CommentSection"
|
|||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../../components/UploadVideo/Upload-styles";
|
} from "../../components/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import {
|
|
||||||
QTUBE_VIDEO_BASE,
|
|
||||||
SUPER_LIKE_BASE,
|
|
||||||
minPriceSuperlike,
|
|
||||||
} from "../../constants";
|
|
||||||
import { Playlists } from "../../components/Playlists/Playlists";
|
import { Playlists } from "../../components/Playlists/Playlists";
|
||||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
||||||
import FileElement from "../../components/common/FileElement";
|
import FileElement from "../../components/common/FileElement";
|
||||||
@ -55,6 +50,11 @@ import {
|
|||||||
isTimestampWithinRange,
|
isTimestampWithinRange,
|
||||||
} from "../VideoContent/VideoContent";
|
} from "../VideoContent/VideoContent";
|
||||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
||||||
|
import {
|
||||||
|
QTUBE_VIDEO_BASE,
|
||||||
|
SUPER_LIKE_BASE,
|
||||||
|
} from "../../constants/Identifiers.ts";
|
||||||
|
import { minPriceSuperlike } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
export const PlaylistContent = () => {
|
export const PlaylistContent = () => {
|
||||||
const { name, id } = useParams();
|
const { name, id } = useParams();
|
||||||
@ -82,7 +82,7 @@ export const PlaylistContent = () => {
|
|||||||
|
|
||||||
const [nameAddress, setNameAddress] = useState<string>("");
|
const [nameAddress, setNameAddress] = useState<string>("");
|
||||||
|
|
||||||
const getAddressName = async (name) => {
|
const getAddressName = async name => {
|
||||||
const response = await qortalRequest({
|
const response = await qortalRequest({
|
||||||
action: "GET_NAME_DATA",
|
action: "GET_NAME_DATA",
|
||||||
name: name,
|
name: name,
|
||||||
@ -297,7 +297,7 @@ export const PlaylistContent = () => {
|
|||||||
|
|
||||||
const nextVideo = useMemo(() => {
|
const nextVideo = useMemo(() => {
|
||||||
const currentVideoIndex = playlistData?.videos?.findIndex(
|
const currentVideoIndex = playlistData?.videos?.findIndex(
|
||||||
(item) => item?.identifier === videoData?.id
|
item => item?.identifier === videoData?.id
|
||||||
);
|
);
|
||||||
if (currentVideoIndex !== -1) {
|
if (currentVideoIndex !== -1) {
|
||||||
const nextVideoIndex = currentVideoIndex + 1;
|
const nextVideoIndex = currentVideoIndex + 1;
|
||||||
@ -318,7 +318,7 @@ export const PlaylistContent = () => {
|
|||||||
|
|
||||||
const onEndVideo = useCallback(() => {
|
const onEndVideo = useCallback(() => {
|
||||||
const currentVideoIndex = playlistData?.videos?.findIndex(
|
const currentVideoIndex = playlistData?.videos?.findIndex(
|
||||||
(item) => item?.identifier === videoData?.id
|
item => item?.identifier === videoData?.id
|
||||||
);
|
);
|
||||||
if (currentVideoIndex !== -1) {
|
if (currentVideoIndex !== -1) {
|
||||||
const nextVideoIndex = currentVideoIndex + 1;
|
const nextVideoIndex = currentVideoIndex + 1;
|
||||||
@ -490,8 +490,8 @@ export const PlaylistContent = () => {
|
|||||||
name={videoData?.user}
|
name={videoData?.user}
|
||||||
service={videoData?.service}
|
service={videoData?.service}
|
||||||
identifier={videoData?.id}
|
identifier={videoData?.id}
|
||||||
onSuccess={(val) => {
|
onSuccess={val => {
|
||||||
setSuperlikelist((prev) => [val, ...prev]);
|
setSuperlikelist(prev => [val, ...prev]);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -554,16 +554,16 @@ export const PlaylistContent = () => {
|
|||||||
cursor: !descriptionHeight
|
cursor: !descriptionHeight
|
||||||
? "default"
|
? "default"
|
||||||
: isExpandedDescription
|
: isExpandedDescription
|
||||||
? "default"
|
? "default"
|
||||||
: "pointer",
|
: "pointer",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
className={
|
className={
|
||||||
!descriptionHeight
|
!descriptionHeight
|
||||||
? ""
|
? ""
|
||||||
: isExpandedDescription
|
: isExpandedDescription
|
||||||
? ""
|
? ""
|
||||||
: "hover-click"
|
: "hover-click"
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{descriptionHeight && !isExpandedDescription && (
|
{descriptionHeight && !isExpandedDescription && (
|
||||||
@ -588,8 +588,8 @@ export const PlaylistContent = () => {
|
|||||||
height: !descriptionHeight
|
height: !descriptionHeight
|
||||||
? "auto"
|
? "auto"
|
||||||
: isExpandedDescription
|
: isExpandedDescription
|
||||||
? "auto"
|
? "auto"
|
||||||
: "100px",
|
: "100px",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -610,7 +610,7 @@ export const PlaylistContent = () => {
|
|||||||
{descriptionHeight && (
|
{descriptionHeight && (
|
||||||
<Typography
|
<Typography
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsExpandedDescription((prev) => !prev);
|
setIsExpandedDescription(prev => !prev);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
|
@ -1,4 +1,10 @@
|
|||||||
import React, { useState, useMemo, useRef, useEffect, useCallback } from "react";
|
import React, {
|
||||||
|
useState,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
} 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";
|
||||||
@ -32,8 +38,7 @@ import { CommentSection } from "../../components/common/Comments/CommentSection"
|
|||||||
import {
|
import {
|
||||||
CrowdfundSubTitle,
|
CrowdfundSubTitle,
|
||||||
CrowdfundSubTitleRow,
|
CrowdfundSubTitleRow,
|
||||||
} from "../../components/UploadVideo/Upload-styles";
|
} from "../../components/PublishVideo/PublishVideo-styles.tsx";
|
||||||
import { FOR_SUPER_LIKE, QTUBE_VIDEO_BASE, SUPER_LIKE_BASE, minPriceSuperlike } from "../../constants";
|
|
||||||
import { Playlists } from "../../components/Playlists/Playlists";
|
import { Playlists } from "../../components/Playlists/Playlists";
|
||||||
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
import { DisplayHtml } from "../../components/common/TextEditor/DisplayHtml";
|
||||||
import FileElement from "../../components/common/FileElement";
|
import FileElement from "../../components/common/FileElement";
|
||||||
@ -42,6 +47,12 @@ import { CommentContainer } from "../../components/common/Comments/Comments-styl
|
|||||||
import { Comment } from "../../components/common/Comments/Comment";
|
import { Comment } from "../../components/common/Comments/Comment";
|
||||||
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
import { SuperLikesSection } from "../../components/common/SuperLikesList/SuperLikesSection";
|
||||||
import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes";
|
import { useFetchSuperLikes } from "../../hooks/useFetchSuperLikes";
|
||||||
|
import {
|
||||||
|
FOR_SUPER_LIKE,
|
||||||
|
QTUBE_VIDEO_BASE,
|
||||||
|
SUPER_LIKE_BASE,
|
||||||
|
} from "../../constants/Identifiers.ts";
|
||||||
|
import { minPriceSuperlike } from "../../constants/Misc.ts";
|
||||||
|
|
||||||
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
export function isTimestampWithinRange(resTimestamp, resCreated) {
|
||||||
// Calculate the absolute difference in milliseconds
|
// Calculate the absolute difference in milliseconds
|
||||||
@ -57,25 +68,25 @@ export function isTimestampWithinRange(resTimestamp, resCreated) {
|
|||||||
export function extractSigValue(metadescription) {
|
export function extractSigValue(metadescription) {
|
||||||
// Function to extract the substring within double asterisks
|
// Function to extract the substring within double asterisks
|
||||||
function extractSubstring(str) {
|
function extractSubstring(str) {
|
||||||
const match = str.match(/\*\*(.*?)\*\*/);
|
const match = str.match(/\*\*(.*?)\*\*/);
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function to extract the 'sig' value
|
// Function to extract the 'sig' value
|
||||||
function extractSig(str) {
|
function extractSig(str) {
|
||||||
const regex = /sig:(.*?)(;|$)/;
|
const regex = /sig:(.*?)(;|$)/;
|
||||||
const match = str.match(regex);
|
const match = str.match(regex);
|
||||||
return match ? match[1] : null;
|
return match ? match[1] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extracting the relevant substring
|
// Extracting the relevant substring
|
||||||
const relevantSubstring = extractSubstring(metadescription);
|
const relevantSubstring = extractSubstring(metadescription);
|
||||||
|
|
||||||
if (relevantSubstring) {
|
if (relevantSubstring) {
|
||||||
// Extracting the 'sig' value
|
// Extracting the 'sig' value
|
||||||
return extractSig(relevantSubstring);
|
return extractSig(relevantSubstring);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,63 +102,61 @@ export const getPaymentInfo = async (signature: string) => {
|
|||||||
// Coin payment info must be added to responseData so we can display it to the user
|
// Coin payment info must be added to responseData so we can display it to the user
|
||||||
const responseData = await response.json();
|
const responseData = await response.json();
|
||||||
if (responseData && !responseData.error) {
|
if (responseData && !responseData.error) {
|
||||||
return responseData;
|
return responseData;
|
||||||
} else {
|
} else {
|
||||||
throw new Error('unable to get payment')
|
throw new Error("unable to get payment");
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('unable to get payment')
|
throw new Error("unable to get payment");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const VideoContent = () => {
|
export const VideoContent = () => {
|
||||||
const { name, id } = useParams();
|
const { name, id } = useParams();
|
||||||
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 calculateAmountSuperlike = useMemo(() => {
|
||||||
|
const totalQort = superlikeList?.reduce((acc, curr) => {
|
||||||
|
if (curr?.amount && !isNaN(parseFloat(curr.amount)))
|
||||||
|
return acc + parseFloat(curr.amount);
|
||||||
|
else return acc;
|
||||||
|
}, 0);
|
||||||
|
return totalQort?.toFixed(2);
|
||||||
|
}, [superlikeList]);
|
||||||
|
const numberOfSuperlikes = useMemo(() => {
|
||||||
|
return superlikeList?.length ?? 0;
|
||||||
|
}, [superlikeList]);
|
||||||
|
|
||||||
|
const [nameAddress, setNameAddress] = useState<string>("");
|
||||||
|
const [descriptionHeight, setDescriptionHeight] = useState<null | number>(
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
const calculateAmountSuperlike = useMemo(()=> {
|
|
||||||
const totalQort = superlikeList?.reduce((acc, curr)=> {
|
|
||||||
if(curr?.amount && !isNaN(parseFloat(curr.amount))) return acc + parseFloat(curr.amount)
|
|
||||||
else return acc
|
|
||||||
}, 0)
|
|
||||||
return totalQort?.toFixed(2)
|
|
||||||
}, [superlikeList])
|
|
||||||
const numberOfSuperlikes = useMemo(()=> {
|
|
||||||
|
|
||||||
return superlikeList?.length ?? 0
|
|
||||||
}, [superlikeList])
|
|
||||||
|
|
||||||
const [nameAddress, setNameAddress] = useState<string>('')
|
|
||||||
const [descriptionHeight, setDescriptionHeight] =
|
|
||||||
useState<null | number>(null);
|
|
||||||
|
|
||||||
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 => {
|
||||||
const response = await qortalRequest({
|
const response = await qortalRequest({
|
||||||
action: "GET_NAME_DATA",
|
action: "GET_NAME_DATA",
|
||||||
name: name
|
name: name,
|
||||||
});
|
});
|
||||||
|
|
||||||
if(response?.owner){
|
if (response?.owner) {
|
||||||
setNameAddress(response.owner)
|
setNameAddress(response.owner);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
useEffect(()=> {
|
useEffect(() => {
|
||||||
if(name){
|
if (name) {
|
||||||
|
getAddressName(name);
|
||||||
|
|
||||||
getAddressName(name)
|
|
||||||
}
|
}
|
||||||
}, [name])
|
}, [name]);
|
||||||
const avatarUrl = useMemo(() => {
|
const avatarUrl = useMemo(() => {
|
||||||
let url = "";
|
let url = "";
|
||||||
if (name && userAvatarHash[name]) {
|
if (name && userAvatarHash[name]) {
|
||||||
@ -237,7 +246,6 @@ export const VideoContent = () => {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (name && id) {
|
if (name && id) {
|
||||||
const existingVideo = hashMapVideos[id];
|
const existingVideo = hashMapVideos[id];
|
||||||
@ -250,84 +258,80 @@ export const VideoContent = () => {
|
|||||||
}
|
}
|
||||||
}, [id, name]);
|
}, [id, name]);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contentRef.current) {
|
if (contentRef.current) {
|
||||||
const height = contentRef.current.offsetHeight;
|
const height = contentRef.current.offsetHeight;
|
||||||
if (height > 100) { // Assuming 100px is your threshold
|
if (height > 100) {
|
||||||
setDescriptionHeight(100)
|
// Assuming 100px is your threshold
|
||||||
|
setDescriptionHeight(100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [videoData]);
|
}, [videoData]);
|
||||||
|
|
||||||
|
const getComments = useCallback(async (id, nameAddressParam) => {
|
||||||
const getComments = useCallback(
|
if (!id) return;
|
||||||
async (id, nameAddressParam) => {
|
try {
|
||||||
if(!id) return
|
setLoadingSuperLikes(true);
|
||||||
try {
|
|
||||||
setLoadingSuperLikes(true);
|
|
||||||
|
|
||||||
|
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}${id.slice(
|
|
||||||
0,39
|
|
||||||
)}&limit=100&includemetadata=true&reverse=true&excludeblocked=true`;
|
|
||||||
const response = await fetch(url, {
|
|
||||||
method: "GET",
|
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const responseData = await response.json();
|
|
||||||
let comments: any[] = [];
|
|
||||||
for (const comment of responseData) {
|
|
||||||
if (comment.identifier && comment.name && comment?.metadata?.description) {
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const result = extractSigValue(comment?.metadata?.description)
|
|
||||||
if(!result) continue
|
|
||||||
const res = await getPaymentInfo(result);
|
|
||||||
if(+res?.amount >= minPriceSuperlike && res.recipient === nameAddressParam && isTimestampWithinRange(res?.timestamp, comment.created)){
|
|
||||||
addSuperlikeRawDataGetToList({name:comment.name, identifier:comment.identifier, content: comment})
|
|
||||||
|
|
||||||
comments = [...comments, {
|
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}${id.slice(
|
||||||
...comment,
|
0,
|
||||||
message: "",
|
39
|
||||||
amount: res.amount
|
)}&limit=100&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||||
}];
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
}
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
} catch (error) {
|
},
|
||||||
|
});
|
||||||
}
|
const responseData = await response.json();
|
||||||
|
let comments: any[] = [];
|
||||||
|
for (const comment of responseData) {
|
||||||
|
if (
|
||||||
|
comment.identifier &&
|
||||||
|
comment.name &&
|
||||||
|
comment?.metadata?.description
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const result = extractSigValue(comment?.metadata?.description);
|
||||||
|
if (!result) continue;
|
||||||
|
const res = await getPaymentInfo(result);
|
||||||
|
if (
|
||||||
|
+res?.amount >= minPriceSuperlike &&
|
||||||
|
res.recipient === nameAddressParam &&
|
||||||
|
isTimestampWithinRange(res?.timestamp, comment.created)
|
||||||
|
) {
|
||||||
|
addSuperlikeRawDataGetToList({
|
||||||
|
name: comment.name,
|
||||||
|
identifier: comment.identifier,
|
||||||
|
content: comment,
|
||||||
|
});
|
||||||
|
|
||||||
|
comments = [
|
||||||
|
...comments,
|
||||||
}
|
{
|
||||||
|
...comment,
|
||||||
|
message: "",
|
||||||
|
amount: res.amount,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
setSuperlikelist(comments);
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
setLoadingSuperLikes(false);
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
[]
|
setSuperlikelist(comments);
|
||||||
);
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
setLoadingSuperLikes(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if(!nameAddress || !id) return
|
if (!nameAddress || !id) return;
|
||||||
getComments(id, nameAddress);
|
getComments(id, nameAddress);
|
||||||
}, [getComments, id, nameAddress]);
|
}, [getComments, id, nameAddress]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
@ -354,59 +358,68 @@ export const VideoContent = () => {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<Spacer height="15px" />
|
<Spacer height="15px" />
|
||||||
<Box sx={{
|
<Box
|
||||||
width: '100%',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'flex-end'
|
|
||||||
}}>
|
|
||||||
<FileAttachmentContainer>
|
|
||||||
|
|
||||||
<FileAttachmentFont>
|
|
||||||
save to disk
|
|
||||||
</FileAttachmentFont>
|
|
||||||
<FileElement
|
|
||||||
fileInfo={{...videoReference,
|
|
||||||
filename: videoData?.filename || videoData?.title?.slice(0,20) + '.mp4',
|
|
||||||
mimeType: videoData?.videoType || '"video/mp4',
|
|
||||||
|
|
||||||
}}
|
|
||||||
title={videoData?.filename || videoData?.title?.slice(0,20)}
|
|
||||||
customStyles={{
|
|
||||||
display: "flex",
|
|
||||||
alignItems: "center",
|
|
||||||
justifyContent: "flex-end",
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<DownloadIcon />
|
|
||||||
</FileElement>
|
|
||||||
</FileAttachmentContainer>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Box sx={{
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
width: '100%',
|
|
||||||
marginTop: '20px',
|
|
||||||
gap: '10px'
|
|
||||||
}}>
|
|
||||||
<VideoTitle
|
|
||||||
variant="h1"
|
|
||||||
color="textPrimary"
|
|
||||||
sx={{
|
sx={{
|
||||||
textAlign: "start",
|
width: "100%",
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "flex-end",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoData?.title}
|
<FileAttachmentContainer>
|
||||||
</VideoTitle>
|
<FileAttachmentFont>save to disk</FileAttachmentFont>
|
||||||
{videoData && (
|
<FileElement
|
||||||
<SuperLike numberOfSuperlikes={numberOfSuperlikes} totalAmount={calculateAmountSuperlike} name={videoData?.user} service={videoData?.service} identifier={videoData?.id} onSuccess={(val)=> {
|
fileInfo={{
|
||||||
setSuperlikelist((prev)=> [val, ...prev])
|
...videoReference,
|
||||||
}} />
|
filename:
|
||||||
)}
|
videoData?.filename ||
|
||||||
|
videoData?.title?.slice(0, 20) + ".mp4",
|
||||||
</Box>
|
mimeType: videoData?.videoType || '"video/mp4',
|
||||||
|
}}
|
||||||
|
title={videoData?.filename || videoData?.title?.slice(0, 20)}
|
||||||
|
customStyles={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "flex-end",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DownloadIcon />
|
||||||
|
</FileElement>
|
||||||
|
</FileAttachmentContainer>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
alignItems: "center",
|
||||||
|
width: "100%",
|
||||||
|
marginTop: "20px",
|
||||||
|
gap: "10px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<VideoTitle
|
||||||
|
variant="h1"
|
||||||
|
color="textPrimary"
|
||||||
|
sx={{
|
||||||
|
textAlign: "start",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{videoData?.title}
|
||||||
|
</VideoTitle>
|
||||||
|
{videoData && (
|
||||||
|
<SuperLike
|
||||||
|
numberOfSuperlikes={numberOfSuperlikes}
|
||||||
|
totalAmount={calculateAmountSuperlike}
|
||||||
|
name={videoData?.user}
|
||||||
|
service={videoData?.service}
|
||||||
|
identifier={videoData?.id}
|
||||||
|
onSuccess={val => {
|
||||||
|
setSuperlikelist(prev => [val, ...prev]);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
{videoData?.created && (
|
{videoData?.created && (
|
||||||
<Typography
|
<Typography
|
||||||
variant="h6"
|
variant="h6"
|
||||||
@ -461,10 +474,16 @@ export const VideoContent = () => {
|
|||||||
borderRadius: "5px",
|
borderRadius: "5px",
|
||||||
padding: "5px",
|
padding: "5px",
|
||||||
width: "100%",
|
width: "100%",
|
||||||
cursor: !descriptionHeight ? "default" : isExpandedDescription ? "default" : "pointer",
|
cursor: !descriptionHeight
|
||||||
|
? "default"
|
||||||
|
: isExpandedDescription
|
||||||
|
? "default"
|
||||||
|
: "pointer",
|
||||||
position: "relative",
|
position: "relative",
|
||||||
}}
|
}}
|
||||||
className={!descriptionHeight ? "": isExpandedDescription ? "" : "hover-click"}
|
className={
|
||||||
|
!descriptionHeight ? "" : isExpandedDescription ? "" : "hover-click"
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{descriptionHeight && !isExpandedDescription && (
|
{descriptionHeight && !isExpandedDescription && (
|
||||||
<Box
|
<Box
|
||||||
@ -483,45 +502,56 @@ export const VideoContent = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Box
|
<Box
|
||||||
ref={contentRef}
|
ref={contentRef}
|
||||||
sx={{
|
sx={{
|
||||||
height: !descriptionHeight ? 'auto' : isExpandedDescription ? "auto" : "100px",
|
height: !descriptionHeight
|
||||||
|
? "auto"
|
||||||
|
: isExpandedDescription
|
||||||
|
? "auto"
|
||||||
|
: "100px",
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{videoData?.htmlDescription ? (
|
{videoData?.htmlDescription ? (
|
||||||
<DisplayHtml html={videoData?.htmlDescription} />
|
<DisplayHtml html={videoData?.htmlDescription} />
|
||||||
) : (
|
) : (
|
||||||
<VideoDescription variant="body1" color="textPrimary" sx={{
|
<VideoDescription
|
||||||
cursor: 'default'
|
variant="body1"
|
||||||
}}>
|
color="textPrimary"
|
||||||
|
sx={{
|
||||||
|
cursor: "default",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{videoData?.fullDescription}
|
{videoData?.fullDescription}
|
||||||
</VideoDescription>
|
</VideoDescription>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
{descriptionHeight && (
|
{descriptionHeight && (
|
||||||
<Typography
|
<Typography
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setIsExpandedDescription((prev) => !prev);
|
setIsExpandedDescription(prev => !prev);
|
||||||
}}
|
}}
|
||||||
sx={{
|
sx={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
fontSize: "16px",
|
fontSize: "16px",
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
paddingLeft: "15px",
|
paddingLeft: "15px",
|
||||||
paddingTop: "15px",
|
paddingTop: "15px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isExpandedDescription ? "Show less" : "...more"}
|
{isExpandedDescription ? "Show less" : "...more"}
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</Box>
|
</Box>
|
||||||
</VideoPlayerContainer>
|
</VideoPlayerContainer>
|
||||||
<SuperLikesSection getMore={()=> {
|
<SuperLikesSection
|
||||||
|
getMore={() => {}}
|
||||||
|
loadingSuperLikes={loadingSuperLikes}
|
||||||
|
superlikes={superlikeList}
|
||||||
|
postId={id || ""}
|
||||||
|
postName={name || ""}
|
||||||
|
/>
|
||||||
|
|
||||||
}} loadingSuperLikes={loadingSuperLikes} superlikes={superlikeList} postId={id || ""} postName={name || ""} />
|
|
||||||
|
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
|
154
src/utils/BoundedNumericTextField.tsx
Normal file
154
src/utils/BoundedNumericTextField.tsx
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
import {
|
||||||
|
IconButton,
|
||||||
|
InputAdornment,
|
||||||
|
TextField,
|
||||||
|
TextFieldProps,
|
||||||
|
} from "@mui/material";
|
||||||
|
import React, { useRef, useState } from "react";
|
||||||
|
import AddIcon from "@mui/icons-material/Add";
|
||||||
|
import RemoveIcon from "@mui/icons-material/Remove";
|
||||||
|
import {
|
||||||
|
removeTrailingZeros,
|
||||||
|
setNumberWithinBounds,
|
||||||
|
} from "./numberFunctions.ts";
|
||||||
|
|
||||||
|
type eventType = React.ChangeEvent<HTMLInputElement>;
|
||||||
|
type BoundedNumericTextFieldProps = {
|
||||||
|
minValue: number;
|
||||||
|
maxValue: number;
|
||||||
|
addIconButtons?: boolean;
|
||||||
|
allowDecimals?: boolean;
|
||||||
|
allowNegatives?: boolean;
|
||||||
|
afterChange?: (s: string) => void;
|
||||||
|
initialValue?: string;
|
||||||
|
maxSigDigits?: number;
|
||||||
|
} & TextFieldProps;
|
||||||
|
|
||||||
|
export const BoundedNumericTextField = ({
|
||||||
|
minValue,
|
||||||
|
maxValue,
|
||||||
|
addIconButtons = true,
|
||||||
|
allowDecimals = true,
|
||||||
|
allowNegatives = false,
|
||||||
|
afterChange,
|
||||||
|
initialValue,
|
||||||
|
maxSigDigits = 6,
|
||||||
|
...props
|
||||||
|
}: BoundedNumericTextFieldProps) => {
|
||||||
|
const [textFieldValue, setTextFieldValue] = useState<string>(
|
||||||
|
initialValue || ""
|
||||||
|
);
|
||||||
|
const ref = useRef<HTMLInputElement | null>(null);
|
||||||
|
|
||||||
|
const stringIsEmpty = (value: string) => {
|
||||||
|
return value === "";
|
||||||
|
};
|
||||||
|
const isAllZerosNum = /^0*\.?0*$/;
|
||||||
|
const isFloatNum = /^-?[0-9]*\.?[0-9]*$/;
|
||||||
|
const isIntegerNum = /^-?[0-9]+$/;
|
||||||
|
const skipMinMaxCheck = (value: string) => {
|
||||||
|
const lastIndexIsDecimal = value.charAt(value.length - 1) === ".";
|
||||||
|
const isEmpty = stringIsEmpty(value);
|
||||||
|
const isAllZeros = isAllZerosNum.test(value);
|
||||||
|
const isInteger = isIntegerNum.test(value);
|
||||||
|
// skipping minMax on all 0s allows values less than 1 to be entered
|
||||||
|
|
||||||
|
return lastIndexIsDecimal || isEmpty || (isAllZeros && !isInteger);
|
||||||
|
};
|
||||||
|
|
||||||
|
const setMinMaxValue = (value: string): string => {
|
||||||
|
if (skipMinMaxCheck(value)) return value;
|
||||||
|
const valueNum = Number(value);
|
||||||
|
|
||||||
|
const boundedNum = setNumberWithinBounds(valueNum, minValue, maxValue);
|
||||||
|
|
||||||
|
const numberInBounds = boundedNum === valueNum;
|
||||||
|
return numberInBounds ? value : boundedNum.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSigDigits = (number: string) => {
|
||||||
|
if (isIntegerNum.test(number)) return 0;
|
||||||
|
const decimalSplit = number.split(".");
|
||||||
|
return decimalSplit[decimalSplit.length - 1].length;
|
||||||
|
};
|
||||||
|
|
||||||
|
const sigDigitsExceeded = (number: string, sigDigits: number) => {
|
||||||
|
return getSigDigits(number) > sigDigits;
|
||||||
|
};
|
||||||
|
|
||||||
|
const filterTypes = (value: string) => {
|
||||||
|
if (allowDecimals === false) value = value.replace(".", "");
|
||||||
|
if (allowNegatives === false) value = value.replace("-", "");
|
||||||
|
if (sigDigitsExceeded(value, maxSigDigits)) {
|
||||||
|
value = value.substring(0, value.length - 1);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
};
|
||||||
|
const filterValue = (value: string) => {
|
||||||
|
if (stringIsEmpty(value)) return "";
|
||||||
|
value = filterTypes(value);
|
||||||
|
if (isFloatNum.test(value)) {
|
||||||
|
return setMinMaxValue(value);
|
||||||
|
}
|
||||||
|
return textFieldValue;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listeners = (e: eventType) => {
|
||||||
|
// console.log("changeEvent:", e);
|
||||||
|
const newValue = filterValue(e.target.value);
|
||||||
|
setTextFieldValue(newValue);
|
||||||
|
if (afterChange) afterChange(newValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const changeValueWithIncDecButton = (changeAmount: number) => {
|
||||||
|
const changedValue = (+textFieldValue + changeAmount).toString();
|
||||||
|
const inBoundsValue = setMinMaxValue(changedValue);
|
||||||
|
setTextFieldValue(inBoundsValue);
|
||||||
|
if (afterChange) afterChange(inBoundsValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatValueOnBlur = (e: eventType) => {
|
||||||
|
let value = e.target.value;
|
||||||
|
if (stringIsEmpty(value) || value === ".") {
|
||||||
|
setTextFieldValue("");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
value = setMinMaxValue(value);
|
||||||
|
value = removeTrailingZeros(value);
|
||||||
|
if (isAllZerosNum.test(value)) value = minValue.toString();
|
||||||
|
|
||||||
|
setTextFieldValue(value);
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
const { onChange, ...noChangeProps } = { ...props };
|
||||||
|
return (
|
||||||
|
<TextField
|
||||||
|
{...noChangeProps}
|
||||||
|
InputProps={{
|
||||||
|
...props?.InputProps,
|
||||||
|
endAdornment: addIconButtons ? (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<IconButton onClick={() => changeValueWithIncDecButton(1)}>
|
||||||
|
<AddIcon />{" "}
|
||||||
|
</IconButton>
|
||||||
|
<IconButton onClick={() => changeValueWithIncDecButton(-1)}>
|
||||||
|
<RemoveIcon />{" "}
|
||||||
|
</IconButton>
|
||||||
|
</InputAdornment>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
onChange={e => listeners(e as eventType)}
|
||||||
|
onBlur={e => {
|
||||||
|
formatValueOnBlur(e as eventType);
|
||||||
|
}}
|
||||||
|
autoComplete="off"
|
||||||
|
value={textFieldValue}
|
||||||
|
inputRef={ref}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default BoundedNumericTextField;
|
28
src/utils/numberFunctions.ts
Normal file
28
src/utils/numberFunctions.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import * as colorsys from "colorsys";
|
||||||
|
|
||||||
|
export const truncateNumber = (value: string | number, sigDigits: number) => {
|
||||||
|
return Number(value).toFixed(sigDigits);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const changeLightness = (hexColor: string, amount: number) => {
|
||||||
|
const hsl = colorsys.hex2Hsl(hexColor);
|
||||||
|
hsl.l += amount;
|
||||||
|
return colorsys.hsl2Hex(hsl);
|
||||||
|
};
|
||||||
|
export const removeTrailingZeros = (s: string) => {
|
||||||
|
return Number(s).toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setNumberWithinBounds = (
|
||||||
|
num: number,
|
||||||
|
minValue: number,
|
||||||
|
maxValue: number
|
||||||
|
) => {
|
||||||
|
if (num > maxValue) return maxValue;
|
||||||
|
if (num < minValue) return minValue;
|
||||||
|
return num;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const numberToInt = (num: number) => {
|
||||||
|
return Math.floor(num);
|
||||||
|
};
|
48
src/utils/qortalRequestFunctions.ts
Normal file
48
src/utils/qortalRequestFunctions.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
AccountInfo,
|
||||||
|
AccountName,
|
||||||
|
GetRequestData,
|
||||||
|
SearchTransactionResponse,
|
||||||
|
TransactionSearchParams,
|
||||||
|
} from "./qortalRequestTypes.ts";
|
||||||
|
|
||||||
|
export const getBalance = async (address: string) => {
|
||||||
|
return (await qortalRequest({
|
||||||
|
action: "GET_BALANCE",
|
||||||
|
address,
|
||||||
|
})) as number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUserAccount = async () => {
|
||||||
|
return (await qortalRequest({
|
||||||
|
action: "GET_USER_ACCOUNT",
|
||||||
|
})) as AccountInfo;
|
||||||
|
};
|
||||||
|
export const getUserBalance = async () => {
|
||||||
|
const accountInfo = await getUserAccount();
|
||||||
|
return (await getBalance(accountInfo.address)) as number;
|
||||||
|
};
|
||||||
|
export const getAccountNames = async (
|
||||||
|
address: string,
|
||||||
|
params?: GetRequestData
|
||||||
|
) => {
|
||||||
|
const names = (await qortalRequest({
|
||||||
|
action: "GET_ACCOUNT_NAMES",
|
||||||
|
address,
|
||||||
|
...params,
|
||||||
|
})) as AccountName[];
|
||||||
|
|
||||||
|
const namelessAddress = { name: "", owner: address };
|
||||||
|
const emptyNamesFilled = names.map(({ name, owner }) => {
|
||||||
|
return name ? { name, owner } : namelessAddress;
|
||||||
|
});
|
||||||
|
|
||||||
|
return emptyNamesFilled.length > 0 ? emptyNamesFilled : [namelessAddress];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const searchTransactions = async (params: TransactionSearchParams) => {
|
||||||
|
return (await qortalRequest({
|
||||||
|
action: "SEARCH_TRANSACTIONS",
|
||||||
|
...params,
|
||||||
|
})) as SearchTransactionResponse[];
|
||||||
|
};
|
76
src/utils/qortalRequestTypes.ts
Normal file
76
src/utils/qortalRequestTypes.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
export type AccountInfo = { address: string; publicKey: string };
|
||||||
|
export type AccountName = { name: string; owner: string };
|
||||||
|
export type ConfirmationStatus = "CONFIRMED" | "UNCONFIRMED" | "BOTH";
|
||||||
|
|
||||||
|
export interface GetRequestData {
|
||||||
|
limit?: number;
|
||||||
|
offset?: number;
|
||||||
|
reverse?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SearchTransactionResponse {
|
||||||
|
type: string;
|
||||||
|
timestamp: number;
|
||||||
|
reference: string;
|
||||||
|
fee: string;
|
||||||
|
signature: string;
|
||||||
|
txGroupId: number;
|
||||||
|
blockHeight: number;
|
||||||
|
approvalStatus: string;
|
||||||
|
creatorAddress: string;
|
||||||
|
senderPublicKey: string;
|
||||||
|
recipient: string;
|
||||||
|
amount: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransactionType =
|
||||||
|
| "GENESIS"
|
||||||
|
| "PAYMENT"
|
||||||
|
| "REGISTER_NAME"
|
||||||
|
| "UPDATE_NAME"
|
||||||
|
| "SELL_NAME"
|
||||||
|
| "CANCEL_SELL_NAME"
|
||||||
|
| "BUY_NAME"
|
||||||
|
| "CREATE_POLL"
|
||||||
|
| "VOTE_ON_POLL"
|
||||||
|
| "ARBITRARY"
|
||||||
|
| "ISSUE_ASSET"
|
||||||
|
| "TRANSFER_ASSET"
|
||||||
|
| "CREATE_ASSET_ORDER"
|
||||||
|
| "CANCEL_ASSET_ORDER"
|
||||||
|
| "MULTI_PAYMENT"
|
||||||
|
| "DEPLOY_AT"
|
||||||
|
| "MESSAGE"
|
||||||
|
| "CHAT"
|
||||||
|
| "PUBLICIZE"
|
||||||
|
| "AIRDROP"
|
||||||
|
| "AT"
|
||||||
|
| "CREATE_GROUP"
|
||||||
|
| "UPDATE_GROUP"
|
||||||
|
| "ADD_GROUP_ADMIN"
|
||||||
|
| "REMOVE_GROUP_ADMIN"
|
||||||
|
| "GROUP_BAN"
|
||||||
|
| "CANCEL_GROUP_BAN"
|
||||||
|
| "GROUP_KICK"
|
||||||
|
| "GROUP_INVITE"
|
||||||
|
| "CANCEL_GROUP_INVITE"
|
||||||
|
| "JOIN_GROUP"
|
||||||
|
| "LEAVE_GROUP"
|
||||||
|
| "GROUP_APPROVAL"
|
||||||
|
| "SET_GROUP"
|
||||||
|
| "UPDATE_ASSET"
|
||||||
|
| "ACCOUNT_FLAGS"
|
||||||
|
| "ENABLE_FORGING"
|
||||||
|
| "REWARD_SHARE"
|
||||||
|
| "ACCOUNT_LEVEL"
|
||||||
|
| "TRANSFER_PRIVS"
|
||||||
|
| "PRESENCE";
|
||||||
|
|
||||||
|
export interface TransactionSearchParams extends GetRequestData {
|
||||||
|
startBlock?: number;
|
||||||
|
blockLimit?: number;
|
||||||
|
txGroupId?: number;
|
||||||
|
txType: TransactionType[];
|
||||||
|
address: string;
|
||||||
|
confirmationStatus: ConfirmationStatus;
|
||||||
|
}
|
8
src/utils/stringFunctions.ts
Normal file
8
src/utils/stringFunctions.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export const getFileExtensionIndex = (s: string) => {
|
||||||
|
const lastIndex = s.lastIndexOf(".");
|
||||||
|
return lastIndex > 0 ? lastIndex : s.length - 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getFileName = (s: string) => {
|
||||||
|
return s.substring(0, getFileExtensionIndex(s));
|
||||||
|
};
|
@ -11,16 +11,24 @@ import { addUser } from "../state/features/authSlice";
|
|||||||
import NavBar from "../components/layout/Navbar/Navbar";
|
import NavBar from "../components/layout/Navbar/Navbar";
|
||||||
import PageLoader from "../components/common/PageLoader";
|
import PageLoader from "../components/common/PageLoader";
|
||||||
import { RootState } from "../state/store";
|
import { RootState } from "../state/store";
|
||||||
import { setSuperlikesAll, setUserAvatarHash } from "../state/features/globalSlice";
|
import {
|
||||||
|
setSuperlikesAll,
|
||||||
|
setUserAvatarHash,
|
||||||
|
} from "../state/features/globalSlice";
|
||||||
import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
|
import { VideoPlayerGlobal } from "../components/common/VideoPlayerGlobal";
|
||||||
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/EditVideo/EditVideo";
|
||||||
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
|
import { EditPlaylist } from "../components/EditPlaylist/EditPlaylist";
|
||||||
import ConsentModal from "../components/common/ConsentModal";
|
import ConsentModal from "../components/common/ConsentModal";
|
||||||
import { SUPER_LIKE_BASE, minPriceSuperlike } from "../constants";
|
import {
|
||||||
import { extractSigValue, getPaymentInfo, isTimestampWithinRange } from "../pages/VideoContent/VideoContent";
|
extractSigValue,
|
||||||
|
getPaymentInfo,
|
||||||
|
isTimestampWithinRange,
|
||||||
|
} from "../pages/VideoContent/VideoContent";
|
||||||
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
|
import { useFetchSuperLikes } from "../hooks/useFetchSuperLikes";
|
||||||
|
import { SUPER_LIKE_BASE } from "../constants/Identifiers.ts";
|
||||||
|
import { minPriceSuperlike } from "../constants/Misc.ts";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -32,14 +40,13 @@ let timer: number | null = null;
|
|||||||
export const queue = new RequestQueue();
|
export const queue = new RequestQueue();
|
||||||
export const queueSuperlikes = new RequestQueue();
|
export const queueSuperlikes = new RequestQueue();
|
||||||
|
|
||||||
|
|
||||||
const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const isDragging = useRef(false);
|
const isDragging = useRef(false);
|
||||||
const [userAvatar, setUserAvatar] = useState<string>("");
|
const [userAvatar, setUserAvatar] = useState<string>("");
|
||||||
const user = useSelector((state: RootState) => state.auth.user);
|
const user = useSelector((state: RootState) => state.auth.user);
|
||||||
const {addSuperlikeRawDataGetToList} = useFetchSuperLikes()
|
const { addSuperlikeRawDataGetToList } = useFetchSuperLikes();
|
||||||
const interval = useRef<any>(null)
|
const interval = useRef<any>(null);
|
||||||
|
|
||||||
const videoPlaying = useSelector(
|
const videoPlaying = useSelector(
|
||||||
(state: RootState) => state.global.videoPlaying
|
(state: RootState) => state.global.videoPlaying
|
||||||
@ -134,80 +141,71 @@ const GlobalWrapper: React.FC<Props> = ({ children, setTheme }) => {
|
|||||||
return isDragging.current;
|
return isDragging.current;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const getSuperlikes = useCallback(async () => {
|
||||||
|
try {
|
||||||
|
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`;
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const responseData = await response.json();
|
||||||
|
let comments: any[] = [];
|
||||||
|
for (const comment of responseData) {
|
||||||
|
if (
|
||||||
|
comment.identifier &&
|
||||||
|
comment.name &&
|
||||||
|
comment?.metadata?.description
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const result = extractSigValue(comment?.metadata?.description);
|
||||||
|
if (!result) continue;
|
||||||
|
const res = await getPaymentInfo(result);
|
||||||
|
if (
|
||||||
|
+res?.amount >= minPriceSuperlike &&
|
||||||
|
isTimestampWithinRange(res?.timestamp, comment.created)
|
||||||
|
) {
|
||||||
|
addSuperlikeRawDataGetToList({
|
||||||
|
name: comment.name,
|
||||||
|
identifier: comment.identifier,
|
||||||
|
content: comment,
|
||||||
|
});
|
||||||
|
|
||||||
const getSuperlikes = useCallback(
|
comments = [
|
||||||
async () => {
|
...comments,
|
||||||
try {
|
{
|
||||||
|
...comment,
|
||||||
|
message: "",
|
||||||
const url = `/arbitrary/resources/search?mode=ALL&service=BLOG_COMMENT&query=${SUPER_LIKE_BASE}&limit=20&includemetadata=true&reverse=true&excludeblocked=true`;
|
amount: res.amount,
|
||||||
const response = await fetch(url, {
|
},
|
||||||
method: "GET",
|
];
|
||||||
headers: {
|
}
|
||||||
"Content-Type": "application/json",
|
} catch (error) {}
|
||||||
},
|
|
||||||
});
|
|
||||||
const responseData = await response.json();
|
|
||||||
let comments: any[] = [];
|
|
||||||
for (const comment of responseData) {
|
|
||||||
if (comment.identifier && comment.name && comment?.metadata?.description) {
|
|
||||||
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
const result = extractSigValue(comment?.metadata?.description)
|
|
||||||
if(!result) continue
|
|
||||||
const res = await getPaymentInfo(result);
|
|
||||||
if(+res?.amount >= minPriceSuperlike && isTimestampWithinRange(res?.timestamp, comment.created)){
|
|
||||||
addSuperlikeRawDataGetToList({name:comment.name, identifier:comment.identifier, content: comment})
|
|
||||||
|
|
||||||
comments = [...comments, {
|
|
||||||
...comment,
|
|
||||||
message: "",
|
|
||||||
amount: res.amount
|
|
||||||
}];
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
dispatch(setSuperlikesAll(comments));
|
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
}
|
}
|
||||||
},
|
dispatch(setSuperlikesAll(comments));
|
||||||
[]
|
} catch (error) {
|
||||||
);
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const checkSuperlikes = useCallback(
|
const checkSuperlikes = useCallback(() => {
|
||||||
() => {
|
let isCalling = false;
|
||||||
let isCalling = false
|
interval.current = setInterval(async () => {
|
||||||
interval.current = setInterval(async () => {
|
if (isCalling) return;
|
||||||
if (isCalling) return
|
isCalling = true;
|
||||||
isCalling = true
|
const res = await getSuperlikes();
|
||||||
const res = await getSuperlikes()
|
isCalling = false;
|
||||||
isCalling = false
|
}, 300000);
|
||||||
}, 300000)
|
getSuperlikes();
|
||||||
getSuperlikes()
|
}, [getSuperlikes]);
|
||||||
},
|
|
||||||
[getSuperlikes])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
checkSuperlikes();
|
checkSuperlikes();
|
||||||
}, [checkSuperlikes]);
|
}, [checkSuperlikes]);
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isLoadingGlobal && <PageLoader />}
|
{isLoadingGlobal && <PageLoader />}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user