diff --git a/renderer/src/modules/api/index.js b/renderer/src/modules/api/index.js index dc14e7d7..39116358 100644 --- a/renderer/src/modules/api/index.js +++ b/renderer/src/modules/api/index.js @@ -69,6 +69,22 @@ export default class BdApi { get UI() {return UI;} get ReactUtils() {return ReactUtils;} get ContextMenu() {return ContextMenuAPI;} + Components = { + get Tooltip() {return DiscordModules.Tooltip;}, + get ColorInput() {return ColorInput;}, + get DropdownInput() {return DropdownInput;}, + get SettingItem() {return SettingItem;}, + get KeybindInput() {return KeybindInput;}, + get NumberInput() {return NumberInput;}, + get RadioInput() {return RadioInput;}, + get SearchInput() {return SearchInput;}, + get SliderInput() {return SliderInput;}, + get SwitchInput() {return SwitchInput;}, + get TextInput() {return TextInput;}, + get SettingGroup() {return SettingGroup;}, + get ErrorBoundary() {return ErrorBoundary;}, + }; + Net = {fetch}; } // Add legacy functions diff --git a/renderer/src/modules/api/ui.js b/renderer/src/modules/api/ui.js index bc6cccb2..c43e2583 100644 --- a/renderer/src/modules/api/ui.js +++ b/renderer/src/modules/api/ui.js @@ -6,6 +6,7 @@ import Notices from "@ui/notices"; import Tooltip from "@ui/tooltip"; import Group, {buildSetting} from "@ui/settings/group"; import React from "@modules/react"; +import ErrorBoundary from "@ui/errorboundary"; /** @@ -80,7 +81,7 @@ const UI = { * @param {string} [options.video] Youtube link or url of a video file to use as the banner * @param {string} [options.poster] URL to use for the video freeze-frame poster * @param {string|ReactElement|Array} [options.footer] What to show in the modal footer - * @param {Array} [options.changes] List of changes to show (see description for details) + * @param {Array} [options.changes] List of changes to show (see description for details) * @returns {string} The key used for this modal. */ showChangelogModal(options) { @@ -169,7 +170,8 @@ const UI = { * as the Group React Component found under the `Components` API. * * `onChange` will always be given 3 arguments: category id, setting id, and setting value. In the case - * that you have settings on the "root" of the panel, the category id is `null`. + * that you have settings on the "root" of the panel, the category id is `null`. Any `onChange` + * listeners attached to individual settings will fire before the panel-level change listener. * * `onDrawerToggle` is given 2 arguments: category id, and the current shown state. You can use this to * save drawer states. @@ -186,18 +188,28 @@ const UI = { */ 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 => { + + return React.createElement(ErrorBoundary, null, settings.map(setting => { + if (!setting.id || !setting.type) throw new Error(`Setting item missing id or type`); + if (setting.type === "category") { const shownByDefault = setting.hasOwnProperty("shown") ? setting.shown : true; - const categoryProps = Object.assign({}, setting, { - onChange, + + return React.createElement(Group, { + ...setting, + onChange: onChange, onDrawerToggle: state => onDrawerToggle?.(setting.id, state), shown: getDrawerState?.(setting.id, shownByDefault) ?? shownByDefault }); - return React.createElement(Group, categoryProps); } - return buildSetting(Object.assign({}, setting, {onChange: value => onChange(null, setting.id, value)})); + + return buildSetting({ + ...setting, + onChange: value => { + setting?.onChange?.(value); + onChange(null, setting.id, value); + } + }); })); } diff --git a/renderer/src/ui/settings/group.jsx b/renderer/src/ui/settings/group.jsx index a0fdddd9..7b1456c7 100644 --- a/renderer/src/ui/settings/group.jsx +++ b/renderer/src/ui/settings/group.jsx @@ -21,8 +21,11 @@ export default function Group({onChange, id, name, button, shown, onDrawerToggle }, [id, onChange]); return - {settings.length && settings.filter(s => !s.hidden).map((setting) => { - const callback = value => change(setting.id, value); + {settings?.length > 0 && settings.filter(s => !s.hidden).map((setting) => { + const callback = value => { + setting?.onChange?.(value); + change(setting.id, value); + }; return buildSetting({...setting, onChange: callback}); })} {children} @@ -42,5 +45,5 @@ export function buildSetting(setting) { if (setting.type == "color") children = ; if (setting.type == "custom") children = setting.children; if (!children) return null; - return {children}; + return {children}; } \ No newline at end of file