Merge branch 'dev'

This commit is contained in:
thororen1234 2024-08-29 09:30:17 -04:00
commit 2dc2916e7a
8 changed files with 224 additions and 34 deletions

View file

@ -9,15 +9,23 @@ import { Flex } from "@components/Flex";
import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc";
import { closeModal, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { LazyComponent } from "@utils/react";
import { filters, find } from "@webpack";
import { Button, Clickable, Forms, Text, Tooltip, useEffect, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
import settings from "../settings";
import { clearLoggedSounds, getLoggedSounds } from "../store";
import { addListener, AvatarStyles, cl, downloadAudio, getEmojiUrl, playSound, removeListener, SoundLogEntry, UserSummaryItem } from "../utils";
import { LogIcon } from "./Icons";
import { openMoreUsersModal } from "./MoreUsersModal";
import { openUserModal } from "./UserModal";
const HeaderBarIcon = LazyComponent(() => {
const filter = filters.byCode(".HEADER_BAR_BADGE");
return find(m => m.Icon && filter(m.Icon)).Icon;
});
export async function openSoundBoardLog(): Promise<void> {
const data = await getLoggedSounds();
@ -29,6 +37,17 @@ export async function openSoundBoardLog(): Promise<void> {
}
export function OpenSBLogsButton() {
return (
<HeaderBarIcon
className="chatBarLogIcon"
onClick={() => openSoundBoardLog()}
tooltip={"Open SoundBoard Log"}
icon={LogIcon}
/>
);
}
export default function SoundBoardLog({ data, closeModal }) {
const [sounds, setSounds] = useState(data);
const [users, setUsers] = useState<User[]>([]);

View file

@ -6,11 +6,13 @@
import { addChatBarButton, removeChatBarButton } from "@api/ChatButtons";
import { disableStyle, enableStyle } from "@api/Styles";
import ErrorBoundary from "@components/ErrorBoundary";
import { Devs, EquicordDevs } from "@utils/constants";
import definePlugin from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
import { ChatBarIcon } from "./components/Icons";
import { OpenSBLogsButton } from "./components/SoundBoardLog";
import settings from "./settings";
import { updateLoggedSounds } from "./store";
import styles from "./styles.css?managed";
@ -19,9 +21,34 @@ import { getListeners } from "./utils";
export default definePlugin({
name: "SoundBoardLogger",
authors: [Devs.Moxxie, EquicordDevs.Fres, Devs.echo, EquicordDevs.thororen],
dependencies: ["ChatInputButtonAPI"],
settings,
description: "Logs all soundboards that are played in a voice chat and allows you to download them",
dependencies: ["ChatInputButtonAPI"],
patches: [
{
predicate: () => settings.store.IconLocation === "toolbar",
find: "toolbar:function",
replacement: {
match: /(function \i\(\i\){)(.{1,200}toolbar.{1,100}mobileToolbar)/,
replace: "$1$self.addSBIconToToolBar(arguments[0]);$2"
}
}
],
settings,
addSBIconToToolBar(e: { toolbar: React.ReactNode[] | React.ReactNode; }) {
if (Array.isArray(e.toolbar))
return e.toolbar.push(
<ErrorBoundary noop={true}>
<OpenSBLogsButton />
</ErrorBoundary>
);
e.toolbar = [
<ErrorBoundary noop={true}>
<OpenSBLogsButton />
</ErrorBoundary>,
e.toolbar,
];
},
start() {
enableStyle(styles);
FluxDispatcher.subscribe("VOICE_CHANNEL_EFFECT_SEND", async sound => {
@ -29,10 +56,10 @@ export default definePlugin({
await updateLoggedSounds(sound);
getListeners().forEach(cb => cb());
});
addChatBarButton("vc-soundlog-button", ChatBarIcon);
if (settings.store.IconLocation === "chat") addChatBarButton("vc-soundlog-button", ChatBarIcon);
},
stop() {
disableStyle(styles);
removeChatBarButton("vc-soundlog-button");
if (settings.store.IconLocation === "chat") removeChatBarButton("vc-soundlog-button");
}
});

View file

@ -85,6 +85,12 @@ const settings = definePluginSettings({
default: 0.5,
markers: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]
},
showLogsButton: {
default: true,
type: OptionType.BOOLEAN,
description: "Toggle to whenever show the toolbox or not",
restartNeeded: true,
},
});
export default settings;

View file

@ -11,11 +11,11 @@ import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import type { PluginNative } from "@utils/types";
import { findComponentByCodeLazy, findByPropsLazy } from "@webpack";
import { Button, Clipboard, Forms, React, showToast, Toasts } from "@webpack/common";
import { findComponentByCodeLazy } from "@webpack";
import { Button, Clipboard, Forms, Parser, React, showToast, Toasts } from "@webpack/common";
import { Theme, ThemeInfoModalProps } from "../types";
import { DownloadIcon } from "../utils/Icons";
import { ClockIcon, DownloadIcon, WarningIcon } from "../utils/Icons";
import { logger } from "./LikesComponent";
const Native = VencordNative.pluginHelpers.ThemeLibrary as PluginNative<typeof import("../native")>;
@ -32,22 +32,23 @@ async function downloadTheme(themesDir: string, theme: Theme) {
}
export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, ...props }) => {
const { type, content, likes, guild, tags } = theme;
const { type, content, likes, guild, tags, last_updated, requiresThemeAttributes } = theme;
const themeContent = window.atob(content);
const metadata = themeContent.match(/\/\*\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+\//g)?.[0] || "";
const donate = metadata.match(/@donate\s+(.+)/)?.[1] || "";
const version = metadata.match(/@version\s+(.+)/)?.[1] || "";
const invite = metadata.match(/@invite\s+(.+)/)?.[1] || "";
const authors = Array.isArray(author) ? author : [author];
const lastUpdated = Math.floor(new Date(last_updated ?? 0).getTime() / 1000);
return (
<ModalRoot {...props}>
<ModalHeader>
<Forms.FormTitle tag="h4">{type} Details</Forms.FormTitle>
</ModalHeader>
<ModalContent>
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>{authors.length > 1 ? "Authors" : "Author"}</Forms.FormTitle>
<div style={{ display: "flex", alignItems: "center", marginBottom: "10px" }}>
@ -94,12 +95,14 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
</Forms.FormText>
</>
)}
{guild && (
{(guild || invite) && (
<>
<Forms.FormTitle tag="h5" style={{ marginTop: "10px" }}>Support Server</Forms.FormTitle>
<Forms.FormText>
{guild.name}
</Forms.FormText>
{guild && (
<Forms.FormText>
{guild.name}
</Forms.FormText>
)}
<Forms.FormText>
<Button
color={Button.Colors.BRAND_NEW}
@ -107,7 +110,8 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
className={Margins.top8}
onClick={async e => {
e.preventDefault();
guild.invite_link != null && openInviteModal(guild.invite_link.split("discord.gg/")[1]).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
const useInvite = guild ? guild.invite_link?.split("discord.gg/")[1] : invite;
useInvite != null && openInviteModal(useInvite).catch(() => showToast("Invalid or expired invite!", Toasts.Type.FAILURE));
}}
>
Join Discord Server
@ -161,6 +165,16 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
</Forms.FormText>
</>
)}
{requiresThemeAttributes && (
<Forms.FormText style={{ marginTop: "10px" }}>
<WarningIcon /> This theme requires the <b>ThemeAttributes</b> plugin!
</Forms.FormText>
)}
{last_updated && (
<Forms.FormText style={{ marginTop: "10px" }}>
<ClockIcon /> This theme was last updated {Parser.parse("<t:" + lastUpdated + ":F>")} ({Parser.parse("<t:" + lastUpdated + ":R>")})
</Forms.FormText>
)}
</div>
</div>
</ModalContent>
@ -184,7 +198,6 @@ export const ThemeInfoModal: React.FC<ThemeInfoModalProps> = ({ author, theme, .
const validThemesDir = await Native.getThemesDir(themesDir, theme);
// check if theme exists, and ask if they want to overwrite
if (exists) {
showToast("A file with the same name already exists!", Toasts.Type.FAILURE);
openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>

View file

@ -17,9 +17,9 @@ import { proxyLazy } from "@utils/lazy";
import { Logger } from "@utils/Logger";
import { Margins } from "@utils/margins";
import { classes } from "@utils/misc";
import { openModal } from "@utils/modal";
import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { findByPropsLazy } from "@webpack";
import { Button, Card, FluxDispatcher, Forms, React, SearchableSelect, TabBar, TextArea, TextInput, Toasts, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { Button, Card, FluxDispatcher, Forms, Parser, React, SearchableSelect, TabBar, TextArea, TextInput, Toasts, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general";
import { Constructor } from "type-fest";
@ -108,22 +108,22 @@ function ThemeTab() {
const themeFilter = (theme: Theme) => {
const enabled = themeLinks.includes(`${API_URL}/${theme.name}`);
const tags = new Set(theme.tags.map(tag => tag.toLowerCase()));
const tags = new Set(theme.tags.map(tag => tag?.toLowerCase()));
if (!enabled && searchValue.status === SearchStatus.ENABLED) return false;
const anyTags = SearchTags[searchValue.status];
if (anyTags && !tags.has(anyTags.toLowerCase())) return false;
if (anyTags && !tags.has(anyTags?.toLowerCase())) return false;
if ((enabled && searchValue.status === SearchStatus.DISABLED) || (!enabled && searchValue.status === SearchStatus.ENABLED)) return false;
if (!searchValue.value.length) return true;
const v = searchValue.value.toLowerCase();
const v = searchValue.value?.toLowerCase();
return (
theme.name.toLowerCase().includes(v) ||
theme.description.toLowerCase().includes(v) ||
(Array.isArray(theme.author) ? theme.author.some(author => author.discord_name.toLowerCase().includes(v)) : theme.author.discord_name.toLowerCase().includes(v)) ||
theme.name?.toLowerCase().includes(v) ||
theme.description?.toLowerCase().includes(v) ||
(Array.isArray(theme.author) ? theme.author.some(author => author.discord_name?.toLowerCase()?.includes(v)) : theme.author.discord_name?.toLowerCase()?.includes(v)) ||
tags.has(v)
);
};
@ -232,7 +232,7 @@ function ThemeTab() {
{theme.name}
</Forms.FormTitle>
<Forms.FormText className="vce-theme-text">
{theme.description}
{Parser.parse(theme.description)}
</Forms.FormText>
<div className="vce-theme-info">
<div style={{
@ -267,8 +267,54 @@ function ThemeTab() {
<Button
onClick={() => {
const onlineThemeLinks = [...themeLinks, `${API_URL}/${theme.name}`];
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
const requiresThemeAttributes = theme.requiresThemeAttributes ?? false;
if (requiresThemeAttributes && !Settings.plugins.ThemeAttributes.enabled) {
openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<Forms.FormTitle tag="h4">Hold on!</Forms.FormTitle>
</ModalHeader>
<ModalContent>
<Forms.FormText style={{
padding: "8px",
}}>
<div style={{ display: "flex", flexDirection: "column" }}>
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
<p>
Do you want to enable it?
</p>
</div>
</Forms.FormText>
</ModalContent>
<ModalFooter>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.GREEN}
onClick={async () => {
Settings.plugins.ThemeAttributes.enabled = true;
modalProps.onClose();
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
}}
>
Enable Plugin
</Button>
<Button
color={Button.Colors.RED}
look={Button.Looks.FILLED}
className={Margins.right8} onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
));
} else {
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
}
}}
size={Button.Sizes.MEDIUM}
color={Button.Colors.GREEN}
@ -364,7 +410,7 @@ function ThemeTab() {
{theme.name}
</Forms.FormTitle>
<Forms.FormText className="vce-theme-text">
{theme.description}
{Parser.parse(theme.description)}
</Forms.FormText>
<img
role="presentation"
@ -406,8 +452,54 @@ function ThemeTab() {
<Button
onClick={() => {
const onlineThemeLinks = [...themeLinks, `${API_URL}/${theme.name}`];
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
const requiresThemeAttributes = theme.requiresThemeAttributes ?? false;
if (requiresThemeAttributes && !Settings.plugins.ThemeAttributes.enabled) {
openModal(modalProps => (
<ModalRoot {...modalProps} size={ModalSize.SMALL}>
<ModalHeader>
<Forms.FormTitle tag="h4">Hold on!</Forms.FormTitle>
</ModalHeader>
<ModalContent>
<Forms.FormText style={{
padding: "8px",
}}>
<div style={{ display: "flex", flexDirection: "column" }}>
<p>This theme requires the <b>ThemeAttributes</b> plugin to work properly!</p>
<p>
Do you want to enable it?
</p>
</div>
</Forms.FormText>
</ModalContent>
<ModalFooter>
<Button
look={Button.Looks.FILLED}
color={Button.Colors.GREEN}
onClick={async () => {
Settings.plugins.ThemeAttributes.enabled = true;
modalProps.onClose();
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
}}
>
Enable Plugin
</Button>
<Button
color={Button.Colors.RED}
look={Button.Looks.FILLED}
className={Margins.right8} onClick={() => modalProps.onClose()}
>
Cancel
</Button>
</ModalFooter>
</ModalRoot>
));
} else {
setThemeLinks(onlineThemeLinks);
Vencord.Settings.themeLinks = onlineThemeLinks;
}
}}
size={Button.Sizes.MEDIUM}
color={Button.Colors.GREEN}

View file

@ -1,4 +1,3 @@
[data-tab-id="ThemeLibrary"]::before {
/* stylelint-disable-next-line property-no-vendor-prefix */
-webkit-mask: var(--si-widget) center/contain no-repeat !important;

View file

@ -24,13 +24,15 @@ export interface Theme {
likes: number;
tags: string[];
thumbnail_url: string;
release_date: string;
release_date: Date;
last_updated?: Date;
guild?: {
name: string;
snowflake: string;
invite_link: string;
};
source?: string;
requiresThemeAttributes?: boolean;
}
export interface ThemeInfoModalProps extends ModalProps {

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: GPL-3.0-or-later
*/
export const LikeIcon = (isLiked: boolean) => (
export const LikeIcon = (isLiked: boolean = false) => (
<svg
viewBox="0 0 20 20"
fill={isLiked ? "red" : "currentColor"}
@ -17,7 +17,7 @@ export const LikeIcon = (isLiked: boolean) => (
</svg>
);
export const DownloadIcon = (props: any) => (
export const DownloadIcon = (props?: any) => (
<svg
aria-hidden="true"
viewBox="0 0 20 20"
@ -30,3 +30,35 @@ export const DownloadIcon = (props: any) => (
<path d="M7.25 7.689V2a.75.75 0 0 1 1.5 0v5.689l1.97-1.969a.749.749 0 1 1 1.06 1.06l-3.25 3.25a.749.749 0 0 1-1.06 0L4.22 6.78a.749.749 0 1 1 1.06-1.06l1.97 1.969Z"></path>
</svg>
);
export const ClockIcon = (props?: any) => {
return (
<svg
aria-hidden="true"
viewBox="0 0 24 24"
width="20"
height="20"
fill="currentColor"
{...props}
>
<path fill-rule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm1-18a1 1 0 1 0-2 0v7c0 .27.1.52.3.7l3 3a1 1 0 0 0 1.4-1.4L13 11.58V5Z" clip-rule="evenodd"></path>
</svg>
);
};
export const WarningIcon = (props?: any) => {
return (
<svg
aria-hidden="true"
viewBox="0 0 24 24"
width="20"
height="20"
fill="currentColor"
{...props}
>
<circle cx="12" cy="12" r="10" fill="transparent"></circle>
<path fill-rule="evenodd" d="M12 23a11 11 0 1 0 0-22 11 11 0 0 0 0 22Zm1.44-15.94L13.06 14a1.06 1.06 0 0 1-2.12 0l-.38-6.94a1 1 0 0 1 1-1.06h.88a1 1 0 0 1 1 1.06Zm-.19 10.69a1.25 1.25 0 1 1-2.5 0 1.25 1.25 0 0 1 2.5 0Z" clip-rule="evenodd"></path>
</svg>
);
};