From 166159dc0ae69f025c2fc076b1b2047d7f3ebf3f Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Sat, 19 Oct 2024 01:19:15 -0400 Subject: [PATCH] Moved Some Stuff Around --- src/components/PluginSettings/PluginModal.tsx | 2 +- .../components/PickerCategoriesSidebar.tsx | 114 ------------ .../moreStickers/components/PickerHeader.tsx | 70 ------- .../moreStickers/components/categories.tsx | 88 +++++++++ .../moreStickers/components/categoryImage.tsx | 60 ------ .../components/categoryScroller.tsx | 37 ---- .../components/categoryWrapper.tsx | 27 --- .../moreStickers/components/header.tsx | 27 --- .../moreStickers/components/iconContainer.tsx | 31 ---- .../moreStickers/components/icons.tsx | 23 ++- .../moreStickers/components/index.ts | 10 + .../components/{settings.tsx => misc.tsx} | 76 +++++++- .../{PickerContent.tsx => picker.tsx} | 175 ++++++++++++------ .../moreStickers/components/recent.ts | 61 ------ .../components/stickerCategory.tsx | 43 ----- .../moreStickers/components/wrapper.tsx | 30 --- src/equicordplugins/moreStickers/index.tsx | 15 +- .../moreStickers/lineEmojis.ts | 2 +- src/equicordplugins/moreStickers/stickers.ts | 2 +- src/equicordplugins/moreStickers/testdata.ts | 12 +- src/equicordplugins/moreStickers/types.ts | 74 +++++++- src/equicordplugins/moreStickers/upload.ts | 7 +- src/equicordplugins/moreStickers/utils.tsx | 13 +- 23 files changed, 402 insertions(+), 597 deletions(-) delete mode 100644 src/equicordplugins/moreStickers/components/PickerCategoriesSidebar.tsx delete mode 100644 src/equicordplugins/moreStickers/components/PickerHeader.tsx create mode 100644 src/equicordplugins/moreStickers/components/categories.tsx delete mode 100644 src/equicordplugins/moreStickers/components/categoryImage.tsx delete mode 100644 src/equicordplugins/moreStickers/components/categoryScroller.tsx delete mode 100644 src/equicordplugins/moreStickers/components/categoryWrapper.tsx delete mode 100644 src/equicordplugins/moreStickers/components/header.tsx delete mode 100644 src/equicordplugins/moreStickers/components/iconContainer.tsx create mode 100644 src/equicordplugins/moreStickers/components/index.ts rename src/equicordplugins/moreStickers/components/{settings.tsx => misc.tsx} (89%) rename src/equicordplugins/moreStickers/components/{PickerContent.tsx => picker.tsx} (72%) delete mode 100644 src/equicordplugins/moreStickers/components/recent.ts delete mode 100644 src/equicordplugins/moreStickers/components/stickerCategory.tsx delete mode 100644 src/equicordplugins/moreStickers/components/wrapper.tsx diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index dce89df6..2b105ef9 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -398,7 +398,7 @@ function resetSettings(plugin: Plugin, warningModalProps?: ModalProps, pluginMod export function openWarningModal(plugin: Plugin, pluginModalProps: ModalProps, onRestartNeeded?: (pluginName: string) => void) { if (Settings.ignoreResetWarning) return resetSettings(plugin, pluginModalProps, pluginModalProps, onRestartNeeded); - + openModal(warningModalProps => ( . -*/ - -import { ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { React, Text } from "@webpack/common"; - -import { CategoryImage } from "./categoryImage"; -import { CategoryScroller } from "./categoryScroller"; -import { CategoryWrapper } from "./categoryWrapper"; -import { CogIcon, RecentlyUsedIcon } from "./icons"; -import { RECENT_STICKERS_ID, RECENT_STICKERS_TITLE } from "./recent"; -import { Settings } from "./settings"; -import { StickerCategory } from "./stickerCategory"; -import { cl, clPicker } from "../utils"; - -export interface StickerCategory { - id: string; - name: string; - iconUrl?: string; -} - -export interface SidebarProps { - packMetas: StickerCategory[]; - onPackSelect: (category: StickerCategory) => void; -} - -export const RecentPack = { - id: RECENT_STICKERS_ID, - name: RECENT_STICKERS_TITLE, -} as StickerCategory; - -export const PickerSidebar = ({ packMetas, onPackSelect }: SidebarProps) => { - const [activePack, setActivePack] = React.useState(RecentPack); - const [hovering, setHovering] = React.useState(false); - - return ( - - - { - if (activePack === RecentPack) return; - - onPackSelect(RecentPack); - setActivePack(RecentPack); - }} - > - - - { - ...packMetas.map(pack => { - return ( - { - if (activePack?.id === pack.id) return; - - onPackSelect(pack); - setActivePack(pack); - }} - isActive={activePack?.id === pack.id} - > - - - ); - }) - } - -
- -
-
- ); -}; diff --git a/src/equicordplugins/moreStickers/components/PickerHeader.tsx b/src/equicordplugins/moreStickers/components/PickerHeader.tsx deleted file mode 100644 index ce8e1ede..00000000 --- a/src/equicordplugins/moreStickers/components/PickerHeader.tsx +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { debounce } from "@shared/debounce"; -import { React, TextInput } from "@webpack/common"; - -import { Header } from "./header"; -import { IconContainer } from "./iconContainer"; -import { CancelIcon, SearchIcon } from "./icons"; -import { clPicker } from "../utils"; - -export interface PickerHeaderProps { - onQueryChange: (query: string) => void; -} - -const debounceQueryChange = debounce((cb: Function, ...args: any) => cb(...args), 150); - -export const PickerHeader = ({ onQueryChange }: PickerHeaderProps) => { - const [query, setQuery] = React.useState(); - - const setQueryDebounced = (value: string, immediate = false) => { - setQuery(value); - if (immediate) onQueryChange(value); - else debounceQueryChange(onQueryChange, value); - }; - - return ( -
-
-
-
- setQueryDebounced(value)} - /> -
-
- - { - (query && query.length > 0) ? - setQueryDebounced("", true)} /> : - - } - -
-
-
-
- ); -}; diff --git a/src/equicordplugins/moreStickers/components/categories.tsx b/src/equicordplugins/moreStickers/components/categories.tsx new file mode 100644 index 00000000..82bb73e1 --- /dev/null +++ b/src/equicordplugins/moreStickers/components/categories.tsx @@ -0,0 +1,88 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { React } from "@webpack/common"; + +import { CategoryImageProps, StickerCategoryProps } from "../types"; +import { cl } from "../utils"; + +export function CategoryImage({ src, alt, isActive }: CategoryImageProps) { + return ( +
+ + + {alt} + + +
+ ); +} + +export function CategoryScroller(props: { children: React.ReactNode, categoryLength: number; }) { + const children = Array.isArray(props.children) ? props.children : [props.children]; + + return ( +
+
{ + children.map(child => ( +
+ {child} +
+ )) + }
+
+ +
+ ); +} + +export function CategoryWrapper(props: { children: JSX.Element | JSX.Element[]; }) { + return ( +
+ {props.children} +
+ ); +} + +export function StickerCategory(props: StickerCategoryProps) { + return ( +
+ {props.children} +
+ ); +} diff --git a/src/equicordplugins/moreStickers/components/categoryImage.tsx b/src/equicordplugins/moreStickers/components/categoryImage.tsx deleted file mode 100644 index 00a996b3..00000000 --- a/src/equicordplugins/moreStickers/components/categoryImage.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { cl } from "../utils"; - -export interface CategoryImageProps { - src: string; - alt?: string; - isActive?: boolean; -} - -export function CategoryImage({ src, alt, isActive }: CategoryImageProps) { - return ( -
- - - {alt} - - -
- ); -} diff --git a/src/equicordplugins/moreStickers/components/categoryScroller.tsx b/src/equicordplugins/moreStickers/components/categoryScroller.tsx deleted file mode 100644 index c7d77601..00000000 --- a/src/equicordplugins/moreStickers/components/categoryScroller.tsx +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { cl } from "../utils"; - -export function CategoryScroller(props: { children: React.ReactNode, categoryLength: number; }) { - const children = Array.isArray(props.children) ? props.children : [props.children]; - - return ( -
-
{ - children.map(child => ( -
- {child} -
- )) - }
-
- -
- ); -} diff --git a/src/equicordplugins/moreStickers/components/categoryWrapper.tsx b/src/equicordplugins/moreStickers/components/categoryWrapper.tsx deleted file mode 100644 index fd6a2559..00000000 --- a/src/equicordplugins/moreStickers/components/categoryWrapper.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { cl } from "../utils"; - -export function CategoryWrapper(props: { children: JSX.Element | JSX.Element[]; }) { - return ( -
- {props.children} -
- ); -} diff --git a/src/equicordplugins/moreStickers/components/header.tsx b/src/equicordplugins/moreStickers/components/header.tsx deleted file mode 100644 index f9257834..00000000 --- a/src/equicordplugins/moreStickers/components/header.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { cl } from "../utils"; - -export function Header(props: { children: JSX.Element | JSX.Element[]; }) { - return ( -
- {props.children} -
- ); -} diff --git a/src/equicordplugins/moreStickers/components/iconContainer.tsx b/src/equicordplugins/moreStickers/components/iconContainer.tsx deleted file mode 100644 index 3bd8e6d6..00000000 --- a/src/equicordplugins/moreStickers/components/iconContainer.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -export function IconContainer(props: { children: JSX.Element | JSX.Element[]; }) { - return ( -
- {props.children} -
- ); -} diff --git a/src/equicordplugins/moreStickers/components/icons.tsx b/src/equicordplugins/moreStickers/components/icons.tsx index 18e3f031..51c632db 100644 --- a/src/equicordplugins/moreStickers/components/icons.tsx +++ b/src/equicordplugins/moreStickers/components/icons.tsx @@ -16,13 +16,28 @@ * along with this program. If not, see . */ +export function IconContainer(props: { children: JSX.Element | JSX.Element[]; }) { + return ( +
+ {props.children} +
+ ); +} + + export function SearchIcon({ width, height, color }: { width: number, height: number, color: string; }) { return ( ); -}; +} export function CancelIcon({ width, height, className, onClick }: { width: number, height: number, className: string, onClick: () => void; }) { return ( @@ -30,7 +45,7 @@ export function CancelIcon({ width, height, className, onClick }: { width: numbe ); -}; +} export function RecentlyUsedIcon({ width, height, color }: { width: number, height: number, color: string; }) { return ( @@ -38,7 +53,7 @@ export function RecentlyUsedIcon({ width, height, color }: { width: number, heig ); -}; +} export function CogIcon({ width, height }: { width: number, height: number; }) { return ( @@ -46,4 +61,4 @@ export function CogIcon({ width, height }: { width: number, height: number; }) { ); -}; +} diff --git a/src/equicordplugins/moreStickers/components/index.ts b/src/equicordplugins/moreStickers/components/index.ts new file mode 100644 index 00000000..8a9996fe --- /dev/null +++ b/src/equicordplugins/moreStickers/components/index.ts @@ -0,0 +1,10 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +export * from "./categories"; +export * from "./icons"; +export * from "./misc"; +export * from "./picker"; diff --git a/src/equicordplugins/moreStickers/components/settings.tsx b/src/equicordplugins/moreStickers/components/misc.tsx similarity index 89% rename from src/equicordplugins/moreStickers/components/settings.tsx rename to src/equicordplugins/moreStickers/components/misc.tsx index 4773c659..c9022420 100644 --- a/src/equicordplugins/moreStickers/components/settings.tsx +++ b/src/equicordplugins/moreStickers/components/misc.tsx @@ -16,21 +16,24 @@ * along with this program. If not, see . */ +import * as DataStore from "@api/DataStore"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Flex } from "@components/Flex"; import { Button, Forms, React, TabBar, Text, TextArea, Toasts } from "@webpack/common"; -import { convert as convertLineSP, getIdFromUrl as getLineStickerPackIdFromUrl, getStickerPackById as getLineStickerPackById, parseHtml as getLineSPFromHtml, isLineStickerPackHtml } from "../lineStickers"; -import { convert as convertLineEP, getIdFromUrl as getLineEmojiPackIdFromUrl, getStickerPackById as getLineEmojiPackById, parseHtml as getLineEPFromHtml, isLineEmojiPackHtml } from "../lineEmojis"; +import { convert as convertLineEP, getIdFromUrl as getLineEmojiPackIdFromUrl, getStickerPackById as getLineEmojiPackById, isLineEmojiPackHtml, parseHtml as getLineEPFromHtml } from "../lineEmojis"; +import { convert as convertLineSP, getIdFromUrl as getLineStickerPackIdFromUrl, getStickerPackById as getLineStickerPackById, isLineStickerPackHtml, parseHtml as getLineSPFromHtml } from "../lineStickers"; import { deleteStickerPack, getStickerPackMetas, saveStickerPack } from "../stickers"; -import { StickerPack, StickerPackMeta } from "../types"; -import { cl, clPicker } from "../utils"; +import { SettingsTabsKey, Sticker, StickerPack, StickerPackMeta } from "../types"; +import { cl, clPicker, Mutex } from "../utils"; -enum SettingsTabsKey { - ADD_STICKER_PACK_URL = "Add from URL", - ADD_STICKER_PACK_HTML = "Add from HTML", - ADD_STICKER_PACK_FILE = "Add from File", -} +const mutex = new Mutex(); + +// The ID of recent sticker and recent sticker pack +export const RECENT_STICKERS_ID = "recent"; +export const RECENT_STICKERS_TITLE = "Recently Used"; + +const KEY = "Vencord-MoreStickers-RecentStickers"; const noDrag = { onMouseDown: e => { e.preventDefault(); return false; }, @@ -189,7 +192,7 @@ export const Settings = () => { errorMessage = e.message; } break; - }; + } case "LineEmojiPack": { try { const id = getLineEmojiPackIdFromUrl(addStickerUrl); @@ -395,3 +398,56 @@ export const Settings = () => { ); }; + + +export function Header(props: { children: JSX.Element | JSX.Element[]; }) { + return ( +
+ {props.children} +
+ ); +} + +export function Wrapper(props: { children: JSX.Element | JSX.Element[]; }) { + return ( +
+ {props.children} +
+ ); +} + +export async function getRecentStickers(): Promise { + return (await DataStore.get(KEY)) ?? []; +} + +export async function setRecentStickers(stickers: Sticker[]): Promise { + const unlock = await mutex.lock(); + try { + await DataStore.set(KEY, stickers); + } finally { + unlock(); + } +} + +export async function addRecentSticker(sticker: Sticker): Promise { + const stickers = await getRecentStickers(); + const index = stickers.findIndex(s => s.id === sticker.id); + if (index !== -1) { + stickers.splice(index, 1); + } + stickers.unshift(sticker); + while (stickers.length > 16) { + stickers.pop(); + } + await setRecentStickers(stickers); +} + +export async function removeRecentStickerByPackId(packId: string): Promise { + const stickers = await getRecentStickers(); + await setRecentStickers(stickers.filter(s => s.stickerPackId !== packId)); +} diff --git a/src/equicordplugins/moreStickers/components/PickerContent.tsx b/src/equicordplugins/moreStickers/components/picker.tsx similarity index 72% rename from src/equicordplugins/moreStickers/components/PickerContent.tsx rename to src/equicordplugins/moreStickers/components/picker.tsx index c7d81181..f7170c33 100644 --- a/src/equicordplugins/moreStickers/components/PickerContent.tsx +++ b/src/equicordplugins/moreStickers/components/picker.tsx @@ -1,64 +1,95 @@ /* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ -import { React } from "@webpack/common"; +import { debounce } from "@shared/debounce"; +import { ModalContent, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { React, Text, TextInput } from "@webpack/common"; -import { Sticker, StickerPack } from "../types"; +import { PickerContent, PickerContentHeader, PickerContentRow, PickerContentRowGrid, PickerHeaderProps, SidebarProps, Sticker, StickerCategoryType, StickerPack } from "../types"; import { sendSticker } from "../upload"; -import { RecentlyUsedIcon } from "./icons"; -import { addRecentSticker, getRecentStickers, RECENT_STICKERS_ID, RECENT_STICKERS_TITLE } from "./recent"; import { clPicker, FFmpegStateContext } from "../utils"; +import { CategoryImage, CategoryScroller, CategoryWrapper, StickerCategory } from "./categories"; +import { CancelIcon, CogIcon, IconContainer, RecentlyUsedIcon, SearchIcon } from "./icons"; +import { addRecentSticker, getRecentStickers, Header, RECENT_STICKERS_ID, RECENT_STICKERS_TITLE, Settings } from "./misc"; -export interface PickerContent { - stickerPacks: StickerPack[]; - selectedStickerPackId?: string | null; - setSelectedStickerPackId: React.Dispatch>; - channelId: string; - closePopout: () => void; - query?: string; -} +const debounceQueryChange = debounce((cb: Function, ...args: any) => cb(...args), 150); -export interface PickerContentHeader { - image: string | React.ReactNode; - title: string; - children?: React.ReactNode; - isSelected?: boolean; - afterScroll?: () => void; - beforeScroll?: () => void; -} +export const RecentPack = { + id: RECENT_STICKERS_ID, + name: RECENT_STICKERS_TITLE, +} as StickerCategoryType; -export interface PickerContentRow { - rowIndex: number; - grid1: PickerContentRowGrid; - grid2?: PickerContentRowGrid; - grid3?: PickerContentRowGrid; - channelId: string; -} +export const PickerSidebar = ({ packMetas, onPackSelect }: SidebarProps) => { + const [activePack, setActivePack] = React.useState(RecentPack); + const [hovering, setHovering] = React.useState(false); -export interface PickerContentRowGrid { - rowIndex: number; - colIndex: number; - sticker: Sticker; - onHover: (sticker: Sticker | null) => void; - isHovered?: boolean; - channelId?: string; - onSend?: (sticker?: Sticker, shouldClose?: boolean) => void; -} + return ( + + + { + if (activePack === RecentPack) return; + + onPackSelect(RecentPack); + setActivePack(RecentPack); + }} + > + + + { + ...packMetas.map(pack => { + return ( + { + if (activePack?.id === pack.id) return; + + onPackSelect(pack); + setActivePack(pack); + }} + isActive={activePack?.id === pack.id} + > + + + ); + }) + } + +
+ +
+
+ ); +}; function PickerContentRowGrid({ rowIndex, @@ -400,3 +431,43 @@ export function PickerContent({ stickerPacks, selectedStickerPackId, setSelected ); } + + +export const PickerHeader = ({ onQueryChange }: PickerHeaderProps) => { + const [query, setQuery] = React.useState(); + + const setQueryDebounced = (value: string, immediate = false) => { + setQuery(value); + if (immediate) onQueryChange(value); + else debounceQueryChange(onQueryChange, value); + }; + + return ( +
+
+
+
+ setQueryDebounced(value)} + /> +
+
+ + { + (query && query.length > 0) ? + setQueryDebounced("", true)} /> : + + } + +
+
+
+
+ ); +}; diff --git a/src/equicordplugins/moreStickers/components/recent.ts b/src/equicordplugins/moreStickers/components/recent.ts deleted file mode 100644 index 012c4287..00000000 --- a/src/equicordplugins/moreStickers/components/recent.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import * as DataStore from "@api/DataStore"; - -import { Sticker } from "../types"; -import { Mutex } from "../utils"; - -const mutex = new Mutex(); - -// The ID of recent sticker and recent sticker pack -export const RECENT_STICKERS_ID = "recent"; -export const RECENT_STICKERS_TITLE = "Recently Used"; - -const KEY = "Vencord-MoreStickers-RecentStickers"; - -export async function getRecentStickers(): Promise { - return (await DataStore.get(KEY)) ?? []; -} - -export async function setRecentStickers(stickers: Sticker[]): Promise { - const unlock = await mutex.lock(); - try { - await DataStore.set(KEY, stickers); - } finally { - unlock(); - } -} - -export async function addRecentSticker(sticker: Sticker): Promise { - const stickers = await getRecentStickers(); - const index = stickers.findIndex(s => s.id === sticker.id); - if (index !== -1) { - stickers.splice(index, 1); - } - stickers.unshift(sticker); - while (stickers.length > 16) { - stickers.pop(); - } - await setRecentStickers(stickers); -} - -export async function removeRecentStickerByPackId(packId: string): Promise { - const stickers = await getRecentStickers(); - await setRecentStickers(stickers.filter(s => s.stickerPackId !== packId)); -} diff --git a/src/equicordplugins/moreStickers/components/stickerCategory.tsx b/src/equicordplugins/moreStickers/components/stickerCategory.tsx deleted file mode 100644 index 165d183f..00000000 --- a/src/equicordplugins/moreStickers/components/stickerCategory.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -import { cl } from "../utils"; - -export interface StickerCategoryProps { - children: React.ReactNode; - onClick?: () => void; - isActive: boolean; - style?: React.CSSProperties; -} - -export function StickerCategory(props: StickerCategoryProps) { - return ( -
- {props.children} -
- ); -} diff --git a/src/equicordplugins/moreStickers/components/wrapper.tsx b/src/equicordplugins/moreStickers/components/wrapper.tsx deleted file mode 100644 index f2eb706d..00000000 --- a/src/equicordplugins/moreStickers/components/wrapper.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2023 Vendicated and contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . -*/ - -export function Wrapper(props: { children: JSX.Element | JSX.Element[]; }) { - return ( -
- {props.children} -
- ); -} diff --git a/src/equicordplugins/moreStickers/index.tsx b/src/equicordplugins/moreStickers/index.tsx index 9426c829..636ca11d 100644 --- a/src/equicordplugins/moreStickers/index.tsx +++ b/src/equicordplugins/moreStickers/index.tsx @@ -18,20 +18,15 @@ import "./style.css"; +import { FFmpeg } from "@ffmpeg/ffmpeg"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { React } from "@webpack/common"; import { Channel } from "discord-types/general"; -import { FFmpeg } from '@ffmpeg/ffmpeg'; - -import { PickerSidebar } from "./components/PickerCategoriesSidebar"; -import { PickerContent } from "./components/PickerContent"; -import { PickerHeader } from "./components/PickerHeader"; -import { Settings } from "./components/settings"; -import { Wrapper } from "./components/wrapper"; +import { PickerContent, PickerHeader, PickerSidebar, Settings, Wrapper } from "./components"; import { getStickerPack, getStickerPackMetas } from "./stickers"; -import { StickerPack, StickerPackMeta, FFmpegState } from "./types"; +import { StickerPack, StickerPackMeta } from "./types"; import { cl, FFmpegStateContext, loadFFmpeg } from "./utils"; export default definePlugin({ @@ -57,7 +52,7 @@ export default definePlugin({ return `${head}${isMoreStickers}?$self.stickerButton:${button}${tail}`; } }, { - match: /(\w=)(\w\.useCallback\(\(\)=>\{\(0,\w+\.\w+\)\([\w\.]*?\.STICKER,.*?);/, + match: /(\w=)(\w\.useCallback\(\(\)=>\{\(0,\w+\.\w+\)\([\w.]*?\.STICKER,.*?);/, replace: (_, decl, cb) => { const newCb = cb.replace(/(?<=\(\)=>\{\(.*?\)\().+?\.STICKER/, "\"stickers+\""); return `${decl}arguments[0]?.stickersType?${newCb}:${cb};`; @@ -71,7 +66,7 @@ export default definePlugin({ }] }, { - find: '.gifts)', + find: ".gifts)", replacement: { match: /,\(null===\(\w=\w\.stickers\)\|\|void 0.*?(\w)\.push\((\(0,\w\.jsx\))\((.+?),{disabled:\w,type:(\w)},"sticker"\)\)/, replace: (m, _, jsx, compo, type) => { diff --git a/src/equicordplugins/moreStickers/lineEmojis.ts b/src/equicordplugins/moreStickers/lineEmojis.ts index e7a95e93..14fb4f3d 100644 --- a/src/equicordplugins/moreStickers/lineEmojis.ts +++ b/src/equicordplugins/moreStickers/lineEmojis.ts @@ -113,7 +113,7 @@ export function parseHtml(html: string): LineEmojiPack { const { id } = mainImage; const stickers = - [...doc.querySelectorAll('.FnStickerPreviewItem')] + [...doc.querySelectorAll(".FnStickerPreviewItem")] .map(x => JSON.parse((x as HTMLElement).dataset.preview ?? "null")) .filter(x => x !== null) .map(x => ({ ...x, stickerPackId: id })) as LineEmoji[]; diff --git a/src/equicordplugins/moreStickers/stickers.ts b/src/equicordplugins/moreStickers/stickers.ts index 9c7a1a6e..daa4cd1c 100644 --- a/src/equicordplugins/moreStickers/stickers.ts +++ b/src/equicordplugins/moreStickers/stickers.ts @@ -18,7 +18,7 @@ import * as DataStore from "@api/DataStore"; -import { removeRecentStickerByPackId } from "./components/recent"; +import { removeRecentStickerByPackId } from "./components"; import { StickerPack, StickerPackMeta } from "./types"; import { Mutex } from "./utils"; const mutex = new Mutex(); diff --git a/src/equicordplugins/moreStickers/testdata.ts b/src/equicordplugins/moreStickers/testdata.ts index f90c7985..33cb23ed 100644 --- a/src/equicordplugins/moreStickers/testdata.ts +++ b/src/equicordplugins/moreStickers/testdata.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { setRecentStickers } from "./components/recent"; +import { setRecentStickers } from "./components"; import { convert, getStickerPackById @@ -44,11 +44,11 @@ export async function initTest() { // Add test sticker packs console.log("Adding test sticker packs."); const lineStickerPackIds = [ - "22814489", // LV.47 野生喵喵怪 - "22567773", // LV.46 野生喵喵怪 - "22256215", // LV.45 野生喵喵怪 - "21936635", // LV.44 野生喵喵怪 - "21836565", // LV.43 野生喵喵怪 + "22814489", // LV.47 + "22567773", // LV.46 + "22256215", // LV.45 + "21936635", // LV.44 + "21836565", // LV.43 ]; const ps: Promise[] = []; for (const id of lineStickerPackIds) { diff --git a/src/equicordplugins/moreStickers/types.ts b/src/equicordplugins/moreStickers/types.ts index b1b2cd3c..e0c6a03a 100644 --- a/src/equicordplugins/moreStickers/types.ts +++ b/src/equicordplugins/moreStickers/types.ts @@ -16,7 +16,13 @@ * along with this program. If not, see . */ -import type { FFmpeg } from '@ffmpeg/ffmpeg'; +import type { FFmpeg } from "@ffmpeg/ffmpeg"; + +export interface CategoryImageProps { + src: string; + alt?: string; + isActive?: boolean; +} export interface LineSticker { animationUrl: string, @@ -62,6 +68,57 @@ export interface LineEmojiPack { stickers: LineEmoji[]; } +export interface PickerHeaderProps { + onQueryChange: (query: string) => void; +} + +export interface PickerContent { + stickerPacks: StickerPack[]; + selectedStickerPackId?: string | null; + setSelectedStickerPackId: React.Dispatch>; + channelId: string; + closePopout: () => void; + query?: string; +} + +export interface PickerContentHeader { + image: string | React.ReactNode; + title: string; + children?: React.ReactNode; + isSelected?: boolean; + afterScroll?: () => void; + beforeScroll?: () => void; +} + +export interface PickerContentRow { + rowIndex: number; + grid1: PickerContentRowGrid; + grid2?: PickerContentRowGrid; + grid3?: PickerContentRowGrid; + channelId: string; +} + +export interface PickerContentRowGrid { + rowIndex: number; + colIndex: number; + sticker: Sticker; + onHover: (sticker: Sticker | null) => void; + isHovered?: boolean; + channelId?: string; + onSend?: (sticker?: Sticker, shouldClose?: boolean) => void; +} + +export enum SettingsTabsKey { + ADD_STICKER_PACK_URL = "Add from URL", + ADD_STICKER_PACK_HTML = "Add from HTML", + ADD_STICKER_PACK_FILE = "Add from File", +} + +export interface SidebarProps { + packMetas: StickerCategoryType[]; + onPackSelect: (category: StickerCategoryType) => void; +} + export interface Sticker { id: string; image: string; @@ -71,6 +128,19 @@ export interface Sticker { isAnimated?: boolean; } +export interface StickerCategoryType { + id: string; + name: string; + iconUrl?: string; +} + +export interface StickerCategoryProps { + children: React.ReactNode; + onClick?: () => void; + isActive: boolean; + style?: React.CSSProperties; +} + export interface StickerPackMeta { id: string; title: string; @@ -88,4 +158,4 @@ export interface StickerPack extends StickerPackMeta { export interface FFmpegState { ffmpeg?: FFmpeg; isLoaded: boolean; -} \ No newline at end of file +} diff --git a/src/equicordplugins/moreStickers/upload.ts b/src/equicordplugins/moreStickers/upload.ts index 33df0229..353792c7 100644 --- a/src/equicordplugins/moreStickers/upload.ts +++ b/src/equicordplugins/moreStickers/upload.ts @@ -16,11 +16,12 @@ * along with this program. If not, see . */ +import { FFmpeg } from "@ffmpeg/ffmpeg"; +import { fetchFile } from "@ffmpeg/util"; import { findByPropsLazy, findLazy } from "@webpack"; import { ChannelStore } from "@webpack/common"; + import { FFmpegState, Sticker } from "./types"; -import { FFmpeg } from '@ffmpeg/ffmpeg'; -import { fetchFile, toBlobURL } from '@ffmpeg/util'; const MessageUpload = findByPropsLazy("instantBatchUpload"); @@ -103,7 +104,7 @@ async function toGIF(url: string, ffmpeg: FFmpeg): Promise { if (typeof data === "string") { throw new Error("Could not read file"); } - return new File([data.buffer], outputFilename, { type: 'image/gif' }); + return new File([data.buffer], outputFilename, { type: "image/gif" }); } export async function sendSticker({ diff --git a/src/equicordplugins/moreStickers/utils.tsx b/src/equicordplugins/moreStickers/utils.tsx index 133a70bb..e1add58d 100644 --- a/src/equicordplugins/moreStickers/utils.tsx +++ b/src/equicordplugins/moreStickers/utils.tsx @@ -17,12 +17,11 @@ */ import { classNameFactory } from "@api/Styles"; - -import { React } from "@webpack/common"; +import type { FFmpeg } from "@ffmpeg/ffmpeg"; import { waitFor } from "@webpack"; -import { FFmpegState } from './types'; +import { React } from "@webpack/common"; -import type { FFmpeg } from '@ffmpeg/ffmpeg'; +import { FFmpegState } from "./types"; export const cl = classNameFactory("vc-more-stickers-"); export const clPicker = (className: string, ...args: any[]) => cl("picker-" + className, ...args); @@ -61,10 +60,10 @@ waitFor("createContext", () => { export async function loadFFmpeg(ffmpeg: FFmpeg, setLoaded: () => void) { console.log("Loading FFmpeg..."); - const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm'; + const baseURL = "https://unpkg.com/@ffmpeg/core@0.12.6/dist/esm"; const classWorkerRaw = "/// \n/// \n/// \nconst MIME_TYPE_JAVASCRIPT = \"text/javascript\";\nconst MIME_TYPE_WASM = \"application/wasm\";\nconst CORE_VERSION = \"0.12.6\";\nconst CORE_URL = `https://unpkg.com/@ffmpeg/core@${CORE_VERSION}/dist/umd/ffmpeg-core.js`;\nvar FFMessageType;\n(function(FFMessageType) {\n FFMessageType[\"LOAD\"] = \"LOAD\";\n FFMessageType[\"EXEC\"] = \"EXEC\";\n FFMessageType[\"WRITE_FILE\"] = \"WRITE_FILE\";\n FFMessageType[\"READ_FILE\"] = \"READ_FILE\";\n FFMessageType[\"DELETE_FILE\"] = \"DELETE_FILE\";\n FFMessageType[\"RENAME\"] = \"RENAME\";\n FFMessageType[\"CREATE_DIR\"] = \"CREATE_DIR\";\n FFMessageType[\"LIST_DIR\"] = \"LIST_DIR\";\n FFMessageType[\"DELETE_DIR\"] = \"DELETE_DIR\";\n FFMessageType[\"ERROR\"] = \"ERROR\";\n FFMessageType[\"DOWNLOAD\"] = \"DOWNLOAD\";\n FFMessageType[\"PROGRESS\"] = \"PROGRESS\";\n FFMessageType[\"LOG\"] = \"LOG\";\n FFMessageType[\"MOUNT\"] = \"MOUNT\";\n FFMessageType[\"UNMOUNT\"] = \"UNMOUNT\";\n})(FFMessageType || (FFMessageType = {}));\n\n\nconst ERROR_UNKNOWN_MESSAGE_TYPE = new Error(\"unknown message type\");\nconst ERROR_NOT_LOADED = new Error(\"ffmpeg is not loaded, call `await ffmpeg.load()` first\");\nconst ERROR_TERMINATED = new Error(\"called FFmpeg.terminate()\");\nconst ERROR_IMPORT_FAILURE = new Error(\"failed to import ffmpeg-core.js\");\n\nlet ffmpeg;\nconst load = async ({ coreURL: _coreURL, wasmURL: _wasmURL, workerURL: _workerURL, }) => {\n const first = !ffmpeg;\n try {\n if (!_coreURL)\n _coreURL = CORE_URL;\n // when web worker type is `classic`.\n importScripts(_coreURL);\n }\n catch {\n if (!_coreURL)\n _coreURL = CORE_URL.replace('/umd/', '/esm/');\n // when web worker type is `module`.\n self.createFFmpegCore = (await import(\n /* webpackIgnore: true */ /* @vite-ignore */ _coreURL)).default;\n if (!self.createFFmpegCore) {\n throw ERROR_IMPORT_FAILURE;\n }\n }\n const coreURL = _coreURL;\n const wasmURL = _wasmURL ? _wasmURL : _coreURL.replace(/.js$/g, \".wasm\");\n const workerURL = _workerURL\n ? _workerURL\n : _coreURL.replace(/.js$/g, \".worker.js\");\n ffmpeg = await self.createFFmpegCore({\n // Fix `Overload resolution failed.` when using multi-threaded ffmpeg-core.\n // Encoded wasmURL and workerURL in the URL as a hack to fix locateFile issue.\n mainScriptUrlOrBlob: `${coreURL}#${btoa(JSON.stringify({ wasmURL, workerURL }))}`,\n });\n ffmpeg.setLogger((data) => self.postMessage({ type: FFMessageType.LOG, data }));\n ffmpeg.setProgress((data) => self.postMessage({\n type: FFMessageType.PROGRESS,\n data,\n }));\n return first;\n};\nconst exec = ({ args, timeout = -1 }) => {\n ffmpeg.setTimeout(timeout);\n ffmpeg.exec(...args);\n const ret = ffmpeg.ret;\n ffmpeg.reset();\n return ret;\n};\nconst writeFile = ({ path, data }) => {\n ffmpeg.FS.writeFile(path, data);\n return true;\n};\nconst readFile = ({ path, encoding }) => ffmpeg.FS.readFile(path, { encoding });\n// TODO: check if deletion works.\nconst deleteFile = ({ path }) => {\n ffmpeg.FS.unlink(path);\n return true;\n};\nconst rename = ({ oldPath, newPath }) => {\n ffmpeg.FS.rename(oldPath, newPath);\n return true;\n};\n// TODO: check if creation works.\nconst createDir = ({ path }) => {\n ffmpeg.FS.mkdir(path);\n return true;\n};\nconst listDir = ({ path }) => {\n const names = ffmpeg.FS.readdir(path);\n const nodes = [];\n for (const name of names) {\n const stat = ffmpeg.FS.stat(`${path}/${name}`);\n const isDir = ffmpeg.FS.isDir(stat.mode);\n nodes.push({ name, isDir });\n }\n return nodes;\n};\n// TODO: check if deletion works.\nconst deleteDir = ({ path }) => {\n ffmpeg.FS.rmdir(path);\n return true;\n};\nconst mount = ({ fsType, options, mountPoint }) => {\n const str = fsType;\n const fs = ffmpeg.FS.filesystems[str];\n if (!fs)\n return false;\n ffmpeg.FS.mount(fs, options, mountPoint);\n return true;\n};\nconst unmount = ({ mountPoint }) => {\n ffmpeg.FS.unmount(mountPoint);\n return true;\n};\nself.onmessage = async ({ data: { id, type, data: _data }, }) => {\n const trans = [];\n let data;\n try {\n if (type !== FFMessageType.LOAD && !ffmpeg)\n throw ERROR_NOT_LOADED; // eslint-disable-line\n switch (type) {\n case FFMessageType.LOAD:\n data = await load(_data);\n break;\n case FFMessageType.EXEC:\n data = exec(_data);\n break;\n case FFMessageType.WRITE_FILE:\n data = writeFile(_data);\n break;\n case FFMessageType.READ_FILE:\n data = readFile(_data);\n break;\n case FFMessageType.DELETE_FILE:\n data = deleteFile(_data);\n break;\n case FFMessageType.RENAME:\n data = rename(_data);\n break;\n case FFMessageType.CREATE_DIR:\n data = createDir(_data);\n break;\n case FFMessageType.LIST_DIR:\n data = listDir(_data);\n break;\n case FFMessageType.DELETE_DIR:\n data = deleteDir(_data);\n break;\n case FFMessageType.MOUNT:\n data = mount(_data);\n break;\n case FFMessageType.UNMOUNT:\n data = unmount(_data);\n break;\n default:\n throw ERROR_UNKNOWN_MESSAGE_TYPE;\n }\n }\n catch (e) {\n self.postMessage({\n id,\n type: FFMessageType.ERROR,\n data: e.toString(),\n });\n return;\n }\n if (data instanceof Uint8Array) {\n trans.push(data.buffer);\n }\n self.postMessage({ id, type, data }, trans);\n};\n\n"; - const classWorkerBlob = new Blob([(new TextEncoder()).encode(classWorkerRaw)], { type: 'text/javascript' }); + const classWorkerBlob = new Blob([(new TextEncoder()).encode(classWorkerRaw)], { type: "text/javascript" }); const classWorkerUrl = URL.createObjectURL(classWorkerBlob); await ffmpeg.load({ @@ -75,4 +74,4 @@ export async function loadFFmpeg(ffmpeg: FFmpeg, setLoaded: () => void) { }); setLoaded(); console.log("FFmpeg loaded!"); -} \ No newline at end of file +}