Fix settings, tooltips, modals (#1542)

* Lazy load sidebar header

* Publish CSS

* Swap to standalone header

* Fix Tooltip component
* Moves internal reference to the centralized DiscordModules.Tooltip component
* Adds `BdApi.Components` namespace with the `Tooltip` component.

* Add Components namespace to bound api

* Fix modal root

* Fix ContextMenu, Fix Modals

---------

Co-authored-by: Strencher <46447572+Strencher@users.noreply.github.com>
This commit is contained in:
Zerebos 2023-03-01 16:24:10 -05:00 committed by GitHub
parent 99f8bc29a8
commit f3b26fbd4d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 59 additions and 43 deletions

View File

@ -16,15 +16,18 @@ const MenuComponents = (() => {
customitem: "Item"
};
// exportKey:()=>identifier
const getExportIdentifier = (string, id) => new RegExp(`(\\w+):\\(\\)=>${id}`).exec(string)?.[1];
try {
let contextMenuId = Object.keys(WebpackModules.require.m).find(e => WebpackModules.require.m[e]?.toString().includes("menuitemcheckbox"));
const ContextMenuModule = WebpackModules.getModule((m, t, id) => id === contextMenuId);
const rawMatches = WebpackModules.require.m[contextMenuId].toString().matchAll(/if\(\w+\.type===\w+\.(\w+)\).+?type:"(.+?)"/g);
const rawMatches = WebpackModules.require.m[contextMenuId].toString().matchAll(/if\(\w+\.type===(\w+)\)[\s\S]+?type:"(.+?)"/g);
const moduleString = WebpackModules.require.m[contextMenuId].toString();
out.Menu = Object.values(ContextMenuModule).find(v => v.toString().includes(".isUsingKeyboardNavigation"));
for (const [, identifier, type] of rawMatches) {
out[componentMap[type]] = ContextMenuModule[identifier];
out[componentMap[type]] = ContextMenuModule[getExportIdentifier(moduleString, identifier)];
}
startupComplete = Object.values(componentMap).every(k => out[k]) && !!out.Menu;
@ -55,7 +58,7 @@ const ContextMenuActions = (() => {
}
}
startupComplete = typeof(out.closeContextMenu) === "function" && typeof(out.openContextMenu) === "function";
startupComplete &&= typeof(out.closeContextMenu) === "function" && typeof(out.openContextMenu) === "function";
} catch (error) {
startupComplete = false;
Logger.stacktrace("ContextMenu~Components", "Fatal startup error:", error);

View File

@ -12,6 +12,7 @@ import Utils from "./utils";
import Webpack from "./webpack";
import * as Legacy from "./legacy";
import ContextMenu from "./contextmenu";
import {DiscordModules} from "modules";
const bounded = new Map();
const PluginAPI = new AddonAPI(PluginManager);
@ -53,6 +54,9 @@ export default class BdApi {
get UI() {return UI;}
get ReactUtils() {return ReactUtils;}
get ContextMenu() {return ContextMenuAPI;}
Components = {
get Tooltip() {return DiscordModules.Tooltip;}
}
}
// Add legacy functions
@ -118,5 +122,10 @@ BdApi.DOM = DOMAPI;
*/
BdApi.ContextMenu = ContextMenuAPI;
BdApi.Components = {
get Tooltip() {return DiscordModules.Tooltip;}
};
Object.freeze(BdApi);
Object.freeze(BdApi.prototype);
Object.freeze(BdApi.Components);

View File

@ -110,4 +110,4 @@ const Webpack = {
Object.freeze(Webpack);
Object.freeze(Webpack.Filters);
export default Webpack;
export default Webpack;

View File

@ -6,7 +6,7 @@
*/
import Utilities from "./utilities";
import WebpackModules from "./webpackmodules";
import WebpackModules, {Filters} from "./webpackmodules";
export default Utilities.memoizeObject({
get React() {return WebpackModules.getByProps("createElement", "cloneElement");},
@ -154,5 +154,12 @@ export default Utilities.memoizeObject({
return Object.assign({}, guildsWrapper, guilds, pill, listItem);
},
get LayerStack() {return WebpackModules.getByProps("pushLayer");}
get LayerStack() {return WebpackModules.getByProps("pushLayer");},
get Tooltip() {
// Make fallback component just pass children, so it can at least render that.
const fallback = props => props.children?.({}) ?? null;
return WebpackModules.getModule(Filters.byPrototypeFields(["renderTooltip"]), {searchExports: true}) ?? fallback;
}
});

View File

@ -1,8 +1,7 @@
import {React, WebpackModules, DiscordModules, Settings} from "modules";
import {React, DiscordModules, Settings} from "modules";
import Checkbox from "./checkbox";
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
const ThemeStore = DiscordModules.ThemeStore;
const languages = ["abap", "abc", "actionscript", "ada", "apache_conf", "asciidoc", "assembly_x86", "autohotkey", "batchfile", "bro", "c_cpp", "c9search", "cirru", "clojure", "cobol", "coffee", "coldfusion", "csharp", "csound_document", "csound_orchestra", "csound_score", "css", "curly", "d", "dart", "diff", "dockerfile", "dot", "drools", "dummy", "dummysyntax", "eiffel", "ejs", "elixir", "elm", "erlang", "forth", "fortran", "ftl", "gcode", "gherkin", "gitignore", "glsl", "gobstones", "golang", "graphqlschema", "groovy", "haml", "handlebars", "haskell", "haskell_cabal", "haxe", "hjson", "html", "html_elixir", "html_ruby", "ini", "io", "jack", "jade", "java", "javascript", "json", "jsoniq", "jsp", "jssm", "jsx", "julia", "kotlin", "latex", "less", "liquid", "lisp", "livescript", "logiql", "lsl", "lua", "luapage", "lucene", "makefile", "markdown", "mask", "matlab", "maze", "mel", "mushcode", "mysql", "nix", "nsis", "objectivec", "ocaml", "pascal", "perl", "pgsql", "php", "pig", "powershell", "praat", "prolog", "properties", "protobuf", "python", "r", "razor", "rdoc", "red", "rhtml", "rst", "ruby", "rust", "sass", "scad", "scala", "scheme", "scss", "sh", "sjs", "smarty", "snippets", "soy_template", "space", "sql", "sqlserver", "stylus", "svg", "swift", "tcl", "tex", "text", "textile", "toml", "tsx", "twig", "typescript", "vala", "vbscript", "velocity", "verilog", "vhdl", "wollok", "xml", "xquery", "yaml", "django"];
@ -108,11 +107,11 @@ export default class CodeEditor extends React.Component {
}
makeButton(button) {
return <Tooltip color="primary" position="top" text={button.tooltip}>
return <DiscordModules.Tooltip color="primary" position="top" text={button.tooltip}>
{props => {
return <button {...props} className="btn btn-primary" onClick={(event) => {button.onClick(event, this.value);}}>{button.label}</button>;
}}
</Tooltip>;
</DiscordModules.Tooltip>;
}
render() {
@ -135,4 +134,4 @@ export default class CodeEditor extends React.Component {
</div>
</div>;
}
}
}

View File

@ -12,17 +12,17 @@ export default class Modals {
static get ModalActions() {
return this._ModalActions ??= {
openModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback") && m?.toString().includes("Layer"), {searchExports: true}),
closeModal: WebpackModules.getModule(m => m?.toString().includes("onCloseCallback()"), {searchExports: true})
openModal: WebpackModules.getModule(m => typeof m === "function" && m?.toString().includes("onCloseCallback") && m?.toString().includes("Layer"), {searchExports: true}),
closeModal: WebpackModules.getModule(m => typeof m === "function" && m?.toString().includes("onCloseCallback()"), {searchExports: true})
};
}
static get ModalStack() {return this._ModalStack ??= WebpackModules.getByProps("push", "update", "pop", "popWithKey");}
static get ModalComponents() {return this._ModalComponents ??= WebpackModules.getByProps("Header", "Footer");}
static get ModalRoot() {return this._ModalRoot ??= WebpackModules.getModule(m => m?.toString?.()?.includes("ENTERING"), {searchExports: true});}
static get ModalRoot() {return this._ModalRoot ??= WebpackModules.getModule(m => m?.toString?.()?.includes("ENTERING") && m?.toString?.()?.includes("headerId"), {searchExports: true});}
static get ModalClasses() {return this._ModalClasses ??= WebpackModules.getByProps("modal", "content");}
static get FlexElements() {return this._FlexElements ??= WebpackModules.getByProps("Child", "Align");}
static get TextElement() {return this._TextElement ??= WebpackModules.getModule(m => m?.Sizes?.SIZE_32 && m.Colors);}
static get ConfirmationModal() {return this._ConfirmationModal ??= WebpackModules.getModule(m => m?.toString?.()?.includes(".confirmButtonColor"));}
static get ConfirmationModal() {return this._ConfirmationModal ??= WebpackModules.getModule(m => m?.toString?.()?.includes(".confirmButtonColor"), {searchExports: true});}
static get Markdown() {return this._Markdown ??= WebpackModules.find(m => m?.prototype?.render && m.rules);}
static get Buttons() {return this._Buttons ??= WebpackModules.getModule(m => m.BorderColors, {searchExports: true});}
static get ModalQueue() {return this._ModalQueue ??= [];}
@ -30,10 +30,14 @@ export default class Modals {
static get hasModalOpen() {return !!document.getElementsByClassName("bd-modal").length;}
static async initialize() {
const names = ["ModalActions", "Markdown", "ModalRoot", "ModalComponents", "Buttons", "TextElement", "FlexElements"];
const names = ["ConfirmationModal", "ModalActions", "Markdown", "ModalRoot", "ModalComponents", "Buttons", "TextElement", "FlexElements"];
for (const name of names) {
const value = this[name];
let value = this[name];
if (name === "ModalActions") {
value = Object.keys(this.ModalActions).every(k => this.ModalActions[k]);
}
if (!value) {
Logger.warn("Modals", `Missing ${name} module!`);

View File

@ -25,7 +25,6 @@ const LinkIcons = {
patreon: PatreonIcon
};
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
const LayerManager = {
pushLayer(component) {
DiscordModules.Dispatcher.dispatch({
@ -149,19 +148,19 @@ export default class AddonCard extends React.Component {
}
makeButton(title, children, action) {
return <Tooltip color="primary" position="top" text={title}>
return <DiscordModules.Tooltip color="primary" position="top" text={title}>
{(props) => {
return <div {...props} className="bd-addon-button" onClick={action}>{children}</div>;
}}
</Tooltip>;
</DiscordModules.Tooltip>;
}
makeControlButton(title, children, action, {danger = false, disabled = false} = {}) {
return <Tooltip color="primary" position="top" text={title}>
return <DiscordModules.Tooltip color="primary" position="top" text={title}>
{(props) => {
return <button {...props} className={"bd-button bd-addon-button" + (danger ? " bd-button-danger" : "") + (disabled ? " bd-button-disabled" : "")} onClick={action}>{children}</button>;
}}
</Tooltip>;
</DiscordModules.Tooltip>;
}
render() {
@ -192,4 +191,4 @@ Object.defineProperty(AddonCard.prototype, "render", {
configurable: false,
set: function() {Logger.warn("AddonCard", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");},
get: () => originalRender
});
});

View File

@ -1,5 +1,5 @@
import Logger from "common/logger";
import {React, Strings, Events, WebpackModules, DataStore} from "modules";
import {React, Strings, Events, DataStore, DiscordModules} from "modules";
import Modals from "../modals";
import SettingsTitle from "./title";
@ -13,8 +13,6 @@ import GridIcon from "../icons/grid";
import NoResults from "../blankslates/noresults";
import EmptyImage from "../blankslates/emptyimage";
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
export default class AddonList extends React.Component {
constructor(props) {
@ -115,11 +113,11 @@ export default class AddonList extends React.Component {
}
makeControlButton(title, children, action, selected = false) {
return <Tooltip color="primary" position="top" text={title}>
return <DiscordModules.Tooltip color="primary" position="top" text={title}>
{(props) => {
return <button {...props} className={"bd-button bd-view-button" + (selected ? " selected" : "")} onClick={action}>{children}</button>;
}}
</Tooltip>;
</DiscordModules.Tooltip>;
}
render() {
@ -213,4 +211,4 @@ Object.defineProperty(AddonList.prototype, "render", {
configurable: false,
set: function() {Logger.warn("AddonList", "Addon policy for plugins #5 https://github.com/BetterDiscord/BetterDiscord/wiki/Addon-Policies#plugins");},
get: () => originalRender
});
});

View File

@ -1,6 +1,4 @@
import {React, WebpackModules} from "modules";
const TooltipWrapper = WebpackModules.getByPrototypes("renderTooltip");
import {DiscordModules, React} from "modules";
const Checkmark = React.memo((props) => (
<svg width="16" height="16" viewBox="0 0 24 24" {...props}>
@ -73,7 +71,7 @@ export default class Color extends React.Component {
return <div className="bd-color-picker-container">
<div className="bd-color-picker-controls">
<TooltipWrapper text="Default" position="bottom">
<DiscordModules.Tooltip text="Default" position="bottom">
{props => (
<div {...props} className="bd-color-picker-default" style={{backgroundColor: resolveColor(defaultValue)}} onClick={() => this.onChange({target: {value: defaultValue}})}>
{intValue === resolveColor(defaultValue, false)
@ -82,15 +80,15 @@ export default class Color extends React.Component {
}
</div>
)}
</TooltipWrapper>
<TooltipWrapper text="Custom Color" position="bottom">
</DiscordModules.Tooltip>
<DiscordModules.Tooltip text="Custom Color" position="bottom">
{props => (
<div className="bd-color-picker-custom">
<Dropper color={getContrastColor(resolveColor(this.state.value, true))} />
<input {...props} style={{backgroundColor: resolveColor(this.state.value)}} type="color" className="bd-color-picker" value={resolveColor(this.state.value)} onChange={this.onChange} />
</div>
)}
</TooltipWrapper>
</DiscordModules.Tooltip>
</div>
<div className="bd-color-picker-swatch">
{
@ -106,4 +104,4 @@ export default class Color extends React.Component {
</div>
</div>;
}
}
}

View File

@ -1,9 +1,8 @@
import {Changelog} from "data";
import {React, WebpackModules} from "modules";
import {DiscordModules, React} from "modules";
import HistoryIcon from "../icons/history";
import Modals from "../modals";
const Tooltip = WebpackModules.getByPrototypes("renderTooltip");
export default class SettingsTitle extends React.Component {
renderHeader() {
@ -13,13 +12,13 @@ export default class SettingsTitle extends React.Component {
render() {
return <div className="bd-sidebar-header">
{this.renderHeader()}
<Tooltip color="primary" position="top" text="Changelog">
<DiscordModules.Tooltip color="primary" position="top" text="Changelog">
{props =>
<div {...props} className="bd-changelog-button" onClick={() => Modals.showChangelogModal(Changelog)}>
<HistoryIcon className="bd-icon" size="16px" />
</div>
}
</Tooltip>
</DiscordModules.Tooltip>
</div>;
}
}