mirror of
https://github.com/Equicord/Equicord.git
synced 2025-06-19 11:27:02 -04:00
Moar Fixes
This commit is contained in:
parent
69237bfe11
commit
8b246e5b9a
14 changed files with 4 additions and 1301 deletions
|
@ -85,6 +85,7 @@ You can join our [discord server](https://discord.gg/5Xh2W87egW) for commits, ch
|
|||
- MessageLoggerEnhanced by Aria
|
||||
- MessageTranslate by Samwich
|
||||
- ModalFade by Kyuuhachi
|
||||
- MoreStickers by Leko & Arjix
|
||||
- NewPluginsManager by Sqaaakoi
|
||||
- NoAppsAllowed by kvba
|
||||
- NoBulletPoints by Samwich
|
||||
|
|
|
@ -1,114 +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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<StickerCategory>(RecentPack);
|
||||
const [hovering, setHovering] = React.useState(false);
|
||||
|
||||
return (
|
||||
<CategoryWrapper>
|
||||
<CategoryScroller categoryLength={packMetas.length}>
|
||||
<StickerCategory
|
||||
style={{ padding: "4px", boxSizing: "border-box", width: "32px" }}
|
||||
isActive={activePack === RecentPack}
|
||||
onClick={() => {
|
||||
if (activePack === RecentPack) return;
|
||||
|
||||
onPackSelect(RecentPack);
|
||||
setActivePack(RecentPack);
|
||||
}}
|
||||
>
|
||||
<RecentlyUsedIcon width={24} height={24} color={
|
||||
activePack === RecentPack ? " var(--interactive-active)" : "var(--interactive-normal)"
|
||||
} />
|
||||
</StickerCategory>
|
||||
{
|
||||
...packMetas.map(pack => {
|
||||
return (
|
||||
<StickerCategory
|
||||
key={pack.id}
|
||||
onClick={() => {
|
||||
if (activePack?.id === pack.id) return;
|
||||
|
||||
onPackSelect(pack);
|
||||
setActivePack(pack);
|
||||
}}
|
||||
isActive={activePack?.id === pack.id}
|
||||
>
|
||||
<CategoryImage src={pack.iconUrl!} alt={pack.name} isActive={activePack?.id === pack.id} />
|
||||
</StickerCategory>
|
||||
);
|
||||
})
|
||||
}
|
||||
</CategoryScroller>
|
||||
<div className={clPicker("settings-cog-container")}>
|
||||
<button
|
||||
className={clPicker("settings-cog") + (
|
||||
hovering ? ` ${clPicker('settings-cog-active')}` : ""
|
||||
)}
|
||||
onClick={() => {
|
||||
openModal(modalProps => {
|
||||
return (
|
||||
<ModalRoot size={ModalSize.LARGE} {...modalProps}>
|
||||
<ModalHeader>
|
||||
<Text tag="h2">Stickers+</Text>
|
||||
</ModalHeader>
|
||||
<ModalContent>
|
||||
<Settings />
|
||||
</ModalContent>
|
||||
</ModalRoot>
|
||||
);
|
||||
});
|
||||
}}
|
||||
onMouseEnter={() => setHovering(true)}
|
||||
onMouseLeave={() => setHovering(false)}
|
||||
>
|
||||
<CogIcon width={20} height={20} />
|
||||
</button>
|
||||
</div>
|
||||
</CategoryWrapper>
|
||||
);
|
||||
};
|
|
@ -1,402 +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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { React } from "@webpack/common";
|
||||
|
||||
import { Sticker, 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";
|
||||
|
||||
export interface PickerContent {
|
||||
stickerPacks: StickerPack[];
|
||||
selectedStickerPackId?: string | null;
|
||||
setSelectedStickerPackId: React.Dispatch<React.SetStateAction<string | null>>;
|
||||
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;
|
||||
}
|
||||
|
||||
function PickerContentRowGrid({
|
||||
rowIndex,
|
||||
colIndex,
|
||||
sticker,
|
||||
onHover,
|
||||
channelId,
|
||||
onSend = () => { },
|
||||
isHovered = false
|
||||
}: PickerContentRowGrid) {
|
||||
if (FFmpegStateContext === undefined) {
|
||||
return <div>FFmpegStateContext is undefined</div>;
|
||||
}
|
||||
|
||||
const ffmpegState = React.useContext(FFmpegStateContext);
|
||||
|
||||
return (
|
||||
<div
|
||||
role="gridcell"
|
||||
aria-rowindex={rowIndex}
|
||||
aria-colindex={colIndex}
|
||||
id={clPicker(`content-row-grid-${rowIndex}-${colIndex}`)}
|
||||
onMouseEnter={() => onHover(sticker)}
|
||||
onClick={e => {
|
||||
if (!channelId) return;
|
||||
|
||||
sendSticker({ channelId, sticker, ctrlKey: e.ctrlKey, shiftKey: e.shiftKey, ffmpegState });
|
||||
addRecentSticker(sticker);
|
||||
onSend(sticker, e.ctrlKey);
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className={clPicker("content-row-grid-sticker")}
|
||||
>
|
||||
<span className={clPicker("content-row-grid-hidden-visually")}>{sticker.title}</span>
|
||||
<div aria-hidden="true">
|
||||
<div className={
|
||||
[
|
||||
clPicker("content-row-grid-inspected-indicator"),
|
||||
`${isHovered ? "inspected" : ""}`
|
||||
].join(" ")
|
||||
}></div>
|
||||
<div className={clPicker("content-row-grid-sticker-node")}>
|
||||
<div className={clPicker("content-row-grid-asset-wrapper")} style={{
|
||||
height: "96px",
|
||||
width: "96px"
|
||||
}}>
|
||||
<img
|
||||
alt={sticker.title}
|
||||
src={sticker.image}
|
||||
draggable="false"
|
||||
datatype="sticker"
|
||||
data-id={sticker.id}
|
||||
className={clPicker("content-row-grid-img")}
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PickerContentRow({ rowIndex, grid1, grid2, grid3, channelId }: PickerContentRow) {
|
||||
return (
|
||||
<div className={clPicker("content-row")}
|
||||
role="row"
|
||||
aria-rowindex={rowIndex}
|
||||
>
|
||||
<PickerContentRowGrid {...grid1} rowIndex={rowIndex} colIndex={1} channelId={channelId} />
|
||||
{grid2 && <PickerContentRowGrid {...grid2} rowIndex={rowIndex} colIndex={2} channelId={channelId} />}
|
||||
{grid3 && <PickerContentRowGrid {...grid3} rowIndex={rowIndex} colIndex={3} channelId={channelId} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
function HeaderCollapseIcon({ isExpanded }: { isExpanded: boolean; }) {
|
||||
return (
|
||||
<svg
|
||||
className={clPicker("content-header-collapse-icon")}
|
||||
width={16} height={16} viewBox="0 0 24 24"
|
||||
style={{
|
||||
transform: `rotate(${isExpanded ? "0" : "-90deg"})`
|
||||
}}
|
||||
>
|
||||
<path fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M16.59 8.59004L12 13.17L7.41 8.59004L6 10L12 16L18 10L16.59 8.59004Z"></path>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function PickerContentHeader({
|
||||
image,
|
||||
title,
|
||||
children,
|
||||
isSelected = false,
|
||||
afterScroll = () => { },
|
||||
beforeScroll = () => { }
|
||||
}: PickerContentHeader) {
|
||||
|
||||
const [isExpand, setIsExpand] = React.useState(true);
|
||||
const headerElem = React.useRef<HTMLDivElement>(null);
|
||||
React.useEffect(() => {
|
||||
if (isSelected && headerElem.current) {
|
||||
beforeScroll();
|
||||
|
||||
headerElem.current.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
block: "start",
|
||||
});
|
||||
|
||||
afterScroll();
|
||||
}
|
||||
}, [isSelected]);
|
||||
|
||||
return (
|
||||
<span>
|
||||
<div className={clPicker("content-header-wrapper")}>
|
||||
<div className={clPicker("content-header-header")} ref={headerElem}
|
||||
aria-expanded={isExpand}
|
||||
aria-label={`Category, ${title}`}
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
setIsExpand(e => !e);
|
||||
}}
|
||||
>
|
||||
<div className={clPicker("content-header-header-icon")}>
|
||||
<div>
|
||||
{typeof image === "string" ? <svg
|
||||
className={clPicker("content-header-svg")}
|
||||
width={16} height={16} viewBox="0 0 16 16"
|
||||
>
|
||||
<foreignObject
|
||||
x={0} y={0} width={16} height={16}
|
||||
overflow="visible" mask="url(#svg-mask-squircle)"
|
||||
>
|
||||
<img
|
||||
alt={title}
|
||||
src={image}
|
||||
className={clPicker("content-header-guild-icon")}
|
||||
loading="lazy"
|
||||
></img>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
: image}
|
||||
</div>
|
||||
</div>
|
||||
<span
|
||||
className={clPicker("content-header-header-label")}
|
||||
>
|
||||
{title}
|
||||
</span>
|
||||
<HeaderCollapseIcon isExpanded={isExpand} />
|
||||
</div>
|
||||
</div>
|
||||
{isExpand ? children : null}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
export function PickerContent({ stickerPacks, selectedStickerPackId, setSelectedStickerPackId, channelId, closePopout, query }: PickerContent) {
|
||||
const [currentSticker, setCurrentSticker] = (
|
||||
React.useState<Sticker | null>((
|
||||
stickerPacks.length && stickerPacks[0].stickers.length) ?
|
||||
stickerPacks[0].stickers[0] :
|
||||
null
|
||||
)
|
||||
);
|
||||
|
||||
const [currentStickerPack, setCurrentStickerPack] = React.useState<StickerPack | null>(stickerPacks.length ? stickerPacks[0] : null);
|
||||
const [recentStickers, setRecentStickers] = React.useState<Sticker[]>([]);
|
||||
|
||||
const stickerPacksElemRef = React.useRef<HTMLDivElement>(null);
|
||||
const scrollerRef = React.useRef<HTMLDivElement>(null);
|
||||
|
||||
function queryFilter(stickers: Sticker[]): Sticker[] {
|
||||
if (!query) return stickers;
|
||||
return stickers.filter(sticker => sticker.title.toLowerCase().includes(query.toLowerCase()));
|
||||
}
|
||||
|
||||
async function fetchRecentStickers() {
|
||||
const recentStickers = await getRecentStickers();
|
||||
setRecentStickers(recentStickers);
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchRecentStickers();
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (currentStickerPack?.id !== currentSticker?.stickerPackId) {
|
||||
setCurrentStickerPack(stickerPacks.find(p => p.id === currentSticker?.stickerPackId) ?? currentStickerPack);
|
||||
}
|
||||
}, [currentSticker]);
|
||||
|
||||
const stickersToRows = (stickers: Sticker[]): JSX.Element[] => stickers
|
||||
.reduce((acc, sticker, i) => {
|
||||
if (i % 3 === 0) {
|
||||
acc.push([]);
|
||||
}
|
||||
acc[acc.length - 1].push(sticker);
|
||||
return acc;
|
||||
}, [] as Sticker[][])
|
||||
.map((stickers, i) => (
|
||||
<PickerContentRow
|
||||
rowIndex={i}
|
||||
channelId={channelId}
|
||||
grid1={{
|
||||
rowIndex: i,
|
||||
colIndex: 1,
|
||||
sticker: stickers[0],
|
||||
onHover: setCurrentSticker,
|
||||
onSend: (_, s) => { !s && closePopout(); },
|
||||
isHovered: currentSticker?.id === stickers[0].id
|
||||
}}
|
||||
grid2={
|
||||
stickers.length > 1 ? {
|
||||
rowIndex: i,
|
||||
colIndex: 2,
|
||||
sticker: stickers[1],
|
||||
onHover: setCurrentSticker,
|
||||
onSend: (_, s) => { !s && closePopout(); },
|
||||
isHovered: currentSticker?.id === stickers[1].id
|
||||
} : undefined
|
||||
}
|
||||
grid3={
|
||||
stickers.length > 2 ? {
|
||||
rowIndex: i,
|
||||
colIndex: 3,
|
||||
sticker: stickers[2],
|
||||
onHover: setCurrentSticker,
|
||||
onSend: (_, s) => { !s && closePopout(); },
|
||||
isHovered: currentSticker?.id === stickers[2].id
|
||||
} : undefined
|
||||
}
|
||||
/>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={clPicker("content-list-wrapper")}>
|
||||
<div className={clPicker("content-wrapper")}>
|
||||
<div className={clPicker("content-scroller")} ref={scrollerRef}>
|
||||
<div className={clPicker("content-list-items")} role="none presentation">
|
||||
<div ref={stickerPacksElemRef}>
|
||||
<PickerContentHeader
|
||||
image={
|
||||
<RecentlyUsedIcon width={16} height={16} color="currentColor" />
|
||||
}
|
||||
title={RECENT_STICKERS_TITLE}
|
||||
isSelected={RECENT_STICKERS_ID === selectedStickerPackId}
|
||||
beforeScroll={() => {
|
||||
scrollerRef.current?.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}}
|
||||
afterScroll={() => { setSelectedStickerPackId(null); }}
|
||||
>
|
||||
{
|
||||
...stickersToRows(
|
||||
queryFilter(recentStickers)
|
||||
)
|
||||
}
|
||||
</PickerContentHeader>
|
||||
{
|
||||
stickerPacks.map(sp => {
|
||||
const rows = stickersToRows(queryFilter(sp.stickers));
|
||||
return (
|
||||
<PickerContentHeader
|
||||
image={sp.logo.image}
|
||||
title={sp.title}
|
||||
isSelected={sp.id === selectedStickerPackId}
|
||||
beforeScroll={() => {
|
||||
scrollerRef.current?.scrollTo({
|
||||
top: 0,
|
||||
});
|
||||
}}
|
||||
afterScroll={() => { setSelectedStickerPackId(null); }}
|
||||
>
|
||||
{...rows}
|
||||
</PickerContentHeader>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{
|
||||
height: `${stickerPacksElemRef.current?.clientHeight ?? 0}px`
|
||||
}}></div>
|
||||
</div>
|
||||
<div
|
||||
className={clPicker("content-inspector")}
|
||||
style={{
|
||||
visibility: !currentSticker ? "hidden" : "visible",
|
||||
...(!currentSticker ? {
|
||||
height: "0"
|
||||
} : {})
|
||||
}}
|
||||
>
|
||||
<div className={clPicker("content-inspector-graphic-primary")} aria-hidden="true">
|
||||
<div>
|
||||
<div className={clPicker("content-row-grid-asset-wrapper")} style={{
|
||||
height: "28px",
|
||||
width: "28px"
|
||||
}}>
|
||||
<img
|
||||
alt={currentSticker?.title ?? ""}
|
||||
src={currentSticker?.image}
|
||||
draggable="false"
|
||||
datatype="sticker"
|
||||
data-id={currentSticker?.id ?? ""}
|
||||
className={clPicker("content-inspector-img")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className={clPicker("content-inspector-text-wrapper")}>
|
||||
<div className={clPicker("content-inspector-title-primary")} data-text-variant="text-md/semibold">{currentSticker?.title ?? ""}</div>
|
||||
<div className={clPicker("content-inspector-title-secondary")} data-text-variant="text-md/semibold">
|
||||
{currentStickerPack?.title ? "from " : ""}
|
||||
<strong>{currentStickerPack?.title ?? ""}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div className={clPicker("content-inspector-graphic-secondary")} aria-hidden="true">
|
||||
<div>
|
||||
<svg width={32} height={32} viewBox="0 0 32 32">
|
||||
<foreignObject x={0} y={0} width={32} height={32} overflow="visible" mask="url(#svg-mask-squircle)">
|
||||
<img
|
||||
alt={currentStickerPack?.title ?? ""}
|
||||
src={currentStickerPack?.logo?.image}
|
||||
></img>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<string | undefined>();
|
||||
|
||||
const setQueryDebounced = (value: string, immediate = false) => {
|
||||
setQuery(value);
|
||||
if (immediate) onQueryChange(value);
|
||||
else debounceQueryChange(onQueryChange, value);
|
||||
};
|
||||
|
||||
return (
|
||||
<Header>
|
||||
<div className={clPicker("container")}>
|
||||
<div>
|
||||
<div className={clPicker("search-box")}>
|
||||
<TextInput
|
||||
style={{ height: "30px" }}
|
||||
|
||||
placeholder="Search stickers"
|
||||
autoFocus={true}
|
||||
value={query}
|
||||
|
||||
onChange={(value: string) => setQueryDebounced(value)}
|
||||
/>
|
||||
</div>
|
||||
<div className={clPicker("search-icon")}>
|
||||
<IconContainer>
|
||||
{
|
||||
(query && query.length > 0) ?
|
||||
<CancelIcon className={clPicker("clear-icon")} width={20} height={20} onClick={() => setQueryDebounced("", true)} /> :
|
||||
<SearchIcon width={20} height={20} color="var(--text-muted)" />
|
||||
}
|
||||
</IconContainer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Header>
|
||||
);
|
||||
};
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cl } from "../utils";
|
||||
|
||||
export interface CategoryImageProps {
|
||||
src: string;
|
||||
alt?: string;
|
||||
isActive?: boolean;
|
||||
}
|
||||
|
||||
export function CategoryImage({ src, alt, isActive }: CategoryImageProps) {
|
||||
return (
|
||||
<div>
|
||||
<svg width={32} height={32} style={{
|
||||
display: "block",
|
||||
contain: "paint",
|
||||
overflow: "hidden",
|
||||
overflowClipMargin: "content-box",
|
||||
}}>
|
||||
<foreignObject
|
||||
className={
|
||||
cl("foreign-object") + (
|
||||
isActive ?
|
||||
` ${cl('foreign-object-active')}`
|
||||
: ""
|
||||
)
|
||||
}
|
||||
|
||||
x={0} y={0}
|
||||
width={32}
|
||||
height={32}
|
||||
overflow="visible"
|
||||
>
|
||||
<img
|
||||
src={src}
|
||||
alt={alt}
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cl } from "../utils";
|
||||
|
||||
export function CategoryScroller(props: { children: React.ReactNode, categoryLength: number; }) {
|
||||
const children = Array.isArray(props.children) ? props.children : [props.children];
|
||||
|
||||
return (
|
||||
<div className={cl("category-scroller")}>
|
||||
<div>{
|
||||
children.map(child => (
|
||||
<div role="listitem">
|
||||
{child}
|
||||
</div>
|
||||
))
|
||||
}</div>
|
||||
<div style={{ height: `${Math.round(41.75 * (props.categoryLength + 1))}px` }}></div>
|
||||
<div aria-hidden="true"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cl } from "../utils";
|
||||
|
||||
export function CategoryWrapper(props: { children: JSX.Element | JSX.Element[]; }) {
|
||||
return (
|
||||
<div className={cl("category-wrapper")}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cl } from "../utils";
|
||||
|
||||
export function Header(props: { children: JSX.Element | JSX.Element[]; }) {
|
||||
return (
|
||||
<div className={cl("header")}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function IconContainer(props: { children: JSX.Element | JSX.Element[]; }) {
|
||||
return (
|
||||
<div style={{
|
||||
width: "20px",
|
||||
height: "20px",
|
||||
boxSizing: "border-box",
|
||||
position: "relative",
|
||||
cursor: "text"
|
||||
}}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<Sticker[]> {
|
||||
return (await DataStore.get(KEY)) ?? [];
|
||||
}
|
||||
|
||||
export async function setRecentStickers(stickers: Sticker[]): Promise<void> {
|
||||
const unlock = await mutex.lock();
|
||||
try {
|
||||
await DataStore.set(KEY, stickers);
|
||||
} finally {
|
||||
unlock();
|
||||
}
|
||||
}
|
||||
|
||||
export async function addRecentSticker(sticker: Sticker): Promise<void> {
|
||||
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<void> {
|
||||
const stickers = await getRecentStickers();
|
||||
await setRecentStickers(stickers.filter(s => s.stickerPackId !== packId));
|
||||
}
|
|
@ -1,397 +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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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 { deleteStickerPack, getStickerPackMetas, saveStickerPack } from "../stickers";
|
||||
import { StickerPack, StickerPackMeta } from "../types";
|
||||
import { cl, clPicker } 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 noDrag = {
|
||||
onMouseDown: e => { e.preventDefault(); return false; },
|
||||
onDragStart: e => { e.preventDefault(); return false; }
|
||||
};
|
||||
|
||||
const StickerPackMetadata = ({ meta, hoveredStickerPackId, setHoveredStickerPackId, refreshStickerPackMetas }:
|
||||
{ meta: StickerPackMeta, [key: string]: any; }
|
||||
) => {
|
||||
return (
|
||||
<div className="sticker-pack"
|
||||
onMouseEnter={() => setHoveredStickerPackId(meta.id)}
|
||||
onMouseLeave={() => setHoveredStickerPackId(null)}
|
||||
>
|
||||
<div className={
|
||||
[
|
||||
clPicker("content-row-grid-inspected-indicator"),
|
||||
hoveredStickerPackId === meta.id ? "inspected" : ""
|
||||
].join(" ")
|
||||
} style={{
|
||||
top: "unset",
|
||||
left: "unset",
|
||||
height: "96px",
|
||||
width: "96px",
|
||||
}}></div>
|
||||
<img src={meta.logo.image} width="96" {...noDrag} />
|
||||
<button
|
||||
className={hoveredStickerPackId === meta.id ? "show" : ""}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await deleteStickerPack(meta.id);
|
||||
Toasts.show({
|
||||
message: "Sticker Pack deleted",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
await refreshStickerPackMetas();
|
||||
} catch (e: any) {
|
||||
Toasts.show({
|
||||
message: e.message,
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" style={{ fill: "var(--status-danger)" }}>
|
||||
<title>Delete</title>
|
||||
<path d="M15 3.999V2H9V3.999H3V5.999H21V3.999H15Z" />
|
||||
<path d="M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z" />
|
||||
</svg>
|
||||
</button>
|
||||
<Text className={cl("pack-title")} tag="span">{meta.title}</Text>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const Settings = () => {
|
||||
const [stickerPackMetas, setstickerPackMetas] = React.useState<StickerPackMeta[]>([]);
|
||||
const [addStickerUrl, setAddStickerUrl] = React.useState<string>("");
|
||||
const [addStickerHtml, setAddStickerHtml] = React.useState<string>("");
|
||||
const [tab, setTab] = React.useState<SettingsTabsKey>(SettingsTabsKey.ADD_STICKER_PACK_URL);
|
||||
const [hoveredStickerPackId, setHoveredStickerPackId] = React.useState<string | null>(null);
|
||||
|
||||
async function refreshStickerPackMetas() {
|
||||
setstickerPackMetas(await getStickerPackMetas());
|
||||
}
|
||||
React.useEffect(() => {
|
||||
refreshStickerPackMetas();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={cl("settings")}>
|
||||
<TabBar
|
||||
type="top"
|
||||
look="brand"
|
||||
selectedItem={tab}
|
||||
onItemSelect={setTab}
|
||||
className="tab-bar"
|
||||
>
|
||||
{
|
||||
Object.values(SettingsTabsKey).map(k => (
|
||||
<TabBar.Item key={k} id={k} className="tab-bar-item">
|
||||
{k}
|
||||
</TabBar.Item>
|
||||
))
|
||||
}
|
||||
</TabBar>
|
||||
|
||||
{tab === SettingsTabsKey.ADD_STICKER_PACK_URL &&
|
||||
<div className="section">
|
||||
<Forms.FormTitle tag="h5">Add Sticker Pack from URL</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
<p>
|
||||
Currently LINE stickers supported only. <br />
|
||||
Telegram stickers support is planned, but due to the lack of a public API, it is most likely to be provided by sticker pack files instead of adding by URL.
|
||||
</p>
|
||||
</Forms.FormText>
|
||||
<Flex flexDirection="row" style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}} >
|
||||
<span style={{
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<CheckedTextInput
|
||||
value={addStickerUrl}
|
||||
onChange={setAddStickerUrl}
|
||||
validate={(v: string) => {
|
||||
try {
|
||||
getLineStickerPackIdFromUrl(v);
|
||||
return true;
|
||||
} catch (e: any) { }
|
||||
try {
|
||||
getLineEmojiPackIdFromUrl(v);
|
||||
return true;
|
||||
} catch (e: any) { }
|
||||
|
||||
return "Invalid URL";
|
||||
}}
|
||||
placeholder="Sticker Pack URL"
|
||||
/>
|
||||
</span>
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async e => {
|
||||
e.preventDefault();
|
||||
|
||||
let type: string = "";
|
||||
try {
|
||||
getLineStickerPackIdFromUrl(addStickerUrl);
|
||||
type = "LineStickerPack";
|
||||
} catch (e: any) { }
|
||||
|
||||
try {
|
||||
getLineEmojiPackIdFromUrl(addStickerUrl);
|
||||
type = "LineEmojiPack";
|
||||
} catch (e: any) { }
|
||||
|
||||
let errorMessage = "";
|
||||
switch (type) {
|
||||
case "LineStickerPack": {
|
||||
try {
|
||||
const id = getLineStickerPackIdFromUrl(addStickerUrl);
|
||||
const lineSP = await getLineStickerPackById(id);
|
||||
const stickerPack = convertLineSP(lineSP);
|
||||
await saveStickerPack(stickerPack);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message;
|
||||
}
|
||||
break;
|
||||
};
|
||||
case "LineEmojiPack": {
|
||||
try {
|
||||
const id = getLineEmojiPackIdFromUrl(addStickerUrl);
|
||||
const lineEP = await getLineEmojiPackById(id);
|
||||
const stickerPack = convertLineEP(lineEP);
|
||||
await saveStickerPack(stickerPack);
|
||||
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
setAddStickerUrl("");
|
||||
refreshStickerPackMetas();
|
||||
|
||||
if (errorMessage) {
|
||||
Toasts.show({
|
||||
message: errorMessage,
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Toasts.show({
|
||||
message: "Sticker Pack added",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}}
|
||||
>Insert</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
}
|
||||
{tab === SettingsTabsKey.ADD_STICKER_PACK_HTML &&
|
||||
<div className="section">
|
||||
<Forms.FormTitle tag="h5">Add Sticker Pack from HTML</Forms.FormTitle>
|
||||
<Forms.FormText>
|
||||
<p>
|
||||
When encountering errors while adding a sticker pack, you can try to add it using the HTML source code of the sticker pack page.<br />
|
||||
This applies to stickers which are region locked / OS locked / etc.<br />
|
||||
The region LINE recognized may vary from the region you are in due to the CORS proxy we're using.
|
||||
</p>
|
||||
</Forms.FormText>
|
||||
<Flex flexDirection="row" style={{
|
||||
alignItems: "center",
|
||||
justifyContent: "center"
|
||||
}} >
|
||||
<span style={{
|
||||
flexGrow: 1
|
||||
}}>
|
||||
<TextArea
|
||||
value={addStickerHtml}
|
||||
onChange={setAddStickerHtml}
|
||||
placeholder="Paste HTML here"
|
||||
rows={1}
|
||||
/>
|
||||
</span>
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async e => {
|
||||
e.preventDefault();
|
||||
|
||||
let errorMessage = "";
|
||||
if (isLineEmojiPackHtml(addStickerHtml)) {
|
||||
try {
|
||||
const lineSP = getLineSPFromHtml(addStickerHtml);
|
||||
const stickerPack = convertLineSP(lineSP);
|
||||
await saveStickerPack(stickerPack);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message;
|
||||
}
|
||||
} else if (isLineStickerPackHtml(addStickerHtml)) {
|
||||
try {
|
||||
const lineEP = getLineEPFromHtml(addStickerHtml);
|
||||
const stickerPack = convertLineEP(lineEP);
|
||||
await saveStickerPack(stickerPack);
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
errorMessage = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
setAddStickerHtml("");
|
||||
refreshStickerPackMetas();
|
||||
|
||||
if (errorMessage) {
|
||||
Toasts.show({
|
||||
message: errorMessage,
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
} else {
|
||||
Toasts.show({
|
||||
message: "Sticker Pack added",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
}
|
||||
}}
|
||||
>Insert from HTML</Button>
|
||||
</Flex>
|
||||
</div>
|
||||
}
|
||||
{
|
||||
tab === SettingsTabsKey.ADD_STICKER_PACK_FILE &&
|
||||
<div className="section">
|
||||
<Forms.FormTitle tag="h5">Add Sticker Pack from File</Forms.FormTitle>
|
||||
|
||||
<Button
|
||||
size={Button.Sizes.SMALL}
|
||||
onClick={async e => {
|
||||
const input = document.createElement("input");
|
||||
input.type = "file";
|
||||
input.accept = ".stickerpack,.stickerpacks,.json";
|
||||
input.onchange = async e => {
|
||||
try {
|
||||
const file = input.files?.[0];
|
||||
if (!file) return;
|
||||
|
||||
const fileText = await file.text();
|
||||
const fileJson = JSON.parse(fileText);
|
||||
let stickerPacks: StickerPack[] = [];
|
||||
if (Array.isArray(fileJson)) {
|
||||
stickerPacks = fileJson;
|
||||
} else {
|
||||
stickerPacks = [fileJson];
|
||||
}
|
||||
|
||||
for (const stickerPack of stickerPacks) {
|
||||
await saveStickerPack(stickerPack);
|
||||
}
|
||||
|
||||
Toasts.show({
|
||||
message: "Sticker Packs added",
|
||||
type: Toasts.Type.SUCCESS,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
} catch (e: any) {
|
||||
console.error(e);
|
||||
Toasts.show({
|
||||
message: e.message,
|
||||
type: Toasts.Type.FAILURE,
|
||||
id: Toasts.genId(),
|
||||
options: {
|
||||
duration: 1000
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}}
|
||||
>
|
||||
Open Sticker Pack File
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
<Forms.FormDivider style={{
|
||||
marginTop: "8px",
|
||||
marginBottom: "8px"
|
||||
}} />
|
||||
<Forms.FormTitle tag="h5">Stickers Management</Forms.FormTitle>
|
||||
|
||||
<div className="section">
|
||||
<div style={{
|
||||
display: "grid",
|
||||
gridTemplateColumns: "repeat(auto-fill, minmax(96px, 1fr))",
|
||||
gap: "8px"
|
||||
}}>
|
||||
{
|
||||
stickerPackMetas.map(meta => (
|
||||
<StickerPackMetadata
|
||||
key={meta.id}
|
||||
meta={meta}
|
||||
hoveredStickerPackId={hoveredStickerPackId}
|
||||
setHoveredStickerPackId={setHoveredStickerPackId}
|
||||
refreshStickerPackMetas={refreshStickerPackMetas}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { cl } from "../utils";
|
||||
|
||||
export interface StickerCategoryProps {
|
||||
children: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
isActive: boolean;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
export function StickerCategory(props: StickerCategoryProps) {
|
||||
return (
|
||||
<div
|
||||
style={props.style}
|
||||
className={
|
||||
cl("sticker-category") +
|
||||
(props.isActive ? ` ${cl('sticker-category-active')}` : "")
|
||||
}
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
onClick={props.onClick}
|
||||
>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export function Wrapper(props: { children: JSX.Element | JSX.Element[]; }) {
|
||||
return (
|
||||
<div style={{
|
||||
position: "relative",
|
||||
display: "grid",
|
||||
gridTemplateColumns: "48px auto",
|
||||
gridTemplateRows: "auto 1fr auto",
|
||||
}}>
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -25,7 +25,7 @@ export default definePlugin({
|
|||
options: {
|
||||
settings: {
|
||||
type: OptionType.COMPONENT,
|
||||
description: "Why is this here? Who is going to read this on a custom component? It isn't even rendered? What is its purpose?",
|
||||
description: "Packs",
|
||||
component: Settings
|
||||
}
|
||||
},
|
||||
|
@ -103,7 +103,8 @@ export default definePlugin({
|
|||
onClick={onClick}
|
||||
style={{ backgroundColor: "transparent" }}
|
||||
>
|
||||
{/* Icon taken from: https://github.com/Pitu/Magane/blob/0ebb09acf9901933ebebe19fbd473ec08cf917b3/src/Button.svelte#L29 */}
|
||||
{
|
||||
/* Icon taken from: https://github.com/Pitu/Magane/blob/0ebb09acf9901933ebebe19fbd473ec08cf917b3/src/Button.svelte#L29 */}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width="24"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue