2023-05-20 00:37:21 +02:00
|
|
|
import Config from "@data/config";
|
|
|
|
|
|
|
|
import FormattableString from "@structs/string";
|
|
|
|
|
2023-05-19 22:38:45 +02:00
|
|
|
import Logger from "@common/logger";
|
2023-05-19 23:14:55 +02:00
|
|
|
import React from "@modules/react";
|
|
|
|
import ReactDOM from "@modules/reactdom";
|
|
|
|
import Strings from "@modules/strings";
|
|
|
|
import Settings from "@modules/settingsmanager";
|
2023-06-15 04:17:10 +02:00
|
|
|
import Events from "@modules/emitter";
|
2023-08-29 05:47:20 +02:00
|
|
|
// import DiscordModules from "@modules/discordmodules";
|
2023-05-19 23:14:55 +02:00
|
|
|
import WebpackModules from "@modules/webpackmodules";
|
|
|
|
import DOMManager from "@modules/dommanager";
|
2023-05-20 00:37:21 +02:00
|
|
|
|
2023-03-31 21:53:36 +02:00
|
|
|
import AddonErrorModal from "./modals/addonerrormodal";
|
2020-11-03 02:47:08 +01:00
|
|
|
import ErrorBoundary from "./errorboundary";
|
2023-03-29 06:13:11 +02:00
|
|
|
import TextElement from "./base/text";
|
|
|
|
import ModalRoot from "./modals/root";
|
2023-08-29 05:47:20 +02:00
|
|
|
// import ModalHeader from "./modals/header";
|
|
|
|
// import ModalContent from "./modals/content";
|
|
|
|
// import ModalFooter from "./modals/footer";
|
2023-03-29 06:13:11 +02:00
|
|
|
|
|
|
|
import ConfirmationModal from "./modals/confirmation";
|
2023-08-29 05:47:20 +02:00
|
|
|
// import Button from "./base/button";
|
2023-03-29 06:13:11 +02:00
|
|
|
import CustomMarkdown from "./base/markdown";
|
2023-03-31 08:06:49 +02:00
|
|
|
import ChangelogModal from "./modals/changelog";
|
2023-04-12 00:30:36 +02:00
|
|
|
import ModalStack, {generateKey} from "./modals/stack";
|
2019-05-31 07:53:11 +02:00
|
|
|
|
2022-09-27 05:33:51 +02:00
|
|
|
|
2019-05-31 07:53:11 +02:00
|
|
|
export default class Modals {
|
|
|
|
|
2019-06-27 22:18:40 +02:00
|
|
|
static get shouldShowAddonErrors() {return Settings.get("settings", "addons", "addonErrors");}
|
2023-03-29 06:13:11 +02:00
|
|
|
static get hasModalOpen() {return !!document.getElementsByClassName("bd-modal").length;}
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2022-09-27 05:33:51 +02:00
|
|
|
static get ModalActions() {
|
2023-10-26 21:26:09 +02:00
|
|
|
return this._ModalActions ??= WebpackModules.getByProps("openModal", "closeModal");
|
2022-09-27 05:33:51 +02:00
|
|
|
}
|
2022-10-08 22:28:56 +02:00
|
|
|
static get ModalQueue() {return this._ModalQueue ??= [];}
|
|
|
|
|
|
|
|
static async initialize() {
|
2023-03-29 06:13:11 +02:00
|
|
|
const names = ["ModalActions"];
|
2022-10-08 22:28:56 +02:00
|
|
|
|
|
|
|
for (const name of names) {
|
2023-03-01 22:24:10 +01:00
|
|
|
let value = this[name];
|
|
|
|
|
|
|
|
if (name === "ModalActions") {
|
|
|
|
value = Object.keys(this.ModalActions).every(k => this.ModalActions[k]);
|
|
|
|
}
|
2022-10-08 22:28:56 +02:00
|
|
|
|
|
|
|
if (!value) {
|
|
|
|
Logger.warn("Modals", `Missing ${name} module!`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static default(title, content, buttons = []) {
|
2022-10-02 09:34:34 +02:00
|
|
|
const modal = DOMManager.parseHTML(`<div class="bd-modal-wrapper theme-dark">
|
2020-07-16 07:42:56 +02:00
|
|
|
<div class="bd-backdrop backdrop-1wrmKB"></div>
|
|
|
|
<div class="bd-modal modal-1UGdnR">
|
|
|
|
<div class="bd-modal-inner inner-1JeGVc">
|
2019-05-31 07:53:11 +02:00
|
|
|
<div class="header header-1R_AjF">
|
|
|
|
<div class="title">${title}</div>
|
|
|
|
</div>
|
|
|
|
<div class="bd-modal-body">
|
|
|
|
<div class="scroller-wrap fade">
|
2022-10-08 22:28:56 +02:00
|
|
|
<div class="scroller"></div>
|
2019-05-31 07:53:11 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
2022-10-08 22:28:56 +02:00
|
|
|
<div class="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp"></div>
|
2019-05-31 07:53:11 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>`);
|
2022-10-08 22:28:56 +02:00
|
|
|
|
|
|
|
const handleClose = () => {
|
2020-07-16 23:17:02 +02:00
|
|
|
modal.classList.add("closing");
|
2022-10-08 22:28:56 +02:00
|
|
|
setTimeout(() => {
|
|
|
|
modal.remove();
|
|
|
|
|
|
|
|
const next = this.ModalQueue.shift();
|
|
|
|
if (!next) return;
|
|
|
|
|
|
|
|
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) => {
|
2022-10-14 05:42:05 +02:00
|
|
|
try {
|
|
|
|
button.action(e);
|
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
Logger.stacktrace("Modals", "Could not fire button listener", error);
|
|
|
|
}
|
2022-10-08 22:28:56 +02:00
|
|
|
|
|
|
|
handleClose();
|
|
|
|
},
|
|
|
|
type: "button",
|
|
|
|
className: "bd-button"
|
|
|
|
});
|
|
|
|
|
2022-10-14 05:42:05 +02:00
|
|
|
if (button.danger) buttonEl.classList.add("bd-button-danger");
|
2022-10-08 22:28:56 +02:00
|
|
|
|
|
|
|
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);
|
2022-10-14 05:42:05 +02:00
|
|
|
}
|
|
|
|
catch (error) {
|
2022-10-08 22:28:56 +02:00
|
|
|
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);
|
|
|
|
});
|
2022-10-14 05:42:05 +02:00
|
|
|
}
|
|
|
|
else {
|
2022-10-08 22:28:56 +02:00
|
|
|
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);
|
2022-10-14 05:42:05 +02:00
|
|
|
}
|
|
|
|
else {
|
2022-10-08 22:28:56 +02:00
|
|
|
handleOpen();
|
|
|
|
}
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static alert(title, content) {
|
2021-04-06 20:09:43 +02:00
|
|
|
this.showConfirmationModal(title, content, {cancelText: null});
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Shows a generic but very customizable confirmation modal with optional confirm and cancel callbacks.
|
|
|
|
* @param {string} title - title of the modal
|
2020-07-16 07:42:56 +02:00
|
|
|
* @param {(string|ReactElement|Array<string|ReactElement>)} children - a single or mixed array of react elements and strings. Everything is wrapped in Discord's `Markdown` component so strings will show and render properly.
|
2019-05-31 07:53:11 +02:00
|
|
|
* @param {object} [options] - options to modify the modal
|
|
|
|
* @param {boolean} [options.danger=false] - whether the main button should be red or not
|
|
|
|
* @param {string} [options.confirmText=Okay] - text for the confirmation/submit button
|
|
|
|
* @param {string} [options.cancelText=Cancel] - text for the cancel button
|
|
|
|
* @param {callable} [options.onConfirm=NOOP] - callback to occur when clicking the submit button
|
|
|
|
* @param {callable} [options.onCancel=NOOP] - callback to occur when clicking the cancel button
|
2024-02-22 01:49:24 +01:00
|
|
|
* @param {callable} [options.onClose=NOOP] - callback to occur when exiting the modal
|
2020-07-16 07:42:56 +02:00
|
|
|
* @param {string} [options.key] - key used to identify the modal. If not provided, one is generated and returned
|
|
|
|
* @returns {string} - the key used for this modal
|
2019-05-31 07:53:11 +02:00
|
|
|
*/
|
|
|
|
static showConfirmationModal(title, content, options = {}) {
|
2020-07-18 04:24:20 +02:00
|
|
|
const ModalActions = this.ModalActions;
|
2022-10-08 22:28:56 +02:00
|
|
|
|
2020-07-16 07:42:56 +02:00
|
|
|
if (content instanceof FormattableString) content = content.toString();
|
2019-05-31 07:53:11 +02:00
|
|
|
|
|
|
|
const emptyFunction = () => {};
|
2024-02-22 01:49:24 +01:00
|
|
|
const {onClose = emptyFunction, onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options;
|
2021-04-05 07:43:30 +02:00
|
|
|
|
2023-03-29 06:13:11 +02:00
|
|
|
if (!this.ModalActions) {
|
2022-10-14 05:42:05 +02:00
|
|
|
return this.default(title, content, [
|
|
|
|
confirmText && {label: confirmText, action: onConfirm},
|
|
|
|
cancelText && {label: cancelText, action: onCancel, danger}
|
|
|
|
].filter(Boolean));
|
|
|
|
}
|
2022-10-08 22:28:56 +02:00
|
|
|
|
2020-07-16 07:42:56 +02:00
|
|
|
if (!Array.isArray(content)) content = [content];
|
2023-03-29 06:13:11 +02:00
|
|
|
content = content.map(c => typeof(c) === "string" ? React.createElement(CustomMarkdown, null, c) : c);
|
2020-07-16 07:42:56 +02:00
|
|
|
|
2023-04-12 00:30:36 +02:00
|
|
|
const modalKey = this.openModal(props => {
|
2022-10-08 22:28:56 +02:00
|
|
|
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({
|
2020-07-18 04:24:20 +02:00
|
|
|
header: title,
|
2023-03-29 06:13:11 +02:00
|
|
|
danger: danger,
|
2020-07-18 04:24:20 +02:00
|
|
|
confirmText: confirmText,
|
|
|
|
cancelText: cancelText,
|
|
|
|
onConfirm: onConfirm,
|
2024-02-22 01:49:24 +01:00
|
|
|
onCancel: onCancel,
|
2024-02-22 05:10:48 +01:00
|
|
|
onCloseCallback: () => {
|
2024-02-24 11:17:59 +01:00
|
|
|
if (props?.transitionState === 2) onClose?.();
|
2024-02-22 05:10:48 +01:00
|
|
|
}
|
2022-10-08 22:28:56 +02:00
|
|
|
}, props), React.createElement(ErrorBoundary, {}, content)));
|
2020-07-18 04:24:20 +02:00
|
|
|
}, {modalKey: key});
|
2022-10-08 22:28:56 +02:00
|
|
|
return modalKey;
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
|
|
|
|
2019-06-27 22:18:40 +02:00
|
|
|
static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
|
|
|
|
if (!pluginErrors || !themeErrors || !this.shouldShowAddonErrors) return;
|
2019-05-31 07:53:11 +02:00
|
|
|
if (!pluginErrors.length && !themeErrors.length) return;
|
2021-04-06 20:09:43 +02:00
|
|
|
|
2023-03-31 21:53:36 +02:00
|
|
|
const options = {
|
|
|
|
ref: this.addonErrorsRef,
|
|
|
|
pluginErrors: Array.isArray(pluginErrors) ? pluginErrors : [],
|
|
|
|
themeErrors: Array.isArray(themeErrors) ? themeErrors : []
|
|
|
|
};
|
2023-04-12 00:30:36 +02:00
|
|
|
this.openModal(props => {
|
2023-03-31 21:53:36 +02:00
|
|
|
return React.createElement(ErrorBoundary, null, React.createElement(AddonErrorModal, Object.assign(options, props)));
|
|
|
|
});
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
2020-02-28 01:00:12 +01:00
|
|
|
|
2022-10-10 00:22:07 +02:00
|
|
|
static showChangelogModal(options = {}) {
|
2023-03-31 08:06:49 +02:00
|
|
|
options = Object.assign({image: "https://i.imgur.com/wuh5yMK.png", description: "", changes: [], title: "BetterDiscord", subtitle: `v${Config.version}`}, options);
|
2022-10-10 00:22:07 +02:00
|
|
|
|
2023-04-12 00:30:36 +02:00
|
|
|
const key = this.openModal(props => {
|
2023-03-31 08:06:49 +02:00
|
|
|
return React.createElement(ErrorBoundary, null, React.createElement(ChangelogModal, Object.assign(options, props)));
|
2020-02-28 01:00:12 +01:00
|
|
|
});
|
2020-07-26 11:34:38 +02:00
|
|
|
return key;
|
2020-02-28 01:00:12 +01:00
|
|
|
}
|
2020-11-03 02:47:08 +01:00
|
|
|
|
|
|
|
static showAddonSettingsModal(name, panel) {
|
|
|
|
|
|
|
|
let child = panel;
|
|
|
|
if (panel instanceof Node || typeof(panel) === "string") {
|
|
|
|
child = class ReactWrapper extends React.Component {
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.elementRef = React.createRef();
|
|
|
|
this.element = panel;
|
2023-03-09 02:07:10 +01:00
|
|
|
this.state = {hasError: false};
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidCatch() {
|
|
|
|
this.setState({hasError: true});
|
2020-11-03 02:47:08 +01:00
|
|
|
}
|
2021-04-05 07:43:30 +02:00
|
|
|
|
2020-11-03 02:47:08 +01:00
|
|
|
componentDidMount() {
|
|
|
|
if (this.element instanceof Node) this.elementRef.current.appendChild(this.element);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
2023-03-31 21:53:36 +02:00
|
|
|
if (this.state.hasError) return React.createElement(TextElement, {color: TextElement.Colors.STATUS_RED}, Strings.Addons.settingsError);
|
2020-11-03 02:47:08 +01:00
|
|
|
const props = {
|
|
|
|
className: "bd-addon-settings-wrap",
|
|
|
|
ref: this.elementRef
|
|
|
|
};
|
|
|
|
if (typeof(this.element) === "string") props.dangerouslySetInnerHTML = {__html: this.element};
|
|
|
|
return React.createElement("div", props);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (typeof(child) === "function") child = React.createElement(child);
|
|
|
|
|
2023-03-31 21:53:36 +02:00
|
|
|
const options = {
|
|
|
|
className: "bd-addon-modal",
|
|
|
|
size: ModalRoot.Sizes.MEDIUM,
|
|
|
|
header: `${name} Settings`,
|
|
|
|
cancelText: null,
|
|
|
|
confirmText: Strings.Modals.done
|
2020-11-03 02:47:08 +01:00
|
|
|
};
|
|
|
|
|
2023-08-29 05:47:20 +02:00
|
|
|
return this.openModal(props => {
|
2023-03-31 21:53:36 +02:00
|
|
|
return React.createElement(ErrorBoundary, null, React.createElement(ConfirmationModal, Object.assign(options, props), child));
|
2020-11-03 02:47:08 +01:00
|
|
|
});
|
|
|
|
}
|
2023-04-12 00:30:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static makeStack() {
|
|
|
|
const div = DOMManager.parseHTML(`<div id="bd-modal-container">`);
|
|
|
|
DOMManager.bdBody.append(div);
|
|
|
|
ReactDOM.render(<ModalStack />, div);
|
|
|
|
this.hasInitialized = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static openModal(render, options = {}) {
|
2023-08-29 05:47:20 +02:00
|
|
|
if (typeof(this.ModalActions.openModal) === "function") return this.ModalActions.openModal(render);
|
2023-04-12 00:30:36 +02:00
|
|
|
if (!this.hasInitialized) this.makeStack();
|
|
|
|
options.modalKey = generateKey(options.modalKey);
|
|
|
|
Events.emit("open-modal", render, options);
|
|
|
|
return options.modalKey;
|
|
|
|
}
|
2022-10-08 22:28:56 +02:00
|
|
|
}
|
2023-04-12 00:30:36 +02:00
|
|
|
|
|
|
|
|
|
|
|
Modals.makeStack();
|