Browse Source

Merge pull request #29 from QortalSeth/main

Publishes are stored as File instead of Base64 to Reduce Load Times
pull/30/head
Qortal Dev 3 months ago committed by GitHub
parent
commit
7bd0946976
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 0
      .eslintrc.cjs
  2. 4
      package-lock.json
  3. 21
      src/components/Publish/EditPlaylist/EditPlaylist.tsx
  4. 40
      src/components/Publish/EditVideo/EditVideo.tsx
  5. 3
      src/components/Publish/MultiplePublish/MultiplePublishAll.tsx
  6. 58
      src/components/Publish/PublishVideo/PublishVideo.tsx
  7. 25
      src/components/common/Comments/CommentEditor.tsx
  8. 2
      src/components/common/ContentButtons/LikeAndDislike-functions.ts
  9. 2
      src/components/common/ContentButtons/LikeAndDislike.tsx
  10. 75
      src/components/common/ContentButtons/SuperLike.tsx
  11. 39
      src/components/common/SuperLikesList/CommentEditor.tsx
  12. 3
      src/constants/Misc.ts
  13. 26
      src/hooks/useFetchVideos.tsx
  14. 9
      src/pages/ContentPages/VideoContent/VideoContent.tsx
  15. 38
      src/pages/Home/VideoList.tsx
  16. 147
      src/utils/PublishFormatter.ts
  17. 2
      src/utils/fetchVideos.ts

0
.eslintrc.js → .eslintrc.cjs

4
package-lock.json generated

