From 11d5a46f0ccc0e7236503b7a8dac000c61809f15 Mon Sep 17 00:00:00 2001 From: IrohDW Date: Fri, 6 Sep 2024 16:49:57 -0600 Subject: [PATCH] New Forum Modal design implmented. Forum Submission in progress. --- src/App.tsx | 2 +- src/components/common/SelectField.tsx | 56 +++++ src/constants/Identifiers.ts | 2 + src/constants/Misc.ts | 3 + src/pages/Forum/Forum.tsx | 10 +- src/pages/Forum/GroupPermissionsForm.tsx | 130 ++++++++++ src/pages/Forum/NewForumModal-data.ts | 289 +++++++++++++++++++++++ src/pages/Forum/NewForumModal.tsx | 211 +++++++++++++++++ src/pages/Forum/QmailTextField.tsx | 58 +++++ src/pages/Home/Home-styles.ts | 1 - src/pages/Home/Home.tsx | 15 +- src/pages/Mail/NewThread.tsx | 4 +- src/utils/QortalRequests.ts | 27 +++ src/utils/toBase64.ts | 270 +++++++++++---------- 14 files changed, 924 insertions(+), 154 deletions(-) create mode 100644 src/components/common/SelectField.tsx create mode 100644 src/pages/Forum/GroupPermissionsForm.tsx create mode 100644 src/pages/Forum/NewForumModal-data.ts create mode 100644 src/pages/Forum/NewForumModal.tsx create mode 100644 src/pages/Forum/QmailTextField.tsx create mode 100644 src/utils/QortalRequests.ts diff --git a/src/App.tsx b/src/App.tsx index 806087f..f559e36 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -22,9 +22,9 @@ function App() { - } /> + } /> diff --git a/src/components/common/SelectField.tsx b/src/components/common/SelectField.tsx new file mode 100644 index 0000000..5c4c504 --- /dev/null +++ b/src/components/common/SelectField.tsx @@ -0,0 +1,56 @@ +import { + FormControl, + InputLabel, + MenuItem, + OutlinedInput, + Select, +} from "@mui/material"; +import { SxProps } from "@mui/system"; +import { signal, Signal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; +import React, { useState } from "react"; + +interface SelectFieldProps { + options: string[]; + value: Signal; + label: string; + sx?: SxProps; +} +export const SelectField = ({ + options, + value, + label, + sx = {}, +}: SelectFieldProps) => { + useSignals(); + + return ( + + + {label} + + + + ); +}; diff --git a/src/constants/Identifiers.ts b/src/constants/Identifiers.ts index 7e3f3cd..f4d84e2 100644 --- a/src/constants/Identifiers.ts +++ b/src/constants/Identifiers.ts @@ -9,3 +9,5 @@ export const AUDIO_BASE = `${appName}_qaudio`; export const FORUM_BASE = `${appName}_forum`; export const THREAD_BASE = `${appName}_thread`; export const THREAD_MESSAGE = `${appName}_thread_message`; + +export const FORUM_GROUPID = 0; // will be determined later diff --git a/src/constants/Misc.ts b/src/constants/Misc.ts index 4937c15..4ea1b8b 100644 --- a/src/constants/Misc.ts +++ b/src/constants/Misc.ts @@ -1 +1,4 @@ export const appOwner = "Q-Mintership"; + +const maxSizeMB = 25; +export const maxPrivateFileSize = maxSizeMB * 1024 * 1024; diff --git a/src/pages/Forum/Forum.tsx b/src/pages/Forum/Forum.tsx index 0afc6df..6b69436 100644 --- a/src/pages/Forum/Forum.tsx +++ b/src/pages/Forum/Forum.tsx @@ -9,12 +9,20 @@ import { InstanceContainer, } from "../Home/Home-styles"; import { GroupMail } from "../Mail/GroupMail"; +import { EncryptionType } from "./NewForumModal"; + +export type GroupPermissionType = "Read" | "Write"; +export interface GroupPermissions { + id: string; + permissions: GroupPermissionType; +} -export type EncryptionType = "None" | "Group" | "GroupAdmin"; interface ForumProps { title: string; description: string; encryption?: EncryptionType; + groupID: string; + groupPermissions: GroupPermissions[]; } export const Forum = ({ encryption = "None" }: ForumProps) => { diff --git a/src/pages/Forum/GroupPermissionsForm.tsx b/src/pages/Forum/GroupPermissionsForm.tsx new file mode 100644 index 0000000..10c7db4 --- /dev/null +++ b/src/pages/Forum/GroupPermissionsForm.tsx @@ -0,0 +1,130 @@ +import AddIcon from "@mui/icons-material/Add"; +import RemoveIcon from "@mui/icons-material/Remove"; +import { + Box, + Button, + IconButton, + MenuItem, + OutlinedInput, + Select, + SelectChangeEvent, +} from "@mui/material"; +import { styled } from "@mui/material/styles"; +import { signal, Signal } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; +import React, { useEffect } from "react"; +import ShortUniqueId from "short-unique-id"; +import { SelectField } from "../../components/common/SelectField"; +import { NewMessageInputRow } from "../Home/Home-styles"; +import { GroupPermissions, GroupPermissionType } from "./Forum"; +import { QmailTextField } from "./QmailTextField"; + +export interface Group { + id: Signal; + name: string; + permissions: Signal; +} +export interface GroupPermissionsFormProps { + groups: Signal; +} +export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => { + useSignals(); + + const uid = new ShortUniqueId(); + const newGroup = { + id: signal(""), + name: "", + permissions: signal("Read"), + } as Group; + const addGroup = () => { + groups.value = [...groups.value, newGroup]; + }; + + const removeGroup = (groupIndex: number) => { + if (groups.value.length > 1) + groups.value = groups.value.filter( + (group, index) => index !== groupIndex + ); + else groups.value = [newGroup]; + }; + + const buttonStyle = { + width: "70px", + height: "70px", + }; + + const StyledIconButton = styled(IconButton)(` + width: 60px; + height: 60px; + border-radius: 4px; + &:hover { + background-color: #cccdd0; + } +`); + + const iconStyle = { fontSize: 50, color: "#2e3d60" }; + const options = ["Read", "Write"]; + + return ( + + {groups.value.map((group, index) => ( + + + + + + + removeGroup(index)}> + + + + + + + + ))} + + ); +}; diff --git a/src/pages/Forum/NewForumModal-data.ts b/src/pages/Forum/NewForumModal-data.ts new file mode 100644 index 0000000..0e9fa2d --- /dev/null +++ b/src/pages/Forum/NewForumModal-data.ts @@ -0,0 +1,289 @@ +import { ReadonlySignal } from "@preact/signals-react"; +import { ATTATCHMENT_BASE, THREAD_BASE } from "../../constants/Identifiers"; +import { + MAIL_ATTACHMENT_SERVICE_TYPE, + MAIL_SERVICE_TYPE, + THREAD_SERVICE_TYPE, +} from "../../constants/mail"; +import { setNotification } from "../../state/features/notificationsSlice"; +import { store } from "../../state/store"; +import { getGroup } from "../../utils/QortalRequests"; +import { objectToBase64, toBase64 } from "../../utils/toBase64"; +import { Group } from "./GroupPermissionsForm"; +import { NewForumModalData } from "./NewForumModal"; + +const getGroupNames = async (groups: Group[]) => { + const groupPromises = groups.map(group => getGroup(group.id.value)); + return await Promise.all(groupPromises); +}; + +const verifyData = async (formData: NewForumModalData) => { + const { name, title, encryption, groups, description } = formData; + + let errorMsg = ""; + if (!name) errorMsg = "Cannot send a message without a access to your name"; + if (!title) errorMsg = "Forum title is empty"; + if (!encryption) errorMsg = "Encryption Type is empty"; + if (!description) errorMsg = "Description is empty"; + if (groups.filter(group => !!group.id).length < groups.length) + errorMsg = "A group has an empty ID"; + if (groups.filter(group => !!group.permissions).length < groups.length) + errorMsg = "A group has an empty permissions"; + + const groupsWithNames = await getGroupNames(groups); + + if (groupsWithNames.filter(group => !!group.groupName).length < groups.length) + errorMsg = "A group doesn't exist"; + + // if (!groupInfo) { + // errorMsg = "Cannot access group information"; + // } + // + // // if (!description) missingFields.push('subject') + // if (missingFields.length > 0) { + // const missingFieldsString = missingFields.join(", "); + // const errMsg = `Missing: ${missingFieldsString}`; + // errorMsg = errMsg; + // } + // const noExtension = attachments.filter(item => !item.extension); + // if (noExtension.length > 0) { + // errorMsg = + // "One of your attachments does not have an extension (example: .png, .pdf, ect...)"; + // } + // + return errorMsg; +}; +export const publishForum = async (formData: NewForumModalData) => { + const { name, title, encryption, groups, description } = formData; + const errorMsg = await verifyData(formData); + try { + if (errorMsg) { + store.dispatch( + setNotification({ + msg: errorMsg, + alertType: "error", + }) + ); + throw new Error(errorMsg); + } + } catch (error) { + console.log(error); + } + debugger; + + // + // const mailObject: any = { + // subject, + // createdAt: Date.now(), + // version: 1, + // attachments, + // textContentV2: value, + // name, + // threadOwner: currentThread?.threadData?.name || name, + // }; + // + // try { + // const groupPublicKeys = Object.keys(members)?.map( + // (key: any) => members[key]?.publicKey + // ); + // if (!groupPublicKeys || groupPublicKeys?.length === 0) { + // throw new Error("No members in this group could be found"); + // } + // + // // START OF ATTACHMENT LOGIC + // + // const attachmentArray: any[] = []; + // for (const singleAttachment of attachments) { + // const attachment = singleAttachment.file; + // + // const fileBase64 = await toBase64(attachment); + // if (typeof fileBase64 !== "string" || !fileBase64) + // throw new Error("Could not convert file to base64"); + // const base64String = fileBase64.split(",")[1]; + // + // const id = uid(); + // const id2 = uid(); + // const identifier = `${ATTATCHMENT_BASE}${id}_${id2}`; + // let fileExtension = attachment?.name?.split(".")?.pop(); + // if (!fileExtension) { + // fileExtension = singleAttachment.extension; + // } + // const obj = { + // name: name, + // service: MAIL_ATTACHMENT_SERVICE_TYPE, + // filename: `${id}.${fileExtension}`, + // originalFilename: attachment?.name || "", + // identifier, + // data64: base64String, + // type: attachment?.type, + // }; + // + // attachmentArray.push(obj); + // } + // + // if (attachmentArray?.length > 0) { + // mailObject.attachments = attachmentArray.map(item => { + // return { + // identifier: item.identifier, + // name, + // service: MAIL_ATTACHMENT_SERVICE_TYPE, + // filename: item.filename, + // originalFilename: item.originalFilename, + // type: item?.type, + // }; + // }); + // + // // const multiplePublish = { + // // action: "PUBLISH_MULTIPLE_QDN_RESOURCES", + // // resources: [...attachmentArray], + // // encrypt: true, + // // publicKeys: groupPublicKeys, + // // }; + // // await qortalRequest(multiplePublish); + // } + // + // //END OF ATTACHMENT LOGIC + // if (!isMessage) { + // const idThread = uid(); + // const messageToBase64 = await objectToBase64(mailObject); + // const threadObject = { + // title: threadTitle, + // groupId: groupInfo.id, + // createdAt: Date.now(), + // name, + // }; + // const threadToBase64 = await objectToBase64(threadObject); + // let identifierThread = `${THREAD_BASE}${groupInfo.id}_${idThread}`; + // let requestBodyThread: any = { + // name: name, + // service: THREAD_SERVICE_TYPE, + // data64: threadToBase64, + // identifier: identifierThread, + // description: threadTitle?.slice(0, 200), + // action: "PUBLISH_QDN_RESOURCE", + // }; + // const idMsg = uid(); + // let groupIndex = identifierThread.indexOf("group"); + // let result = identifierThread.substring(groupIndex); + // let identifier = `qortal_qmail_thmsg_${result}_${idMsg}`; + // let requestBody: any = { + // name: name, + // service: MAIL_SERVICE_TYPE, + // data64: messageToBase64, + // identifier, + // }; + // const multiplePublishMsg = { + // action: "PUBLISH_MULTIPLE_QDN_RESOURCES", + // resources: [requestBody, ...attachmentArray], + // encrypt: true, + // publicKeys: groupPublicKeys, + // }; + // await qortalRequest(requestBodyThread); + // setPublishes(multiplePublishMsg); + // setIsOpenMultiplePublish(true); + // // await qortalRequest(multiplePublishMsg); + // // dispatch( + // // setNotification({ + // // msg: "Message sent", + // // alertType: "success", + // // }) + // // ); + // if (threadCallback) { + // // threadCallback({ + // // threadData: threadObject, + // // threadOwner: name, + // // name, + // // threadId: identifierThread, + // // created: Date.now(), + // // service: 'MAIL_PRIVATE', + // // identifier: identifier + // // }) + // setCallbackContent({ + // thread: { + // threadData: threadObject, + // threadOwner: name, + // name, + // threadId: identifierThread, + // created: Date.now(), + // service: "MAIL_PRIVATE", + // identifier: identifier, + // }, + // }); + // } + // closeModal(); + // } else { + // if (!currentThread) throw new Error("unable to locate thread Id"); + // const idThread = currentThread.threadId; + // const messageToBase64 = await objectToBase64(mailObject); + // const idMsg = uid(); + // let groupIndex = idThread.indexOf("group"); + // let result = idThread.substring(groupIndex); + // let identifier = `qortal_qmail_thmsg_${result}_${idMsg}`; + // let requestBody: any = { + // name: name, + // service: MAIL_SERVICE_TYPE, + // data64: messageToBase64, + // identifier, + // }; + // const multiplePublishMsg = { + // action: "PUBLISH_MULTIPLE_QDN_RESOURCES", + // resources: [requestBody, ...attachmentArray], + // encrypt: true, + // publicKeys: groupPublicKeys, + // }; + // setPublishes(multiplePublishMsg); + // setIsOpenMultiplePublish(true); + // // await qortalRequest(multiplePublishMsg); + // // dispatch( + // // setNotification({ + // // msg: "Message sent", + // // alertType: "success", + // // }) + // // ); + // if (messageCallback) { + // setCallbackContent({ + // message: { + // identifier, + // id: identifier, + // name, + // service: MAIL_SERVICE_TYPE, + // created: Date.now(), + // ...mailObject, + // }, + // }); + // // messageCallback({ + // // identifier, + // // id: identifier, + // // name, + // // service: MAIL_SERVICE_TYPE, + // // created: Date.now(), + // // ...mailObject, + // // }); + // } + // + // closeModal(); + // } + // } catch (error: any) { + // let notificationObj = null; + // if (typeof error === "string") { + // notificationObj = { + // msg: error || "Failed to send message", + // alertType: "error", + // }; + // } else if (typeof error?.error === "string") { + // notificationObj = { + // msg: error?.error || "Failed to send message", + // alertType: "error", + // }; + // } else { + // notificationObj = { + // msg: error?.message || "Failed to send message", + // alertType: "error", + // }; + // } + // if (!notificationObj) return; + // dispatch(setNotification(notificationObj)); + // + // throw new Error("Failed to send message"); + // } +}; diff --git a/src/pages/Forum/NewForumModal.tsx b/src/pages/Forum/NewForumModal.tsx new file mode 100644 index 0000000..745ed77 --- /dev/null +++ b/src/pages/Forum/NewForumModal.tsx @@ -0,0 +1,211 @@ +import CloseIcon from "@mui/icons-material/Close"; +import { Box, Input, Typography } from "@mui/material"; +import { + ReadonlySignal, + Signal, + signal, + useComputed, + useSignal, + useSignalEffect, +} from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; +import mime from "mime"; +import React, { useState } from "react"; +import { useDropzone } from "react-dropzone"; +import { useDispatch, useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; +import ComposeIconSVG from "../../assets/svgs/ComposeIcon.svg"; +import { CreateThreadIcon } from "../../assets/svgs/CreateThreadIcon"; +import ModalCloseSVG from "../../assets/svgs/ModalClose.svg"; +import AttachmentSVG from "../../assets/svgs/NewMessageAttachment.svg"; +import { SendNewMessage } from "../../assets/svgs/SendNewMessage"; +import { SelectField } from "../../components/common/SelectField"; +import { Spacer } from "../../components/common/Spacer"; +import { TextEditor } from "../../components/common/TextEditor/TextEditor"; +import { ReusableModal } from "../../components/modals/ReusableModal"; +import { useTestIdentifiers } from "../../constants/Identifiers"; +import { appOwner, maxPrivateFileSize } from "../../constants/Misc"; +import { setNotification } from "../../state/features/notificationsSlice"; +import { RootState } from "../../state/store"; +import { formatBytes } from "../../utils/displaySize"; +import { getFileExtension } from "../../utils/helpers"; +import { + AttachmentContainer, + CloseContainer, + ComposeContainer, + ComposeIcon, + ComposeP, + InstanceContainer, + InstanceFooter, + InstanceListContainer, + InstanceListHeader, + NewMessageAttachmentImg, + NewMessageCloseImg, + NewMessageHeaderP, + NewMessageInputRow, + NewMessageSendButton, + NewMessageSendP, +} from "../Home/Home-styles"; +import { GroupPermissionType } from "./Forum"; +import { Group, GroupPermissionsForm } from "./GroupPermissionsForm"; +import { publishForum } from "./NewForumModal-data"; +import { QmailTextField } from "./QmailTextField"; + +export type EncryptionType = "None" | "Group" | "GroupAdmin"; +export type NewForumModalData = { + name: string | undefined; + title: string; + encryption: "None" | "Group" | "GroupAdmin" | ""; + groups: Group[]; + description: string; +}; + +export const NewForumModal = () => { + useSignals(); + const navigate = useNavigate(); + const isOpen = useSignal(false); + + const forumTitle = useSignal(""); + const description = useSignal(""); + + const selectedEncryptionType = useSignal("None"); + const formData = useComputed(() => { + return { + name: user?.name, + title: forumTitle.value, + encryption: selectedEncryptionType.value, + groups: groups.value, + description: description.value, + }; + }); + + const { user } = useSelector((state: RootState) => state.auth); + + // const dispatch = useDispatch(); + + const closeModal = () => { + forumTitle.value = ""; + description.value = ""; + isOpen.value = false; + }; + + const groups = signal([ + { + id: signal(""), + name: "", + permissions: signal("Read"), + }, + ]); + return ( + + + {(user?.name === appOwner || useTestIdentifiers) && ( + (isOpen.value = true)}> + + {"New Forum"} + + )} + navigate("/sponsorshipData")}> + + {"Sponsorship Data"} + + + + + {"New Forum"} + + + + + + + + + + + + +

Description

+ { + description.value = val; + }} + /> +
+
+ + publishForum(formData.value)}> + {"Create Forum"} + + + + +
+
+ ); +}; diff --git a/src/pages/Forum/QmailTextField.tsx b/src/pages/Forum/QmailTextField.tsx new file mode 100644 index 0000000..8c35245 --- /dev/null +++ b/src/pages/Forum/QmailTextField.tsx @@ -0,0 +1,58 @@ +import { Input } from "@mui/material"; +import { SxProps } from "@mui/system"; +import { Signal, useSignalEffect } from "@preact/signals-react"; +import { useSignals } from "@preact/signals-react/runtime"; +import React from "react"; + +interface QmailTextFieldProps { + value: Signal; + label: string; + sx?: SxProps; + filter?: RegExp; + maxLength?: number; +} +export const QmailTextField = ({ + value, + label, + sx = {}, + filter, + maxLength = 60, +}: QmailTextFieldProps) => { + useSignals(); + useSignalEffect(() => { + if (filter) value.value = value.value.replace(filter, ""); + }); + + return ( + <> + { + if (e.target.value.length <= maxLength) value.value = e.target.value; + }} + placeholder={label} + disableUnderline + autoComplete="off" + autoCorrect="off" + sx={{ + width: "100%", + color: "black", + "& .MuiInput-input::placeholder": { + color: "rgba(84, 84, 84, 0.70) !important", + fontSize: "100%", + fontStyle: "normal", + fontWeight: 400, + lineHeight: "120%", // 24px + letterSpacing: "0.15px", + opacity: 1, + }, + "&:focus": { + outline: "none", + }, + ...sx, + }} + /> + + ); +}; diff --git a/src/pages/Home/Home-styles.ts b/src/pages/Home/Home-styles.ts index 528182d..5509dbe 100644 --- a/src/pages/Home/Home-styles.ts +++ b/src/pages/Home/Home-styles.ts @@ -419,7 +419,6 @@ export const NewMessageInputRow = styled(Box)(({ theme }) => ({ justifyContent: "space-between", borderBottom: "3px solid rgba(237, 239, 241, 1)", width: "100%", - paddingBottom: "6px", })); export const NewMessageInputLabelP = styled(Typography)` color: rgba(84, 84, 84, 0.7); diff --git a/src/pages/Home/Home.tsx b/src/pages/Home/Home.tsx index e6a54e4..3b763e0 100644 --- a/src/pages/Home/Home.tsx +++ b/src/pages/Home/Home.tsx @@ -1,4 +1,5 @@ import { Box } from "@mui/material"; +import { signal } from "@preact/signals-react"; import React, { useCallback, useEffect, @@ -13,6 +14,7 @@ import { appOwner } from "../../constants/Misc"; import { useFetchMail } from "../../hooks/useFetchMail"; import { RootState } from "../../state/store"; import { executeEvent } from "../../utils/events"; +import { NewForumModal } from "../Forum/NewForumModal"; import { ComposeContainer, @@ -88,20 +90,9 @@ export const Home = () => { } }, [user]); - const publishNewForum = () => { - executeEvent("openNewThreadModal", {}); - }; - return ( - - {(user?.name === appOwner || useTestIdentifiers) && ( - - - {"New Forum"} - - )} - + ); }; diff --git a/src/pages/Mail/NewThread.tsx b/src/pages/Mail/NewThread.tsx index ffea22c..2efff0e 100644 --- a/src/pages/Mail/NewThread.tsx +++ b/src/pages/Mail/NewThread.tsx @@ -191,7 +191,7 @@ export const NewThread = ({ }; }, [openModalPostFromEvent]); - async function publishQDNResource() { + const publishQDNResource = async () => { let name: string = ""; let errorMsg = ""; @@ -446,7 +446,7 @@ export const NewThread = ({ throw new Error("Failed to send message"); } - } + }; const sendMail = () => { publishQDNResource(); diff --git a/src/utils/QortalRequests.ts b/src/utils/QortalRequests.ts new file mode 100644 index 0000000..f179a03 --- /dev/null +++ b/src/utils/QortalRequests.ts @@ -0,0 +1,27 @@ +export interface GroupData { + groupId: number; + owner: string; + groupName: string; + description: string; + created: number; + isOpen: boolean; + approvalThreshold: string; + minimumBlockDelay: number; + maximumBlockDelay: number; + memberCount: number; +} + +export const listGroups = async () => { + return (await qortalRequest({ action: "LIST_GROUPS" })) as GroupData[]; +}; + +export const getGroup = async (groupID: number | string) => { + const url = `/groups/${groupID.toString()}`; + const response = await fetch(url, { + method: "GET", + headers: { + "Content-Type": "application/json", + }, + }); + return (await response.json()) as GroupData; +}; diff --git a/src/utils/toBase64.ts b/src/utils/toBase64.ts index 53577b3..edb7fb6 100644 --- a/src/utils/toBase64.ts +++ b/src/utils/toBase64.ts @@ -1,125 +1,121 @@ export const toBase64 = (file: File): Promise => new Promise((resolve, reject) => { - const reader = new FileReader() - reader.readAsDataURL(file) - reader.onload = () => resolve(reader.result) - reader.onerror = (error) => { - 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) + const reader = new FileReader(); + reader.readAsDataURL(file); + reader.onload = () => resolve(reader.result); + reader.onerror = error => { + reject(error); + }; + }); + +export const objectToBase64 = async (obj: any): Promise => { + // 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,", + "" + ); + console.log(`base64 resolution: ${base64}`); + resolve(base64); + } else { + reject(new Error("Failed to read the Blob as a base64-encoded string")); } - reader.readAsDataURL(blob) - }) + }; + 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( + "" + ); } - export function objectToUint8Array(obj: any) { - // Convert the object to a JSON string - const jsonString = JSON.stringify(obj) + return btoa(binaryString); +} - // Encode the JSON string as a byte array using TextEncoder - const encoder = new TextEncoder() - const byteArray = encoder.encode(jsonString) +export function objectToUint8ArrayFromResponse(obj: any) { + const len = Object.keys(obj).length; + const result = new Uint8Array(len); - // Create a new Uint8Array and set its content to the encoded byte array - const uint8Array = new Uint8Array(byteArray) - - return uint8Array + 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 - 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 < len; i++) { +// binary += String.fromCharCode(bytes[i]) +// } - 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(binary) +// } - return btoa(binaryString) - } - - - - export function objectToUint8ArrayFromResponse(obj: any) { - const len = Object.keys(obj).length - const result = new Uint8Array(len) +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++) { - result[i] = obj[i] - } - - return result + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); } - // 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) + return bytes; +} - 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); - 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); - // Convert the JSON string back into an object - const obj = JSON.parse(jsonString) + return obj; +} - return obj - } - - export function processFileInChunks(file: File): Promise { - return new Promise((resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => { +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) { @@ -133,38 +129,38 @@ export const toBase64 = (file: File): Promise => }; 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; +// } - // 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; - // } - \ No newline at end of file +// return combinedArray; +// }