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:
parent
826162952c
commit
4f2a87a610
6 changed files with 113 additions and 31 deletions
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
onChange(newValue);
|
||||||
if (typeof isValid === "string") setError(isValid);
|
|
||||||
else if (!isValid) setError("Invalid input provided.");
|
|
||||||
else {
|
|
||||||
setError(null);
|
|
||||||
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>
|
||||||
|
|
|
@ -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"),
|
||||||
|
@ -106,9 +113,9 @@ export default definePlugin({
|
||||||
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
for (const plugin of Object.values(Vencord.Plugins.plugins)) {
|
||||||
if (!Vencord.Plugins.isPluginEnabled(plugin.name) || !plugin.settings) continue;
|
if (!Vencord.Plugins.isPluginEnabled(plugin.name) || !plugin.settings) continue;
|
||||||
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] = [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
Loading…
Reference in a new issue