2020-02-28 01:00:12 +01:00
|
|
|
import {Config} from "data";
|
2020-07-16 23:17:02 +02:00
|
|
|
import {Logger, WebpackModules, React, Settings, Strings, DOM, DiscordModules} from "modules";
|
2020-07-16 07:42:56 +02:00
|
|
|
import FormattableString from "../structs/string";
|
2020-11-03 02:47:08 +01:00
|
|
|
import ErrorBoundary from "./errorboundary";
|
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");}
|
2019-06-03 22:25:08 +02:00
|
|
|
|
2020-07-18 04:24:20 +02:00
|
|
|
static get ModalActions() {return WebpackModules.getByProps("openModal", "updateModal");}
|
2019-05-31 07:53:11 +02:00
|
|
|
static get ModalStack() {return WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
|
2020-11-03 02:47:08 +01:00
|
|
|
static get ModalComponents() {return WebpackModules.getByProps("ModalRoot");}
|
|
|
|
static get ModalClasses() {return WebpackModules.getByProps("modal", "content");}
|
2019-05-31 07:53:11 +02:00
|
|
|
static get AlertModal() {return WebpackModules.getByPrototypes("handleCancel", "handleSubmit", "handleMinorConfirm");}
|
2020-11-03 02:47:08 +01:00
|
|
|
static get FlexElements() {return WebpackModules.getByProps("Child", "Align");}
|
|
|
|
static get FormTitle() {return WebpackModules.findByDisplayName("FormTitle");}
|
2019-05-31 07:53:11 +02:00
|
|
|
static get TextElement() {return WebpackModules.getByProps("Sizes", "Weights");}
|
2020-07-18 04:24:20 +02:00
|
|
|
static get ConfirmationModal() {return WebpackModules.findByDisplayName("ConfirmModal");}
|
2020-07-16 07:42:56 +02:00
|
|
|
static get Markdown() {return WebpackModules.findByDisplayName("Markdown");}
|
2020-11-03 02:47:08 +01:00
|
|
|
static get Buttons() {return WebpackModules.getByProps("ButtonColors");}
|
2019-05-31 07:53:11 +02:00
|
|
|
|
|
|
|
static default(title, content) {
|
2020-07-16 23:17:02 +02:00
|
|
|
const modal = DOM.createElement(`<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">
|
|
|
|
<div class="scroller">
|
|
|
|
${content}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-07-16 07:42:56 +02:00
|
|
|
<div class="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp">
|
2019-06-30 05:09:48 +02:00
|
|
|
<button type="button" class="bd-button">${Strings.Modals.okay}</button>
|
2019-05-31 07:53:11 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>`);
|
2019-06-22 06:37:19 +02:00
|
|
|
modal.querySelector(".footer button").addEventListener("click", () => {
|
2020-07-16 23:17:02 +02:00
|
|
|
modal.classList.add("closing");
|
2020-07-25 10:22:57 +02:00
|
|
|
setTimeout(() => {modal.remove();}, 300);
|
2019-05-31 07:53:11 +02:00
|
|
|
});
|
2019-06-22 06:37:19 +02:00
|
|
|
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
|
2020-07-16 23:17:02 +02:00
|
|
|
modal.classList.add("closing");
|
2020-07-25 10:22:57 +02:00
|
|
|
setTimeout(() => {modal.remove();}, 300);
|
2019-05-31 07:53:11 +02:00
|
|
|
});
|
2019-06-22 06:37:19 +02:00
|
|
|
document.querySelector("#app-mount").append(modal);
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
static alert(title, content) {
|
2020-07-19 01:01:49 +02:00
|
|
|
this.showConfirmationModal(title, content, {cancelText: ""});
|
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
|
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-16 07:42:56 +02:00
|
|
|
const Markdown = this.Markdown;
|
2019-05-31 07:53:11 +02:00
|
|
|
const ConfirmationModal = this.ConfirmationModal;
|
2020-07-18 04:24:20 +02:00
|
|
|
const ModalActions = this.ModalActions;
|
2020-07-16 07:42:56 +02:00
|
|
|
if (content instanceof FormattableString) content = content.toString();
|
2020-07-18 04:24:20 +02:00
|
|
|
if (!this.ModalActions || !this.ConfirmationModal || !this.Markdown) return this.default(title, content);
|
2019-05-31 07:53:11 +02:00
|
|
|
|
|
|
|
const emptyFunction = () => {};
|
2020-07-16 07:42:56 +02:00
|
|
|
const {onConfirm = emptyFunction, onCancel = emptyFunction, confirmText = Strings.Modals.okay, cancelText = Strings.Modals.cancel, danger = false, key = undefined} = options;
|
|
|
|
|
|
|
|
if (!Array.isArray(content)) content = [content];
|
|
|
|
content = content.map(c => typeof(c) === "string" ? React.createElement(Markdown, null, c) : c);
|
|
|
|
|
2020-07-18 04:24:20 +02:00
|
|
|
return ModalActions.openModal(props => {
|
|
|
|
return React.createElement(ConfirmationModal, Object.assign({
|
|
|
|
header: title,
|
2020-11-03 02:47:08 +01:00
|
|
|
confirmButtonColor: danger ? this.Buttons.ButtonColors.RED : this.Buttons.ButtonColors.PRIMARY,
|
2020-07-18 04:24:20 +02:00
|
|
|
confirmText: confirmText,
|
|
|
|
cancelText: cancelText,
|
|
|
|
onConfirm: onConfirm,
|
|
|
|
onCancel: onCancel
|
|
|
|
}, props), content);
|
|
|
|
}, {modalKey: key});
|
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;
|
2020-07-16 23:17:02 +02:00
|
|
|
const modal = DOM.createElement(`<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 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>
|
2019-05-31 07:53:11 +02:00
|
|
|
<div class="bd-modal-body">
|
|
|
|
<div class="tab-bar-container">
|
|
|
|
<div class="tab-bar TOP">
|
2019-06-26 21:23:07 +02:00
|
|
|
<div class="tab-bar-item">${Strings.Panels.plugins}</div>
|
|
|
|
<div class="tab-bar-item">${Strings.Panels.themes}</div>
|
2019-05-31 07:53:11 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div class="table-header">
|
2019-06-25 22:36:34 +02:00
|
|
|
<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>
|
2019-05-31 07:53:11 +02:00
|
|
|
</div>
|
2020-07-16 07:42:56 +02:00
|
|
|
<div class="scroller-wrap fade">
|
2019-05-31 07:53:11 +02:00
|
|
|
<div class="scroller">
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
2020-07-16 07:42:56 +02:00
|
|
|
<div class="footer footer-2yfCgX footer-3rDWdC footer-2gL1pp">
|
2019-06-30 05:09:48 +02:00
|
|
|
<button type="button" class="bd-button">${Strings.Modals.okay}</button>
|
2019-05-31 07:53:11 +02:00
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>`);
|
|
|
|
|
|
|
|
const generateTab = function(errors) {
|
2020-07-16 23:17:02 +02:00
|
|
|
const container = DOM.createElement(`<div class="errors">`);
|
2019-05-31 07:53:11 +02:00
|
|
|
for (const err of errors) {
|
2020-07-16 23:17:02 +02:00
|
|
|
const error = DOM.createElement(`<div class="error">
|
2019-05-31 07:53:11 +02:00
|
|
|
<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) {
|
2020-07-16 23:17:02 +02:00
|
|
|
error.querySelectorAll("a").forEach(el => el.addEventListener("click", (e) => {
|
2019-05-31 07:53:11 +02:00
|
|
|
e.preventDefault();
|
2019-06-27 22:18:40 +02:00
|
|
|
Logger.stacktrace("AddonError", `Error details for ${err.name ? err.name : err.file}.`, err.error);
|
2020-07-16 23:17:02 +02:00
|
|
|
}));
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return container;
|
|
|
|
};
|
|
|
|
|
|
|
|
const tabs = [generateTab(pluginErrors), generateTab(themeErrors)];
|
|
|
|
|
2020-07-16 23:17:02 +02:00
|
|
|
modal.querySelectorAll(".tab-bar-item").forEach(el => el.addEventListener("click", (e) => {
|
2019-05-31 07:53:11 +02:00
|
|
|
e.preventDefault();
|
2020-07-16 23:17:02 +02:00
|
|
|
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)]);
|
|
|
|
}));
|
2019-05-31 07:53:11 +02:00
|
|
|
|
2020-07-16 23:17:02 +02:00
|
|
|
modal.querySelector(".footer button").addEventListener("click", () => {
|
|
|
|
DOM.addClass(modal, "closing");
|
2020-07-25 10:22:57 +02:00
|
|
|
setTimeout(() => {modal.remove();}, 300);
|
2019-05-31 07:53:11 +02:00
|
|
|
});
|
2020-07-16 23:17:02 +02:00
|
|
|
modal.querySelector(".bd-backdrop").addEventListener("click", () => {
|
|
|
|
DOM.addClass(modal, "closing");
|
2020-07-25 10:22:57 +02:00
|
|
|
setTimeout(() => {modal.remove();}, 300);
|
2019-05-31 07:53:11 +02:00
|
|
|
});
|
2020-07-16 23:17:02 +02:00
|
|
|
DOM.query("#app-mount").append(modal);
|
|
|
|
if (pluginErrors.length) modal.querySelector(".tab-bar-item").click();
|
|
|
|
else modal.querySelectorAll(".tab-bar-item")[1].click();
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|
2020-02-28 01:00:12 +01:00
|
|
|
|
2020-07-16 07:42:56 +02:00
|
|
|
static showChangelogModal(options = {}) {
|
2020-02-28 01:00:12 +01:00
|
|
|
const ModalStack = WebpackModules.getByProps("push", "update", "pop", "popWithKey");
|
|
|
|
const ChangelogClasses = WebpackModules.getByProps("fixed", "improved");
|
2020-07-16 07:42:56 +02:00
|
|
|
const TextElement = WebpackModules.findByDisplayName("Text");
|
2020-02-28 01:00:12 +01:00
|
|
|
const FlexChild = WebpackModules.getByProps("Child");
|
|
|
|
const Titles = WebpackModules.getByProps("Tags", "default");
|
|
|
|
const Changelog = WebpackModules.getModule(m => m.defaultProps && m.defaultProps.selectable == false);
|
|
|
|
const MarkdownParser = WebpackModules.getByProps("defaultRules", "parse");
|
2020-07-16 07:42:56 +02:00
|
|
|
if (!Changelog || !ModalStack || !ChangelogClasses || !TextElement || !FlexChild || !Titles || !MarkdownParser) return Logger.warn("Modals", "showChangelogModal missing modules");
|
2020-02-28 01:00:12 +01:00
|
|
|
|
2020-07-27 00:47:04 +02:00
|
|
|
const {image = "https://repository-images.githubusercontent.com/105473537/957b5480-7c26-11e9-8401-50fa820cbae5", description = "", changes = [], title = "BetterDiscord", subtitle = `v${Config.bdVersion}`, footer} = options;
|
2020-02-28 01:00:12 +01:00
|
|
|
const ce = React.createElement;
|
2020-07-26 11:34:38 +02:00
|
|
|
const changelogItems = [options.video ? ce("video", {src: options.video, poster: options.poster, controls: true, className: ChangelogClasses.video}) : ce("img", {src: image})];
|
2020-02-28 01:00:12 +01:00
|
|
|
if (description) changelogItems.push(ce("p", null, MarkdownParser.parse(description)));
|
|
|
|
for (let c = 0; c < changes.length; c++) {
|
|
|
|
const entry = changes[c];
|
|
|
|
const type = ChangelogClasses[entry.type] ? ChangelogClasses[entry.type] : ChangelogClasses.added;
|
|
|
|
const margin = c == 0 ? ChangelogClasses.marginTop : "";
|
|
|
|
changelogItems.push(ce("h1", {className: `${type} ${margin}`,}, entry.title));
|
|
|
|
const list = ce("ul", null, entry.items.map(i => ce("li", null, MarkdownParser.parse(i))));
|
|
|
|
changelogItems.push(list);
|
|
|
|
}
|
|
|
|
const renderHeader = function() {
|
|
|
|
return ce(FlexChild.Child, {grow: 1, shrink: 1},
|
|
|
|
ce(Titles.default, {tag: Titles.Tags.H4}, title),
|
2020-07-16 07:42:56 +02:00
|
|
|
ce(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD, className: ChangelogClasses.date}, subtitle)
|
2020-02-28 01:00:12 +01:00
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const renderFooter = () => {
|
|
|
|
const Anchor = WebpackModules.getModule(m => m.displayName == "Anchor");
|
|
|
|
const AnchorClasses = WebpackModules.getByProps("anchorUnderlineOnHover") || {anchor: "anchor-3Z-8Bb", anchorUnderlineOnHover: "anchorUnderlineOnHover-2ESHQB"};
|
|
|
|
const joinSupportServer = (click) => {
|
|
|
|
click.preventDefault();
|
|
|
|
click.stopPropagation();
|
|
|
|
ModalStack.pop();
|
2020-07-16 07:42:56 +02:00
|
|
|
DiscordModules.InviteActions.acceptInviteAndTransitionToInviteChannel("2HScm8j");
|
2020-02-28 01:00:12 +01:00
|
|
|
};
|
|
|
|
const supportLink = Anchor ? ce(Anchor, {onClick: joinSupportServer}, "Join our Discord Server.") : ce("a", {className: `${AnchorClasses.anchor} ${AnchorClasses.anchorUnderlineOnHover}`, onClick: joinSupportServer}, "Join our Discord Server.");
|
2020-07-16 07:42:56 +02:00
|
|
|
const defaultFooter = ce(TextElement, {size: TextElement.Sizes.SMALL, color: TextElement.Colors.STANDARD}, "Need support? ", supportLink);
|
2020-02-28 01:00:12 +01:00
|
|
|
return ce(FlexChild.Child, {grow: 1, shrink: 1}, footer ? footer : defaultFooter);
|
|
|
|
};
|
2020-07-26 11:34:38 +02:00
|
|
|
|
|
|
|
const ModalActions = this.ModalActions;
|
|
|
|
const OriginalModalClasses = WebpackModules.getByProps("hideOnFullscreen");
|
|
|
|
const originalRoot = OriginalModalClasses.root;
|
|
|
|
if (originalRoot) OriginalModalClasses.root = `${originalRoot} bd-changelog-modal`;
|
|
|
|
const key = ModalActions.openModal(props => {
|
|
|
|
return React.createElement(Changelog, Object.assign({
|
|
|
|
className: `bd-changelog ${ChangelogClasses.container}`,
|
|
|
|
selectable: true,
|
|
|
|
onScroll: _ => _,
|
|
|
|
onClose: _ => _,
|
|
|
|
renderHeader: renderHeader,
|
|
|
|
renderFooter: renderFooter,
|
|
|
|
}, props), changelogItems);
|
2020-02-28 01:00:12 +01:00
|
|
|
});
|
2020-07-27 01:11:16 +02:00
|
|
|
|
|
|
|
const closeModal = ModalActions.closeModal;
|
|
|
|
ModalActions.closeModal = function(k) {
|
|
|
|
Reflect.apply(closeModal, this, arguments);
|
|
|
|
setTimeout(() => {if (originalRoot && k === key) OriginalModalClasses.root = originalRoot;}, 1000);
|
|
|
|
ModalActions.closeModal = closeModal;
|
|
|
|
};
|
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;
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
if (this.element instanceof Node) this.elementRef.current.appendChild(this.element);
|
|
|
|
// if (typeof(this.element) === "string") this.elementRef.current.appendChild(this.element);
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
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);
|
|
|
|
|
|
|
|
const mc = this.ModalComponents;
|
|
|
|
const modal = props => {
|
2020-11-05 09:28:58 +01:00
|
|
|
return React.createElement(mc.ModalRoot, Object.assign({size: mc.ModalSize.SMALL, className: "bd-addon-modal"}, props),
|
2020-11-03 02:47:08 +01:00
|
|
|
React.createElement(mc.ModalHeader, {separator: false, className: "bd-addon-modal-header"},
|
|
|
|
React.createElement(this.FormTitle, {tag: "h4"}, `${name} Settings`),
|
|
|
|
React.createElement(this.FlexElements.Child, {grow: 0},
|
2020-11-05 09:28:58 +01:00
|
|
|
React.createElement(mc.ModalCloseButton, {className: "bd-modal-close", onClick: props.onClose})
|
2020-11-03 02:47:08 +01:00
|
|
|
)
|
|
|
|
),
|
|
|
|
React.createElement(mc.ModalContent, {className: "bd-addon-modal-settings"},
|
|
|
|
React.createElement(ErrorBoundary, {}, child)
|
|
|
|
),
|
|
|
|
React.createElement(mc.ModalFooter, {className: "bd-addon-modal-footer"},
|
|
|
|
React.createElement(this.Buttons.default, {onClick: props.onClose, className: "bd-button"}, Strings.Modals.done)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
return this.ModalActions.openModal(props => {
|
|
|
|
return React.createElement(modal, props);
|
|
|
|
});
|
|
|
|
}
|
2019-05-31 07:53:11 +02:00
|
|
|
}
|