From ea748dfb605386b80a4919183ad6fa9249a82e21 Mon Sep 17 00:00:00 2001 From: Justice Almanzar Date: Fri, 13 Jan 2023 17:15:45 -0500 Subject: feat: Typesafe Settings Definitions (#403) Co-authored-by: Ven --- src/utils/types.ts | 139 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 39 deletions(-) (limited to 'src/utils') diff --git a/src/utils/types.ts b/src/utils/types.ts index d3083fc..5ab6857 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -81,8 +81,14 @@ export interface PluginDef { target?: "WEB" | "DESKTOP" | "BOTH"; /** * Optionally provide settings that the user can configure in the Plugins tab of settings. + * @deprecated Use `settings` instead */ + // TODO: Remove when everything is migrated to `settings` options?: Record; + /** + * Optionally provide settings that the user can configure in the Plugins tab of settings. + */ + settings?: DefinedSettings; /** * Check that this returns true before allowing a save to complete. * If a string is returned, show the error to the user. @@ -107,19 +113,25 @@ export enum OptionType { COMPONENT, } -export type PluginOptionsItem = - | PluginOptionString - | PluginOptionNumber - | PluginOptionBoolean - | PluginOptionSelect - | PluginOptionSlider - | PluginOptionComponent; +export type SettingsDefinition = Record; +export type SettingsChecks = { + [K in keyof D]?: D[K] extends PluginSettingComponentDef ? IsDisabled> : + (IsDisabled> & IsValid, DefinedSettings>); +}; -export interface PluginOptionBase { +export type PluginSettingDef = ( + | PluginSettingStringDef + | PluginSettingNumberDef + | PluginSettingBooleanDef + | PluginSettingSelectDef + | PluginSettingSliderDef + | PluginSettingComponentDef +) & PluginSettingCommon; + +export interface PluginSettingCommon { description: string; placeholder?: string; onChange?(newValue: any): void; - disabled?(): boolean; restartNeeded?: boolean; componentProps?: Record; /** @@ -127,49 +139,47 @@ export interface PluginOptionBase { */ target?: "WEB" | "DESKTOP" | "BOTH"; } - -export interface PluginOptionString extends PluginOptionBase { - type: OptionType.STRING; +interface IsDisabled { /** - * Prevents the user from saving settings if this is false or a string + * Checks if this setting should be disabled */ - isValid?(value: string): boolean | string; - default?: string; + disabled?(this: D): boolean; } - -export interface PluginOptionNumber extends PluginOptionBase { - type: OptionType.NUMBER | OptionType.BIGINT; +interface IsValid { /** * Prevents the user from saving settings if this is false or a string */ - isValid?(value: number | BigInt): boolean | string; - default?: number; + isValid?(this: D, value: T): boolean | string; } -export interface PluginOptionBoolean extends PluginOptionBase { +export interface PluginSettingStringDef { + type: OptionType.STRING; + default?: string; +} +export interface PluginSettingNumberDef { + type: OptionType.NUMBER; + default?: number; +} +export interface PluginSettingBigIntDef { + type: OptionType.BIGINT; + default?: BigInt; +} +export interface PluginSettingBooleanDef { type: OptionType.BOOLEAN; - /** - * Prevents the user from saving settings if this is false or a string - */ - isValid?(value: boolean): boolean | string; default?: boolean; } -export interface PluginOptionSelect extends PluginOptionBase { +export interface PluginSettingSelectDef { type: OptionType.SELECT; - /** - * Prevents the user from saving settings if this is false or a string - */ - isValid?(value: PluginOptionSelectOption): boolean | string; - options: PluginOptionSelectOption[]; + options: readonly PluginSettingSelectOption[]; } -export interface PluginOptionSelectOption { +export interface PluginSettingSelectOption { label: string; value: string | number | boolean; default?: boolean; } -export interface PluginOptionSlider extends PluginOptionBase { +export interface PluginSettingSliderDef { type: OptionType.SLIDER; /** * All the possible values in the slider. Needs at least two values. @@ -183,10 +193,6 @@ export interface PluginOptionSlider extends PluginOptionBase { * 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): boolean | string; } interface IPluginOptionComponentProps { @@ -206,12 +212,67 @@ interface IPluginOptionComponentProps { /** * The options object */ - option: PluginOptionComponent; + option: PluginSettingComponentDef; } -export interface PluginOptionComponent extends PluginOptionBase { +export interface PluginSettingComponentDef { type: OptionType.COMPONENT; component: (props: IPluginOptionComponentProps) => JSX.Element; } +/** Maps a `PluginSettingDef` to its value type */ +type PluginSettingType = O extends PluginSettingStringDef ? string : + O extends PluginSettingNumberDef ? number : + O extends PluginSettingBigIntDef ? BigInt : + O extends PluginSettingBooleanDef ? boolean : + O extends PluginSettingSelectDef ? O["options"][number]["value"] : + O extends PluginSettingSliderDef ? number : + O extends PluginSettingComponentDef ? any : + never; + +type SettingsStore = { + [K in keyof D]: PluginSettingType; +}; + +/** An instance of defined plugin settings */ +export interface DefinedSettings = {}> { + /** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */ + store: SettingsStore; + /** + * React hook for getting the settings for this plugin + * @param filter optional filter to avoid rerenders for irrelavent settings + */ + use>(filter?: F[]): Pick, F>; + /** Definitions of each setting */ + def: D; + /** Setting methods with return values that could rely on other settings */ + checks: C; + /** + * Name of the plugin these settings belong to, + * will be an empty string until plugin is initialized + */ + pluginName: string; +} + +export type PartialExcept = Partial & Required>; + export type IpcRes = { ok: true; value: V; } | { ok: false, error: any; }; + +/* -------------------------------------------- */ +/* Legacy Options Types */ +/* -------------------------------------------- */ + +export type PluginOptionBase = PluginSettingCommon & IsDisabled; +export type PluginOptionsItem = + | PluginOptionString + | PluginOptionNumber + | PluginOptionBoolean + | PluginOptionSelect + | PluginOptionSlider + | PluginOptionComponent; +export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon; -- cgit