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
|
- MessageLoggerEnhanced by Aria
|
||||||
- MessageTranslate by Samwich
|
- MessageTranslate by Samwich
|
||||||
- ModalFade by Kyuuhachi
|
- ModalFade by Kyuuhachi
|
||||||
|
- MoreStickers by Leko & Arjix
|
||||||
- NewPluginsManager by Sqaaakoi
|
- NewPluginsManager by Sqaaakoi
|
||||||
- NoAppsAllowed by kvba
|
- NoAppsAllowed by kvba
|
||||||
- NoBulletPoints by Samwich
|
- 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: {
|
options: {
|
||||||
settings: {
|
settings: {
|
||||||
type: OptionType.COMPONENT,
|
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
|
component: Settings
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -103,7 +103,8 @@ export default definePlugin({
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
style={{ backgroundColor: "transparent" }}
|
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
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="24"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue