Browse Source

New Forum Modal design implmented. Forum Submission in progress.

master
Qortal Dev 2 weeks ago
parent
commit
11d5a46f0c
  1. 2
      src/App.tsx
  2. 56
      src/components/common/SelectField.tsx
  3. 2
      src/constants/Identifiers.ts
  4. 3
      src/constants/Misc.ts
  5. 10
      src/pages/Forum/Forum.tsx
  6. 130
      src/pages/Forum/GroupPermissionsForm.tsx
  7. 289
      src/pages/Forum/NewForumModal-data.ts
  8. 211
      src/pages/Forum/NewForumModal.tsx
  9. 58
      src/pages/Forum/QmailTextField.tsx
  10. 1
      src/pages/Home/Home-styles.ts
  11. 15
      src/pages/Home/Home.tsx
  12. 4
      src/pages/Mail/NewThread.tsx
  13. 27
      src/utils/QortalRequests.ts
  14. 270
      src/utils/toBase64.ts

2
src/App.tsx

@ -22,9 +22,9 @@ function App() {
<DownloadWrapper>
<GlobalWrapper>
<CssBaseline />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/sponsorshipData" element={<Home />} />
</Routes>
</GlobalWrapper>
</DownloadWrapper>

56
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<string>;
label: string;
sx?: SxProps;
}
export const SelectField = ({
options,
value,
label,
sx = {},
}: SelectFieldProps) => {
useSignals();
return (
<FormControl fullWidth>
<InputLabel
sx={{
fontSize: "100%",
color: "rgba(84, 84, 84, 0.70)",
}}
id={label}
>
{label}
</InputLabel>
<Select
fullWidth
labelId={label}
value={value.value || ""}
variant={"standard"}
onChange={e => {
value.value = e.target.value;
}}
sx={{ color: "black", ...sx }}
>
{options.map(option => (
<MenuItem key={option} value={option}>
{option}
</MenuItem>
))}
</Select>
</FormControl>
);
};

2
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

3
src/constants/Misc.ts

@ -1 +1,4 @@
export const appOwner = "Q-Mintership";
const maxSizeMB = 25;
export const maxPrivateFileSize = maxSizeMB * 1024 * 1024;

10
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) => {

130
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<string>;
name: string;
permissions: Signal<GroupPermissionType>;
}
export interface GroupPermissionsFormProps {
groups: Signal<Group[]>;
}
export const GroupPermissionsForm = ({ groups }: GroupPermissionsFormProps) => {
useSignals();
const uid = new ShortUniqueId();
const newGroup = {
id: signal<string>(""),
name: "",
permissions: signal<GroupPermissionType>("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 (
<Box
sx={{
display: "flex",
width: "100%",
flexDirection: "column",
alignItems: "end",
}}
>
{groups.value.map((group, index) => (
<NewMessageInputRow
sx={{
height: "80px",
width: "100%",
display: "grid",
gridTemplateColumns: "1fr 1fr",
alignItems: "end",
}}
key={uid()}
>
<Box>
<StyledIconButton
onClick={addGroup}
sx={{
visibility:
index === groups.value.length - 1 ? "visible" : "hidden",
}}
>
<AddIcon sx={iconStyle} />
</StyledIconButton>
<StyledIconButton onClick={() => removeGroup(index)}>
<RemoveIcon sx={{ ...iconStyle, fontSize: 50 }} />
</StyledIconButton>
<QmailTextField
value={group.id}
label={"Group ID"}
filter={/[^0-9]/}
sx={{
width: "50%",
height: "70px",
borderBottom: "1px solid gray",
}}
maxLength={10}
/>
</Box>
<SelectField
options={options}
value={group.permissions}
label={"Group Permissions"}
sx={{
"& .MuiSvgIcon-root": {
color: "gray",
},
}}
/>
</NewMessageInputRow>
))}
</Box>
);
};

289
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");
// }
};

