mirror of
https://github.com/Equicord/Equicord.git
synced 2025-01-31 03:33:36 -05:00
Quoter Update
This commit is contained in:
parent
e38a70a25e
commit
8f93f62ef0
3 changed files with 276 additions and 148 deletions
15
src/equicordplugins/quoter/components.tsx
Normal file
15
src/equicordplugins/quoter/components.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
export function QuoteIcon() {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M21 3C21.5523 3 22 3.44772 22 4V18C22 18.5523 21.5523 19 21 19H6.455L2 22.5V4C2 3.44772 2.44772 3 3 3H21ZM20 5H4V18.385L5.76333 17H20V5ZM10.5153 7.4116L10.9616 8.1004C9.29402 9.0027 9.32317 10.4519 9.32317 10.7645C9.47827 10.7431 9.64107 10.7403 9.80236 10.7553C10.7045 10.8389 11.4156 11.5795 11.4156 12.5C11.4156 13.4665 10.6321 14.25 9.66558 14.25C9.12905 14.25 8.61598 14.0048 8.29171 13.6605C7.77658 13.1137 7.5 12.5 7.5 11.5052C7.5 9.75543 8.72825 8.18684 10.5153 7.4116ZM15.5153 7.4116L15.9616 8.1004C14.294 9.0027 14.3232 10.4519 14.3232 10.7645C14.4783 10.7431 14.6411 10.7403 14.8024 10.7553C15.7045 10.8389 16.4156 11.5795 16.4156 12.5C16.4156 13.4665 15.6321 14.25 14.6656 14.25C14.1291 14.25 13.616 14.0048 13.2917 13.6605C12.7766 13.1137 12.5 12.5 12.5 11.5052C12.5 9.75543 13.7283 8.18684 15.5153 7.4116Z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
|
@ -5,26 +5,26 @@
|
|||
*/
|
||||
|
||||
import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { getCurrentChannel } from "@utils/discord";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import definePlugin from "@utils/types";
|
||||
import { Button, Menu, Switch, Text, UploadHandler, useEffect, useState } from "@webpack/common";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
import { Button, Menu, Select, Switch, Text, TextInput, UploadHandler, useEffect, UserStore, useState } from "@webpack/common";
|
||||
import { Message } from "discord-types/general";
|
||||
|
||||
let recentmessage: Message;
|
||||
let grayscale;
|
||||
import { QuoteIcon } from "./components";
|
||||
import { canvasToBlob, fetchImageAsBlob, FixUpQuote, wrapText } from "./utils";
|
||||
|
||||
const messagePatch: NavContextMenuPatchCallback = (children, { message }) => () => {
|
||||
enum ImageStyle {
|
||||
inspirational
|
||||
}
|
||||
|
||||
const messagePatch: NavContextMenuPatchCallback = (children, { message }) => {
|
||||
recentmessage = message;
|
||||
if (!message.content) return;
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-text", children);
|
||||
if (!group) return;
|
||||
|
||||
group.splice(
|
||||
group.findIndex(c => c?.props?.id === "copy-text") + 1,
|
||||
0,
|
||||
const buttonElement =
|
||||
<Menu.MenuItem
|
||||
id="vc-quote"
|
||||
label="Quote"
|
||||
|
@ -32,39 +32,110 @@ const messagePatch: NavContextMenuPatchCallback = (children, { message }) => ()
|
|||
action={async () => {
|
||||
openModal(props => <QuoteModal {...props} />);
|
||||
}}
|
||||
/>
|
||||
/>;
|
||||
|
||||
const group = findGroupChildrenByChildId("copy-text", children);
|
||||
if (!group) {
|
||||
children.push(buttonElement);
|
||||
return;
|
||||
}
|
||||
|
||||
group.splice(
|
||||
group.findIndex(c => c?.props?.id === "copy-text") + 1, 0, buttonElement
|
||||
);
|
||||
};
|
||||
|
||||
export function QuoteIcon({
|
||||
height = 24,
|
||||
width = 24,
|
||||
className
|
||||
}: {
|
||||
height?: number;
|
||||
width?: number;
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path
|
||||
d="M21 3C21.5523 3 22 3.44772 22 4V18C22 18.5523 21.5523 19 21 19H6.455L2 22.5V4C2 3.44772 2.44772 3 3 3H21ZM20 5H4V18.385L5.76333 17H20V5ZM10.5153 7.4116L10.9616 8.1004C9.29402 9.0027 9.32317 10.4519 9.32317 10.7645C9.47827 10.7431 9.64107 10.7403 9.80236 10.7553C10.7045 10.8389 11.4156 11.5795 11.4156 12.5C11.4156 13.4665 10.6321 14.25 9.66558 14.25C9.12905 14.25 8.61598 14.0048 8.29171 13.6605C7.77658 13.1137 7.5 12.5 7.5 11.5052C7.5 9.75543 8.72825 8.18684 10.5153 7.4116ZM15.5153 7.4116L15.9616 8.1004C14.294 9.0027 14.3232 10.4519 14.3232 10.7645C14.4783 10.7431 14.6411 10.7403 14.8024 10.7553C15.7045 10.8389 16.4156 11.5795 16.4156 12.5C16.4156 13.4665 15.6321 14.25 14.6656 14.25C14.1291 14.25 13.616 14.0048 13.2917 13.6605C12.7766 13.1137 12.5 12.5 12.5 11.5052C12.5 9.75543 13.7283 8.18684 15.5153 7.4116Z"
|
||||
></path>
|
||||
</svg>
|
||||
);
|
||||
let recentmessage: Message;
|
||||
let grayscale;
|
||||
let setStyle: ImageStyle = ImageStyle.inspirational;
|
||||
let customMessage: string = "";
|
||||
let customImage: string = "";
|
||||
let customName: string = "";
|
||||
let isUserCustomCapable = false;
|
||||
|
||||
enum userIDOptions {
|
||||
usernameNormalized,
|
||||
userName,
|
||||
userId
|
||||
}
|
||||
const settings = definePluginSettings({
|
||||
userIdentifier:
|
||||
{
|
||||
type: OptionType.SELECT,
|
||||
description: "What the author's name should be displayed as",
|
||||
options: [
|
||||
{ label: "Username Normalized", value: userIDOptions.usernameNormalized, default: true },
|
||||
{ label: "Username", value: userIDOptions.userName },
|
||||
{ label: "User ID", value: userIDOptions.userId }
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
export default definePlugin({
|
||||
name: "Quoter",
|
||||
description: "Adds the ability to create an inspirational quote image from a message",
|
||||
authors: [Devs.Samwich],
|
||||
contextMenus: {
|
||||
"message": messagePatch
|
||||
},
|
||||
settings
|
||||
});
|
||||
|
||||
function sizeUpgrade(url) {
|
||||
const u = new URL(url);
|
||||
u.searchParams.set("size", "1024");
|
||||
u.searchParams.set("size", "512");
|
||||
return u.toString();
|
||||
}
|
||||
|
||||
let preparingSentence: string[] = [];
|
||||
const preparingSentence: string[] = [];
|
||||
const lines: string[] = [];
|
||||
|
||||
async function createQuoteImage(avatarUrl: string, name: string, quoteOld: string, grayScale: boolean): Promise<Blob> {
|
||||
const quote = removeCustomEmojis(quoteOld);
|
||||
async function createQuoteImage(avatarUrl: string, quoteOld: string, grayScale: boolean): Promise<Blob> {
|
||||
let name: string = "";
|
||||
|
||||
switch (settings.store.userIdentifier) {
|
||||
case userIDOptions.usernameNormalized:
|
||||
const meow = recentmessage.author.usernameNormalized;
|
||||
if (meow) {
|
||||
name = meow;
|
||||
}
|
||||
else {
|
||||
name = recentmessage.author.username;
|
||||
}
|
||||
break;
|
||||
case userIDOptions.userName:
|
||||
name = recentmessage.author.username;
|
||||
break;
|
||||
case userIDOptions.userId:
|
||||
name = recentmessage.author.id;
|
||||
break;
|
||||
default:
|
||||
name = "MAN WTF HAPPENED";
|
||||
break;
|
||||
}
|
||||
let quote;
|
||||
if (isUserCustomCapable && customMessage.length > 0 && customImage.length > 0 && customName.length > 0) {
|
||||
quote = FixUpQuote(customMessage);
|
||||
avatarUrl = customImage;
|
||||
name = customName;
|
||||
} else if (isUserCustomCapable && customMessage.length > 0 && customImage.length > 0) {
|
||||
quote = FixUpQuote(customMessage);
|
||||
avatarUrl = customImage;
|
||||
} else if (isUserCustomCapable && customMessage.length > 0 && customName.length > 0) {
|
||||
quote = FixUpQuote(customMessage);
|
||||
name = customName;
|
||||
} else if (isUserCustomCapable && customImage.length > 0 && customName.length > 0) {
|
||||
avatarUrl = customImage;
|
||||
name = customName;
|
||||
} else if (isUserCustomCapable && customImage.length > 0) {
|
||||
avatarUrl = customImage;
|
||||
} else if (isUserCustomCapable && customName.length > 0) {
|
||||
name = customName;
|
||||
} else if (isUserCustomCapable && customMessage.length > 0) {
|
||||
quote = FixUpQuote(customMessage);
|
||||
} else {
|
||||
quote = FixUpQuote(quoteOld);
|
||||
}
|
||||
const canvas = document.createElement("canvas");
|
||||
const ctx = canvas.getContext("2d");
|
||||
|
||||
|
@ -72,120 +143,100 @@ async function createQuoteImage(avatarUrl: string, name: string, quoteOld: strin
|
|||
throw new Error("Cant get 2d rendering context :(");
|
||||
}
|
||||
|
||||
const cardWidth = 1200;
|
||||
const cardHeight = 600;
|
||||
switch (setStyle) {
|
||||
case ImageStyle.inspirational:
|
||||
|
||||
canvas.width = cardWidth;
|
||||
canvas.height = cardHeight;
|
||||
const cardWidth = 1200;
|
||||
const cardHeight = 600;
|
||||
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
canvas.width = cardWidth;
|
||||
canvas.height = cardHeight;
|
||||
|
||||
const avatarBlob = await fetchImageAsBlob(avatarUrl);
|
||||
const fadeBlob = await fetchImageAsBlob("https://files.catbox.moe/54e96l.png");
|
||||
ctx.fillStyle = "#000";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
|
||||
const avatar = new Image();
|
||||
const fade = new Image();
|
||||
const avatarBlob = await fetchImageAsBlob(avatarUrl);
|
||||
const fadeBlob = await fetchImageAsBlob("https://files.catbox.moe/54e96l.png");
|
||||
|
||||
const avatarPromise = new Promise<void>(resolve => {
|
||||
avatar.onload = () => resolve();
|
||||
avatar.src = URL.createObjectURL(avatarBlob);
|
||||
});
|
||||
const avatar = new Image();
|
||||
const fade = new Image();
|
||||
|
||||
const fadePromise = new Promise<void>(resolve => {
|
||||
fade.onload = () => resolve();
|
||||
fade.src = URL.createObjectURL(fadeBlob);
|
||||
});
|
||||
const avatarPromise = new Promise<void>(resolve => {
|
||||
avatar.onload = () => resolve();
|
||||
avatar.src = URL.createObjectURL(avatarBlob);
|
||||
});
|
||||
|
||||
await Promise.all([avatarPromise, fadePromise]);
|
||||
const fadePromise = new Promise<void>(resolve => {
|
||||
fade.onload = () => resolve();
|
||||
fade.src = URL.createObjectURL(fadeBlob);
|
||||
});
|
||||
|
||||
if (grayScale) {
|
||||
ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight);
|
||||
ctx.globalCompositeOperation = "saturation";
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
} else {
|
||||
ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight);
|
||||
}
|
||||
ctx.drawImage(fade, cardHeight - 400, 0, 400, cardHeight);
|
||||
await Promise.all([avatarPromise, fadePromise]);
|
||||
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "italic 20px Georgia";
|
||||
const quoteWidth = cardWidth / 2 - 50;
|
||||
const quoteX = ((cardWidth - cardHeight));
|
||||
const quoteY = cardHeight / 2 - 10;
|
||||
wrapText(ctx, quote, quoteX, quoteY, quoteWidth, 20);
|
||||
ctx.drawImage(avatar, 0, 0, cardHeight, cardHeight);
|
||||
|
||||
const wrappedTextHeight = lines.length * 25;
|
||||
|
||||
ctx.font = "bold 16px Georgia";
|
||||
const authorNameX = (cardHeight * 1.5) - (ctx.measureText(`- ${name}`).width / 2) - 30;
|
||||
const authorNameY = quoteY + wrappedTextHeight + 30;
|
||||
|
||||
ctx.fillText(`- ${name}`, authorNameX, authorNameY);
|
||||
preparingSentence.length = 0;
|
||||
lines.length = 0;
|
||||
return new Promise<Blob>(resolve => {
|
||||
canvas.toBlob(blob => {
|
||||
if (blob) {
|
||||
resolve(blob);
|
||||
} else {
|
||||
throw new Error("Failed to create Blob");
|
||||
if (grayScale) {
|
||||
ctx.globalCompositeOperation = "saturation";
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, cardWidth, cardHeight);
|
||||
ctx.globalCompositeOperation = "source-over";
|
||||
}
|
||||
}, "image/png");
|
||||
});
|
||||
|
||||
function wrapText(
|
||||
context: CanvasRenderingContext2D,
|
||||
text: string,
|
||||
x: number,
|
||||
y: number,
|
||||
maxWidth: number,
|
||||
lineHeight: number
|
||||
) {
|
||||
const words = text.split(" ");
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const workSentence = preparingSentence.join(" ") + " " + words[i];
|
||||
ctx.drawImage(fade, cardHeight - 400, 0, 400, cardHeight);
|
||||
|
||||
if (context.measureText(workSentence).width > maxWidth) {
|
||||
lines.push(preparingSentence.join(" "));
|
||||
preparingSentence = [words[i]];
|
||||
} else {
|
||||
preparingSentence.push(words[i]);
|
||||
}
|
||||
}
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.font = "italic 20px Georgia";
|
||||
const quoteWidth = cardWidth / 2 - 50;
|
||||
const quoteX = ((cardWidth - cardHeight));
|
||||
const quoteY = cardHeight / 2 - 10;
|
||||
wrapText(ctx, `"${quote}"`, quoteX, quoteY, quoteWidth, 20, preparingSentence, lines);
|
||||
|
||||
lines.push(preparingSentence.join(" "));
|
||||
const wrappedTextHeight = lines.length * 25;
|
||||
|
||||
lines.forEach(element => {
|
||||
const lineWidth = context.measureText(element).width;
|
||||
const xOffset = (maxWidth - lineWidth) / 2;
|
||||
ctx.font = "bold 16px Georgia";
|
||||
const authorNameX = (cardHeight * 1.5) - (ctx.measureText(`- ${name}`).width / 2) - 30;
|
||||
const authorNameY = quoteY + wrappedTextHeight + 30;
|
||||
|
||||
y += lineHeight;
|
||||
context.fillText(element, x + xOffset, y);
|
||||
});
|
||||
ctx.fillText(`- ${name}`, authorNameX, authorNameY);
|
||||
preparingSentence.length = 0;
|
||||
lines.length = 0;
|
||||
return await canvasToBlob(canvas);
|
||||
}
|
||||
|
||||
async function fetchImageAsBlob(url: string): Promise<Blob> {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
return blob;
|
||||
}
|
||||
|
||||
function removeCustomEmojis(quote) {
|
||||
const emojiRegex = /<a?:(\w+):(\d+)>/g;
|
||||
return quote.replace(emojiRegex, "");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function registerStyleChange(style) {
|
||||
setStyle = style;
|
||||
GeneratePreview();
|
||||
}
|
||||
|
||||
async function setIsUserCustomCapable() {
|
||||
const allowList: string[] = await fetch("https://raw.githubusercontent.com/Equicord/Ignore/main/quoterusers.json").then(e => e.json());
|
||||
isUserCustomCapable = allowList.includes(UserStore.getCurrentUser().id);
|
||||
}
|
||||
|
||||
|
||||
function QuoteModal(props: ModalProps) {
|
||||
setIsUserCustomCapable();
|
||||
const [gray, setGray] = useState(true);
|
||||
useEffect(() => {
|
||||
grayscale = gray;
|
||||
GeneratePreview();
|
||||
}, [gray]);
|
||||
|
||||
const safeTextContent = recentmessage && recentmessage.content ? recentmessage.content : "";
|
||||
const safeAvatarContent = recentmessage && recentmessage.author.avatar ? recentmessage.author.avatar : "";
|
||||
const safeUsernameContent = recentmessage && recentmessage.author.username ? recentmessage.author.username : "";
|
||||
|
||||
const [customText, setCustomText] = useState(safeTextContent);
|
||||
const [customAvatar, setCustomAvatar] = useState(safeAvatarContent);
|
||||
const [customUsername, setCustomUsername] = useState(safeUsernameContent);
|
||||
useEffect(() => {
|
||||
customMessage = customText;
|
||||
customImage = customAvatar;
|
||||
customName = customUsername;
|
||||
GeneratePreview();
|
||||
}, [customText]);
|
||||
|
||||
return (
|
||||
<ModalRoot {...props} size={ModalSize.MEDIUM}>
|
||||
<ModalHeader separator={false}>
|
||||
|
@ -197,7 +248,26 @@ function QuoteModal(props: ModalProps) {
|
|||
<ModalContent scrollbarType="none">
|
||||
<img src={""} id={"quoterPreview"} style={{ borderRadius: "20px", width: "100%" }}></img>
|
||||
<br></br><br></br>
|
||||
{isUserCustomCapable &&
|
||||
(
|
||||
<>
|
||||
<TextInput onChange={setCustomText} value={customText} placeholder="Custom Message"></TextInput>
|
||||
<br />
|
||||
<TextInput onChange={setCustomAvatar} value={customAvatar} placeholder="Custom Message"></TextInput>
|
||||
<br />
|
||||
<TextInput onChange={setCustomUsername} value={customUsername} placeholder="Custom Message"></TextInput>
|
||||
<br />
|
||||
</>
|
||||
)}
|
||||
<Switch value={gray} onChange={setGray}>Grayscale</Switch>
|
||||
<Select look={1}
|
||||
options={Object.keys(ImageStyle).filter(key => isNaN(parseInt(key, 10))).map(key => ({
|
||||
label: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
value: ImageStyle[key as keyof typeof ImageStyle]
|
||||
}))}
|
||||
select={v => registerStyleChange(v)} isSelected={v => v === setStyle}
|
||||
serialize={v => v}></Select>
|
||||
<br />
|
||||
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={() => Export()} style={{ display: "inline-block", marginRight: "5px" }}>Export</Button>
|
||||
<Button color={Button.Colors.BRAND_NEW} size={Button.Sizes.SMALL} onClick={() => SendInChat(props.onClose)} style={{ display: "inline-block" }}>Send</Button>
|
||||
</ModalContent>
|
||||
|
@ -207,47 +277,25 @@ function QuoteModal(props: ModalProps) {
|
|||
}
|
||||
|
||||
async function SendInChat(onClose) {
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.author.username, recentmessage.content, grayscale);
|
||||
const preview = generateFileNamePreview(recentmessage.content);
|
||||
const imageName = `${preview} - ${recentmessage.author.username}`;
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
|
||||
const imageName = `${new Date().toISOString()} - ${recentmessage.author.username}`;
|
||||
const file = new File([image], `${imageName}.png`, { type: "image/png" });
|
||||
UploadHandler.promptToUpload([file], getCurrentChannel(), 0);
|
||||
onClose();
|
||||
}
|
||||
|
||||
async function Export() {
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.author.username, recentmessage.content, grayscale);
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
|
||||
const link = document.createElement("a");
|
||||
link.href = URL.createObjectURL(image);
|
||||
const preview = generateFileNamePreview(recentmessage.content);
|
||||
|
||||
const imageName = `${preview} - ${recentmessage.author.username}`;
|
||||
const imageName = `${new Date().toISOString()} - ${recentmessage.author.username}`;
|
||||
link.download = `${imageName}.png`;
|
||||
link.click();
|
||||
link.remove();
|
||||
}
|
||||
|
||||
async function GeneratePreview() {
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.author.username, recentmessage.content, grayscale);
|
||||
const image = await createQuoteImage(sizeUpgrade(recentmessage.author.getAvatarURL()), recentmessage.content, grayscale);
|
||||
document.getElementById("quoterPreview")?.setAttribute("src", URL.createObjectURL(image));
|
||||
}
|
||||
|
||||
function generateFileNamePreview(message) {
|
||||
const words = message.split(" ");
|
||||
let preview;
|
||||
if (words.length >= 6) {
|
||||
preview = words.slice(0, 6).join(" ");
|
||||
} else {
|
||||
preview = words.slice(0, words.length).join(" ");
|
||||
}
|
||||
return preview;
|
||||
}
|
||||
|
||||
export default definePlugin({
|
||||
name: "Quoter",
|
||||
description: "Adds the ability to create a quote image from a message",
|
||||
authors: [Devs.Samwich],
|
||||
contextMenus: {
|
||||
"message": messagePatch
|
||||
}
|
||||
});
|
||||
|
|
65
src/equicordplugins/quoter/utils.tsx
Normal file
65
src/equicordplugins/quoter/utils.tsx
Normal file
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { UserStore } from "@webpack/common";
|
||||
|
||||
export function canvasToBlob(canvas: HTMLCanvasElement): Promise<Blob> {
|
||||
return new Promise<Blob>(resolve => {
|
||||
canvas.toBlob(blob => {
|
||||
if (blob) {
|
||||
resolve(blob);
|
||||
} else {
|
||||
throw new Error("Failed to create Blob");
|
||||
}
|
||||
}, "image/png");
|
||||
});
|
||||
}
|
||||
|
||||
export function wrapText(context: CanvasRenderingContext2D, text: string, x: number, y: number, maxWidth: number, lineHeight: number, preparingSentence: string[], lines: string[]) {
|
||||
const words = text.split(" ");
|
||||
for (let i = 0; i < words.length; i++) {
|
||||
const workSentence = preparingSentence.join(" ") + " " + words[i];
|
||||
|
||||
if (context.measureText(workSentence).width > maxWidth) {
|
||||
lines.push(preparingSentence.join(" "));
|
||||
preparingSentence = [words[i]];
|
||||
} else {
|
||||
preparingSentence.push(words[i]);
|
||||
}
|
||||
}
|
||||
|
||||
lines.push(preparingSentence.join(" "));
|
||||
|
||||
lines.forEach(element => {
|
||||
const lineWidth = context.measureText(element).width;
|
||||
const xOffset = (maxWidth - lineWidth) / 2;
|
||||
|
||||
y += lineHeight;
|
||||
context.fillText(element, x + xOffset, y);
|
||||
});
|
||||
}
|
||||
|
||||
export async function fetchImageAsBlob(url: string): Promise<Blob> {
|
||||
const response = await fetch(url);
|
||||
const blob = await response.blob();
|
||||
return blob;
|
||||
}
|
||||
|
||||
export function FixUpQuote(quote) {
|
||||
const emojiRegex = /<a?:(\w+):(\d+)>/g;
|
||||
quote = quote.replace(emojiRegex, "");
|
||||
|
||||
|
||||
const mentionRegex = /<@(.*)>/;
|
||||
let result = quote;
|
||||
|
||||
mentionRegex.exec(quote)?.forEach(match => {
|
||||
console.log(match);
|
||||
result = result.replace(match, `@${UserStore.getUser(match.replace("<@", "").replace(">", "")).username}`);
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
Loading…
Reference in a new issue