Reduce reliance on internals

This commit is contained in:
Zack Rauen 2023-03-31 15:53:36 -04:00
parent 815d91e76f
commit b5ad46a63f
11 changed files with 78 additions and 308 deletions

View File

@ -1,49 +0,0 @@
import Utilities from "./utilities";
import ClassName from "../structs/classname";
import WebpackModules from "./webpackmodules";
const combineClasses = function (...props) {
return Object.assign({}, ...props.map(prop => WebpackModules.getByProps(...prop)));
};
const DiscordClassModules = Utilities.memoizeObject({
get Text() {
return combineClasses(
["size20", "size12"],
["selectable", "colorMuted"]
);
},
get Titles() {
return combineClasses(
["wrapper", "base"],
["defaultColor", "h4"]
);
},
get EmptyImage() {return WebpackModules.getByProps("emptyImage", "emptyHeader");},
get Modal() {return WebpackModules.getByProps("content", "root", "header", "close");},
get Scrollers() {return WebpackModules.getByProps("thin", "scrollerBase", "content");},
get Margins() {return WebpackModules.getByProps("marginXSmall", "marginBottom8");},
get Integrations() {return WebpackModules.getByProps("secondaryHeader", "detailsWrapper");},
get Card() {return WebpackModules.getByProps("card", "topDivider", "description");},
});
const emptyClassModule = new Proxy({}, {
get() {return "";}
});
const DiscordClasses = new Proxy(DiscordClassModules, {
get(list, item) {
if (list[item] === undefined) return emptyClassModule;
if (typeof(list[item]) === "string") return list[item];
return new Proxy(list[item], {
get(obj, prop) {
if (!(prop in obj)) return "";
return new ClassName(obj[prop]);
}
});
}
});
export default DiscordClasses;

View File

