1
0
Fork 1
mirror of https://github.com/Vendicated/Vencord.git synced 2025-01-10 09:56:24 +00:00

add lists to plugin modals (super shitty)

also fixes some bugs lol
This commit is contained in:
Elvyra 2025-01-03 14:27:29 +01:00
parent 826162952c
commit 4f2a87a610
6 changed files with 113 additions and 31 deletions

View file

@ -40,11 +40,11 @@ import {
ISettingElementProps, ISettingElementProps,
SettingBooleanComponent, SettingBooleanComponent,
SettingCustomComponent, SettingCustomComponent,
SettingListComponent,
SettingNumericComponent, SettingNumericComponent,
SettingSelectComponent, SettingSelectComponent,
SettingSliderComponent, SettingSliderComponent,
SettingTextComponent, SettingTextComponent,
SettingListComponent,
} from "./components"; } from "./components";
import { openContributorModal } from "./ContributorModal"; import { openContributorModal } from "./ContributorModal";
import { GithubButton, WebsiteButton } from "./LinkIconButton"; import { GithubButton, WebsiteButton } from "./LinkIconButton";
@ -83,10 +83,10 @@ const Components: Record<OptionType, React.ComponentType<ISettingElementProps<an
[OptionType.SELECT]: SettingSelectComponent, [OptionType.SELECT]: SettingSelectComponent,
[OptionType.SLIDER]: SettingSliderComponent, [OptionType.SLIDER]: SettingSliderComponent,
[OptionType.COMPONENT]: SettingCustomComponent, [OptionType.COMPONENT]: SettingCustomComponent,
/* [OptionType.LIST]: SettingListComponent, [OptionType.LIST]: SettingListComponent,
[OptionType.USERS]: SettingListComponent, [OptionType.USERS]: SettingListComponent,
[OptionType.CHANNELS]: SettingListComponent, [OptionType.CHANNELS]: SettingListComponent,
[OptionType.GUILDS]: SettingListComponent */ [OptionType.GUILDS]: SettingListComponent
}; };
export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) { export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {

View file

@ -16,14 +16,39 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import ErrorBoundary from "@components/ErrorBoundary";
import { Flex } from "@components/Flex";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { wordsFromCamel, wordsToTitle } from "@utils/text";
import { PluginOptionSelect } from "@utils/types"; import { OptionType, PluginOptionList } from "@utils/types";
import { Forms, useState, useEffect, ChannelStore, UserStore, GuildStore } from "@webpack/common"; import { findComponentByCodeLazy } from "@webpack";
import { Button,ChannelStore, Forms, GuildStore, React, useState } from "@webpack/common";
import { ISettingElementProps } from "."; import { ISettingElementProps } from ".";
import { Channel } from "discord-types/general";
export function SettingListComponent({ option, pluginSettings, definedSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) { const UserMentionComponent = findComponentByCodeLazy(".USER_MENTION)");
const CloseIcon = () => {
return <svg viewBox="0 0 20 20" fill="currentColor" aria-hidden="true" width="18" height="18">
<path d="M17.3 18.7a1 1 0 0 0 1.4-1.4L13.42 12l5.3-5.3a1 1 0 0 0-1.42-1.4L12 10.58l-5.3-5.3a1 1 0 0 0-1.4 1.42L10.58 12l-5.3 5.3a1 1 0 1 0 1.42 1.4L12 13.42l5.3 5.3Z" />
</svg>;
};
interface UserMentionComponentProps {
id: string;
channelId: string;
guildId: string;
}
export function SettingListComponent({
option,
pluginSettings,
definedSettings,
onChange,
onError,
id
}: ISettingElementProps<PluginOptionList>) {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [items, setItems] = useState<string[]>([]); const [items, setItems] = useState<string[]>([]);
@ -34,31 +59,75 @@ export function SettingListComponent({ option, pluginSettings, definedSettings,
setItems([...items, newItem]); setItems([...items, newItem]);
setNewItem(""); setNewItem("");
} }
pluginSettings[id] = items;
}; };
if (items.length === 0 && pluginSettings[id].length !== 0) {
setItems(pluginSettings[id]);
}
const removeItem = (index: number) => { const removeItem = (index: number) => {
setItems(items.filter((_, i) => i !== index)); setItems(items.filter((_, i) => i !== index));
pluginSettings[id] = items;
}; };
useEffect(() => {
onError(error !== null);
}, [error]);
function handleChange(newValue) { function handleChange(newValue) {
const isValid = option.isValid?.call(definedSettings, newValue) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
setError(null);
onChange(newValue); onChange(newValue);
} }
function wrapChannel(id: string) {
const channel = ChannelStore.getChannel(id) as Channel;
if (!channel) {
return "Unknown Channel";
}
return (GuildStore.getGuild(channel.guild_id)?.name ?? "Unknown Guild") + " - " + channel.name;
} }
// FIXME make channels and guilds nicer!
return ( return (
<Forms.FormSection> <Forms.FormSection>
<Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle> <Forms.FormTitle>{wordsToTitle(wordsFromCamel(id))}</Forms.FormTitle>
<Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText> <Forms.FormText className={Margins.bottom16} type="description">{option.description}</Forms.FormText>
<ErrorBoundary noop>
{items.map((item, index) => (
<React.Fragment key={`${item}-${index}`}>
<Flex
flexDirection="row"
style={{
gap: "1px",
}}
>
{option.type === OptionType.USERS ? (
<UserMentionComponent
userId={item}
className="mention"
/>
) : option.type === OptionType.CHANNELS ? (
<span style={{ color: "white" }}>{wrapChannel(item)}</span>
) : option.type === OptionType.GUILDS ? (
<span style={{ color: "white" }}>
{GuildStore.getGuild(item)?.name || "Unknown Guild"}
</span>
// TODO add logo to guild and channel?
) : (
<span>{item}</span>
)}
<Button
size={Button.Sizes.MIN}
onClick={() => removeItem(index)}
style={
{ background: "none", }
}
>
<CloseIcon />
</Button>
</Flex>
</React.Fragment>
))}
</ErrorBoundary>
{error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</Forms.FormSection> </Forms.FormSection>

View file

@ -8,7 +8,7 @@ import { NavContextMenuPatchCallback } from "@api/ContextMenu";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { Menu, useState } from "@webpack/common"; import { Menu, React } from "@webpack/common";
function createContextMenu(name: "Guild" | "User" | "Channel", value: any) { function createContextMenu(name: "Guild" | "User" | "Channel", value: any) {
return ( return (
@ -26,16 +26,24 @@ function renderRegisteredPlugins(name: "Guild" | "User" | "Channel", value: any)
const type = name === "Guild" ? OptionType.GUILDS : name === "User" ? OptionType.USERS : OptionType.CHANNELS; const type = name === "Guild" ? OptionType.GUILDS : name === "User" ? OptionType.USERS : OptionType.CHANNELS;
const plugins = registeredPlugins[type]; const plugins = registeredPlugins[type];
const [checkedItems, setCheckedItems] = useState<Record<string, boolean>>({});
const [checkedItems, setCheckedItems] = React.useState<Record<string, boolean>>(
Object.fromEntries(
Object.keys(plugins).flatMap(plugin =>
plugins[plugin].map(setting => [`${plugin}-${setting}-${value.id}`, Vencord.Plugins.plugins[plugin].settings?.store[setting].includes(value.id)])
)
)
);
const handleCheckboxClick = (plugin: string, setting: string) => { const handleCheckboxClick = (plugin: string, setting: string) => {
const key = `${plugin}-${setting}`; const key = `${plugin}-${setting}-${value.id}`;
setCheckedItems(prevState => ({ setCheckedItems(prevState => ({
...prevState, ...prevState,
[key]: !prevState[key] [key]: !prevState[key]
})); }));
// @ts-ignore (can't be undefined because settings have to exist for this to be called in the first place) // @ts-ignore settings must be defined otherwise we wouldn't be here
const s = Vencord.Plugins.plugins[plugin].settings.store[setting]; const s = Vencord.Plugins.plugins[plugin].settings.store[setting];
// @ts-ignore
Vencord.Plugins.plugins[plugin].settings.store[setting] = s.includes(value.id) Vencord.Plugins.plugins[plugin].settings.store[setting] = s.includes(value.id)
? s.filter(id => id !== value.id) ? s.filter(id => id !== value.id)
: [...s, value.id]; : [...s, value.id];
@ -49,10 +57,9 @@ function renderRegisteredPlugins(name: "Guild" | "User" | "Channel", value: any)
{plugins[plugin].map(setting => ( {plugins[plugin].map(setting => (
<Menu.MenuCheckboxItem <Menu.MenuCheckboxItem
id={`vc-plugin-settings-${plugin}-${setting}`} id={`vc-plugin-settings-${plugin}-${setting}`}
// @ts-ignore label={Vencord.Plugins.plugins[plugin].settings?.def[setting].popoutText ?? setting}
label={Vencord.Plugins.plugins[plugin].settings.def[setting].popoutText ?? setting}
action={() => handleCheckboxClick(plugin, setting)} action={() => handleCheckboxClick(plugin, setting)}
checked={checkedItems[`${plugin}-${setting}`]} checked={checkedItems[`${plugin}-${setting}-${value.id}`]}
/> />
))} ))}
</Menu.MenuItem> </Menu.MenuItem>
@ -93,7 +100,7 @@ const registeredPlugins: Record<OptionType.USERS | OptionType.GUILDS | OptionTyp
export default definePlugin({ export default definePlugin({
name: "SettingListsAPI", name: "SettingListsAPI",
description: "API to automatically add context menus for settings", description: "API that automatically adds context menus for User/Guild/Channel arrays of plugins",
authors: [Devs.Elvyra], authors: [Devs.Elvyra],
contextMenus: { contextMenus: {
"channel-context": MakeContextCallback("Channel"), "channel-context": MakeContextCallback("Channel"),
@ -108,7 +115,7 @@ export default definePlugin({
const settings = plugin.settings.def; const settings = plugin.settings.def;
for (const settingKey of Object.keys(settings)) { for (const settingKey of Object.keys(settings)) {
const setting = settings[settingKey]; const setting = settings[settingKey];
if (setting.type === OptionType.USERS || setting.type === OptionType.GUILDS || setting.type === OptionType.CHANNELS) { if ((setting.type === OptionType.USERS || setting.type === OptionType.GUILDS || setting.type === OptionType.CHANNELS) && !setting.hidePopout) {
if (!registeredPlugins[setting.type][plugin.name]) { if (!registeredPlugins[setting.type][plugin.name]) {
registeredPlugins[setting.type][plugin.name] = []; registeredPlugins[setting.type][plugin.name] = [];
} }

View file

@ -86,12 +86,12 @@ const settings = definePluginSettings({
}, },
ignoreChannels: { ignoreChannels: {
type: OptionType.CHANNELS, type: OptionType.CHANNELS,
description: "List of channel IDs to ignore", description: "List of channels to ignore",
popoutText: "Ignore deleted messages in this channel" popoutText: "Ignore deleted messages in this channel"
}, },
ignoreGuilds: { ignoreGuilds: {
type: OptionType.GUILDS, type: OptionType.GUILDS,
description: "List of guild IDs to ignore", description: "List of guilds to ignore",
popoutText: "Ignore deleted messages in this guild", popoutText: "Ignore deleted messages in this guild",
}, },
}); });

View file

@ -74,15 +74,18 @@ const settings = definePluginSettings({
stringRules: { stringRules: {
type: OptionType.LIST, type: OptionType.LIST,
hidden: true, hidden: true,
description: ""
}, },
regexRules: { regexRules: {
type: OptionType.LIST, type: OptionType.LIST,
hidden: true, hidden: true,
description: ""
}, },
migrated: { migrated: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
hidden: true, hidden: true,
default: false, default: false,
description: ""
} }
}); });
@ -232,8 +235,8 @@ function applyRules(content: string): string {
} }
} }
if (settings.store.stringRulesregexRules) { if (settings.store.regexRules) {
for (const rule of settings.store.stringRulesregexRules) { for (const rule of settings.store.regexRules) {
if (!rule.find) continue; if (!rule.find) continue;
if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue; if (rule.onlyIfIncludes && !content.includes(rule.onlyIfIncludes)) continue;

View file

@ -267,6 +267,7 @@ export interface PluginSettingSliderDef {
export interface PluginSettingListDef{ export interface PluginSettingListDef{
type: OptionType.LIST | OptionType.CHANNELS | OptionType.GUILDS | OptionType.USERS; type: OptionType.LIST | OptionType.CHANNELS | OptionType.GUILDS | OptionType.USERS;
popoutText?: string; popoutText?: string;
hidePopout?: boolean;
} }
export interface IPluginOptionComponentProps { export interface IPluginOptionComponentProps {
@ -302,6 +303,7 @@ type PluginSettingType<O extends PluginSettingDef> = O extends PluginSettingStri
O extends PluginSettingSelectDef ? O["options"][number]["value"] : O extends PluginSettingSelectDef ? O["options"][number]["value"] :
O extends PluginSettingSliderDef ? number : O extends PluginSettingSliderDef ? number :
O extends PluginSettingComponentDef ? any : O extends PluginSettingComponentDef ? any :
O extends PluginSettingListDef ? any[] :
never; never;
type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? ( type PluginSettingDefaultType<O extends PluginSettingDef> = O extends PluginSettingSelectDef ? (
O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined O["options"] extends { default?: boolean; }[] ? O["options"][number]["value"] : undefined
@ -361,6 +363,7 @@ export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon
export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>; export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid<PluginSettingSelectOption>;
export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>; export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid<number>;
export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon; export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon;
export type PluginOptionList = PluginSettingListDef & PluginSettingCommon;
export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = { export type PluginNative<PluginExports extends Record<string, (event: Electron.IpcMainInvokeEvent, ...args: any[]) => any>> = {
[key in keyof PluginExports]: [key in keyof PluginExports]: