diff --git a/renderer/src/modules/api/index.js b/renderer/src/modules/api/index.js index 2a68654a..ffca6ab8 100644 --- a/renderer/src/modules/api/index.js +++ b/renderer/src/modules/api/index.js @@ -29,6 +29,9 @@ import SwitchInput from "@ui/settings/components/switch"; import TextInput from "@ui/settings/components/textbox"; import SettingGroup from "@ui/settings/group"; import ErrorBoundary from "@ui/errorboundary"; +import Text from "@ui/base/text"; +import Flex from "@ui/base/flex"; +import Button from "@ui/base/button"; const bounded = new Map(); const PluginAPI = new AddonAPI(PluginManager); @@ -39,6 +42,31 @@ const DOMAPI = new DOM(); const ContextMenuAPI = new ContextMenu(); const DefaultLogger = new Logger(); +/** + * `Components` is a namespace holding a series of React components. It is available under {@link BdApi}. + * @type Components + * @summary {@link Components} a namespace holding a series of React components + * @name Components + */ +const 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;}, + get Text() {return Text;}, + get Flex() {return Flex;}, + get Button() {return Button;}, +}; + /** * `BdApi` is a globally (`window.BdApi`) accessible object for use by plugins and developers to make their lives easier. * @name BdApi @@ -72,21 +100,7 @@ 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;}, - }; + get Components() {return Components;} Net = {fetch}; } @@ -157,21 +171,7 @@ BdApi.ContextMenu = ContextMenuAPI; * An set of react components plugins can make use of. * @type Components */ -BdApi.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;}, -}; +BdApi.Components = Components; /** * An instance of {@link Net} for using network related tools. diff --git a/renderer/src/modules/api/ui.js b/renderer/src/modules/api/ui.js index 9ba2f8ab..8a54f083 100644 --- a/renderer/src/modules/api/ui.js +++ b/renderer/src/modules/api/ui.js @@ -189,7 +189,7 @@ const UI = { buildSettingsPanel({settings, onChange, onDrawerToggle, getDrawerState}) { if (!settings?.length) throw new Error("No settings provided!"); - return React.createElement(ErrorBoundary, null, settings.map(setting => { + return React.createElement(ErrorBoundary, {id: "buildSettingsPanel", name: "BdApi.UI"}, settings.map(setting => { if (!setting.id || !setting.type) throw new Error(`Setting item missing id or type`); if (setting.type === "category") { diff --git a/renderer/src/ui/errorboundary.jsx b/renderer/src/ui/errorboundary.jsx index 4587411e..8f712c7e 100644 --- a/renderer/src/ui/errorboundary.jsx +++ b/renderer/src/ui/errorboundary.jsx @@ -4,19 +4,37 @@ import IPC from "@modules/ipc"; export default class ErrorBoundary extends React.Component { + /** + * Creates an error boundary with optional fallbacks and debug info. + * @param {object} props + * @param {ReactElement[]} props.children - An optional id for debugging purposes + * @param {string} [props.id="Unknown"] - An optional id for debugging purposes + * @param {string} [props.name="Unknown"] - An optional name for debugging purposes + * @param {boolean} [props.hideError=false] - Whether to hide the default error message in the ui (never shown if there is a fallback) + * @param {ReactElement} [props.fallback] - A fallback to show on error + * @param {function} [props.onError] - A callback called with the error when it happens + */ constructor(props) { - super(props); - this.state = {hasError: false}; + super(props); + this.state = {hasError: false}; } componentDidCatch(error) { - this.setState({hasError: true}); - if (typeof this.props.onError === "function") this.props.onError(error); + this.setState({hasError: true}); + Logger.stacktrace("ErrorBoundary", `React error detected for {name: ${this.props.name ?? "Unknown"}, id: ${this.props.id ?? "Unknown"}}`, error); + if (typeof this.props.onError === "function") this.props.onError(error); } render() { - if (this.state.hasError && !this.props.hideError) return