@ -1,12 +1,12 @@
{
"name": "qtube",
"version": "0.0.0",
"version": "2.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "qtube",
"version": "0.0.0",
"version": "2.0.0",
"dependencies": {
"@emotion/react": "^11.10.6",
"@emotion/styled": "^11.10.6",

21
src/components/Publish/EditPlaylist/EditPlaylist.tsx

@ -31,7 +31,11 @@ import AddBoxIcon from "@mui/icons-material/AddBox";
import { useDropzone } from "react-dropzone";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
import {
objectToBase64,
objectToFile,
uint8ArrayToBase64,
} from "../../../utils/PublishFormatter.ts";
import { RootState } from "../../../state/store.ts";
import {
upsertVideosBeginning,
@ -165,14 +169,16 @@ export const EditPlaylist = () => {
const responseDataSearchVid = await response.json();
if (responseDataSearchVid?.length > 0) {
let resourceData2 = responseDataSearchVid[0];
const resourceData2 = responseDataSearchVid[0];
videos.push(resourceData2);
}
}
}
combinedData.videos = videos;
setPlaylistData(combinedData);
} catch (error) {}
} catch (error) {
console.log(error);
}
}, []);
useEffect(() => {
@ -263,13 +269,13 @@ export const EditPlaylist = () => {
if (!descriptionVid) throw new Error("cannot find video code");
// Split the string by ';'
let parts = descriptionVid.split(";");
const parts = descriptionVid.split(";");
// Initialize a variable to hold the code value
let codeValue = "";
// Loop through the parts to find the one that starts with 'code:'
for (let part of parts) {
for (const part of parts) {
if (part.startsWith("code:")) {
codeValue = part.split(":")[1];
break;
@ -309,11 +315,10 @@ export const EditPlaylist = () => {
.map(item => `c:${item.code};`)
.slice(0, 10)
.join("");
let metadescription =
const metadescription =
`**category:${category};subcategory:${subcategory};${codes}**` +
stringDescription.slice(0, 120);
const crowdfundObjectToBase64 = await objectToBase64(playlistObject);
// Description is obtained from raw data
let identifier = editVideoProperties?.id;
@ -333,7 +338,7 @@ export const EditPlaylist = () => {
action: "PUBLISH_QDN_RESOURCE",
name: username,
service: "PLAYLIST",
data64: crowdfundObjectToBase64,
file: objectToFile(playlistObject),
title: title.slice(0, 50),
description: metadescription,
identifier: identifier,

40
src/components/Publish/EditVideo/EditVideo.tsx

@ -35,7 +35,11 @@ import AddBoxIcon from "@mui/icons-material/AddBox";
import { useDropzone } from "react-dropzone";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
import {
objectToBase64,
objectToFile,
uint8ArrayToBase64,
} from "../../../utils/PublishFormatter.ts";
import { RootState } from "../../../state/store.ts";
import {
upsertVideosBeginning,
@ -53,7 +57,11 @@ import { extractTextFromHTML } from "../../common/TextEditor/utils.ts";
import { toBase64 } from "../PublishVideo/PublishVideo.tsx";
import { FrameExtractor } from "../../common/FrameExtractor/FrameExtractor.tsx";
import { QTUBE_VIDEO_BASE } from "../../../constants/Identifiers.ts";
import { titleFormatter } from "../../../constants/Misc.ts";
import {
maxSize,
titleFormatter,
videoMaxSize,
} from "../../../constants/Misc.ts";
const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 });
@ -103,7 +111,7 @@ export const EditVideo = () => {
"video/*": [],
},
maxFiles: 1,
maxSize: 419430400, // 400 MB in bytes
maxSize,
onDrop: (acceptedFiles, rejectedFiles) => {
const firstFile = acceptedFiles[0];
@ -114,7 +122,7 @@ export const EditVideo = () => {
rejectedFiles.forEach(({ file, errors }) => {
errors.forEach(error => {
if (error.code === "file-too-large") {
errorString = "File must be under 400mb";
errorString = `File must be under ${videoMaxSize}MB`;
}
console.log(`Error with file ${file.name}: ${error.message}`);
});
@ -248,7 +256,7 @@ export const EditVideo = () => {
);
return;
}
let listOfPublishes = [];
const listOfPublishes = [];
const category = selectedCategoryVideos.id;
const subcategory = selectedSubCategoryVideos?.id || "";
@ -259,14 +267,14 @@ export const EditVideo = () => {
fileExtension = fileExtensionSplit?.pop() || "mp4";
}
let filename = title.slice(0, 15);
const filename = title.slice(0, 15);
// Step 1: Replace all white spaces with underscores
// Replace all forms of whitespace (including non-standard ones) with underscores
let stringWithUnderscores = filename.replace(/[\s\uFEFF\xA0]+/g, "_");
const stringWithUnderscores = filename.replace(/[\s\uFEFF\xA0]+/g, "_");
// Remove all non-alphanumeric characters (except underscores)
let alphanumericString = stringWithUnderscores.replace(
const alphanumericString = stringWithUnderscores.replace(
/[^a-zA-Z0-9_]/g,
""
);
@ -287,17 +295,16 @@ export const EditVideo = () => {
filename: `${alphanumericString.trim()}.${fileExtension}`,
};
let metadescription =
const metadescription =
`**category:${category};subcategory:${subcategory};code:${editVideoProperties.code}**` +
description.slice(0, 150);
const crowdfundObjectToBase64 = await objectToBase64(videoObject);
// Description is obtained from raw data
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: username,
service: "DOCUMENT",
data64: crowdfundObjectToBase64,
file: objectToFile(videoObject),
title: title.slice(0, 50),
description: metadescription,
identifier: editVideoProperties.id,
@ -318,7 +325,6 @@ export const EditVideo = () => {
tag1: QTUBE_VIDEO_BASE,
filename: `${alphanumericString.trim()}.${fileExtension}`,
};
listOfPublishes.push(requestBodyVideo);
}
@ -377,7 +383,7 @@ export const EditVideo = () => {
const onFramesExtracted = async imgs => {
try {
let imagesExtracts = [];
const imagesExtracts = [];
for (const img of imgs) {
try {
@ -395,7 +401,9 @@ export const EditVideo = () => {
compressedFile = file;
resolve();
},
error(err) {},
error(error) {
console.log(error);
},
});
});
if (!compressedFile) continue;
@ -407,7 +415,9 @@ export const EditVideo = () => {
}
setImageExtracts(imagesExtracts);
} catch (error) {}
} catch (error) {
console.log(error);
}
};
return (

3
src/components/Publish/MultiplePublish/MultiplePublishAll.tsx

@ -78,7 +78,7 @@ export const MultiplePublish = ({
);
const retry = () => {
let newlistOfMultiplePublishes: any[] = [];
const newlistOfMultiplePublishes: any[] = [];
listOfUnsuccessfulPublishes?.forEach(item => {
const findPub = publishes?.resources.find(
(res: any) => res?.identifier === item.identifier
@ -125,6 +125,7 @@ export const MultiplePublish = ({
);
return (
<Box
key={publish?.identifier}
sx={{
display: "flex",
gap: "20px",

58
src/components/Publish/PublishVideo/PublishVideo.tsx

@ -38,7 +38,7 @@ import { useDropzone } from "react-dropzone";
import AddIcon from "@mui/icons-material/Add";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import { objectToBase64, uint8ArrayToBase64 } from "../../../utils/toBase64.ts";
import { objectToBase64, objectToFile, uint8ArrayToBase64 } from "../../../utils/PublishFormatter.ts";
import { RootState } from "../../../state/store.ts";
import {
upsertVideosBeginning,
@ -65,7 +65,7 @@ import {
QTUBE_PLAYLIST_BASE,
QTUBE_VIDEO_BASE,
} from "../../../constants/Identifiers.ts";
import { titleFormatter } from "../../../constants/Misc.ts";
import { maxSize, titleFormatter, videoMaxSize } from "../../../constants/Misc.ts";
import { getFileName } from "../../../utils/stringFunctions.ts";
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
@ -137,11 +137,13 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
const [isCheckDescriptionIsTitle, setIsCheckDescriptionIsTitle] =
useState(false);
const [imageExtracts, setImageExtracts] = useState<any>({});
const { getRootProps, getInputProps } = useDropzone({
accept: {
"video/*": [],
},
maxSize: 419430400, // 400 MB in bytes
maxSize,
onDrop: (acceptedFiles, rejectedFiles) => {
const formatArray = acceptedFiles.map(item => {
let filteredTitle = "";
@ -164,7 +166,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
rejectedFiles.forEach(({ file, errors }) => {
errors.forEach(error => {
if (error.code === "file-too-large") {
errorString = "File must be under 400mb";
errorString = `File must be under ${videoMaxSize}MB`;
}
console.log(`Error with file ${file.name}: ${error.message}`);
});
@ -180,10 +182,10 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
},
});
useEffect(() => {
if (editContent) {
}
}, [editContent]);
// useEffect(() => {
// if (editContent) {
// }
// }, [editContent]);
const onClose = () => {
setIsOpen(false);
@ -238,7 +240,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
return;
}
let listOfPublishes = [];
const listOfPublishes = [];
for (let i = 0; i < files.length; i++) {
const publish = files[i];
@ -274,18 +276,17 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
fileExtension = fileExtensionSplit?.pop() || "mp4";
}
let filename = title.slice(0, 15);
const filename = title.slice(0, 15);
// Step 1: Replace all white spaces with underscores
// Replace all forms of whitespace (including non-standard ones) with underscores
let stringWithUnderscores = filename.replace(/[\s\uFEFF\xA0]+/g, "_");
const stringWithUnderscores = filename.replace(/[\s\uFEFF\xA0]+/g, "_");
// Remove all non-alphanumeric characters (except underscores)
let alphanumericString = stringWithUnderscores.replace(
const alphanumericString = stringWithUnderscores.replace(
/[^a-zA-Z0-9_]/g,
""
);
const videoObject: any = {
title,
version: 1,
@ -306,17 +307,16 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
filename: `${alphanumericString.trim()}.${fileExtension}`,
};
let metadescription =
const metadescription =
`**category:${category};subcategory:${subcategory};code:${code}**` +
fullDescription.slice(0, 150);
const crowdfundObjectToBase64 = await objectToBase64(videoObject);
// Description is obtained from raw data
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: name,
service: "DOCUMENT",
data64: crowdfundObjectToBase64,
file: objectToFile(videoObject),
title: title.slice(0, 50),
description: metadescription,
identifier: identifier + "_metadata",
@ -392,17 +392,17 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
.slice(0, 10)
.join("");
let metadescription =
const metadescription =
`**category:${category};subcategory:${subcategory};${codes}**` +
stringDescription.slice(0, 120);
const crowdfundObjectToBase64 = await objectToBase64(playlistObject);
// Description is obtained from raw data
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: name,
service: "PLAYLIST",
data64: crowdfundObjectToBase64,
file: objectToFile(playlistObject),
title: title.slice(0, 50),
description: metadescription,
identifier: identifier + "_metadata",
@ -447,17 +447,17 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
.slice(0, 10)
.join("");
let metadescription =
const metadescription =
`**category:${playlistObject.category};subcategory:${playlistObject.subcategory};${codes}**` +
playlistObject.description.slice(0, 120);
const crowdfundObjectToBase64 = await objectToBase64(playlistObject);
// Description is obtained from raw data
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: name,
service: "PLAYLIST",
data64: crowdfundObjectToBase64,
file: objectToFile( playlistObject),
title: playlistObject.title.slice(0, 50),
description: metadescription,
identifier: selectExistingPlaylist.identifier,
@ -480,24 +480,24 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
let notificationObj: any = null;
if (typeof error === "string") {
notificationObj = {
msg: error || "Failed to publish crowdfund",
msg: error || "Failed to publish video",
alertType: "error",
};
} else if (typeof error?.error === "string") {
notificationObj = {
msg: error?.error || "Failed to publish crowdfund",
msg: error?.error || "Failed to publish video",
alertType: "error",
};
} else {
notificationObj = {
msg: error?.message || "Failed to publish crowdfund",
msg: error?.message || "Failed to publish video",
alertType: "error",
};
}
if (!notificationObj) return;
dispatch(setNotification(notificationObj));
throw new Error("Failed to publish crowdfund");
throw new Error("Failed to publish video");
}
}
@ -578,7 +578,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
const onFramesExtracted = async (imgs, index) => {
try {
let imagesExtracts = [];
const imagesExtracts = [];
for (const img of imgs) {
try {
@ -596,7 +596,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
compressedFile = file;
resolve();
},
error(err) {},
error(error) {console.log(error)},
});
});
if (!compressedFile) continue;
@ -613,7 +613,7 @@ export const PublishVideo = ({ editId, editContent }: NewCrowdfundProps) => {
[index]: imagesExtracts,
};
});
} catch (error) {}
} catch (error) {console.log(error)}
};
return (

25
src/components/common/Comments/CommentEditor.tsx

@ -4,7 +4,10 @@ import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../../state/store";
import ShortUniqueId from "short-unique-id";
import { setNotification } from "../../../state/features/notificationsSlice";
import { toBase64 } from "../../../utils/toBase64";
import {
publishFormatter,
stringToFile,
} from "../../../utils/PublishFormatter.ts";
import localforage from "localforage";
import {
CommentInput,
@ -30,11 +33,13 @@ export interface Item {
export async function addItem(item: Item): Promise<void> {
// Get all items
let notificationComments: Item[] =
const notificationComments: Item[] =
(await notification.getItem("comments")) || [];
// Find the item with the same id, if it exists
let existingItemIndex = notificationComments.findIndex(i => i.id === item.id);
const existingItemIndex = notificationComments.findIndex(
i => i.id === item.id
);
if (existingItemIndex !== -1) {
// If the item exists, update its date
@ -55,10 +60,10 @@ export async function addItem(item: Item): Promise<void> {
}
export async function updateItemDate(item: any): Promise<void> {
// Get all items
let notificationComments: Item[] =
const notificationComments: Item[] =
(await notification.getItem("comments")) || [];
let notificationCreatorComment: any =
const notificationCreatorComment: any =
(await notification.getItem("post-comments")) || {};
const findPostId = notificationCreatorComment[item.postId];
if (findPostId) {
@ -121,13 +126,10 @@ export const CommentEditor = ({
identifier: string,
idForNotification?: string
) => {
let address;
let name;
const address = user?.address;
const name = user?.name || "";
let errorMsg = "";
address = user?.address;
name = user?.name || "";
if (!address) {
errorMsg = "Cannot post: your address isn't available";
}
@ -150,12 +152,11 @@ export const CommentEditor = ({
}
try {
const base64 = utf8ToBase64(value);
const resourceResponse = await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: name,
service: "BLOG_COMMENT",
data64: base64,
file: stringToFile(value),
identifier: identifier,
});
dispatch(

2
src/components/common/ContentButtons/LikeAndDislike-functions.ts

@ -14,7 +14,7 @@ export const getCurrentLikeType = async (
});
return response?.likeType;
} catch (e) {
console.log("liketype error: ", e);
// console.log("liketype error: ", e);
return NEUTRAL;
}
};

2
src/components/common/ContentButtons/LikeAndDislike.tsx

@ -7,7 +7,7 @@ import { Box, Tooltip } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import ShortUniqueId from "short-unique-id";
import { objectToBase64 } from "../../../utils/toBase64.ts";
import { objectToBase64 } from "../../../utils/PublishFormatter.ts";
import { RootState } from "../../../state/store.ts";
import { FOR, FOR_LIKE, LIKE_BASE } from "../../../constants/Identifiers.ts";
import {

75
src/components/common/ContentButtons/SuperLike.tsx

@ -21,7 +21,10 @@ import { MultiplePublish } from "../../Publish/MultiplePublish/MultiplePublishAl
import { useDispatch, useSelector } from "react-redux";
import { setNotification } from "../../../state/features/notificationsSlice.ts";
import ShortUniqueId from "short-unique-id";
import { objectToBase64 } from "../../../utils/toBase64.ts";
import {
objectToBase64,
objectToFile,
} from "../../../utils/PublishFormatter.ts";
import { minPriceSuperlike } from "../../../constants/Misc.ts";
import { CommentInput } from "../Comments/Comments-styles.tsx";
import {
@ -55,9 +58,7 @@ export const SuperLike = ({
const [isOpen, setIsOpen] = useState<boolean>(false);
const [superlikeDonationAmount, setSuperlikeDonationAmount] =
useState<number>(10);
const [qortalDevDonationAmount, setQortalDevDonationAmount] =
useState<number>(0);
useState<number>(minPriceSuperlike);
const [currentBalance, setCurrentBalance] = useState<string>("");
const [comment, setComment] = useState<string>("");
@ -82,15 +83,12 @@ export const SuperLike = ({
if (!name) throw new Error("Could not retrieve content creator's name");
const estimatedTransactionFees = 0.1;
const donationExceedsBalance =
superlikeDonationAmount +
qortalDevDonationAmount +
estimatedTransactionFees >=
+currentBalance;
superlikeDonationAmount + estimatedTransactionFees >= +currentBalance;
if (donationExceedsBalance) {
throw new Error("Total donations exceeds current balance");
}
let resName = await qortalRequest({
const resName = await qortalRequest({
action: "GET_NAME_DATA",
name: name,
});
@ -106,10 +104,10 @@ export const SuperLike = ({
superlikeDonationAmount < minPriceSuperlike
)
throw new Error(
`The amount needs to be at least ${minPriceSuperlike} QORT`
`The amount is ${superlikeDonationAmount}, but it needs to be at least ${minPriceSuperlike} QORT`
);
let listOfPublishes = [];
const listOfPublishes = [];
const res = await qortalRequest({
action: "SEND_COIN",
@ -118,24 +116,7 @@ export const SuperLike = ({
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:${
const metadescription = `**sig:${
res.signature
};${FOR}:${name}_${FOR_SUPER_LIKE};nm:${name.slice(
0,
@ -148,7 +129,7 @@ export const SuperLike = ({
39
)}_${id}`;
const superLikeToBase64 = await objectToBase64({
const superLikeToFile = objectToFile({
comment,
transactionReference: res.signature,
notificationInformation: {
@ -166,7 +147,7 @@ export const SuperLike = ({
action: "PUBLISH_QDN_RESOURCE",
name: username,
service: "BLOG_COMMENT",
data64: superLikeToBase64,
file: superLikeToFile,
title: "",
description: metadescription,
identifier: identifierSuperLike,
@ -187,9 +168,9 @@ export const SuperLike = ({
dispatch(
setNotification({
msg:
error ||
error?.error ||
error?.message ||
error ||
"Failed to publish Super Like",
alertType: "error",
})
@ -339,36 +320,6 @@ export const SuperLike = ({
InputLabelProps={{ style: { fontSize: "18px" } }}
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>
</DialogContent>
<CrowdfundActionButtonRow>

39
src/components/common/SuperLikesList/CommentEditor.tsx

@ -4,7 +4,12 @@ import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../../state/store";
import ShortUniqueId from "short-unique-id";
import { setNotification } from "../../../state/features/notificationsSlice";
import { objectToBase64, toBase64 } from "../../../utils/toBase64";
import {
objectToBase64,
objectToFile,
publishFormatter,
stringToFile,
} from "../../../utils/PublishFormatter.ts";
import localforage from "localforage";
import {
CommentInput,
@ -30,11 +35,13 @@ export interface Item {
export async function addItem(item: Item): Promise<void> {
// Get all items
let notificationComments: Item[] =
const notificationComments: Item[] =
(await notification.getItem("comments")) || [];
// Find the item with the same id, if it exists
let existingItemIndex = notificationComments.findIndex(i => i.id === item.id);
const existingItemIndex = notificationComments.findIndex(
i => i.id === item.id
);
if (existingItemIndex !== -1) {
// If the item exists, update its date
@ -55,10 +62,10 @@ export async function addItem(item: Item): Promise<void> {
}
export async function updateItemDate(item: any): Promise<void> {
// Get all items
let notificationComments: Item[] =
const notificationComments: Item[] =
(await notification.getItem("comments")) || [];
let notificationCreatorComment: any =
const notificationCreatorComment: any =
(await notification.getItem("post-comments")) || {};
const findPostId = notificationCreatorComment[item.postId];
if (findPostId) {
@ -127,13 +134,10 @@ export const CommentEditor = ({
identifier: string,
idForNotification?: string
) => {
let address;
let name;
const address = user?.address;
const name = user?.name || "";
let errorMsg = "";
address = user?.address;
name = user?.name || "";
if (!address) {
errorMsg = "Cannot post: your address isn't available";
}
@ -156,7 +160,7 @@ export const CommentEditor = ({
}
try {
let data64 = null;
let dataFile = null;
let description = "";
let tag1 = "";
let superObj = {};
@ -177,17 +181,18 @@ export const CommentEditor = ({
notificationInformation: comment.notificationInformation,
about: comment.about,
};
const superLikeToBase64 = await objectToBase64(superObj);
data64 = superLikeToBase64;
const superLikeToFile = await objectToFile(superObj);
dataFile = superLikeToFile;
}
if (isSuperLike && !data64) throw new Error("unable to edit Super like");
if (isSuperLike && !dataFile)
throw new Error("unable to edit Super like");
const base64 = utf8ToBase64(value);
const stringFile = stringToFile(value);
const resourceResponse = await qortalRequest({
action: "PUBLISH_QDN_RESOURCE",
name: name,
service: "BLOG_COMMENT",
data64: isSuperLike ? data64 : base64,
file: isSuperLike ? dataFile : stringFile,
identifier: identifier,
description,
tag1,
@ -248,7 +253,7 @@ export const CommentEditor = ({
let identifier = `${COMMENT_BASE}${postId.slice(-12)}_base_${id}`;
let idForNotification = identifier;
let service = "BLOG_COMMENT";
const service = "BLOG_COMMENT";
if (isReply && commentId) {
const removeBaseCommentId = commentId;
removeBaseCommentId.replace("_base_", "");

3
src/constants/Misc.ts

@ -1,3 +1,6 @@
export const minPriceSuperlike = 1;
export const titleFormatter = /[^a-zA-Z0-9\s-_!?()&'",.;:|—~@#$%^*+=<>]/g;
export const titleFormatterOnSave = /[^a-zA-Z0-9\s-_!()&',.;—~@#$%^+=]/g;
export const videoMaxSize = 400; // Size in Megabytes (decimal)
export const maxSize = videoMaxSize *1024*1024

26
src/hooks/useFetchVideos.tsx

@ -7,7 +7,7 @@ import {
upsertVideos,
upsertVideosBeginning,
Video,
upsertFilteredVideos,
upsertFilteredVideos, removeFromHashMap,
} from "../state/features/videoSlice";
import {
setIsLoadingGlobal,
@ -72,7 +72,7 @@ export const useFetchVideos = () => {
const getAvatar = React.useCallback(async (author: string) => {
try {
let url = await qortalRequest({
const url = await qortalRequest({
action: "GET_QDN_RESOURCE_URL",
name: author,
service: "THUMBNAIL",
@ -85,14 +85,14 @@ export const useFetchVideos = () => {
url,
})
);
} catch (error) {}
} catch (error) {console.log(error)}
}, []);
const getVideo = async (
user: string,
videoId: string,
content: any,
retries: number = 0
retries = 0
) => {
try {
const res = await fetchAndEvaluateVideos({
@ -100,8 +100,11 @@ export const useFetchVideos = () => {
videoId,
content,
});
dispatch(addToHashMap(res));
if (res?.isValid) {
dispatch(addToHashMap(res));
} else {
dispatch(removeFromHashMap(videoId));
}
} catch (error) {
retries = retries + 1;
if (retries < 2) {
@ -183,7 +186,7 @@ export const useFetchVideos = () => {
}
}
}
} catch (error) {
} catch (error) {console.log(error)
} finally {
dispatch(setIsLoadingGlobal(false));
}
@ -311,7 +314,6 @@ export const useFetchVideos = () => {
}
} catch (error) {
console.log({ error });
} finally {
}
},
[videos, hashMapVideos]
@ -370,8 +372,7 @@ export const useFetchVideos = () => {
}
}
}
} catch (error) {
} finally {
} catch (error) {console.log(error)
}
},
[filteredVideos, hashMapVideos]
@ -411,12 +412,12 @@ export const useFetchVideos = () => {
const newArray = responseData.slice(0, findVideo);
dispatch(setCountNewVideos(newArray.length));
return;
} catch (error) {}
} catch (error) {console.log(error)}
}, [videos]);
const getVideosCount = React.useCallback(async () => {
try {
let url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QTUBE_VIDEO_BASE}`;
const url = `/arbitrary/resources/search?mode=ALL&includemetadata=false&limit=0&service=DOCUMENT&identifier=${QTUBE_VIDEO_BASE}`;
const response = await fetch(url, {
method: "GET",
@ -436,7 +437,6 @@ export const useFetchVideos = () => {
dispatch(setVideosPerNamePublished(videosPerNamePublished));
} catch (error) {
console.log({ error });
} finally {
}
}, []);

9
src/pages/ContentPages/VideoContent/VideoContent.tsx

@ -65,10 +65,10 @@ import { LikeAndDislike } from "../../../components/common/ContentButtons/LikeAn
export function isTimestampWithinRange(resTimestamp, resCreated) {
// Calculate the absolute difference in milliseconds
var difference = Math.abs(resTimestamp - resCreated);
const difference = Math.abs(resTimestamp - resCreated);
// 2 minutes in milliseconds
var twoMinutesInMilliseconds = 3 * 60 * 1000;
const twoMinutesInMilliseconds = 3 * 60 * 1000;
// Check if the difference is within 2 minutes
return difference <= twoMinutesInMilliseconds;
@ -282,6 +282,7 @@ export const VideoContent = () => {
}
}
} catch (error) {
console.log(error);
} finally {
dispatch(setIsLoadingGlobal(false));
}
@ -356,7 +357,9 @@ export const VideoContent = () => {
},
];
}
} catch (error) {}
} catch (error) {
console.log(error);
}
}
}

38
src/pages/Home/VideoList.tsx

@ -1,3 +1,15 @@
import BlockIcon from "@mui/icons-material/Block";
import EditIcon from "@mui/icons-material/Edit";
import { Avatar, Box, Tooltip, useTheme } from "@mui/material";
import React, { useMemo, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG.tsx";
import ResponsiveImage from "../../components/ResponsiveImage.tsx";
import { blockUser, setEditPlaylist, setEditVideo, Video } from "../../state/features/videoSlice.ts";
import { RootState } from "../../state/store.ts";
import { formatDate } from "../../utils/time.ts";
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
import {
BlockIconContainer,
BottomParent,
@ -10,23 +22,7 @@ import {
VideoCardTitle,
VideoUploadDate,
} from "./VideoList-styles.tsx";
import { Avatar, Box, Tooltip, useTheme } from "@mui/material";
import EditIcon from "@mui/icons-material/Edit";
import {
blockUser,
setEditPlaylist,
setEditVideo,
} from "../../state/features/videoSlice.ts";
import BlockIcon from "@mui/icons-material/Block";
import ResponsiveImage from "../../components/ResponsiveImage.tsx";
import { formatDate } from "../../utils/time.ts";
import { PlaylistSVG } from "../../assets/svgs/PlaylistSVG.tsx";
import { VideoCardImageContainer } from "./VideoCardImageContainer.tsx";
import React, { useState } from "react";
import { Video } from "../../state/features/videoSlice.ts";
import { useDispatch, useSelector } from "react-redux";
import { RootState } from "../../state/store.ts";
import { useNavigate } from "react-router-dom";
interface VideoListProps {
videos: Video[];
}
@ -59,12 +55,16 @@ export const VideoList = ({ videos }: VideoListProps) => {
if (response === true) {
dispatch(blockUser(user));
}
} catch (error) {}
} catch (error) {console.log(error)}
};
const filteredVideos = useMemo(() => {
return videos.filter((video: Video) => hashMapVideos[`${video.id}-${video.user}`]?.isValid);
}, [videos, hashMapVideos]);
return (
<VideoCardContainer>
{videos.map((video: any) => {
{filteredVideos.map((video: any) => {
const fullId = video ? `${video.id}-${video.user}` : undefined;
const existingVideo = hashMapVideos[fullId];
let hasHash = false;

147
src/utils/toBase64.ts → src/utils/PublishFormatter.ts

@ -1,90 +1,107 @@
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
export const publishFormatter = (
file: File
): Promise<string | ArrayBuffer | null> =>
new Promise((resolve, reject) => {
const reader = new FileReader()
reader.readAsDataURL(file)
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
const result = reader.result
reader.onload = null // remove onload handler
reader.onerror = null // remove onerror handler
resolve(result)
}
reader.onerror = (error) => {
reader.onload = null // remove onload handler
reader.onerror = null // remove onerror handler
reject(error)
}
})
const result = reader.result;
reader.onload = null; // remove onload handler
reader.onerror = null; // remove onerror handler
resolve(result);
};
reader.onerror = error => {
reader.onload = null; // remove onload handler
reader.onerror = null; // remove onerror handler
reject(error);
};
});
export function objectToBase64(obj: any) {
// Step 1: Convert the object to a JSON string
const jsonString = JSON.stringify(obj)
const jsonString = JSON.stringify(obj);
// Step 2: Create a Blob from the JSON string
const blob = new Blob([jsonString], { type: 'application/json' })
const blob = new Blob([jsonString], { type: "application/json" });
// Step 3: Create a FileReader to read the Blob as a base64-encoded string
return new Promise<string>((resolve, reject) => {
const reader = new FileReader()
const reader = new FileReader();
reader.onloadend = () => {
if (typeof reader.result === 'string') {
if (typeof reader.result === "string") {
// Remove 'data:application/json;base64,' prefix
const base64 = reader.result.replace(
'data:application/json;base64,',
''
)
resolve(base64)
"data:application/json;base64,",
""
);
resolve(base64);
} else {
reject(new Error('Failed to read the Blob as a base64-encoded string'))
reject(new Error("Failed to read the Blob as a base64-encoded string"));
}
}
};
reader.onerror = () => {
reject(reader.error)
}
reader.readAsDataURL(blob)
})
reject(reader.error);
};
reader.readAsDataURL(blob);
});
}
export const stringToFile = (text: string) => {
return new File([text], "", {
type: "text/plain",
});
};
export const objectToFile = (obj: object) => {
// Step 1: Convert the object to a JSON string
const jsonString = JSON.stringify(obj);
const fileType = { type: "application/json" };
// Step 2: Create a Blob from the JSON string
const blob = new Blob([jsonString], fileType);
return new File([blob], ``, fileType);
};
export function objectToUint8Array(obj: any) {
// Convert the object to a JSON string
const jsonString = JSON.stringify(obj)
const jsonString = JSON.stringify(obj);
// Encode the JSON string as a byte array using TextEncoder
const encoder = new TextEncoder()
const byteArray = encoder.encode(jsonString)
const encoder = new TextEncoder();
const byteArray = encoder.encode(jsonString);
// Create a new Uint8Array and set its content to the encoded byte array
const uint8Array = new Uint8Array(byteArray)
const uint8Array = new Uint8Array(byteArray);
return uint8Array
return uint8Array;
}
export function uint8ArrayToBase64(uint8Array: Uint8Array): string {
const length = uint8Array.length
let binaryString = ''
const chunkSize = 1024 * 1024 // Process 1MB at a time
const length = uint8Array.length;
let binaryString = "";
const chunkSize = 1024 * 1024; // Process 1MB at a time
for (let i = 0; i < length; i += chunkSize) {
const chunkEnd = Math.min(i + chunkSize, length)
const chunk = uint8Array.subarray(i, chunkEnd)
binaryString += Array.from(chunk, (byte) => String.fromCharCode(byte)).join(
''
)
const chunkEnd = Math.min(i + chunkSize, length);
const chunk = uint8Array.subarray(i, chunkEnd);
binaryString += Array.from(chunk, byte => String.fromCharCode(byte)).join(
""
);
}
return btoa(binaryString)
return btoa(binaryString);
}
export function objectToUint8ArrayFromResponse(obj: any) {
const len = Object.keys(obj).length
const result = new Uint8Array(len)
const len = Object.keys(obj).length;
const result = new Uint8Array(len);
for (let i = 0; i < len; i++) {
result[i] = obj[i]
result[i] = obj[i];
}
return result
return result;
}
// export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string {
// let binary = ''
@ -99,46 +116,46 @@ export function objectToUint8ArrayFromResponse(obj: any) {
// }
export function base64ToUint8Array(base64: string) {
const binaryString = atob(base64)
const len = binaryString.length
const bytes = new Uint8Array(len)
const binaryString = atob(base64);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i)
bytes[i] = binaryString.charCodeAt(i);
}
return bytes
return bytes;
}
export function uint8ArrayToObject(uint8Array: Uint8Array) {
// Decode the byte array using TextDecoder
const decoder = new TextDecoder()
const jsonString = decoder.decode(uint8Array)
const decoder = new TextDecoder();
const jsonString = decoder.decode(uint8Array);
// Convert the JSON string back into an object
const obj = JSON.parse(jsonString)
const obj = JSON.parse(jsonString);
return obj
return obj;
}
export function processFileInChunks(file: File): Promise<Uint8Array> {
return new Promise(
(resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => {
const reader = new FileReader()
const reader = new FileReader();
reader.onload = function (event: ProgressEvent<FileReader>) {
const arrayBuffer = event.target?.result as ArrayBuffer
const uint8Array = new Uint8Array(arrayBuffer)
resolve(uint8Array)
}
const arrayBuffer = event.target?.result as ArrayBuffer;
const uint8Array = new Uint8Array(arrayBuffer);
resolve(uint8Array);
};
reader.onerror = function (error: ProgressEvent<FileReader>) {
reject(error)
}
reject(error);
};
reader.readAsArrayBuffer(file)
reader.readAsArrayBuffer(file);
}
)
);
}
// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise<Uint8Array> {

2
src/utils/fetchVideos.ts

@ -18,7 +18,7 @@ export const fetchAndEvaluateVideos = async (data: any) => {
service: content?.service || 'DOCUMENT',
identifier: videoId
})
if (checkStructure(responseData)) {
if (responseData) {
obj = {
...content,
...responseData,

Loading…
Cancel
Save