mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-28 07:54:22 -04:00
forked!!
This commit is contained in:
parent
538b87062a
commit
ea7451bcdc
326 changed files with 24876 additions and 2280 deletions
27
src/equicordplugins/sekaiStickers/Components/Canvas.tsx
Normal file
27
src/equicordplugins/sekaiStickers/Components/Canvas.tsx
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { useEffect, useRef } from "@webpack/common";
|
||||
|
||||
const Canvas = props => {
|
||||
|
||||
const { draw, ...rest } = props;
|
||||
const canvasRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
// @ts-ignore
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
draw(context);
|
||||
|
||||
}, [draw]);
|
||||
|
||||
return <canvas ref={canvasRef} {...rest} />;
|
||||
};
|
||||
|
||||
export default Canvas;
|
51
src/equicordplugins/sekaiStickers/Components/Picker.tsx
Normal file
51
src/equicordplugins/sekaiStickers/Components/Picker.tsx
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Flex } from "@components/Flex";
|
||||
import { ModalCloseButton, ModalContent, ModalHeader, ModalProps, ModalRoot, ModalSize } from "@utils/modal";
|
||||
import { React, ScrollerThin, Text, TextInput } from "@webpack/common";
|
||||
|
||||
import { characters } from "../characters.json";
|
||||
|
||||
export default function CharSelectModal({ modalProps, setCharacter }: { modalProps: ModalProps; setCharacter?: any; }) {
|
||||
const [search, setSearch] = React.useState<string>("");
|
||||
|
||||
const memoedSearchChar = React.useMemo(() => {
|
||||
const s = search.toLowerCase();
|
||||
return characters.map((c, index) => {
|
||||
if (
|
||||
s === c.id ||
|
||||
c.name.toLowerCase().includes(s) ||
|
||||
c.character.toLowerCase().includes(s)
|
||||
) {
|
||||
return (
|
||||
<img key={index} onClick={() => { modalProps.onClose(); setCharacter(index); }} src={`https://st.ayaka.one/img/${c.img}`} srcSet={`https://st.ayaka.one/img/${c.img}`} loading="lazy" />
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
}, [search, characters]);
|
||||
return (
|
||||
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/bold" style={{ flexGrow: 1 }}>Select character menu</Text>
|
||||
<ModalCloseButton onClick={modalProps.onClose} ></ModalCloseButton>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Flex flexDirection="column" style={{ paddingTop: 12 }}>
|
||||
<TextInput content="mafuyu" placeholder="Mafuyu" onChange={(e: string) => setSearch(e)} />
|
||||
<ScrollerThin style={{ height: 520 }}>
|
||||
<div style={{ display: "grid", gridTemplateColumns: "repeat(3, 330px)", rowGap: 6, columnGap: 5, gridTemplateRows: "repeat(3, 256px)" }}>
|
||||
{memoedSearchChar}
|
||||
</div>
|
||||
</ScrollerThin>
|
||||
</Flex>
|
||||
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { Flex } from "@components/Flex";
|
||||
import { ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
|
||||
import { Button, ChannelStore, Forms, React, SelectedChannelStore, Slider, Switch, Text, TextArea, UploadHandler } from "@webpack/common";
|
||||
|
||||
import { characters } from "../characters.json";
|
||||
import Canvas from "./Canvas";
|
||||
import CharSelectModal from "./Picker";
|
||||
|
||||
export default function SekaiStickersModal({ modalProps, settings }: { modalProps: ModalProps; settings: any; }) {
|
||||
const [text, setText] = React.useState<string>("奏でーかわいい");
|
||||
const [character, setChracter] = React.useState<number>(49);
|
||||
const [fontSize, setFontSize] = React.useState<number>(characters[character].defaultText.s);
|
||||
const [rotate, setRotate] = React.useState<number>(characters[character].defaultText.r);
|
||||
const [curve, setCurve] = React.useState<boolean>(false);
|
||||
const [isImgLoaded, setImgLoaded] = React.useState<boolean>(false);
|
||||
const [position, setPosition] = React.useState<{ x: number, y: number; }>({ x: characters[character].defaultText.x, y: characters[character].defaultText.y });
|
||||
const [spaceSize, setSpaceSize] = React.useState<number>(1);
|
||||
let canvast!: HTMLCanvasElement;
|
||||
const img = new Image();
|
||||
img.crossOrigin = "anonymous";
|
||||
img.src = "https://st.ayaka.one/img/" + characters[character].img;
|
||||
|
||||
React.useEffect(() => {
|
||||
setPosition({
|
||||
x: characters[character].defaultText.x,
|
||||
y: characters[character].defaultText.y
|
||||
});
|
||||
setFontSize(characters[character].defaultText.s);
|
||||
setRotate(characters[character].defaultText.r);
|
||||
setImgLoaded(false);
|
||||
}, [character]);
|
||||
|
||||
img.onload = () => { setImgLoaded(true); };
|
||||
const angle = (Math.PI * text.length) / 7;
|
||||
|
||||
const draw = ctx => {
|
||||
ctx.canvas.width = 296;
|
||||
ctx.canvas.height = 256;
|
||||
|
||||
if (isImgLoaded && document.fonts.check("12px YurukaStd")) {
|
||||
const hRatio = ctx.canvas.width / img.width;
|
||||
const vRatio = ctx.canvas.height / img.height;
|
||||
const ratio = Math.min(hRatio, vRatio);
|
||||
const centerShiftX = (ctx.canvas.width - img.width * ratio) / 2;
|
||||
const centerShiftY = (ctx.canvas.height - img.height * ratio) / 2;
|
||||
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||
ctx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
img.width,
|
||||
img.height,
|
||||
centerShiftX,
|
||||
centerShiftY,
|
||||
img.width * ratio,
|
||||
img.height * ratio
|
||||
);
|
||||
ctx.font = `${fontSize}px YurukaStd, SSFangTangTi`;
|
||||
ctx.lineWidth = 9;
|
||||
ctx.save();
|
||||
|
||||
ctx.translate(position.x, position.y);
|
||||
ctx.rotate(rotate / 10);
|
||||
ctx.textAlign = "center";
|
||||
ctx.strokeStyle = "white";
|
||||
ctx.fillStyle = characters[character].color;
|
||||
const lines = text.split("\n");
|
||||
if (curve) {
|
||||
for (const line of lines) {
|
||||
for (let i = 0; i < line.length; i++) {
|
||||
ctx.rotate(angle / line.length / 2.5);
|
||||
ctx.save();
|
||||
ctx.translate(0, -1 * fontSize * 3.5);
|
||||
ctx.strokeText(line[i], 0, -1 * spaceSize);
|
||||
ctx.fillText(line[i], 0, -1 * spaceSize);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let i = 0, k = 0; i < lines.length; i++) {
|
||||
ctx.strokeText(lines[i], 0, k);
|
||||
ctx.fillText(lines[i], 0, k);
|
||||
k += spaceSize;
|
||||
}
|
||||
ctx.restore();
|
||||
}
|
||||
canvast = ctx.canvas;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<ModalRoot {...modalProps} size={ModalSize.DYNAMIC}>
|
||||
<ModalHeader>
|
||||
<Text variant="heading-lg/bold" style={{ flexGrow: 1 }}>Sekai Stickers</Text>
|
||||
<ModalCloseButton onClick={modalProps.onClose} ></ModalCloseButton>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Flex flexDirection="row" style={{ paddingTop: 12 }}>
|
||||
<div style={{ marginRight: 30 }}>
|
||||
<Canvas draw={draw} id="SekaiCard_Canvas" />
|
||||
<Forms.FormTitle>Text Y Pos</Forms.FormTitle>
|
||||
<Slider minValue={0} maxValue={256} asValueChanges={va => { va = Math.round(va); setPosition({ x: position.x, y: curve ? 256 + fontSize * 3 - va : 256 - va }); }} initialValue={curve ? 256 - position.y + fontSize * 3 : 256 - position.y} orientation={"vertical"} onValueRender={va => String(Math.round(va))} />
|
||||
<Forms.FormTitle>Text XZ Pos</Forms.FormTitle>
|
||||
<Slider minValue={0} maxValue={296} asValueChanges={va => { va = Math.round(va); setPosition({ y: position.y, x: va }); }} initialValue={position.x} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
|
||||
</div>
|
||||
<div style={{ marginRight: 10, width: "30vw" }}>
|
||||
<Forms.FormTitle>Text</Forms.FormTitle>
|
||||
<TextArea onChange={setText} placeholder={text} rows={4} spellCheck={false} />
|
||||
<Forms.FormTitle>Rotation</Forms.FormTitle>
|
||||
<Slider markers={[-10, -5, 0, 5, 10]} stickToMarkers={false} minValue={-10} maxValue={10} asValueChanges={val => setRotate(val)} initialValue={rotate} keyboardStep={0.2} orientation={"horizontal"} onValueRender={(v: number) => String(v.toFixed(2))} />
|
||||
<Forms.FormTitle>Font Size</Forms.FormTitle>
|
||||
<Slider minValue={10} asValueChanges={val => setFontSize(Math.round(val))} maxValue={100} initialValue={fontSize} keyboardStep={1} orientation={"horizontal"} onValueRender={(v: number) => String(Math.round(v))} />
|
||||
<Forms.FormTitle>Spacing</Forms.FormTitle>
|
||||
<Slider markers={[18, 36, 72, 100]} stickToMarkers={false} minValue={18} maxValue={100} initialValue={spaceSize} asValueChanges={e => setSpaceSize(e)} onValueRender={e => String(Math.round(e))} />
|
||||
<Switch value={curve} onChange={val => setCurve(val)}>Enable curve</Switch>
|
||||
<Button onClick={() => openModal(props => <CharSelectModal modalProps={props} setCharacter={setChracter} />)}>Switch Character</Button>
|
||||
</div>
|
||||
</Flex>
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<Flex flexDirection="row" style={{ gap: 12 }}>
|
||||
<Button onClick={() => {
|
||||
if (settings.store.AutoCloseModal) modalProps.onClose();
|
||||
canvast.toBlob(blob => {
|
||||
const file = new File([blob as Blob], `${characters[character].character}-sekai_cards.png`, { type: "image/png" });
|
||||
UploadHandler.promptToUpload([file], ChannelStore.getChannel(SelectedChannelStore.getChannelId()), 0);
|
||||
});
|
||||
}}>Upload as Attachment</Button>
|
||||
</Flex>
|
||||
</ModalFooter>
|
||||
</ModalRoot>
|
||||
);
|
||||
}
|
5035
src/equicordplugins/sekaiStickers/characters.json.ts
Normal file
5035
src/equicordplugins/sekaiStickers/characters.json.ts
Normal file
File diff suppressed because it is too large
Load diff
54
src/equicordplugins/sekaiStickers/index.tsx
Normal file
54
src/equicordplugins/sekaiStickers/index.tsx
Normal file
|
@ -0,0 +1,54 @@
|
|||
|
||||
/*
|
||||
* Vencord, a Discord client mod
|
||||
* Copyright (c) 2024 Vendicated and contributors
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*/
|
||||
|
||||
import { addChatBarButton, ChatBarButton, removeChatBarButton } from "@api/ChatButtons";
|
||||
import { definePluginSettings } from "@api/Settings";
|
||||
import { Devs } from "@utils/constants";
|
||||
import { openModal } from "@utils/modal";
|
||||
import definePlugin, { OptionType } from "@utils/types";
|
||||
|
||||
import SekaiStickersModal from "./Components/SekaiStickersModal";
|
||||
import { kanadeSvg } from "./kanade.svg";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
AutoCloseModal: {
|
||||
type: OptionType.BOOLEAN,
|
||||
description: "Auto close modal when done",
|
||||
default: true
|
||||
},
|
||||
});
|
||||
|
||||
const SekaiStickerChatButton: ChatBarButton = () => {
|
||||
return (
|
||||
<ChatBarButton onClick={() => openModal(props => <SekaiStickersModal modalProps={props} settings={settings} />)} tooltip="Sekai Stickers">
|
||||
{kanadeSvg()}
|
||||
</ChatBarButton>
|
||||
);
|
||||
};
|
||||
|
||||
let IS_FONTS_LOADED = false;
|
||||
export default definePlugin({
|
||||
name: "Sekai Stickers",
|
||||
description: "Sekai Stickers built in discord originally from github.com/TheOriginalAyaka",
|
||||
authors: [Devs.MaiKokain],
|
||||
dependencies: ["ChatInputButtonAPI"],
|
||||
settings,
|
||||
start: async () => {
|
||||
const fonts = [{ name: "YurukaStd", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/YurukaStd.woff2" }, { name: "SSFangTangTi", url: "https://raw.githubusercontent.com/TheOriginalAyaka/sekai-stickers/main/src/fonts/ShangShouFangTangTi.woff2" }];
|
||||
if (!IS_FONTS_LOADED) {
|
||||
fonts.map(n => {
|
||||
new FontFace(n.name, `url(${n.url})`).load().then(
|
||||
font => { document.fonts.add(font); },
|
||||
err => { console.log(err); }
|
||||
);
|
||||
});
|
||||
IS_FONTS_LOADED = true;
|
||||
}
|
||||
addChatBarButton("SekaiStickers", SekaiStickerChatButton);
|
||||
},
|
||||
stop: () => removeChatBarButton("SekaiStickers")
|
||||
});
|
21
src/equicordplugins/sekaiStickers/kanade.svg.tsx
Normal file
21
src/equicordplugins/sekaiStickers/kanade.svg.tsx
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue