Fix Modals, Toasts
* Make external modules be cached * Fix ConfirmationModal component being found wrong * Make default modal actually be standalone and be used for react crash fallback * Fixes toasts showing up in the crash screen
This commit is contained in:
parent
dd97539da3
commit
32bf2be211
|
@ -52,6 +52,8 @@ export default new class Core {
|
||||||
Logger.log("Startup", "Initializing Editor");
|
Logger.log("Startup", "Initializing Editor");
|
||||||
await Editor.initialize();
|
await Editor.initialize();
|
||||||
|
|
||||||
|
Modals.initialize();
|
||||||
|
|
||||||
Logger.log("Startup", "Initializing Builtins");
|
Logger.log("Startup", "Initializing Builtins");
|
||||||
for (const module in Builtins) {
|
for (const module in Builtins) {
|
||||||
Builtins[module].initialize();
|
Builtins[module].initialize();
|
||||||
|
@ -148,4 +150,4 @@ export default new class Core {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
.bd-modal-wrapper {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1000;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-backdrop {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: rgba(0,0,0, .6);
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 33%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal-inner {
|
||||||
|
background: var(--background-primary);
|
||||||
|
border-radius: 4px;
|
||||||
|
overflow: hidden;
|
||||||
|
animation: bd-modal-open ease-out;
|
||||||
|
animation-duration: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal-wrapper.closing .bd-modal-inner {
|
||||||
|
animation: bd-modal-close ease-in;
|
||||||
|
animation-duration: 300ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal .footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
padding: 15px;
|
||||||
|
background: var(--background-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal-body {
|
||||||
|
padding: 20px 15px;
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal .header {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal .title {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal-body {
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bd-modal .footer .bd-button {
|
||||||
|
min-width: 80px;
|
||||||
|
height: 38px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bd-modal-close {
|
||||||
|
to {transform: scale(0.7);}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes bd-modal-open {
|
||||||
|
from {transform: scale(0.7);}
|
||||||
|
}
|
|
@ -1,26 +1,27 @@
|
||||||
import Logger from "common/logger";
|
import Logger from "common/logger";
|
||||||
import {React, IPC} from "modules";
|
import {React, IPC} from "modules";
|
||||||
|
|
||||||
export default class ErrorBoundary extends React.Component {
|
export default class ErrorBoundary extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {hasError: false};
|
this.state = {hasError: false};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidCatch() {
|
componentDidCatch(error) {
|
||||||
this.setState({hasError: true});
|
this.setState({hasError: true});
|
||||||
}
|
if (typeof this.props.onError === "function") this.props.onError(error);
|
||||||
|
}
|
||||||
render() {
|
|
||||||
if (this.state.hasError) return <div onClick={() => IPC.openDevTools()} className="react-error">There was an unexpected Error. Click to open console for more details.</div>;
|
render() {
|
||||||
return this.props.children;
|
if (this.state.hasError) 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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
const originalRender = ErrorBoundary.prototype.render;
|
|
||||||
Object.defineProperty(ErrorBoundary.prototype, "render", {
|
const originalRender = ErrorBoundary.prototype.render;
|
||||||
enumerable: false,
|
Object.defineProperty(ErrorBoundary.prototype, "render", {
|
||||||
configurable: false,
|
enumerable: false,
|
||||||
set: function() {Logger.warn("ErrorBoundary", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");},
|
configurable: false,
|
||||||
get: () => originalRender
|
set: function() {Logger.warn("ErrorBoundary", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");},
|
||||||
});
|
get: () => originalRender
|
||||||
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import {Config} from "data";
|
import {Config} from "data";
|
||||||
import Logger from "common/logger";
|
import Logger from "common/logger";
|
||||||
import {WebpackModules, React, Settings, Strings, DOMManager, DiscordModules} from "modules";
|
import {WebpackModules, React, ReactDOM, Settings, Strings, DOMManager, DiscordModules} from "modules";
|
||||||
import FormattableString from "../structs/string";
|
import FormattableString from "../structs/string";
|
||||||
import AddonErrorModal from "./addonerrormodal";
|
import AddonErrorModal from "./addonerrormodal";
|
||||||
import ErrorBoundary from "./errorboundary";
|
import ErrorBoundary from "./errorboundary";
|
||||||
|
@ -11,23 +11,38 @@ export default class Modals {
|
||||||
static get shouldShowAddonErrors() {return Settings.get("settings", "addons", "addonErrors");}
|
static get shouldShowAddonErrors() {return Settings.get("settings", "addons", "addonErrors");}
|
||||||
|
|
||||||
static get ModalActions() {
|
static get ModalActions() {
|
||||||
return {
|
return this._ModalActions ??= {
|
||||||
openModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback") && m?.toString().includes("Layer")),
|
openModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback") && m?.toString().includes("Layer")),
|
||||||
closeModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback()"))
|
closeModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback()"))
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
static get ModalStack() {return WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
|
static get ModalStack() {return this._ModalStack ??= WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
|
||||||
static get ModalComponents() {return WebpackModules.getByProps("Header", "Footer");}
|
static get ModalComponents() {return this._ModalComponents ??= WebpackModules.getByProps("Header", "Footer");}
|
||||||
static get ModalRoot() {return WebpackModules.getModule(m => m?.toString().includes("ENTERING"));}
|
static get ModalRoot() {return this._ModalRoot ??= WebpackModules.getModule(m => m?.toString().includes("ENTERING"));}
|
||||||
static get ModalClasses() {return WebpackModules.getByProps("modal", "content");}
|
static get ModalClasses() {return this._ModalClasses ??= WebpackModules.getByProps("modal", "content");}
|
||||||
static get FlexElements() {return WebpackModules.getByProps("Child", "Align");}
|
static get FlexElements() {return this._FlexElements ??= WebpackModules.getByProps("Child", "Align");}
|
||||||
static get FormTitle() {return WebpackModules.getByProps("Tags", "Sizes");}
|
static get FormTitle() {return this._FormTitle ??= WebpackModules.getByProps("Tags", "Sizes");}
|
||||||
static get TextElement() {return WebpackModules.getModule(m => m?.Sizes?.SIZE_32 && m.Colors);}
|
static get TextElement() {return this._TextElement ??= WebpackModules.getModule(m => m?.Sizes?.SIZE_32 && m.Colors);}
|
||||||
static get ConfirmationModal() {return WebpackModules.getModule(m => m?.toString()?.includes("confirmText"));}
|
static get ConfirmationModal() {return this._ConfirmationModal ??= WebpackModules.getModule(m => m?.toString()?.includes(".confirmButtonColor"));}
|
||||||
static get Markdown() {return WebpackModules.find(m => m?.prototype?.render && m.rules);}
|
static get Markdown() {return this._Markdown ??= WebpackModules.find(m => m?.prototype?.render && m.rules);}
|
||||||
static get Buttons() {return WebpackModules.getByProps("BorderColors");}
|
static get Buttons() {return this._Buttons ??= WebpackModules.getByProps("BorderColors");}
|
||||||
|
static get ModalQueue() {return this._ModalQueue ??= [];}
|
||||||
|
|
||||||
|
static get hasModalOpen() {return !!document.getElementsByClassName("bd-modal").length;}
|
||||||
|
|
||||||
static default(title, content) {
|
static async initialize() {
|
||||||
|
const names = ["ModalActions", "Markdown", "ModalRoot", "ModalComponents", "Buttons", "TextElement", "FlexElements"];
|
||||||
|
|
||||||
|
for (const name of names) {
|
||||||
|
const value = this[name];
|
||||||
|
|
||||||
|
if (!value) {
|
||||||
|
Logger.warn("Modals", `Missing ${name} module!`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static default(title, content, buttons = []) {
|
||||||
const modal = DOMManager.parseHTML(`<div class="bd-modal-wrapper theme-dark">
|
const modal = DOMManager.parseHTML(`<div class="bd-modal-wrapper theme-dark">
|
||||||
<div class="bd-backdrop backdrop-1wrmKB"></div>
|
<div class="bd-backdrop backdrop-1wrmKB"></div>
|
||||||
<div class="bd-modal modal-1UGdnR">
|
<div class="bd-modal modal-1UGdnR">
|
||||||
|
@ -37,26 +52,77 @@ export default class Modals {
|
||||||
</div>
|
</div>
|
||||||
<div class="bd-modal-body">
|
<div class="bd-modal-body">
|
||||||
<div class="scroller-wrap fade">
|
<div class="scroller-wrap fade">
|
||||||
<div class="scroller">
|
<div class="scroller"></div>
|
||||||
${content}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp">
|
<div class="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp"></div>
|
||||||
<button type="button" class="bd-button">${Strings.Modals.okay}</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>`);
|
</div>`);
|
||||||
modal.querySelector(".footer button").addEventListener("click", () => {
|
|
||||||
|
const handleClose = () => {
|
||||||
modal.classList.add("closing");
|
modal.classList.add("closing");
|
||||||
setTimeout(() => {modal.remove();}, 300);
|
setTimeout(() => {
|
||||||
});
|
modal.remove();
|
||||||
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
|
|
||||||
modal.classList.add("closing");
|
const next = this.ModalQueue.shift();
|
||||||
setTimeout(() => {modal.remove();}, 300);
|
if (!next) return;
|
||||||
});
|
|
||||||
document.querySelector("#app-mount").append(modal);
|
next();
|
||||||
|
}, 300);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!buttons.length) {
|
||||||
|
buttons.push({
|
||||||
|
label: Strings.Modals.okay,
|
||||||
|
action: handleClose
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonContainer = modal.querySelector(".footer");
|
||||||
|
for (const button of buttons) {
|
||||||
|
const buttonEl = Object.assign(document.createElement("button"), {
|
||||||
|
onclick: (e) => {
|
||||||
|
try {button.action(e);} catch (error) {console.error(error);}
|
||||||
|
|
||||||
|
handleClose();
|
||||||
|
},
|
||||||
|
type: "button",
|
||||||
|
className: "bd-button"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (button.danger) buttonEl.classList.add("bd-button-danger")
|
||||||
|
|
||||||
|
buttonEl.append(button.label);
|
||||||
|
buttonContainer.appendChild(buttonEl);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(content) ? content.every(el => React.isValidElement(el)) : React.isValidElement(content)) {
|
||||||
|
const container = modal.querySelector(".scroller");
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReactDOM.render(content, container);
|
||||||
|
} catch (error) {
|
||||||
|
container.append(DOMManager.parseHTML(`<span style="color: red">There was an unexpected error. Modal could not be rendered.</span>`));
|
||||||
|
}
|
||||||
|
|
||||||
|
DOMManager.onRemoved(container, () => {
|
||||||
|
ReactDOM.unmountComponentAtNode(container);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
modal.querySelector(".scroller").append(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
modal.querySelector(".footer button").addEventListener("click", handleClose);
|
||||||
|
modal.querySelector(".bd-backdrop").addEventListener("click", handleClose);
|
||||||
|
|
||||||
|
const handleOpen = () => document.getElementById("app-mount").append(modal);
|
||||||
|
|
||||||
|
if (this.hasModalOpen) {
|
||||||
|
this.ModalQueue.push(handleOpen);
|
||||||
|
} else {
|
||||||
|
handleOpen();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static alert(title, content) {
|
static alert(title, content) {
|
||||||
|
@ -80,25 +146,41 @@ export default class Modals {
|
||||||
const Markdown = this.Markdown;
|
const Markdown = this.Markdown;
|
||||||
const ConfirmationModal = this.ConfirmationModal;
|
const ConfirmationModal = this.ConfirmationModal;
|
||||||
const ModalActions = this.ModalActions;
|
const ModalActions = this.ModalActions;
|
||||||
|
|
||||||
if (content instanceof FormattableString) content = content.toString();
|
if (content instanceof FormattableString) content = content.toString();
|
||||||
if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) return this.default(title, content);
|
|
||||||
|
|
||||||
const emptyFunction = () => {};
|
const emptyFunction = () => {};
|
||||||
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options;
|
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options;
|
||||||
|
|
||||||
|
if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) return this.default(title, content, [
|
||||||
|
confirmText && {label: confirmText, action: onConfirm},
|
||||||
|
cancelText && {label: cancelText, action: onCancel, danger}
|
||||||
|
].filter(Boolean));
|
||||||
|
|
||||||
if (!Array.isArray(content)) content = [content];
|
if (!Array.isArray(content)) content = [content];
|
||||||
content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c);
|
content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c);
|
||||||
|
|
||||||
return ModalActions.openModal(props => {
|
let modalKey = ModalActions.openModal(props => {
|
||||||
return React.createElement(ConfirmationModal, Object.assign({
|
return React.createElement(ErrorBoundary, {
|
||||||
|
onError: () => {
|
||||||
|
setTimeout(() => {
|
||||||
|
ModalActions.closeModal(modalKey);
|
||||||
|
this.default(title, content, [
|
||||||
|
confirmText && {label: confirmText, action: onConfirm},
|
||||||
|
cancelText && {label: cancelText, action: onCancel, danger}
|
||||||
|
].filter(Boolean));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, React.createElement(ConfirmationModal, Object.assign({
|
||||||
header: title,
|
header: title,
|
||||||
confirmButtonColor: danger ? this.Buttons.Colors.RED : this.Buttons.Colors.BRAND,
|
confirmButtonColor: danger ? this.Buttons.Colors.RED : this.Buttons.Colors.BRAND,
|
||||||
confirmText: confirmText,
|
confirmText: confirmText,
|
||||||
cancelText: cancelText,
|
cancelText: cancelText,
|
||||||
onConfirm: onConfirm,
|
onConfirm: onConfirm,
|
||||||
onCancel: onCancel
|
onCancel: onCancel
|
||||||
}, props), content);
|
}, props), React.createElement(ErrorBoundary, {}, content)));
|
||||||
}, {modalKey: key});
|
}, {modalKey: key});
|
||||||
|
return modalKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
|
static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
|
||||||
|
@ -110,7 +192,7 @@ export default class Modals {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addonErrorsRef = React.createRef();
|
this.addonErrorsRef = React.createRef();
|
||||||
this.ModalActions.openModal(props => React.createElement(this.ModalRoot, Object.assign(props, {
|
this.ModalActions.openModal(props => React.createElement(ErrorBoundary, null, React.createElement(this.ModalRoot, Object.assign(props, {
|
||||||
size: "medium",
|
size: "medium",
|
||||||
className: "bd-error-modal",
|
className: "bd-error-modal",
|
||||||
children: [
|
children: [
|
||||||
|
@ -127,7 +209,7 @@ export default class Modals {
|
||||||
className: "bd-button"
|
className: "bd-button"
|
||||||
}, Strings.Modals.okay))
|
}, Strings.Modals.okay))
|
||||||
]
|
]
|
||||||
})));
|
}))));
|
||||||
}
|
}
|
||||||
|
|
||||||
static showChangelogModal(options = {}) {
|
static showChangelogModal(options = {}) {
|
||||||
|
@ -179,14 +261,14 @@ export default class Modals {
|
||||||
const originalRoot = OriginalModalClasses.root;
|
const originalRoot = OriginalModalClasses.root;
|
||||||
if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`;
|
if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`;
|
||||||
const key = ModalActions.openModal(props => {
|
const key = ModalActions.openModal(props => {
|
||||||
return React.createElement(Changelog, Object.assign({
|
return React.createElement(ErrorBoundary, null, React.createElement(Changelog, Object.assign({
|
||||||
className: `bd-changelog ${ChangelogClasses.container}`,
|
className: `bd-changelog ${ChangelogClasses.container}`,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
onScroll: _ => _,
|
onScroll: _ => _,
|
||||||
onClose: _ => _,
|
onClose: _ => _,
|
||||||
renderHeader: renderHeader,
|
renderHeader: renderHeader,
|
||||||
renderFooter: renderFooter,
|
renderFooter: renderFooter,
|
||||||
}, props), changelogItems);
|
}, props), changelogItems));
|
||||||
});
|
});
|
||||||
|
|
||||||
const closeModal = ModalActions.closeModal;
|
const closeModal = ModalActions.closeModal;
|
||||||
|
@ -241,7 +323,7 @@ export default class Modals {
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.ModalActions.openModal(props => {
|
return this.ModalActions.openModal(props => {
|
||||||
return React.createElement(modal, props);
|
return React.createElement(ErrorBoundary, null, React.createElement(modal, props));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,7 @@ export default class Toasts {
|
||||||
const form = container ? container.querySelector("form") : null;
|
const form = container ? container.querySelector("form") : null;
|
||||||
const left = container ? container.getBoundingClientRect().left : 310;
|
const left = container ? container.getBoundingClientRect().left : 310;
|
||||||
const right = memberlist ? memberlist.getBoundingClientRect().left : 0;
|
const right = memberlist ? memberlist.getBoundingClientRect().left : 0;
|
||||||
const width = right ? right - container.getBoundingClientRect().left : container.offsetWidth;
|
const width = right ? right - container.getBoundingClientRect().left : (container?.offsetWidth ?? document.body.offsetWidth / 2);
|
||||||
const bottom = form ? form.offsetHeight : 80;
|
const bottom = form ? form.offsetHeight : 80;
|
||||||
const toastWrapper = document.createElement("div");
|
const toastWrapper = document.createElement("div");
|
||||||
toastWrapper.classList.add("bd-toasts");
|
toastWrapper.classList.add("bd-toasts");
|
||||||
|
@ -73,4 +73,4 @@ export default class Toasts {
|
||||||
toastWrapper.style.setProperty("bottom", bottom + "px");
|
toastWrapper.style.setProperty("bottom", bottom + "px");
|
||||||
DOMManager.bdBody.appendChild(toastWrapper);
|
DOMManager.bdBody.appendChild(toastWrapper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue