From 00b5d800a0dd9acb2571098f38ce984f62df8ba3 Mon Sep 17 00:00:00 2001 From: Zerebos Date: Thu, 5 Dec 2024 16:31:40 -0500 Subject: [PATCH] Create settings builder --- renderer/src/modules/api/index.js | 2 + renderer/src/modules/api/ui.js | 60 ++++++++++++++++++++++ renderer/src/modules/react.js | 6 ++- renderer/src/ui/settings/group.jsx | 82 ++++++++++++++++-------------- 4 files changed, 110 insertions(+), 40 deletions(-) diff --git a/renderer/src/modules/api/index.js b/renderer/src/modules/api/index.js index cf9174ee..937b0644 100644 --- a/renderer/src/modules/api/index.js +++ b/renderer/src/modules/api/index.js @@ -26,6 +26,7 @@ import SearchInput from "@ui/settings/components/search"; import SliderInput from "@ui/settings/components/slider"; import SwitchInput from "@ui/settings/components/switch"; import TextInput from "@ui/settings/components/textbox"; +import SettingGroup from "@ui/settings/group"; const bounded = new Map(); const PluginAPI = new AddonAPI(PluginManager); @@ -79,6 +80,7 @@ export default class BdApi { get SliderInput() {return SliderInput;}, get SwitchInput() {return SwitchInput;}, get TextInput() {return TextInput;}, + get SettingGroup() {return SettingGroup;} }; Net = {fetch}; } diff --git a/renderer/src/modules/api/ui.js b/renderer/src/modules/api/ui.js index d7176f38..9b24b190 100644 --- a/renderer/src/modules/api/ui.js +++ b/renderer/src/modules/api/ui.js @@ -4,6 +4,8 @@ import Modals from "@ui/modals"; import Toasts from "@ui/toasts"; import Notices from "@ui/notices"; import Tooltip from "@ui/tooltip"; +import Group, {buildSetting} from "@ui/settings/group"; +import React from "@modules/react"; /** @@ -137,6 +139,64 @@ const UI = { if (data.error) throw new Error(data.error); return data; + }, + + /** + * Creates a single setting wrapped in a setting item that has a name and note. + * The shape of the object should match the props of the component you want to render, check the + * `BdApi.Components` section for details. Shown below are ones common to all setting types. + * @param {object} setting + * @param {string} setting.id Identifier to used for callbacks + * @param {string} setting.name Visual name to display + * @param {string} setting.note Visual description to display + * @param {any} setting.value Current value of the setting + * @param {CallableFunction} [setting.onChange] Callback when the value changes (only argument is new value) + * @param {boolean} [setting.disabled] Whether this setting is disabled + + * @returns A SettingItem with a an input as the child + */ + buildSetting(setting) { + return buildSetting(setting); + }, + + /** + * Creates a settings panel (react element) based on json-like data. + * + * The `settings` array here is an array of the same settings types described in `buildSetting` above. + * However, this API allows one additional setting "type" called `group`. This has the same properties + * as the React Component found under the `Components` API. + * + * `onChange` will always be given 3 arguments: group id, setting id, and setting value. In the case + * that you have settings on the "root" of the panel, the group id is `null`. + * + * `onDrawerToggle` is given 2 arguments: group id, and the current shown state. You can use this to + * save drawer states. + * + * `getDrawerState` is given 2 arguments: group id, and the default shown state. You can use this to + * recall a saved drawer state. + * + * @param {object} props + * @param {Array} props.settings Array of settings to show + * @param {CallableFunction} props.onChange Function called on every change + * @param {CallableFunction} [props.onDrawerToggle] Optionally used to save drawer states + * @param {CallableFunction} [props.getDrawerState] Optionially used to recall drawer states + * @returns React element usable for a settings panel + */ + buildSettingsPanel({settings, onChange, onDrawerToggle, getDrawerState}) { + if (!settings?.length) throw new Error("No settings provided!"); + if (typeof(onChange) !== "function") throw new Error("No change listener provided!"); + return React.createElement(React.Fragment, null, settings.map(setting => { + if (setting.type === "group") { + const shownByDefault = setting.hasOwnProperty("shown") ? setting.shown : true; + const groupProps = Object.assign({}, setting, { + onChange, + onDrawerToggle: state => onDrawerToggle?.(setting.id, state), + shown: getDrawerState?.(setting.id, shownByDefault) ?? shownByDefault + }); + return React.createElement(Group, groupProps); + } + return buildSetting(Object.assign({}, setting, {onChange: value => onChange(null, setting.id, value)})); + })); } }; diff --git a/renderer/src/modules/react.js b/renderer/src/modules/react.js index b5720fbd..f6162cd0 100644 --- a/renderer/src/modules/react.js +++ b/renderer/src/modules/react.js @@ -1,3 +1,5 @@ -import DiscordModules from "./discordmodules"; -export default DiscordModules.React; +import DiscordModules from "./discordmodules"; +/** @type {import("react")} */ +const React = DiscordModules.React; +export default React; export const ReactDOM = DiscordModules.ReactDOM; \ No newline at end of file diff --git a/renderer/src/ui/settings/group.jsx b/renderer/src/ui/settings/group.jsx index aba72f62..9e270122 100644 --- a/renderer/src/ui/settings/group.jsx +++ b/renderer/src/ui/settings/group.jsx @@ -1,39 +1,45 @@ -import React from "@modules/react"; - -import Drawer from "./drawer"; -import Switch from "./components/switch"; -import Dropdown from "./components/dropdown"; -import Number from "./components/number"; -import Item from "./components/item"; -import Textbox from "./components/textbox"; -import Slider from "./components/slider"; -import Radio from "./components/radio"; -import Keybind from "./components/keybind"; -import Color from "./components/color"; - -const {useCallback} = React; - - -export default function Group({onChange, id, name, button, shown, onDrawerToggle, showDivider, collapsible, settings}) { - const change = useCallback((settingId, value) => { - if (id) onChange?.(id, settingId, value); - else onChange?.(settingId, value); - }, [id, onChange]); - - return - {settings.filter(s => !s.hidden).map((setting) => { - let component = null; - const callback = value => change(setting.id, value); - if (setting.type == "dropdown") component = ; - if (setting.type == "number") component = ; - if (setting.type == "switch") component = ; - if (setting.type == "text") component = ; - if (setting.type == "slider") component = ; - if (setting.type == "radio") component = ; - if (setting.type == "keybind") component = ; - if (setting.type == "color") component = ; - if (!component) return null; - return {component}; - })} - ; +import React from "@modules/react"; + +import Drawer from "./drawer"; +import Switch from "./components/switch"; +import Dropdown from "./components/dropdown"; +import Number from "./components/number"; +import Item from "./components/item"; +import Textbox from "./components/textbox"; +import Slider from "./components/slider"; +import Radio from "./components/radio"; +import Keybind from "./components/keybind"; +import Color from "./components/color"; + +const {useCallback} = React; + + +export default function Group({onChange, id, name, button, shown, onDrawerToggle, showDivider, collapsible, settings, children}) { + const change = useCallback((settingId, value) => { + if (id) onChange?.(id, settingId, value); + else onChange?.(settingId, value); + }, [id, onChange]); + + return + {settings.length && settings.filter(s => !s.hidden).map((setting) => { + const callback = value => change(setting.id, value); + return buildSetting({...setting, onChange: callback}); + })} + {children} + ; +} + + +export function buildSetting(setting) { + let component = null; + if (setting.type == "dropdown") component = ; + if (setting.type == "number") component = ; + if (setting.type == "switch") component = ; + if (setting.type == "text") component = ; + if (setting.type == "slider") component = ; + if (setting.type == "radio") component = ; + if (setting.type == "keybind") component = ; + if (setting.type == "color") component = ; + if (!component) return null; + return {component}; } \ No newline at end of file