Browse Source

Deleted videos no longer appear on Home Page.

SuperLikes and comments publish as file instead of Base64.

Fixed bug causing the minimum Superlike to be 10 QORT when it is supposed to be 1 QORT.
pull/29/head
Qortal Dev 3 months ago
parent
commit
88cd24ae4d
  1. 21
      src/components/Publish/EditPlaylist/EditPlaylist.tsx
  2. 23
      src/components/Publish/EditVideo/EditVideo.tsx
  3. 25
      src/components/common/Comments/CommentEditor.tsx
  4. 75
      src/components/common/ContentButtons/SuperLike.tsx
  5. 39
      src/components/common/SuperLikesList/CommentEditor.tsx
  6. 9
      src/pages/ContentPages/VideoContent/VideoContent.tsx
  7. 147
      src/utils/PublishFormatter.ts

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/PublishFormatter.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,

23
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, objectToFile, uint8ArrayToBase64 } from "../../../utils/PublishFormatter.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 { maxSize, titleFormatter, videoMaxSize } from "../../../constants/Misc.ts";
import {
maxSize,
titleFormatter,
videoMaxSize,
} from "../../../constants/Misc.ts";
const uid = new ShortUniqueId();
const shortuid = new ShortUniqueId({ length: 5 });
@ -291,13 +299,12 @@ export const EditVideo = () => {
`**category:${category};subcategory:${subcategory};code:${editVideoProperties.code}**` +
description.slice(0, 150);
const videoObjectToFile = objectToFile(videoObject);
// Description is obtained from raw data
const requestBodyJson: any = {
action: "PUBLISH_QDN_RESOURCE",
name: username,
service: "DOCUMENT",
file: videoObjectToFile,
file: objectToFile(videoObject),
title: title.slice(0, 50),
description: metadescription,
identifier: editVideoProperties.id,
@ -394,7 +401,9 @@ export const EditVideo = () => {
compressedFile = file;
resolve();
},
error(error) {console.log(error)},
error(error) {
console.log(error);
},
});
});
if (!compressedFile) continue;
@ -406,7 +415,9 @@ export const EditVideo = () => {
}
setImageExtracts(imagesExtracts);
} catch (error) {console.log(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 { publishFormatter } from "../../../utils/PublishFormatter.ts";
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(

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/PublishFormatter.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, publishFormatter } from "../../../utils/PublishFormatter.ts";
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_", "");

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

147
src/utils/PublishFormatter.ts

@ -1,98 +1,107 @@
export const publishFormatter = (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 function objectToFile(obj: any) {
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 jsonString = JSON.stringify(obj);
const fileType = { type: "application/json" };
// Step 2: Create a Blob from the JSON string
return new Blob([jsonString], { type: 'application/json' })
}
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 = ''
@ -107,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> {

Loading…
Cancel
Save