@ -11,151 +11,13 @@ import WebpackModules, {Filters} from "./webpackmodules";
export default Utilities.memoizeObject({
get React() {return WebpackModules.getByProps("createElement", "cloneElement");},
get ReactDOM() {return WebpackModules.getByProps("render", "findDOMNode");},
get Flux() {return WebpackModules.getByProps("connectStores");},
get Events() {return WebpackModules.getByPrototypes("setMaxListeners", "emit");},
/* Guild Info, Stores, and Utilities */
get GuildStore() {return WebpackModules.getByProps("getGuild");},
get SortedGuildStore() {return WebpackModules.getByProps("getSortedGuilds");},
get SelectedGuildStore() {return WebpackModules.getByProps("getLastSelectedGuildId");},
get GuildSync() {return WebpackModules.getByProps("getSyncedGuilds");},
get GuildInfo() {return WebpackModules.getByProps("getAcronym");},
get GuildChannelsStore() {return WebpackModules.getByProps("getChannels", "getDefaultChannel");},
get GuildMemberStore() {return WebpackModules.getByProps("getMember");},
get MemberCountStore() {return WebpackModules.getByProps("getMemberCounts");},
get GuildEmojiStore() {return WebpackModules.getByProps("getEmojis");},
get GuildActions() {return WebpackModules.getByProps("markGuildAsRead");},
get GuildPermissions() {return WebpackModules.getByProps("getGuildPermissions");},
/* Channel Store & Actions */
get ChannelStore() {return WebpackModules.getByProps("getChannel", "getDMFromUserId");},
get SelectedChannelStore() {return WebpackModules.getByProps("getLastSelectedChannelId");},
get ChannelActions() {return WebpackModules.getByProps("selectChannel");},
get PrivateChannelActions() {return WebpackModules.getByProps("openPrivateChannel");},
get ChannelSelector() {return WebpackModules.getByProps("selectGuild", "selectChannel");},
/* Current User Info, State and Settings */
get UserInfoStore() {return WebpackModules.getByProps("getToken");},
get LocaleStore() {return WebpackModules.getByProps("locale", "initialize");},
get ThemeStore() {return WebpackModules.getByProps("theme", "initialize");},
get AccountManager() {return WebpackModules.getByProps("register", "login");},
get UserSettingsUpdater() {return WebpackModules.getByProps("updateRemoteSettings");},
get OnlineWatcher() {return WebpackModules.getByProps("isOnline");},
get CurrentUserIdle() {return WebpackModules.getByProps("getIdleTime");},
get RelationshipStore() {return WebpackModules.getByProps("isBlocked", "getFriendIDs");},
get RelationshipManager() {return WebpackModules.getByProps("addRelationship");},
get MentionStore() {return WebpackModules.getByProps("getMentions");},
/* User Stores and Utils */
get UserStore() {return WebpackModules.getByProps("getCurrentUser", "getUser");},
get UserStatusStore() {return WebpackModules.getByProps("getStatus", "getState");},
get UserTypingStore() {return WebpackModules.getByProps("isTyping");},
get UserActivityStore() {return WebpackModules.getByProps("getActivity");},
get UserNameResolver() {return WebpackModules.getByProps("getName");},
get UserNoteStore() {return WebpackModules.getByProps("getNote");},
get UserNoteActions() {return WebpackModules.getByProps("updateNote");},
/* Emoji Store and Utils */
get EmojiInfo() {return WebpackModules.getByProps("isEmojiDisabled");},
get EmojiUtils() {return WebpackModules.getByProps("getGuildEmoji");},
get EmojiStore() {return WebpackModules.getByProps("getByCategory", "EMOJI_NAME_RE");},
/* Invite Store and Utils */
get InviteStore() {return WebpackModules.getByProps("getInvites");},
get InviteResolver() {return WebpackModules.getByProps("findInvite");},
get InviteActions() {return WebpackModules.getByProps("acceptInvite");},
/* Discord Objects & Utils */
get DiscordConstants() {return WebpackModules.getByProps("Permissions", "ActivityTypes", "StatusTypes");},
get DiscordPermissions() {return WebpackModules.getByProps("Permissions", "ActivityTypes", "StatusTypes").Permissions;},
get PermissionUtils() {return WebpackModules.getByProps("getHighestRole");},
get ColorConverter() {return WebpackModules.getByProps("hex2int");},
get ColorShader() {return WebpackModules.getByProps("darken");},
get TinyColor() {return WebpackModules.getByPrototypes("toRgb");},
get ClassResolver() {return WebpackModules.getByProps("getClass");},
get ButtonData() {return WebpackModules.getByProps("ButtonSizes");},
get IconNames() {return WebpackModules.getByProps("IconNames");},
get NavigationUtils() {return WebpackModules.getByProps("transitionTo", "replaceWith", "getHistory");},
/* Discord Messages */
get MessageStore() {return WebpackModules.getByProps("getMessages");},
get MessageActions() {return WebpackModules.getByProps("jumpToMessage", "_sendMessage");},
get MessageQueue() {return WebpackModules.getByProps("enqueue");},
get MessageParser() {return WebpackModules.getByProps("createMessage", "parse", "unparse");},
/* Text Processing */
get hljs() {return WebpackModules.getByProps("highlight", "highlightBlock");},
get SimpleMarkdown() {return WebpackModules.getByProps("parseBlock", "parseInline", "defaultOutput");},
/* Experiments */
get ExperimentStore() {return WebpackModules.getByProps("getExperimentOverrides");},
get ExperimentsManager() {return WebpackModules.getByProps("isDeveloper");},
get CurrentExperiment() {return WebpackModules.getByProps("getExperimentId");},
/* Images, Avatars and Utils */
get ImageResolver() {return WebpackModules.getByProps("getUserAvatarURL", "getGuildIconURL");},
get ImageUtils() {return WebpackModules.getByProps("getSizedImageSrc");},
get AvatarDefaults() {return WebpackModules.getByProps("getUserAvatarURL", "DEFAULT_AVATARS");},
/* Window, DOM, HTML */
get WindowInfo() {return WebpackModules.getByProps("isFocused", "windowSize");},
get TagInfo() {return WebpackModules.getByProps("VALID_TAG_NAMES");},
get DOMInfo() {return WebpackModules.getByProps("canUseDOM");},
/* Locale/Location and Time */
get LocaleManager() {return WebpackModules.getByProps("setLocale");},
get Moment() {return WebpackModules.getByProps("parseZone");},
get LocationManager() {return WebpackModules.getByProps("createLocation");},
get Timestamps() {return WebpackModules.getByProps("fromTimestamp");},
get TimeFormatter() {return WebpackModules.getByProps("dateFormat");},
/* Strings and Utils */
get Strings() {return WebpackModules.getByProps("Messages").Messages;},
get StringFormats() {return WebpackModules.getByProps("a", "z");},
get StringUtils() {return WebpackModules.getByProps("toASCII");},
/* URLs and Utils */
get URLParser() {return WebpackModules.getByProps("Url", "parse");},
get ExtraURLs() {return WebpackModules.getByProps("getArticleURL");},
/* Drag & Drop */
get DNDActions() {return WebpackModules.getByProps("beginDrag");},
get DNDSources() {return WebpackModules.getByProps("addTarget");},
get DNDObjects() {return WebpackModules.getByProps("DragSource");},
/* Media Stuff (Audio/Video) */
get MediaDeviceInfo() {return WebpackModules.getByProps("Codecs", "SUPPORTED_BROWSERS");},
get MediaInfo() {return WebpackModules.getByProps("getOutputVolume");},
get MediaEngineInfo() {return WebpackModules.getByProps("MediaEngineFeatures");},
get VoiceInfo() {return WebpackModules.getByProps("EchoCancellation");},
get VideoStream() {return WebpackModules.getByProps("getVideoStream");},
get SoundModule() {return WebpackModules.getByProps("playSound");},
/* Electron & Other Internals with Utils*/
get ElectronModule() {return WebpackModules.getByProps("setBadge");},
get Dispatcher() {return WebpackModules.getByProps("dispatch", "subscribe", "register");},
get PathUtils() {return WebpackModules.getByProps("hasBasename");},
get NotificationModule() {return WebpackModules.getByProps("showNotification");},
get RouterModule() {return WebpackModules.getByProps("Router");},
get APIModule() {return WebpackModules.getByProps("getAPIBaseURL");},
get AnalyticEvents() {return WebpackModules.getByProps("AnalyticEventConfigs");},
get KeyGenerator() {return WebpackModules.getByRegex(/"binary"/);},
get Buffers() {return WebpackModules.getByProps("INSPECT_MAX_BYTES", "kMaxLength");},
get DeviceStore() {return WebpackModules.getByProps("getDevices");},
get SoftwareInfo() {return WebpackModules.getByProps("os");},
get CurrentContext() {return WebpackModules.getByProps("setTagsContext");},
/* Commonly Used Classes */
get GuildClasses() {
const guildsWrapper = WebpackModules.getByProps("base", "guilds");
const guilds = WebpackModules.getByProps("wrapper", "acronym");
const pill = WebpackModules.getByProps("circleIconButton");
const listItem = WebpackModules.getModule(m => m.listItem && !m.pill && !m.sidebar);
return Object.assign({}, guildsWrapper, guilds, pill, listItem);
},
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;

View File

@ -14,5 +14,4 @@ export {default as Patcher} from "./patcher";
export {default as LocaleManager} from "./localemanager";
export {default as Strings} from "./strings";
export {default as IPC} from "./ipc";
export {default as Logger} from "common/logger";
export {default as DiscordClasses} from "./discordclasses";
export {default as Logger} from "common/logger";

View File

@ -1,50 +0,0 @@
// import Selector from "./selector";
/**
* Representation of a Class Name
**/
class ClassName {
/**
*
* @param {string} name - name of the class to represent
*/
constructor(name) {
this.value = name;
}
/**
* Concatenates new class names to the current one using spaces.
* @param {string} classNames - list of class names to add to this class name
* @returns {ClassName} returns self to allow chaining
*/
add(...classNames) {
for (let i = 0; i < classNames.length; i++) this.value += " " + classNames[i];
return this;
}
/**
* Returns the raw class name, this is how native function get the value.
* @returns {string} raw class name.
*/
toString() {
return this.value;
}
/**
* Returns the raw class name, this is how native function get the value.
* @returns {string} raw class name.
*/
valueOf() {
return this.value;
}
get single() {
return this.value.split(" ")[0];
}
get first() {
return this.value.split(" ")[0];
}
}
export default ClassName;

View File

@ -78,4 +78,16 @@
scrollbar-color: var(--background-tertiary) var(--background-secondary);
background: var(--background-secondary);
border: 1px solid var(--background-tertiary);
}
.bd-addon-error-details {
display: flex;
flex-grow: 0;
justify-content: flex-start;
margin-top: 4px;
}
.bd-addon-error-details-icon {
margin-right: 4px;
color: var(--interactive-normal);
}

View File

@ -1,10 +1,12 @@
import {React, DiscordClasses} from "modules";
import {React, WebpackModules} from "modules";
import SimpleMarkdown from "../../structs/markdown";
const EmptyImageClasses = WebpackModules.getByProps("emptyImage", "emptyHeader") ?? {emptyContainer: "emptyContainer-poti7J", emptyImage: "emptyImage-2pCD2j", emptyHeader: "emptyHeader-2cxTFP"};
export default function EmptyImage(props) {
return <div className={`bd-empty-image-container ${DiscordClasses.EmptyImage.emptyContainer}` + (props.className ? ` ${props.className}` : "")}>
<div className={`bd-empty-image ${DiscordClasses.EmptyImage.emptyImage}`}></div>
<div className={`bd-empty-image-header ${DiscordClasses.EmptyImage.emptyHeader}`}>
return <div className={`bd-empty-image-container ${EmptyImageClasses.emptyContainer}` + (props.className ? ` ${props.className}` : "")}>
<div className={`bd-empty-image ${EmptyImageClasses.emptyImage}`}></div>
<div className={`bd-empty-image-header ${EmptyImageClasses.emptyHeader}`}>
{props.title || "You don't have anything!"}
</div>
<div className={`bd-empty-image-message`}>

View File

@ -2,7 +2,7 @@ import {Config} from "data";
import Logger from "common/logger";
import {WebpackModules, React, ReactDOM, Settings, Strings, DOMManager} from "modules";
import FormattableString from "../structs/string";
import AddonErrorModal from "./addonerrormodal";
import AddonErrorModal from "./modals/addonerrormodal";
import ErrorBoundary from "./errorboundary";
import TextElement from "./base/text";
import ModalRoot from "./modals/root";
@ -198,29 +198,15 @@ export default class Modals {
static showAddonErrors({plugins: pluginErrors = [], themes: themeErrors = []}) {
if (!pluginErrors || !themeErrors || !this.shouldShowAddonErrors) return;
if (!pluginErrors.length && !themeErrors.length) return;
if (this.addonErrorsRef && this.addonErrorsRef.current) {
return this.addonErrorsRef.current.refreshTabs(Array.isArray(pluginErrors) ? pluginErrors : [], Array.isArray(themeErrors) ? themeErrors : []);
}
this.addonErrorsRef = React.createRef();
this.ModalActions.openModal(props => React.createElement(ErrorBoundary, null, React.createElement(ModalRoot, Object.assign(props, {
size: ModalRoot.Sizes.MEDIUM,
className: "bd-error-modal",
children: [
React.createElement(AddonErrorModal, {
ref: this.addonErrorsRef,
pluginErrors: Array.isArray(pluginErrors) ? pluginErrors : [],
themeErrors: Array.isArray(themeErrors) ? themeErrors : [],
onClose: props.onClose
}),
React.createElement(ModalFooter, {
className: "bd-error-modal-footer",
}, React.createElement(Button, {
onClick: props.onClose
}, Strings.Modals.okay))
]
}))));
const options = {
ref: this.addonErrorsRef,
pluginErrors: Array.isArray(pluginErrors) ? pluginErrors : [],
themeErrors: Array.isArray(themeErrors) ? themeErrors : []
};
this.ModalActions.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(AddonErrorModal, Object.assign(options, props)));
});
}
static showChangelogModal(options = {}) {
@ -253,7 +239,7 @@ export default class Modals {
}
render() {
if (this.state.hasError) return null;
if (this.state.hasError) return React.createElement(TextElement, {color: TextElement.Colors.STATUS_RED}, Strings.Addons.settingsError);
const props = {
className: "bd-addon-settings-wrap",
ref: this.elementRef
@ -265,22 +251,16 @@ export default class Modals {
}
if (typeof(child) === "function") child = React.createElement(child);
const modal = props => {
return React.createElement(ErrorBoundary, {}, React.createElement(ModalRoot, Object.assign({size: ModalRoot.Sizes.MEDIUM, className: "bd-addon-modal" + " " + ModalRoot.Sizes.MEDIUM}, props),
React.createElement(ModalHeader, null,
React.createElement(TextElement, {tag: "h1", size: TextElement.Sizes.SIZE_20, strong: true}, `${name} Settings`)
),
React.createElement(ModalContent, null,
React.createElement(ErrorBoundary, {}, child)
),
React.createElement(ModalFooter, null,
React.createElement(Button, {onClick: props.onClose}, Strings.Modals.done)
)
));
const options = {
className: "bd-addon-modal",
size: ModalRoot.Sizes.MEDIUM,
header: `${name} Settings`,
cancelText: null,
confirmText: Strings.Modals.done
};
return this.ModalActions.openModal(props => {
return React.createElement(ErrorBoundary, null, React.createElement(modal, props));
return React.createElement(ErrorBoundary, null, React.createElement(ConfirmationModal, Object.assign(options, props), child));
});
}
}

View File

@ -1,7 +1,14 @@
import {React, Strings, WebpackModules, DiscordClasses} from "modules";
import Extension from "./icons/extension";
import ThemeIcon from "./icons/theme";
import Divider from "./divider";
import {React, Strings, WebpackModules} from "modules";
import Extension from "../icons/extension";
import ThemeIcon from "../icons/theme";
import Divider from "../divider";
import Text from "../base/text";
import Header from "./header";
import Content from "./content";
import Flex from "../base/flex";
import ModalRoot from "./root";
import Footer from "./footer";
import Button from "../base/button";
const Parser = Object(WebpackModules.getByProps("defaultRules", "parse")).defaultRules;
@ -30,12 +37,12 @@ function AddonError({err, index}) {
{err.type == "plugin" ? <Extension /> : <ThemeIcon />}
</div>
<div className="bd-addon-error-header-inner">
<h3 className={`bd-addon-error-file ${DiscordClasses.Text.colorHeaderPrimary} ${DiscordClasses.Integrations.secondaryHeader} ${DiscordClasses.Text.size16}`}>{err.name}</h3>
<div className={`bd-addon-error-details ${DiscordClasses.Integrations.detailsWrapper}`}>
<svg className={DiscordClasses.Integrations.detailsIcon} aria-hidden="false" width="16" height="16" viewBox="0 0 12 12">
<Text tag="h3" size={Text.Sizes.SIZE_16} color={Text.Colors.HEADER_PRIMARY} strong={true}>{err.name}</Text>
<div className="bd-addon-error-details">
<svg className="bd-addon-error-details-icon" aria-hidden="false" width="16" height="16" viewBox="0 0 12 12">
<path fill="currentColor" d="M6 1C3.243 1 1 3.244 1 6c0 2.758 2.243 5 5 5s5-2.242 5-5c0-2.756-2.243-5-5-5zm0 2.376a.625.625 0 110 1.25.625.625 0 010-1.25zM7.5 8.5h-3v-1h1V6H5V5h1a.5.5 0 01.5.5v2h1v1z"></path>
</svg>
<div className={`${DiscordClasses.Text.colorHeaderSecondary} ${DiscordClasses.Text.size12}`}>{err.message}</div>
<Text color={Text.Colors.HEADER_SECONDARY} size={Text.Sizes.SIZE_12}>{err.message}</Text>
</div>
</div>
<svg className="bd-addon-error-expander" width="24" height="24" viewBox="0 0 24 24">
@ -51,7 +58,7 @@ function generateTab(id, errors) {
return {id, errors, name: Strings.Panels[id]};
}
export default function AddonErrorModal({pluginErrors, themeErrors}) {
export default function AddonErrorModal({transitionState, onClose, pluginErrors, themeErrors}) {
const tabs = useMemo(() => {
return [
pluginErrors.length && generateTab("plugins", pluginErrors),
@ -63,17 +70,22 @@ export default function AddonErrorModal({pluginErrors, themeErrors}) {
const switchToTab = useCallback((id) => setTab(id), []);
const selectedTab = tabs.find(e => e.id === tabId);
return <>
<div className={`bd-error-modal-header ${DiscordClasses.Modal.header} ${DiscordClasses.Modal.separator}`}>
<h4 className={`${DiscordClasses.Titles.defaultColor} ${DiscordClasses.Text.size14} ${DiscordClasses.Titles.h4} ${DiscordClasses.Margins.marginBottom8}`}>{Strings.Modals.addonErrors}</h4>
<div className="bd-tab-bar">
{tabs.map(tab => <div onClick={() => {switchToTab(tab.id);}} className={joinClassNames("bd-tab-item", tab.id === selectedTab.id && "selected")}>{tab.name}</div>)}
</div>
</div>
<div className={`bd-error-modal-content ${DiscordClasses.Modal.content} ${DiscordClasses.Scrollers.thin}`}>
<div className="bd-addon-errors">
{selectedTab.errors.map((error, index) => <AddonError index={index} err={error} />)}
</div>
</div>
</>;
return <ModalRoot transitionState={transitionState} className="bd-error-modal" size={ModalRoot.Sizes.MEDIUM}>
<Header className="bd-error-modal-header">
<Flex direction={Flex.Direction.VERTICAL}>
<Text tag="h1" size={Text.Sizes.SIZE_14} color={Text.Colors.HEADER_PRIMARY} strong={true} style={{"text-transform": "uppercase", "margin-bottom": "8px"}}>{Strings.Modals.addonErrors}</Text>
<div className="bd-tab-bar">
{tabs.map(tab => <div onClick={() => {switchToTab(tab.id);}} className={joinClassNames("bd-tab-item", tab.id === selectedTab.id && "selected")}>{tab.name}</div>)}
</div>
</Flex>
</Header>
<Content className="bd-error-modal-content">
<div className="bd-addon-errors">
{selectedTab.errors.map((error, index) => <AddonError index={index} err={error} />)}
</div>
</Content>
<Footer className="bd-error-modal-footer">
<Button onClick={onClose}>{Strings.Modals.okay}</Button>
</Footer>
</ModalRoot>;
}

View File

@ -47,7 +47,7 @@ export default function ChangelogModal({transitionState, footer, title, subtitle
const entry = changes[c];
const type = "bd-changelog-" + entry.type;
const margin = c == 0 ? " bd-changelog-first" : "";
items.push(<h1 className={`bd-changelog-title ${type}${margin}`}>entry.title</h1>);
items.push(<h1 className={`bd-changelog-title ${type}${margin}`}>{entry.title}</h1>);
if (entry.description) items.push(<p>{SimpleMarkdownExt.parseToReact(entry.description)}</p>);
const list = <ul>{entry.items.map(i => <li>{SimpleMarkdownExt.parseToReact(i)}</li>)}</ul>;
items.push(list);

View File

@ -1,4 +1,4 @@
import {React} from "modules";
import {React, Strings} from "modules";
import Root from "./root";
import Header from "./header";
import Footer from "./footer";
@ -10,7 +10,7 @@ import Button from "../base/button";
const {useRef, useEffect} = React;
export default function ConfirmationModal({transitionState, onClose, header, children, danger = false, onCancel = () => {}, onConfirm = () => {}, cancelText = "Cancel", confirmText = "Okay"}) {
export default function ConfirmationModal({transitionState, onClose, className, size = Root.Sizes.SMALL, header, children, danger = false, onCancel = () => {}, onConfirm = () => {}, cancelText = Strings.Modals.cancel, confirmText = Strings.Modals.okay}) {
useEffect(() => {
setTimeout(() => buttonRef?.current?.focus?.(), 0);
@ -19,8 +19,10 @@ export default function ConfirmationModal({transitionState, onClose, header, chi
const buttonRef = useRef(null);
return <Root transitionState={transitionState} size={Root.Sizes.SMALL}>
<Header><Text tag="h1" size={Text.Sizes.SIZE_20} color={Text.Colors.HEADER_PRIMARY} strong={true}>{header}</Text></Header>
return <Root transitionState={transitionState} size={size} className={className}>
<Header>
<Text tag="h1" size={Text.Sizes.SIZE_20} color={Text.Colors.HEADER_PRIMARY} strong={true}>{header}</Text>
</Header>
<Content>{children}</Content>
<Footer>
{confirmText && <Button

View File

@ -2,7 +2,7 @@ import {React, Utilities} from "modules";
import Flex from "../base/flex";
export default function Header({id, className, children}) {
export default function Footer({id, className, children}) {
return <Flex
id={id}
className={Utilities.className("bd-modal-footer", className)}