Improve errorboundary and expose more components
This commit is contained in:
parent
8ddce94f7e
commit
a608786ac3
|
@ -29,6 +29,9 @@ import SwitchInput from "@ui/settings/components/switch";
|
||||||
import TextInput from "@ui/settings/components/textbox";
|
import TextInput from "@ui/settings/components/textbox";
|
||||||
import SettingGroup from "@ui/settings/group";
|
import SettingGroup from "@ui/settings/group";
|
||||||
import ErrorBoundary from "@ui/errorboundary";
|
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 bounded = new Map();
|
||||||
const PluginAPI = new AddonAPI(PluginManager);
|
const PluginAPI = new AddonAPI(PluginManager);
|
||||||
|
@ -39,6 +42,31 @@ const DOMAPI = new DOM();
|
||||||
const ContextMenuAPI = new ContextMenu();
|
const ContextMenuAPI = new ContextMenu();
|
||||||
const DefaultLogger = new Logger();
|
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.
|
* `BdApi` is a globally (`window.BdApi`) accessible object for use by plugins and developers to make their lives easier.
|
||||||
* @name BdApi
|
* @name BdApi
|
||||||
|
@ -72,21 +100,7 @@ export default class BdApi {
|
||||||
get UI() {return UI;}
|
get UI() {return UI;}
|
||||||
get ReactUtils() {return ReactUtils;}
|
get ReactUtils() {return ReactUtils;}
|
||||||
get ContextMenu() {return ContextMenuAPI;}
|
get ContextMenu() {return ContextMenuAPI;}
|
||||||
Components = {
|
get Components() {return 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};
|
Net = {fetch};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,21 +171,7 @@ BdApi.ContextMenu = ContextMenuAPI;
|
||||||
* An set of react components plugins can make use of.
|
* An set of react components plugins can make use of.
|
||||||
* @type Components
|
* @type Components
|
||||||
*/
|
*/
|
||||||
BdApi.Components = {
|
BdApi.Components = 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;},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An instance of {@link Net} for using network related tools.
|
* An instance of {@link Net} for using network related tools.
|
||||||
|
|
|
@ -189,7 +189,7 @@ const UI = {
|
||||||
buildSettingsPanel({settings, onChange, onDrawerToggle, getDrawerState}) {
|
buildSettingsPanel({settings, onChange, onDrawerToggle, getDrawerState}) {
|
||||||
if (!settings?.length) throw new Error("No settings provided!");
|
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.id || !setting.type) throw new Error(`Setting item missing id or type`);
|
||||||
|
|
||||||
if (setting.type === "category") {
|
if (setting.type === "category") {
|
||||||
|
|
|
@ -4,19 +4,37 @@ import IPC from "@modules/ipc";
|
||||||
|
|
||||||
|
|
||||||
export default class ErrorBoundary extends React.Component {
|
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) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {hasError: false};
|
this.state = {hasError: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch(error) {
|
componentDidCatch(error) {
|
||||||
this.setState({hasError: true});
|
this.setState({hasError: true});
|
||||||
if (typeof this.props.onError === "function") this.props.onError(error);
|
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() {
|
render() {
|
||||||
if (this.state.hasError && !this.props.hideError) return <div onClick={() => IPC.openDevTools()} className="react-error">There was an unexpected Error. Click to open console for more details.</div>;
|
if (this.state.hasError && this.props.fallback) {
|
||||||
return this.props.children;
|
return this.props.fallback;
|
||||||
|
}
|
||||||
|
else if (this.state.hasError && !this.props.hideError) {
|
||||||
|
return <div onClick={() => IPC.openDevTools()} className="react-error">
|
||||||
|
There was an unexpected Error. Click to open console for more details.
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +42,6 @@ const originalRender = ErrorBoundary.prototype.render;
|
||||||
Object.defineProperty(ErrorBoundary.prototype, "render", {
|
Object.defineProperty(ErrorBoundary.prototype, "render", {
|
||||||
enumerable: false,
|
enumerable: false,
|
||||||
configurable: false,
|
configurable: false,
|
||||||
set: function() {Logger.warn("ErrorBoundary", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");},
|
set: function() {Logger.warn("ErrorBoundary", "Addon policy for plugins https://docs.betterdiscord.app/plugins/introduction/guidelines#scope");},
|
||||||
get: () => originalRender
|
get: () => originalRender
|
||||||
});
|
});
|
||||||
|
|
|
@ -199,7 +199,7 @@ export default class Modals {
|
||||||
onCloseCallback: () => {
|
onCloseCallback: () => {
|
||||||
if (props?.transitionState === 2) onClose?.();
|
if (props?.transitionState === 2) onClose?.();
|
||||||
}
|
}
|
||||||
}, props), React.createElement(ErrorBoundary, {}, content)));
|
}, props), React.createElement(ErrorBoundary, {id: "showConfirmationModal", name: "Modals"}, content)));
|
||||||
}, {modalKey: key});
|
}, {modalKey: key});
|
||||||
return modalKey;
|
return modalKey;
|
||||||
}
|
}
|
||||||
|
@ -214,13 +214,13 @@ export default class Modals {
|
||||||
themeErrors: Array.isArray(themeErrors) ? themeErrors : []
|
themeErrors: Array.isArray(themeErrors) ? themeErrors : []
|
||||||
};
|
};
|
||||||
this.openModal(props => {
|
this.openModal(props => {
|
||||||
return React.createElement(ErrorBoundary, null, React.createElement(AddonErrorModal, Object.assign(options, props)));
|
return React.createElement(ErrorBoundary, {id: "showAddonErrors", name: "Modals"}, React.createElement(AddonErrorModal, Object.assign(options, props)));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static showChangelogModal(options = {}) {
|
static showChangelogModal(options = {}) {
|
||||||
const key = this.openModal(props => {
|
const key = this.openModal(props => {
|
||||||
return React.createElement(ErrorBoundary, null, React.createElement(ChangelogModal, Object.assign(options, props)));
|
return React.createElement(ErrorBoundary, {id: "showChangelogModal", name: "Modals"}, React.createElement(ChangelogModal, Object.assign(options, props)));
|
||||||
});
|
});
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,7 @@ export default class Modals {
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.openModal(props => {
|
return this.openModal(props => {
|
||||||
return React.createElement(ErrorBoundary, null, React.createElement(ConfirmationModal, Object.assign(options, props), child));
|
return React.createElement(ErrorBoundary, {id: "showAddonSettingsModal", name: "Modals"}, React.createElement(ConfirmationModal, Object.assign(options, props), child));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ export default class Modals {
|
||||||
static makeStack() {
|
static makeStack() {
|
||||||
const div = DOMManager.parseHTML(`<div id="bd-modal-container">`);
|
const div = DOMManager.parseHTML(`<div id="bd-modal-container">`);
|
||||||
DOMManager.bdBody.append(div);
|
DOMManager.bdBody.append(div);
|
||||||
ReactDOM.render(<ErrorBoundary hideError={true}><ModalStack /></ErrorBoundary>, div);
|
ReactDOM.render(<ErrorBoundary id="makeStack" name="Modals" hideError={true}><ModalStack /></ErrorBoundary>, div);
|
||||||
this.hasInitialized = true;
|
this.hasInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -183,7 +183,9 @@ export default function AddonList({prefix, type, title, folder, addonList, addon
|
||||||
return sorted.map(addon => {
|
return sorted.map(addon => {
|
||||||
const hasSettings = addon.instance && typeof(addon.instance.getSettingsPanel) === "function";
|
const hasSettings = addon.instance && typeof(addon.instance.getSettingsPanel) === "function";
|
||||||
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
|
const getSettings = hasSettings && addon.instance.getSettingsPanel.bind(addon.instance);
|
||||||
return <ErrorBoundary><AddonCard disabled={addon.partial} type={type} prefix={prefix} editAddon={() => triggerEdit(addon.id)} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} /></ErrorBoundary>;
|
return <ErrorBoundary id={addon.id} name="AddonCard">
|
||||||
|
<AddonCard disabled={addon.partial} type={type} prefix={prefix} editAddon={() => triggerEdit(addon.id)} deleteAddon={() => triggerDelete(addon.id)} key={addon.id} enabled={addonState[addon.id]} addon={addon} onChange={onChange} reload={reload} hasSettings={hasSettings} getSettingsPanel={getSettings} />
|
||||||
|
</ErrorBoundary>;
|
||||||
});
|
});
|
||||||
}, [addonList, addonState, onChange, reload, triggerDelete, triggerEdit, type, prefix, sort, ascending, query, forced]); // eslint-disable-line react-hooks/exhaustive-deps
|
}, [addonList, addonState, onChange, reload, triggerDelete, triggerEdit, type, prefix, sort, ascending, query, forced]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue