feat: settings sliders (#120)

Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
megumin 2022-10-19 20:57:27 +01:00 committed by GitHub
parent efab399309
commit 1f50f78912
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 123 additions and 40 deletions

@ -15,10 +15,9 @@ import {
SettingInputComponent, SettingInputComponent,
SettingNumericComponent, SettingNumericComponent,
SettingSelectComponent, SettingSelectComponent,
SettingSliderComponent,
} from "./components"; } from "./components";
const { FormSection, FormText, FormTitle } = Forms;
const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers")); const UserSummaryItem = lazyWebpack(filters.byCode("defaultRenderUser", "showDefaultAvatarsForNullUsers"));
const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"])); const AvatarStyles = lazyWebpack(filters.byProps(["moreUsers", "emptyUser", "avatarContainer", "clickableAvatar"]));
const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any; const UserRecord: Constructor<Partial<User>> = proxyLazy(() => UserStore.getCurrentUser().constructor) as any;
@ -80,7 +79,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
function renderSettings() { function renderSettings() {
if (!pluginSettings || !plugin.options) { if (!pluginSettings || !plugin.options) {
return <FormText>There are no settings for this plugin.</FormText>; return <Forms.FormText>There are no settings for this plugin.</Forms.FormText>;
} }
const options: JSX.Element[] = []; const options: JSX.Element[] = [];
@ -110,6 +109,11 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
} }
case OptionType.BOOLEAN: { case OptionType.BOOLEAN: {
options.push(<SettingBooleanComponent key={key} option={setting} {...props} />); options.push(<SettingBooleanComponent key={key} option={setting} {...props} />);
break;
}
case OptionType.SLIDER: {
options.push(<SettingSliderComponent key={key} option={setting} {...props} />);
break;
} }
} }
} }
@ -142,9 +146,9 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
<Text variant="heading-md/bold">{plugin.name}</Text> <Text variant="heading-md/bold">{plugin.name}</Text>
</ModalHeader> </ModalHeader>
<ModalContent style={{ marginBottom: 8, marginTop: 8 }}> <ModalContent style={{ marginBottom: 8, marginTop: 8 }}>
<FormSection> <Forms.FormSection>
<FormTitle tag="h3">About {plugin.name}</FormTitle> <Forms.FormTitle tag="h3">About {plugin.name}</Forms.FormTitle>
<FormText>{plugin.description}</FormText> <Forms.FormText>{plugin.description}</Forms.FormText>
<div style={{ marginTop: 8, marginBottom: 8, width: "fit-content" }}> <div style={{ marginTop: 8, marginBottom: 8, width: "fit-content" }}>
<UserSummaryItem <UserSummaryItem
users={authors} users={authors}
@ -157,20 +161,20 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti
renderMoreUsers={renderMoreUsers} renderMoreUsers={renderMoreUsers}
/> />
</div> </div>
</FormSection> </Forms.FormSection>
{!!plugin.settingsAboutComponent && ( {!!plugin.settingsAboutComponent && (
<div style={{ marginBottom: 8 }}> <div style={{ marginBottom: 8 }}>
<FormSection> <Forms.FormSection>
<ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent"> <ErrorBoundary message="An error occurred while rendering this plugin's custom InfoComponent">
<plugin.settingsAboutComponent /> <plugin.settingsAboutComponent />
</ErrorBoundary> </ErrorBoundary>
</FormSection> </Forms.FormSection>
</div> </div>
)} )}
<FormSection> <Forms.FormSection>
<FormTitle tag="h3">Settings</FormTitle> <Forms.FormTitle tag="h3">Settings</Forms.FormTitle>
{renderSettings()} {renderSettings()}
</FormSection> </Forms.FormSection>
</ModalContent> </ModalContent>
<ModalFooter> <ModalFooter>
<Flex> <Flex>

