From d1e676da255c45a0dfc007d5b460e987611c8463 Mon Sep 17 00:00:00 2001 From: IrohDW Date: Wed, 10 Jul 2024 12:46:52 -0600 Subject: [PATCH] Publishing is now a file instead of Base64. This will reduce load times by about 1/3 on all publishes after this update. Fixed minification bug caused by store.ts Whitespace (new lines) in comments are preserved --- src/components/EditIssue/EditIssue.tsx | 5 +- src/components/EditPlaylist/EditPlaylist.tsx | 17 +- src/components/PublishIssue/PublishIssue.tsx | 5 +- src/components/common/Comments/Comment.tsx | 2 +- .../common/Comments/CommentEditor.tsx | 29 +- .../common/Comments/Comments-styles.tsx | 3 +- .../FeePricePublish/FeePricePublish.ts | 10 +- src/state/store.ts | 1 + .../{toBase64.ts => PublishFormatter.ts} | 365 +++++++++--------- 9 files changed, 224 insertions(+), 213 deletions(-) rename src/utils/{toBase64.ts => PublishFormatter.ts} (51%) diff --git a/src/components/EditIssue/EditIssue.tsx b/src/components/EditIssue/EditIssue.tsx index e381656..2a18064 100644 --- a/src/components/EditIssue/EditIssue.tsx +++ b/src/components/EditIssue/EditIssue.tsx @@ -32,7 +32,7 @@ import { import { setNotification } from "../../state/features/notificationsSlice.ts"; import { RootState } from "../../state/store.ts"; import { BountyData, validateBountyInput } from "../../utils/qortalRequests.ts"; -import { objectToBase64 } from "../../utils/toBase64.js"; +import { objectToBase64, objectToFile } from "../../utils/PublishFormatter.ts"; import { isNumber } from "../../utils/utilFunctions.ts"; import { AutocompleteQappNames, @@ -360,13 +360,12 @@ export const EditIssue = () => { if (log) console.log("% of characters used:", metadescription.length / 240); - const fileObjectToBase64 = await objectToBase64(issueObject); // Description is obtained from raw data const requestBodyJson: any = { action: "PUBLISH_QDN_RESOURCE", name: name, service: "DOCUMENT", - data64: fileObjectToBase64, + file: objectToFile(issueObject), title: title.slice(0, 50), description: metadescription, identifier: editIssueProperties.id, diff --git a/src/components/EditPlaylist/EditPlaylist.tsx b/src/components/EditPlaylist/EditPlaylist.tsx index 5b0159b..f0ad181 100644 --- a/src/components/EditPlaylist/EditPlaylist.tsx +++ b/src/components/EditPlaylist/EditPlaylist.tsx @@ -27,7 +27,7 @@ import ShortUniqueId from "short-unique-id"; import { useDispatch, useSelector } from "react-redux"; import { setNotification } from "../../state/features/notificationsSlice"; -import { objectToBase64 } from "../../utils/toBase64"; +import { objectToBase64, objectToFile } from "../../utils/PublishFormatter.ts"; import { RootState } from "../../state/store"; import { setEditPlaylist, @@ -159,14 +159,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(() => { @@ -266,13 +268,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 +311,10 @@ export const EditPlaylist = () => { }; const codes = videoStructured.map(item => `c:${item.code};`).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; @@ -325,7 +326,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, diff --git a/src/components/PublishIssue/PublishIssue.tsx b/src/components/PublishIssue/PublishIssue.tsx index 4f8e40b..4bbf31b 100644 --- a/src/components/PublishIssue/PublishIssue.tsx +++ b/src/components/PublishIssue/PublishIssue.tsx @@ -35,7 +35,7 @@ import { ThemeButtonBright } from "../../pages/Home/Home-styles.tsx"; import { setNotification } from "../../state/features/notificationsSlice"; import { RootState } from "../../state/store"; import { BountyData, validateBountyInput } from "../../utils/qortalRequests.ts"; -import { objectToBase64 } from "../../utils/toBase64"; +import { objectToBase64, objectToFile } from "../../utils/PublishFormatter.ts"; import { isNumber } from "../../utils/utilFunctions.ts"; import { AutocompleteQappNames, @@ -325,13 +325,12 @@ export const PublishIssue = ({ editId, editContent }: NewCrowdfundProps) => { if (log) console.log("% of characters used:", metadescription.length / 240); - const fileObjectToBase64 = await objectToBase64(issueObject); // Description is obtained from raw data const requestBodyJson: any = { action: "PUBLISH_QDN_RESOURCE", name: name, service: "DOCUMENT", - data64: fileObjectToBase64, + file: objectToFile(issueObject), title: title.slice(0, 50), description: metadescription, identifier: identifier + "_metadata", diff --git a/src/components/common/Comments/Comment.tsx b/src/components/common/Comments/Comment.tsx index 0033323..8482967 100644 --- a/src/components/common/Comments/Comment.tsx +++ b/src/components/common/Comments/Comment.tsx @@ -239,7 +239,7 @@ const CommentCard = ({ - {message} + {message} { // 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 @@ -58,10 +61,10 @@ export async function addItem(item: Item): Promise { } export async function updateItemDate(item: any): Promise { // 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) { @@ -124,11 +127,9 @@ 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 || ""; const notificationMessage = `This is an automated Q-Support notification indicating that someone has commented on your issue here: qortal://APP/Q-Support/issue/${postName}/${postId}`; @@ -156,12 +157,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, }); @@ -180,13 +180,6 @@ export const CommentEditor = ({ }); } if (!isReply && !isEdit) { - // const notificationMessage = `This is an automated Q-Support notification indicating that someone has commented on your issue here: - // qortal://APP/Q-Support/issue/${postName}/${postId} - // - // Here are the first ${maxNotificationLength} characters of the comment: - // - // ${value.substring(0, maxNotificationLength)}`; - await sendQchatDM(postName, notificationMessage); } return resourceResponse; @@ -269,5 +262,3 @@ export const CommentEditor = ({ ); }; - -const sendDMwithComment = () => {}; diff --git a/src/components/common/Comments/Comments-styles.tsx b/src/components/common/Comments/Comments-styles.tsx index c95eb41..66c96cb 100644 --- a/src/components/common/Comments/Comments-styles.tsx +++ b/src/components/common/Comments/Comments-styles.tsx @@ -93,8 +93,9 @@ export const StyledCardComment = styled(Typography)(({ theme }) => ({ letterSpacing: 0, fontWeight: 400, color: theme.palette.text.primary, - fontSize: "19px", + fontSize: "100%", wordBreak: "break-word", + whiteSpace: "pre-wrap", })); export const TitleText = styled(Typography)({ diff --git a/src/constants/PublishFees/FeePricePublish/FeePricePublish.ts b/src/constants/PublishFees/FeePricePublish/FeePricePublish.ts index 8cb18cf..c9f116e 100644 --- a/src/constants/PublishFees/FeePricePublish/FeePricePublish.ts +++ b/src/constants/PublishFees/FeePricePublish/FeePricePublish.ts @@ -1,6 +1,9 @@ import { setFeeData } from "../../../state/features/globalSlice.ts"; import { store } from "../../../state/store.js"; -import { objectToBase64 } from "../../../utils/toBase64.ts"; +import { + objectToBase64, + objectToFile, +} from "../../../utils/PublishFormatter.ts"; import { useTestIdentifiers } from "../../Identifiers.ts"; import { appName, FEE_BASE, feeAmountBase, FeeType } from "../FeeData.tsx"; @@ -50,7 +53,7 @@ export const addFeePrice = async ( feeType: FeeType = "default", coinType: CoinType = "QORT" ) => { - let fees = await fetchFees(); + const fees = await fetchFees(); fees.push({ time: Date.now(), @@ -59,14 +62,13 @@ export const addFeePrice = async ( coinType, }); - const feesBase64 = await objectToBase64(fees); console.log("fees are: ", fees); await qortalRequest({ action: "PUBLISH_QDN_RESOURCE", name: appName, identifier: FEE_BASE, service: feesPublishService, - data64: feesBase64, + file: objectToFile(fees), }); }; diff --git a/src/state/store.ts b/src/state/store.ts index 135d0b4..91a0637 100644 --- a/src/state/store.ts +++ b/src/state/store.ts @@ -1,3 +1,4 @@ +/* eslint-disable */ import { configureStore } from "@reduxjs/toolkit"; import authReducer from "./features/authSlice.js"; import fileReducer from "./features/fileSlice.ts"; diff --git a/src/utils/toBase64.ts b/src/utils/PublishFormatter.ts similarity index 51% rename from src/utils/toBase64.ts rename to src/utils/PublishFormatter.ts index e335d36..7954021 100644 --- a/src/utils/toBase64.ts +++ b/src/utils/PublishFormatter.ts @@ -1,174 +1,191 @@ -export const toBase64 = (file: File): Promise => - new Promise((resolve, reject) => { - 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) - } - }) - -export function objectToBase64(obj: any) { - // Step 1: Convert the object to a JSON string - const jsonString = JSON.stringify(obj) - - // Step 2: Create a Blob from the JSON string - 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((resolve, reject) => { - const reader = new FileReader() - reader.onloadend = () => { - if (typeof reader.result === 'string') { - // Remove 'data:application/json;base64,' prefix - const base64 = reader.result.replace( - 'data:application/json;base64,', - '' - ) - resolve(base64) - } else { - reject(new Error('Failed to read the Blob as a base64-encoded string')) - } - } - reader.onerror = () => { - reject(reader.error) - } - reader.readAsDataURL(blob) - }) -} - -export function objectToUint8Array(obj: any) { - // Convert the object to a JSON string - const jsonString = JSON.stringify(obj) - - // Encode the JSON string as a byte array using TextEncoder - 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) - - return uint8Array -} - -export function uint8ArrayToBase64(uint8Array: Uint8Array): string { - 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( - '' - ) - } - - return btoa(binaryString) -} - -export function objectToUint8ArrayFromResponse(obj: any) { - const len = Object.keys(obj).length - const result = new Uint8Array(len) - - for (let i = 0; i < len; i++) { - result[i] = obj[i] - } - - return result -} -// export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string { -// let binary = '' -// const bytes = new Uint8Array(arrayBuffer) -// const len = bytes.length - -// for (let i = 0; i < len; i++) { -// binary += String.fromCharCode(bytes[i]) -// } - -// return btoa(binary) -// } - -export function base64ToUint8Array(base64: string) { - 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) - } - - return bytes -} - -export function uint8ArrayToObject(uint8Array: Uint8Array) { - // Decode the byte array using TextDecoder - const decoder = new TextDecoder() - const jsonString = decoder.decode(uint8Array) - - // Convert the JSON string back into an object - const obj = JSON.parse(jsonString) - - return obj -} - -export function processFileInChunks(file: File): Promise { - return new Promise( - (resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => { - const reader = new FileReader() - - reader.onload = function (event: ProgressEvent) { - const arrayBuffer = event.target?.result as ArrayBuffer - const uint8Array = new Uint8Array(arrayBuffer) - resolve(uint8Array) - } - - reader.onerror = function (error: ProgressEvent) { - reject(error) - } - - reader.readAsArrayBuffer(file) - } - ) -} - -// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise { -// const fileStream = file.stream(); -// const reader = fileStream.getReader(); -// const totalLength = file.size; - -// if (totalLength <= 0 || isNaN(totalLength)) { -// throw new Error('Invalid file size'); -// } - -// const combinedArray = new Uint8Array(totalLength); -// let offset = 0; - -// while (offset < totalLength) { -// const { value, done } = await reader.read(); - -// if (done) { -// break; -// } - -// const chunk = new Uint8Array(value.buffer, value.byteOffset, value.byteLength); - -// // Set elements one by one instead of using combinedArray.set(chunk, offset) -// for (let i = 0; i < chunk.length; i++) { -// combinedArray[offset + i] = chunk[i]; -// } - -// offset += chunk.length; -// } - -// return combinedArray; -// } +export const publishFormatter = ( + file: File +): Promise => + new Promise((resolve, reject) => { + 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); + }; + }); + +export function objectToBase64(obj: any) { + // Step 1: Convert the object to a JSON string + const jsonString = JSON.stringify(obj); + + // Step 2: Create a Blob from the JSON string + 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((resolve, reject) => { + const reader = new FileReader(); + reader.onloadend = () => { + if (typeof reader.result === "string") { + // Remove 'data:application/json;base64,' prefix + const base64 = reader.result.replace( + "data:application/json;base64,", + "" + ); + resolve(base64); + } else { + reject(new Error("Failed to read the Blob as a base64-encoded string")); + } + }; + reader.onerror = () => { + 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); + + // Encode the JSON string as a byte array using TextEncoder + 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); + + return uint8Array; +} + +export function uint8ArrayToBase64(uint8Array: Uint8Array): string { + 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( + "" + ); + } + + return btoa(binaryString); +} + +export function objectToUint8ArrayFromResponse(obj: any) { + const len = Object.keys(obj).length; + const result = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + result[i] = obj[i]; + } + + return result; +} +// export function uint8ArrayToBase64(arrayBuffer: Uint8Array): string { +// let binary = '' +// const bytes = new Uint8Array(arrayBuffer) +// const len = bytes.length + +// for (let i = 0; i < len; i++) { +// binary += String.fromCharCode(bytes[i]) +// } + +// return btoa(binary) +// } + +export function base64ToUint8Array(base64: string) { + 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); + } + + return bytes; +} + +export function uint8ArrayToObject(uint8Array: Uint8Array) { + // Decode the byte array using TextDecoder + const decoder = new TextDecoder(); + const jsonString = decoder.decode(uint8Array); + + // Convert the JSON string back into an object + const obj = JSON.parse(jsonString); + + return obj; +} + +export function processFileInChunks(file: File): Promise { + return new Promise( + (resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => { + const reader = new FileReader(); + + reader.onload = function (event: ProgressEvent) { + const arrayBuffer = event.target?.result as ArrayBuffer; + const uint8Array = new Uint8Array(arrayBuffer); + resolve(uint8Array); + }; + + reader.onerror = function (error: ProgressEvent) { + reject(error); + }; + + reader.readAsArrayBuffer(file); + } + ); +} + +// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise { +// const fileStream = file.stream(); +// const reader = fileStream.getReader(); +// const totalLength = file.size; + +// if (totalLength <= 0 || isNaN(totalLength)) { +// throw new Error('Invalid file size'); +// } + +// const combinedArray = new Uint8Array(totalLength); +// let offset = 0; + +// while (offset < totalLength) { +// const { value, done } = await reader.read(); + +// if (done) { +// break; +// } + +// const chunk = new Uint8Array(value.buffer, value.byteOffset, value.byteLength); + +// // Set elements one by one instead of using combinedArray.set(chunk, offset) +// for (let i = 0; i < chunk.length; i++) { +// combinedArray[offset + i] = chunk[i]; +// } + +// offset += chunk.length; +// } + +// return combinedArray; +// }