feat: settings sliders (#120)
Co-authored-by: Ven <vendicated@riseup.net>
This commit is contained in:
parent
efab399309
commit
1f50f78912
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: {
|
||||||
|
|
Loading…
Reference in a new issue