Revamp error modal

This commit is contained in:
Zack Rauen 2021-04-06 14:09:43 -04:00
parent aeeb95d84b
commit e1a510ab4c
10 changed files with 251 additions and 118 deletions

View File

@ -201,7 +201,7 @@ export default class AddonManager {
const addon = __non_webpack_require__(path.resolve(this.addonFolder, filename));
}
catch (error) {
return new AddonError(filename, filename, Strings.Addons.compileError, {message: error.message, stack: error.stack});
return new AddonError(filename, filename, Strings.Addons.compileError, {message: error.message, stack: error.stack}, this.prefix);
}
const addon = __non_webpack_require__(path.resolve(this.addonFolder, filename));
@ -209,7 +209,7 @@ export default class AddonManager {
// await Promise.resolve(addon);
// addon = __non_webpack_require__(path.resolve(this.addonFolder, filename));
// console.log(addon);
if (this.addonList.find(c => c.id == addon.id)) return new AddonError(addon.name, filename, Strings.Addons.alreadyExists.format({type: this.prefix, name: addon.name}));
if (this.addonList.find(c => c.id == addon.id)) return new AddonError(addon.name, filename, Strings.Addons.alreadyExists.format({type: this.prefix, name: addon.name}), this.prefix);
const error = this.initializeAddon(addon);
if (error) return error;

View File

@ -153,7 +153,7 @@ export default new class Core {
catch (err) {
Logger.stacktrace("Updater", "Failed to update", err);
Modals.showConfirmationModal("Update Failed", "BetterDiscord failed to update. Please download the latest version of the installer from GitHub (https://github.com/BetterDiscord/Installer/releases/latest) and reinstall.", {
cancelText: ""
cancelText: null
});
}
}

View File

@ -66,9 +66,10 @@ export default new class PluginManager extends AddonManager {
unloadPlugin(idOrFileOrAddon) {return this.unloadAddon(idOrFileOrAddon);}
loadPlugin(filename) {return this.loadAddon(filename);}
loadAddon(filename) {
loadAddon(filename, shouldCTE = true) {
const error = super.loadAddon(filename);
if (error) Modals.showAddonErrors({plugins: [error]});
if (error && shouldCTE) Modals.showAddonErrors({plugins: [error]});
return error;
}
reloadPlugin(idOrFileOrAddon) {
@ -79,7 +80,7 @@ export default new class PluginManager extends AddonManager {
/* Overrides */
initializeAddon(addon) {
if (!addon.exports) return new AddonError(addon.name, addon.filename, "Plugin had no exports", {message: "Plugin had no exports or no name property.", stack: ""});
if (!addon.exports) return new AddonError(addon.name, addon.filename, "Plugin had no exports", {message: "Plugin had no exports or no name property.", stack: ""}, this.prefix);
try {
const PluginClass = addon.exports;
const thePlugin = new PluginClass();
@ -93,10 +94,10 @@ export default new class PluginManager extends AddonManager {
}
catch (error) {
this.state[addon.id] = false;
return new AddonError(addon.name, addon.filename, "load() could not be fired.", {message: error.message, stack: error.stack});
return new AddonError(addon.name, addon.filename, "load() could not be fired.", {message: error.message, stack: error.stack}, this.prefix);
}
}
catch (error) {return new AddonError(addon.name, addon.filename, "Could not be constructed.", {message: error.message, stack: error.stack});}
catch (error) {return new AddonError(addon.name, addon.filename, "Could not be constructed.", {message: error.message, stack: error.stack}, this.prefix);}
}
getFileModification(module, fileContent, meta) {
@ -138,7 +139,7 @@ export default new class PluginManager extends AddonManager {
this.state[addon.id] = false;
Toasts.error(Strings.Addons.couldNotStart.format({name: addon.name, version: addon.version}));
Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be started.`, err);
return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "start()"}), {message: err.message, stack: err.stack});
return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "start()"}), {message: err.message, stack: err.stack}, this.prefix);
}
this.emit("started", addon.id);
Toasts.show(Strings.Addons.enabled.format({name: addon.name, version: addon.version}));
@ -155,7 +156,7 @@ export default new class PluginManager extends AddonManager {
this.state[addon.id] = false;
Toasts.error(Strings.Addons.couldNotStop.format({name: addon.name, version: addon.version}));
Logger.stacktrace(this.name, `${addon.name} v${addon.version} could not be started.`, err);
return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "stop()"}), {message: err.message, stack: err.stack});
return new AddonError(addon.name, addon.filename, Strings.Addons.enabled.format({method: "stop()"}), {message: err.message, stack: err.stack}, this.prefix);
}
this.emit("stopped", addon.id);
Toasts.show(Strings.Addons.disabled.format({name: addon.name, version: addon.version}));

View File

@ -47,9 +47,10 @@ export default new class ThemeManager extends AddonManager {
loadTheme(filename) {return this.loadAddon(filename);}
reloadTheme(idOrFileOrAddon) {return this.reloadAddon(idOrFileOrAddon);}
loadAddon(filename) {
loadAddon(filename, shouldCTE = true) {
const error = super.loadAddon(filename);
if (error) Modals.showAddonErrors({themes: [error]});
if (error && shouldCTE) Modals.showAddonErrors({themes: [error]});
return error;
}
/* Overrides */

View File

@ -1,8 +1,9 @@
export default class AddonError extends Error {
constructor(name, filename, message, error) {
constructor(name, filename, message, error, type) {
super(message);
this.name = name;
this.file = filename;
this.error = error;
this.type = type;
}
}

View File

@ -90,6 +90,10 @@ export default class BuiltinModule {
return Patcher.before(this.name, object, func, callback);
}
instead(object, func, callback) {
return Patcher.instead(this.name, object, func, callback);
}
after(object, func, callback) {
return Patcher.after(this.name, object, func, callback);
}

View File

@ -0,0 +1,44 @@
.be-modal .tab-bar.TOP .tab-bar-item:nth-child(1) {
margin-left: 20px;
}
.bd-addon-error {
margin: 10px;
border-radius: 4px;
overflow: hidden;
}
.bd-addon-error-header {
display: flex;
padding: 10px;
background: rgba(0, 0, 0, 0.2);
align-items: center;
}
.bd-addon-error-message {
font-weight: 700;
}
.bd-addon-error-body {
padding: 10px;
background: var(--background-mobile-secondary);
}
.bd-addon-error-header svg {
margin-right: 7px;
}
.bd-addon-error-stack-header svg {
float: right;
transform: rotate(90deg);
transition: 0.4s;
color: #fff;
}
.bd-addon-error-stack.opened svg {
transform: rotate(0deg);
}
.bd-addon-error-stack-header {
color: #b9bbbe;
}

View File

@ -32,6 +32,14 @@
transform: translateZ(0);
}
.bd-modal {
border-radius: 3px;
overflow: hidden;
display: flex;
flex-direction: column;
flex: 1;
}
.bd-modal-wrapper.closing .bd-backdrop {
animation: bd-backdrop-closing 200ms linear;
animation-fill-mode: forwards;
@ -66,7 +74,7 @@
transform: scale(1);
}
.bd-modal-wrapper .bd-modal-inner {
/* .bd-modal .bd-modal-inner {
display: flex;
contain: layout;
flex-direction: column;
@ -79,6 +87,14 @@
min-height: 200px;
width: 440px;
user-select: text;
} */
.bd-modal .bd-modal-inner {
display: flex;
flex-direction: column;
flex: 1;
max-height: 660px;
overflow-y: auto;
}
.bd-modal-wrapper .bd-content-modal .bd-modal-inner {
@ -86,7 +102,7 @@
width: 700px;
}
.bd-modal-wrapper .header {
.bd-modal .header {
background-color: #35393e;
box-shadow: 0 2px 3px 0 rgba(0, 0, 0, 0.2);
padding: 12px 20px;
@ -97,7 +113,7 @@
line-height: 19px;
}
.bd-modal-wrapper .bd-modal-body {
.bd-modal .bd-modal-body {
background-color: #36393f;
color: #fff;
overflow: hidden;
@ -108,22 +124,22 @@
position: relative;
}
.bd-modal-wrapper .scroller {
.bd-modal .scroller {
padding: 10px;
overflow-y: auto;
}
.bd-modal-wrapper .bd-content-modal .bd-modal-body {
.bd-modal .bd-content-modal .bd-modal-body {
padding: 0;
}
.bd-modal-wrapper .footer {
.bd-modal .footer {
display: flex;
justify-content: flex-end;
padding: 10px 20px;
}
.bd-modal-wrapper .footer button {
.bd-modal .footer button {
min-height: 32px;
min-width: 60px;
align-items: center;
@ -136,7 +152,7 @@
user-select: none;
}
.bd-modal-wrapper .tab-bar-container {
.bd-modal .tab-bar-container {
align-items: center;
border-bottom: 0;
background: rgba(0, 0, 0, 0.2);
@ -147,7 +163,7 @@
margin-bottom: 15px;
}
.bd-modal-wrapper .tab-bar.TOP {
.bd-modal .tab-bar.TOP {
margin: 0;
border: 0;
display: flex;
@ -156,30 +172,30 @@
align-items: center;
}
.bd-modal-wrapper .tab-bar-container .tab-bar-item {
margin: 0 15px;
padding: 15px 0;
color: #fff;
opacity: 0.5;
transition: opacity 200ms ease;
border-bottom: 2px solid transparent;
}
.bd-modal-wrapper .tab-bar-container .tab-bar-item:hover {
border-color: #fff;
.bd-modal .tab-bar-container .tab-bar-item {
margin: 10px;
padding: 7px 10px;
border-radius: 5px;
opacity: 0.7;
cursor: pointer;
}
.bd-modal-wrapper .tab-bar-container .tab-bar-item.selected {
.bd-modal .tab-bar-item:not(.selected):hover {
background: var(--background-primary);
}
.bd-modal .tab-bar.TOP .tab-bar-item:nth-child(1) {
margin-left: 20px;
}
.bd-modal .tab-bar-container .tab-bar-item.selected {
opacity: 1;
border-color: #fff;
background: #36393f;
border-radius: 5px;
font-weight: 600;
}
.bd-modal-wrapper .tab-bar.TOP .tab-bar-item + .tab-bar-item {
margin: 0;
}
.bd-modal-wrapper .table-header {
.bd-modal .table-header {
display: flex;
justify-content: space-between;
color: #fff;
@ -190,23 +206,23 @@
font-size: 14px;
}
.bd-modal-wrapper .table-column {
.bd-modal .table-column {
width: 25%;
word-wrap: break-word;
}
.bd-modal-wrapper .table-column.column-error {
.bd-modal .table-column.column-error {
width: 50%;
}
.bd-modal-wrapper .errors {
.bd-modal .errors {
display: flex;
flex-direction: column;
font-size: 14px;
padding: 0 5px;
}
.bd-modal-wrapper .error {
.bd-modal .error {
display: flex;
color: #fff;
border-bottom: 1px solid rgba(255, 255, 255, 0.25);
@ -214,12 +230,12 @@
align-items: center;
}
.bd-modal-wrapper .error-link {
.bd-modal .error-link {
color: #3e82e5;
font-weight: 500;
}
.bd-modal-wrapper .bd-content-modal .scroller {
.bd-modal .bd-content-modal .scroller {
padding-top: 0;
}

View File

@ -0,0 +1,122 @@
import {React, Strings, WebpackModules} from "modules";
import DownArrow from "./icons/downarrow";
import Extension from "./icons/extension";
import ThemeIcon from "./icons/theme";
const Parser = Object(WebpackModules.getByProps("defaultRules", "parse")).defaultRules;
const joinClassNames = (...classNames) => classNames.filter(e => e).join(" ");
class Collapse extends React.Component {
constructor(props) {
super(props);
this.state = {opened: false};
}
toggle() {
if (!this.props.error.stack) return;
this.setState({opened: !this.state.opened});
}
render() {
const title = this.props.error.error ? this.props.error.message : this.props.error.message;
const stack = this.props.error.error && this.props.error.error.stack;
return <div className={joinClassNames("bd-addon-error-stack", this.state.opened && "opened")}>
<div onClick={() => {this.toggle();}} className="bd-addon-error-stack-header">
{!this.state.opened && title}
{stack
? <>
<DownArrow />
{this.state.opened && <div className="bd-addon-error-stack-shown">{Parser ? Parser.codeBlock.react({content: stack, lang: "js"}, null, {}) : stack}</div>}
</>
: null}
</div>
</div>;
}
}
export default class AddonErrorModal extends React.Component {
constructor(props) {
super(props);
const tabs = this.getTabs();
this.state = {
selectedTab: tabs[0].id
};
}
mergeErrors(errors1 = [], errors2 = []) {
const list = [];
const allErrors = [...errors2, ...errors1];
for (const error of allErrors) {
if (list.find(e => e.file === error.file)) continue;
list.push(error);
}
return list;
}
refreshTabs(pluginErrors, themeErrors) {
this._tabs = null;
this.props.pluginErrors = this.mergeErrors(this.props.pluginErrors, pluginErrors);
this.props.themeErrors = this.mergeErrors(this.props.themeErrors, themeErrors);
this.forceUpdate();
}
generateTab(id, errors) {
return {
id: id,
name: Strings.Panels[id],
errors: errors
};
}
getTabs() {
return this._tabs || (this._tabs = [
this.props.pluginErrors.length && this.generateTab("plugins", this.props.pluginErrors),
this.props.themeErrors.length && this.generateTab("themes", this.props.themeErrors)
].filter(e => e));
}
renderError(err) {
return <div className="bd-addon-error">
<div className="bd-addon-error-header">
{err.type == "plugin" ? <Extension /> : <ThemeIcon />}
<div className="bd-addon-error-message">{err.name} - {err.message}</div>
</div>
<div className="bd-addon-error-body">
<Collapse error={err} message={err.message} />
</div>
</div>;
}
switchToTab(id) {
this.setState({selectedTab: id});
}
render() {
const selectedTab = this.getTabs().find(e => this.state.selectedTab === e.id);
const tabs = this.getTabs();
return <div className="bd-modal bd-content-modal modal-1UGdnR">
<div className="bd-modal-inner inner-1JeGVc">
<div className="header header-1R_AjF"><div className="title">{Strings.Modals.addonErrors}</div></div>
<div className="bd-modal-body">
<div className="tab-bar-container">
<div className="tab-bar TOP">
{tabs.map(tab => <div onClick={() => {this.switchToTab(tab.id);}} className={joinClassNames("tab-bar-item", tab.id === selectedTab.id && "selected")}>{tab.name}</div>)}
</div>
</div>
<div className="scroller-wrap fade">
<div className="scroller">
{selectedTab.errors.map(error => this.renderError(error))}
</div>
</div>
</div>
<div className="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp">
<button type="button" onClick={() => {this.props.onClose();}} className="bd-button">{Strings.Modals.okay}</button>
</div>
</div>
</div>;
}
}

View File

@ -2,6 +2,7 @@ import {Config} from "data";
import Logger from "common/logger";
import {WebpackModules, React, Settings, Strings, DOM, DiscordModules} from "modules";
import FormattableString from "../structs/string";
import AddonErrorModal from "./addonerrormodal";
import ErrorBoundary from "./errorboundary";
export default class Modals {
@ -53,7 +54,7 @@ export default class Modals {
}
static alert(title, content) {
this.showConfirmationModal(title, content, {cancelText: ""});
this.showConfirmationModal(title, content, {cancelText: null});
}
/**
@ -97,78 +98,21 @@ export default class Modals {
static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
if (!pluginErrors || !themeErrors || !this.shouldShowAddonErrors) return;
if (!pluginErrors.length && !themeErrors.length) return;
const modal = DOM.createElement(`<div class="bd-modal-wrapper theme-dark">
<div class="bd-backdrop backdrop-1wrmKB"></div>
<div class="bd-modal bd-content-modal modal-1UGdnR">
<div class="bd-modal-inner inner-1JeGVc">
<div class="header header-1R_AjF"><div class="title">${Strings.Modals.addonErrors}</div></div>
<div class="bd-modal-body">
<div class="tab-bar-container">
<div class="tab-bar TOP">
<div class="tab-bar-item">${Strings.Panels.plugins}</div>
<div class="tab-bar-item">${Strings.Panels.themes}</div>
</div>
</div>
<div class="table-header">
<div class="table-column column-name">${Strings.Modals.name}</div>
<div class="table-column column-message">${Strings.Modals.message}</div>
<div class="table-column column-error">${Strings.Modals.error}</div>
</div>
<div class="scroller-wrap fade">
<div class="scroller">
if (this.addonErrorsRef && this.addonErrorsRef.current) {
return this.addonErrorsRef.current.refreshTabs(Array.isArray(pluginErrors) ? pluginErrors : [], Array.isArray(themeErrors) ? themeErrors : []);
}
</div>
</div>
</div>
<div class="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp">
<button type="button" class="bd-button">${Strings.Modals.okay}</button>
</div>
</div>
</div>
</div>`);
const generateTab = function(errors) {
const container = DOM.createElement(`<div class="errors">`);
for (const err of errors) {
const error = DOM.createElement(`<div class="error">
<div class="table-column column-name">${err.name ? err.name : err.file}</div>
<div class="table-column column-message">${err.message}</div>
<div class="table-column column-error"><a class="error-link" href="">${err.error ? err.error.message : ""}</a></div>
</div>`);
container.append(error);
if (err.error) {
error.querySelectorAll("a").forEach(el => el.addEventListener("click", (e) => {
e.preventDefault();
Logger.stacktrace("AddonError", `Error details for ${err.name ? err.name : err.file}.`, err.error);
}));
}
}
return container;
};
const tabs = [generateTab(pluginErrors), generateTab(themeErrors)];
modal.querySelectorAll(".tab-bar-item").forEach(el => el.addEventListener("click", (e) => {
e.preventDefault();
const selected = modal.querySelector(".tab-bar-item.selected");
if (selected) DOM.removeClass(selected, "selected");
DOM.addClass(e.target, "selected");
const scroller = modal.querySelector(".scroller");
scroller.innerHTML = "";
scroller.append(tabs[DOM.index(e.target)]);
}));
modal.querySelector(".footer button").addEventListener("click", () => {
DOM.addClass(modal, "closing");
setTimeout(() => {modal.remove();}, 300);
});
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
DOM.addClass(modal, "closing");
setTimeout(() => {modal.remove();}, 300);
});
DOM.query("#app-mount").append(modal);
if (pluginErrors.length) modal.querySelector(".tab-bar-item").click();
else modal.querySelectorAll(".tab-bar-item")[1].click();
this.addonErrorsRef = React.createRef();
this.ModalActions.openModal(props => React.createElement(this.ModalComponents.ModalRoot, Object.assign(props, {
size: "medium",
children: React.createElement(AddonErrorModal, {
ref: this.addonErrorsRef,
pluginErrors: Array.isArray(pluginErrors) ? pluginErrors : [],
themeErrors: Array.isArray(themeErrors) ? themeErrors : [],
onClose: props.onClose
})
})));
}
static showChangelogModal(options = {}) {