@ -2,8 +2,6 @@ import { ISettingElementProps } from ".";
import { PluginOptionBoolean } from "../../../utils/types"; import { PluginOptionBoolean } from "../../../utils/types";
import { Forms, React, Select } from "../../../webpack/common"; import { Forms, React, Select } from "../../../webpack/common";
const { FormSection, FormTitle, FormText } = Forms;
export function SettingBooleanComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionBoolean>) { export function SettingBooleanComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionBoolean>) {
const def = pluginSettings[id] ?? option.default; const def = pluginSettings[id] ?? option.default;
@ -31,8 +29,8 @@ export function SettingBooleanComponent({ option, pluginSettings, id, onChange,
} }
return ( return (
<FormSection> <Forms.FormSection>
<FormTitle>{option.description}</FormTitle> <Forms.FormTitle>{option.description}</Forms.FormTitle>
<Select <Select
isDisabled={option.disabled?.() ?? false} isDisabled={option.disabled?.() ?? false}
options={options} options={options}
@ -44,8 +42,8 @@ export function SettingBooleanComponent({ option, pluginSettings, id, onChange,
serialize={v => String(v)} serialize={v => String(v)}
{...option.componentProps} {...option.componentProps}
/> />
{error && <FormText style={{ color: "var(--text-danger)" }}>{error}</FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</FormSection> </Forms.FormSection>
); );
} }

@ -2,8 +2,6 @@ import { ISettingElementProps } from ".";
import { OptionType, PluginOptionNumber } from "../../../utils/types"; import { OptionType, PluginOptionNumber } from "../../../utils/types";
import { Forms, React, TextInput } from "../../../webpack/common"; import { Forms, React, TextInput } from "../../../webpack/common";
const { FormSection, FormTitle, FormText } = Forms;
const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER); const MAX_SAFE_NUMBER = BigInt(Number.MAX_SAFE_INTEGER);
export function SettingNumericComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionNumber>) { export function SettingNumericComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionNumber>) {
@ -33,8 +31,8 @@ export function SettingNumericComponent({ option, pluginSettings, id, onChange,
} }
return ( return (
<FormSection> <Forms.FormSection>
<FormTitle>{option.description}</FormTitle> <Forms.FormTitle>{option.description}</Forms.FormTitle>
<TextInput <TextInput
type="number" type="number"
pattern="-?[0-9]+" pattern="-?[0-9]+"
@ -44,7 +42,7 @@ export function SettingNumericComponent({ option, pluginSettings, id, onChange,
disabled={option.disabled?.() ?? false} disabled={option.disabled?.() ?? false}
{...option.componentProps} {...option.componentProps}
/> />
{error && <FormText style={{ color: "var(--text-danger)" }}>{error}</FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</FormSection> </Forms.FormSection>
); );
} }

@ -2,8 +2,6 @@ import { ISettingElementProps } from ".";
import { PluginOptionSelect } from "../../../utils/types"; import { PluginOptionSelect } from "../../../utils/types";
import { Forms, React, Select } from "../../../webpack/common"; import { Forms, React, Select } from "../../../webpack/common";
const { FormSection, FormTitle, FormText } = Forms;
export function SettingSelectComponent({ option, pluginSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) { export function SettingSelectComponent({ option, pluginSettings, onChange, onError, id }: ISettingElementProps<PluginOptionSelect>) {
const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value; const def = pluginSettings[id] ?? option.options?.find(o => o.default)?.value;
@ -25,8 +23,8 @@ export function SettingSelectComponent({ option, pluginSettings, onChange, onErr
} }
return ( return (
<FormSection> <Forms.FormSection>
<FormTitle>{option.description}</FormTitle> <Forms.FormTitle>{option.description}</Forms.FormTitle>
<Select <Select
isDisabled={option.disabled?.() ?? false} isDisabled={option.disabled?.() ?? false}
options={option.options} options={option.options}
@ -38,7 +36,7 @@ export function SettingSelectComponent({ option, pluginSettings, onChange, onErr
serialize={v => String(v)} serialize={v => String(v)}
{...option.componentProps} {...option.componentProps}
/> />
{error && <FormText style={{ color: "var(--text-danger)" }}>{error}</FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</FormSection> </Forms.FormSection>
); );
} }

@ -0,0 +1,49 @@
import { ISettingElementProps } from ".";
import { PluginOptionSlider } from "../../../utils/types";
import { Forms, React, Slider } from "../../../webpack/common";
export function makeRange(start: number, end: number, step = 1) {
const ranges: number[] = [];
for (let value = start; value <= end; value += step) {
ranges.push(Math.round(value * 100) / 100);
}
return ranges;
}
export function SettingSliderComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionSlider>) {
const def = pluginSettings[id] ?? option.default;
const [error, setError] = React.useState<string | null>(null);
React.useEffect(() => {
onError(error !== null);
}, [error]);
function handleChange(newValue: number): void {
let isValid = (option.isValid && option.isValid(newValue)) ?? true;
if (typeof isValid === "string") setError(isValid);
else if (!isValid) setError("Invalid input provided.");
else {
setError(null);
onChange(newValue);
}
}
return (
<Forms.FormSection>
<Forms.FormTitle>{option.description}</Forms.FormTitle>
<Slider
disabled={option.disabled?.() ?? false}
markers={option.markers}
minValue={option.markers[0]}
maxValue={option.markers[option.markers.length - 1]}
initialValue={def}
onValueChange={handleChange}
onValueRender={(v: number) => String(v.toFixed(2))}
stickToMarkers={option.stickToMarkers ?? true}
{...option.componentProps}
/>
</Forms.FormSection>
);
}

@ -2,8 +2,6 @@ import { ISettingElementProps } from ".";
import { PluginOptionString } from "../../../utils/types"; import { PluginOptionString } from "../../../utils/types";
import { Forms, React, TextInput } from "../../../webpack/common"; import { Forms, React, TextInput } from "../../../webpack/common";
const { FormSection, FormTitle, FormText } = Forms;
export function SettingInputComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) { export function SettingInputComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps<PluginOptionString>) {
const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null); const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null);
const [error, setError] = React.useState<string | null>(null); const [error, setError] = React.useState<string | null>(null);
@ -23,8 +21,8 @@ export function SettingInputComponent({ option, pluginSettings, id, onChange, on
} }
return ( return (
<FormSection> <Forms.FormSection>
<FormTitle>{option.description}</FormTitle> <Forms.FormTitle>{option.description}</Forms.FormTitle>
<TextInput <TextInput
type="text" type="text"
value={state} value={state}
@ -33,7 +31,7 @@ export function SettingInputComponent({ option, pluginSettings, id, onChange, on
disabled={option.disabled?.() ?? false} disabled={option.disabled?.() ?? false}
{...option.componentProps} {...option.componentProps}
/> />
{error && <FormText style={{ color: "var(--text-danger)" }}>{error}</FormText>} {error && <Forms.FormText style={{ color: "var(--text-danger)" }}>{error}</Forms.FormText>}
</FormSection> </Forms.FormSection>
); );
} }

@ -15,3 +15,4 @@ export * from "./SettingBooleanComponent";
export * from "./SettingNumericComponent"; export * from "./SettingNumericComponent";
export * from "./SettingSelectComponent"; export * from "./SettingSelectComponent";
export * from "./SettingTextComponent"; export * from "./SettingTextComponent";
export * from "./SettingSliderComponent";

@ -1,8 +1,11 @@
import definePlugin from "../utils/types";
import { Devs } from "../utils/constants";
import { Message, ReactionEmoji } from "discord-types/general"; import { Message, ReactionEmoji } from "discord-types/general";
import { FluxDispatcher, SelectedChannelStore } from "../webpack/common";
import { makeRange } from "../components/PluginSettings/components/SettingSliderComponent";
import { Devs } from "../utils/constants";
import { sleep } from "../utils/misc"; import { sleep } from "../utils/misc";
import definePlugin, { OptionType } from "../utils/types";
import { Settings } from "../Vencord";
import { FluxDispatcher, SelectedChannelStore } from "../webpack/common";
interface IMessageCreate { interface IMessageCreate {
type: "MESSAGE_CREATE"; type: "MESSAGE_CREATE";
@ -67,6 +70,16 @@ export default definePlugin({
FluxDispatcher.unsubscribe("MESSAGE_CREATE", this.onMessage); FluxDispatcher.unsubscribe("MESSAGE_CREATE", this.onMessage);
FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD", this.onReaction); FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD", this.onReaction);
}, },
options: {
volume: {
description: "Volume of the 🗿🗿🗿",
type: OptionType.SLIDER,
markers: makeRange(0, 1, 0.1),
default: 0.5,
stickToMarkers: false,
}
}
}); });
function countOccurrences(sourceString: string, subString: string) { function countOccurrences(sourceString: string, subString: string) {
@ -101,5 +114,6 @@ function getMoyaiCount(message: string) {
function boom() { function boom() {
const audioElement = document.createElement("audio"); const audioElement = document.createElement("audio");
audioElement.src = MOYAI_URL; audioElement.src = MOYAI_URL;
audioElement.volume = Settings.plugins.Moyai.volume;
audioElement.play(); audioElement.play();
} }

@ -70,13 +70,15 @@ export enum OptionType {
BIGINT, BIGINT,
BOOLEAN, BOOLEAN,
SELECT, SELECT,
SLIDER,
} }
export type PluginOptionsItem = export type PluginOptionsItem =
| PluginOptionString | PluginOptionString
| PluginOptionNumber | PluginOptionNumber
| PluginOptionBoolean | PluginOptionBoolean
| PluginOptionSelect; | PluginOptionSelect
| PluginOptionSlider;
export interface PluginOptionBase { export interface PluginOptionBase {
description: string; description: string;
@ -132,4 +134,24 @@ export interface PluginOptionSelectOption {
default?: boolean; default?: boolean;
} }
export interface PluginOptionSlider extends PluginOptionBase {
type: OptionType.SLIDER;
/**
* All the possible values in the slider. Needs at least two values.
*/
markers: number[];
/**
* Default value to use
*/
default: number;
/**
* If false, allow users to select values in-between your markers.
*/
stickToMarkers?: boolean;
/**
* Prevents the user from saving settings if this is false or a string
*/
isValid?(value: number): number;
}
export type IpcRes<V = any> = { ok: true; value: V; } | { ok: false, error: any; }; export type IpcRes<V = any> = { ok: true; value: V; } | { ok: false, error: any; };

@ -31,6 +31,7 @@ export let TextInput: any;
export let Text: (props: TextProps) => JSX.Element; export let Text: (props: TextProps) => JSX.Element;
export const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); export const Select = lazyWebpack(filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems"));
export const Slider = lazyWebpack(filters.byCode("closestMarkerIndex", "stickToMarkers"));
export let Parser: any; export let Parser: any;
export let Alerts: { export let Alerts: {