211
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<boolean>(false);
const forumTitle = useSignal<string>("");
const description = useSignal<string>("");
const selectedEncryptionType = useSignal<EncryptionType | "">("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<Group[]>([
{
id: signal<string>(""),
name: "",
permissions: signal<GroupPermissionType>("Read"),
},
]);
return (
<Box
sx={{
display: "flex",
}}
>
<InstanceContainer>
{(user?.name === appOwner || useTestIdentifiers) && (
<ComposeContainer onClick={e => (isOpen.value = true)}>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{"New Forum"}</ComposeP>
</ComposeContainer>
)}
<ComposeContainer onClick={e => navigate("/sponsorshipData")}>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{"Sponsorship Data"}</ComposeP>
</ComposeContainer>
</InstanceContainer>
<ReusableModal
open={isOpen.value}
customStyles={{
maxHeight: "95vh",
maxWidth: "950px",
height: "700px",
borderRadius: "12px 12px 0px 0px",
background: "var(--Mail-Background, #313338)",
padding: "0px",
gap: "0px",
}}
>
<InstanceListHeader
sx={{
backgroundColor: "unset",
height: "50px",
padding: "20px 42px",
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<NewMessageHeaderP>{"New Forum"}</NewMessageHeaderP>
<CloseContainer onClick={closeModal}>
<NewMessageCloseImg src={ModalCloseSVG} />
</CloseContainer>
</InstanceListHeader>
<InstanceListContainer
sx={{
backgroundColor: "rgba(217, 217, 217, 1)",
padding: "20px 42px",
height: "calc(100% - 150px)",
flexShrink: 0,
}}
>
<NewMessageInputRow sx={{ height: "80px", alignItems: "end" }}>
<QmailTextField
value={forumTitle}
label={"Forum Title"}
sx={{
height: "60px",
borderBottom: "1px solid gray",
}}
maxLength={30}
/>
<SelectField
options={["None", "Group", "GroupAdmin"]}
label={"Encryption Type"}
value={selectedEncryptionType}
sx={{
"& .MuiSvgIcon-root": {
color: "gray",
},
}}
/>
</NewMessageInputRow>
<GroupPermissionsForm groups={groups} />
<Spacer height="40px" />
<Box
sx={{
maxHeight: "40vh",
}}
>
<p style={{ color: "black" }}> Description </p>
<TextEditor
inlineContent={description.value}
setInlineContent={(val: any) => {
description.value = val;
}}
/>
</Box>
</InstanceListContainer>
<InstanceFooter
sx={{
backgroundColor: "rgba(217, 217, 217, 1)",
padding: "20px 42px",
alignItems: "center",
height: "90px",
}}
>
<NewMessageSendButton onClick={() => publishForum(formData.value)}>
<NewMessageSendP>{"Create Forum"}</NewMessageSendP>
<CreateThreadIcon
color="red"
opacity={1}
height="25px"
width="25px"
/>
</NewMessageSendButton>
</InstanceFooter>
</ReusableModal>
</Box>
);
};

58
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<string>;
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 (
<>
<Input
id="standard-adornment-name"
value={value.value}
onChange={e => {
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,
}}
/>
</>
);
};

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

15
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 (
<Box>
<InstanceContainer>
{(user?.name === appOwner || useTestIdentifiers) && (
<ComposeContainer onClick={publishNewForum}>
<ComposeIcon src={ComposeIconSVG} />
<ComposeP>{"New Forum"}</ComposeP>
</ComposeContainer>
)}
</InstanceContainer>
<NewForumModal />
</Box>
);
};

4
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();

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

270
src/utils/toBase64.ts

@ -1,125 +1,121 @@
export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
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<string>((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<string> => {
// 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<Uint8Array> {
return new Promise((resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => {
export function processFileInChunks(file: File): Promise<Uint8Array> {
return new Promise(
(resolve: (value: Uint8Array) => void, reject: (reason?: any) => void) => {
const reader = new FileReader();
reader.onload = function (event: ProgressEvent<FileReader>) {
@ -133,38 +129,38 @@ export const toBase64 = (file: File): Promise<string | ArrayBuffer | null> =>
};
reader.readAsArrayBuffer(file);
});
}
}
);
}
// export async function processFileInChunks(file: File, chunkSize = 1024 * 1024): Promise<Uint8Array> {
// 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<Uint8Array> {
// 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;
// }
// return combinedArray;
// }

Loading…
Cancel
Save