aboutsummaryrefslogtreecommitdiff
path: root/src/components/PluginSettings/PluginModal.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/components/PluginSettings/PluginModal.tsx')
-rw-r--r--src/components/PluginSettings/PluginModal.tsx202
1 files changed, 202 insertions, 0 deletions
diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx
new file mode 100644
index 0000000..a324300
--- /dev/null
+++ b/src/components/PluginSettings/PluginModal.tsx
@@ -0,0 +1,202 @@
+import { User } from "discord-types/general";
+import { Constructor } from "type-fest";
+
+import { generateId } from "../../api/Commands";
+import { useSettings } from "../../api/settings";
+import { lazyWebpack, proxyLazy } from "../../utils";
+import { ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize } from "../../utils/modal";
+import { OptionType, Plugin } from "../../utils/types";
+import { filters } from "../../webpack";
+import { Button, FluxDispatcher, Forms, React, Text, Tooltip, UserStore, UserUtils } from "../../webpack/common";
+import ErrorBoundary from "../ErrorBoundary";
+import { Flex } from "../Flex";
+import {
+ SettingBooleanComponent,
+ SettingInputComponent,
+ SettingNumericComponent,
+ SettingSelectComponent,
+} from "./components";
+
+const { FormSection, FormText, FormTitle } = Forms;
+
+const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
+const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"]));
+const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
+
+interface PluginModalProps extends ModalProps {
+ plugin: Plugin;
+ onRestartNeeded(): void;
+}
+
+/** To stop discord making unwanted requests... */
+function makeDummyUser(user: { name: string, id: BigInt; }) {
+ const newUser = new UserRecord({
+ username: user.name,
+ id: generateId(),
+ bot: true,
+ });
+ FluxDispatcher.dispatch({
+ type: "USER_UPDATE",
+ user: newUser,
+ });
+ return newUser;
+}
+
+export default function PluginModal({ plugin, onRestartNeeded, onClose, transitionState }: PluginModalProps) {
+ const [authors, setAuthors] = React.useState<Partial<User>[]>([]);
+
+ const pluginSettings = useSettings().plugins[plugin.name];
+
+ const [tempSettings, setTempSettings] = React.useState<Record<string, any>>({});
+
+ const [errors, setErrors] = React.useState<Record<string, boolean>>({});
+
+ const canSubmit = () => Object.values(errors).every(e => !e);
+
+ React.useEffect(() => {
+ (async () => {
+ for (const user of plugin.authors.slice(0, 6)) {
+ const author = user.id ? await UserUtils.fetchUser(`${user.id}`).catch(() => null) : makeDummyUser(user);
+ setAuthors(a => [...a, author || makeDummyUser(user)]);
+ }
+ })();
+ }, []);
+
+ function saveAndClose() {
+ if (!plugin.options) {
+ onClose();
+ return;
+ }
+ let restartNeeded = false;
+ for (const [key, value] of Object.entries(tempSettings)) {
+ const option = plugin.options[key];
+ pluginSettings[key] = value;
+ option?.onChange?.(value);
+ if (option?.restartNeeded) restartNeeded = true;
+ }
+ if (restartNeeded) onRestartNeeded();
+ onClose();
+ }
+
+ function renderSettings() {
+ if (!pluginSettings || !plugin.options) {
+ return <FormText>There are no settings for this plugin.</FormText>;
+ }
+
+ const options: JSX.Element[] = [];
+ for (const [key, setting] of Object.entries(plugin.options)) {
+ function onChange(newValue) {
+ setTempSettings(s => ({ ...s, [key]: newValue }));
+ }
+
+ function onError(hasError: boolean) {
+ setErrors(e => ({ ...e, [key]: hasError }));
+ }
+
+ const props = { onChange, pluginSettings, id: key, onError };
+ switch (setting.type) {
+ case OptionType.SELECT: {
+ options.push(<SettingSelectComponent key={key} option={setting} {...props} />);
+ break;
+ }
+ case OptionType.STRING: {
+ options.push(<SettingInputComponent key={key} option={setting} {...props} />);
+ break;
+ }
+ case OptionType.NUMBER:
+ case OptionType.BIGINT: {
+ options.push(<SettingNumericComponent key={key} option={setting} {...props} />);
+ break;
+ }
+ case OptionType.BOOLEAN: {
+ options.push(<SettingBooleanComponent key={key} option={setting} {...props} />);
+ }
+ }
+ }
+ return <Flex flexDirection="column" style={{ gap: 12 }}>{options}</Flex>;
+ }
+
+ function renderMoreUsers(_label: string, count: number) {
+ const sliceCount = plugin.authors.length - count;
+ const sliceStart = plugin.authors.length - sliceCount;
+ const sliceEnd = sliceStart + plugin.authors.length - count;
+
+ return (
+ <Tooltip text={plugin.authors.slice(sliceStart, sliceEnd).map(u => u.name).join(", ")}>
+ {({ onMouseEnter, onMouseLeave }) => (
+ <div
+ className={AvatarStyles.moreUsers}
+ onMouseEnter={onMouseEnter}
+ onMouseLeave={onMouseLeave}
+ >
+ +{sliceCount}
+ </div>
+ )}
+ </Tooltip>
+ );
+ }
+
+ return (
+ <ModalRoot transitionState={transitionState} size={ModalSize.MEDIUM}>
+ <ModalHeader>
+ <Text variant="heading-md/bold">{plugin.name}</Text>
+ </ModalHeader>
+ <ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
+ <FormSection>
+ <FormTitle tag="h3">About {plugin.name}</FormTitle>
+ <FormText>{plugin.description}</FormText>
+ <div style={{ marginTop: 8, marginBottom: 8, width: "fit-content" }}>
+ <UserSummaryItem
+ users={authors}
+ count={plugin.authors.length}
+ guildId={undefined}
+ renderIcon={false}
+ max={6}
+ showDefaultAvatarsForNullUsers
+ showUserPopout
+ renderMoreUsers={renderMoreUsers}
+ />
+ </div>
+ </FormSection>
+ {!!plugin.settingsAboutComponent && (
+ <div style={{ marginBottom: 8 }}>
+ <FormSection>
+ <ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
+ <plugin.settingsAboutComponent />
+ </ErrorBoundary>
+ </FormSection>
+ </div>
+ )}
+ <FormSection>
+ <FormTitle tag="h3">Settings</FormTitle>
+ {renderSettings()}
+ </FormSection>
+ </ModalContent>
+ <ModalFooter>
+ <Flex>
+ <Button
+ onClick={onClose}
+ size={Button.Sizes.SMALL}
+ color={Button.Colors.RED}
+ >
+ Exit Without Saving
+ </Button>
+ <Tooltip text="You must fix all errors before saving" shouldShow={!canSubmit()}>
+ {({ onMouseEnter, onMouseLeave }) => (
+ <Button
+ size={Button.Sizes.SMALL}
+ color={Button.Colors.BRAND}
+ onClick={saveAndClose}
+ onMouseEnter={onMouseEnter}
+ onMouseLeave={onMouseLeave}
+ disabled={!canSubmit()}
+ >
+ Save & Exit
+ </Button>
+ )}
+ </Tooltip>
+ </Flex>
+ </ModalFooter>
+ </ModalRoot>
+ );
+}